From 5b7ab8061a1e4ef87c4d4963818850a674453ba8 Mon Sep 17 00:00:00 2001 From: Andres Suarez Date: Tue, 20 Dec 2022 10:22:44 -0500 Subject: [PATCH 001/362] Stabilize path_as_mut_os_str Closes #105021 --- library/std/src/path.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/library/std/src/path.rs b/library/std/src/path.rs index 73b5056e9326..2cb7b3de16a8 100644 --- a/library/std/src/path.rs +++ b/library/std/src/path.rs @@ -1468,7 +1468,6 @@ impl PathBuf { /// # Examples /// /// ``` - /// #![feature(path_as_mut_os_str)] /// use std::path::{Path, PathBuf}; /// /// let mut path = PathBuf::from("/foo"); @@ -1480,7 +1479,7 @@ impl PathBuf { /// path.as_mut_os_string().push("baz"); /// assert_eq!(path, Path::new("/foo/barbaz")); /// ``` - #[unstable(feature = "path_as_mut_os_str", issue = "105021")] + #[stable(feature = "path_as_mut_os_str", since = "CURRENT_RUSTC_VERSION")] #[must_use] #[inline] pub fn as_mut_os_string(&mut self) -> &mut OsString { @@ -2036,7 +2035,6 @@ impl Path { /// # Examples /// /// ``` - /// #![feature(path_as_mut_os_str)] /// use std::path::{Path, PathBuf}; /// /// let mut path = PathBuf::from("Foo.TXT"); @@ -2046,7 +2044,7 @@ impl Path { /// path.as_mut_os_str().make_ascii_lowercase(); /// assert_eq!(path, Path::new("foo.txt")); /// ``` - #[unstable(feature = "path_as_mut_os_str", issue = "105021")] + #[stable(feature = "path_as_mut_os_str", since = "CURRENT_RUSTC_VERSION")] #[must_use] #[inline] pub fn as_mut_os_str(&mut self) -> &mut OsStr { From fc8477798e3c68130317ffd32adcfb75f3ea9535 Mon Sep 17 00:00:00 2001 From: Caio Date: Mon, 9 Jan 2023 09:30:00 -0300 Subject: [PATCH 002/362] Stabilize `nonzero_min_max` --- library/core/src/num/nonzero.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/core/src/num/nonzero.rs b/library/core/src/num/nonzero.rs index fbda8f82b1bd..bbb3b6453d96 100644 --- a/library/core/src/num/nonzero.rs +++ b/library/core/src/num/nonzero.rs @@ -1152,7 +1152,7 @@ macro_rules! nonzero_min_max_unsigned { #[doc = concat!("# use std::num::", stringify!($Ty), ";")] #[doc = concat!("assert_eq!(", stringify!($Ty), "::MIN.get(), 1", stringify!($Int), ");")] /// ``` - #[unstable(feature = "nonzero_min_max", issue = "89065")] + #[stable(feature = "nonzero_min_max", since = "CURRENT_RUSTC_VERSION")] pub const MIN: Self = Self::new(1).unwrap(); /// The largest value that can be represented by this non-zero @@ -1167,7 +1167,7 @@ macro_rules! nonzero_min_max_unsigned { #[doc = concat!("# use std::num::", stringify!($Ty), ";")] #[doc = concat!("assert_eq!(", stringify!($Ty), "::MAX.get(), ", stringify!($Int), "::MAX);")] /// ``` - #[unstable(feature = "nonzero_min_max", issue = "89065")] + #[stable(feature = "nonzero_min_max", since = "CURRENT_RUSTC_VERSION")] pub const MAX: Self = Self::new(<$Int>::MAX).unwrap(); } )+ @@ -1194,7 +1194,7 @@ macro_rules! nonzero_min_max_signed { #[doc = concat!("# use std::num::", stringify!($Ty), ";")] #[doc = concat!("assert_eq!(", stringify!($Ty), "::MIN.get(), ", stringify!($Int), "::MIN);")] /// ``` - #[unstable(feature = "nonzero_min_max", issue = "89065")] + #[stable(feature = "nonzero_min_max", since = "CURRENT_RUSTC_VERSION")] pub const MIN: Self = Self::new(<$Int>::MIN).unwrap(); /// The largest value that can be represented by this non-zero @@ -1213,7 +1213,7 @@ macro_rules! nonzero_min_max_signed { #[doc = concat!("# use std::num::", stringify!($Ty), ";")] #[doc = concat!("assert_eq!(", stringify!($Ty), "::MAX.get(), ", stringify!($Int), "::MAX);")] /// ``` - #[unstable(feature = "nonzero_min_max", issue = "89065")] + #[stable(feature = "nonzero_min_max", since = "CURRENT_RUSTC_VERSION")] pub const MAX: Self = Self::new(<$Int>::MAX).unwrap(); } )+ From d1b7681e1e9aa43a4b4abc9c04c4e6f62fbdb9c6 Mon Sep 17 00:00:00 2001 From: Caio Date: Mon, 9 Jan 2023 10:01:38 -0300 Subject: [PATCH 003/362] Remove unstable feature --- library/core/src/num/nonzero.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/library/core/src/num/nonzero.rs b/library/core/src/num/nonzero.rs index bbb3b6453d96..06d22d84aedc 100644 --- a/library/core/src/num/nonzero.rs +++ b/library/core/src/num/nonzero.rs @@ -1147,8 +1147,6 @@ macro_rules! nonzero_min_max_unsigned { /// # Examples /// /// ``` - /// #![feature(nonzero_min_max)] - /// #[doc = concat!("# use std::num::", stringify!($Ty), ";")] #[doc = concat!("assert_eq!(", stringify!($Ty), "::MIN.get(), 1", stringify!($Int), ");")] /// ``` @@ -1162,8 +1160,6 @@ macro_rules! nonzero_min_max_unsigned { /// # Examples /// /// ``` - /// #![feature(nonzero_min_max)] - /// #[doc = concat!("# use std::num::", stringify!($Ty), ";")] #[doc = concat!("assert_eq!(", stringify!($Ty), "::MAX.get(), ", stringify!($Int), "::MAX);")] /// ``` @@ -1189,8 +1185,6 @@ macro_rules! nonzero_min_max_signed { /// # Examples /// /// ``` - /// #![feature(nonzero_min_max)] - /// #[doc = concat!("# use std::num::", stringify!($Ty), ";")] #[doc = concat!("assert_eq!(", stringify!($Ty), "::MIN.get(), ", stringify!($Int), "::MIN);")] /// ``` @@ -1208,8 +1202,6 @@ macro_rules! nonzero_min_max_signed { /// # Examples /// /// ``` - /// #![feature(nonzero_min_max)] - /// #[doc = concat!("# use std::num::", stringify!($Ty), ";")] #[doc = concat!("assert_eq!(", stringify!($Ty), "::MAX.get(), ", stringify!($Int), "::MAX);")] /// ``` From 41856b0a0f0a612c1e64088baed5389d82f93157 Mon Sep 17 00:00:00 2001 From: Ezra Shaw Date: Sat, 14 Jan 2023 23:00:17 +1300 Subject: [PATCH 004/362] allow negative numeric literals in `concat!` --- compiler/rustc_builtin_macros/src/concat.rs | 15 ++++++++++++++- tests/ui/macros/bad-concat.stderr | 2 +- tests/ui/macros/concat.stderr | 4 ++-- tests/ui/macros/issue-106837.rs | 10 ++++++++++ tests/ui/macros/issue-106837.stderr | 18 ++++++++++++++++++ 5 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 tests/ui/macros/issue-106837.rs create mode 100644 tests/ui/macros/issue-106837.stderr diff --git a/compiler/rustc_builtin_macros/src/concat.rs b/compiler/rustc_builtin_macros/src/concat.rs index 7da9bdc38a2a..36682bbe0708 100644 --- a/compiler/rustc_builtin_macros/src/concat.rs +++ b/compiler/rustc_builtin_macros/src/concat.rs @@ -42,6 +42,18 @@ pub fn expand_concat( has_errors = true; } }, + // We also want to allow negative numeric literals. + ast::ExprKind::Unary(ast::UnOp::Neg, ref expr) if let ast::ExprKind::Lit(token_lit) = expr.kind => { + match ast::LitKind::from_token_lit(token_lit) { + Ok(ast::LitKind::Int(i, _)) => accumulator.push_str(&format!("-{i}")), + Ok(ast::LitKind::Float(f, _)) => accumulator.push_str(&format!("-{f}")), + Err(err) => { + report_lit_error(&cx.sess.parse_sess, err, token_lit, e.span); + has_errors = true; + } + _ => missing_literal.push(e.span), + } + } ast::ExprKind::IncludedBytes(..) => { cx.span_err(e.span, "cannot concatenate a byte string literal") } @@ -53,9 +65,10 @@ pub fn expand_concat( } } } + if !missing_literal.is_empty() { let mut err = cx.struct_span_err(missing_literal, "expected a literal"); - err.note("only literals (like `\"foo\"`, `42` and `3.14`) can be passed to `concat!()`"); + err.note("only literals (like `\"foo\"`, `-42` and `3.14`) can be passed to `concat!()`"); err.emit(); return DummyResult::any(sp); } else if has_errors { diff --git a/tests/ui/macros/bad-concat.stderr b/tests/ui/macros/bad-concat.stderr index 4316fd312c73..d67f3c33d36e 100644 --- a/tests/ui/macros/bad-concat.stderr +++ b/tests/ui/macros/bad-concat.stderr @@ -4,7 +4,7 @@ error: expected a literal LL | let _ = concat!(x, y, z, "bar"); | ^ ^ ^ | - = note: only literals (like `"foo"`, `42` and `3.14`) can be passed to `concat!()` + = note: only literals (like `"foo"`, `-42` and `3.14`) can be passed to `concat!()` error: aborting due to previous error diff --git a/tests/ui/macros/concat.stderr b/tests/ui/macros/concat.stderr index 61fb9de1ef96..d65d9354454c 100644 --- a/tests/ui/macros/concat.stderr +++ b/tests/ui/macros/concat.stderr @@ -16,7 +16,7 @@ error: expected a literal LL | concat!(foo); | ^^^ | - = note: only literals (like `"foo"`, `42` and `3.14`) can be passed to `concat!()` + = note: only literals (like `"foo"`, `-42` and `3.14`) can be passed to `concat!()` error: expected a literal --> $DIR/concat.rs:5:13 @@ -24,7 +24,7 @@ error: expected a literal LL | concat!(foo()); | ^^^^^ | - = note: only literals (like `"foo"`, `42` and `3.14`) can be passed to `concat!()` + = note: only literals (like `"foo"`, `-42` and `3.14`) can be passed to `concat!()` error: aborting due to 4 previous errors diff --git a/tests/ui/macros/issue-106837.rs b/tests/ui/macros/issue-106837.rs new file mode 100644 index 000000000000..7bbd3d68a900 --- /dev/null +++ b/tests/ui/macros/issue-106837.rs @@ -0,0 +1,10 @@ +fn main() { + concat!(-42); + concat!(-3.14); + + concat!(-"hello"); + //~^ ERROR expected a literal + + concat!(--1); + //~^ ERROR expected a literal +} diff --git a/tests/ui/macros/issue-106837.stderr b/tests/ui/macros/issue-106837.stderr new file mode 100644 index 000000000000..998d6c5eb6f2 --- /dev/null +++ b/tests/ui/macros/issue-106837.stderr @@ -0,0 +1,18 @@ +error: expected a literal + --> $DIR/issue-106837.rs:5:13 + | +LL | concat!(-"hello"); + | ^^^^^^^^ + | + = note: only literals (like `"foo"`, `-42` and `3.14`) can be passed to `concat!()` + +error: expected a literal + --> $DIR/issue-106837.rs:8:13 + | +LL | concat!(--1); + | ^^^ + | + = note: only literals (like `"foo"`, `-42` and `3.14`) can be passed to `concat!()` + +error: aborting due to 2 previous errors + From 660d985d12bd66dde21490fefb31c9b7fb22a67e Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sun, 15 Jan 2023 22:39:43 +0100 Subject: [PATCH 005/362] Guarantee the memory layout of `Cell` --- library/core/src/cell.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/library/core/src/cell.rs b/library/core/src/cell.rs index 129213fde749..c65c30e566a1 100644 --- a/library/core/src/cell.rs +++ b/library/core/src/cell.rs @@ -209,6 +209,12 @@ pub use once::OnceCell; /// A mutable memory location. /// +/// # Memory layout +/// +/// `Cell` has the same [memory layout and caveats as +/// `UnsafeCell`](UnsafeCell#memory-layout). In particular, this means that +/// `Cell` has the same in-memory representation as its inner type `T`. +/// /// # Examples /// /// In this example, you can see that `Cell` enables mutation inside an From 52f7a218fb496aefb2364ccc508556c45df47e7f Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 16 Dec 2022 04:20:34 +0000 Subject: [PATCH 006/362] Relax ordering rules for `asm!` operands The `asm!` and `global_asm!` macros require their operands to appear strictly in the following order: - Template strings - Positional operands - Named operands - Explicit register operands - `clobber_abi` - `options` This is overly strict and can be inconvienent when building complex `asm!` statements with macros. This PR relaxes the ordering requirements as follows: - Template strings must still come before all other operands. - Positional operands must still come before named and explicit register operands. - Named and explicit register operands can be freely mixed. - `options` and `clobber_abi` can appear in any position. --- compiler/rustc_builtin_macros/src/asm.rs | 35 +--- tests/ui/asm/aarch64/parse-error.rs | 23 +-- tests/ui/asm/aarch64/parse-error.stderr | 169 ++++++----------- .../bad-template.aarch64_mirunsafeck.stderr | 5 + .../bad-template.aarch64_thirunsafeck.stderr | 5 + .../bad-template.x86_64_mirunsafeck.stderr | 5 + .../bad-template.x86_64_thirunsafeck.stderr | 5 + tests/ui/asm/x86_64/parse-error.rs | 23 +-- tests/ui/asm/x86_64/parse-error.stderr | 179 ++++++------------ 9 files changed, 155 insertions(+), 294 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/asm.rs b/compiler/rustc_builtin_macros/src/asm.rs index 925392b500ad..7f7f27837ea1 100644 --- a/compiler/rustc_builtin_macros/src/asm.rs +++ b/compiler/rustc_builtin_macros/src/asm.rs @@ -203,17 +203,6 @@ pub fn parse_asm_args<'a>( // Validate the order of named, positional & explicit register operands and // clobber_abi/options. We do this at the end once we have the full span // of the argument available. - if !args.options_spans.is_empty() { - diag.struct_span_err(span, "arguments are not allowed after options") - .span_labels(args.options_spans.clone(), "previous options") - .span_label(span, "argument") - .emit(); - } else if let Some((_, abi_span)) = args.clobber_abis.last() { - diag.struct_span_err(span, "arguments are not allowed after clobber_abi") - .span_label(*abi_span, "clobber_abi") - .span_label(span, "argument") - .emit(); - } if explicit_reg { if name.is_some() { diag.struct_span_err(span, "explicit register arguments cannot have names").emit(); @@ -227,17 +216,6 @@ pub fn parse_asm_args<'a>( .emit(); continue; } - if !args.reg_args.is_empty() { - let mut err = diag.struct_span_err( - span, - "named arguments cannot follow explicit register arguments", - ); - err.span_label(span, "named argument"); - for pos in &args.reg_args { - err.span_label(args.operands[*pos].1, "explicit register argument"); - } - err.emit(); - } args.named_args.insert(name, slot); } else { if !args.named_args.is_empty() || !args.reg_args.is_empty() { @@ -478,15 +456,6 @@ fn parse_clobber_abi<'a>(p: &mut Parser<'a>, args: &mut AsmArgs) -> PResult<'a, let full_span = span_start.to(p.prev_token.span); - if !args.options_spans.is_empty() { - let mut err = p - .sess - .span_diagnostic - .struct_span_err(full_span, "clobber_abi is not allowed after options"); - err.span_labels(args.options_spans.clone(), "options"); - return Err(err); - } - match &new_abis[..] { // should have errored above during parsing [] => unreachable!(), @@ -699,6 +668,10 @@ fn expand_preparsed_asm(ecx: &mut ExtCtxt<'_>, args: AsmArgs) -> Option $DIR/parse-error.rs:39:31 - | -LL | asm!("{}", options(), const foo); - | --------- ^^^^^^^^^ argument - | | - | previous options - error: expected string literal - --> $DIR/parse-error.rs:42:30 + --> $DIR/parse-error.rs:41:30 | LL | asm!("", clobber_abi(foo)); | ^^^ not a string literal error: expected one of `)` or `,`, found `foo` - --> $DIR/parse-error.rs:44:34 + --> $DIR/parse-error.rs:43:34 | LL | asm!("", clobber_abi("C" foo)); | ^^^ expected one of `)` or `,` error: expected string literal - --> $DIR/parse-error.rs:46:35 + --> $DIR/parse-error.rs:45:35 | LL | asm!("", clobber_abi("C", foo)); | ^^^ not a string literal -error: arguments are not allowed after clobber_abi - --> $DIR/parse-error.rs:48:38 - | -LL | asm!("{}", clobber_abi("C"), const foo); - | ---------------- ^^^^^^^^^ argument - | | - | clobber_abi - -error: clobber_abi is not allowed after options - --> $DIR/parse-error.rs:51:29 - | -LL | asm!("", options(), clobber_abi("C")); - | --------- ^^^^^^^^^^^^^^^^ - | | - | options - -error: clobber_abi is not allowed after options - --> $DIR/parse-error.rs:53:31 - | -LL | asm!("{}", options(), clobber_abi("C"), const foo); - | --------- ^^^^^^^^^^^^^^^^ - | | - | options - error: duplicate argument named `a` - --> $DIR/parse-error.rs:55:36 + --> $DIR/parse-error.rs:52:36 | LL | asm!("{a}", a = const foo, a = const bar); | ------------- ^^^^^^^^^^^^^ duplicate argument @@ -141,7 +109,7 @@ LL | asm!("{a}", a = const foo, a = const bar); | previously here error: argument never used - --> $DIR/parse-error.rs:55:36 + --> $DIR/parse-error.rs:52:36 | LL | asm!("{a}", a = const foo, a = const bar); | ^^^^^^^^^^^^^ argument never used @@ -149,29 +117,13 @@ LL | asm!("{a}", a = const foo, a = const bar); = help: if this argument is intentionally unused, consider using it in an asm comment: `"/* {1} */"` error: explicit register arguments cannot have names - --> $DIR/parse-error.rs:60:18 + --> $DIR/parse-error.rs:57:18 | LL | asm!("", a = in("x0") foo); | ^^^^^^^^^^^^^^^^ -error: named arguments cannot follow explicit register arguments - --> $DIR/parse-error.rs:62:35 - | -LL | asm!("{a}", in("x0") foo, a = const bar); - | ------------ ^^^^^^^^^^^^^ named argument - | | - | explicit register argument - -error: named arguments cannot follow explicit register arguments - --> $DIR/parse-error.rs:65:35 - | -LL | asm!("{a}", in("x0") foo, a = const bar); - | ------------ ^^^^^^^^^^^^^ named argument - | | - | explicit register argument - error: positional arguments cannot follow named arguments or explicit register arguments - --> $DIR/parse-error.rs:68:35 + --> $DIR/parse-error.rs:63:35 | LL | asm!("{1}", in("x0") foo, const bar); | ------------ ^^^^^^^^^ positional argument @@ -179,19 +131,19 @@ LL | asm!("{1}", in("x0") foo, const bar); | explicit register argument error: expected one of `clobber_abi`, `const`, `in`, `inlateout`, `inout`, `lateout`, `options`, `out`, or `sym`, found `""` - --> $DIR/parse-error.rs:71:29 + --> $DIR/parse-error.rs:66:29 | LL | asm!("", options(), ""); | ^^ expected one of 9 possible tokens error: expected one of `clobber_abi`, `const`, `in`, `inlateout`, `inout`, `lateout`, `options`, `out`, or `sym`, found `"{}"` - --> $DIR/parse-error.rs:73:33 + --> $DIR/parse-error.rs:68:33 | LL | asm!("{}", in(reg) foo, "{}", out(reg) foo); | ^^^^ expected one of 9 possible tokens error: asm template must be a string literal - --> $DIR/parse-error.rs:75:14 + --> $DIR/parse-error.rs:70:14 | LL | asm!(format!("{{{}}}", 0), in(reg) foo); | ^^^^^^^^^^^^^^^^^^^^ @@ -199,7 +151,7 @@ LL | asm!(format!("{{{}}}", 0), in(reg) foo); = note: this error originates in the macro `format` (in Nightly builds, run with -Z macro-backtrace for more info) error: asm template must be a string literal - --> $DIR/parse-error.rs:77:21 + --> $DIR/parse-error.rs:72:21 | LL | asm!("{1}", format!("{{{}}}", 0), in(reg) foo, out(reg) bar); | ^^^^^^^^^^^^^^^^^^^^ @@ -207,135 +159,115 @@ LL | asm!("{1}", format!("{{{}}}", 0), in(reg) foo, out(reg) bar); = note: this error originates in the macro `format` (in Nightly builds, run with -Z macro-backtrace for more info) error: _ cannot be used for input operands - --> $DIR/parse-error.rs:79:28 + --> $DIR/parse-error.rs:74:28 | LL | asm!("{}", in(reg) _); | ^ error: _ cannot be used for input operands - --> $DIR/parse-error.rs:81:31 + --> $DIR/parse-error.rs:76:31 | LL | asm!("{}", inout(reg) _); | ^ error: _ cannot be used for input operands - --> $DIR/parse-error.rs:83:35 + --> $DIR/parse-error.rs:78:35 | LL | asm!("{}", inlateout(reg) _); | ^ error: requires at least a template string argument - --> $DIR/parse-error.rs:90:1 + --> $DIR/parse-error.rs:85:1 | LL | global_asm!(); | ^^^^^^^^^^^^^ error: asm template must be a string literal - --> $DIR/parse-error.rs:92:13 + --> $DIR/parse-error.rs:87:13 | LL | global_asm!(FOO); | ^^^ error: expected token: `,` - --> $DIR/parse-error.rs:94:18 + --> $DIR/parse-error.rs:89:18 | LL | global_asm!("{}" FOO); | ^^^ expected `,` error: expected operand, options, or additional template string - --> $DIR/parse-error.rs:96:19 + --> $DIR/parse-error.rs:91:19 | LL | global_asm!("{}", FOO); | ^^^ expected operand, options, or additional template string error: expected expression, found end of macro arguments - --> $DIR/parse-error.rs:98:24 + --> $DIR/parse-error.rs:93:24 | LL | global_asm!("{}", const); | ^ expected expression error: expected one of `,`, `.`, `?`, or an operator, found `FOO` - --> $DIR/parse-error.rs:100:30 + --> $DIR/parse-error.rs:95:30 | LL | global_asm!("{}", const(reg) FOO); | ^^^ expected one of `,`, `.`, `?`, or an operator error: expected one of `)`, `att_syntax`, or `raw`, found `FOO` - --> $DIR/parse-error.rs:102:25 + --> $DIR/parse-error.rs:97:25 | LL | global_asm!("", options(FOO)); | ^^^ expected one of `)`, `att_syntax`, or `raw` error: expected one of `)`, `att_syntax`, or `raw`, found `nomem` - --> $DIR/parse-error.rs:104:25 + --> $DIR/parse-error.rs:99:25 | LL | global_asm!("", options(nomem FOO)); | ^^^^^ expected one of `)`, `att_syntax`, or `raw` error: expected one of `)`, `att_syntax`, or `raw`, found `nomem` - --> $DIR/parse-error.rs:106:25 + --> $DIR/parse-error.rs:101:25 | LL | global_asm!("", options(nomem, FOO)); | ^^^^^ expected one of `)`, `att_syntax`, or `raw` -error: arguments are not allowed after options - --> $DIR/parse-error.rs:108:30 - | -LL | global_asm!("{}", options(), const FOO); - | --------- ^^^^^^^^^ argument - | | - | previous options - error: expected string literal - --> $DIR/parse-error.rs:110:29 + --> $DIR/parse-error.rs:104:29 | LL | global_asm!("", clobber_abi(FOO)); | ^^^ not a string literal error: expected one of `)` or `,`, found `FOO` - --> $DIR/parse-error.rs:112:33 + --> $DIR/parse-error.rs:106:33 | LL | global_asm!("", clobber_abi("C" FOO)); | ^^^ expected one of `)` or `,` error: expected string literal - --> $DIR/parse-error.rs:114:34 + --> $DIR/parse-error.rs:108:34 | LL | global_asm!("", clobber_abi("C", FOO)); | ^^^ not a string literal -error: arguments are not allowed after clobber_abi - --> $DIR/parse-error.rs:116:37 - | -LL | global_asm!("{}", clobber_abi("C"), const FOO); - | ---------------- ^^^^^^^^^ argument - | | - | clobber_abi - error: `clobber_abi` cannot be used with `global_asm!` - --> $DIR/parse-error.rs:116:19 + --> $DIR/parse-error.rs:110:19 | LL | global_asm!("{}", clobber_abi("C"), const FOO); | ^^^^^^^^^^^^^^^^ -error: clobber_abi is not allowed after options - --> $DIR/parse-error.rs:119:28 +error: `clobber_abi` cannot be used with `global_asm!` + --> $DIR/parse-error.rs:112:28 | LL | global_asm!("", options(), clobber_abi("C")); - | --------- ^^^^^^^^^^^^^^^^ - | | - | options + | ^^^^^^^^^^^^^^^^ -error: clobber_abi is not allowed after options - --> $DIR/parse-error.rs:121:30 +error: `clobber_abi` cannot be used with `global_asm!` + --> $DIR/parse-error.rs:114:30 | LL | global_asm!("{}", options(), clobber_abi("C"), const FOO); - | --------- ^^^^^^^^^^^^^^^^ - | | - | options + | ^^^^^^^^^^^^^^^^ error: duplicate argument named `a` - --> $DIR/parse-error.rs:123:35 + --> $DIR/parse-error.rs:116:35 | LL | global_asm!("{a}", a = const FOO, a = const BAR); | ------------- ^^^^^^^^^^^^^ duplicate argument @@ -343,7 +275,7 @@ LL | global_asm!("{a}", a = const FOO, a = const BAR); | previously here error: argument never used - --> $DIR/parse-error.rs:123:35 + --> $DIR/parse-error.rs:116:35 | LL | global_asm!("{a}", a = const FOO, a = const BAR); | ^^^^^^^^^^^^^ argument never used @@ -351,19 +283,19 @@ LL | global_asm!("{a}", a = const FOO, a = const BAR); = help: if this argument is intentionally unused, consider using it in an asm comment: `"/* {1} */"` error: expected one of `clobber_abi`, `const`, `options`, or `sym`, found `""` - --> $DIR/parse-error.rs:126:28 + --> $DIR/parse-error.rs:119:28 | LL | global_asm!("", options(), ""); | ^^ expected one of `clobber_abi`, `const`, `options`, or `sym` error: expected one of `clobber_abi`, `const`, `options`, or `sym`, found `"{}"` - --> $DIR/parse-error.rs:128:30 + --> $DIR/parse-error.rs:121:30 | LL | global_asm!("{}", const FOO, "{}", const FOO); | ^^^^ expected one of `clobber_abi`, `const`, `options`, or `sym` error: asm template must be a string literal - --> $DIR/parse-error.rs:130:13 + --> $DIR/parse-error.rs:123:13 | LL | global_asm!(format!("{{{}}}", 0), const FOO); | ^^^^^^^^^^^^^^^^^^^^ @@ -371,7 +303,7 @@ LL | global_asm!(format!("{{{}}}", 0), const FOO); = note: this error originates in the macro `format` (in Nightly builds, run with -Z macro-backtrace for more info) error: asm template must be a string literal - --> $DIR/parse-error.rs:132:20 + --> $DIR/parse-error.rs:125:20 | LL | global_asm!("{1}", format!("{{{}}}", 0), const FOO, const BAR); | ^^^^^^^^^^^^^^^^^^^^ @@ -388,7 +320,7 @@ LL | asm!("{}", options(), const foo); | ^^^ non-constant value error[E0435]: attempt to use a non-constant value in a constant - --> $DIR/parse-error.rs:48:44 + --> $DIR/parse-error.rs:47:44 | LL | let mut foo = 0; | ----------- help: consider using `const` instead of `let`: `const foo` @@ -397,7 +329,16 @@ LL | asm!("{}", clobber_abi("C"), const foo); | ^^^ non-constant value error[E0435]: attempt to use a non-constant value in a constant - --> $DIR/parse-error.rs:55:31 + --> $DIR/parse-error.rs:50:55 + | +LL | let mut foo = 0; + | ----------- help: consider using `const` instead of `let`: `const foo` +... +LL | asm!("{}", options(), clobber_abi("C"), const foo); + | ^^^ non-constant value + +error[E0435]: attempt to use a non-constant value in a constant + --> $DIR/parse-error.rs:52:31 | LL | let mut foo = 0; | ----------- help: consider using `const` instead of `let`: `const foo` @@ -406,7 +347,7 @@ LL | asm!("{a}", a = const foo, a = const bar); | ^^^ non-constant value error[E0435]: attempt to use a non-constant value in a constant - --> $DIR/parse-error.rs:55:46 + --> $DIR/parse-error.rs:52:46 | LL | let mut bar = 0; | ----------- help: consider using `const` instead of `let`: `const bar` @@ -415,7 +356,7 @@ LL | asm!("{a}", a = const foo, a = const bar); | ^^^ non-constant value error[E0435]: attempt to use a non-constant value in a constant - --> $DIR/parse-error.rs:62:45 + --> $DIR/parse-error.rs:59:45 | LL | let mut bar = 0; | ----------- help: consider using `const` instead of `let`: `const bar` @@ -424,7 +365,7 @@ LL | asm!("{a}", in("x0") foo, a = const bar); | ^^^ non-constant value error[E0435]: attempt to use a non-constant value in a constant - --> $DIR/parse-error.rs:65:45 + --> $DIR/parse-error.rs:61:45 | LL | let mut bar = 0; | ----------- help: consider using `const` instead of `let`: `const bar` @@ -433,7 +374,7 @@ LL | asm!("{a}", in("x0") foo, a = const bar); | ^^^ non-constant value error[E0435]: attempt to use a non-constant value in a constant - --> $DIR/parse-error.rs:68:41 + --> $DIR/parse-error.rs:63:41 | LL | let mut bar = 0; | ----------- help: consider using `const` instead of `let`: `const bar` @@ -441,6 +382,6 @@ LL | let mut bar = 0; LL | asm!("{1}", in("x0") foo, const bar); | ^^^ non-constant value -error: aborting due to 64 previous errors +error: aborting due to 57 previous errors For more information about this error, try `rustc --explain E0435`. diff --git a/tests/ui/asm/bad-template.aarch64_mirunsafeck.stderr b/tests/ui/asm/bad-template.aarch64_mirunsafeck.stderr index bb6a222b22ee..b16f9a06c2ab 100644 --- a/tests/ui/asm/bad-template.aarch64_mirunsafeck.stderr +++ b/tests/ui/asm/bad-template.aarch64_mirunsafeck.stderr @@ -81,6 +81,11 @@ note: explicit register arguments cannot be used in the asm template | LL | asm!("{}", in("x0") foo); | ^^^^^^^^^^^^ +help: use the register name directly in the assembly code + --> $DIR/bad-template.rs:48:20 + | +LL | asm!("{}", in("x0") foo); + | ^^^^^^^^^^^^ error: asm template modifier must be a single character --> $DIR/bad-template.rs:50:17 diff --git a/tests/ui/asm/bad-template.aarch64_thirunsafeck.stderr b/tests/ui/asm/bad-template.aarch64_thirunsafeck.stderr index bb6a222b22ee..b16f9a06c2ab 100644 --- a/tests/ui/asm/bad-template.aarch64_thirunsafeck.stderr +++ b/tests/ui/asm/bad-template.aarch64_thirunsafeck.stderr @@ -81,6 +81,11 @@ note: explicit register arguments cannot be used in the asm template | LL | asm!("{}", in("x0") foo); | ^^^^^^^^^^^^ +help: use the register name directly in the assembly code + --> $DIR/bad-template.rs:48:20 + | +LL | asm!("{}", in("x0") foo); + | ^^^^^^^^^^^^ error: asm template modifier must be a single character --> $DIR/bad-template.rs:50:17 diff --git a/tests/ui/asm/bad-template.x86_64_mirunsafeck.stderr b/tests/ui/asm/bad-template.x86_64_mirunsafeck.stderr index 903b5e959f3e..41ac37c33c2e 100644 --- a/tests/ui/asm/bad-template.x86_64_mirunsafeck.stderr +++ b/tests/ui/asm/bad-template.x86_64_mirunsafeck.stderr @@ -81,6 +81,11 @@ note: explicit register arguments cannot be used in the asm template | LL | asm!("{}", in("eax") foo); | ^^^^^^^^^^^^^ +help: use the register name directly in the assembly code + --> $DIR/bad-template.rs:45:20 + | +LL | asm!("{}", in("eax") foo); + | ^^^^^^^^^^^^^ error: asm template modifier must be a single character --> $DIR/bad-template.rs:50:17 diff --git a/tests/ui/asm/bad-template.x86_64_thirunsafeck.stderr b/tests/ui/asm/bad-template.x86_64_thirunsafeck.stderr index 903b5e959f3e..41ac37c33c2e 100644 --- a/tests/ui/asm/bad-template.x86_64_thirunsafeck.stderr +++ b/tests/ui/asm/bad-template.x86_64_thirunsafeck.stderr @@ -81,6 +81,11 @@ note: explicit register arguments cannot be used in the asm template | LL | asm!("{}", in("eax") foo); | ^^^^^^^^^^^^^ +help: use the register name directly in the assembly code + --> $DIR/bad-template.rs:45:20 + | +LL | asm!("{}", in("eax") foo); + | ^^^^^^^^^^^^^ error: asm template modifier must be a single character --> $DIR/bad-template.rs:50:17 diff --git a/tests/ui/asm/x86_64/parse-error.rs b/tests/ui/asm/x86_64/parse-error.rs index 9aeb6b2853fb..2e714d464ae7 100644 --- a/tests/ui/asm/x86_64/parse-error.rs +++ b/tests/ui/asm/x86_64/parse-error.rs @@ -37,8 +37,7 @@ fn main() { asm!("", options(nomem, foo)); //~^ ERROR expected one of asm!("{}", options(), const foo); - //~^ ERROR arguments are not allowed after options - //~^^ ERROR attempt to use a non-constant value in a constant + //~^ ERROR attempt to use a non-constant value in a constant asm!("", clobber_abi()); //~^ ERROR at least one abi must be provided asm!("", clobber_abi(foo)); @@ -48,12 +47,10 @@ fn main() { asm!("", clobber_abi("C", foo)); //~^ ERROR expected string literal asm!("{}", clobber_abi("C"), const foo); - //~^ ERROR arguments are not allowed after clobber_abi - //~^^ ERROR attempt to use a non-constant value in a constant + //~^ ERROR attempt to use a non-constant value in a constant asm!("", options(), clobber_abi("C")); - //~^ ERROR clobber_abi is not allowed after options asm!("{}", options(), clobber_abi("C"), const foo); - //~^ ERROR clobber_abi is not allowed after options + //~^ ERROR attempt to use a non-constant value in a constant asm!("{a}", a = const foo, a = const bar); //~^ ERROR duplicate argument named `a` //~^^ ERROR argument never used @@ -62,11 +59,9 @@ fn main() { asm!("", a = in("eax") foo); //~^ ERROR explicit register arguments cannot have names asm!("{a}", in("eax") foo, a = const bar); - //~^ ERROR named arguments cannot follow explicit register arguments - //~^^ ERROR attempt to use a non-constant value in a constant + //~^ ERROR attempt to use a non-constant value in a constant asm!("{a}", in("eax") foo, a = const bar); - //~^ ERROR named arguments cannot follow explicit register arguments - //~^^ ERROR attempt to use a non-constant value in a constant + //~^ ERROR attempt to use a non-constant value in a constant asm!("{1}", in("eax") foo, const bar); //~^ ERROR positional arguments cannot follow named arguments or explicit register arguments //~^^ ERROR attempt to use a non-constant value in a constant @@ -108,7 +103,6 @@ global_asm!("", options(nomem FOO)); global_asm!("", options(nomem, FOO)); //~^ ERROR expected one of global_asm!("{}", options(), const FOO); -//~^ ERROR arguments are not allowed after options global_asm!("", clobber_abi(FOO)); //~^ ERROR expected string literal global_asm!("", clobber_abi("C" FOO)); @@ -116,12 +110,11 @@ global_asm!("", clobber_abi("C" FOO)); global_asm!("", clobber_abi("C", FOO)); //~^ ERROR expected string literal global_asm!("{}", clobber_abi("C"), const FOO); -//~^ ERROR arguments are not allowed after clobber_abi -//~^^ ERROR `clobber_abi` cannot be used with `global_asm!` +//~^ ERROR `clobber_abi` cannot be used with `global_asm!` global_asm!("", options(), clobber_abi("C")); -//~^ ERROR clobber_abi is not allowed after options +//~^ ERROR `clobber_abi` cannot be used with `global_asm!` global_asm!("{}", options(), clobber_abi("C"), const FOO); -//~^ ERROR clobber_abi is not allowed after options +//~^ ERROR `clobber_abi` cannot be used with `global_asm!` global_asm!("", clobber_abi("C"), clobber_abi("C")); //~^ ERROR `clobber_abi` cannot be used with `global_asm!` global_asm!("{a}", a = const FOO, a = const BAR); diff --git a/tests/ui/asm/x86_64/parse-error.stderr b/tests/ui/asm/x86_64/parse-error.stderr index 57702c37b7ce..0c9d6f71529c 100644 --- a/tests/ui/asm/x86_64/parse-error.stderr +++ b/tests/ui/asm/x86_64/parse-error.stderr @@ -82,64 +82,32 @@ error: expected one of `)`, `att_syntax`, `may_unwind`, `nomem`, `noreturn`, `no LL | asm!("", options(nomem, foo)); | ^^^ expected one of 10 possible tokens -error: arguments are not allowed after options - --> $DIR/parse-error.rs:39:31 - | -LL | asm!("{}", options(), const foo); - | --------- ^^^^^^^^^ argument - | | - | previous options - error: at least one abi must be provided as an argument to `clobber_abi` - --> $DIR/parse-error.rs:42:30 + --> $DIR/parse-error.rs:41:30 | LL | asm!("", clobber_abi()); | ^ error: expected string literal - --> $DIR/parse-error.rs:44:30 + --> $DIR/parse-error.rs:43:30 | LL | asm!("", clobber_abi(foo)); | ^^^ not a string literal error: expected one of `)` or `,`, found `foo` - --> $DIR/parse-error.rs:46:34 + --> $DIR/parse-error.rs:45:34 | LL | asm!("", clobber_abi("C" foo)); | ^^^ expected one of `)` or `,` error: expected string literal - --> $DIR/parse-error.rs:48:35 + --> $DIR/parse-error.rs:47:35 | LL | asm!("", clobber_abi("C", foo)); | ^^^ not a string literal -error: arguments are not allowed after clobber_abi - --> $DIR/parse-error.rs:50:38 - | -LL | asm!("{}", clobber_abi("C"), const foo); - | ---------------- ^^^^^^^^^ argument - | | - | clobber_abi - -error: clobber_abi is not allowed after options - --> $DIR/parse-error.rs:53:29 - | -LL | asm!("", options(), clobber_abi("C")); - | --------- ^^^^^^^^^^^^^^^^ - | | - | options - -error: clobber_abi is not allowed after options - --> $DIR/parse-error.rs:55:31 - | -LL | asm!("{}", options(), clobber_abi("C"), const foo); - | --------- ^^^^^^^^^^^^^^^^ - | | - | options - error: duplicate argument named `a` - --> $DIR/parse-error.rs:57:36 + --> $DIR/parse-error.rs:54:36 | LL | asm!("{a}", a = const foo, a = const bar); | ------------- ^^^^^^^^^^^^^ duplicate argument @@ -147,7 +115,7 @@ LL | asm!("{a}", a = const foo, a = const bar); | previously here error: argument never used - --> $DIR/parse-error.rs:57:36 + --> $DIR/parse-error.rs:54:36 | LL | asm!("{a}", a = const foo, a = const bar); | ^^^^^^^^^^^^^ argument never used @@ -155,29 +123,13 @@ LL | asm!("{a}", a = const foo, a = const bar); = help: if this argument is intentionally unused, consider using it in an asm comment: `"/* {1} */"` error: explicit register arguments cannot have names - --> $DIR/parse-error.rs:62:18 + --> $DIR/parse-error.rs:59:18 | LL | asm!("", a = in("eax") foo); | ^^^^^^^^^^^^^^^^^ -error: named arguments cannot follow explicit register arguments - --> $DIR/parse-error.rs:64:36 - | -LL | asm!("{a}", in("eax") foo, a = const bar); - | ------------- ^^^^^^^^^^^^^ named argument - | | - | explicit register argument - -error: named arguments cannot follow explicit register arguments - --> $DIR/parse-error.rs:67:36 - | -LL | asm!("{a}", in("eax") foo, a = const bar); - | ------------- ^^^^^^^^^^^^^ named argument - | | - | explicit register argument - error: positional arguments cannot follow named arguments or explicit register arguments - --> $DIR/parse-error.rs:70:36 + --> $DIR/parse-error.rs:65:36 | LL | asm!("{1}", in("eax") foo, const bar); | ------------- ^^^^^^^^^ positional argument @@ -185,19 +137,19 @@ LL | asm!("{1}", in("eax") foo, const bar); | explicit register argument error: expected one of `clobber_abi`, `const`, `in`, `inlateout`, `inout`, `lateout`, `options`, `out`, or `sym`, found `""` - --> $DIR/parse-error.rs:73:29 + --> $DIR/parse-error.rs:68:29 | LL | asm!("", options(), ""); | ^^ expected one of 9 possible tokens error: expected one of `clobber_abi`, `const`, `in`, `inlateout`, `inout`, `lateout`, `options`, `out`, or `sym`, found `"{}"` - --> $DIR/parse-error.rs:75:33 + --> $DIR/parse-error.rs:70:33 | LL | asm!("{}", in(reg) foo, "{}", out(reg) foo); | ^^^^ expected one of 9 possible tokens error: asm template must be a string literal - --> $DIR/parse-error.rs:77:14 + --> $DIR/parse-error.rs:72:14 | LL | asm!(format!("{{{}}}", 0), in(reg) foo); | ^^^^^^^^^^^^^^^^^^^^ @@ -205,7 +157,7 @@ LL | asm!(format!("{{{}}}", 0), in(reg) foo); = note: this error originates in the macro `format` (in Nightly builds, run with -Z macro-backtrace for more info) error: asm template must be a string literal - --> $DIR/parse-error.rs:79:21 + --> $DIR/parse-error.rs:74:21 | LL | asm!("{1}", format!("{{{}}}", 0), in(reg) foo, out(reg) bar); | ^^^^^^^^^^^^^^^^^^^^ @@ -213,141 +165,121 @@ LL | asm!("{1}", format!("{{{}}}", 0), in(reg) foo, out(reg) bar); = note: this error originates in the macro `format` (in Nightly builds, run with -Z macro-backtrace for more info) error: _ cannot be used for input operands - --> $DIR/parse-error.rs:81:28 + --> $DIR/parse-error.rs:76:28 | LL | asm!("{}", in(reg) _); | ^ error: _ cannot be used for input operands - --> $DIR/parse-error.rs:83:31 + --> $DIR/parse-error.rs:78:31 | LL | asm!("{}", inout(reg) _); | ^ error: _ cannot be used for input operands - --> $DIR/parse-error.rs:85:35 + --> $DIR/parse-error.rs:80:35 | LL | asm!("{}", inlateout(reg) _); | ^ error: requires at least a template string argument - --> $DIR/parse-error.rs:92:1 + --> $DIR/parse-error.rs:87:1 | LL | global_asm!(); | ^^^^^^^^^^^^^ error: asm template must be a string literal - --> $DIR/parse-error.rs:94:13 + --> $DIR/parse-error.rs:89:13 | LL | global_asm!(FOO); | ^^^ error: expected token: `,` - --> $DIR/parse-error.rs:96:18 + --> $DIR/parse-error.rs:91:18 | LL | global_asm!("{}" FOO); | ^^^ expected `,` error: expected operand, options, or additional template string - --> $DIR/parse-error.rs:98:19 + --> $DIR/parse-error.rs:93:19 | LL | global_asm!("{}", FOO); | ^^^ expected operand, options, or additional template string error: expected expression, found end of macro arguments - --> $DIR/parse-error.rs:100:24 + --> $DIR/parse-error.rs:95:24 | LL | global_asm!("{}", const); | ^ expected expression error: expected one of `,`, `.`, `?`, or an operator, found `FOO` - --> $DIR/parse-error.rs:102:30 + --> $DIR/parse-error.rs:97:30 | LL | global_asm!("{}", const(reg) FOO); | ^^^ expected one of `,`, `.`, `?`, or an operator error: expected one of `)`, `att_syntax`, or `raw`, found `FOO` - --> $DIR/parse-error.rs:104:25 + --> $DIR/parse-error.rs:99:25 | LL | global_asm!("", options(FOO)); | ^^^ expected one of `)`, `att_syntax`, or `raw` error: expected one of `)`, `att_syntax`, or `raw`, found `nomem` - --> $DIR/parse-error.rs:106:25 + --> $DIR/parse-error.rs:101:25 | LL | global_asm!("", options(nomem FOO)); | ^^^^^ expected one of `)`, `att_syntax`, or `raw` error: expected one of `)`, `att_syntax`, or `raw`, found `nomem` - --> $DIR/parse-error.rs:108:25 + --> $DIR/parse-error.rs:103:25 | LL | global_asm!("", options(nomem, FOO)); | ^^^^^ expected one of `)`, `att_syntax`, or `raw` -error: arguments are not allowed after options - --> $DIR/parse-error.rs:110:30 - | -LL | global_asm!("{}", options(), const FOO); - | --------- ^^^^^^^^^ argument - | | - | previous options - error: expected string literal - --> $DIR/parse-error.rs:112:29 + --> $DIR/parse-error.rs:106:29 | LL | global_asm!("", clobber_abi(FOO)); | ^^^ not a string literal error: expected one of `)` or `,`, found `FOO` - --> $DIR/parse-error.rs:114:33 + --> $DIR/parse-error.rs:108:33 | LL | global_asm!("", clobber_abi("C" FOO)); | ^^^ expected one of `)` or `,` error: expected string literal - --> $DIR/parse-error.rs:116:34 + --> $DIR/parse-error.rs:110:34 | LL | global_asm!("", clobber_abi("C", FOO)); | ^^^ not a string literal -error: arguments are not allowed after clobber_abi - --> $DIR/parse-error.rs:118:37 - | -LL | global_asm!("{}", clobber_abi("C"), const FOO); - | ---------------- ^^^^^^^^^ argument - | | - | clobber_abi - error: `clobber_abi` cannot be used with `global_asm!` - --> $DIR/parse-error.rs:118:19 + --> $DIR/parse-error.rs:112:19 | LL | global_asm!("{}", clobber_abi("C"), const FOO); | ^^^^^^^^^^^^^^^^ -error: clobber_abi is not allowed after options - --> $DIR/parse-error.rs:121:28 +error: `clobber_abi` cannot be used with `global_asm!` + --> $DIR/parse-error.rs:114:28 | LL | global_asm!("", options(), clobber_abi("C")); - | --------- ^^^^^^^^^^^^^^^^ - | | - | options - -error: clobber_abi is not allowed after options - --> $DIR/parse-error.rs:123:30 - | -LL | global_asm!("{}", options(), clobber_abi("C"), const FOO); - | --------- ^^^^^^^^^^^^^^^^ - | | - | options + | ^^^^^^^^^^^^^^^^ error: `clobber_abi` cannot be used with `global_asm!` - --> $DIR/parse-error.rs:125:17 + --> $DIR/parse-error.rs:116:30 + | +LL | global_asm!("{}", options(), clobber_abi("C"), const FOO); + | ^^^^^^^^^^^^^^^^ + +error: `clobber_abi` cannot be used with `global_asm!` + --> $DIR/parse-error.rs:118:17 | LL | global_asm!("", clobber_abi("C"), clobber_abi("C")); | ^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ error: duplicate argument named `a` - --> $DIR/parse-error.rs:127:35 + --> $DIR/parse-error.rs:120:35 | LL | global_asm!("{a}", a = const FOO, a = const BAR); | ------------- ^^^^^^^^^^^^^ duplicate argument @@ -355,7 +287,7 @@ LL | global_asm!("{a}", a = const FOO, a = const BAR); | previously here error: argument never used - --> $DIR/parse-error.rs:127:35 + --> $DIR/parse-error.rs:120:35 | LL | global_asm!("{a}", a = const FOO, a = const BAR); | ^^^^^^^^^^^^^ argument never used @@ -363,19 +295,19 @@ LL | global_asm!("{a}", a = const FOO, a = const BAR); = help: if this argument is intentionally unused, consider using it in an asm comment: `"/* {1} */"` error: expected one of `clobber_abi`, `const`, `options`, or `sym`, found `""` - --> $DIR/parse-error.rs:130:28 + --> $DIR/parse-error.rs:123:28 | LL | global_asm!("", options(), ""); | ^^ expected one of `clobber_abi`, `const`, `options`, or `sym` error: expected one of `clobber_abi`, `const`, `options`, or `sym`, found `"{}"` - --> $DIR/parse-error.rs:132:30 + --> $DIR/parse-error.rs:125:30 | LL | global_asm!("{}", const FOO, "{}", const FOO); | ^^^^ expected one of `clobber_abi`, `const`, `options`, or `sym` error: asm template must be a string literal - --> $DIR/parse-error.rs:134:13 + --> $DIR/parse-error.rs:127:13 | LL | global_asm!(format!("{{{}}}", 0), const FOO); | ^^^^^^^^^^^^^^^^^^^^ @@ -383,7 +315,7 @@ LL | global_asm!(format!("{{{}}}", 0), const FOO); = note: this error originates in the macro `format` (in Nightly builds, run with -Z macro-backtrace for more info) error: asm template must be a string literal - --> $DIR/parse-error.rs:136:20 + --> $DIR/parse-error.rs:129:20 | LL | global_asm!("{1}", format!("{{{}}}", 0), const FOO, const BAR); | ^^^^^^^^^^^^^^^^^^^^ @@ -400,7 +332,7 @@ LL | asm!("{}", options(), const foo); | ^^^ non-constant value error[E0435]: attempt to use a non-constant value in a constant - --> $DIR/parse-error.rs:50:44 + --> $DIR/parse-error.rs:49:44 | LL | let mut foo = 0; | ----------- help: consider using `const` instead of `let`: `const foo` @@ -409,7 +341,16 @@ LL | asm!("{}", clobber_abi("C"), const foo); | ^^^ non-constant value error[E0435]: attempt to use a non-constant value in a constant - --> $DIR/parse-error.rs:57:31 + --> $DIR/parse-error.rs:52:55 + | +LL | let mut foo = 0; + | ----------- help: consider using `const` instead of `let`: `const foo` +... +LL | asm!("{}", options(), clobber_abi("C"), const foo); + | ^^^ non-constant value + +error[E0435]: attempt to use a non-constant value in a constant + --> $DIR/parse-error.rs:54:31 | LL | let mut foo = 0; | ----------- help: consider using `const` instead of `let`: `const foo` @@ -418,7 +359,7 @@ LL | asm!("{a}", a = const foo, a = const bar); | ^^^ non-constant value error[E0435]: attempt to use a non-constant value in a constant - --> $DIR/parse-error.rs:57:46 + --> $DIR/parse-error.rs:54:46 | LL | let mut bar = 0; | ----------- help: consider using `const` instead of `let`: `const bar` @@ -427,7 +368,7 @@ LL | asm!("{a}", a = const foo, a = const bar); | ^^^ non-constant value error[E0435]: attempt to use a non-constant value in a constant - --> $DIR/parse-error.rs:64:46 + --> $DIR/parse-error.rs:61:46 | LL | let mut bar = 0; | ----------- help: consider using `const` instead of `let`: `const bar` @@ -436,7 +377,7 @@ LL | asm!("{a}", in("eax") foo, a = const bar); | ^^^ non-constant value error[E0435]: attempt to use a non-constant value in a constant - --> $DIR/parse-error.rs:67:46 + --> $DIR/parse-error.rs:63:46 | LL | let mut bar = 0; | ----------- help: consider using `const` instead of `let`: `const bar` @@ -445,7 +386,7 @@ LL | asm!("{a}", in("eax") foo, a = const bar); | ^^^ non-constant value error[E0435]: attempt to use a non-constant value in a constant - --> $DIR/parse-error.rs:70:42 + --> $DIR/parse-error.rs:65:42 | LL | let mut bar = 0; | ----------- help: consider using `const` instead of `let`: `const bar` @@ -453,6 +394,6 @@ LL | let mut bar = 0; LL | asm!("{1}", in("eax") foo, const bar); | ^^^ non-constant value -error: aborting due to 66 previous errors +error: aborting due to 59 previous errors For more information about this error, try `rustc --explain E0435`. From 8ca25b8e49ca3442a56029f59677dfaab5b6eaf5 Mon Sep 17 00:00:00 2001 From: Markus Everling Date: Thu, 29 Dec 2022 23:43:34 +0100 Subject: [PATCH 007/362] Fix `vec_deque::Drain` FIXME --- .../alloc/src/collections/vec_deque/drain.rs | 28 +++++-------------- .../alloc/src/collections/vec_deque/mod.rs | 18 ++++++------ 2 files changed, 17 insertions(+), 29 deletions(-) diff --git a/library/alloc/src/collections/vec_deque/drain.rs b/library/alloc/src/collections/vec_deque/drain.rs index 89feb361ddc1..a102aaad452e 100644 --- a/library/alloc/src/collections/vec_deque/drain.rs +++ b/library/alloc/src/collections/vec_deque/drain.rs @@ -57,31 +57,17 @@ impl<'a, T, A: Allocator> Drain<'a, T, A> { unsafe fn as_slices(&self) -> (*mut [T], *mut [T]) { unsafe { let deque = self.deque.as_ref(); - // FIXME: This is doing almost exactly the same thing as the else branch in `VecDeque::slice_ranges`. - // Unfortunately, we can't just call `slice_ranges` here, as the deque's `len` is currently - // just `drain_start`, so the range check would (almost) always panic. Between temporarily - // adjusting the deques `len` to call `slice_ranges`, and just copy pasting the `slice_ranges` - // implementation, this seemed like the less hacky solution, though it might be good to - // find a better one in the future. - // because `self.remaining != 0`, we know that `self.idx < deque.original_len`, so it's a valid - // logical index. - let wrapped_start = deque.to_physical_idx(self.idx); + let start = self.idx; + // We know that `self.idx + self.remaining <= deque.len <= usize::MAX`, so this won't overflow. + let end = start + self.remaining; - let head_len = deque.capacity() - wrapped_start; - - let (a_range, b_range) = if head_len >= self.remaining { - (wrapped_start..wrapped_start + self.remaining, 0..0) - } else { - let tail_len = self.remaining - head_len; - (wrapped_start..deque.capacity(), 0..tail_len) - }; - - // SAFETY: the range `self.idx..self.idx+self.remaining` lies strictly inside - // the range `0..deque.original_len`. because of this, and because of the fact - // that we acquire `a_range` and `b_range` exactly like `slice_ranges` would, + // SAFETY: the range `start..end` lies strictly inside + // the range `0..deque.original_len`. Because of this, and because + // we haven't touched the elements inside this range yet, // it's guaranteed that `a_range` and `b_range` represent valid ranges into // the deques buffer. + let (a_range, b_range) = deque.slice_ranges(start..end, end); (deque.buffer_range(a_range), deque.buffer_range(b_range)) } } diff --git a/library/alloc/src/collections/vec_deque/mod.rs b/library/alloc/src/collections/vec_deque/mod.rs index c955db46d29f..6d3e784c8b7f 100644 --- a/library/alloc/src/collections/vec_deque/mod.rs +++ b/library/alloc/src/collections/vec_deque/mod.rs @@ -1147,7 +1147,7 @@ impl VecDeque { #[inline] #[stable(feature = "deque_extras_15", since = "1.5.0")] pub fn as_slices(&self) -> (&[T], &[T]) { - let (a_range, b_range) = self.slice_ranges(..); + let (a_range, b_range) = self.slice_ranges(.., self.len); // SAFETY: `slice_ranges` always returns valid ranges into // the physical buffer. unsafe { (&*self.buffer_range(a_range), &*self.buffer_range(b_range)) } @@ -1181,7 +1181,7 @@ impl VecDeque { #[inline] #[stable(feature = "deque_extras_15", since = "1.5.0")] pub fn as_mut_slices(&mut self) -> (&mut [T], &mut [T]) { - let (a_range, b_range) = self.slice_ranges(..); + let (a_range, b_range) = self.slice_ranges(.., self.len); // SAFETY: `slice_ranges` always returns valid ranges into // the physical buffer. unsafe { (&mut *self.buffer_range(a_range), &mut *self.buffer_range(b_range)) } @@ -1223,19 +1223,21 @@ impl VecDeque { /// Given a range into the logical buffer of the deque, this function /// return two ranges into the physical buffer that correspond to - /// the given range. - fn slice_ranges(&self, range: R) -> (Range, Range) + /// the given range. The `len` parameter should usually just be `self.len`; + /// the reason it's passed explicitly is that if the deque is wrapped in + /// a `Drain`, then `self.len` is not actually the length of the deque. + fn slice_ranges(&self, range: R, len: usize) -> (Range, Range) where R: RangeBounds, { - let Range { start, end } = slice::range(range, ..self.len); + let Range { start, end } = slice::range(range, ..len); let len = end - start; if len == 0 { (0..0, 0..0) } else { // `slice::range` guarantees that `start <= end <= self.len`. - // because `len != 0`, we know that `start < end`, so `start < self.len` + // because `len != 0`, we know that `start < end`, so `start < len` // and the indexing is valid. let wrapped_start = self.to_physical_idx(start); @@ -1281,7 +1283,7 @@ impl VecDeque { where R: RangeBounds, { - let (a_range, b_range) = self.slice_ranges(range); + let (a_range, b_range) = self.slice_ranges(range, self.len); // SAFETY: The ranges returned by `slice_ranges` // are valid ranges into the physical buffer, so // it's ok to pass them to `buffer_range` and @@ -1321,7 +1323,7 @@ impl VecDeque { where R: RangeBounds, { - let (a_range, b_range) = self.slice_ranges(range); + let (a_range, b_range) = self.slice_ranges(range, self.len); // SAFETY: The ranges returned by `slice_ranges` // are valid ranges into the physical buffer, so // it's ok to pass them to `buffer_range` and From 1e114a88bde098d1c057161aa252fa75d5739592 Mon Sep 17 00:00:00 2001 From: Markus Everling Date: Sun, 5 Feb 2023 02:16:43 +0100 Subject: [PATCH 008/362] Add `slice_ranges` safety comment --- library/alloc/src/collections/vec_deque/drain.rs | 9 ++++----- library/alloc/src/collections/vec_deque/mod.rs | 8 ++++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/library/alloc/src/collections/vec_deque/drain.rs b/library/alloc/src/collections/vec_deque/drain.rs index a102aaad452e..99bd7902e694 100644 --- a/library/alloc/src/collections/vec_deque/drain.rs +++ b/library/alloc/src/collections/vec_deque/drain.rs @@ -62,11 +62,10 @@ impl<'a, T, A: Allocator> Drain<'a, T, A> { // We know that `self.idx + self.remaining <= deque.len <= usize::MAX`, so this won't overflow. let end = start + self.remaining; - // SAFETY: the range `start..end` lies strictly inside - // the range `0..deque.original_len`. Because of this, and because - // we haven't touched the elements inside this range yet, - // it's guaranteed that `a_range` and `b_range` represent valid ranges into - // the deques buffer. + // SAFETY: `start..end` represents the range of elements that + // haven't been drained yet, so they're all initialized, + // and `slice::range(start..end, end) == start..end`, + // so the preconditions for `slice_ranges` are met. let (a_range, b_range) = deque.slice_ranges(start..end, end); (deque.buffer_range(a_range), deque.buffer_range(b_range)) } diff --git a/library/alloc/src/collections/vec_deque/mod.rs b/library/alloc/src/collections/vec_deque/mod.rs index 6d3e784c8b7f..813430ae6153 100644 --- a/library/alloc/src/collections/vec_deque/mod.rs +++ b/library/alloc/src/collections/vec_deque/mod.rs @@ -1226,6 +1226,14 @@ impl VecDeque { /// the given range. The `len` parameter should usually just be `self.len`; /// the reason it's passed explicitly is that if the deque is wrapped in /// a `Drain`, then `self.len` is not actually the length of the deque. + /// + /// # Safety + /// + /// This function is always safe to call. For the resulting ranges to be valid + /// ranges into the physical buffer, the caller must ensure that for all possible + /// values of `range` and `len`, the result of calling `slice::range(range, ..len)` + /// represents a valid range into the logical buffer, and that all elements + /// in that range are initialized. fn slice_ranges(&self, range: R, len: usize) -> (Range, Range) where R: RangeBounds, From c43937316af1321a97c03a22cd04273eba63d5b9 Mon Sep 17 00:00:00 2001 From: Caio Date: Sun, 12 Feb 2023 17:34:07 -0300 Subject: [PATCH 009/362] [arithmetic_side_effects] Fix #10252 --- .../src/operators/arithmetic_side_effects.rs | 13 +- tests/ui/arithmetic_side_effects.rs | 28 ++ tests/ui/arithmetic_side_effects.stderr | 288 ++++++++++-------- 3 files changed, 206 insertions(+), 123 deletions(-) diff --git a/clippy_lints/src/operators/arithmetic_side_effects.rs b/clippy_lints/src/operators/arithmetic_side_effects.rs index 87a8a2ed12b2..25e8de948637 100644 --- a/clippy_lints/src/operators/arithmetic_side_effects.rs +++ b/clippy_lints/src/operators/arithmetic_side_effects.rs @@ -143,6 +143,10 @@ impl ArithmeticSideEffects { return; } let has_valid_op = if Self::is_integral(lhs_ty) && Self::is_integral(rhs_ty) { + if let hir::BinOpKind::Shl | hir::BinOpKind::Shr = op.node { + // At least for integers, shifts are already handled by the CTFE + return; + } let (actual_lhs, lhs_ref_counter) = peel_hir_expr_refs(lhs); let (actual_rhs, rhs_ref_counter) = peel_hir_expr_refs(rhs); match ( @@ -151,10 +155,13 @@ impl ArithmeticSideEffects { ) { (None, None) => false, (None, Some(n)) | (Some(n), None) => match (&op.node, n) { - (hir::BinOpKind::Div | hir::BinOpKind::Rem, 0) => false, + // Division and module are always valid if applied to non-zero integers + (hir::BinOpKind::Div | hir::BinOpKind::Rem, local_n) if local_n != 0 => true, + // Addition or subtracting zeros is always a no-op (hir::BinOpKind::Add | hir::BinOpKind::Sub, 0) - | (hir::BinOpKind::Div | hir::BinOpKind::Rem, _) - | (hir::BinOpKind::Mul, 0 | 1) => true, + // Multiplication by 1 or 0 will never overflow + | (hir::BinOpKind::Mul, 0 | 1) + => true, _ => false, }, (Some(_), Some(_)) => { diff --git a/tests/ui/arithmetic_side_effects.rs b/tests/ui/arithmetic_side_effects.rs index 2611e3a785f6..ee7d2ba444b2 100644 --- a/tests/ui/arithmetic_side_effects.rs +++ b/tests/ui/arithmetic_side_effects.rs @@ -45,24 +45,32 @@ impl_arith!( Div, Custom, Custom, div; Mul, Custom, Custom, mul; Rem, Custom, Custom, rem; + Shl, Custom, Custom, shl; + Shr, Custom, Custom, shr; Sub, Custom, Custom, sub; Add, Custom, &Custom, add; Div, Custom, &Custom, div; Mul, Custom, &Custom, mul; Rem, Custom, &Custom, rem; + Shl, Custom, &Custom, shl; + Shr, Custom, &Custom, shr; Sub, Custom, &Custom, sub; Add, &Custom, Custom, add; Div, &Custom, Custom, div; Mul, &Custom, Custom, mul; Rem, &Custom, Custom, rem; + Shl, &Custom, Custom, shl; + Shr, &Custom, Custom, shr; Sub, &Custom, Custom, sub; Add, &Custom, &Custom, add; Div, &Custom, &Custom, div; Mul, &Custom, &Custom, mul; Rem, &Custom, &Custom, rem; + Shl, &Custom, &Custom, shl; + Shr, &Custom, &Custom, shr; Sub, &Custom, &Custom, sub; ); @@ -71,24 +79,32 @@ impl_assign_arith!( DivAssign, Custom, Custom, div_assign; MulAssign, Custom, Custom, mul_assign; RemAssign, Custom, Custom, rem_assign; + ShlAssign, Custom, Custom, shl_assign; + ShrAssign, Custom, Custom, shr_assign; SubAssign, Custom, Custom, sub_assign; AddAssign, Custom, &Custom, add_assign; DivAssign, Custom, &Custom, div_assign; MulAssign, Custom, &Custom, mul_assign; RemAssign, Custom, &Custom, rem_assign; + ShlAssign, Custom, &Custom, shl_assign; + ShrAssign, Custom, &Custom, shr_assign; SubAssign, Custom, &Custom, sub_assign; AddAssign, &Custom, Custom, add_assign; DivAssign, &Custom, Custom, div_assign; MulAssign, &Custom, Custom, mul_assign; RemAssign, &Custom, Custom, rem_assign; + ShlAssign, &Custom, Custom, shl_assign; + ShrAssign, &Custom, Custom, shr_assign; SubAssign, &Custom, Custom, sub_assign; AddAssign, &Custom, &Custom, add_assign; DivAssign, &Custom, &Custom, div_assign; MulAssign, &Custom, &Custom, mul_assign; RemAssign, &Custom, &Custom, rem_assign; + ShlAssign, &Custom, &Custom, shl_assign; + ShrAssign, &Custom, &Custom, shr_assign; SubAssign, &Custom, &Custom, sub_assign; ); @@ -297,6 +313,10 @@ pub fn unknown_ops_or_runtime_ops_that_can_overflow() { _custom %= &Custom; _custom *= Custom; _custom *= &Custom; + _custom >>= Custom; + _custom >>= &Custom; + _custom <<= Custom; + _custom <<= &Custom; _custom += -Custom; _custom += &-Custom; _custom -= -Custom; @@ -307,6 +327,10 @@ pub fn unknown_ops_or_runtime_ops_that_can_overflow() { _custom %= &-Custom; _custom *= -Custom; _custom *= &-Custom; + _custom >>= -Custom; + _custom >>= &-Custom; + _custom <<= -Custom; + _custom <<= &-Custom; // Binary _n = _n + 1; @@ -347,6 +371,10 @@ pub fn unknown_ops_or_runtime_ops_that_can_overflow() { _custom = Custom + &Custom; _custom = &Custom + Custom; _custom = &Custom + &Custom; + _custom = _custom >> _custom; + _custom = _custom >> &_custom; + _custom = Custom << _custom; + _custom = &Custom << _custom; // Unary _n = -_n; diff --git a/tests/ui/arithmetic_side_effects.stderr b/tests/ui/arithmetic_side_effects.stderr index 17a2448fbfca..3895f08964ce 100644 --- a/tests/ui/arithmetic_side_effects.stderr +++ b/tests/ui/arithmetic_side_effects.stderr @@ -1,5 +1,5 @@ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:270:5 + --> $DIR/arithmetic_side_effects.rs:286:5 | LL | _n += 1; | ^^^^^^^ @@ -7,592 +7,640 @@ LL | _n += 1; = note: `-D clippy::arithmetic-side-effects` implied by `-D warnings` error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:271:5 + --> $DIR/arithmetic_side_effects.rs:287:5 | LL | _n += &1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:272:5 + --> $DIR/arithmetic_side_effects.rs:288:5 | LL | _n -= 1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:273:5 + --> $DIR/arithmetic_side_effects.rs:289:5 | LL | _n -= &1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:274:5 + --> $DIR/arithmetic_side_effects.rs:290:5 | LL | _n /= 0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:275:5 + --> $DIR/arithmetic_side_effects.rs:291:5 | LL | _n /= &0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:276:5 + --> $DIR/arithmetic_side_effects.rs:292:5 | LL | _n %= 0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:277:5 + --> $DIR/arithmetic_side_effects.rs:293:5 | LL | _n %= &0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:278:5 + --> $DIR/arithmetic_side_effects.rs:294:5 | LL | _n *= 2; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:279:5 + --> $DIR/arithmetic_side_effects.rs:295:5 | LL | _n *= &2; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:280:5 + --> $DIR/arithmetic_side_effects.rs:296:5 | LL | _n += -1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:281:5 + --> $DIR/arithmetic_side_effects.rs:297:5 | LL | _n += &-1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:282:5 + --> $DIR/arithmetic_side_effects.rs:298:5 | LL | _n -= -1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:283:5 + --> $DIR/arithmetic_side_effects.rs:299:5 | LL | _n -= &-1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:284:5 + --> $DIR/arithmetic_side_effects.rs:300:5 | LL | _n /= -0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:285:5 + --> $DIR/arithmetic_side_effects.rs:301:5 | LL | _n /= &-0; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:286:5 + --> $DIR/arithmetic_side_effects.rs:302:5 | LL | _n %= -0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:287:5 + --> $DIR/arithmetic_side_effects.rs:303:5 | LL | _n %= &-0; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:288:5 + --> $DIR/arithmetic_side_effects.rs:304:5 | LL | _n *= -2; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:289:5 + --> $DIR/arithmetic_side_effects.rs:305:5 | LL | _n *= &-2; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:290:5 + --> $DIR/arithmetic_side_effects.rs:306:5 | LL | _custom += Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:291:5 + --> $DIR/arithmetic_side_effects.rs:307:5 | LL | _custom += &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:292:5 + --> $DIR/arithmetic_side_effects.rs:308:5 | LL | _custom -= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:293:5 + --> $DIR/arithmetic_side_effects.rs:309:5 | LL | _custom -= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:294:5 + --> $DIR/arithmetic_side_effects.rs:310:5 | LL | _custom /= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:295:5 + --> $DIR/arithmetic_side_effects.rs:311:5 | LL | _custom /= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:296:5 + --> $DIR/arithmetic_side_effects.rs:312:5 | LL | _custom %= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:297:5 + --> $DIR/arithmetic_side_effects.rs:313:5 | LL | _custom %= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:298:5 + --> $DIR/arithmetic_side_effects.rs:314:5 | LL | _custom *= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:299:5 + --> $DIR/arithmetic_side_effects.rs:315:5 | LL | _custom *= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:300:5 + --> $DIR/arithmetic_side_effects.rs:316:5 + | +LL | _custom >>= Custom; + | ^^^^^^^^^^^^^^^^^^ + +error: arithmetic operation that can potentially result in unexpected side-effects + --> $DIR/arithmetic_side_effects.rs:317:5 + | +LL | _custom >>= &Custom; + | ^^^^^^^^^^^^^^^^^^^ + +error: arithmetic operation that can potentially result in unexpected side-effects + --> $DIR/arithmetic_side_effects.rs:318:5 + | +LL | _custom <<= Custom; + | ^^^^^^^^^^^^^^^^^^ + +error: arithmetic operation that can potentially result in unexpected side-effects + --> $DIR/arithmetic_side_effects.rs:319:5 + | +LL | _custom <<= &Custom; + | ^^^^^^^^^^^^^^^^^^^ + +error: arithmetic operation that can potentially result in unexpected side-effects + --> $DIR/arithmetic_side_effects.rs:320:5 | LL | _custom += -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:301:5 + --> $DIR/arithmetic_side_effects.rs:321:5 | LL | _custom += &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:302:5 + --> $DIR/arithmetic_side_effects.rs:322:5 | LL | _custom -= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:303:5 + --> $DIR/arithmetic_side_effects.rs:323:5 | LL | _custom -= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:304:5 + --> $DIR/arithmetic_side_effects.rs:324:5 | LL | _custom /= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:305:5 + --> $DIR/arithmetic_side_effects.rs:325:5 | LL | _custom /= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:306:5 + --> $DIR/arithmetic_side_effects.rs:326:5 | LL | _custom %= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:307:5 + --> $DIR/arithmetic_side_effects.rs:327:5 | LL | _custom %= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:308:5 + --> $DIR/arithmetic_side_effects.rs:328:5 | LL | _custom *= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:309:5 + --> $DIR/arithmetic_side_effects.rs:329:5 | LL | _custom *= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:312:10 + --> $DIR/arithmetic_side_effects.rs:330:5 + | +LL | _custom >>= -Custom; + | ^^^^^^^^^^^^^^^^^^^ + +error: arithmetic operation that can potentially result in unexpected side-effects + --> $DIR/arithmetic_side_effects.rs:331:5 + | +LL | _custom >>= &-Custom; + | ^^^^^^^^^^^^^^^^^^^^ + +error: arithmetic operation that can potentially result in unexpected side-effects + --> $DIR/arithmetic_side_effects.rs:332:5 + | +LL | _custom <<= -Custom; + | ^^^^^^^^^^^^^^^^^^^ + +error: arithmetic operation that can potentially result in unexpected side-effects + --> $DIR/arithmetic_side_effects.rs:333:5 + | +LL | _custom <<= &-Custom; + | ^^^^^^^^^^^^^^^^^^^^ + +error: arithmetic operation that can potentially result in unexpected side-effects + --> $DIR/arithmetic_side_effects.rs:336:10 | LL | _n = _n + 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:313:10 + --> $DIR/arithmetic_side_effects.rs:337:10 | LL | _n = _n + &1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:314:10 + --> $DIR/arithmetic_side_effects.rs:338:10 | LL | _n = 1 + _n; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:315:10 + --> $DIR/arithmetic_side_effects.rs:339:10 | LL | _n = &1 + _n; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:316:10 + --> $DIR/arithmetic_side_effects.rs:340:10 | LL | _n = _n - 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:317:10 + --> $DIR/arithmetic_side_effects.rs:341:10 | LL | _n = _n - &1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:318:10 + --> $DIR/arithmetic_side_effects.rs:342:10 | LL | _n = 1 - _n; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:319:10 + --> $DIR/arithmetic_side_effects.rs:343:10 | LL | _n = &1 - _n; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:320:10 + --> $DIR/arithmetic_side_effects.rs:344:10 | LL | _n = _n / 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:321:10 + --> $DIR/arithmetic_side_effects.rs:345:10 | LL | _n = _n / &0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:322:10 + --> $DIR/arithmetic_side_effects.rs:346:10 | LL | _n = _n % 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:323:10 + --> $DIR/arithmetic_side_effects.rs:347:10 | LL | _n = _n % &0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:324:10 + --> $DIR/arithmetic_side_effects.rs:348:10 | LL | _n = _n * 2; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:325:10 + --> $DIR/arithmetic_side_effects.rs:349:10 | LL | _n = _n * &2; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:326:10 + --> $DIR/arithmetic_side_effects.rs:350:10 | LL | _n = 2 * _n; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:327:10 + --> $DIR/arithmetic_side_effects.rs:351:10 | LL | _n = &2 * _n; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:328:10 + --> $DIR/arithmetic_side_effects.rs:352:10 | LL | _n = 23 + &85; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:329:10 + --> $DIR/arithmetic_side_effects.rs:353:10 | LL | _n = &23 + 85; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:330:10 + --> $DIR/arithmetic_side_effects.rs:354:10 | LL | _n = &23 + &85; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:331:15 + --> $DIR/arithmetic_side_effects.rs:355:15 | LL | _custom = _custom + _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:332:15 + --> $DIR/arithmetic_side_effects.rs:356:15 | LL | _custom = _custom + &_custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:333:15 + --> $DIR/arithmetic_side_effects.rs:357:15 | LL | _custom = Custom + _custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:334:15 + --> $DIR/arithmetic_side_effects.rs:358:15 | LL | _custom = &Custom + _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:335:15 + --> $DIR/arithmetic_side_effects.rs:359:15 | LL | _custom = _custom - Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:336:15 + --> $DIR/arithmetic_side_effects.rs:360:15 | LL | _custom = _custom - &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:337:15 + --> $DIR/arithmetic_side_effects.rs:361:15 | LL | _custom = Custom - _custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:338:15 + --> $DIR/arithmetic_side_effects.rs:362:15 | LL | _custom = &Custom - _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:339:15 + --> $DIR/arithmetic_side_effects.rs:363:15 | LL | _custom = _custom / Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:340:15 + --> $DIR/arithmetic_side_effects.rs:364:15 | LL | _custom = _custom / &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:341:15 + --> $DIR/arithmetic_side_effects.rs:365:15 | LL | _custom = _custom % Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:342:15 + --> $DIR/arithmetic_side_effects.rs:366:15 | LL | _custom = _custom % &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:343:15 + --> $DIR/arithmetic_side_effects.rs:367:15 | LL | _custom = _custom * Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:344:15 + --> $DIR/arithmetic_side_effects.rs:368:15 | LL | _custom = _custom * &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:345:15 + --> $DIR/arithmetic_side_effects.rs:369:15 | LL | _custom = Custom * _custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:346:15 + --> $DIR/arithmetic_side_effects.rs:370:15 | LL | _custom = &Custom * _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:347:15 + --> $DIR/arithmetic_side_effects.rs:371:15 | LL | _custom = Custom + &Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:348:15 + --> $DIR/arithmetic_side_effects.rs:372:15 | LL | _custom = &Custom + Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:349:15 + --> $DIR/arithmetic_side_effects.rs:373:15 | LL | _custom = &Custom + &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:352:10 + --> $DIR/arithmetic_side_effects.rs:374:15 + | +LL | _custom = _custom >> _custom; + | ^^^^^^^^^^^^^^^^^^ + +error: arithmetic operation that can potentially result in unexpected side-effects + --> $DIR/arithmetic_side_effects.rs:375:15 + | +LL | _custom = _custom >> &_custom; + | ^^^^^^^^^^^^^^^^^^^ + +error: arithmetic operation that can potentially result in unexpected side-effects + --> $DIR/arithmetic_side_effects.rs:376:15 + | +LL | _custom = Custom << _custom; + | ^^^^^^^^^^^^^^^^^ + +error: arithmetic operation that can potentially result in unexpected side-effects + --> $DIR/arithmetic_side_effects.rs:377:15 + | +LL | _custom = &Custom << _custom; + | ^^^^^^^^^^^^^^^^^^ + +error: arithmetic operation that can potentially result in unexpected side-effects + --> $DIR/arithmetic_side_effects.rs:380:10 | LL | _n = -_n; | ^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:353:10 + --> $DIR/arithmetic_side_effects.rs:381:10 | LL | _n = -&_n; | ^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:354:15 + --> $DIR/arithmetic_side_effects.rs:382:15 | LL | _custom = -_custom; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:355:15 + --> $DIR/arithmetic_side_effects.rs:383:15 | LL | _custom = -&_custom; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:364:5 + --> $DIR/arithmetic_side_effects.rs:392:5 | LL | 1 + i; | ^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:365:5 + --> $DIR/arithmetic_side_effects.rs:393:5 | LL | i * 2; | ^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:367:5 + --> $DIR/arithmetic_side_effects.rs:395:5 | LL | i - 2 + 2 - i; | ^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:368:5 + --> $DIR/arithmetic_side_effects.rs:396:5 | LL | -i; | ^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:369:5 - | -LL | i >> 1; - | ^^^^^^ - -error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:370:5 - | -LL | i << 1; - | ^^^^^^ - -error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:379:5 + --> $DIR/arithmetic_side_effects.rs:407:5 | LL | i += 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:380:5 + --> $DIR/arithmetic_side_effects.rs:408:5 | LL | i -= 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:381:5 + --> $DIR/arithmetic_side_effects.rs:409:5 | LL | i *= 2; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:383:5 + --> $DIR/arithmetic_side_effects.rs:411:5 | LL | i /= 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:385:5 + --> $DIR/arithmetic_side_effects.rs:413:5 | LL | i /= var1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:386:5 + --> $DIR/arithmetic_side_effects.rs:414:5 | LL | i /= var2; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:388:5 + --> $DIR/arithmetic_side_effects.rs:416:5 | LL | i %= 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:390:5 + --> $DIR/arithmetic_side_effects.rs:418:5 | LL | i %= var1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:391:5 + --> $DIR/arithmetic_side_effects.rs:419:5 | LL | i %= var2; | ^^^^^^^^^ -error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:392:5 - | -LL | i <<= 3; - | ^^^^^^^ - -error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:393:5 - | -LL | i >>= 2; - | ^^^^^^^ - -error: aborting due to 99 previous errors +error: aborting due to 107 previous errors From ed1f467aabf03a451355b6e0de8de2f6a2e101ce Mon Sep 17 00:00:00 2001 From: jmviz Date: Fri, 17 Feb 2023 17:41:39 -0500 Subject: [PATCH 010/362] add openDocs to context menu. add further restrictions to context menu when clauses to prevent irrelevant commands in non-rust files --- editors/code/package.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/editors/code/package.json b/editors/code/package.json index 3610e993f822..b206c15f34c2 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -1869,8 +1869,13 @@ "editor/context": [ { "command": "rust-analyzer.peekTests", - "when": "inRustProject", + "when": "inRustProject && editorTextFocus && editorLangId == rust", "group": "navigation@1000" + }, + { + "command": "rust-analyzer.openDocs", + "when": "inRustProject && editorTextFocus && editorLangId == rust", + "group": "navigation@1001" } ] }, From f531abcef52c3c3ab283eb82a9487d5e9192bf12 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sun, 19 Feb 2023 11:54:12 +0100 Subject: [PATCH 011/362] Do not suggest using Self in const generic parameters --- clippy_lints/src/use_self.rs | 15 +++++++++++---- tests/ui/use_self.fixed | 10 ++++++++++ tests/ui/use_self.rs | 10 ++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/use_self.rs b/clippy_lints/src/use_self.rs index 3cd35838961f..8f2b4a7eafbd 100644 --- a/clippy_lints/src/use_self.rs +++ b/clippy_lints/src/use_self.rs @@ -10,8 +10,8 @@ use rustc_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, + Expr, ExprKind, FnRetTy, FnSig, GenericArg, GenericParam, GenericParamKind, HirId, Impl, ImplItemKind, Item, + ItemKind, Pat, PatKind, Path, QPath, Ty, TyKind, }; use rustc_hir_analysis::hir_ty_to_ty; use rustc_lint::{LateContext, LateLintPass}; @@ -96,7 +96,7 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { // 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 ItemKind::Impl(Impl { self_ty, generics,.. }) = 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| { @@ -105,10 +105,17 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { if !item.span.from_expansion(); if !is_from_proc_macro(cx, item); // expensive, should be last check then { + // Self cannot be used inside const generic parameters + let types_to_skip = generics.params.iter().filter_map(|param| { + match param { + GenericParam { kind: GenericParamKind::Const { ty: Ty { hir_id, ..}, ..}, ..} => Some(*hir_id), + _ => None, + } + }).chain(std::iter::once(self_ty.hir_id)).collect(); StackItem::Check { impl_id: item.owner_id.def_id, in_body: 0, - types_to_skip: std::iter::once(self_ty.hir_id).collect(), + types_to_skip, } } else { StackItem::NoCheck diff --git a/tests/ui/use_self.fixed b/tests/ui/use_self.fixed index 0a6166571ebe..3ac6217312a8 100644 --- a/tests/ui/use_self.fixed +++ b/tests/ui/use_self.fixed @@ -647,3 +647,13 @@ fn msrv_1_37() { } } } + +mod issue_10371 { + struct Val {} + + impl From> for i32 { + fn from(_: Val) -> Self { + todo!() + } + } +} diff --git a/tests/ui/use_self.rs b/tests/ui/use_self.rs index 39c2b431f7fb..9dc5d1e3f9b2 100644 --- a/tests/ui/use_self.rs +++ b/tests/ui/use_self.rs @@ -647,3 +647,13 @@ fn msrv_1_37() { } } } + +mod issue_10371 { + struct Val {} + + impl From> for i32 { + fn from(_: Val) -> Self { + todo!() + } + } +} From 443801755c10f64aa3a5b400e8cdc026a7ba6e5e Mon Sep 17 00:00:00 2001 From: Ryo Yoshida Date: Sun, 19 Feb 2023 19:02:51 +0900 Subject: [PATCH 012/362] Refactor - Remove unnecessary references and derefs - Manual formatting --- crates/hir-def/src/lib.rs | 2 +- crates/hir-def/src/nameres.rs | 18 +++++++++--------- crates/hir-def/src/visibility.rs | 5 +++-- crates/hir/src/display.rs | 2 +- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs index d07c5fb67c6f..2aab1ccd914c 100644 --- a/crates/hir-def/src/lib.rs +++ b/crates/hir-def/src/lib.rs @@ -128,7 +128,7 @@ impl ModuleId { } } -/// An ID of a module, **local** to a specific crate +/// An ID of a module, **local** to a `DefMap`. pub type LocalModuleId = Idx; #[derive(Debug)] diff --git a/crates/hir-def/src/nameres.rs b/crates/hir-def/src/nameres.rs index 393747d304b7..a7ce0360516a 100644 --- a/crates/hir-def/src/nameres.rs +++ b/crates/hir-def/src/nameres.rs @@ -342,7 +342,7 @@ impl DefMap { } pub(crate) fn block_id(&self) -> Option { - self.block.as_ref().map(|block| block.block) + self.block.map(|block| block.block) } pub(crate) fn prelude(&self) -> Option { @@ -354,7 +354,7 @@ impl DefMap { } pub fn module_id(&self, local_id: LocalModuleId) -> ModuleId { - let block = self.block.as_ref().map(|b| b.block); + let block = self.block.map(|b| b.block); ModuleId { krate: self.krate, local_id, block } } @@ -432,9 +432,9 @@ impl DefMap { /// Returns the module containing `local_mod`, either the parent `mod`, or the module containing /// the block, if `self` corresponds to a block expression. pub fn containing_module(&self, local_mod: LocalModuleId) -> Option { - match &self[local_mod].parent { - Some(parent) => Some(self.module_id(*parent)), - None => self.block.as_ref().map(|block| block.parent), + match self[local_mod].parent { + Some(parent) => Some(self.module_id(parent)), + None => self.block.map(|block| block.parent), } } @@ -444,11 +444,11 @@ impl DefMap { let mut buf = String::new(); let mut arc; let mut current_map = self; - while let Some(block) = ¤t_map.block { + while let Some(block) = current_map.block { go(&mut buf, current_map, "block scope", current_map.root); buf.push('\n'); arc = block.parent.def_map(db); - current_map = &*arc; + current_map = &arc; } go(&mut buf, current_map, "crate", current_map.root); return buf; @@ -472,10 +472,10 @@ impl DefMap { let mut buf = String::new(); let mut arc; let mut current_map = self; - while let Some(block) = ¤t_map.block { + while let Some(block) = current_map.block { format_to!(buf, "{:?} in {:?}\n", block.block, block.parent); arc = block.parent.def_map(db); - current_map = &*arc; + current_map = &arc; } format_to!(buf, "crate scope\n"); diff --git a/crates/hir-def/src/visibility.rs b/crates/hir-def/src/visibility.rs index 087268a9ecee..eee73b9f3738 100644 --- a/crates/hir-def/src/visibility.rs +++ b/crates/hir-def/src/visibility.rs @@ -11,7 +11,7 @@ use crate::{ nameres::DefMap, path::{ModPath, PathKind}, resolver::HasResolver, - ConstId, FunctionId, HasModule, LocalFieldId, ModuleId, VariantId, + ConstId, FunctionId, HasModule, LocalFieldId, LocalModuleId, ModuleId, VariantId, }; /// Visibility of an item, not yet resolved. @@ -142,7 +142,8 @@ impl Visibility { arc = to_module.def_map(db); &arc }; - let is_block_root = matches!(to_module.block, Some(_) if to_module_def_map[to_module.local_id].parent.is_none()); + let is_block_root = + to_module.block.is_some() && to_module_def_map[to_module.local_id].parent.is_none(); if is_block_root { to_module = to_module_def_map.containing_module(to_module.local_id).unwrap(); } diff --git a/crates/hir/src/display.rs b/crates/hir/src/display.rs index 0d19420127f5..830d261d7869 100644 --- a/crates/hir/src/display.rs +++ b/crates/hir/src/display.rs @@ -50,7 +50,7 @@ impl HirDisplay for Function { let write_self_param = |ty: &TypeRef, f: &mut HirFormatter<'_>| match ty { TypeRef::Path(p) if p.is_self_type() => f.write_str("self"), - TypeRef::Reference(inner, lifetime, mut_) if matches!(&**inner,TypeRef::Path(p) if p.is_self_type()) => + TypeRef::Reference(inner, lifetime, mut_) if matches!(&**inner, TypeRef::Path(p) if p.is_self_type()) => { f.write_char('&')?; if let Some(lifetime) = lifetime { From 83e24fec98f1fbecb29ea34bfd2bc6e0f32d25aa Mon Sep 17 00:00:00 2001 From: Ryo Yoshida Date: Sun, 19 Feb 2023 23:30:49 +0900 Subject: [PATCH 013/362] Fix associated item visibility in block-local impls --- crates/hir-def/src/nameres/collector.rs | 12 +++--- crates/hir-def/src/nameres/path_resolution.rs | 4 +- crates/hir-def/src/resolver.rs | 4 +- crates/hir-def/src/visibility.rs | 2 +- .../src/handlers/private_assoc_item.rs | 38 +++++++++++++++++++ 5 files changed, 52 insertions(+), 8 deletions(-) diff --git a/crates/hir-def/src/nameres/collector.rs b/crates/hir-def/src/nameres/collector.rs index 4b39a20d86c6..e3704bf2164b 100644 --- a/crates/hir-def/src/nameres/collector.rs +++ b/crates/hir-def/src/nameres/collector.rs @@ -666,8 +666,10 @@ impl DefCollector<'_> { macro_: Macro2Id, vis: &RawVisibility, ) { - let vis = - self.def_map.resolve_visibility(self.db, module_id, vis).unwrap_or(Visibility::Public); + let vis = self + .def_map + .resolve_visibility(self.db, module_id, vis, false) + .unwrap_or(Visibility::Public); self.def_map.modules[module_id].scope.declare(macro_.into()); self.update( module_id, @@ -831,7 +833,7 @@ impl DefCollector<'_> { let mut def = directive.status.namespaces(); let vis = self .def_map - .resolve_visibility(self.db, module_id, &directive.import.visibility) + .resolve_visibility(self.db, module_id, &directive.import.visibility, false) .unwrap_or(Visibility::Public); match import.kind { @@ -1547,7 +1549,7 @@ impl ModCollector<'_, '_> { }; let resolve_vis = |def_map: &DefMap, visibility| { def_map - .resolve_visibility(db, self.module_id, visibility) + .resolve_visibility(db, self.module_id, visibility, false) .unwrap_or(Visibility::Public) }; @@ -1823,7 +1825,7 @@ impl ModCollector<'_, '_> { ) -> LocalModuleId { let def_map = &mut self.def_collector.def_map; let vis = def_map - .resolve_visibility(self.def_collector.db, self.module_id, visibility) + .resolve_visibility(self.def_collector.db, self.module_id, visibility, false) .unwrap_or(Visibility::Public); let modules = &mut def_map.modules; let origin = match definition { diff --git a/crates/hir-def/src/nameres/path_resolution.rs b/crates/hir-def/src/nameres/path_resolution.rs index 1d9d5cccded2..25478481dd0b 100644 --- a/crates/hir-def/src/nameres/path_resolution.rs +++ b/crates/hir-def/src/nameres/path_resolution.rs @@ -78,6 +78,7 @@ impl DefMap { // pub(path) // ^^^^ this visibility: &RawVisibility, + within_impl: bool, ) -> Option { let mut vis = match visibility { RawVisibility::Module(path) => { @@ -102,7 +103,8 @@ impl DefMap { // `super` to its parent (etc.). However, visibilities must only refer to a module in the // DefMap they're written in, so we restrict them when that happens. if let Visibility::Module(m) = vis { - if self.block_id() != m.block { + // ...unless we're resolving visibility for an associated item in an impl. + if self.block_id() != m.block && !within_impl { cov_mark::hit!(adjust_vis_in_block_def_map); vis = Visibility::Module(self.module_id(self.root())); tracing::debug!("visibility {:?} points outside DefMap, adjusting to {:?}", m, vis); diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs index 86958e3daea4..0a44f65ad4a0 100644 --- a/crates/hir-def/src/resolver.rs +++ b/crates/hir-def/src/resolver.rs @@ -214,10 +214,12 @@ impl Resolver { db: &dyn DefDatabase, visibility: &RawVisibility, ) -> Option { + let within_impl = + self.scopes().find(|scope| matches!(scope, Scope::ImplDefScope(_))).is_some(); match visibility { RawVisibility::Module(_) => { let (item_map, module) = self.item_scope(); - item_map.resolve_visibility(db, module, visibility) + item_map.resolve_visibility(db, module, visibility, within_impl) } RawVisibility::Public => Some(Visibility::Public), } diff --git a/crates/hir-def/src/visibility.rs b/crates/hir-def/src/visibility.rs index eee73b9f3738..c9fcaae56cf0 100644 --- a/crates/hir-def/src/visibility.rs +++ b/crates/hir-def/src/visibility.rs @@ -120,7 +120,7 @@ impl Visibility { self, db: &dyn DefDatabase, def_map: &DefMap, - mut from_module: crate::LocalModuleId, + mut from_module: LocalModuleId, ) -> bool { let mut to_module = match self { Visibility::Module(m) => m, diff --git a/crates/ide-diagnostics/src/handlers/private_assoc_item.rs b/crates/ide-diagnostics/src/handlers/private_assoc_item.rs index 0b3121c765d8..67da5c7f27d1 100644 --- a/crates/ide-diagnostics/src/handlers/private_assoc_item.rs +++ b/crates/ide-diagnostics/src/handlers/private_assoc_item.rs @@ -115,6 +115,44 @@ mod module { fn main(s: module::Struct) { s.method(); } +"#, + ); + } + + #[test] + fn can_see_through_top_level_anonymous_const() { + // regression test for #14046. + check_diagnostics( + r#" +struct S; +mod m { + const _: () = { + impl crate::S { + pub(crate) fn method(self) {} + pub(crate) const A: usize = 42; + } + }; + mod inner { + const _: () = { + impl crate::S { + pub(crate) fn method2(self) {} + pub(crate) const B: usize = 42; + pub(super) fn private(self) {} + pub(super) const PRIVATE: usize = 42; + } + }; + } +} +fn main() { + S.method(); + S::A; + S.method2(); + S::B; + S.private(); + //^^^^^^^^^^^ error: function `private` is private + S::PRIVATE; + //^^^^^^^^^^ error: const `PRIVATE` is private +} "#, ); } From 2351875e6a2e2f30787624002440ea9690b7cc12 Mon Sep 17 00:00:00 2001 From: jmviz Date: Sun, 19 Feb 2023 10:12:44 -0500 Subject: [PATCH 014/362] change titles of commands in context menu to title case. shorten open docs command --- editors/code/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/editors/code/package.json b/editors/code/package.json index b206c15f34c2..243208c4962b 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -226,7 +226,7 @@ }, { "command": "rust-analyzer.openDocs", - "title": "Open docs under cursor", + "title": "Open Docs", "category": "rust-analyzer" }, { @@ -236,7 +236,7 @@ }, { "command": "rust-analyzer.peekTests", - "title": "Peek related tests", + "title": "Peek Related Tests", "category": "rust-analyzer" }, { From d4166234ef54a1019fe200adb414d0580133cd69 Mon Sep 17 00:00:00 2001 From: Ryo Yoshida Date: Sun, 19 Feb 2023 23:32:24 +0900 Subject: [PATCH 015/362] Adjust block-local impl item visibility rendering --- crates/hir/src/display.rs | 31 ++++-- crates/hir/src/lib.rs | 16 ++- crates/ide/src/hover/tests.rs | 178 ++++++++++++++++++++++++++++++++++ 3 files changed, 216 insertions(+), 9 deletions(-) diff --git a/crates/hir/src/display.rs b/crates/hir/src/display.rs index 830d261d7869..66bf2a2900e8 100644 --- a/crates/hir/src/display.rs +++ b/crates/hir/src/display.rs @@ -17,15 +17,23 @@ use hir_ty::{ }; use crate::{ - Adt, Const, ConstParam, Enum, Field, Function, GenericParam, HasCrate, HasVisibility, - LifetimeParam, Macro, Module, Static, Struct, Trait, TyBuilder, Type, TypeAlias, - TypeOrConstParam, TypeParam, Union, Variant, + Adt, AsAssocItem, AssocItemContainer, Const, ConstParam, Enum, Field, Function, GenericParam, + HasCrate, HasVisibility, LifetimeParam, Macro, Module, Static, Struct, Trait, TyBuilder, Type, + TypeAlias, TypeOrConstParam, TypeParam, Union, Variant, }; impl HirDisplay for Function { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { - let data = f.db.function_data(self.id); - write_visibility(self.module(f.db).id, self.visibility(f.db), f)?; + let db = f.db; + let data = db.function_data(self.id); + let container = self.as_assoc_item(db).map(|it| it.container(db)); + let mut module = self.module(db); + if let Some(AssocItemContainer::Impl(_)) = container { + // Block-local impls are "hoisted" to the nearest (non-block) module. + module = module.nearest_non_block_module(db); + } + let module_id = module.id; + write_visibility(module_id, self.visibility(db), f)?; if data.has_default_kw() { f.write_str("default ")?; } @@ -35,7 +43,7 @@ impl HirDisplay for Function { if data.has_async_kw() { f.write_str("async ")?; } - if self.is_unsafe_to_call(f.db) { + if self.is_unsafe_to_call(db) { f.write_str("unsafe ")?; } if let Some(abi) = &data.abi { @@ -442,8 +450,15 @@ fn write_where_clause(def: GenericDefId, f: &mut HirFormatter<'_>) -> Result<(), impl HirDisplay for Const { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { - write_visibility(self.module(f.db).id, self.visibility(f.db), f)?; - let data = f.db.const_data(self.id); + let db = f.db; + let container = self.as_assoc_item(db).map(|it| it.container(db)); + let mut module = self.module(db); + if let Some(AssocItemContainer::Impl(_)) = container { + // Block-local impls are "hoisted" to the nearest (non-block) module. + module = module.nearest_non_block_module(db); + } + write_visibility(module.id, self.visibility(db), f)?; + let data = db.const_data(self.id); f.write_str("const ")?; match &data.name { Some(name) => write!(f, "{name}: ")?, diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 2cb4ed2c3351..4db0e20098c3 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -46,7 +46,7 @@ use hir_def::{ item_tree::ItemTreeNode, lang_item::{LangItem, LangItemTarget}, layout::{Layout, LayoutError, ReprOptions}, - nameres::{self, diagnostics::DefDiagnostic}, + nameres::{self, diagnostics::DefDiagnostic, ModuleOrigin}, per_ns::PerNs, resolver::{HasResolver, Resolver}, src::HasSource as _, @@ -488,6 +488,20 @@ impl Module { Some(Module { id: def_map.module_id(parent_id) }) } + /// Finds nearest non-block ancestor `Module` (`self` included). + fn nearest_non_block_module(self, db: &dyn HirDatabase) -> Module { + let mut id = self.id; + loop { + let def_map = id.def_map(db.upcast()); + let origin = def_map[id.local_id].origin; + if matches!(origin, ModuleOrigin::BlockExpr { .. }) { + id = id.containing_module(db.upcast()).expect("block without parent module") + } else { + return Module { id }; + } + } + } + pub fn path_to_root(self, db: &dyn HirDatabase) -> Vec { let mut res = vec![self]; let mut curr = self; diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index bd7ce2f1d0d0..c199d1040af7 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -5647,3 +5647,181 @@ fn main() { "#]], ); } + +#[test] +fn assoc_fn_in_block_local_impl() { + check( + r#" +struct S; +mod m { + const _: () = { + impl crate::S { + pub(crate) fn foo() {} + } + }; +} +fn test() { + S::foo$0(); +} +"#, + expect![[r#" + *foo* + + ```rust + test::S + ``` + + ```rust + pub(crate) fn foo() + ``` + "#]], + ); + + check( + r#" +struct S; +mod m { + const _: () = { + const _: () = { + impl crate::S { + pub(crate) fn foo() {} + } + }; + }; +} +fn test() { + S::foo$0(); +} +"#, + expect![[r#" + *foo* + + ```rust + test::S + ``` + + ```rust + pub(crate) fn foo() + ``` + "#]], + ); + + check( + r#" +struct S; +mod m { + mod inner { + const _: () = { + impl crate::S { + pub(super) fn foo() {} + } + }; + } + + fn test() { + crate::S::foo$0(); + } +} +"#, + expect![[r#" + *foo* + + ```rust + test::S + ``` + + ```rust + pub(super) fn foo() + ``` + "#]], + ); +} + +#[test] +fn assoc_const_in_block_local_impl() { + check( + r#" +struct S; +mod m { + const _: () = { + impl crate::S { + pub(crate) const A: () = (); + } + }; +} +fn test() { + S::A$0; +} +"#, + expect![[r#" + *A* + + ```rust + test + ``` + + ```rust + pub(crate) const A: () = () + ``` + "#]], + ); + + check( + r#" +struct S; +mod m { + const _: () = { + const _: () = { + impl crate::S { + pub(crate) const A: () = (); + } + }; + }; +} +fn test() { + S::A$0; +} +"#, + expect![[r#" + *A* + + ```rust + test + ``` + + ```rust + pub(crate) const A: () = () + ``` + "#]], + ); + + check( + r#" +struct S; +mod m { + mod inner { + const _: () = { + impl crate::S { + pub(super) const A: () = (); + } + }; + } + + fn test() { + crate::S::A$0; + } +} +"#, + expect![[r#" + *A* + + ```rust + test + ``` + + ```rust + pub(super) const A: () = () + ``` + "#]], + ); +} From 7e711da2f07778d62f6411de5da520f1e260d761 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Mon, 20 Feb 2023 10:14:12 +0200 Subject: [PATCH 016/362] :arrow_up: rust-analyzer --- Cargo.lock | 6 +- Cargo.toml | 2 + crates/hir-def/Cargo.toml | 2 +- crates/hir-def/src/adt.rs | 5 +- crates/hir-def/src/body.rs | 140 ++++++---- crates/hir-def/src/body/lower.rs | 6 + crates/hir-def/src/body/tests.rs | 13 + crates/hir-def/src/item_tree/lower.rs | 9 +- .../hir-def/src/macro_expansion_tests/mbe.rs | 2 +- .../macro_expansion_tests/mbe/regression.rs | 21 +- crates/hir-def/src/path.rs | 62 +++-- crates/hir-def/src/path/lower.rs | 84 ++++-- crates/hir-def/src/pretty.rs | 2 +- crates/hir-def/src/type_ref.rs | 6 +- crates/hir-expand/Cargo.toml | 2 +- crates/hir-expand/src/lib.rs | 4 + crates/hir-ty/Cargo.toml | 2 +- crates/hir-ty/src/display.rs | 6 +- crates/hir-ty/src/lower.rs | 4 +- crates/hir/Cargo.toml | 2 +- crates/ide-assists/Cargo.toml | 2 +- .../src/handlers/generate_getter.rs | 107 +++++++- crates/ide-assists/src/tests.rs | 42 ++- crates/ide-completion/Cargo.toml | 2 +- .../ide-completion/src/completions/postfix.rs | 12 + crates/ide-completion/src/context.rs | 37 ++- crates/ide-completion/src/context/analysis.rs | 160 ++++++----- crates/ide-completion/src/item.rs | 104 +++----- crates/ide-completion/src/lib.rs | 18 +- crates/ide-completion/src/render.rs | 27 +- crates/ide-completion/src/render/literal.rs | 2 +- .../src/render/union_literal.rs | 2 +- crates/ide-completion/src/tests.rs | 45 ++-- crates/ide-completion/src/tests/expression.rs | 252 ++++++++++++++++++ crates/ide-completion/src/tests/special.rs | 81 +++--- crates/ide-db/Cargo.toml | 3 +- crates/ide-db/src/active_parameter.rs | 80 +++++- crates/ide-db/src/line_index.rs | 193 +++++++------- crates/ide-db/src/search.rs | 82 +++--- crates/ide/Cargo.toml | 2 +- crates/ide/src/hover.rs | 17 ++ crates/ide/src/hover/tests.rs | 35 +++ crates/ide/src/inlay_hints/binding_mode.rs | 29 +- crates/ide/src/lib.rs | 2 +- crates/ide/src/references.rs | 15 ++ crates/ide/src/rename.rs | 5 +- crates/ide/src/shuffle_crate_graph.rs | 22 +- crates/ide/src/signature_help.rs | 124 ++++----- crates/ide/src/syntax_highlighting/tests.rs | 2 +- crates/mbe/Cargo.toml | 2 +- crates/parser/Cargo.toml | 1 + crates/parser/src/grammar.rs | 30 +++ crates/parser/src/grammar/attributes.rs | 2 + crates/parser/src/grammar/expressions.rs | 39 ++- crates/parser/src/grammar/expressions/atom.rs | 34 +-- crates/parser/src/grammar/generic_args.rs | 32 ++- crates/parser/src/grammar/generic_params.rs | 29 +- crates/parser/src/grammar/items/adt.rs | 25 +- crates/parser/src/grammar/params.rs | 14 +- crates/parser/src/grammar/paths.rs | 11 +- crates/parser/src/grammar/types.rs | 3 +- crates/parser/src/tests.rs | 6 + crates/parser/src/tests/top_entries.rs | 2 +- .../0009_broken_struct_type_parameter.rast | 3 +- .../parser/err/0013_invalid_type.rast | 38 ++- .../test_data/parser/err/0022_bad_exprs.rast | 18 +- .../parser/err/0024_many_type_parens.rast | 211 +++++++-------- .../test_data/parser/err/0025_nope.rast | 7 +- .../parser/err/0042_weird_blocks.rast | 2 +- .../parser/err/0048_double_fish.rast | 19 +- .../inline/err/0002_misplaced_label_err.rast | 2 +- .../inline/err/0015_arg_list_recovery.rast | 77 ++++++ .../inline/err/0015_arg_list_recovery.rs | 5 + .../err/0015_missing_fn_param_type.rast | 2 +- crates/proc-macro-api/src/version.rs | 13 +- crates/rust-analyzer/Cargo.toml | 2 +- .../default_12483297303756020505_0.profraw | Bin 0 -> 25152 bytes crates/rust-analyzer/src/caps.rs | 17 +- crates/rust-analyzer/src/cli/lsif.rs | 5 +- crates/rust-analyzer/src/config.rs | 8 +- .../rust-analyzer/src/diagnostics/to_proto.rs | 4 +- crates/rust-analyzer/src/from_proto.rs | 13 +- crates/rust-analyzer/src/line_index.rs | 5 +- crates/rust-analyzer/src/lsp_ext.rs | 27 +- crates/rust-analyzer/src/lsp_utils.rs | 38 ++- crates/rust-analyzer/src/main_loop.rs | 1 + crates/rust-analyzer/src/reload.rs | 5 +- crates/rust-analyzer/src/to_proto.rs | 66 ++--- crates/rust-analyzer/tests/slow-tests/main.rs | 4 +- .../rust-analyzer/tests/slow-tests/support.rs | 1 + crates/stdx/src/lib.rs | 1 + crates/stdx/src/rand.rs | 21 ++ crates/syntax/src/lib.rs | 2 +- docs/dev/architecture.md | 2 +- docs/dev/lsp-extensions.md | 2 +- editors/code/src/commands.ts | 6 + editors/code/src/main.ts | 1 + lib/lsp-server/Cargo.toml | 2 +- 98 files changed, 1801 insertions(+), 943 deletions(-) create mode 100644 crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rast create mode 100644 crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rs create mode 100644 crates/rust-analyzer/default_12483297303756020505_0.profraw create mode 100644 crates/stdx/src/rand.rs diff --git a/Cargo.lock b/Cargo.lock index ef0316f30fb9..ec197767259d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -711,6 +711,7 @@ dependencies = [ "limit", "memchr", "once_cell", + "oorandom", "parser", "profile", "rayon", @@ -932,9 +933,9 @@ dependencies = [ [[package]] name = "lsp-types" -version = "0.93.2" +version = "0.94.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be6e9c7e2d18f651974370d7aff703f9513e0df6e464fd795660edc77e6ca51" +checksum = "0b63735a13a1f9cd4f4835223d828ed9c2e35c8c5e61837774399f558b6a1237" dependencies = [ "bitflags", "serde", @@ -1173,6 +1174,7 @@ dependencies = [ "limit", "rustc-ap-rustc_lexer", "sourcegen", + "stdx", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ef81105505b0..333f03ce2ffe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,3 +74,5 @@ toolchain = { path = "./crates/toolchain", version = "0.0.0" } tt = { path = "./crates/tt", version = "0.0.0" } vfs-notify = { path = "./crates/vfs-notify", version = "0.0.0" } vfs = { path = "./crates/vfs", version = "0.0.0" } +# non-local crates +smallvec = { version = "1.10.0", features = ["const_new", "union", "const_generics"] } diff --git a/crates/hir-def/Cargo.toml b/crates/hir-def/Cargo.toml index 1daf0428c242..31d4018d2b6a 100644 --- a/crates/hir-def/Cargo.toml +++ b/crates/hir-def/Cargo.toml @@ -27,7 +27,7 @@ itertools = "0.10.5" la-arena = { version = "0.3.0", path = "../../lib/la-arena" } once_cell = "1.17.0" rustc-hash = "1.1.0" -smallvec = "1.10.0" +smallvec.workspace = true tracing = "0.1.35" rustc_abi = { version = "0.0.20221221", package = "hkalbasi-rustc-ap-rustc_abi", default-features = false } diff --git a/crates/hir-def/src/adt.rs b/crates/hir-def/src/adt.rs index dcea679567a5..9bc1c54a3c64 100644 --- a/crates/hir-def/src/adt.rs +++ b/crates/hir-def/src/adt.rs @@ -2,9 +2,10 @@ use std::sync::Arc; -use crate::tt::{Delimiter, DelimiterKind, Leaf, Subtree, TokenTree}; use base_db::CrateId; +use cfg::CfgOptions; use either::Either; + use hir_expand::{ name::{AsName, Name}, HirFileId, InFile, @@ -24,12 +25,12 @@ use crate::{ src::HasChildSource, src::HasSource, trace::Trace, + tt::{Delimiter, DelimiterKind, Leaf, Subtree, TokenTree}, type_ref::TypeRef, visibility::RawVisibility, EnumId, LocalEnumVariantId, LocalFieldId, LocalModuleId, Lookup, ModuleId, StructId, UnionId, VariantId, }; -use cfg::CfgOptions; /// Note that we use `StructData` for unions as well! #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/crates/hir-def/src/body.rs b/crates/hir-def/src/body.rs index 9713256813eb..8fd9255b8b13 100644 --- a/crates/hir-def/src/body.rs +++ b/crates/hir-def/src/body.rs @@ -19,7 +19,7 @@ use la_arena::{Arena, ArenaMap}; use limit::Limit; use profile::Count; use rustc_hash::FxHashMap; -use syntax::{ast, AstPtr, SyntaxNodePtr}; +use syntax::{ast, AstPtr, SyntaxNode, SyntaxNodePtr}; use crate::{ attr::Attrs, @@ -51,7 +51,8 @@ pub struct Expander { def_map: Arc, current_file_id: HirFileId, module: LocalModuleId, - recursion_limit: usize, + /// `recursion_depth == usize::MAX` indicates that the recursion limit has been reached. + recursion_depth: usize, } impl CfgExpander { @@ -84,7 +85,7 @@ impl Expander { def_map, current_file_id, module: module.local_id, - recursion_limit: 0, + recursion_depth: 0, } } @@ -93,31 +94,37 @@ impl Expander { db: &dyn DefDatabase, macro_call: ast::MacroCall, ) -> Result>, UnresolvedMacro> { - if self.recursion_limit(db).check(self.recursion_limit + 1).is_err() { - cov_mark::hit!(your_stack_belongs_to_me); - return Ok(ExpandResult::only_err(ExpandError::Other( - "reached recursion limit during macro expansion".into(), - ))); + let mut unresolved_macro_err = None; + + let result = self.within_limit(db, |this| { + let macro_call = InFile::new(this.current_file_id, ¯o_call); + + let resolver = + |path| this.resolve_path_as_macro(db, &path).map(|it| macro_id_to_def_id(db, it)); + + let mut err = None; + let call_id = match macro_call.as_call_id_with_errors( + db, + this.def_map.krate(), + resolver, + &mut |e| { + err.get_or_insert(e); + }, + ) { + Ok(call_id) => call_id, + Err(resolve_err) => { + unresolved_macro_err = Some(resolve_err); + return ExpandResult { value: None, err: None }; + } + }; + ExpandResult { value: call_id.ok(), err } + }); + + if let Some(err) = unresolved_macro_err { + Err(err) + } else { + Ok(result) } - - let macro_call = InFile::new(self.current_file_id, ¯o_call); - - let resolver = - |path| self.resolve_path_as_macro(db, &path).map(|it| macro_id_to_def_id(db, it)); - - let mut err = None; - let call_id = - macro_call.as_call_id_with_errors(db, self.def_map.krate(), resolver, &mut |e| { - err.get_or_insert(e); - })?; - let call_id = match call_id { - Ok(it) => it, - Err(_) => { - return Ok(ExpandResult { value: None, err }); - } - }; - - Ok(self.enter_expand_inner(db, call_id, err)) } pub fn enter_expand_id( @@ -125,15 +132,14 @@ impl Expander { db: &dyn DefDatabase, call_id: MacroCallId, ) -> ExpandResult> { - self.enter_expand_inner(db, call_id, None) + self.within_limit(db, |_this| ExpandResult::ok(Some(call_id))) } - fn enter_expand_inner( - &mut self, + fn enter_expand_inner( db: &dyn DefDatabase, call_id: MacroCallId, mut err: Option, - ) -> ExpandResult> { + ) -> ExpandResult> { if err.is_none() { err = db.macro_expand_error(call_id); } @@ -154,29 +160,21 @@ impl Expander { } }; - let node = match T::cast(raw_node) { - Some(it) => it, - None => { - // This can happen without being an error, so only forward previous errors. - return ExpandResult { value: None, err }; - } - }; - - tracing::debug!("macro expansion {:#?}", node.syntax()); - - self.recursion_limit += 1; - let mark = - Mark { file_id: self.current_file_id, bomb: DropBomb::new("expansion mark dropped") }; - self.cfg_expander.hygiene = Hygiene::new(db.upcast(), file_id); - self.current_file_id = file_id; - - ExpandResult { value: Some((mark, node)), err } + ExpandResult { value: Some((file_id, raw_node)), err } } pub fn exit(&mut self, db: &dyn DefDatabase, mut mark: Mark) { self.cfg_expander.hygiene = Hygiene::new(db.upcast(), mark.file_id); self.current_file_id = mark.file_id; - self.recursion_limit -= 1; + if self.recursion_depth == usize::MAX { + // Recursion limit has been reached somewhere in the macro expansion tree. Reset the + // depth only when we get out of the tree. + if !self.current_file_id.is_macro() { + self.recursion_depth = 0; + } + } else { + self.recursion_depth -= 1; + } mark.bomb.defuse(); } @@ -215,6 +213,50 @@ impl Expander { #[cfg(test)] return Limit::new(std::cmp::min(32, limit)); } + + fn within_limit( + &mut self, + db: &dyn DefDatabase, + op: F, + ) -> ExpandResult> + where + F: FnOnce(&mut Self) -> ExpandResult>, + { + if self.recursion_depth == usize::MAX { + // Recursion limit has been reached somewhere in the macro expansion tree. We should + // stop expanding other macro calls in this tree, or else this may result in + // exponential number of macro expansions, leading to a hang. + // + // The overflow error should have been reported when it occurred (see the next branch), + // so don't return overflow error here to avoid diagnostics duplication. + cov_mark::hit!(overflow_but_not_me); + return ExpandResult::only_err(ExpandError::RecursionOverflowPosioned); + } else if self.recursion_limit(db).check(self.recursion_depth + 1).is_err() { + self.recursion_depth = usize::MAX; + cov_mark::hit!(your_stack_belongs_to_me); + return ExpandResult::only_err(ExpandError::Other( + "reached recursion limit during macro expansion".into(), + )); + } + + let ExpandResult { value, err } = op(self); + let Some(call_id) = value else { + return ExpandResult { value: None, err }; + }; + + Self::enter_expand_inner(db, call_id, err).map(|value| { + value.and_then(|(new_file_id, node)| { + let node = T::cast(node)?; + + self.recursion_depth += 1; + self.cfg_expander.hygiene = Hygiene::new(db.upcast(), new_file_id); + let old_file_id = std::mem::replace(&mut self.current_file_id, new_file_id); + let mark = + Mark { file_id: old_file_id, bomb: DropBomb::new("expansion mark dropped") }; + Some((mark, node)) + }) + }) + } } #[derive(Debug)] diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs index a78fa91f53bd..04b1c4f01e22 100644 --- a/crates/hir-def/src/body/lower.rs +++ b/crates/hir-def/src/body/lower.rs @@ -624,6 +624,10 @@ impl ExprCollector<'_> { krate: *krate, }); } + Some(ExpandError::RecursionOverflowPosioned) => { + // Recursion limit has been reached in the macro expansion tree, but not in + // this very macro call. Don't add diagnostics to avoid duplication. + } Some(err) => { self.source_map.diagnostics.push(BodyDiagnostic::MacroError { node: InFile::new(outer_file, syntax_ptr), @@ -636,6 +640,8 @@ impl ExprCollector<'_> { match res.value { Some((mark, expansion)) => { + // Keep collecting even with expansion errors so we can provide completions and + // other services in incomplete macro expressions. self.source_map.expansions.insert(macro_call_ptr, self.expander.current_file_id); let prev_ast_id_map = mem::replace( &mut self.ast_id_map, diff --git a/crates/hir-def/src/body/tests.rs b/crates/hir-def/src/body/tests.rs index c9601f855273..edee2c7ff96b 100644 --- a/crates/hir-def/src/body/tests.rs +++ b/crates/hir-def/src/body/tests.rs @@ -61,6 +61,19 @@ fn main() { n_nuple!(1,2,3); } ); } +#[test] +fn your_stack_belongs_to_me2() { + cov_mark::check!(overflow_but_not_me); + lower( + r#" +macro_rules! foo { + () => {{ foo!(); foo!(); }} +} +fn main() { foo!(); } +"#, + ); +} + #[test] fn recursion_limit() { cov_mark::check!(your_stack_belongs_to_me); diff --git a/crates/hir-def/src/item_tree/lower.rs b/crates/hir-def/src/item_tree/lower.rs index 27705cbbbdc5..d4d3c5ef19a6 100644 --- a/crates/hir-def/src/item_tree/lower.rs +++ b/crates/hir-def/src/item_tree/lower.rs @@ -659,15 +659,16 @@ fn desugar_future_path(orig: TypeRef) -> Path { let path = path![core::future::Future]; let mut generic_args: Vec<_> = std::iter::repeat(None).take(path.segments().len() - 1).collect(); - let mut last = GenericArgs::empty(); let binding = AssociatedTypeBinding { name: name![Output], args: None, type_ref: Some(orig), - bounds: Vec::new(), + bounds: Box::default(), }; - last.bindings.push(binding); - generic_args.push(Some(Interned::new(last))); + generic_args.push(Some(Interned::new(GenericArgs { + bindings: Box::new([binding]), + ..GenericArgs::empty() + }))); Path::from_known_path(path, generic_args) } diff --git a/crates/hir-def/src/macro_expansion_tests/mbe.rs b/crates/hir-def/src/macro_expansion_tests/mbe.rs index 49bbc64bff18..7a3e8c3b05c9 100644 --- a/crates/hir-def/src/macro_expansion_tests/mbe.rs +++ b/crates/hir-def/src/macro_expansion_tests/mbe.rs @@ -1476,7 +1476,7 @@ macro_rules! m { /* parse error: expected identifier */ /* parse error: expected SEMICOLON */ /* parse error: expected SEMICOLON */ -/* parse error: expected expression */ +/* parse error: expected expression, item or let statement */ fn f() { K::(C("0")); } diff --git a/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs b/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs index d2505e7cafe5..8358a46f0a91 100644 --- a/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs +++ b/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs @@ -830,8 +830,7 @@ macro_rules! rgb_color { /* parse error: expected COMMA */ /* parse error: expected R_ANGLE */ /* parse error: expected SEMICOLON */ -/* parse error: expected SEMICOLON */ -/* parse error: expected expression */ +/* parse error: expected expression, item or let statement */ pub fn new() { let _ = 0as u32<<(8+8); } @@ -848,21 +847,21 @@ pub fn new() { // BLOCK_EXPR@10..31 // STMT_LIST@10..31 // L_CURLY@10..11 "{" -// LET_STMT@11..27 +// LET_STMT@11..28 // LET_KW@11..14 "let" // WILDCARD_PAT@14..15 // UNDERSCORE@14..15 "_" // EQ@15..16 "=" -// CAST_EXPR@16..27 +// CAST_EXPR@16..28 // LITERAL@16..17 // INT_NUMBER@16..17 "0" // AS_KW@17..19 "as" -// PATH_TYPE@19..27 -// PATH@19..27 -// PATH_SEGMENT@19..27 +// PATH_TYPE@19..28 +// PATH@19..28 +// PATH_SEGMENT@19..28 // NAME_REF@19..22 // IDENT@19..22 "u32" -// GENERIC_ARG_LIST@22..27 +// GENERIC_ARG_LIST@22..28 // L_ANGLE@22..23 "<" // TYPE_ARG@23..27 // DYN_TRAIT_TYPE@23..27 @@ -877,9 +876,9 @@ pub fn new() { // ERROR@25..26 // INT_NUMBER@25..26 "8" // PLUS@26..27 "+" -// EXPR_STMT@27..28 -// LITERAL@27..28 -// INT_NUMBER@27..28 "8" +// CONST_ARG@27..28 +// LITERAL@27..28 +// INT_NUMBER@27..28 "8" // ERROR@28..29 // R_PAREN@28..29 ")" // SEMICOLON@29..30 ";" diff --git a/crates/hir-def/src/path.rs b/crates/hir-def/src/path.rs index 25a23fcd61a5..36d4c36a2689 100644 --- a/crates/hir-def/src/path.rs +++ b/crates/hir-def/src/path.rs @@ -38,18 +38,18 @@ impl Display for ImportAlias { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Path { /// Type based path like `::foo`. - /// Note that paths like `::foo` are desugard to `Trait::::foo`. + /// Note that paths like `::foo` are desugared to `Trait::::foo`. type_anchor: Option>, mod_path: Interned, - /// Invariant: the same len as `self.mod_path.segments` - generic_args: Box<[Option>]>, + /// Invariant: the same len as `self.mod_path.segments` or `None` if all segments are `None`. + generic_args: Option>]>>, } /// Generic arguments to a path segment (e.g. the `i32` in `Option`). This /// also includes bindings of associated types, like in `Iterator`. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct GenericArgs { - pub args: Vec, + pub args: Box<[GenericArg]>, /// This specifies whether the args contain a Self type as the first /// element. This is the case for path segments like ``, where /// `T` is actually a type parameter for the path `Trait` specifying the @@ -57,7 +57,7 @@ pub struct GenericArgs { /// is left out. pub has_self_type: bool, /// Associated type bindings like in `Iterator`. - pub bindings: Vec, + pub bindings: Box<[AssociatedTypeBinding]>, /// Whether these generic args were desugared from `Trait(Arg) -> Output` /// parenthesis notation typically used for the `Fn` traits. pub desugared_from_fn: bool, @@ -77,7 +77,7 @@ pub struct AssociatedTypeBinding { /// Bounds for the associated type, like in `Iterator`. (This is the unstable `associated_type_bounds` /// feature.) - pub bounds: Vec>, + pub bounds: Box<[Interned]>, } /// A single generic argument. @@ -102,7 +102,7 @@ impl Path { ) -> Path { let generic_args = generic_args.into(); assert_eq!(path.len(), generic_args.len()); - Path { type_anchor: None, mod_path: Interned::new(path), generic_args } + Path { type_anchor: None, mod_path: Interned::new(path), generic_args: Some(generic_args) } } pub fn kind(&self) -> &PathKind { @@ -114,7 +114,14 @@ impl Path { } pub fn segments(&self) -> PathSegments<'_> { - PathSegments { segments: self.mod_path.segments(), generic_args: &self.generic_args } + let s = PathSegments { + segments: self.mod_path.segments(), + generic_args: self.generic_args.as_deref(), + }; + if let Some(generic_args) = s.generic_args { + assert_eq!(s.segments.len(), generic_args.len()); + } + s } pub fn mod_path(&self) -> &ModPath { @@ -131,13 +138,15 @@ impl Path { self.mod_path.kind, self.mod_path.segments()[..self.mod_path.segments().len() - 1].iter().cloned(), )), - generic_args: self.generic_args[..self.generic_args.len() - 1].to_vec().into(), + generic_args: self.generic_args.as_ref().map(|it| it[..it.len() - 1].to_vec().into()), }; Some(res) } pub fn is_self_type(&self) -> bool { - self.type_anchor.is_none() && *self.generic_args == [None] && self.mod_path.is_Self() + self.type_anchor.is_none() + && self.generic_args.as_deref().is_none() + && self.mod_path.is_Self() } } @@ -149,11 +158,11 @@ pub struct PathSegment<'a> { pub struct PathSegments<'a> { segments: &'a [Name], - generic_args: &'a [Option>], + generic_args: Option<&'a [Option>]>, } impl<'a> PathSegments<'a> { - pub const EMPTY: PathSegments<'static> = PathSegments { segments: &[], generic_args: &[] }; + pub const EMPTY: PathSegments<'static> = PathSegments { segments: &[], generic_args: None }; pub fn is_empty(&self) -> bool { self.len() == 0 } @@ -167,26 +176,29 @@ impl<'a> PathSegments<'a> { self.get(self.len().checked_sub(1)?) } pub fn get(&self, idx: usize) -> Option> { - assert_eq!(self.segments.len(), self.generic_args.len()); let res = PathSegment { name: self.segments.get(idx)?, - args_and_bindings: self.generic_args.get(idx).unwrap().as_ref().map(|it| &**it), + args_and_bindings: self.generic_args.and_then(|it| it.get(idx)?.as_deref()), }; Some(res) } pub fn skip(&self, len: usize) -> PathSegments<'a> { - assert_eq!(self.segments.len(), self.generic_args.len()); - PathSegments { segments: &self.segments[len..], generic_args: &self.generic_args[len..] } + PathSegments { + segments: &self.segments.get(len..).unwrap_or(&[]), + generic_args: self.generic_args.and_then(|it| it.get(len..)), + } } pub fn take(&self, len: usize) -> PathSegments<'a> { - assert_eq!(self.segments.len(), self.generic_args.len()); - PathSegments { segments: &self.segments[..len], generic_args: &self.generic_args[..len] } + PathSegments { + segments: &self.segments.get(..len).unwrap_or(&self.segments), + generic_args: self.generic_args.map(|it| it.get(..len).unwrap_or(it)), + } } pub fn iter(&self) -> impl Iterator> { - self.segments.iter().zip(self.generic_args.iter()).map(|(name, args)| PathSegment { - name, - args_and_bindings: args.as_ref().map(|it| &**it), - }) + self.segments + .iter() + .zip(self.generic_args.into_iter().flatten().chain(iter::repeat(&None))) + .map(|(name, args)| PathSegment { name, args_and_bindings: args.as_deref() }) } } @@ -200,9 +212,9 @@ impl GenericArgs { pub(crate) fn empty() -> GenericArgs { GenericArgs { - args: Vec::new(), + args: Box::default(), has_self_type: false, - bindings: Vec::new(), + bindings: Box::default(), desugared_from_fn: false, } } @@ -213,7 +225,7 @@ impl From for Path { Path { type_anchor: None, mod_path: Interned::new(ModPath::from_segments(PathKind::Plain, iter::once(name))), - generic_args: Box::new([None]), + generic_args: None, } } } diff --git a/crates/hir-def/src/path/lower.rs b/crates/hir-def/src/path/lower.rs index d570191595b6..c85a11db6d19 100644 --- a/crates/hir-def/src/path/lower.rs +++ b/crates/hir-def/src/path/lower.rs @@ -1,5 +1,7 @@ //! Transforms syntax into `Path` objects, ideally with accounting for hygiene +use std::iter; + use crate::type_ref::ConstScalarOrPath; use either::Either; @@ -45,8 +47,11 @@ pub(super) fn lower_path(mut path: ast::Path, ctx: &LowerCtx<'_>) -> Option { kind = PathKind::DollarCrate(crate_id); @@ -56,7 +61,6 @@ pub(super) fn lower_path(mut path: ast::Path, ctx: &LowerCtx<'_>) -> Option { segments.push(name![Self]); - generic_args.push(None) } ast::PathSegmentKind::Type { type_ref, trait_ref } => { assert!(path.qualifier().is_none()); // this can only occur at the first segment @@ -77,18 +81,33 @@ pub(super) fn lower_path(mut path: ast::Path, ctx: &LowerCtx<'_>) -> Option it.as_ref().clone(), - None => GenericArgs::empty(), - }; - args_inner.has_self_type = true; - args_inner.args.insert(0, GenericArg::Type(self_type)); - *last_segment = Some(Interned::new(args_inner)); + let last_segment = generic_args.get_mut(segments.len() - num_segments)?; + *last_segment = Some(Interned::new(match last_segment.take() { + Some(it) => GenericArgs { + args: iter::once(self_type) + .chain(it.args.iter().cloned()) + .collect(), + + has_self_type: true, + bindings: it.bindings.clone(), + desugared_from_fn: it.desugared_from_fn, + }, + None => GenericArgs { + args: Box::new([self_type]), + has_self_type: true, + ..GenericArgs::empty() + }, + })); } } } @@ -115,7 +134,10 @@ pub(super) fn lower_path(mut path: ast::Path, ctx: &LowerCtx<'_>) -> Option) -> Option Option { if let Some(q) = path.qualifier() { @@ -174,7 +200,7 @@ pub(super) fn lower_generic_args( .map(|it| Interned::new(TypeBound::from_ast(lower_ctx, it))) .collect() } else { - Vec::new() + Box::default() }; bindings.push(AssociatedTypeBinding { name, args, type_ref, bounds }); } @@ -195,7 +221,12 @@ pub(super) fn lower_generic_args( if args.is_empty() && bindings.is_empty() { return None; } - Some(GenericArgs { args, has_self_type: false, bindings, desugared_from_fn: false }) + Some(GenericArgs { + args: args.into_boxed_slice(), + has_self_type: false, + bindings: bindings.into_boxed_slice(), + desugared_from_fn: false, + }) } /// Collect `GenericArgs` from the parts of a fn-like path, i.e. `Fn(X, Y) @@ -205,33 +236,30 @@ fn lower_generic_args_from_fn_path( params: Option, ret_type: Option, ) -> Option { - let mut args = Vec::new(); - let mut bindings = Vec::new(); let params = params?; let mut param_types = Vec::new(); for param in params.params() { let type_ref = TypeRef::from_ast_opt(ctx, param.ty()); param_types.push(type_ref); } - let arg = GenericArg::Type(TypeRef::Tuple(param_types)); - args.push(arg); - if let Some(ret_type) = ret_type { + let args = Box::new([GenericArg::Type(TypeRef::Tuple(param_types))]); + let bindings = if let Some(ret_type) = ret_type { let type_ref = TypeRef::from_ast_opt(ctx, ret_type.ty()); - bindings.push(AssociatedTypeBinding { + Box::new([AssociatedTypeBinding { name: name![Output], args: None, type_ref: Some(type_ref), - bounds: Vec::new(), - }); + bounds: Box::default(), + }]) } else { // -> () let type_ref = TypeRef::Tuple(Vec::new()); - bindings.push(AssociatedTypeBinding { + Box::new([AssociatedTypeBinding { name: name![Output], args: None, type_ref: Some(type_ref), - bounds: Vec::new(), - }); - } + bounds: Box::default(), + }]) + }; Some(GenericArgs { args, has_self_type: false, bindings, desugared_from_fn: true }) } diff --git a/crates/hir-def/src/pretty.rs b/crates/hir-def/src/pretty.rs index 1c0bd204d309..2d45c8c8da1a 100644 --- a/crates/hir-def/src/pretty.rs +++ b/crates/hir-def/src/pretty.rs @@ -71,7 +71,7 @@ pub(crate) fn print_generic_args(generics: &GenericArgs, buf: &mut dyn Write) -> first = false; print_generic_arg(arg, buf)?; } - for binding in &generics.bindings { + for binding in generics.bindings.iter() { if !first { write!(buf, ", ")?; } diff --git a/crates/hir-def/src/type_ref.rs b/crates/hir-def/src/type_ref.rs index 8fa12c7aafda..9652b01b91bf 100644 --- a/crates/hir-def/src/type_ref.rs +++ b/crates/hir-def/src/type_ref.rs @@ -292,7 +292,7 @@ impl TypeRef { } for segment in path.segments().iter() { if let Some(args_and_bindings) = segment.args_and_bindings { - for arg in &args_and_bindings.args { + for arg in args_and_bindings.args.iter() { match arg { crate::path::GenericArg::Type(type_ref) => { go(type_ref, f); @@ -301,11 +301,11 @@ impl TypeRef { | crate::path::GenericArg::Lifetime(_) => {} } } - for binding in &args_and_bindings.bindings { + for binding in args_and_bindings.bindings.iter() { if let Some(type_ref) = &binding.type_ref { go(type_ref, f); } - for bound in &binding.bounds { + for bound in binding.bounds.iter() { match bound.as_ref() { TypeBound::Path(path, _) | TypeBound::ForLifetime(_, path) => { go_path(path, f) diff --git a/crates/hir-expand/Cargo.toml b/crates/hir-expand/Cargo.toml index 525cdc32b875..5c684be03cf2 100644 --- a/crates/hir-expand/Cargo.toml +++ b/crates/hir-expand/Cargo.toml @@ -21,7 +21,7 @@ itertools = "0.10.5" hashbrown = { version = "0.12.1", features = [ "inline-more", ], default-features = false } -smallvec = { version = "1.10.0", features = ["const_new"] } +smallvec.workspace = true # local deps stdx.workspace = true diff --git a/crates/hir-expand/src/lib.rs b/crates/hir-expand/src/lib.rs index bc941b541724..a52716cc02c2 100644 --- a/crates/hir-expand/src/lib.rs +++ b/crates/hir-expand/src/lib.rs @@ -55,6 +55,7 @@ pub type ExpandResult = ValueResult; pub enum ExpandError { UnresolvedProcMacro(CrateId), Mbe(mbe::ExpandError), + RecursionOverflowPosioned, Other(Box), } @@ -69,6 +70,9 @@ impl fmt::Display for ExpandError { match self { ExpandError::UnresolvedProcMacro(_) => f.write_str("unresolved proc-macro"), ExpandError::Mbe(it) => it.fmt(f), + ExpandError::RecursionOverflowPosioned => { + f.write_str("overflow expanding the original macro") + } ExpandError::Other(it) => f.write_str(it), } } diff --git a/crates/hir-ty/Cargo.toml b/crates/hir-ty/Cargo.toml index 490bbe1e7240..a8b8d5222e49 100644 --- a/crates/hir-ty/Cargo.toml +++ b/crates/hir-ty/Cargo.toml @@ -16,7 +16,7 @@ cov-mark = "2.0.0-pre.1" itertools = "0.10.5" arrayvec = "0.7.2" bitflags = "1.3.2" -smallvec = "1.10.0" +smallvec.workspace = true ena = "0.14.0" tracing = "0.1.35" rustc-hash = "1.1.0" diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs index 5fcbdf34f3cb..b22064d8c42e 100644 --- a/crates/hir-ty/src/display.rs +++ b/crates/hir-ty/src/display.rs @@ -1419,7 +1419,7 @@ impl HirDisplay for Path { write!(f, "<")?; let mut first = true; - for arg in &generic_args.args { + for arg in generic_args.args.iter() { if first { first = false; if generic_args.has_self_type { @@ -1431,7 +1431,7 @@ impl HirDisplay for Path { } arg.hir_fmt(f)?; } - for binding in &generic_args.bindings { + for binding in generic_args.bindings.iter() { if first { first = false; } else { @@ -1445,7 +1445,7 @@ impl HirDisplay for Path { } None => { write!(f, ": ")?; - f.write_joined(&binding.bounds, " + ")?; + f.write_joined(binding.bounds.iter(), " + ")?; } } } diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs index 7cce13a793e0..299646737221 100644 --- a/crates/hir-ty/src/lower.rs +++ b/crates/hir-ty/src/lower.rs @@ -1025,7 +1025,7 @@ impl<'a> TyLoweringContext<'a> { last_segment .into_iter() .filter_map(|segment| segment.args_and_bindings) - .flat_map(|args_and_bindings| &args_and_bindings.bindings) + .flat_map(|args_and_bindings| args_and_bindings.bindings.iter()) .flat_map(move |binding| { let found = associated_type_by_name_including_super_traits( self.db, @@ -1068,7 +1068,7 @@ impl<'a> TyLoweringContext<'a> { AliasEq { alias: AliasTy::Projection(projection_ty.clone()), ty }; preds.push(crate::wrap_empty_binders(WhereClause::AliasEq(alias_eq))); } - for bound in &binding.bounds { + for bound in binding.bounds.iter() { preds.extend(self.lower_type_bound( bound, TyKind::Alias(AliasTy::Projection(projection_ty.clone())).intern(Interner), diff --git a/crates/hir/Cargo.toml b/crates/hir/Cargo.toml index 32cde8a77325..ef40a8902d73 100644 --- a/crates/hir/Cargo.toml +++ b/crates/hir/Cargo.toml @@ -16,7 +16,7 @@ rustc-hash = "1.1.0" either = "1.7.0" arrayvec = "0.7.2" itertools = "0.10.5" -smallvec = "1.10.0" +smallvec.workspace = true once_cell = "1.17.0" # local deps diff --git a/crates/ide-assists/Cargo.toml b/crates/ide-assists/Cargo.toml index 3954abfdb7c4..447e38f91f43 100644 --- a/crates/ide-assists/Cargo.toml +++ b/crates/ide-assists/Cargo.toml @@ -16,7 +16,7 @@ cov-mark = "2.0.0-pre.1" itertools = "0.10.5" either = "1.7.0" -smallvec = "1.10.0" +smallvec.workspace = true # local deps stdx.workspace = true diff --git a/crates/ide-assists/src/handlers/generate_getter.rs b/crates/ide-assists/src/handlers/generate_getter.rs index 15641b448d00..4595cfe29c85 100644 --- a/crates/ide-assists/src/handlers/generate_getter.rs +++ b/crates/ide-assists/src/handlers/generate_getter.rs @@ -180,7 +180,9 @@ pub(crate) fn generate_getter_impl( // Insert `$0` only for last getter we generate if i == record_fields_count - 1 { - getter_buf = getter_buf.replacen("fn ", "fn $0", 1); + if ctx.config.snippet_cap.is_some() { + getter_buf = getter_buf.replacen("fn ", "fn $0", 1); + } } // For first element we do not merge with '\n', as @@ -330,7 +332,7 @@ fn parse_record_field(record_field: ast::RecordField, mutable: bool) -> Option &Data { + &self.data + } +} +"#, + ); + + check_assist_no_snippet_cap( + generate_getter_mut, + r#" +struct Context { + dat$0a: Data, +} +"#, + r#" +struct Context { + data: Data, +} + +impl Context { + fn data_mut(&mut self) -> &mut Data { + &mut self.data + } +} +"#, + ); + } + #[test] fn test_generate_getter_already_implemented() { check_assist_not_applicable( @@ -433,6 +478,29 @@ impl Context { ); } + #[test] + fn test_generate_getter_from_field_with_visibility_marker_no_snippet_cap() { + check_assist_no_snippet_cap( + generate_getter, + r#" +pub(crate) struct Context { + dat$0a: Data, +} +"#, + r#" +pub(crate) struct Context { + data: Data, +} + +impl Context { + pub(crate) fn data(&self) -> &Data { + &self.data + } +} +"#, + ); + } + #[test] fn test_multiple_generate_getter() { check_assist( @@ -468,6 +536,41 @@ impl Context { ); } + #[test] + fn test_multiple_generate_getter_no_snippet_cap() { + check_assist_no_snippet_cap( + generate_getter, + r#" +struct Context { + data: Data, + cou$0nt: usize, +} + +impl Context { + fn data(&self) -> &Data { + &self.data + } +} +"#, + r#" +struct Context { + data: Data, + count: usize, +} + +impl Context { + fn data(&self) -> &Data { + &self.data + } + + fn count(&self) -> &usize { + &self.count + } +} +"#, + ); + } + #[test] fn test_not_a_special_case() { cov_mark::check_count!(convert_reference_type, 0); diff --git a/crates/ide-assists/src/tests.rs b/crates/ide-assists/src/tests.rs index fca268a1f0b2..94be99fd7abf 100644 --- a/crates/ide-assists/src/tests.rs +++ b/crates/ide-assists/src/tests.rs @@ -33,6 +33,20 @@ pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig { assist_emit_must_use: false, }; +pub(crate) const TEST_CONFIG_NO_SNIPPET_CAP: AssistConfig = AssistConfig { + snippet_cap: None, + allowed: None, + insert_use: InsertUseConfig { + granularity: ImportGranularity::Crate, + prefix_kind: hir::PrefixKind::Plain, + enforce_granularity: true, + group: true, + skip_glob_imports: true, + }, + prefer_no_std: false, + assist_emit_must_use: false, +}; + pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { RootDatabase::with_single_file(text) } @@ -43,6 +57,22 @@ pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_ check(assist, ra_fixture_before, ExpectedResult::After(&ra_fixture_after), None); } +#[track_caller] +pub(crate) fn check_assist_no_snippet_cap( + assist: Handler, + ra_fixture_before: &str, + ra_fixture_after: &str, +) { + let ra_fixture_after = trim_indent(ra_fixture_after); + check_with_config( + TEST_CONFIG_NO_SNIPPET_CAP, + assist, + ra_fixture_before, + ExpectedResult::After(&ra_fixture_after), + None, + ); +} + // There is no way to choose what assist within a group you want to test against, // so this is here to allow you choose. pub(crate) fn check_assist_by_label( @@ -119,6 +149,17 @@ enum ExpectedResult<'a> { #[track_caller] fn check(handler: Handler, before: &str, expected: ExpectedResult<'_>, assist_label: Option<&str>) { + check_with_config(TEST_CONFIG, handler, before, expected, assist_label); +} + +#[track_caller] +fn check_with_config( + config: AssistConfig, + handler: Handler, + before: &str, + expected: ExpectedResult<'_>, + assist_label: Option<&str>, +) { let (mut db, file_with_caret_id, range_or_offset) = RootDatabase::with_range_or_offset(before); db.set_enable_proc_attr_macros(true); let text_without_caret = db.file_text(file_with_caret_id).to_string(); @@ -126,7 +167,6 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult<'_>, assist_la let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() }; let sema = Semantics::new(&db); - let config = TEST_CONFIG; let ctx = AssistContext::new(sema, &config, frange); let resolve = match expected { ExpectedResult::Unresolved => AssistResolveStrategy::None, diff --git a/crates/ide-completion/Cargo.toml b/crates/ide-completion/Cargo.toml index 34ef092cfc44..092fb303668f 100644 --- a/crates/ide-completion/Cargo.toml +++ b/crates/ide-completion/Cargo.toml @@ -16,7 +16,7 @@ cov-mark = "2.0.0-pre.1" itertools = "0.10.5" once_cell = "1.17.0" -smallvec = "1.10.0" +smallvec.workspace = true # local deps diff --git a/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs index 90c523735da8..c55bd9aaae52 100644 --- a/crates/ide-completion/src/completions/postfix.rs +++ b/crates/ide-completion/src/completions/postfix.rs @@ -747,4 +747,16 @@ fn main() { "#, ); } + + #[test] + fn no_postfix_completions_in_if_block_that_has_an_else() { + check( + r#" +fn test() { + if true {}.$0 else {} +} +"#, + expect![[r#""#]], + ); + } } diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index aa77f449530e..ea54068b0f8b 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -571,28 +571,25 @@ impl<'a> CompletionContext<'a> { // try to skip completions on path with invalid colons // this approach works in normal path and inside token tree - match original_token.kind() { - T![:] => { - // return if no prev token before colon - let prev_token = original_token.prev_token()?; + if original_token.kind() == T![:] { + // return if no prev token before colon + let prev_token = original_token.prev_token()?; - // only has a single colon - if prev_token.kind() != T![:] { - return None; - } - - // has 3 colon or 2 coloncolon in a row - // special casing this as per discussion in https://github.com/rust-lang/rust-analyzer/pull/13611#discussion_r1031845205 - // and https://github.com/rust-lang/rust-analyzer/pull/13611#discussion_r1032812751 - if prev_token - .prev_token() - .map(|t| t.kind() == T![:] || t.kind() == T![::]) - .unwrap_or(false) - { - return None; - } + // only has a single colon + if prev_token.kind() != T![:] { + return None; + } + + // has 3 colon or 2 coloncolon in a row + // special casing this as per discussion in https://github.com/rust-lang/rust-analyzer/pull/13611#discussion_r1031845205 + // and https://github.com/rust-lang/rust-analyzer/pull/13611#discussion_r1032812751 + if prev_token + .prev_token() + .map(|t| t.kind() == T![:] || t.kind() == T![::]) + .unwrap_or(false) + { + return None; } - _ => {} } let AnalysisResult { diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs index 4bff665ab1d7..db0045aef6e0 100644 --- a/crates/ide-completion/src/context/analysis.rs +++ b/crates/ide-completion/src/context/analysis.rs @@ -29,6 +29,7 @@ pub(super) struct AnalysisResult { pub(super) analysis: CompletionAnalysis, pub(super) expected: (Option, Option), pub(super) qualifier_ctx: QualifierCtx, + /// the original token of the expanded file pub(super) token: SyntaxToken, pub(super) offset: TextSize, } @@ -213,15 +214,6 @@ fn analyze( let _p = profile::span("CompletionContext::analyze"); let ExpansionResult { original_file, speculative_file, offset, fake_ident_token, derive_ctx } = expansion_result; - let syntax_element = NodeOrToken::Token(fake_ident_token); - if is_in_token_of_for_loop(syntax_element.clone()) { - // for pat $0 - // there is nothing to complete here except `in` keyword - // don't bother populating the context - // FIXME: the completion calculations should end up good enough - // such that this special case becomes unnecessary - return None; - } // Overwrite the path kind for derives if let Some((original_file, file_with_fake_ident, offset, origin_attr)) = derive_ctx { @@ -249,37 +241,35 @@ fn analyze( return None; } - let name_like = match find_node_at_offset(&speculative_file, offset) { - Some(it) => it, - None => { - let analysis = if let Some(original) = ast::String::cast(original_token.clone()) { - CompletionAnalysis::String { - original, - expanded: ast::String::cast(self_token.clone()), + let Some(name_like) = find_node_at_offset(&speculative_file, offset) else { + let analysis = if let Some(original) = ast::String::cast(original_token.clone()) { + CompletionAnalysis::String { + original, + expanded: ast::String::cast(self_token.clone()), + } + } else { + // Fix up trailing whitespace problem + // #[attr(foo = $0 + let token = syntax::algo::skip_trivia_token(self_token.clone(), Direction::Prev)?; + let p = token.parent()?; + if p.kind() == SyntaxKind::TOKEN_TREE + && p.ancestors().any(|it| it.kind() == SyntaxKind::META) + { + let colon_prefix = previous_non_trivia_token(self_token.clone()) + .map_or(false, |it| T![:] == it.kind()); + CompletionAnalysis::UnexpandedAttrTT { + fake_attribute_under_caret: fake_ident_token + .parent_ancestors() + .find_map(ast::Attr::cast), + colon_prefix, } } else { - // Fix up trailing whitespace problem - // #[attr(foo = $0 - let token = syntax::algo::skip_trivia_token(self_token.clone(), Direction::Prev)?; - let p = token.parent()?; - if p.kind() == SyntaxKind::TOKEN_TREE - && p.ancestors().any(|it| it.kind() == SyntaxKind::META) - { - let colon_prefix = previous_non_trivia_token(self_token.clone()) - .map_or(false, |it| T![:] == it.kind()); - CompletionAnalysis::UnexpandedAttrTT { - fake_attribute_under_caret: syntax_element - .ancestors() - .find_map(ast::Attr::cast), - colon_prefix, - } - } else { - return None; - } - }; - return Some((analysis, (None, None), QualifierCtx::default())); - } + return None; + } + }; + return Some((analysis, (None, None), QualifierCtx::default())); }; + let expected = expected_type_and_name(sema, self_token, &name_like); let mut qual_ctx = QualifierCtx::default(); let analysis = match name_like { @@ -290,6 +280,22 @@ fn analyze( let parent = name_ref.syntax().parent()?; let (nameref_ctx, qualifier_ctx) = classify_name_ref(sema, &original_file, name_ref, parent)?; + + if let NameRefContext { + kind: + NameRefKind::Path(PathCompletionCtx { kind: PathKind::Expr { .. }, path, .. }, ..), + .. + } = &nameref_ctx + { + if is_in_token_of_for_loop(path) { + // for pat $0 + // there is nothing to complete here except `in` keyword + // don't bother populating the context + // Ideally this special casing wouldn't be needed, but the parser recovers + return None; + } + } + qual_ctx = qualifier_ctx; CompletionAnalysis::NameRef(nameref_ctx) } @@ -323,16 +329,14 @@ fn expected_type_and_name( ast::FieldExpr(e) => e .syntax() .ancestors() - .map_while(ast::FieldExpr::cast) - .last() - .map(|it| it.syntax().clone()), + .take_while(|it| ast::FieldExpr::can_cast(it.kind())) + .last(), ast::PathSegment(e) => e .syntax() .ancestors() .skip(1) .take_while(|it| ast::Path::can_cast(it.kind()) || ast::PathExpr::can_cast(it.kind())) - .find_map(ast::PathExpr::cast) - .map(|it| it.syntax().clone()), + .find(|it| ast::PathExpr::can_cast(it.kind())), _ => None } }; @@ -605,6 +609,18 @@ fn classify_name_ref( }, _ => false, }; + + let reciever_is_part_of_indivisible_expression = match &receiver { + Some(ast::Expr::IfExpr(_)) => { + let next_token_kind = next_non_trivia_token(name_ref.syntax().clone()).map(|t| t.kind()); + next_token_kind == Some(SyntaxKind::ELSE_KW) + }, + _ => false + }; + if reciever_is_part_of_indivisible_expression { + return None; + } + let kind = NameRefKind::DotAccess(DotAccess { receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)), kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal }, @@ -656,8 +672,15 @@ fn classify_name_ref( }; let after_if_expr = |node: SyntaxNode| { let prev_expr = (|| { + let node = match node.parent().and_then(ast::ExprStmt::cast) { + Some(stmt) => stmt.syntax().clone(), + None => node, + }; let prev_sibling = non_trivia_sibling(node.into(), Direction::Prev)?.into_node()?; - ast::ExprStmt::cast(prev_sibling)?.expr() + + ast::ExprStmt::cast(prev_sibling.clone()) + .and_then(|it| it.expr()) + .or_else(|| ast::Expr::cast(prev_sibling)) })(); matches!(prev_expr, Some(ast::Expr::IfExpr(_))) }; @@ -1251,40 +1274,29 @@ fn path_or_use_tree_qualifier(path: &ast::Path) -> Option<(ast::Path, bool)> { Some((use_tree.path()?, true)) } -pub(crate) fn is_in_token_of_for_loop(element: SyntaxElement) -> bool { +fn is_in_token_of_for_loop(path: &ast::Path) -> bool { // oh my ... (|| { - let syntax_token = element.into_token()?; - let range = syntax_token.text_range(); - let for_expr = syntax_token.parent_ancestors().find_map(ast::ForExpr::cast)?; - - // check if the current token is the `in` token of a for loop - if let Some(token) = for_expr.in_token() { - return Some(syntax_token == token); + let expr = path.syntax().parent().and_then(ast::PathExpr::cast)?; + let for_expr = expr.syntax().parent().and_then(ast::ForExpr::cast)?; + if for_expr.in_token().is_some() { + return Some(false); } let pat = for_expr.pat()?; - if range.end() < pat.syntax().text_range().end() { - // if we are inside or before the pattern we can't be at the `in` token position - return None; - } let next_sibl = next_non_trivia_sibling(pat.syntax().clone().into())?; Some(match next_sibl { - // the loop body is some node, if our token is at the start we are at the `in` position, - // otherwise we could be in a recovered expression, we don't wanna ruin completions there - syntax::NodeOrToken::Node(n) => n.text_range().start() == range.start(), - // the loop body consists of a single token, if we are this we are certainly at the `in` token position - syntax::NodeOrToken::Token(t) => t == syntax_token, + syntax::NodeOrToken::Node(n) => { + n.text_range().start() == path.syntax().text_range().start() + } + syntax::NodeOrToken::Token(t) => { + t.text_range().start() == path.syntax().text_range().start() + } }) })() .unwrap_or(false) } -#[test] -fn test_for_is_prev2() { - crate::tests::check_pattern_is_applicable(r"fn __() { for i i$0 }", is_in_token_of_for_loop); -} - -pub(crate) fn is_in_loop_body(node: &SyntaxNode) -> bool { +fn is_in_loop_body(node: &SyntaxNode) -> bool { node.ancestors() .take_while(|it| it.kind() != SyntaxKind::FN && it.kind() != SyntaxKind::CLOSURE_EXPR) .find_map(|it| { @@ -1317,6 +1329,22 @@ fn previous_non_trivia_token(e: impl Into) -> Option None } +fn next_non_trivia_token(e: impl Into) -> Option { + let mut token = match e.into() { + SyntaxElement::Node(n) => n.last_token()?, + SyntaxElement::Token(t) => t, + } + .next_token(); + while let Some(inner) = token { + if !inner.kind().is_trivia() { + return Some(inner); + } else { + token = inner.next_token(); + } + } + None +} + fn next_non_trivia_sibling(ele: SyntaxElement) -> Option { let mut e = ele.next_sibling_or_token(); while let Some(inner) = e { diff --git a/crates/ide-completion/src/item.rs b/crates/ide-completion/src/item.rs index 657eab5b1b83..2f65491d85e1 100644 --- a/crates/ide-completion/src/item.rs +++ b/crates/ide-completion/src/item.rs @@ -14,13 +14,14 @@ use crate::{ render::{render_path_resolution, RenderContext}, }; -/// `CompletionItem` describes a single completion variant in the editor pop-up. -/// It is basically a POD with various properties. To construct a -/// `CompletionItem`, use `new` method and the `Builder` struct. +/// `CompletionItem` describes a single completion entity which expands to 1 or more entries in the +/// editor pop-up. It is basically a POD with various properties. To construct a +/// [`CompletionItem`], use [`Builder::new`] method and the [`Builder`] struct. #[derive(Clone)] +#[non_exhaustive] pub struct CompletionItem { /// Label in the completion pop up which identifies completion. - label: SmolStr, + pub label: SmolStr, /// Range of identifier that is being completed. /// /// It should be used primarily for UI, but we also use this to convert @@ -29,33 +30,33 @@ pub struct CompletionItem { /// `source_range` must contain the completion offset. `text_edit` should /// start with what `source_range` points to, or VSCode will filter out the /// completion silently. - source_range: TextRange, + pub source_range: TextRange, /// What happens when user selects this item. /// /// Typically, replaces `source_range` with new identifier. - text_edit: TextEdit, - is_snippet: bool, + pub text_edit: TextEdit, + pub is_snippet: bool, /// What item (struct, function, etc) are we completing. - kind: CompletionItemKind, + pub kind: CompletionItemKind, /// Lookup is used to check if completion item indeed can complete current /// ident. /// /// That is, in `foo.bar$0` lookup of `abracadabra` will be accepted (it /// contains `bar` sub sequence), and `quux` will rejected. - lookup: Option, + pub lookup: Option, /// Additional info to show in the UI pop up. - detail: Option, - documentation: Option, + pub detail: Option, + pub documentation: Option, /// Whether this item is marked as deprecated - deprecated: bool, + pub deprecated: bool, /// If completing a function call, ask the editor to show parameter popup /// after completion. - trigger_call_info: bool, + pub trigger_call_info: bool, /// We use this to sort completion. Relevance records facts like "do the /// types align precisely?". We can't sort by relevances directly, they are @@ -64,36 +65,39 @@ pub struct CompletionItem { /// Note that Relevance ignores fuzzy match score. We compute Relevance for /// all possible items, and then separately build an ordered completion list /// based on relevance and fuzzy matching with the already typed identifier. - relevance: CompletionRelevance, + pub relevance: CompletionRelevance, /// Indicates that a reference or mutable reference to this variable is a /// possible match. - ref_match: Option<(Mutability, TextSize)>, + // FIXME: We shouldn't expose Mutability here (that is HIR types at all), its fine for now though + // until we have more splitting completions in which case we should think about + // generalizing this. See https://github.com/rust-lang/rust-analyzer/issues/12571 + pub ref_match: Option<(Mutability, TextSize)>, /// The import data to add to completion's edits. - import_to_add: SmallVec<[LocatedImport; 1]>, + pub import_to_add: SmallVec<[LocatedImport; 1]>, } // We use custom debug for CompletionItem to make snapshot tests more readable. impl fmt::Debug for CompletionItem { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut s = f.debug_struct("CompletionItem"); - s.field("label", &self.label()).field("source_range", &self.source_range()); - if self.text_edit().len() == 1 { - let atom = &self.text_edit().iter().next().unwrap(); + s.field("label", &self.label).field("source_range", &self.source_range); + if self.text_edit.len() == 1 { + let atom = &self.text_edit.iter().next().unwrap(); s.field("delete", &atom.delete); s.field("insert", &atom.insert); } else { s.field("text_edit", &self.text_edit); } - s.field("kind", &self.kind()); - if self.lookup() != self.label() { + s.field("kind", &self.kind); + if self.lookup() != self.label { s.field("lookup", &self.lookup()); } - if let Some(detail) = self.detail() { + if let Some(detail) = &self.detail { s.field("detail", &detail); } - if let Some(documentation) = self.documentation() { + if let Some(documentation) = &self.documentation { s.field("documentation", &documentation); } if self.deprecated { @@ -351,63 +355,25 @@ impl CompletionItem { } } - /// What user sees in pop-up in the UI. - pub fn label(&self) -> &str { - &self.label - } - pub fn source_range(&self) -> TextRange { - self.source_range - } - - pub fn text_edit(&self) -> &TextEdit { - &self.text_edit - } - /// Whether `text_edit` is a snippet (contains `$0` markers). - pub fn is_snippet(&self) -> bool { - self.is_snippet - } - - /// Short one-line additional information, like a type - pub fn detail(&self) -> Option<&str> { - self.detail.as_deref() - } - /// A doc-comment - pub fn documentation(&self) -> Option { - self.documentation.clone() - } /// What string is used for filtering. pub fn lookup(&self) -> &str { self.lookup.as_deref().unwrap_or(&self.label) } - pub fn kind(&self) -> CompletionItemKind { - self.kind - } - - pub fn deprecated(&self) -> bool { - self.deprecated - } - - pub fn relevance(&self) -> CompletionRelevance { - self.relevance - } - - pub fn trigger_call_info(&self) -> bool { - self.trigger_call_info - } - - pub fn ref_match(&self) -> Option<(Mutability, TextSize, CompletionRelevance)> { + pub fn ref_match(&self) -> Option<(String, text_edit::Indel, CompletionRelevance)> { // Relevance of the ref match should be the same as the original // match, but with exact type match set because self.ref_match // is only set if there is an exact type match. let mut relevance = self.relevance; relevance.type_match = Some(CompletionRelevanceTypeMatch::Exact); - self.ref_match.map(|(mutability, offset)| (mutability, offset, relevance)) - } - - pub fn imports_to_add(&self) -> &[LocatedImport] { - &self.import_to_add + self.ref_match.map(|(mutability, offset)| { + ( + format!("&{}{}", mutability.as_keyword_for_ref(), self.label), + text_edit::Indel::insert(offset, format!("&{}", mutability.as_keyword_for_ref())), + relevance, + ) + }) } } diff --git a/crates/ide-completion/src/lib.rs b/crates/ide-completion/src/lib.rs index 4b48ec6bc339..6fe781114039 100644 --- a/crates/ide-completion/src/lib.rs +++ b/crates/ide-completion/src/lib.rs @@ -156,13 +156,15 @@ pub fn completions( // prevent `(` from triggering unwanted completion noise if trigger_character == Some('(') { - if let CompletionAnalysis::NameRef(NameRefContext { kind, .. }) = &analysis { - if let NameRefKind::Path( - path_ctx @ PathCompletionCtx { kind: PathKind::Vis { has_in_token }, .. }, - ) = kind - { - completions::vis::complete_vis_path(&mut completions, ctx, path_ctx, has_in_token); - } + if let CompletionAnalysis::NameRef(NameRefContext { + kind: + NameRefKind::Path( + path_ctx @ PathCompletionCtx { kind: PathKind::Vis { has_in_token }, .. }, + ), + .. + }) = analysis + { + completions::vis::complete_vis_path(&mut completions, ctx, path_ctx, has_in_token); } return Some(completions.into()); } @@ -170,7 +172,7 @@ pub fn completions( { let acc = &mut completions; - match &analysis { + match analysis { CompletionAnalysis::Name(name_ctx) => completions::complete_name(acc, ctx, name_ctx), CompletionAnalysis::NameRef(name_ref_ctx) => { completions::complete_name_ref(acc, ctx, name_ref_ctx) diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs index d6476c10258e..d99ad5f9f04b 100644 --- a/crates/ide-completion/src/render.rs +++ b/crates/ide-completion/src/render.rs @@ -503,18 +503,18 @@ mod tests { #[track_caller] fn check_relevance_for_kinds(ra_fixture: &str, kinds: &[CompletionItemKind], expect: Expect) { let mut actual = get_all_items(TEST_CONFIG, ra_fixture, None); - actual.retain(|it| kinds.contains(&it.kind())); - actual.sort_by_key(|it| cmp::Reverse(it.relevance().score())); + actual.retain(|it| kinds.contains(&it.kind)); + actual.sort_by_key(|it| cmp::Reverse(it.relevance.score())); check_relevance_(actual, expect); } #[track_caller] fn check_relevance(ra_fixture: &str, expect: Expect) { let mut actual = get_all_items(TEST_CONFIG, ra_fixture, None); - actual.retain(|it| it.kind() != CompletionItemKind::Snippet); - actual.retain(|it| it.kind() != CompletionItemKind::Keyword); - actual.retain(|it| it.kind() != CompletionItemKind::BuiltinType); - actual.sort_by_key(|it| cmp::Reverse(it.relevance().score())); + actual.retain(|it| it.kind != CompletionItemKind::Snippet); + actual.retain(|it| it.kind != CompletionItemKind::Keyword); + actual.retain(|it| it.kind != CompletionItemKind::BuiltinType); + actual.sort_by_key(|it| cmp::Reverse(it.relevance.score())); check_relevance_(actual, expect); } @@ -525,12 +525,11 @@ mod tests { .flat_map(|it| { let mut items = vec![]; - let tag = it.kind().tag(); - let relevance = display_relevance(it.relevance()); - items.push(format!("{tag} {} {relevance}\n", it.label())); + let tag = it.kind.tag(); + let relevance = display_relevance(it.relevance); + items.push(format!("{tag} {} {relevance}\n", it.label)); - if let Some((mutability, _offset, relevance)) = it.ref_match() { - let label = format!("&{}{}", mutability.as_keyword_for_ref(), it.label()); + if let Some((label, _indel, relevance)) = it.ref_match() { let relevance = display_relevance(relevance); items.push(format!("{tag} {label} {relevance}\n")); @@ -587,6 +586,7 @@ fn main() { Foo::Fo$0 } ), lookup: "Foo{}", detail: "Foo { x: i32, y: i32 }", + trigger_call_info: true, }, ] "#]], @@ -614,6 +614,7 @@ fn main() { Foo::Fo$0 } ), lookup: "Foo()", detail: "Foo(i32, i32)", + trigger_call_info: true, }, ] "#]], @@ -679,6 +680,7 @@ fn main() { Foo::Fo$0 } Variant, ), detail: "Foo", + trigger_call_info: true, }, ] "#]], @@ -745,6 +747,7 @@ fn main() { let _: m::Spam = S$0 } postfix_match: None, is_definite: false, }, + trigger_call_info: true, }, CompletionItem { label: "m::Spam::Foo", @@ -770,6 +773,7 @@ fn main() { let _: m::Spam = S$0 } postfix_match: None, is_definite: false, }, + trigger_call_info: true, }, ] "#]], @@ -942,6 +946,7 @@ use self::E::*; documentation: Documentation( "variant docs", ), + trigger_call_info: true, }, CompletionItem { label: "E", diff --git a/crates/ide-completion/src/render/literal.rs b/crates/ide-completion/src/render/literal.rs index 64dab02f7c5c..ed78fcd8e652 100644 --- a/crates/ide-completion/src/render/literal.rs +++ b/crates/ide-completion/src/render/literal.rs @@ -113,7 +113,7 @@ fn render( item.detail(rendered.detail); match snippet_cap { - Some(snippet_cap) => item.insert_snippet(snippet_cap, rendered.literal), + Some(snippet_cap) => item.insert_snippet(snippet_cap, rendered.literal).trigger_call_info(), None => item.insert_text(rendered.literal), }; diff --git a/crates/ide-completion/src/render/union_literal.rs b/crates/ide-completion/src/render/union_literal.rs index 1b09ad1731f9..6e0c53ec94c4 100644 --- a/crates/ide-completion/src/render/union_literal.rs +++ b/crates/ide-completion/src/render/union_literal.rs @@ -72,7 +72,7 @@ pub(crate) fn render_union_literal( .set_relevance(ctx.completion_relevance()); match ctx.snippet_cap() { - Some(snippet_cap) => item.insert_snippet(snippet_cap, literal), + Some(snippet_cap) => item.insert_snippet(snippet_cap, literal).trigger_call_info(), None => item.insert_text(literal), }; diff --git a/crates/ide-completion/src/tests.rs b/crates/ide-completion/src/tests.rs index 540b0fd0ef7d..1fe48b9e96f9 100644 --- a/crates/ide-completion/src/tests.rs +++ b/crates/ide-completion/src/tests.rs @@ -23,7 +23,7 @@ mod type_pos; mod use_tree; mod visibility; -use hir::{db::DefDatabase, PrefixKind, Semantics}; +use hir::{db::DefDatabase, PrefixKind}; use ide_db::{ base_db::{fixture::ChangeFixture, FileLoader, FilePosition}, imports::insert_use::{ImportGranularity, InsertUseConfig}, @@ -31,7 +31,6 @@ use ide_db::{ }; use itertools::Itertools; use stdx::{format_to, trim_indent}; -use syntax::{AstNode, NodeOrToken, SyntaxElement}; use test_utils::assert_eq_text; use crate::{ @@ -109,10 +108,10 @@ fn completion_list_with_config( let items = get_all_items(config, ra_fixture, trigger_character); let items = items .into_iter() - .filter(|it| it.kind() != CompletionItemKind::BuiltinType || it.label() == "u32") - .filter(|it| include_keywords || it.kind() != CompletionItemKind::Keyword) - .filter(|it| include_keywords || it.kind() != CompletionItemKind::Snippet) - .sorted_by_key(|it| (it.kind(), it.label().to_owned(), it.detail().map(ToOwned::to_owned))) + .filter(|it| it.kind != CompletionItemKind::BuiltinType || it.label == "u32") + .filter(|it| include_keywords || it.kind != CompletionItemKind::Keyword) + .filter(|it| include_keywords || it.kind != CompletionItemKind::Snippet) + .sorted_by_key(|it| (it.kind, it.label.clone(), it.detail.as_ref().map(ToOwned::to_owned))) .collect(); render_completion_list(items) } @@ -139,8 +138,8 @@ pub(crate) fn do_completion_with_config( ) -> Vec { get_all_items(config, code, None) .into_iter() - .filter(|c| c.kind() == kind) - .sorted_by(|l, r| l.label().cmp(r.label())) + .filter(|c| c.kind == kind) + .sorted_by(|l, r| l.label.cmp(&r.label)) .collect() } @@ -149,18 +148,18 @@ fn render_completion_list(completions: Vec) -> String { s.chars().count() } let label_width = - completions.iter().map(|it| monospace_width(it.label())).max().unwrap_or_default().min(22); + completions.iter().map(|it| monospace_width(&it.label)).max().unwrap_or_default().min(22); completions .into_iter() .map(|it| { - let tag = it.kind().tag(); - let var_name = format!("{tag} {}", it.label()); + let tag = it.kind.tag(); + let var_name = format!("{tag} {}", it.label); let mut buf = var_name; - if let Some(detail) = it.detail() { - let width = label_width.saturating_sub(monospace_width(it.label())); + if let Some(detail) = it.detail { + let width = label_width.saturating_sub(monospace_width(&it.label)); format_to!(buf, "{:width$} {}", "", detail, width = width); } - if it.deprecated() { + if it.deprecated { format_to!(buf, " DEPRECATED"); } format_to!(buf, "\n"); @@ -192,13 +191,13 @@ pub(crate) fn check_edit_with_config( .unwrap_or_else(|| panic!("can't find {what:?} completion in {completions:#?}")); let mut actual = db.file_text(position.file_id).to_string(); - let mut combined_edit = completion.text_edit().to_owned(); + let mut combined_edit = completion.text_edit.clone(); resolve_completion_edits( &db, &config, position, - completion.imports_to_add().iter().filter_map(|import_edit| { + completion.import_to_add.iter().filter_map(|import_edit| { let import_path = &import_edit.import_path; let import_name = import_path.segments().last()?; Some((import_path.to_string(), import_name.to_string())) @@ -216,15 +215,6 @@ pub(crate) fn check_edit_with_config( assert_eq_text!(&ra_fixture_after, &actual) } -pub(crate) fn check_pattern_is_applicable(code: &str, check: impl FnOnce(SyntaxElement) -> bool) { - let (db, pos) = position(code); - - let sema = Semantics::new(&db); - let original_file = sema.parse(pos.file_id); - let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap(); - assert!(check(NodeOrToken::Token(token))); -} - pub(crate) fn get_all_items( config: CompletionConfig, code: &str, @@ -235,7 +225,7 @@ pub(crate) fn get_all_items( .map_or_else(Vec::default, Into::into); // validate res.iter().for_each(|it| { - let sr = it.source_range(); + let sr = it.source_range; assert!( sr.contains_inclusive(position.offset), "source range {sr:?} does not contain the offset {:?} of the completion request: {it:?}", @@ -246,8 +236,9 @@ pub(crate) fn get_all_items( } #[test] -fn test_no_completions_required() { +fn test_no_completions_in_for_loop_in_kw_pos() { assert_eq!(completion_list(r#"fn foo() { for i i$0 }"#), String::new()); + assert_eq!(completion_list(r#"fn foo() { for i in$0 }"#), String::new()); } #[test] diff --git a/crates/ide-completion/src/tests/expression.rs b/crates/ide-completion/src/tests/expression.rs index 043f552bd8a4..c1c6a689eb18 100644 --- a/crates/ide-completion/src/tests/expression.rs +++ b/crates/ide-completion/src/tests/expression.rs @@ -745,3 +745,255 @@ fn return_value_no_block() { r#"fn f() -> i32 { match () { () => return $0 } }"#, ); } + +#[test] +fn else_completion_after_if() { + check_empty( + r#" +fn foo() { if foo {} $0 } +"#, + expect![[r#" + fn foo() fn() + bt u32 + kw const + kw crate:: + kw else + kw else if + kw enum + kw extern + kw false + kw fn + kw for + kw if + kw if let + kw impl + kw let + kw loop + kw match + kw mod + kw return + kw self:: + kw static + kw struct + kw trait + kw true + kw type + kw union + kw unsafe + kw use + kw while + kw while let + sn macro_rules + sn pd + sn ppd + "#]], + ); + check_empty( + r#" +fn foo() { if foo {} el$0 } +"#, + expect![[r#" + fn foo() fn() + bt u32 + kw const + kw crate:: + kw else + kw else if + kw enum + kw extern + kw false + kw fn + kw for + kw if + kw if let + kw impl + kw let + kw loop + kw match + kw mod + kw return + kw self:: + kw static + kw struct + kw trait + kw true + kw type + kw union + kw unsafe + kw use + kw while + kw while let + sn macro_rules + sn pd + sn ppd + "#]], + ); + check_empty( + r#" +fn foo() { bar(if foo {} $0) } +"#, + expect![[r#" + fn foo() fn() + bt u32 + kw crate:: + kw else + kw else if + kw false + kw for + kw if + kw if let + kw loop + kw match + kw return + kw self:: + kw true + kw unsafe + kw while + kw while let + "#]], + ); + check_empty( + r#" +fn foo() { bar(if foo {} el$0) } +"#, + expect![[r#" + fn foo() fn() + bt u32 + kw crate:: + kw else + kw else if + kw false + kw for + kw if + kw if let + kw loop + kw match + kw return + kw self:: + kw true + kw unsafe + kw while + kw while let + "#]], + ); + check_empty( + r#" +fn foo() { if foo {} $0 let x = 92; } +"#, + expect![[r#" + fn foo() fn() + bt u32 + kw const + kw crate:: + kw else + kw else if + kw enum + kw extern + kw false + kw fn + kw for + kw if + kw if let + kw impl + kw let + kw loop + kw match + kw mod + kw return + kw self:: + kw static + kw struct + kw trait + kw true + kw type + kw union + kw unsafe + kw use + kw while + kw while let + sn macro_rules + sn pd + sn ppd + "#]], + ); + check_empty( + r#" +fn foo() { if foo {} el$0 let x = 92; } +"#, + expect![[r#" + fn foo() fn() + bt u32 + kw const + kw crate:: + kw else + kw else if + kw enum + kw extern + kw false + kw fn + kw for + kw if + kw if let + kw impl + kw let + kw loop + kw match + kw mod + kw return + kw self:: + kw static + kw struct + kw trait + kw true + kw type + kw union + kw unsafe + kw use + kw while + kw while let + sn macro_rules + sn pd + sn ppd + "#]], + ); + check_empty( + r#" +fn foo() { if foo {} el$0 { let x = 92; } } +"#, + expect![[r#" + fn foo() fn() + bt u32 + kw const + kw crate:: + kw else + kw else if + kw enum + kw extern + kw false + kw fn + kw for + kw if + kw if let + kw impl + kw let + kw loop + kw match + kw mod + kw return + kw self:: + kw static + kw struct + kw trait + kw true + kw type + kw union + kw unsafe + kw use + kw while + kw while let + sn macro_rules + sn pd + sn ppd + "#]], + ); +} diff --git a/crates/ide-completion/src/tests/special.rs b/crates/ide-completion/src/tests/special.rs index 6052b0623204..cb71c7b2bdef 100644 --- a/crates/ide-completion/src/tests/special.rs +++ b/crates/ide-completion/src/tests/special.rs @@ -2,10 +2,17 @@ use expect_test::{expect, Expect}; -use crate::tests::{check_edit, completion_list_no_kw, completion_list_with_trigger_character}; +use crate::tests::{ + check_edit, completion_list, completion_list_no_kw, completion_list_with_trigger_character, +}; + +fn check_no_kw(ra_fixture: &str, expect: Expect) { + let actual = completion_list_no_kw(ra_fixture); + expect.assert_eq(&actual) +} fn check(ra_fixture: &str, expect: Expect) { - let actual = completion_list_no_kw(ra_fixture); + let actual = completion_list(ra_fixture); expect.assert_eq(&actual) } @@ -59,7 +66,7 @@ fn _alpha() {} #[test] fn completes_prelude() { - check( + check_no_kw( r#" //- /main.rs edition:2018 crate:main deps:std fn foo() { let x: $0 } @@ -81,7 +88,7 @@ pub mod prelude { #[test] fn completes_prelude_macros() { - check( + check_no_kw( r#" //- /main.rs edition:2018 crate:main deps:std fn f() {$0} @@ -110,7 +117,7 @@ mod macros { #[test] fn completes_std_prelude_if_core_is_defined() { - check( + check_no_kw( r#" //- /main.rs crate:main deps:core,std fn foo() { let x: $0 } @@ -140,7 +147,7 @@ pub mod prelude { #[test] fn respects_doc_hidden() { - check( + check_no_kw( r#" //- /lib.rs crate:lib deps:std fn f() { @@ -168,7 +175,7 @@ pub mod prelude { #[test] fn respects_doc_hidden_in_assoc_item_list() { - check( + check_no_kw( r#" //- /lib.rs crate:lib deps:std struct S; @@ -195,7 +202,7 @@ pub mod prelude { #[test] fn associated_item_visibility() { - check( + check_no_kw( r#" //- /lib.rs crate:lib new_source_root:library pub struct S; @@ -222,7 +229,7 @@ fn foo() { let _ = lib::S::$0 } #[test] fn completes_union_associated_method() { - check( + check_no_kw( r#" union U {}; impl U { fn m() { } } @@ -237,7 +244,7 @@ fn foo() { let _ = U::$0 } #[test] fn completes_trait_associated_method_1() { - check( + check_no_kw( r#" trait Trait { fn m(); } @@ -251,7 +258,7 @@ fn foo() { let _ = Trait::$0 } #[test] fn completes_trait_associated_method_2() { - check( + check_no_kw( r#" trait Trait { fn m(); } @@ -268,7 +275,7 @@ fn foo() { let _ = S::$0 } #[test] fn completes_trait_associated_method_3() { - check( + check_no_kw( r#" trait Trait { fn m(); } @@ -285,7 +292,7 @@ fn foo() { let _ = ::$0 } #[test] fn completes_ty_param_assoc_ty() { - check( + check_no_kw( r#" trait Super { type Ty; @@ -318,7 +325,7 @@ fn foo() { T::$0 } #[test] fn completes_self_param_assoc_ty() { - check( + check_no_kw( r#" trait Super { type Ty; @@ -358,7 +365,7 @@ impl Sub for Wrap { #[test] fn completes_type_alias() { - check( + check_no_kw( r#" struct S; impl S { fn foo() {} } @@ -376,7 +383,7 @@ fn main() { T::$0; } #[test] fn completes_qualified_macros() { - check( + check_no_kw( r#" #[macro_export] macro_rules! foo { () => {} } @@ -392,7 +399,7 @@ fn main() { let _ = crate::$0 } #[test] fn does_not_complete_non_fn_macros() { - check( + check_no_kw( r#" mod m { #[rustc_builtin_macro] @@ -403,7 +410,7 @@ fn f() {m::$0} "#, expect![[r#""#]], ); - check( + check_no_kw( r#" mod m { #[rustc_builtin_macro] @@ -418,7 +425,7 @@ fn f() {m::$0} #[test] fn completes_reexported_items_under_correct_name() { - check( + check_no_kw( r#" fn foo() { self::m::$0 } @@ -475,7 +482,7 @@ mod p { #[test] fn completes_in_simple_macro_call() { - check( + check_no_kw( r#" macro_rules! m { ($e:expr) => { $e } } fn main() { m!(self::f$0); } @@ -490,7 +497,7 @@ fn foo() {} #[test] fn function_mod_share_name() { - check( + check_no_kw( r#" fn foo() { self::m::$0 } @@ -508,7 +515,7 @@ mod m { #[test] fn completes_hashmap_new() { - check( + check_no_kw( r#" struct RandomState; struct HashMap {} @@ -529,7 +536,7 @@ fn foo() { #[test] fn completes_variant_through_self() { cov_mark::check!(completes_variant_through_self); - check( + check_no_kw( r#" enum Foo { Bar, @@ -552,7 +559,7 @@ impl Foo { #[test] fn completes_non_exhaustive_variant_within_the_defining_crate() { - check( + check_no_kw( r#" enum Foo { #[non_exhaustive] @@ -570,7 +577,7 @@ fn foo(self) { "#]], ); - check( + check_no_kw( r#" //- /main.rs crate:main deps:e fn foo(self) { @@ -593,7 +600,7 @@ enum Foo { #[test] fn completes_primitive_assoc_const() { cov_mark::check!(completes_primitive_assoc_const); - check( + check_no_kw( r#" //- /lib.rs crate:lib deps:core fn f() { @@ -618,7 +625,7 @@ impl u8 { #[test] fn completes_variant_through_alias() { cov_mark::check!(completes_variant_through_alias); - check( + check_no_kw( r#" enum Foo { Bar @@ -636,7 +643,7 @@ fn main() { #[test] fn respects_doc_hidden2() { - check( + check_no_kw( r#" //- /lib.rs crate:lib deps:dep fn f() { @@ -665,7 +672,7 @@ pub mod m {} #[test] fn type_anchor_empty() { - check( + check_no_kw( r#" trait Foo { fn foo() -> Self; @@ -688,7 +695,7 @@ fn bar() -> Bar { #[test] fn type_anchor_type() { - check( + check_no_kw( r#" trait Foo { fn foo() -> Self; @@ -715,7 +722,7 @@ fn bar() -> Bar { #[test] fn type_anchor_type_trait() { - check( + check_no_kw( r#" trait Foo { fn foo() -> Self; @@ -741,7 +748,7 @@ fn bar() -> Bar { #[test] fn completes_fn_in_pub_trait_generated_by_macro() { - check( + check_no_kw( r#" mod other_mod { macro_rules! make_method { @@ -775,7 +782,7 @@ fn main() { #[test] fn completes_fn_in_pub_trait_generated_by_recursive_macro() { - check( + check_no_kw( r#" mod other_mod { macro_rules! make_method { @@ -815,7 +822,7 @@ fn main() { #[test] fn completes_const_in_pub_trait_generated_by_macro() { - check( + check_no_kw( r#" mod other_mod { macro_rules! make_const { @@ -847,7 +854,7 @@ fn main() { #[test] fn completes_locals_from_macros() { - check( + check_no_kw( r#" macro_rules! x { @@ -875,7 +882,7 @@ fn main() { #[test] fn regression_12644() { - check( + check_no_kw( r#" macro_rules! __rust_force_expr { ($e:expr) => { @@ -974,7 +981,7 @@ fn foo { crate:::$0 } "#, expect![""], ); - check( + check_no_kw( r#" fn foo { crate::::$0 } "#, diff --git a/crates/ide-db/Cargo.toml b/crates/ide-db/Cargo.toml index 9672bb9b7b59..57daaf623df2 100644 --- a/crates/ide-db/Cargo.toml +++ b/crates/ide-db/Cargo.toml @@ -37,8 +37,9 @@ text-edit.workspace = true hir.workspace = true [dev-dependencies] -xshell = "0.2.2" expect-test = "1.4.0" +oorandom = "11.1.3" +xshell = "0.2.2" # local deps test-utils.workspace = true diff --git a/crates/ide-db/src/active_parameter.rs b/crates/ide-db/src/active_parameter.rs index 7109c6fd188f..2b6b60547b35 100644 --- a/crates/ide-db/src/active_parameter.rs +++ b/crates/ide-db/src/active_parameter.rs @@ -2,9 +2,10 @@ use either::Either; use hir::{Semantics, Type}; +use parser::T; use syntax::{ ast::{self, HasArgList, HasName}, - AstNode, SyntaxToken, + match_ast, AstNode, NodeOrToken, SyntaxToken, }; use crate::RootDatabase; @@ -58,7 +59,7 @@ pub fn callable_for_node( calling_node: &ast::CallableExpr, token: &SyntaxToken, ) -> Option<(hir::Callable, Option)> { - let callable = match &calling_node { + let callable = match calling_node { ast::CallableExpr::Call(call) => { let expr = call.expr()?; sema.type_of_expr(&expr)?.adjusted().as_callable(sema.db) @@ -66,13 +67,78 @@ pub fn callable_for_node( ast::CallableExpr::MethodCall(call) => sema.resolve_method_call_as_callable(call), }?; let active_param = if let Some(arg_list) = calling_node.arg_list() { - let param = arg_list - .args() - .take_while(|arg| arg.syntax().text_range().end() <= token.text_range().start()) - .count(); - Some(param) + Some( + arg_list + .syntax() + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .filter(|t| t.kind() == T![,]) + .take_while(|t| t.text_range().start() <= token.text_range().start()) + .count(), + ) } else { None }; Some((callable, active_param)) } + +pub fn generic_def_for_node( + sema: &Semantics<'_, RootDatabase>, + generic_arg_list: &ast::GenericArgList, + token: &SyntaxToken, +) -> Option<(hir::GenericDef, usize, bool)> { + let parent = generic_arg_list.syntax().parent()?; + let def = match_ast! { + match parent { + ast::PathSegment(ps) => { + let res = sema.resolve_path(&ps.parent_path())?; + let generic_def: hir::GenericDef = match res { + hir::PathResolution::Def(hir::ModuleDef::Adt(it)) => it.into(), + hir::PathResolution::Def(hir::ModuleDef::Function(it)) => it.into(), + hir::PathResolution::Def(hir::ModuleDef::Trait(it)) => it.into(), + hir::PathResolution::Def(hir::ModuleDef::TypeAlias(it)) => it.into(), + hir::PathResolution::Def(hir::ModuleDef::Variant(it)) => it.into(), + hir::PathResolution::Def(hir::ModuleDef::BuiltinType(_)) + | hir::PathResolution::Def(hir::ModuleDef::Const(_)) + | hir::PathResolution::Def(hir::ModuleDef::Macro(_)) + | hir::PathResolution::Def(hir::ModuleDef::Module(_)) + | hir::PathResolution::Def(hir::ModuleDef::Static(_)) => return None, + hir::PathResolution::BuiltinAttr(_) + | hir::PathResolution::ToolModule(_) + | hir::PathResolution::Local(_) + | hir::PathResolution::TypeParam(_) + | hir::PathResolution::ConstParam(_) + | hir::PathResolution::SelfType(_) + | hir::PathResolution::DeriveHelper(_) => return None, + }; + + generic_def + }, + ast::AssocTypeArg(_) => { + // FIXME: We don't record the resolutions for this anywhere atm + return None; + }, + ast::MethodCallExpr(mcall) => { + // recv.method::<$0>() + let method = sema.resolve_method_call(&mcall)?; + method.into() + }, + _ => return None, + } + }; + + let active_param = generic_arg_list + .syntax() + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .filter(|t| t.kind() == T![,]) + .take_while(|t| t.text_range().start() <= token.text_range().start()) + .count(); + + let first_arg_is_non_lifetime = generic_arg_list + .generic_args() + .next() + .map_or(false, |arg| !matches!(arg, ast::GenericArg::LifetimeArg(_))); + + Some((def, active_param, first_arg_is_non_lifetime)) +} diff --git a/crates/ide-db/src/line_index.rs b/crates/ide-db/src/line_index.rs index 8f12ab334094..16814a1e636f 100644 --- a/crates/ide-db/src/line_index.rs +++ b/crates/ide-db/src/line_index.rs @@ -7,20 +7,13 @@ use syntax::{TextRange, TextSize}; #[derive(Clone, Debug, PartialEq, Eq)] pub struct LineIndex { - /// Offset the the beginning of each line, zero-based + /// Offset the beginning of each line, zero-based. pub(crate) newlines: Vec, - /// List of non-ASCII characters on each line - pub(crate) utf16_lines: NoHashHashMap>, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct LineColUtf16 { - /// Zero-based - pub line: u32, - /// Zero-based - pub col: u32, + /// List of non-ASCII characters on each line. + pub(crate) line_wide_chars: NoHashHashMap>, } +/// Line/Column information in native, utf8 format. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct LineCol { /// Zero-based @@ -29,34 +22,57 @@ pub struct LineCol { pub col: u32, } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum WideEncoding { + Utf16, + Utf32, +} + +/// Line/Column information in legacy encodings. +/// +/// Deliberately not a generic type and different from `LineCol`. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct WideLineCol { + /// Zero-based + pub line: u32, + /// Zero-based + pub col: u32, +} + #[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub(crate) struct Utf16Char { +pub(crate) struct WideChar { /// Start offset of a character inside a line, zero-based pub(crate) start: TextSize, /// End offset of a character inside a line, zero-based pub(crate) end: TextSize, } -impl Utf16Char { +impl WideChar { /// Returns the length in 8-bit UTF-8 code units. fn len(&self) -> TextSize { self.end - self.start } - /// Returns the length in 16-bit UTF-16 code units. - fn len_utf16(&self) -> usize { - if self.len() == TextSize::from(4) { - 2 - } else { - 1 + /// Returns the length in UTF-16 or UTF-32 code units. + fn wide_len(&self, enc: WideEncoding) -> usize { + match enc { + WideEncoding::Utf16 => { + if self.len() == TextSize::from(4) { + 2 + } else { + 1 + } + } + + WideEncoding::Utf32 => 1, } } } impl LineIndex { pub fn new(text: &str) -> LineIndex { - let mut utf16_lines = NoHashHashMap::default(); - let mut utf16_chars = Vec::new(); + let mut line_wide_chars = NoHashHashMap::default(); + let mut wide_chars = Vec::new(); let mut newlines = Vec::with_capacity(16); newlines.push(TextSize::from(0)); @@ -71,8 +87,8 @@ impl LineIndex { newlines.push(curr_row); // Save any utf-16 characters seen in the previous line - if !utf16_chars.is_empty() { - utf16_lines.insert(line, mem::take(&mut utf16_chars)); + if !wide_chars.is_empty() { + line_wide_chars.insert(line, mem::take(&mut wide_chars)); } // Prepare for processing the next line @@ -82,18 +98,18 @@ impl LineIndex { } if !c.is_ascii() { - utf16_chars.push(Utf16Char { start: curr_col, end: curr_col + c_len }); + wide_chars.push(WideChar { start: curr_col, end: curr_col + c_len }); } curr_col += c_len; } // Save any utf-16 characters seen in the last line - if !utf16_chars.is_empty() { - utf16_lines.insert(line, utf16_chars); + if !wide_chars.is_empty() { + line_wide_chars.insert(line, wide_chars); } - LineIndex { newlines, utf16_lines } + LineIndex { newlines, line_wide_chars } } pub fn line_col(&self, offset: TextSize) -> LineCol { @@ -109,13 +125,13 @@ impl LineIndex { .map(|offset| offset + TextSize::from(line_col.col)) } - pub fn to_utf16(&self, line_col: LineCol) -> LineColUtf16 { - let col = self.utf8_to_utf16_col(line_col.line, line_col.col.into()); - LineColUtf16 { line: line_col.line, col: col as u32 } + pub fn to_wide(&self, enc: WideEncoding, line_col: LineCol) -> WideLineCol { + let col = self.utf8_to_wide_col(enc, line_col.line, line_col.col.into()); + WideLineCol { line: line_col.line, col: col as u32 } } - pub fn to_utf8(&self, line_col: LineColUtf16) -> LineCol { - let col = self.utf16_to_utf8_col(line_col.line, line_col.col); + pub fn to_utf8(&self, enc: WideEncoding, line_col: WideLineCol) -> LineCol { + let col = self.wide_to_utf8_col(enc, line_col.line, line_col.col); LineCol { line: line_col.line, col: col.into() } } @@ -132,12 +148,12 @@ impl LineIndex { .filter(|it| !it.is_empty()) } - fn utf8_to_utf16_col(&self, line: u32, col: TextSize) -> usize { + fn utf8_to_wide_col(&self, enc: WideEncoding, line: u32, col: TextSize) -> usize { let mut res: usize = col.into(); - if let Some(utf16_chars) = self.utf16_lines.get(&line) { - for c in utf16_chars { + if let Some(wide_chars) = self.line_wide_chars.get(&line) { + for c in wide_chars { if c.end <= col { - res -= usize::from(c.len()) - c.len_utf16(); + res -= usize::from(c.len()) - c.wide_len(enc); } else { // From here on, all utf16 characters come *after* the character we are mapping, // so we don't need to take them into account @@ -148,11 +164,11 @@ impl LineIndex { res } - fn utf16_to_utf8_col(&self, line: u32, mut col: u32) -> TextSize { - if let Some(utf16_chars) = self.utf16_lines.get(&line) { - for c in utf16_chars { + fn wide_to_utf8_col(&self, enc: WideEncoding, line: u32, mut col: u32) -> TextSize { + if let Some(wide_chars) = self.line_wide_chars.get(&line) { + for c in wide_chars { if col > u32::from(c.start) { - col += u32::from(c.len()) - c.len_utf16() as u32; + col += u32::from(c.len()) - c.wide_len(enc) as u32; } else { // From here on, all utf16 characters come *after* the character we are mapping, // so we don't need to take them into account @@ -167,6 +183,9 @@ impl LineIndex { #[cfg(test)] mod tests { + use test_utils::skip_slow_tests; + + use super::WideEncoding::{Utf16, Utf32}; use super::*; #[test] @@ -210,67 +229,59 @@ mod tests { const C: char = 'x'; ", ); - assert_eq!(col_index.utf16_lines.len(), 0); + assert_eq!(col_index.line_wide_chars.len(), 0); } #[test] - fn test_single_char() { - let col_index = LineIndex::new( - " -const C: char = 'メ'; -", - ); + fn test_every_chars() { + if skip_slow_tests() { + return; + } - assert_eq!(col_index.utf16_lines.len(), 1); - assert_eq!(col_index.utf16_lines[&1].len(), 1); - assert_eq!(col_index.utf16_lines[&1][0], Utf16Char { start: 17.into(), end: 20.into() }); + let text: String = { + let mut chars: Vec = ((0 as char)..char::MAX).collect(); // Neat! + chars.extend("\n".repeat(chars.len() / 16).chars()); + let mut rng = oorandom::Rand32::new(stdx::rand::seed()); + stdx::rand::shuffle(&mut chars, |i| rng.rand_range(0..i as u32) as usize); + chars.into_iter().collect() + }; + assert!(text.contains('💩')); // Sanity check. - // UTF-8 to UTF-16, no changes - assert_eq!(col_index.utf8_to_utf16_col(1, 15.into()), 15); + let line_index = LineIndex::new(&text); - // UTF-8 to UTF-16 - assert_eq!(col_index.utf8_to_utf16_col(1, 22.into()), 20); + let mut lin_col = LineCol { line: 0, col: 0 }; + let mut col_utf16 = 0; + let mut col_utf32 = 0; + for (offset, c) in text.char_indices() { + let got_offset = line_index.offset(lin_col).unwrap(); + assert_eq!(usize::from(got_offset), offset); - // UTF-16 to UTF-8, no changes - assert_eq!(col_index.utf16_to_utf8_col(1, 15), TextSize::from(15)); + let got_lin_col = line_index.line_col(got_offset); + assert_eq!(got_lin_col, lin_col); - // UTF-16 to UTF-8 - assert_eq!(col_index.utf16_to_utf8_col(1, 19), TextSize::from(21)); + for enc in [Utf16, Utf32] { + let wide_lin_col = line_index.to_wide(enc, lin_col); + let got_lin_col = line_index.to_utf8(enc, wide_lin_col); + assert_eq!(got_lin_col, lin_col); - let col_index = LineIndex::new("a𐐏b"); - assert_eq!(col_index.utf16_to_utf8_col(0, 3), TextSize::from(5)); - } + let want_col = match enc { + Utf16 => col_utf16, + Utf32 => col_utf32, + }; + assert_eq!(wide_lin_col.col, want_col) + } - #[test] - fn test_string() { - let col_index = LineIndex::new( - " -const C: char = \"メ メ\"; -", - ); - - assert_eq!(col_index.utf16_lines.len(), 1); - assert_eq!(col_index.utf16_lines[&1].len(), 2); - assert_eq!(col_index.utf16_lines[&1][0], Utf16Char { start: 17.into(), end: 20.into() }); - assert_eq!(col_index.utf16_lines[&1][1], Utf16Char { start: 21.into(), end: 24.into() }); - - // UTF-8 to UTF-16 - assert_eq!(col_index.utf8_to_utf16_col(1, 15.into()), 15); - - assert_eq!(col_index.utf8_to_utf16_col(1, 21.into()), 19); - assert_eq!(col_index.utf8_to_utf16_col(1, 25.into()), 21); - - assert!(col_index.utf8_to_utf16_col(2, 15.into()) == 15); - - // UTF-16 to UTF-8 - assert_eq!(col_index.utf16_to_utf8_col(1, 15), TextSize::from(15)); - - // メ UTF-8: 0xE3 0x83 0xA1, UTF-16: 0x30E1 - assert_eq!(col_index.utf16_to_utf8_col(1, 17), TextSize::from(17)); // first メ at 17..20 - assert_eq!(col_index.utf16_to_utf8_col(1, 18), TextSize::from(20)); // space - assert_eq!(col_index.utf16_to_utf8_col(1, 19), TextSize::from(21)); // second メ at 21..24 - - assert_eq!(col_index.utf16_to_utf8_col(2, 15), TextSize::from(15)); + if c == '\n' { + lin_col.line += 1; + lin_col.col = 0; + col_utf16 = 0; + col_utf32 = 0; + } else { + lin_col.col += c.len_utf8() as u32; + col_utf16 += c.len_utf16() as u32; + col_utf32 += 1; + } + } } #[test] diff --git a/crates/ide-db/src/search.rs b/crates/ide-db/src/search.rs index ada2821d6b14..c18a27f17d22 100644 --- a/crates/ide-db/src/search.rs +++ b/crates/ide-db/src/search.rs @@ -455,15 +455,21 @@ impl<'a> FindUsages<'a> { } let find_nodes = move |name: &str, node: &syntax::SyntaxNode, offset: TextSize| { - node.token_at_offset(offset).find(|it| it.text() == name).map(|token| { - // FIXME: There should be optimization potential here - // Currently we try to descend everything we find which - // means we call `Semantics::descend_into_macros` on - // every textual hit. That function is notoriously - // expensive even for things that do not get down mapped - // into macros. - sema.descend_into_macros(token).into_iter().filter_map(|it| it.parent()) - }) + node.token_at_offset(offset) + .find(|it| { + // `name` is stripped of raw ident prefix. See the comment on name retrieval above. + it.text().trim_start_matches("r#") == name + }) + .into_iter() + .flat_map(|token| { + // FIXME: There should be optimization potential here + // Currently we try to descend everything we find which + // means we call `Semantics::descend_into_macros` on + // every textual hit. That function is notoriously + // expensive even for things that do not get down mapped + // into macros. + sema.descend_into_macros(token).into_iter().filter_map(|it| it.parent()) + }) }; for (text, file_id, search_range) in scope_files(sema, &search_scope) { @@ -471,30 +477,23 @@ impl<'a> FindUsages<'a> { // Search for occurrences of the items name for offset in match_indices(&text, finder, search_range) { - if let Some(iter) = find_nodes(name, &tree, offset) { - for name in iter.filter_map(ast::NameLike::cast) { - if match name { - ast::NameLike::NameRef(name_ref) => { - self.found_name_ref(&name_ref, sink) - } - ast::NameLike::Name(name) => self.found_name(&name, sink), - ast::NameLike::Lifetime(lifetime) => { - self.found_lifetime(&lifetime, sink) - } - } { - return; - } + for name in find_nodes(name, &tree, offset).filter_map(ast::NameLike::cast) { + if match name { + ast::NameLike::NameRef(name_ref) => self.found_name_ref(&name_ref, sink), + ast::NameLike::Name(name) => self.found_name(&name, sink), + ast::NameLike::Lifetime(lifetime) => self.found_lifetime(&lifetime, sink), + } { + return; } } } // Search for occurrences of the `Self` referring to our type if let Some((self_ty, finder)) = &include_self_kw_refs { for offset in match_indices(&text, finder, search_range) { - if let Some(iter) = find_nodes("Self", &tree, offset) { - for name_ref in iter.filter_map(ast::NameRef::cast) { - if self.found_self_ty_name_ref(self_ty, &name_ref, sink) { - return; - } + for name_ref in find_nodes("Self", &tree, offset).filter_map(ast::NameRef::cast) + { + if self.found_self_ty_name_ref(self_ty, &name_ref, sink) { + return; } } } @@ -513,21 +512,21 @@ impl<'a> FindUsages<'a> { let tree = Lazy::new(move || sema.parse(file_id).syntax().clone()); for offset in match_indices(&text, finder, search_range) { - if let Some(iter) = find_nodes("super", &tree, offset) { - for name_ref in iter.filter_map(ast::NameRef::cast) { - if self.found_name_ref(&name_ref, sink) { - return; - } + for name_ref in + find_nodes("super", &tree, offset).filter_map(ast::NameRef::cast) + { + if self.found_name_ref(&name_ref, sink) { + return; } } } if let Some(finder) = &is_crate_root { for offset in match_indices(&text, finder, search_range) { - if let Some(iter) = find_nodes("crate", &tree, offset) { - for name_ref in iter.filter_map(ast::NameRef::cast) { - if self.found_name_ref(&name_ref, sink) { - return; - } + for name_ref in + find_nodes("crate", &tree, offset).filter_map(ast::NameRef::cast) + { + if self.found_name_ref(&name_ref, sink) { + return; } } } @@ -566,11 +565,10 @@ impl<'a> FindUsages<'a> { let finder = &Finder::new("self"); for offset in match_indices(&text, finder, search_range) { - if let Some(iter) = find_nodes("self", &tree, offset) { - for name_ref in iter.filter_map(ast::NameRef::cast) { - if self.found_self_module_name_ref(&name_ref, sink) { - return; - } + for name_ref in find_nodes("self", &tree, offset).filter_map(ast::NameRef::cast) + { + if self.found_self_module_name_ref(&name_ref, sink) { + return; } } } diff --git a/crates/ide/Cargo.toml b/crates/ide/Cargo.toml index 414c08ff7e03..30e514e4136a 100644 --- a/crates/ide/Cargo.toml +++ b/crates/ide/Cargo.toml @@ -22,7 +22,7 @@ pulldown-cmark-to-cmark = "10.0.4" pulldown-cmark = { version = "0.9.1", default-features = false } url = "2.3.1" dot = "0.1.4" -smallvec = "1.10.0" +smallvec.workspace = true # local deps cfg.workspace = true diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 2058a4f5f190..5f2c61f5b5f8 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -201,6 +201,23 @@ fn hover_simple( Some(render::struct_rest_pat(sema, config, &record_pat)) }) + }) + // try () call hovers + .or_else(|| { + descended().find_map(|token| { + if token.kind() != T!['('] && token.kind() != T![')'] { + return None; + } + let arg_list = token.parent().and_then(ast::ArgList::cast)?.syntax().parent()?; + let call_expr = syntax::match_ast! { + match arg_list { + ast::CallExpr(expr) => expr.into(), + ast::MethodCallExpr(expr) => expr.into(), + _ => return None, + } + }; + render::type_info_of(sema, config, &Either::Left(call_expr)) + }) }); result.map(|mut res: HoverResult| { diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index 2830212add8e..bd7ce2f1d0d0 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -5612,3 +5612,38 @@ fn main() { "#, ); } + +#[test] +fn hover_call_parens() { + check( + r#" +fn foo() -> i32 {} +fn main() { + foo($0); +} +"#, + expect![[r#" + *)* + ```rust + i32 + ``` + "#]], + ); + check( + r#" +struct S; +impl S { + fn foo(self) -> i32 {} +} +fn main() { + S.foo($0); +} +"#, + expect![[r#" + *)* + ```rust + i32 + ``` + "#]], + ); +} diff --git a/crates/ide/src/inlay_hints/binding_mode.rs b/crates/ide/src/inlay_hints/binding_mode.rs index 11b9cd269bfa..5d9729263c27 100644 --- a/crates/ide/src/inlay_hints/binding_mode.rs +++ b/crates/ide/src/inlay_hints/binding_mode.rs @@ -29,8 +29,17 @@ pub(super) fn hints( _ => None, }) .last(); - let range = - outer_paren_pat.as_ref().map_or_else(|| pat.syntax(), |it| it.syntax()).text_range(); + let range = outer_paren_pat.as_ref().map_or_else( + || match pat { + // for ident patterns that @ bind a name, render the un-ref patterns in front of the inner pattern + // instead of the name as that makes it more clear and doesn't really change the outcome + ast::Pat::IdentPat(it) => { + it.pat().map_or_else(|| it.syntax().text_range(), |it| it.syntax().text_range()) + } + it => it.syntax().text_range(), + }, + |it| it.syntax().text_range(), + ); let pattern_adjustments = sema.pattern_adjustments(pat); pattern_adjustments.iter().for_each(|ty| { let reference = ty.is_reference(); @@ -123,4 +132,20 @@ fn __( }"#, ); } + + #[test] + fn hints_binding_modes_complex_ident_pat() { + check_with_config( + InlayHintsConfig { binding_mode_hints: true, ..DISABLED_CONFIG }, + r#" +struct Struct { + field: &'static str, +} +fn foo(s @ Struct { field, .. }: &Struct) {} + //^^^^^^^^^^^^^^^^^^^^^^^^ref + //^^^^^^^^^^^^^^^^^^^^& + //^^^^^ref +"#, + ); + } } diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 4ead9d4d0a86..f2b535bdc7ef 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -115,7 +115,7 @@ pub use ide_db::{ SourceRoot, SourceRootId, }, label::Label, - line_index::{LineCol, LineColUtf16, LineIndex}, + line_index::{LineCol, LineIndex}, search::{ReferenceCategory, SearchScope}, source_change::{FileSystemEdit, SourceChange}, symbol_index::Query, diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index 60fb1544a8fe..cabbc287279f 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs @@ -2016,4 +2016,19 @@ fn method$0() {} "#]], ); } + + #[test] + fn raw_identifier() { + check( + r#" +fn r#fn$0() {} +fn main() { r#fn(); } +"#, + expect![[r#" + r#fn Function FileId(0) 0..12 3..7 + + FileId(0) 25..29 + "#]], + ); + } } diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs index 8e89160ef5e0..c0237e1edd0d 100644 --- a/crates/ide/src/rename.rs +++ b/crates/ide/src/rename.rs @@ -1371,7 +1371,6 @@ pub fn baz() {} #[test] fn test_rename_mod_from_raw_ident() { - // FIXME: `r#fn` in path expression is not renamed. check_expect( "foo", r#" @@ -1397,6 +1396,10 @@ pub fn baz() {} insert: "foo", delete: 4..8, }, + Indel { + insert: "foo", + delete: 23..27, + }, ], }, }, diff --git a/crates/ide/src/shuffle_crate_graph.rs b/crates/ide/src/shuffle_crate_graph.rs index ae539a5d397f..e606072a8237 100644 --- a/crates/ide/src/shuffle_crate_graph.rs +++ b/crates/ide/src/shuffle_crate_graph.rs @@ -18,7 +18,9 @@ pub(crate) fn shuffle_crate_graph(db: &mut RootDatabase) { let crate_graph = db.crate_graph(); let mut shuffled_ids = crate_graph.iter().collect::>(); - shuffle(&mut shuffled_ids); + + let mut rng = oorandom::Rand32::new(stdx::rand::seed()); + stdx::rand::shuffle(&mut shuffled_ids, |i| rng.rand_range(0..i as u32) as usize); let mut new_graph = CrateGraph::default(); @@ -52,21 +54,3 @@ pub(crate) fn shuffle_crate_graph(db: &mut RootDatabase) { db.set_crate_graph_with_durability(Arc::new(new_graph), Durability::HIGH); } - -fn shuffle(slice: &mut [T]) { - let mut rng = oorandom::Rand32::new(seed()); - - let mut remaining = slice.len() - 1; - while remaining > 0 { - let index = rng.rand_range(0..remaining as u32); - slice.swap(remaining, index as usize); - remaining -= 1; - } -} - -fn seed() -> u64 { - use std::collections::hash_map::RandomState; - use std::hash::{BuildHasher, Hasher}; - - RandomState::new().build_hasher().finish() -} diff --git a/crates/ide/src/signature_help.rs b/crates/ide/src/signature_help.rs index a666562f1010..f70ca55a508d 100644 --- a/crates/ide/src/signature_help.rs +++ b/crates/ide/src/signature_help.rs @@ -7,12 +7,16 @@ use either::Either; use hir::{ AssocItem, GenericParam, HasAttrs, HirDisplay, ModuleDef, PathResolution, Semantics, Trait, }; -use ide_db::{active_parameter::callable_for_node, base_db::FilePosition, FxIndexMap}; +use ide_db::{ + active_parameter::{callable_for_node, generic_def_for_node}, + base_db::FilePosition, + FxIndexMap, +}; use stdx::format_to; use syntax::{ algo, ast::{self, HasArgList}, - match_ast, AstNode, Direction, SyntaxKind, SyntaxToken, TextRange, TextSize, + match_ast, AstNode, Direction, SyntaxToken, TextRange, TextSize, }; use crate::RootDatabase; @@ -105,10 +109,10 @@ pub(crate) fn signature_help(db: &RootDatabase, position: FilePosition) -> Optio // Stop at multi-line expressions, since the signature of the outer call is not very // helpful inside them. if let Some(expr) = ast::Expr::cast(node.clone()) { - if expr.syntax().text().contains_char('\n') - && expr.syntax().kind() != SyntaxKind::RECORD_EXPR + if !matches!(expr, ast::Expr::RecordExpr(..)) + && expr.syntax().text().contains_char('\n') { - return None; + break; } } } @@ -122,18 +126,16 @@ fn signature_help_for_call( token: SyntaxToken, ) -> Option { // Find the calling expression and its NameRef - let mut node = arg_list.syntax().parent()?; + let mut nodes = arg_list.syntax().ancestors().skip(1); let calling_node = loop { - if let Some(callable) = ast::CallableExpr::cast(node.clone()) { - if callable + if let Some(callable) = ast::CallableExpr::cast(nodes.next()?) { + let inside_callable = callable .arg_list() - .map_or(false, |it| it.syntax().text_range().contains(token.text_range().start())) - { + .map_or(false, |it| it.syntax().text_range().contains(token.text_range().start())); + if inside_callable { break callable; } } - - node = node.parent()?; }; let (callable, active_parameter) = callable_for_node(sema, &calling_node, &token)?; @@ -216,59 +218,11 @@ fn signature_help_for_call( fn signature_help_for_generics( sema: &Semantics<'_, RootDatabase>, - garg_list: ast::GenericArgList, + arg_list: ast::GenericArgList, token: SyntaxToken, ) -> Option { - let arg_list = garg_list - .syntax() - .ancestors() - .filter_map(ast::GenericArgList::cast) - .find(|list| list.syntax().text_range().contains(token.text_range().start()))?; - - let mut active_parameter = arg_list - .generic_args() - .take_while(|arg| arg.syntax().text_range().end() <= token.text_range().start()) - .count(); - - let first_arg_is_non_lifetime = arg_list - .generic_args() - .next() - .map_or(false, |arg| !matches!(arg, ast::GenericArg::LifetimeArg(_))); - - let mut generics_def = if let Some(path) = - arg_list.syntax().ancestors().find_map(ast::Path::cast) - { - let res = sema.resolve_path(&path)?; - let generic_def: hir::GenericDef = match res { - hir::PathResolution::Def(hir::ModuleDef::Adt(it)) => it.into(), - hir::PathResolution::Def(hir::ModuleDef::Function(it)) => it.into(), - hir::PathResolution::Def(hir::ModuleDef::Trait(it)) => it.into(), - hir::PathResolution::Def(hir::ModuleDef::TypeAlias(it)) => it.into(), - hir::PathResolution::Def(hir::ModuleDef::Variant(it)) => it.into(), - hir::PathResolution::Def(hir::ModuleDef::BuiltinType(_)) - | hir::PathResolution::Def(hir::ModuleDef::Const(_)) - | hir::PathResolution::Def(hir::ModuleDef::Macro(_)) - | hir::PathResolution::Def(hir::ModuleDef::Module(_)) - | hir::PathResolution::Def(hir::ModuleDef::Static(_)) => return None, - hir::PathResolution::BuiltinAttr(_) - | hir::PathResolution::ToolModule(_) - | hir::PathResolution::Local(_) - | hir::PathResolution::TypeParam(_) - | hir::PathResolution::ConstParam(_) - | hir::PathResolution::SelfType(_) - | hir::PathResolution::DeriveHelper(_) => return None, - }; - - generic_def - } else if let Some(method_call) = arg_list.syntax().parent().and_then(ast::MethodCallExpr::cast) - { - // recv.method::<$0>() - let method = sema.resolve_method_call(&method_call)?; - method.into() - } else { - return None; - }; - + let (mut generics_def, mut active_parameter, first_arg_is_non_lifetime) = + generic_def_for_node(sema, &arg_list, &token)?; let mut res = SignatureHelp { doc: None, signature: String::new(), @@ -307,9 +261,9 @@ fn signature_help_for_generics( // eg. `None::` // We'll use the signature of the enum, but include the docs of the variant. res.doc = it.docs(db).map(|it| it.into()); - let it = it.parent_enum(db); - format_to!(res.signature, "enum {}", it.name(db)); - generics_def = it.into(); + let enum_ = it.parent_enum(db); + format_to!(res.signature, "enum {}", enum_.name(db)); + generics_def = enum_.into(); } // These don't have generic args that can be specified hir::GenericDef::Impl(_) | hir::GenericDef::Const(_) => return None, @@ -388,16 +342,13 @@ fn signature_help_for_record_lit( record: ast::RecordExpr, token: SyntaxToken, ) -> Option { - let arg_list = record - .syntax() - .ancestors() - .filter_map(ast::RecordExpr::cast) - .find(|list| list.syntax().text_range().contains(token.text_range().start()))?; - - let active_parameter = arg_list + let active_parameter = record .record_expr_field_list()? - .fields() - .take_while(|arg| arg.syntax().text_range().end() <= token.text_range().start()) + .syntax() + .children_with_tokens() + .filter_map(syntax::NodeOrToken::into_token) + .filter(|t| t.kind() == syntax::T![,]) + .take_while(|t| t.text_range().start() <= token.text_range().start()) .count(); let mut res = SignatureHelp { @@ -1594,4 +1545,27 @@ impl S { "#]], ); } + + #[test] + fn test_enum_in_nested_method_in_lambda() { + check( + r#" +enum A { + A, + B +} + +fn bar(_: A) { } + +fn main() { + let foo = Foo; + std::thread::spawn(move || { bar(A:$0) } ); +} +"#, + expect![[r#" + fn bar(_: A) + ^^^^ + "#]], + ); + } } diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs index 2f870d769c0f..fc9b5d3ba4cd 100644 --- a/crates/ide/src/syntax_highlighting/tests.rs +++ b/crates/ide/src/syntax_highlighting/tests.rs @@ -1126,5 +1126,5 @@ fn benchmark_syntax_highlighting_parser() { .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Function)) .count() }; - assert_eq!(hash, 1609); + assert_eq!(hash, 1608); } diff --git a/crates/mbe/Cargo.toml b/crates/mbe/Cargo.toml index 280ffc219bac..82105522ebdd 100644 --- a/crates/mbe/Cargo.toml +++ b/crates/mbe/Cargo.toml @@ -14,7 +14,7 @@ doctest = false [dependencies] cov-mark = "2.0.0-pre.1" rustc-hash = "1.1.0" -smallvec = "1.10.0" +smallvec.workspace = true tracing = "0.1.35" # local deps diff --git a/crates/parser/Cargo.toml b/crates/parser/Cargo.toml index 08359133f1aa..6e962abd7547 100644 --- a/crates/parser/Cargo.toml +++ b/crates/parser/Cargo.toml @@ -20,4 +20,5 @@ limit.workspace = true [dev-dependencies] expect-test = "1.4.0" +stdx.workspace = true sourcegen.workspace = true diff --git a/crates/parser/src/grammar.rs b/crates/parser/src/grammar.rs index 485b612f0818..15ec9e167e02 100644 --- a/crates/parser/src/grammar.rs +++ b/crates/parser/src/grammar.rs @@ -200,6 +200,8 @@ impl BlockLike { } } +const VISIBILITY_FIRST: TokenSet = TokenSet::new(&[T![pub], T![crate]]); + fn opt_visibility(p: &mut Parser<'_>, in_tuple_field: bool) -> bool { match p.current() { T![pub] => { @@ -340,3 +342,31 @@ fn error_block(p: &mut Parser<'_>, message: &str) { p.eat(T!['}']); m.complete(p, ERROR); } + +/// The `parser` passed this is required to at least consume one token if it returns `true`. +/// If the `parser` returns false, parsing will stop. +fn delimited( + p: &mut Parser<'_>, + bra: SyntaxKind, + ket: SyntaxKind, + delim: SyntaxKind, + first_set: TokenSet, + mut parser: impl FnMut(&mut Parser<'_>) -> bool, +) { + p.bump(bra); + while !p.at(ket) && !p.at(EOF) { + if !parser(p) { + break; + } + if !p.at(delim) { + if p.at_ts(first_set) { + p.error(format!("expected {:?}", delim)); + } else { + break; + } + } else { + p.bump(delim); + } + } + p.expect(ket); +} diff --git a/crates/parser/src/grammar/attributes.rs b/crates/parser/src/grammar/attributes.rs index 0cf6a16f86a5..4ecaa6e6a85e 100644 --- a/crates/parser/src/grammar/attributes.rs +++ b/crates/parser/src/grammar/attributes.rs @@ -1,5 +1,7 @@ use super::*; +pub(super) const ATTRIBUTE_FIRST: TokenSet = TokenSet::new(&[T![#]]); + pub(super) fn inner_attrs(p: &mut Parser<'_>) { while p.at(T![#]) && p.nth(1) == T![!] { attr(p, true); diff --git a/crates/parser/src/grammar/expressions.rs b/crates/parser/src/grammar/expressions.rs index 7516ac3c4bd3..4b080102a2c3 100644 --- a/crates/parser/src/grammar/expressions.rs +++ b/crates/parser/src/grammar/expressions.rs @@ -1,5 +1,7 @@ mod atom; +use crate::grammar::attributes::ATTRIBUTE_FIRST; + use super::*; pub(crate) use self::atom::{block_expr, match_arm_list}; @@ -68,6 +70,12 @@ pub(super) fn stmt(p: &mut Parser<'_>, semicolon: Semicolon) { Err(m) => m, }; + if !p.at_ts(EXPR_FIRST) { + p.err_and_bump("expected expression, item or let statement"); + m.abandon(p); + return; + } + if let Some((cm, blocklike)) = expr_stmt(p, Some(m)) { if !(p.at(T!['}']) || (semicolon != Semicolon::Required && p.at(EOF))) { // test no_semi_after_block @@ -227,6 +235,12 @@ fn expr_bp( attributes::outer_attrs(p); m }); + + if !p.at_ts(EXPR_FIRST) { + p.err_recover("expected expression", atom::EXPR_RECOVERY_SET); + m.abandon(p); + return None; + } let mut lhs = match lhs(p, r) { Some((lhs, blocklike)) => { let lhs = lhs.extend_to(p, m); @@ -551,23 +565,20 @@ fn cast_expr(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker { m.complete(p, CAST_EXPR) } +// test_err arg_list_recovery +// fn main() { +// foo(bar::); +// foo(bar:); +// foo(bar+); +// } fn arg_list(p: &mut Parser<'_>) { assert!(p.at(T!['('])); let m = p.start(); - p.bump(T!['(']); - while !p.at(T![')']) && !p.at(EOF) { - // test arg_with_attr - // fn main() { - // foo(#[attr] 92) - // } - if !expr(p) { - break; - } - if !p.at(T![')']) && !p.expect(T![,]) { - break; - } - } - p.eat(T![')']); + // test arg_with_attr + // fn main() { + // foo(#[attr] 92) + // } + delimited(p, T!['('], T![')'], T![,], EXPR_FIRST.union(ATTRIBUTE_FIRST), expr); m.complete(p, ARG_LIST); } diff --git a/crates/parser/src/grammar/expressions/atom.rs b/crates/parser/src/grammar/expressions/atom.rs index a23f900b7386..efc2603835e8 100644 --- a/crates/parser/src/grammar/expressions/atom.rs +++ b/crates/parser/src/grammar/expressions/atom.rs @@ -40,26 +40,28 @@ pub(super) const ATOM_EXPR_FIRST: TokenSet = T!['{'], T!['['], T![|], - T![move], - T![box], - T![if], - T![while], - T![match], - T![unsafe], - T![return], - T![yield], - T![do], - T![break], - T![continue], T![async], - T![try], + T![box], + T![break], T![const], - T![loop], + T![continue], + T![do], T![for], + T![if], + T![let], + T![loop], + T![match], + T![move], + T![return], + T![static], + T![try], + T![unsafe], + T![while], + T![yield], LIFETIME_IDENT, ])); -const EXPR_RECOVERY_SET: TokenSet = TokenSet::new(&[T![let]]); +pub(super) const EXPR_RECOVERY_SET: TokenSet = TokenSet::new(&[T![')'], T![']']]); pub(super) fn atom_expr( p: &mut Parser<'_>, @@ -116,7 +118,7 @@ pub(super) fn atom_expr( // fn main() { // 'loop: impl // } - p.error("expected a loop"); + p.error("expected a loop or block"); m.complete(p, ERROR); return None; } @@ -157,7 +159,7 @@ pub(super) fn atom_expr( T![for] => for_expr(p, None), _ => { - p.err_recover("expected expression", EXPR_RECOVERY_SET); + p.err_and_bump("expected expression"); return None; } }; diff --git a/crates/parser/src/grammar/generic_args.rs b/crates/parser/src/grammar/generic_args.rs index c438943a0026..919d9b91ebab 100644 --- a/crates/parser/src/grammar/generic_args.rs +++ b/crates/parser/src/grammar/generic_args.rs @@ -5,27 +5,35 @@ pub(super) fn opt_generic_arg_list(p: &mut Parser<'_>, colon_colon_required: boo if p.at(T![::]) && p.nth(2) == T![<] { m = p.start(); p.bump(T![::]); - p.bump(T![<]); } else if !colon_colon_required && p.at(T![<]) && p.nth(1) != T![=] { m = p.start(); - p.bump(T![<]); } else { return; } - while !p.at(EOF) && !p.at(T![>]) { - generic_arg(p); - if !p.at(T![>]) && !p.expect(T![,]) { - break; - } - } - p.expect(T![>]); + delimited(p, T![<], T![>], T![,], GENERIC_ARG_FIRST, generic_arg); m.complete(p, GENERIC_ARG_LIST); } +const GENERIC_ARG_FIRST: TokenSet = TokenSet::new(&[ + LIFETIME_IDENT, + IDENT, + T!['{'], + T![true], + T![false], + T![-], + INT_NUMBER, + FLOAT_NUMBER, + CHAR, + BYTE, + STRING, + BYTE_STRING, +]) +.union(types::TYPE_FIRST); + // test generic_arg // type T = S; -fn generic_arg(p: &mut Parser<'_>) { +fn generic_arg(p: &mut Parser<'_>) -> bool { match p.current() { LIFETIME_IDENT => lifetime_arg(p), T!['{'] | T![true] | T![false] | T![-] => const_arg(p), @@ -68,8 +76,10 @@ fn generic_arg(p: &mut Parser<'_>) { } } } - _ => type_arg(p), + _ if p.at_ts(types::TYPE_FIRST) => type_arg(p), + _ => return false, } + true } // test lifetime_arg diff --git a/crates/parser/src/grammar/generic_params.rs b/crates/parser/src/grammar/generic_params.rs index 6db28ef13239..7fcf938babdb 100644 --- a/crates/parser/src/grammar/generic_params.rs +++ b/crates/parser/src/grammar/generic_params.rs @@ -1,3 +1,5 @@ +use crate::grammar::attributes::ATTRIBUTE_FIRST; + use super::*; pub(super) fn opt_generic_param_list(p: &mut Parser<'_>) { @@ -11,32 +13,31 @@ pub(super) fn opt_generic_param_list(p: &mut Parser<'_>) { fn generic_param_list(p: &mut Parser<'_>) { assert!(p.at(T![<])); let m = p.start(); - p.bump(T![<]); + delimited(p, T![<], T![>], T![,], GENERIC_PARAM_FIRST.union(ATTRIBUTE_FIRST), |p| { + // test generic_param_attribute + // fn foo<#[lt_attr] 'a, #[t_attr] T>() {} + let m = p.start(); + attributes::outer_attrs(p); + generic_param(p, m) + }); - while !p.at(EOF) && !p.at(T![>]) { - generic_param(p); - if !p.at(T![>]) && !p.expect(T![,]) { - break; - } - } - p.expect(T![>]); m.complete(p, GENERIC_PARAM_LIST); } -fn generic_param(p: &mut Parser<'_>) { - let m = p.start(); - // test generic_param_attribute - // fn foo<#[lt_attr] 'a, #[t_attr] T>() {} - attributes::outer_attrs(p); +const GENERIC_PARAM_FIRST: TokenSet = TokenSet::new(&[IDENT, LIFETIME_IDENT, T![const]]); + +fn generic_param(p: &mut Parser<'_>, m: Marker) -> bool { match p.current() { LIFETIME_IDENT => lifetime_param(p, m), IDENT => type_param(p, m), T![const] => const_param(p, m), _ => { m.abandon(p); - p.err_and_bump("expected type parameter"); + p.err_and_bump("expected generic parameter"); + return false; } } + true } // test lifetime_param diff --git a/crates/parser/src/grammar/items/adt.rs b/crates/parser/src/grammar/items/adt.rs index e7d30516b951..17f41b8e13a4 100644 --- a/crates/parser/src/grammar/items/adt.rs +++ b/crates/parser/src/grammar/items/adt.rs @@ -1,3 +1,5 @@ +use crate::grammar::attributes::ATTRIBUTE_FIRST; + use super::*; // test struct_item @@ -141,28 +143,31 @@ pub(crate) fn record_field_list(p: &mut Parser<'_>) { } } +const TUPLE_FIELD_FIRST: TokenSet = + types::TYPE_FIRST.union(ATTRIBUTE_FIRST).union(VISIBILITY_FIRST); + fn tuple_field_list(p: &mut Parser<'_>) { assert!(p.at(T!['('])); let m = p.start(); - p.bump(T!['(']); - while !p.at(T![')']) && !p.at(EOF) { + delimited(p, T!['('], T![')'], T![,], TUPLE_FIELD_FIRST, |p| { let m = p.start(); // test tuple_field_attrs // struct S (#[attr] f32); attributes::outer_attrs(p); - opt_visibility(p, true); + let has_vis = opt_visibility(p, true); if !p.at_ts(types::TYPE_FIRST) { p.error("expected a type"); - m.complete(p, ERROR); - break; + if has_vis { + m.complete(p, ERROR); + } else { + m.abandon(p); + } + return false; } types::type_(p); m.complete(p, TUPLE_FIELD); + true + }); - if !p.at(T![')']) { - p.expect(T![,]); - } - } - p.expect(T![')']); m.complete(p, TUPLE_FIELD_LIST); } diff --git a/crates/parser/src/grammar/params.rs b/crates/parser/src/grammar/params.rs index 20e8e95f0662..74eae9151a26 100644 --- a/crates/parser/src/grammar/params.rs +++ b/crates/parser/src/grammar/params.rs @@ -1,3 +1,5 @@ +use crate::grammar::attributes::ATTRIBUTE_FIRST; + use super::*; // test param_list @@ -66,14 +68,20 @@ fn list_(p: &mut Parser<'_>, flavor: Flavor) { } }; - if !p.at_ts(PARAM_FIRST) { + if !p.at_ts(PARAM_FIRST.union(ATTRIBUTE_FIRST)) { p.error("expected value parameter"); m.abandon(p); break; } param(p, m, flavor); - if !p.at(ket) { - p.expect(T![,]); + if !p.at(T![,]) { + if p.at_ts(PARAM_FIRST.union(ATTRIBUTE_FIRST)) { + p.error("expected `,`"); + } else { + break; + } + } else { + p.bump(T![,]); } } diff --git a/crates/parser/src/grammar/paths.rs b/crates/parser/src/grammar/paths.rs index af3b6f63cf51..1064ae9970c9 100644 --- a/crates/parser/src/grammar/paths.rs +++ b/crates/parser/src/grammar/paths.rs @@ -67,6 +67,10 @@ fn path_for_qualifier( } } +const EXPR_PATH_SEGMENT_RECOVERY_SET: TokenSet = + items::ITEM_RECOVERY_SET.union(TokenSet::new(&[T![')'], T![,], T![let]])); +const TYPE_PATH_SEGMENT_RECOVERY_SET: TokenSet = types::TYPE_RECOVERY_SET; + fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) { let m = p.start(); // test qual_paths @@ -102,7 +106,12 @@ fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) { m.complete(p, NAME_REF); } _ => { - p.err_recover("expected identifier", items::ITEM_RECOVERY_SET); + let recover_set = match mode { + Mode::Use => items::ITEM_RECOVERY_SET, + Mode::Type => TYPE_PATH_SEGMENT_RECOVERY_SET, + Mode::Expr => EXPR_PATH_SEGMENT_RECOVERY_SET, + }; + p.err_recover("expected identifier", recover_set); if empty { // test_err empty_segment // use crate::; diff --git a/crates/parser/src/grammar/types.rs b/crates/parser/src/grammar/types.rs index 5c6e18fee8bf..7d0b156c5a06 100644 --- a/crates/parser/src/grammar/types.rs +++ b/crates/parser/src/grammar/types.rs @@ -17,8 +17,9 @@ pub(super) const TYPE_FIRST: TokenSet = paths::PATH_FIRST.union(TokenSet::new(&[ T![Self], ])); -const TYPE_RECOVERY_SET: TokenSet = TokenSet::new(&[ +pub(super) const TYPE_RECOVERY_SET: TokenSet = TokenSet::new(&[ T![')'], + T![>], T![,], // test_err struct_field_recover // struct S { f pub g: () } diff --git a/crates/parser/src/tests.rs b/crates/parser/src/tests.rs index c1b4e9a7d8ae..2fec765bd787 100644 --- a/crates/parser/src/tests.rs +++ b/crates/parser/src/tests.rs @@ -15,6 +15,7 @@ use crate::{LexedStr, TopEntryPoint}; #[test] fn lex_ok() { for case in TestCase::list("lexer/ok") { + let _guard = stdx::panic_context::enter(format!("{:?}", case.rs)); let actual = lex(&case.text); expect_file![case.rast].assert_eq(&actual) } @@ -23,6 +24,7 @@ fn lex_ok() { #[test] fn lex_err() { for case in TestCase::list("lexer/err") { + let _guard = stdx::panic_context::enter(format!("{:?}", case.rs)); let actual = lex(&case.text); expect_file![case.rast].assert_eq(&actual) } @@ -46,6 +48,7 @@ fn lex(text: &str) -> String { #[test] fn parse_ok() { for case in TestCase::list("parser/ok") { + let _guard = stdx::panic_context::enter(format!("{:?}", case.rs)); let (actual, errors) = parse(TopEntryPoint::SourceFile, &case.text); assert!(!errors, "errors in an OK file {}:\n{actual}", case.rs.display()); expect_file![case.rast].assert_eq(&actual); @@ -55,6 +58,7 @@ fn parse_ok() { #[test] fn parse_inline_ok() { for case in TestCase::list("parser/inline/ok") { + let _guard = stdx::panic_context::enter(format!("{:?}", case.rs)); let (actual, errors) = parse(TopEntryPoint::SourceFile, &case.text); assert!(!errors, "errors in an OK file {}:\n{actual}", case.rs.display()); expect_file![case.rast].assert_eq(&actual); @@ -64,6 +68,7 @@ fn parse_inline_ok() { #[test] fn parse_err() { for case in TestCase::list("parser/err") { + let _guard = stdx::panic_context::enter(format!("{:?}", case.rs)); let (actual, errors) = parse(TopEntryPoint::SourceFile, &case.text); assert!(errors, "no errors in an ERR file {}:\n{actual}", case.rs.display()); expect_file![case.rast].assert_eq(&actual) @@ -73,6 +78,7 @@ fn parse_err() { #[test] fn parse_inline_err() { for case in TestCase::list("parser/inline/err") { + let _guard = stdx::panic_context::enter(format!("{:?}", case.rs)); let (actual, errors) = parse(TopEntryPoint::SourceFile, &case.text); assert!(errors, "no errors in an ERR file {}:\n{actual}", case.rs.display()); expect_file![case.rast].assert_eq(&actual) diff --git a/crates/parser/src/tests/top_entries.rs b/crates/parser/src/tests/top_entries.rs index eb640dc7fc74..49dd9e293b8f 100644 --- a/crates/parser/src/tests/top_entries.rs +++ b/crates/parser/src/tests/top_entries.rs @@ -65,7 +65,7 @@ fn macro_stmt() { MACRO_STMTS ERROR SHEBANG "#!/usr/bin/rust" - error 0: expected expression + error 0: expected expression, item or let statement "##]], ); check( diff --git a/crates/parser/test_data/parser/err/0009_broken_struct_type_parameter.rast b/crates/parser/test_data/parser/err/0009_broken_struct_type_parameter.rast index a0154321718b..cdc01863ab04 100644 --- a/crates/parser/test_data/parser/err/0009_broken_struct_type_parameter.rast +++ b/crates/parser/test_data/parser/err/0009_broken_struct_type_parameter.rast @@ -44,8 +44,7 @@ SOURCE_FILE IDENT "T" SEMICOLON ";" WHITESPACE "\n" -error 9: expected type parameter -error 11: expected COMMA +error 9: expected generic parameter error 11: expected R_ANGLE error 11: expected `;`, `{`, or `(` error 12: expected an item diff --git a/crates/parser/test_data/parser/err/0013_invalid_type.rast b/crates/parser/test_data/parser/err/0013_invalid_type.rast index eec84a0c67d9..b485c71ab394 100644 --- a/crates/parser/test_data/parser/err/0013_invalid_type.rast +++ b/crates/parser/test_data/parser/err/0013_invalid_type.rast @@ -43,17 +43,14 @@ SOURCE_FILE IDENT "Box" GENERIC_ARG_LIST L_ANGLE "<" - TYPE_ARG - ERROR - AT "@" - WHITESPACE " " - TUPLE_FIELD - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Any" - ERROR + ERROR + AT "@" + WHITESPACE " " + MACRO_CALL + PATH + PATH_SEGMENT + NAME_REF + IDENT "Any" ERROR R_ANGLE ">" ERROR @@ -69,17 +66,14 @@ SOURCE_FILE ERROR SEMICOLON ";" WHITESPACE "\n\n" -error 67: expected type -error 68: expected COMMA -error 68: expected R_ANGLE -error 68: expected COMMA -error 68: expected R_ANGLE -error 68: expected COMMA -error 68: expected R_ANGLE -error 68: expected COMMA -error 72: expected COMMA -error 72: expected a type -error 72: expected R_PAREN +error 67: expected R_ANGLE +error 67: expected R_ANGLE +error 67: expected R_ANGLE +error 67: expected R_PAREN +error 67: expected SEMICOLON +error 67: expected an item +error 72: expected BANG +error 72: expected `{`, `[`, `(` error 72: expected SEMICOLON error 72: expected an item error 73: expected an item diff --git a/crates/parser/test_data/parser/err/0022_bad_exprs.rast b/crates/parser/test_data/parser/err/0022_bad_exprs.rast index 900394bd9601..d97fc6c72091 100644 --- a/crates/parser/test_data/parser/err/0022_bad_exprs.rast +++ b/crates/parser/test_data/parser/err/0022_bad_exprs.rast @@ -145,27 +145,29 @@ SOURCE_FILE error 16: expected expression error 17: expected R_BRACK error 17: expected SEMICOLON -error 17: expected expression +error 17: expected expression, item or let statement error 25: expected a name error 26: expected `;`, `{`, or `(` error 30: expected pattern error 31: expected SEMICOLON error 53: expected expression +error 54: expected R_PAREN error 54: expected SEMICOLON -error 54: expected expression +error 54: expected expression, item or let statement error 60: expected type error 60: expected `{` -error 60: expected expression +error 60: expected expression, item or let statement error 65: expected pattern error 65: expected SEMICOLON -error 65: expected expression +error 65: expected expression, item or let statement error 92: expected expression +error 93: expected R_PAREN error 93: expected SEMICOLON -error 93: expected expression -error 95: expected expression -error 96: expected expression +error 93: expected expression, item or let statement +error 95: expected expression, item or let statement +error 96: expected expression, item or let statement error 103: expected a name error 104: expected `{` error 108: expected pattern error 108: expected SEMICOLON -error 108: expected expression +error 108: expected expression, item or let statement diff --git a/crates/parser/test_data/parser/err/0024_many_type_parens.rast b/crates/parser/test_data/parser/err/0024_many_type_parens.rast index d374f86610b2..f0dbc9b1027f 100644 --- a/crates/parser/test_data/parser/err/0024_many_type_parens.rast +++ b/crates/parser/test_data/parser/err/0024_many_type_parens.rast @@ -168,44 +168,44 @@ SOURCE_FILE L_PAREN "(" ERROR QUESTION "?" - EXPR_STMT - PATH_EXPR - PATH - PATH_SEGMENT - NAME_REF - IDENT "Sized" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Sized" ERROR R_PAREN ")" WHITESPACE " " ERROR PLUS "+" WHITESPACE " " - TUPLE_EXPR - L_PAREN "(" - CLOSURE_EXPR - FOR_KW "for" - GENERIC_PARAM_LIST - L_ANGLE "<" - LIFETIME_PARAM - LIFETIME - LIFETIME_IDENT "'a" - R_ANGLE ">" - WHITESPACE " " + EXPR_STMT BIN_EXPR BIN_EXPR - BIN_EXPR + TUPLE_EXPR + L_PAREN "(" + CLOSURE_EXPR + FOR_KW "for" + GENERIC_PARAM_LIST + L_ANGLE "<" + LIFETIME_PARAM + LIFETIME + LIFETIME_IDENT "'a" + R_ANGLE ">" + WHITESPACE " " BIN_EXPR - PATH_EXPR - PATH - PATH_SEGMENT - NAME_REF - IDENT "Trait" - L_ANGLE "<" - ERROR - LIFETIME_IDENT "'a" - R_ANGLE ">" - ERROR - R_PAREN ")" + BIN_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "Trait" + L_ANGLE "<" + ERROR + LIFETIME_IDENT "'a" + R_ANGLE ">" + R_PAREN ")" WHITESPACE " " PLUS "+" WHITESPACE " " @@ -220,108 +220,93 @@ SOURCE_FILE R_ANGLE ">" ERROR SEMICOLON ";" - WHITESPACE "\n " - LET_EXPR - LET_KW "let" - WHITESPACE " " - WILDCARD_PAT - UNDERSCORE "_" - ERROR - COLON ":" + WHITESPACE "\n " + LET_STMT + LET_KW "let" WHITESPACE " " - BIN_EXPR - BIN_EXPR - PATH_EXPR - PATH - PATH_SEGMENT - NAME_REF - IDENT "Box" - L_ANGLE "<" - TUPLE_EXPR - L_PAREN "(" - CLOSURE_EXPR - FOR_KW "for" - GENERIC_PARAM_LIST - L_ANGLE "<" - LIFETIME_PARAM - LIFETIME - LIFETIME_IDENT "'a" - R_ANGLE ">" - WHITESPACE " " - BIN_EXPR - BIN_EXPR - BIN_EXPR - BIN_EXPR - PATH_EXPR - PATH - PATH_SEGMENT - NAME_REF - IDENT "Trait" + WILDCARD_PAT + UNDERSCORE "_" + COLON ":" + WHITESPACE " " + DYN_TRAIT_TYPE + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Box" + GENERIC_ARG_LIST L_ANGLE "<" - ERROR - LIFETIME_IDENT "'a" - R_ANGLE ">" - ERROR - R_PAREN ")" - WHITESPACE " " - PLUS "+" - WHITESPACE " " - PAREN_EXPR - L_PAREN "(" - PATH_EXPR - PATH - PATH_SEGMENT - NAME_REF - IDENT "Copy" - R_PAREN ")" - WHITESPACE " " - PLUS "+" - WHITESPACE " " - PAREN_EXPR - L_PAREN "(" - ERROR - QUESTION "?" - PATH_EXPR + TYPE_ARG + PAREN_TYPE + L_PAREN "(" + FOR_TYPE + FOR_KW "for" + GENERIC_PARAM_LIST + L_ANGLE "<" + LIFETIME_PARAM + LIFETIME + LIFETIME_IDENT "'a" + R_ANGLE ">" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Trait" + GENERIC_ARG_LIST + L_ANGLE "<" + LIFETIME_ARG + LIFETIME + LIFETIME_IDENT "'a" + R_ANGLE ">" + R_PAREN ")" + WHITESPACE " " + PLUS "+" + WHITESPACE " " + TYPE_BOUND + L_PAREN "(" + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Copy" + R_PAREN ")" + WHITESPACE " " + PLUS "+" + WHITESPACE " " + TYPE_BOUND + L_PAREN "(" + QUESTION "?" + PATH_TYPE PATH PATH_SEGMENT NAME_REF IDENT "Sized" R_PAREN ")" - R_ANGLE ">" - ERROR - SEMICOLON ";" + ERROR + R_ANGLE ">" + SEMICOLON ";" WHITESPACE "\n" R_CURLY "}" WHITESPACE "\n" -error 88: expected COMMA error 88: expected R_ANGLE error 121: expected SEMICOLON -error 121: expected expression +error 121: expected expression, item or let statement error 140: expected type error 141: expected R_PAREN error 141: expected COMMA -error 141: expected R_ANGLE -error 141: expected SEMICOLON +error 146: expected R_ANGLE error 146: expected SEMICOLON -error 146: expected expression -error 148: expected expression +error 146: expected expression, item or let statement +error 148: expected expression, item or let statement error 158: expected `|` error 158: expected COMMA error 165: expected expression error 168: expected expression error 179: expected expression -error 180: expected COMMA -error 190: expected EQ -error 190: expected expression -error 191: expected COMMA -error 204: expected `|` -error 204: expected COMMA -error 211: expected expression -error 214: expected expression -error 228: expected expression -error 229: expected R_PAREN -error 229: expected COMMA -error 236: expected expression -error 237: expected COMMA -error 237: expected expression -error 237: expected R_PAREN +error 180: expected SEMICOLON +error 215: expected R_ANGLE +error 235: expected SEMICOLON +error 235: expected expression, item or let statement diff --git a/crates/parser/test_data/parser/err/0025_nope.rast b/crates/parser/test_data/parser/err/0025_nope.rast index 6b49724ec9aa..b6bc0088374f 100644 --- a/crates/parser/test_data/parser/err/0025_nope.rast +++ b/crates/parser/test_data/parser/err/0025_nope.rast @@ -156,8 +156,7 @@ SOURCE_FILE PATH_SEGMENT NAME_REF IDENT "i32" - WHITESPACE " " - ERROR + WHITESPACE " " ERROR L_CURLY "{" R_CURLY "}" @@ -199,10 +198,8 @@ error 95: expected type error 95: expected COMMA error 96: expected field error 98: expected field declaration +error 371: expected R_PAREN error 371: expected COMMA -error 372: expected a type -error 372: expected R_PAREN -error 372: expected COMMA error 372: expected enum variant error 374: expected enum variant error 494: expected pattern diff --git a/crates/parser/test_data/parser/err/0042_weird_blocks.rast b/crates/parser/test_data/parser/err/0042_weird_blocks.rast index 9cea337ce9c4..1cdc6e6e7192 100644 --- a/crates/parser/test_data/parser/err/0042_weird_blocks.rast +++ b/crates/parser/test_data/parser/err/0042_weird_blocks.rast @@ -72,4 +72,4 @@ SOURCE_FILE error 24: expected existential, fn, trait or impl error 41: expected existential, fn, trait or impl error 56: expected a block -error 75: expected a loop +error 75: expected a loop or block diff --git a/crates/parser/test_data/parser/err/0048_double_fish.rast b/crates/parser/test_data/parser/err/0048_double_fish.rast index 3a05bfee1ee9..207a5c24dffd 100644 --- a/crates/parser/test_data/parser/err/0048_double_fish.rast +++ b/crates/parser/test_data/parser/err/0048_double_fish.rast @@ -12,7 +12,7 @@ SOURCE_FILE STMT_LIST L_CURLY "{" WHITESPACE "\n " - EXPR_STMT + BIN_EXPR PATH_EXPR PATH PATH_SEGMENT @@ -41,13 +41,14 @@ SOURCE_FILE COLON2 "::" ERROR L_ANGLE "<" - BIN_EXPR - PATH_EXPR - PATH - PATH_SEGMENT - NAME_REF - IDENT "nope" - SHR ">>" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "nope" + R_ANGLE ">" + R_ANGLE ">" ERROR SEMICOLON ";" WHITESPACE "\n" @@ -114,8 +115,6 @@ SOURCE_FILE WHITESPACE "\n" error 30: expected identifier error 31: expected COMMA -error 31: expected R_ANGLE -error 31: expected SEMICOLON error 37: expected expression error 75: expected identifier error 76: expected SEMICOLON diff --git a/crates/parser/test_data/parser/inline/err/0002_misplaced_label_err.rast b/crates/parser/test_data/parser/inline/err/0002_misplaced_label_err.rast index 56cea4b15674..ea5203fb96e0 100644 --- a/crates/parser/test_data/parser/inline/err/0002_misplaced_label_err.rast +++ b/crates/parser/test_data/parser/inline/err/0002_misplaced_label_err.rast @@ -23,6 +23,6 @@ SOURCE_FILE WHITESPACE "\n" R_CURLY "}" WHITESPACE "\n" -error 22: expected a loop +error 22: expected a loop or block error 27: expected type error 27: expected `{` diff --git a/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rast b/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rast new file mode 100644 index 000000000000..5d0fe859c296 --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rast @@ -0,0 +1,77 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "main" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE "\n " + EXPR_STMT + CALL_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "foo" + ARG_LIST + L_PAREN "(" + PATH_EXPR + PATH + PATH + PATH_SEGMENT + NAME_REF + IDENT "bar" + COLON2 "::" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n " + EXPR_STMT + CALL_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "foo" + ARG_LIST + L_PAREN "(" + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "bar" + ERROR + COLON ":" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n " + EXPR_STMT + CALL_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "foo" + ARG_LIST + L_PAREN "(" + BIN_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "bar" + PLUS "+" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + R_CURLY "}" + WHITESPACE "\n" +error 25: expected identifier +error 39: expected COMMA +error 39: expected expression +error 55: expected expression diff --git a/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rs b/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rs new file mode 100644 index 000000000000..0e7ac9cc3075 --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rs @@ -0,0 +1,5 @@ +fn main() { + foo(bar::); + foo(bar:); + foo(bar+); +} diff --git a/crates/parser/test_data/parser/inline/err/0015_missing_fn_param_type.rast b/crates/parser/test_data/parser/inline/err/0015_missing_fn_param_type.rast index e72df374d1bc..ea50ad35d74d 100644 --- a/crates/parser/test_data/parser/inline/err/0015_missing_fn_param_type.rast +++ b/crates/parser/test_data/parser/inline/err/0015_missing_fn_param_type.rast @@ -49,5 +49,5 @@ SOURCE_FILE R_CURLY "}" WHITESPACE "\n" error 6: missing type for function parameter -error 6: expected COMMA +error 6: expected `,` error 16: missing type for function parameter diff --git a/crates/proc-macro-api/src/version.rs b/crates/proc-macro-api/src/version.rs index 40125c2a512a..cf637ec359a2 100644 --- a/crates/proc-macro-api/src/version.rs +++ b/crates/proc-macro-api/src/version.rs @@ -120,17 +120,20 @@ pub fn read_version(dylib_path: &AbsPath) -> io::Result { let version = u32::from_be_bytes([dot_rustc[4], dot_rustc[5], dot_rustc[6], dot_rustc[7]]); // Last supported version is: // https://github.com/rust-lang/rust/commit/0696e79f2740ad89309269b460579e548a5cd632 - match version { - 5 | 6 => {} + let snappy_portion = match version { + 5 | 6 => &dot_rustc[8..], + 7 => { + let len_bytes = &dot_rustc[8..12]; + let data_len = u32::from_be_bytes(len_bytes.try_into().unwrap()) as usize; + &dot_rustc[12..data_len + 12] + } _ => { return Err(io::Error::new( io::ErrorKind::InvalidData, format!("unsupported metadata version {version}"), )); } - } - - let snappy_portion = &dot_rustc[8..]; + }; let mut snappy_decoder = SnapDecoder::new(snappy_portion); diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index e3aa880d0058..f0f1900c78c5 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml @@ -24,7 +24,7 @@ crossbeam-channel = "0.5.5" dissimilar = "1.0.4" itertools = "0.10.5" scip = "0.1.1" -lsp-types = { version = "=0.93.2", features = ["proposed"] } +lsp-types = { version = "=0.94", features = ["proposed"] } parking_lot = "0.12.1" xflags = "0.3.0" oorandom = "11.1.3" diff --git a/crates/rust-analyzer/default_12483297303756020505_0.profraw b/crates/rust-analyzer/default_12483297303756020505_0.profraw new file mode 100644 index 0000000000000000000000000000000000000000..e49d7c144922d09aeca6dc5f31c8791b6c0939e2 GIT binary patch literal 25152 zcmZoHO3N=Q$obE~00xW-V2Xj^29$Lx1j`p&PLxfUcfp@DJrDvq^lxGNJgjDP2Gz|2wbWC5E4QUk&) zAcBE`VN#pe;zBn4XKZgnZms6#US6BR z)X3If%Qt%gO#KU}`USqI=EKxaZ2$NA?}PbK-_MJSnAt33y8%_-;D@T759;4lZB^_N zx2}5p|NsB}WiDSXnERp9a0(Vb&jj6m9F~z>15^J2YCc>clmSy89VU}x7vY%g$_2>NK?k?NpOR>|Iy;@Kiw6JD{nDh0h1K z>Sd>=38YPr3tolOF& zz5s{%MAv@-X$LEIuy*)*dH>R8`|uKy9v0wGUvB*Q-(P11W0?9AQ1t>K*xjESp=SSJ z+T`sp_b-5|FTkN*cWtQg`w8*x(gGaH`#tkmE1>Epps8nr#=RFrsHIMy!d1oPS0MvYka8&hNQ1kgeIWt`B?OJ!_y8U`k{w;v2zYu|{ zo)xM-;NgPRQ>OZ>uDF!$+4kFwVuAmVRbH)hFOk?-x9C%du^O*BbpIZn>=X;VFQshdKjJ!OGjN?~`~psPwLbxhDdu zJ|G^ud+ONvZbqvFfWq@i=bx#322k}4Q1v_Zih=6^kbglqH}1mR-;Z}TST(GhCC44e z%mGyocLaCb0H}bS8!^6`9P5lX|dqC!cupR&6l4y?)A~5$WpsBwARS$E& z`K#+^lFBCRl@Rz?S^Z)M0|%OVkefjmmfx9dTKCV(U6==RzXlHVAh&?@t&B9T)7=uS zwfqf!z@Z-G9*}xYy_3I{#Z+5i=D&CbiC>T$GKQHyk*W5?6{AI6=^4{`q<;Nj zJpfgIAr&<}z|7Y*PQR`eEOs4c{tBpihcs06F!O)bx%zsz-z$WoMY2UG7sKt1cR{Vy)_uW^E@SAeSjfWtki+RAK6DR=c@>VG`JpFS5!D=k`>-1--$ z{sIB@8&|S#JnvYe2g{EuaHxmn$GDAo1Y(7_t85=x304qOn`4?tB$LAk+wz5n9hpFEIb-zIos`)VW zr4#u-8=JGwfw^Y^R6W#ta0(V**I$H~O*=Nd2-cpdfT}-$WN=1>MNt65&;Vsy~23{nxEO zC*R%ZGCkwPCUEhw&R~a8-BT0PmLFS=ed(j1BZH8e~M*Kk?nA=Up&q;WKk_{0DBfFP@^SAw1dp z097B*jA}kC{F~J8$lev)a-zRyUeb)37OzAWb2 z09F5?4b^;hsLmDhXBo~kco6P7t^M^Z$=OVB|8}6NhmM;vBp+@1V)y&&&YVfF6hEyN z2QahG1J5ZiGkom(sNcwE(MRgCX zJz4jxOzNOW{%5Wot0pkL{L6O%svd4IlmYYayot+ST3)qY01M9pF!d8r&4-2m?bs8F zb;jqCVCi!PRJ{QX_06lzrvI5@qyST2097A=L;bv+M}Nt)PyPf`?*UccfJ6N%pKEfz zo}AzP#mZI|6rKuD^&8OC!@~1iW?Y=e$vxiB^=>`c{&EQ?2UI;YSl|>ae7?%`Kf7Uc z?6F@jU+0dOOE`brg@iv`A(R1AZ#nPk{nrofr^$Rc&Smpr2J;K3dWET|;mHHlH}$=u zS4P~@d9d)8fV$_wbX4`Q@PGfM|J);{W%FU`KS0fg8V9Ff>f6i8)@ZVZE8h!nP_$dc z$#ep$K41oR^E-+g6{b9_t!P`b;^u70*-Q(d>I=}+!~DytpLlmw^x@gpZdz^iS5`*;kiS>u_a5U)lGa>dSGW8 zZvs@k!yHugF!KYlFC{NN{bRoAy{)%F<8=X0^$l}T)x*-mt1S^WIdYZ@Vd>2Rs$O9p zs(M&>K5*E&OkBe!8y5ZoQ1t;g)L%MtE3mstAReav#T`icnSe|EhRWf=@>KYMalQlV$Jmxs@eUF-!=^%v%&`WMz;X5}*b z&)j|Q^NJ0E#q&RXVx0h0U$6*Oy%sd(T{`)ybk7xE7S7hq3(|LLaBYBwPs0jS^|1P) zc%p>X%vW!1p42z`ui@rkIRI6^U?r-0*m(Gs=%1bsge&-;)HkjN)h|1s>JO|!RS!!K zr&rE-X&UR^yW9LvC8&M70;;}YHFovuPPr{Tu%c)=tp73ts@`D@s(M)Zi8#)EG;31} zGfX`!J#WCFzP-hH6|eb$9S=h8%m9tAW)eZZA5MSRAPD33T6UI`3|@7Q+Up`U7a{Ve$Lk|LOvUPfL_`T32455dD{V1yuco z4XEycsn17C_Z6*odkg=Kl4HYxanK-2fkNtbnS&fJ6P8{=2g~COp^z z3(o+k`VTnN2X;E7d}Xb=R#&N-4r*^`K-CLuLUj);eYP@11l;1e`?9y@lRC)15>WLF zn^D!n{42Wklm8nbr`DC*js7J3-pMThRUfbwRXr^IEYLsvxjV$w9;W`oZAkg!unoKV zJ&W5epUL@_22+0ls$O9`cJ=ZM2VEr}Y}JCP-vCuFumih#@$U6}lMjmCg{hwaRnM>! zyZRr+S{?cat%PCo#RX9HAJEjp!qeoKwak|fEPAm1Q36!`gDdFSzF{}2dRY45Sjx-sQ)%Pj%qOnp)h~81C_vRii$6F8E8l*cJt^x|xjPz`ehhG^ zhnYXYb^5WZKf{<{>NyCgmyDhEub;>J1}yzQxCMz{xI>@}nEBH_=}s;aTBZ(Dzk`7K zEu}o>@vEdWVCp9jQ2$YVdbsOiQ%0Ej3uh9qO6}C)V_7Vp&pi=Q_{9H zDJ}c<@7hf(QHj;F`4w=ehsCeS`?l2mw_2y1KC@64RNitx)h{@RJ$@sk&7LwQZApZs zhX*(Dhi9bRqiyF`J@^Atzk`5!{<1$SPL)|Xf86_Sf$b_zrU?YpUwP_s)J|gA33zxC zP=9snJ_&o~NA9rn<3K?D2lFZ2(NeEwz|>0+P`^6S|M@2tQ@DFRfKncw`H}mJOcGn% zUoC-|e}aJex?S@&Kg%oD`8xg9i?p2@TnlihhxJbZ)1m#r>%ghZ#afMes5n) zy)ZxZW;)D04N&#aYyziX`9=88q2^ORgb%^OKLV=W;W&2l^-6wfTmF%N_g@U4>fr`M z88Gu-s-&OPpAsttGoOQi`sABaT5}6t{D!H2a2hP{Rk7AHSu#A2!?Tz5{yDV?X@hzRzv|RSykDI0ZBR(t^_e)1UAu)^L4gV}ALU zPXea?466CC@;QCZ(+#{@6MYKSvov0|(Pz?ts{e2nRXs1%royzJ_NOjw^nj(G1gQE6 zmr&Ki++W)sllDrK14)v)Gb5|}qeJlVLUjk6|2A8p$KVftDl`EP~~Q@c!9?$2kdfU1WEH=GiK zvRdw0+^jr!^xcdotNII5ue1EXpK<5py-iM* zn4LJo+3*QN8mRqt0~-Dp(A2~H`_1vfg3r&JEMV#nK-C*OK{X$yUOazC%B^oldSL2T zK-Eupie0@(E3?RhQ}P@z^|1W-;Td-IlKDpJ@nLr@r-{r_IJmQow*qQ@zzbCMu<{e; zZ)k8~Q0Vf6_`y&U(A7bO;Zy*eg`ELQA21rbD%y#{;tOU2Iv*wuqtX2fD_>yauCV?z zOdX8Ir5`2^qhab{=EB4&rD5RI%*7=RlSik~^`pzf_%L-a+72oKqha9L=AcV$~CJ7rHz;AKe~w zd0c#0zJ|pUj7B#Poe$Fo6NmB9;}>QxOdQ5Xr(x#6#9``TG`c)Y9U*a;e{iXX=|`tw z>S6wZ)f+HAjE3oh@zM3c_%L-a8dko-<{x0<^rB(zLZ^wfA6*}e57Up%hl#^zLh>+m zglJf}qth^bu=E3yhl%5=$6)S)(biB6==x#uP-!>?QwP%rlYoiCXqY&RhPe~QN2hV= zLsy5+hv|dS=<+Z=OddwV#0k-`bPUr63m2F;F8wg|xa48xz~s@*L+7LCb96q;KJVCrq4 zGB6swe1e$|Rfa*q%)yYLlK^`8NXWe~c^D0I=0N)c=8jQ%c!U5fJ;Bl$Z2u$7cd-5z zjE0HB)Wc|8;;?!QW)C46rXEJa%tfbR_L54Yn}g0L);yR#V%5`29n4+yrg8ZX<_`4o z55|Y-gQ*gQkJC%TZgGqc)o@?ADv8AHQI8_{y0}7H2P9x?^%t}PTSRbrITYe6gg|~+*-0$^x;3D{spVQtLy)~^VaF%Ov&$)zD9LtcV9Yq z^U$7m+##wAec>6`x9iT)@z@hS|8Llg#gC_7;pg(;+B+@L;hA19`*-d&&$r)xwaH{- zrqaqzHY1}%^S}B*9;cRQ{Bn`0Zhp+3ciXS`9Mb{Did$3rY_ks=2(+E7zgY7k<)r+~ z^(0U47VTsVgG9&t;5XozaZ8UwiW6 zROY0FuEw{2d(JLZOjlu#?QapizR&)_`S0;@agwTf=gC#jP{?PEXtU z#_^4Y(_4@0vh5cqe{6eNoEV&Su300hL6hlCQiHqUf$)<_!9hDxHg!>_g}3Fc1w1B;U41m?f?0Cxr)=7w=h=R+B@MaSF?dY%gfzQWHx#J*}W}8 z|ETlFC#pvz0ynrNIemO9eOc}olWws@+KtA3(OljZ?vm^YMq$Z0*Lagt#nR8}wB58Z z+_xuK^iW~B+w=w5%@dZhoYcK_{l?8$3vKoG!ql^m*KJ?Z0Z+zzEgOn*w>UipI^6>H0VYqZOEpw)&!>tRKczss?XjxIQ%3?LkiXH7M zB%UvwCOs>CiIm#1rAt(oEt61DjZ{&O+QGzd+(qKPg#)r_C(dXa8tI(X_VV>P=izh8 zvseGD_c;&WbAG;tCg(Spn0OiadKsA*`1u$cnQbg!Ww5&?^eVs~VuEQ$_(#q_&Ol95 zqX17Gw&0ULT~}6Bo%~suy3$p(s%l=P>Q4pJg<9_vwG?tDaf%q4ocHiC^W5s=Wo&BX zX<}$(?qAHxaNwBuhIXczj1{-$_Sy0`8Hl((u7CN*>7d<<7Y;EzYNw7|`V#CpW!An| z@5K{~3JZ3fJZq~S&Kv7`GgPrh!ZXmXH{i{M%^&~m*q9z^AU&CVLt)j=DU4+$pB@^| z&}@)oj<9S{o?D#sd!BAgN2X_#^_3-)`1kBvRB~G?`(Z%VmSbC`O-nWf+An!r5*ah= z-@Y%Uj#3Z38(FUh739tO#(UvfaA;U0)VG}=-v)l<3<%_8dK9G0JoQMDL1@gRMa#nj zrp%uc6|-i!_@t;Qvu93^m^5?#@<(!ulES7$O^6Rk%9uAXZNj7}lTs!m&6$*yFl}1K zLoS9yOKH|=j17zxw^Yxb<~n2`(3)5?J(553(#h2PLgl+RLd_MAb#h(xxU_HotaWx4 zABz9j1Vv1fDUCh-x%T?>HgDDaf3~UJp2>dmK-=%tQ>@&SOlAJqWPK`_w_s~WbTzQUFe62!aoBnot{Riz#lNc**^`7?y zxuGp_k2l|iljlt$y$hA~mS~%+8qW*}xGfa@<%if_hp_vvem;=R6F*%nsPpNToOa9W z{Hr^|_gby;n_I=Y!}q<=^u*Z}BKy3X%5r3X9$S=c(7>s5H*v)dqw_o7NJx|vJgE`O zJy;~`_EYE3>vysl8zbE(>n`)ycj>=zng6j&C9{^cnsY0EGwqrlr2U+whpFP$(uvX8 zhZQ8+Ue^Cy=epP|ZM(8=v)+^-i7#pr{Jsk8;=I;>ejOEYOMIR1<&m>M?fmJ_wf$Lx z4`;04Pj>RUA-nKp40mu-;=ZQ0f+ue6-p2Vf`Yy}kxgl*!w))O|;8WDM@}lU|pIR@k zzTPc;UM-OMpG|xFr;wBi&mM-BXBI8<{+up9!!)tZQqQ05x!|VY^-jiLIC6jekS?nZ z*edf-@WF=m2u;4=O}Wq2R)*fHc#)bH+9p2k=VNrfg`dO*n#R6kz5tgWGW>GJ8*=ey5qpFMf;^5@g% zKQkLSd{P8uIdDDMc{G7*k)oyjdY!E+T>Q_k)vy;oJLl4#Glzl{)J1%hc^Z=@n=BA> z{@kO&&CKlld5K7Kig$7FXA|#m2DZz31#_Jr@K)Tq8|6LumYcx3KjPzY-r5n)%?mtOyKEAh7d z@7qtZKFzb+?-O_7!PbPy8*a^6koodwnrCwG?F%cOm%Nd;+EcfYFSqKB$*TQNz9m1K zWH?#0ZAs;(g1^F%i+?3fa@)CQow?eK*}p=0S4d1Kz1+6X@0yvPS-XF1NAa34`E>4G zev{8OE-O36*&SoKMbUG){JdN5)0ZubT9MKkbZ9|=79<%Usq*|NgvO%Uow+%;i6OXwBMOHXk;F{YkT$_mw){0<{g$-X67A# z`_?t%zuT_=-?Dk1=OxeO3lR;xDKp1=nz68x*2+~!C#-6g{Q5?=wq9~t^^c-?fmhZq zTiy4ia%(wWluUEhQN<@AAwY!t*Ub*{SGxFto zmQlEMUU}%(v|>A{+ETmO-!}Jkox4}JzPEEi!=yW#1H6=0DfN3U`Y)^zop3?w*`k8T zhH$4BLI$7g3?^^wvzM{To?9lMDLgfpfloj}coECQwC{?)@^>8izCttH+Iib@@y}{) zKOJ3C^K6$_2%SuusUZ6Bk@L!LoG(AfJ!C(-Pd846>GR2AZHZ4ODg@UbymY1Q{AJ#> z{6o*><+e?U`Ql==vVc>2tHpz=mPucpzm&W-Ut@{W?&}NF7tC5Q^`xhPXz-<1n`Ta1 z_&XwW{mUOYbIaHD@U%WvVD9d;*4PomTW})vHp99T+R;b0*Bp`Fz_Vi42_D6x$);Bo zen;dMO*gpnydeH^(wRoX_XanoY}U@y(o5cLEfeF<5;|c;DBH2_RdTayuj+^;?k{Pc zd}blng3?n;(`M|P}T&((0DP3zL*-}US~?O^i2*5mDg;CM~Hi657$ z?97~fbiVA%M&3UG-hy?wMylWWqOyCK*e;cBoW0PG2Nq8r2uU z(s$@`giEH0`$N_AT}N8B)=qdoxBDXVt0|E}mwqIad{8Phi@TBeZrhHW<%QRDng85v zzahQrUgR<%(aj#bdzP5l-E!wrw)bt+3fM0(Z`zXAzb=PG1k9^G=MZS@*chnP`s+|# zQnT*Q?DmIYQptUdPa?J16_*9=yjIs^qUm>3@|aF52aC9nzpw7e%Kj7BurL(t9)S+orF!H=T1IxK2x(y7;2qh6I_=`vplapI$m8 z9a$?n<<8=heyr-M6W>hQSXQ(6%Z8ozB*cv`XstCC{Xg07;LFboZwDot@PB2WWxC~i z)aq5cZpXD(ZjRbsv3={l)%_=r2~7Oa&Mp?3Ir-SljdA-DB5z!LdSd3%ys5tgv|Xnh z7pO2(_*vIn@w$-f%k&$qSKd#uzoMCUSl893lVjV*1<4kP<#Bz&b+^){J{3;xIwjD( zRjyU%Tlu~%oxgHtzng8=7bRvDF>&X&$Y~-{Gbb#*HY3*6s4+=&$r(n~WsOhI?#NVp zeQeK7A(4+WBjm#mWlm=gD|)NXXoNq-n8rQ9q@(5(EvVtR$q(j{}B z+cut;xqR6p^h6KC`kmYFRj;1!rlnDGC;j>4FEL%}PBS9(zpDl0Zsv&OE6B}W&abc0 zFRXWAarwE5#0o#Qc_BNPa+i2I2CwIiS*d)~!=^677$t?r#olJgHnO9;IpZ_g+!+q)a|8NHb6TUT=qz zqk>octKGPn@BO_`Pe1+n%2KKLnd5ZAxu*wC;Z^`8!kA7Bqs<(a4 zz7I-#uG4Q!_~M?weZaq zlHyY=5%t}&QC3w+Kga#y=Z(UOva(nE%F@%*rn~Mczn>KTR`2%*OItJf^{zJ;OqX7D z@fb%_fym~H_QDRi$D&Ckb;o|ZWqI+lO+)_K4wHk8=6gSfd_7WdE$Xl+<7)2XtJ>Gh zN&T^j?QZVo&cjCCiBZQF_#N+ge@BS_alF#8uQhj*mf5BrZ@#YkZ^etfZoyJ%G0#_; z8T9#PEmgTTrA63LLUGv@*`3~#-X(rnUz(N5D0r>##O+H(s~S8~JFf-JDd+gNpFeiH zA>VYlny*t`ud*2KRac2rOjXP{8Ff}8qTv<0%*?8w^aMzo$Gi*N)-0bsDQe2BWy_y4Epj;JbC9X+nWUQM zG_jSLX>HR^vQ2AC>zuZZK`m0wv6u5JQ^l>LA-?{13OyVo-NN@P~^7FVdH&vJrOgGunEoQ za?VYecgDH4X4*^Eeb=YeM{ec6n)C3h$@wB{{jzQEZ1|n)ZqDudlIA^8yJG&Yjl9l& z$uIl)Ty>>4Fr=)F+x_qA+gDRA%NWO=mSU~at9X0hMgO_XukyY25)+Sh3-Wz=)+)y2 zp=rX#Y_&A3>SMj?%=?#IIJ*r#vFw@H5w)P~>~fB3^JlJ$FHQ1Ze41ZLmi1^thXGE<>-q1HQxs}4ESB4pyH%u36cmzb26mXMH^H8CeIVP;NR z#)DH5{-VXcx_R?m5=p#A#wRIAO? zjHfr~u8#BhmU<$}cD_1u<91174bzb?Pfd3H{A5F~^_eIZknI5N>Vx5lg2 z|IA6B^TsCU4Lr7PG4L`q^D{BrY~o?;UzpmX?lgVn#)W~CGP@Rbdd}+f%!*p1&9Lu` z@=ilbBs2X^>uxO3@zOu*dsbW1?6kLTzrHajg_>^kFf{Zq+wNg(X5edPQq0P*HlP1Z z8%qPI%R0e2`-p)+%g+CcqE77IB$wID$*#y!6e_iNda>FuzZA>$@$x5+g~v`c*{!^# z#+b+NMR5H8Gw*+WUF<#I{OxDv@?ai|^+b`@-_aGrnf971b;CKY8c!LtUkl?nP2#95{@_J^8H9(R>QfHck%ffURPrx``pTJNj;o(*e{Y($@TK`NYUI&B3BE= zHS4zDH4$NpnqZ?5xch;VZb9mbRl0SpE}48TnTY{a)n_0OHqXWEpw36zUG)il|}Jl)dNo_U^hTb8ztVIjYa;d{m|&;Y0}c!cCg{YlFw zfj+k06PvyyIlfTMxunZEWt)ojkDqLhF3z4_X?W&FW=3!7_lZAk6`#)(dOmT(15<}r z+byfkyjjZ9?6oyqdx}EPI=&2j&;Oxs%r>PRHun^ESR5f%a*JCr*0@~Vnpgbi@2`bx zCeB+pqo(1w{rss1jTX%{UNtrSZY=}CKhPdP}t>#ZsELE$V;2tLo#VC++Y5KUy~N(6lFW<{NSxRpQ)z+wSUJt<~$oN>}gM zSg&)!>2y!d4FxGFzdR}Z7h2)04DE~yv>5)0yh`2j>1RZ5Zh)uVv^$gZZ?6wqe``^w zUF!ckzauwa^EuNJb?5`@fp_N1r?PZ2Rov>G;0r1M+K$$ryqUGhMBe=Zn|O=BnEG#;fsxJR|@rzm???+YFCw(q@u{Lm- zc-!r>q2I-?<@tPWk)Jt@+3WR^>^!^grkC*{grz{26USlk*0i-kx4&9@{qfn0oja8u^v6 zGMry4Chp=@af|ifMcxJl9@Ya>9lCrTtY`jlSwVNBB1h$>`aO?d^W2|w)-5FAfN)7u zezfuQWsg?g)Q&N<@{jobvU5wiV7SS|)sC+|GRJ63NWWuR0cyORv*v3y5O57l)z>+Y zW|O>`VTziU)GCu{^~wP)y$Tw)`CVQvth!pc>sw{`!ZV$xzQ}n^`|%^U^!2KByUM06 zF|AKCziHZT*Bagx^j-1F5=*IOFO!^_>W%E?2D>twJa^hat99AP`HBq2*V%EV7 z8M(vt-_vFb2n|K@qn?K5CZBWXwe>pLH}&*B4y|ztF)HRR z3i0aY2??3Q6mnovLR?2zbw^iaT47CGreEK}O1D`XUADqLL`VlJpO_0rz<0jZJXYqr(H*kpr`nUSFn zOHm9XOCXQVT38dw6Et}Sib9i9zH5BEdiBqqK66^f(C7o}N2h}_qVol&3V_dfr&>~fvu*|oHBqIR;4X^*d|r-{F}p|RmM4?j;o zQ_~Vw24g>~Rmss1*MV&V%}N+)YkHm0(cH$cQP}%aw~oz1p2=Iz>=C*Ar02?$9Odqc zsX?DrL=7g+nH(PzHH9l>`HBWk=g&N!MU<7jy_-dXCx<)^VQx+dXZZYHQQO7^VmPE) zH}N@l{-md`9ylxeYH51;nu5HCNTD8^4GewF3=I9u{LK6}8f+_JWtcKSB>4ejcEzpQ zQ#*MN8}KkVq$t-tGH$rdGr>(kmsOKTqTuPr;3a*tRVKZ|fLEw<(qxux z`n6Zx)>1fkX>?V^@Th{O?#UxMmRB#CoE2qobC7(ktPKeWP#W+%t8J|3tz%?z-owb( z(8SBj&&%7_(A?X{vy_$L#&+&|QP70A#Y5}t8P86AKfO~r8lCLLJigt*-j61KE-p6q zeq{Xl(x+aXN1IO~j>E+SYjbYpq zB&TuL^gMgTt+x@k`)->Fxc)zSo~^z7+s2oRG=)?gHlA>fXDLeC@p_F;ZffQB+w1jJ z;~y+>P1de{t;Rozr}N7h%fk=nDBc&;yMFrX@pb$JODjwsyS{pm;V=QnSE=Ezvhm*<&#{`khvKMgAE;*Y)S+Wqs-^WCn+hSjFk z!Rz>+T2(!Y?)zE$J^OO;^&?f^PXF{_`eJ=j z!;mYzHG$D3Mb%`P#NPVA2O0~v%@S61=X`RyE2eDD(hVN7Y`lIm9mw4_+3@*{oDYT! z(F(6O&t&qOUCr`c^y#`Y_bw(C-F_Is<&renurXagbJ~{QMsmxSJ)9ZyX#XY~k5{)| zzv_CwYIX$wO^%5g9-?c6_?%`51*gx~S{58`dPC?l_q){(i?53Fw@q5KCGo^P$(Jz! zlczL@`#T;@=l)&y;sC?7$w876mhyafd*NY6`7QIMi{_g|mGwkrS~s#(Zg|Wqz+X0R zL&mRdx1L;BYS_mbk-Sw|B+l1h>Cx#HcOuy)ir>xB7Z&{$@FZVk!P2v*?3d-nURf!! zqG6@YoSKN+0^y;tWqq%9y=QwY2WZ#~rVTPAeqv3y)$_I6)y zvxJh$AqAl)Z_Wr_eVbx@DSY!*@68|EN_R(<$JAZU*~9y*Be`?)rN9-JCN9yuF?Vmj z(XM!btnCUJ5{50uReZ$eo(S?>`jKPavYlW4ZA<)h{g1@<*17fzf>{C|g*_3Mp0Q|` z&7JD2tT(3hRdS3u8k#-yY7Y&=+h+zFO#-*Vc)viZU}8)g5{cehfv-q@$L z@7Ie>VhY~ZQsz&saN9R)lDW12_Sq{nnY32tTwMO<g0-_5SryrZKwGcLvS$JU6r>zlWV_}@Oz<9H+FQsMtwTdz-$nIv$?!&&J`@6b6fyTRKh^Q!ItpAycW zllGhnm=YMQXe76@Wl3oNq1%twY+HHu@Ur>0<}VfJU)B4s$nR9j9IZ#*0etr(ZD)pl z=UjbwlPq7Zg%Zm|;guc7E|f?4FA4oO`C8W92da-nFW;EnB{9kK@J0@ah3EdUowL$t zSqg5-`gVgG)~@ww*A^Pyddc6x@yKa+k+73o?gn2kIfc5tGuOrNe2;r3^K+McsZZFG zNxvpd(Vtn~+;W?v?ylI1cD@(qBjWbtJ#5}6e{9~CbyqlZjbhwxWQt#&FS`9qL{MVZ zUq{D^v;VTo7s`rH*4@Nr8~t}%+v77AzdU;Kqk-$9h0TA4ZL3WE_A}O1+?sm!AZT8m zCE%BSHJ@jXww$-1{-cwVr*H5y`y#Mw!Q210BkC6{XE!!@e0}FzTOaNKO`)l~B)VC& zK5UfAuerRqFYuc0KX>DgVeQrLTzgYje|A$} z79ZeWQZ`4>G%7DA$y}!UnGJ7zAflAj*Er?xr>kdHe(wBP-!tBZemY*}Cg%;dn|K)d zn)&;gnEHDA_!<|b_8ebmr@vKZh26yi1}87v(K)H3d-cqjD@V?VGRWVO&%MD|UvaDV zoG)Lqf`G%r3T3wX7dy&tGnY80HFT;h&`rAZIJ}B!dj999OV@j~1lXGH+I5S{%4&OS+uf{*sq+4el$pwG^4T&M@c@_ZQ}L-m&(;4T0~E9ew>$l`9{?y&y-wVb-8 zCzma)oj6}*=Z$$=X2^Qfn7_HSU$orO)rh;eOs&p&Y1<@Y<8M=|4{unwDr8MifV<1Q zGrabCVF_Js9*~d)w-WTvp7uHC?`dk%Wb`Ol$Xke)_q4H*chKW*ZlTG+XNx|4-t5iI zbJmz=@?{-g-e94#!I#eT1oQO7GEU-Gbmz66>&kQxxC GmN)={4^z$n literal 0 HcmV?d00001 diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs index 841861635c6e..3628670ac98b 100644 --- a/crates/rust-analyzer/src/caps.rs +++ b/crates/rust-analyzer/src/caps.rs @@ -1,4 +1,5 @@ //! Advertises the capabilities of the LSP Server. +use ide_db::line_index::WideEncoding; use lsp_types::{ CallHierarchyServerCapability, ClientCapabilities, CodeActionKind, CodeActionOptions, CodeActionProviderCapability, CodeLensOptions, CompletionOptions, @@ -16,16 +17,19 @@ use lsp_types::{ use serde_json::json; use crate::config::{Config, RustfmtConfig}; -use crate::lsp_ext::supports_utf8; +use crate::line_index::PositionEncoding; +use crate::lsp_ext::negotiated_encoding; use crate::semantic_tokens; pub fn server_capabilities(config: &Config) -> ServerCapabilities { ServerCapabilities { - position_encoding: if supports_utf8(config.caps()) { - Some(PositionEncodingKind::UTF8) - } else { - None - }, + position_encoding: Some(match negotiated_encoding(config.caps()) { + PositionEncoding::Utf8 => PositionEncodingKind::UTF8, + PositionEncoding::Wide(wide) => match wide { + WideEncoding::Utf16 => PositionEncodingKind::UTF16, + WideEncoding::Utf32 => PositionEncodingKind::UTF32, + }, + }), text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions { open_close: Some(true), change: Some(TextDocumentSyncKind::INCREMENTAL), @@ -134,6 +138,7 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities { resolve_provider: Some(true), }, ))), + inline_value_provider: None, experimental: Some(json!({ "externalDocs": true, "hoverRange": true, diff --git a/crates/rust-analyzer/src/cli/lsif.rs b/crates/rust-analyzer/src/cli/lsif.rs index 60a7f99ccdb8..3fc1aa4eaeb4 100644 --- a/crates/rust-analyzer/src/cli/lsif.rs +++ b/crates/rust-analyzer/src/cli/lsif.rs @@ -11,6 +11,7 @@ use ide::{ use ide_db::LineIndexDatabase; use ide_db::base_db::salsa::{self, ParallelDatabase}; +use ide_db::line_index::WideEncoding; use lsp_types::{self, lsif}; use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace}; use vfs::{AbsPathBuf, Vfs}; @@ -127,7 +128,7 @@ impl LsifManager<'_> { let line_index = self.db.line_index(file_id); let line_index = LineIndex { index: line_index, - encoding: PositionEncoding::Utf16, + encoding: PositionEncoding::Wide(WideEncoding::Utf16), endings: LineEndings::Unix, }; let range_id = self.add_vertex(lsif::Vertex::Range { @@ -249,7 +250,7 @@ impl LsifManager<'_> { let line_index = self.db.line_index(file_id); let line_index = LineIndex { index: line_index, - encoding: PositionEncoding::Utf16, + encoding: PositionEncoding::Wide(WideEncoding::Utf16), endings: LineEndings::Unix, }; let result = folds diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index be09938c2c4a..f609a50a05fa 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -33,7 +33,7 @@ use crate::{ caps::completion_item_edit_resolve, diagnostics::DiagnosticsMapConfig, line_index::PositionEncoding, - lsp_ext::{self, supports_utf8, WorkspaceSymbolSearchKind, WorkspaceSymbolSearchScope}, + lsp_ext::{self, negotiated_encoding, WorkspaceSymbolSearchKind, WorkspaceSymbolSearchScope}, }; mod patch_old_style; @@ -999,11 +999,7 @@ impl Config { } pub fn position_encoding(&self) -> PositionEncoding { - if supports_utf8(&self.caps) { - PositionEncoding::Utf8 - } else { - PositionEncoding::Utf16 - } + negotiated_encoding(&self.caps) } fn experimental(&self, index: &'static str) -> bool { diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs index 55b89019b47a..415fa4e02f20 100644 --- a/crates/rust-analyzer/src/diagnostics/to_proto.rs +++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use flycheck::{Applicability, DiagnosticLevel, DiagnosticSpan}; +use ide_db::line_index::WideEncoding; use itertools::Itertools; use stdx::format_to; use vfs::{AbsPath, AbsPathBuf}; @@ -95,7 +96,8 @@ fn position( let mut char_offset = 0; let len_func = match position_encoding { PositionEncoding::Utf8 => char::len_utf8, - PositionEncoding::Utf16 => char::len_utf16, + PositionEncoding::Wide(WideEncoding::Utf16) => char::len_utf16, + PositionEncoding::Wide(WideEncoding::Utf32) => |_| 1, }; for c in line.text.chars() { char_offset += 1; diff --git a/crates/rust-analyzer/src/from_proto.rs b/crates/rust-analyzer/src/from_proto.rs index 2dbb14fcd9a6..50af38cd6fe3 100644 --- a/crates/rust-analyzer/src/from_proto.rs +++ b/crates/rust-analyzer/src/from_proto.rs @@ -1,7 +1,10 @@ //! Conversion lsp_types types to rust-analyzer specific ones. use anyhow::format_err; -use ide::{Annotation, AnnotationKind, AssistKind, LineCol, LineColUtf16}; -use ide_db::base_db::{FileId, FilePosition, FileRange}; +use ide::{Annotation, AnnotationKind, AssistKind, LineCol}; +use ide_db::{ + base_db::{FileId, FilePosition, FileRange}, + line_index::WideLineCol, +}; use syntax::{TextRange, TextSize}; use vfs::AbsPathBuf; @@ -26,9 +29,9 @@ pub(crate) fn vfs_path(url: &lsp_types::Url) -> Result { pub(crate) fn offset(line_index: &LineIndex, position: lsp_types::Position) -> Result { let line_col = match line_index.encoding { PositionEncoding::Utf8 => LineCol { line: position.line, col: position.character }, - PositionEncoding::Utf16 => { - let line_col = LineColUtf16 { line: position.line, col: position.character }; - line_index.index.to_utf8(line_col) + PositionEncoding::Wide(enc) => { + let line_col = WideLineCol { line: position.line, col: position.character }; + line_index.index.to_utf8(enc, line_col) } }; let text_size = diff --git a/crates/rust-analyzer/src/line_index.rs b/crates/rust-analyzer/src/line_index.rs index 2945dba12f25..791cd931d42a 100644 --- a/crates/rust-analyzer/src/line_index.rs +++ b/crates/rust-analyzer/src/line_index.rs @@ -7,9 +7,12 @@ use std::sync::Arc; +use ide_db::line_index::WideEncoding; + +#[derive(Clone, Copy)] pub enum PositionEncoding { Utf8, - Utf16, + Wide(WideEncoding), } pub(crate) struct LineIndex { diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs index 08b2c837de37..e33589cc5369 100644 --- a/crates/rust-analyzer/src/lsp_ext.rs +++ b/crates/rust-analyzer/src/lsp_ext.rs @@ -2,6 +2,7 @@ use std::{collections::HashMap, path::PathBuf}; +use ide_db::line_index::WideEncoding; use lsp_types::request::Request; use lsp_types::PositionEncodingKind; use lsp_types::{ @@ -10,6 +11,8 @@ use lsp_types::{ }; use serde::{Deserialize, Serialize}; +use crate::line_index::PositionEncoding; + pub enum AnalyzerStatus {} impl Request for AnalyzerStatus { @@ -481,16 +484,22 @@ pub(crate) enum CodeLensResolveData { References(lsp_types::TextDocumentPositionParams), } -pub fn supports_utf8(caps: &lsp_types::ClientCapabilities) -> bool { - match &caps.general { - Some(general) => general - .position_encodings - .as_deref() - .unwrap_or_default() - .iter() - .any(|it| it == &PositionEncodingKind::UTF8), - _ => false, +pub fn negotiated_encoding(caps: &lsp_types::ClientCapabilities) -> PositionEncoding { + let client_encodings = match &caps.general { + Some(general) => general.position_encodings.as_deref().unwrap_or_default(), + None => &[], + }; + + for enc in client_encodings { + if enc == &PositionEncodingKind::UTF8 { + return PositionEncoding::Utf8; + } else if enc == &PositionEncodingKind::UTF32 { + return PositionEncoding::Wide(WideEncoding::Utf32); + } + // NB: intentionally prefer just about anything else to utf-16. } + + PositionEncoding::Wide(WideEncoding::Utf16) } pub enum MoveItem {} diff --git a/crates/rust-analyzer/src/lsp_utils.rs b/crates/rust-analyzer/src/lsp_utils.rs index baa77a005e22..30f1c53c198f 100644 --- a/crates/rust-analyzer/src/lsp_utils.rs +++ b/crates/rust-analyzer/src/lsp_utils.rs @@ -161,6 +161,7 @@ impl GlobalState { } pub(crate) fn apply_document_changes( + encoding: PositionEncoding, file_contents: impl FnOnce() -> String, mut content_changes: Vec, ) -> String { @@ -192,9 +193,9 @@ pub(crate) fn apply_document_changes( let mut line_index = LineIndex { // the index will be overwritten in the bottom loop's first iteration index: Arc::new(ide::LineIndex::new(&text)), - // We don't care about line endings or offset encoding here. + // We don't care about line endings here. endings: LineEndings::Unix, - encoding: PositionEncoding::Utf16, + encoding, }; // The changes we got must be applied sequentially, but can cross lines so we @@ -256,6 +257,7 @@ pub(crate) fn all_edits_are_disjoint( #[cfg(test)] mod tests { + use ide_db::line_index::WideEncoding; use lsp_types::{ CompletionItem, CompletionTextEdit, InsertReplaceEdit, Position, Range, TextDocumentContentChangeEvent, @@ -278,9 +280,11 @@ mod tests { }; } - let text = apply_document_changes(|| String::new(), vec![]); + let encoding = PositionEncoding::Wide(WideEncoding::Utf16); + let text = apply_document_changes(encoding, || String::new(), vec![]); assert_eq!(text, ""); let text = apply_document_changes( + encoding, || text, vec![TextDocumentContentChangeEvent { range: None, @@ -289,39 +293,49 @@ mod tests { }], ); assert_eq!(text, "the"); - let text = apply_document_changes(|| text, c![0, 3; 0, 3 => " quick"]); + let text = apply_document_changes(encoding, || text, c![0, 3; 0, 3 => " quick"]); assert_eq!(text, "the quick"); - let text = apply_document_changes(|| text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]); + let text = + apply_document_changes(encoding, || text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]); assert_eq!(text, "quick foxes"); - let text = apply_document_changes(|| text, c![0, 11; 0, 11 => "\ndream"]); + let text = apply_document_changes(encoding, || text, c![0, 11; 0, 11 => "\ndream"]); assert_eq!(text, "quick foxes\ndream"); - let text = apply_document_changes(|| text, c![1, 0; 1, 0 => "have "]); + let text = apply_document_changes(encoding, || text, c![1, 0; 1, 0 => "have "]); assert_eq!(text, "quick foxes\nhave dream"); let text = apply_document_changes( + encoding, || text, c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"], ); assert_eq!(text, "the quick foxes\nhave quiet dreams\n"); - let text = apply_document_changes(|| text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]); + let text = apply_document_changes( + encoding, + || text, + c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"], + ); assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n"); let text = apply_document_changes( + encoding, || text, c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"], ); assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n"); - let text = apply_document_changes(|| text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]); + let text = + apply_document_changes(encoding, || text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]); assert_eq!(text, "the quick \nthey have quiet dreams\n"); let text = String::from("❤️"); - let text = apply_document_changes(|| text, c![0, 0; 0, 0 => "a"]); + let text = apply_document_changes(encoding, || text, c![0, 0; 0, 0 => "a"]); assert_eq!(text, "a❤️"); let text = String::from("a\nb"); - let text = apply_document_changes(|| text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]); + let text = + apply_document_changes(encoding, || text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]); assert_eq!(text, "adcb"); let text = String::from("a\nb"); - let text = apply_document_changes(|| text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]); + let text = + apply_document_changes(encoding, || text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]); assert_eq!(text, "ațc\ncb"); } diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 346a74e270f9..d1e38b33c7de 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -831,6 +831,7 @@ impl GlobalState { let vfs = &mut this.vfs.write().0; let file_id = vfs.file_id(&path).unwrap(); let text = apply_document_changes( + this.config.position_encoding(), || std::str::from_utf8(vfs.file_contents(file_id)).unwrap().into(), params.content_changes, ); diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index 5ac5af94f5ae..abce0d737827 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -268,7 +268,10 @@ impl GlobalState { ] }) }) - .map(|glob_pattern| lsp_types::FileSystemWatcher { glob_pattern, kind: None }) + .map(|glob_pattern| lsp_types::FileSystemWatcher { + glob_pattern: lsp_types::GlobPattern::String(glob_pattern), + kind: None, + }) .collect(), }; let registration = lsp_types::Registration { diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index 5bdc1bf8d9bb..92029dc1de78 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -31,8 +31,8 @@ pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::P let line_col = line_index.index.line_col(offset); match line_index.encoding { PositionEncoding::Utf8 => lsp_types::Position::new(line_col.line, line_col.col), - PositionEncoding::Utf16 => { - let line_col = line_index.index.to_utf16(line_col); + PositionEncoding::Wide(enc) => { + let line_col = line_index.index.to_wide(enc, line_col); lsp_types::Position::new(line_col.line, line_col.col) } } @@ -212,7 +212,7 @@ pub(crate) fn completion_items( tdpp: lsp_types::TextDocumentPositionParams, items: Vec, ) -> Vec { - let max_relevance = items.iter().map(|it| it.relevance().score()).max().unwrap_or_default(); + let max_relevance = items.iter().map(|it| it.relevance.score()).max().unwrap_or_default(); let mut res = Vec::with_capacity(items.len()); for item in items { completion_item(&mut res, config, line_index, &tdpp, max_relevance, item); @@ -235,22 +235,26 @@ fn completion_item( item: CompletionItem, ) { let insert_replace_support = config.insert_replace_support().then_some(tdpp.position); + let ref_match = item.ref_match(); + let lookup = item.lookup().to_string(); + let mut additional_text_edits = Vec::new(); // LSP does not allow arbitrary edits in completion, so we have to do a // non-trivial mapping here. let text_edit = { let mut text_edit = None; - let source_range = item.source_range(); - for indel in item.text_edit().iter() { + let source_range = item.source_range; + for indel in item.text_edit { if indel.delete.contains_range(source_range) { + // Extract this indel as the main edit text_edit = Some(if indel.delete == source_range { self::completion_text_edit(line_index, insert_replace_support, indel.clone()) } else { assert!(source_range.end() == indel.delete.end()); let range1 = TextRange::new(indel.delete.start(), source_range.start()); let range2 = source_range; - let indel1 = Indel::replace(range1, String::new()); + let indel1 = Indel::delete(range1); let indel2 = Indel::replace(range2, indel.insert.clone()); additional_text_edits.push(self::text_edit(line_index, indel1)); self::completion_text_edit(line_index, insert_replace_support, indel2) @@ -264,23 +268,23 @@ fn completion_item( text_edit.unwrap() }; - let insert_text_format = item.is_snippet().then_some(lsp_types::InsertTextFormat::SNIPPET); - let tags = item.deprecated().then(|| vec![lsp_types::CompletionItemTag::DEPRECATED]); - let command = if item.trigger_call_info() && config.client_commands().trigger_parameter_hints { + let insert_text_format = item.is_snippet.then_some(lsp_types::InsertTextFormat::SNIPPET); + let tags = item.deprecated.then(|| vec![lsp_types::CompletionItemTag::DEPRECATED]); + let command = if item.trigger_call_info && config.client_commands().trigger_parameter_hints { Some(command::trigger_parameter_hints()) } else { None }; let mut lsp_item = lsp_types::CompletionItem { - label: item.label().to_string(), - detail: item.detail().map(|it| it.to_string()), - filter_text: Some(item.lookup().to_string()), - kind: Some(completion_item_kind(item.kind())), + label: item.label.to_string(), + detail: item.detail.map(|it| it.to_string()), + filter_text: Some(lookup), + kind: Some(completion_item_kind(item.kind)), text_edit: Some(text_edit), additional_text_edits: Some(additional_text_edits), - documentation: item.documentation().map(documentation), - deprecated: Some(item.deprecated()), + documentation: item.documentation.map(documentation), + deprecated: Some(item.deprecated), tags, command, insert_text_format, @@ -294,12 +298,13 @@ fn completion_item( }); } - set_score(&mut lsp_item, max_relevance, item.relevance()); + set_score(&mut lsp_item, max_relevance, item.relevance); if config.completion().enable_imports_on_the_fly { - if let imports @ [_, ..] = item.imports_to_add() { - let imports: Vec<_> = imports - .iter() + if !item.import_to_add.is_empty() { + let imports: Vec<_> = item + .import_to_add + .into_iter() .filter_map(|import_edit| { let import_path = &import_edit.import_path; let import_name = import_path.segments().last()?; @@ -316,18 +321,13 @@ fn completion_item( } } - if let Some((mutability, offset, relevance)) = item.ref_match() { - let mut lsp_item_with_ref = lsp_item.clone(); + if let Some((label, indel, relevance)) = ref_match { + let mut lsp_item_with_ref = lsp_types::CompletionItem { label, ..lsp_item.clone() }; + lsp_item_with_ref + .additional_text_edits + .get_or_insert_with(Default::default) + .push(self::text_edit(line_index, indel)); set_score(&mut lsp_item_with_ref, max_relevance, relevance); - lsp_item_with_ref.label = - format!("&{}{}", mutability.as_keyword_for_ref(), lsp_item_with_ref.label); - lsp_item_with_ref.additional_text_edits.get_or_insert_with(Default::default).push( - self::text_edit( - line_index, - Indel::insert(offset, format!("&{}", mutability.as_keyword_for_ref())), - ), - ); - acc.push(lsp_item_with_ref); }; @@ -766,6 +766,7 @@ pub(crate) fn folding_range( end_line, end_character: None, kind, + collapsed_text: None, } } else { lsp_types::FoldingRange { @@ -774,6 +775,7 @@ pub(crate) fn folding_range( end_line: range.end.line, end_character: Some(range.end.character), kind, + collapsed_text: None, } } } @@ -1360,7 +1362,7 @@ pub(crate) mod command { pub(crate) fn trigger_parameter_hints() -> lsp_types::Command { lsp_types::Command { title: "triggerParameterHints".into(), - command: "editor.action.triggerParameterHints".into(), + command: "rust-analyzer.triggerParameterHints".into(), arguments: None, } } @@ -1429,7 +1431,7 @@ fn main() { let line_index = LineIndex { index: Arc::new(ide::LineIndex::new(text)), endings: LineEndings::Unix, - encoding: PositionEncoding::Utf16, + encoding: PositionEncoding::Utf8, }; let converted: Vec = folds.into_iter().map(|it| folding_range(text, &line_index, true, it)).collect(); diff --git a/crates/rust-analyzer/tests/slow-tests/main.rs b/crates/rust-analyzer/tests/slow-tests/main.rs index 5e3e19d44d73..587d640969aa 100644 --- a/crates/rust-analyzer/tests/slow-tests/main.rs +++ b/crates/rust-analyzer/tests/slow-tests/main.rs @@ -22,7 +22,7 @@ use lsp_types::{ notification::DidOpenTextDocument, request::{ CodeActionRequest, Completion, Formatting, GotoTypeDefinition, HoverRequest, - WillRenameFiles, WorkspaceSymbol, + WillRenameFiles, WorkspaceSymbolRequest, }, CodeActionContext, CodeActionParams, CompletionParams, DidOpenTextDocumentParams, DocumentFormattingParams, FileRename, FormattingOptions, GotoDefinitionParams, HoverParams, @@ -1095,5 +1095,5 @@ pub fn bar() {} .server() .wait_until_workspace_is_loaded(); - server.request::(Default::default(), json!([])); + server.request::(Default::default(), json!([])); } diff --git a/crates/rust-analyzer/tests/slow-tests/support.rs b/crates/rust-analyzer/tests/slow-tests/support.rs index b7275df0f401..037fc89ace09 100644 --- a/crates/rust-analyzer/tests/slow-tests/support.rs +++ b/crates/rust-analyzer/tests/slow-tests/support.rs @@ -107,6 +107,7 @@ impl<'a> Project<'a> { did_change_watched_files: Some( lsp_types::DidChangeWatchedFilesClientCapabilities { dynamic_registration: Some(true), + relative_pattern_support: None, }, ), ..Default::default() diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs index bd24d7d28bac..5639aaf57cd9 100644 --- a/crates/stdx/src/lib.rs +++ b/crates/stdx/src/lib.rs @@ -11,6 +11,7 @@ pub mod hash; pub mod process; pub mod panic_context; pub mod non_empty_vec; +pub mod rand; pub use always_assert::{always, never}; diff --git a/crates/stdx/src/rand.rs b/crates/stdx/src/rand.rs new file mode 100644 index 000000000000..64aa57eae09c --- /dev/null +++ b/crates/stdx/src/rand.rs @@ -0,0 +1,21 @@ +//! We don't use `rand`, as that's too many things for us. +//! +//! We currently use oorandom instead, but it's missing these two utilities. +//! Perhaps we should switch to `fastrand`, or our own small PRNG, it's not like +//! we need anything more complicated than xor-shift. + +pub fn shuffle(slice: &mut [T], mut rand_index: impl FnMut(usize) -> usize) { + let mut remaining = slice.len() - 1; + while remaining > 0 { + let index = rand_index(remaining); + slice.swap(remaining, index); + remaining -= 1; + } +} + +pub fn seed() -> u64 { + use std::collections::hash_map::RandomState; + use std::hash::{BuildHasher, Hasher}; + + RandomState::new().build_hasher().finish() +} diff --git a/crates/syntax/src/lib.rs b/crates/syntax/src/lib.rs index 84c66b27e69f..6f57cbad66b1 100644 --- a/crates/syntax/src/lib.rs +++ b/crates/syntax/src/lib.rs @@ -186,7 +186,7 @@ impl SourceFile { /// ``` #[macro_export] macro_rules! match_ast { - (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) }; + (match $node:ident { $($tt:tt)* }) => { $crate::match_ast!(match ($node) { $($tt)* }) }; (match ($node:expr) { $( $( $path:ident )::+ ($it:pat) => $res:expr, )* diff --git a/docs/dev/architecture.md b/docs/dev/architecture.md index a07cf036e060..895de5798ac3 100644 --- a/docs/dev/architecture.md +++ b/docs/dev/architecture.md @@ -119,7 +119,7 @@ See [#93](https://github.com/rust-lang/rust-analyzer/pull/93) for an example PR **Architecture Invariant:** `syntax` crate is completely independent from the rest of rust-analyzer. It knows nothing about salsa or LSP. This is important because it is possible to make useful tooling using only the syntax tree. Without semantic information, you don't need to be able to _build_ code, which makes the tooling more robust. -See also https://web.stanford.edu/~mlfbrown/paper.pdf. +See also https://mlfbrown.com/paper.pdf. You can view the `syntax` crate as an entry point to rust-analyzer. `syntax` crate is an **API Boundary**. diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md index a794e866181d..c3623a5cc46c 100644 --- a/docs/dev/lsp-extensions.md +++ b/docs/dev/lsp-extensions.md @@ -1,5 +1,5 @@ $DIR/missing_doc.rs:17:1 - | -LL | pub type PubTypedef = String; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - error: missing documentation for a module --> $DIR/missing_doc.rs:19:1 | LL | mod module_no_dox {} | ^^^^^^^^^^^^^^^^^^^^ -error: missing documentation for a module - --> $DIR/missing_doc.rs:20:1 - | -LL | pub mod pub_module_no_dox {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: missing documentation for a function - --> $DIR/missing_doc.rs:24:1 - | -LL | pub fn foo2() {} - | ^^^^^^^^^^^^^^^^ - error: missing documentation for a function --> $DIR/missing_doc.rs:25:1 | @@ -69,50 +51,18 @@ error: missing documentation for a variant LL | BarB, | ^^^^ -error: missing documentation for an enum - --> $DIR/missing_doc.rs:44:1 - | -LL | / pub enum PubBaz { -LL | | PubBazA { a: isize }, -LL | | } - | |_^ - -error: missing documentation for a variant - --> $DIR/missing_doc.rs:45:5 - | -LL | PubBazA { a: isize }, - | ^^^^^^^^^^^^^^^^^^^^ - -error: missing documentation for a struct field - --> $DIR/missing_doc.rs:45:15 - | -LL | PubBazA { a: isize }, - | ^^^^^^^^ - error: missing documentation for a constant --> $DIR/missing_doc.rs:65:1 | LL | const FOO: u32 = 0; | ^^^^^^^^^^^^^^^^^^^ -error: missing documentation for a constant - --> $DIR/missing_doc.rs:72:1 - | -LL | pub const FOO4: u32 = 0; - | ^^^^^^^^^^^^^^^^^^^^^^^^ - error: missing documentation for a static --> $DIR/missing_doc.rs:74:1 | LL | static BAR: u32 = 0; | ^^^^^^^^^^^^^^^^^^^^ -error: missing documentation for a static - --> $DIR/missing_doc.rs:81:1 - | -LL | pub static BAR4: u32 = 0; - | ^^^^^^^^^^^^^^^^^^^^^^^^^ - error: missing documentation for a module --> $DIR/missing_doc.rs:83:1 | @@ -125,35 +75,17 @@ LL | | } LL | | } | |_^ -error: missing documentation for a function - --> $DIR/missing_doc.rs:86:5 - | -LL | pub fn undocumented1() {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: missing documentation for a function - --> $DIR/missing_doc.rs:87:5 - | -LL | pub fn undocumented2() {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^ - error: missing documentation for a function --> $DIR/missing_doc.rs:88:5 | LL | fn undocumented3() {} | ^^^^^^^^^^^^^^^^^^^^^ -error: missing documentation for a function - --> $DIR/missing_doc.rs:93:9 - | -LL | pub fn also_undocumented1() {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - error: missing documentation for a function --> $DIR/missing_doc.rs:94:9 | LL | fn also_undocumented2() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 24 previous errors +error: aborting due to 13 previous errors diff --git a/tests/ui/missing_doc_impl.stderr b/tests/ui/missing_doc_impl.stderr index b410f56e1671..111d65469660 100644 --- a/tests/ui/missing_doc_impl.stderr +++ b/tests/ui/missing_doc_impl.stderr @@ -21,60 +21,12 @@ error: missing documentation for a struct field LL | b: isize, | ^^^^^^^^ -error: missing documentation for a struct - --> $DIR/missing_doc_impl.rs:18:1 - | -LL | / pub struct PubFoo { -LL | | pub a: isize, -LL | | b: isize, -LL | | } - | |_^ - -error: missing documentation for a struct field - --> $DIR/missing_doc_impl.rs:19:5 - | -LL | pub a: isize, - | ^^^^^^^^^^^^ - error: missing documentation for a struct field --> $DIR/missing_doc_impl.rs:20:5 | LL | b: isize, | ^^^^^^^^ -error: missing documentation for a trait - --> $DIR/missing_doc_impl.rs:43:1 - | -LL | / pub trait C { -LL | | fn foo(&self); -LL | | fn foo_with_impl(&self) {} -LL | | } - | |_^ - -error: missing documentation for a method - --> $DIR/missing_doc_impl.rs:44:5 - | -LL | fn foo(&self); - | ^^^^^^^^^^^^^^ - -error: missing documentation for a method - --> $DIR/missing_doc_impl.rs:45:5 - | -LL | fn foo_with_impl(&self) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: missing documentation for an associated type - --> $DIR/missing_doc_impl.rs:55:5 - | -LL | type AssociatedType; - | ^^^^^^^^^^^^^^^^^^^^ - -error: missing documentation for an associated type - --> $DIR/missing_doc_impl.rs:56:5 - | -LL | type AssociatedTypeDef = Self; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - error: missing documentation for an associated function --> $DIR/missing_doc_impl.rs:67:5 | @@ -89,12 +41,6 @@ error: missing documentation for an associated function LL | fn bar() {} | ^^^^^^^^^^^ -error: missing documentation for an associated function - --> $DIR/missing_doc_impl.rs:74:5 - | -LL | pub fn foo() {} - | ^^^^^^^^^^^^^^^ - error: missing documentation for an associated function --> $DIR/missing_doc_impl.rs:78:5 | @@ -103,5 +49,5 @@ LL | | 1 LL | | } | |_____^ -error: aborting due to 15 previous errors +error: aborting due to 7 previous errors From 0413fb35babdfe8fe2456e7b3d520f1ffb5de231 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sat, 25 Feb 2023 19:08:29 -0500 Subject: [PATCH 031/362] Merge commit '149392b0baa4730c68f3c3eadf5c6ed7b16b85a4' into clippyup --- CHANGELOG.md | 7 + README.md | 39 +- .../development/infrastructure/backport.md | 1 + book/src/development/infrastructure/sync.md | 3 +- book/src/lint_configuration.md | 18 +- clippy_dev/src/new_lint.rs | 9 +- clippy_dev/src/update_lints.rs | 6 +- clippy_lints/src/box_default.rs | 3 +- .../src/casts/cast_possible_truncation.rs | 2 +- clippy_lints/src/declared_lints.rs | 7 + clippy_lints/src/dereference.rs | 78 +++- clippy_lints/src/doc.rs | 26 +- clippy_lints/src/entry.rs | 12 +- .../src/extra_unused_type_parameters.rs | 93 +++- clippy_lints/src/format_args.rs | 1 + .../src/functions/impl_trait_in_params.rs | 50 +++ clippy_lints/src/functions/mod.rs | 29 ++ .../src/inconsistent_struct_constructor.rs | 4 +- clippy_lints/src/let_underscore.rs | 52 ++- clippy_lints/src/lib.rs | 16 +- clippy_lints/src/lifetimes.rs | 8 + clippy_lints/src/literal_representation.rs | 12 +- clippy_lints/src/loops/never_loop.rs | 59 +-- clippy_lints/src/manual_let_else.rs | 153 ++++--- clippy_lints/src/methods/bytes_nth.rs | 42 +- clippy_lints/src/methods/expect_used.rs | 4 +- clippy_lints/src/methods/implicit_clone.rs | 4 +- clippy_lints/src/methods/mod.rs | 31 ++ .../methods/suspicious_command_arg_space.rs | 39 ++ clippy_lints/src/methods/unwrap_used.rs | 4 +- clippy_lints/src/missing_doc.rs | 32 +- clippy_lints/src/module_style.rs | 2 +- clippy_lints/src/mut_key.rs | 3 +- clippy_lints/src/needless_pass_by_value.rs | 2 +- clippy_lints/src/no_mangle_with_rust_abi.rs | 65 +++ .../src/operators/arithmetic_side_effects.rs | 33 +- clippy_lints/src/question_mark_used.rs | 52 +++ clippy_lints/src/returns.rs | 112 +++-- .../src/significant_drop_tightening.rs | 399 ++++++++++++++++++ clippy_lints/src/swap.rs | 109 +++-- clippy_lints/src/transmute/mod.rs | 28 ++ .../transmute/transmute_int_to_non_zero.rs | 61 +++ clippy_lints/src/unnested_or_patterns.rs | 12 +- clippy_lints/src/utils/conf.rs | 13 +- .../internal_lints/unnecessary_def_path.rs | 4 +- clippy_utils/src/macros.rs | 9 +- clippy_utils/src/numeric_literal.rs | 2 +- clippy_utils/src/paths.rs | 1 + clippy_utils/src/sugg.rs | 8 +- clippy_utils/src/ty.rs | 29 +- lintcheck/src/config.rs | 2 +- lintcheck/src/main.rs | 12 +- rust-toolchain | 2 +- src/driver.rs | 5 +- tests/ui-internal/custom_ice_message.rs | 3 +- tests/ui-internal/custom_ice_message.stderr | 4 +- ...unnecessary_def_path_hardcoded_path.stderr | 18 +- tests/ui-toml/expect_used/expect_used.rs | 12 + .../pub_crate_missing_docs/clippy.toml | 1 + .../pub_crate_missing_doc.rs | 59 +++ .../pub_crate_missing_doc.stderr | 52 +++ .../toml_unknown_key/conf_unknown_key.stderr | 1 + tests/ui-toml/unwrap_used/unwrap_used.rs | 6 + tests/ui-toml/unwrap_used/unwrap_used.stderr | 10 +- tests/ui/arithmetic_side_effects.rs | 27 ++ tests/ui/arithmetic_side_effects.stderr | 198 ++++----- tests/ui/box_default.fixed | 18 + tests/ui/box_default.rs | 18 + tests/ui/box_default.stderr | 14 +- tests/ui/bytes_nth.fixed | 6 +- tests/ui/bytes_nth.rs | 2 +- tests/ui/bytes_nth.stderr | 10 +- tests/ui/cast.stderr | 36 +- tests/ui/cast_size.stderr | 18 +- .../needless_pass_by_value-w-late-bound.rs | 9 + ...needless_pass_by_value-w-late-bound.stderr | 15 + tests/ui/doc/doc-fixable.fixed | 2 +- tests/ui/doc/doc-fixable.rs | 4 +- tests/ui/doc/doc-fixable.stderr | 24 +- tests/ui/entry.fixed | 14 + tests/ui/entry.rs | 14 + tests/ui/explicit_auto_deref.fixed | 14 + tests/ui/explicit_auto_deref.rs | 14 + tests/ui/extra_unused_type_parameters.rs | 53 ++- tests/ui/extra_unused_type_parameters.stderr | 42 +- tests/ui/format.fixed | 6 + tests/ui/format.rs | 6 + tests/ui/format.stderr | 30 +- tests/ui/impl_trait_in_params.rs | 17 + tests/ui/impl_trait_in_params.stderr | 25 ++ tests/ui/large_digit_groups.fixed | 4 +- tests/ui/large_digit_groups.stderr | 20 +- tests/ui/let_underscore_untyped.rs | 54 +++ tests/ui/let_underscore_untyped.stderr | 51 +++ tests/ui/literals.stderr | 10 +- tests/ui/manual_let_else.rs | 11 + tests/ui/manual_let_else_match.rs | 29 +- tests/ui/manual_let_else_match.stderr | 15 +- tests/ui/map_flatten_fixable.fixed | 1 + tests/ui/map_flatten_fixable.rs | 1 + tests/ui/map_flatten_fixable.stderr | 18 +- tests/ui/methods.rs | 1 + tests/ui/methods.stderr | 4 +- tests/ui/must_use_candidates.fixed | 2 +- tests/ui/must_use_candidates.rs | 2 +- tests/ui/needless_lifetimes.fixed | 10 + tests/ui/needless_lifetimes.rs | 10 + tests/ui/needless_return.fixed | 10 + tests/ui/needless_return.rs | 10 + tests/ui/needless_return.stderr | 18 +- tests/ui/never_loop.rs | 45 ++ tests/ui/never_loop.stderr | 15 +- tests/ui/no_mangle_with_rust_abi.fixed | 48 +++ tests/ui/no_mangle_with_rust_abi.rs | 48 +++ tests/ui/no_mangle_with_rust_abi.stderr | 45 ++ tests/ui/question_mark_used.rs | 15 + tests/ui/question_mark_used.stderr | 11 + tests/ui/significant_drop_tightening.fixed | 84 ++++ tests/ui/significant_drop_tightening.rs | 80 ++++ tests/ui/significant_drop_tightening.stderr | 94 +++++ tests/ui/suspicious_command_arg_space.rs | 10 + tests/ui/suspicious_command_arg_space.stderr | 25 ++ tests/ui/swap.fixed | 24 +- tests/ui/swap.rs | 29 +- tests/ui/swap.stderr | 65 ++- tests/ui/transmute_int_to_non_zero.rs | 41 ++ tests/ui/transmute_int_to_non_zero.stderr | 64 +++ tests/ui/uninlined_format_args.fixed | 4 + tests/ui/uninlined_format_args.rs | 4 + tests/ui/unreadable_literal.fixed | 2 +- tests/ui/unreadable_literal.stderr | 10 +- 131 files changed, 3025 insertions(+), 630 deletions(-) create mode 100644 clippy_lints/src/functions/impl_trait_in_params.rs create mode 100644 clippy_lints/src/methods/suspicious_command_arg_space.rs create mode 100644 clippy_lints/src/no_mangle_with_rust_abi.rs create mode 100644 clippy_lints/src/question_mark_used.rs create mode 100644 clippy_lints/src/significant_drop_tightening.rs create mode 100644 clippy_lints/src/transmute/transmute_int_to_non_zero.rs create mode 100644 tests/ui-toml/pub_crate_missing_docs/clippy.toml create mode 100644 tests/ui-toml/pub_crate_missing_docs/pub_crate_missing_doc.rs create mode 100644 tests/ui-toml/pub_crate_missing_docs/pub_crate_missing_doc.stderr create mode 100644 tests/ui/crashes/needless_pass_by_value-w-late-bound.rs create mode 100644 tests/ui/crashes/needless_pass_by_value-w-late-bound.stderr create mode 100644 tests/ui/impl_trait_in_params.rs create mode 100644 tests/ui/impl_trait_in_params.stderr create mode 100644 tests/ui/let_underscore_untyped.rs create mode 100644 tests/ui/let_underscore_untyped.stderr create mode 100644 tests/ui/no_mangle_with_rust_abi.fixed create mode 100644 tests/ui/no_mangle_with_rust_abi.rs create mode 100644 tests/ui/no_mangle_with_rust_abi.stderr create mode 100644 tests/ui/question_mark_used.rs create mode 100644 tests/ui/question_mark_used.stderr create mode 100644 tests/ui/significant_drop_tightening.fixed create mode 100644 tests/ui/significant_drop_tightening.rs create mode 100644 tests/ui/significant_drop_tightening.stderr create mode 100644 tests/ui/suspicious_command_arg_space.rs create mode 100644 tests/ui/suspicious_command_arg_space.stderr create mode 100644 tests/ui/transmute_int_to_non_zero.rs create mode 100644 tests/ui/transmute_int_to_non_zero.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 659e8aebcd57..765826ed867d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4430,6 +4430,7 @@ Released 2018-09-13 [`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 +[`impl_trait_in_params`]: https://rust-lang.github.io/rust-clippy/master/index.html#impl_trait_in_params [`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 @@ -4494,6 +4495,7 @@ Released 2018-09-13 [`let_underscore_future`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_future [`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_underscore_untyped`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_untyped [`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 @@ -4620,6 +4622,7 @@ Released 2018-09-13 [`no_effect`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect [`no_effect_replace`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect_replace [`no_effect_underscore_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect_underscore_binding +[`no_mangle_with_rust_abi`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_mangle_with_rust_abi [`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 @@ -4675,6 +4678,7 @@ Released 2018-09-13 [`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 +[`question_mark_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#question_mark_used [`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 @@ -4734,6 +4738,7 @@ Released 2018-09-13 [`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 +[`significant_drop_tightening`]: https://rust-lang.github.io/rust-clippy/master/index.html#significant_drop_tightening [`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 @@ -4764,6 +4769,7 @@ Released 2018-09-13 [`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_command_arg_space`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_command_arg_space [`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 @@ -4790,6 +4796,7 @@ Released 2018-09-13 [`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_int_to_non_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_non_zero [`transmute_null_to_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_null_to_fn [`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 diff --git a/README.md b/README.md index 95f6d2cc45c8..3e7379ace7ea 100644 --- a/README.md +++ b/README.md @@ -19,21 +19,35 @@ You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the | `clippy::complexity` | code that does something simple but in a complex way | **warn** | | `clippy::perf` | code that can be written to run faster | **warn** | | `clippy::pedantic` | lints which are rather strict or have occasional false positives | allow | +| `clippy::restriction` | lints which prevent the use of language and library features[^restrict] | allow | | `clippy::nursery` | new lints that are still under development | allow | | `clippy::cargo` | lints for the cargo manifest | allow | More to come, please [file an issue](https://github.com/rust-lang/rust-clippy/issues) if you have ideas! -The [lint list](https://rust-lang.github.io/rust-clippy/master/index.html) also contains "restriction lints", which are -for things which are usually not considered "bad", but may be useful to turn on in specific cases. These should be used -very selectively, if at all. +The `restriction` category should, *emphatically*, not be enabled as a whole. The contained +lints may lint against perfectly reasonable code, may not have an alternative suggestion, +and may contradict any other lints (including other categories). Lints should be considered +on a case-by-case basis before enabling. + +[^restrict]: Some use cases for `restriction` lints include: + - Strict coding styles (e.g. [`clippy::else_if_without_else`]). + - Additional restrictions on CI (e.g. [`clippy::todo`]). + - Preventing panicking in certain functions (e.g. [`clippy::unwrap_used`]). + - Running a lint only on a subset of code (e.g. `#[forbid(clippy::float_arithmetic)]` on a module). + +[`clippy::else_if_without_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#else_if_without_else +[`clippy::todo`]: https://rust-lang.github.io/rust-clippy/master/index.html#todo +[`clippy::unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_used + +--- Table of contents: -* [Usage instructions](#usage) -* [Configuration](#configuration) -* [Contributing](#contributing) -* [License](#license) +* [Usage instructions](#usage) +* [Configuration](#configuration) +* [Contributing](#contributing) +* [License](#license) ## Usage @@ -64,6 +78,7 @@ Once you have rustup and the latest stable release (at least Rust 1.29) installe ```terminal rustup component add clippy ``` + If it says that it can't find the `clippy` component, please run `rustup self update`. #### Step 3: Run Clippy @@ -143,16 +158,16 @@ line. (You can swap `clippy::all` with the specific lint category you are target You can add options to your code to `allow`/`warn`/`deny` Clippy lints: -* the whole set of `Warn` lints using the `clippy` lint group (`#![deny(clippy::all)]`). +* the whole set of `Warn` lints using the `clippy` lint group (`#![deny(clippy::all)]`). Note that `rustc` has additional [lint groups](https://doc.rust-lang.org/rustc/lints/groups.html). -* all lints using both the `clippy` and `clippy::pedantic` lint groups (`#![deny(clippy::all)]`, +* all lints using both the `clippy` and `clippy::pedantic` lint groups (`#![deny(clippy::all)]`, `#![deny(clippy::pedantic)]`). Note that `clippy::pedantic` contains some very aggressive lints prone to false positives. -* only some lints (`#![deny(clippy::single_match, clippy::box_vec)]`, etc.) +* only some lints (`#![deny(clippy::single_match, clippy::box_vec)]`, etc.) -* `allow`/`warn`/`deny` can be limited to a single function or module using `#[allow(...)]`, etc. +* `allow`/`warn`/`deny` can be limited to a single function or module using `#[allow(...)]`, etc. Note: `allow` means to suppress the lint for your code. With `warn` the lint will only emit a warning, while with `deny` the lint will emit an error, when @@ -176,12 +191,14 @@ cargo clippy -- -W clippy::lint_name This also works with lint groups. For example, you can run Clippy with warnings for all lints enabled: + ```terminal cargo clippy -- -W clippy::pedantic ``` If you care only about a single lint, you can allow all others and then explicitly warn on the lint(s) you are interested in: + ```terminal cargo clippy -- -A clippy::all -W clippy::useless_format -W clippy::... ``` diff --git a/book/src/development/infrastructure/backport.md b/book/src/development/infrastructure/backport.md index 15f3d1f08060..6920c4e46561 100644 --- a/book/src/development/infrastructure/backport.md +++ b/book/src/development/infrastructure/backport.md @@ -28,6 +28,7 @@ repository. You can do this with: ```bash # Assuming the current directory corresponds to the Rust repository $ git checkout beta +# Make sure to change `your-github-name` to your github name in the following command $ git subtree pull -p src/tools/clippy https://github.com//rust-clippy backport $ ./x.py test src/tools/clippy ``` diff --git a/book/src/development/infrastructure/sync.md b/book/src/development/infrastructure/sync.md index 5a0f7409a2e4..02cfc11b55ac 100644 --- a/book/src/development/infrastructure/sync.md +++ b/book/src/development/infrastructure/sync.md @@ -79,8 +79,7 @@ to be run inside the `rust` directory): `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 + # 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 and you have to run this command again. git subtree push -P src/tools/clippy clippy-local sync-from-rust ``` diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index 32e8e218c405..33f2b5c1de99 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -53,6 +53,7 @@ Please use that command to update the file and do not edit it by hand. | [ignore-interior-mutability](#ignore-interior-mutability) | `["bytes::Bytes"]` | | [allow-mixed-uninlined-format-args](#allow-mixed-uninlined-format-args) | `true` | | [suppress-restriction-lint-in-const](#suppress-restriction-lint-in-const) | `false` | +| [missing-docs-in-crate-items](#missing-docs-in-crate-items) | `false` | ### arithmetic-side-effects-allowed Suppress checking of the passed type names in all types of operations. @@ -471,7 +472,7 @@ The maximum size of a file included via `include_bytes!()` or `include_str!()`, ### allow-expect-in-tests -Whether `expect` should be allowed within `#[cfg(test)]` +Whether `expect` should be allowed in test functions or `#[cfg(test)]` **Default Value:** `false` (`bool`) @@ -479,7 +480,7 @@ Whether `expect` should be allowed within `#[cfg(test)]` ### allow-unwrap-in-tests -Whether `unwrap` should be allowed in test cfg +Whether `unwrap` should be allowed in test functions or `#[cfg(test)]` **Default Value:** `false` (`bool`) @@ -487,7 +488,7 @@ Whether `unwrap` should be allowed in test cfg ### allow-dbg-in-tests -Whether `dbg!` should be allowed in test functions +Whether `dbg!` should be allowed in test functions or `#[cfg(test)]` **Default Value:** `false` (`bool`) @@ -495,7 +496,7 @@ Whether `dbg!` should be allowed in test functions ### allow-print-in-tests -Whether print macros (ex. `println!`) should be allowed in test functions +Whether print macros (ex. `println!`) should be allowed in test functions or `#[cfg(test)]` **Default Value:** `false` (`bool`) @@ -540,4 +541,13 @@ if no suggestion can be made. * [indexing_slicing](https://rust-lang.github.io/rust-clippy/master/index.html#indexing_slicing) +### missing-docs-in-crate-items +Whether to **only** check for missing documentation in items visible within the current +crate. For example, `pub(crate)` items. + +**Default Value:** `false` (`bool`) + +* [missing_docs_in_private_items](https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items) + + diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index ec7f1dd0d846..420214d9256d 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -1,5 +1,6 @@ use crate::clippy_project_root; use indoc::{formatdoc, writedoc}; +use std::fmt; use std::fmt::Write as _; use std::fs::{self, OpenOptions}; use std::io::prelude::*; @@ -256,7 +257,7 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String { ) }); - let _ = write!(result, "{}", get_lint_declaration(&name_upper, category)); + let _: fmt::Result = write!(result, "{}", get_lint_declaration(&name_upper, category)); result.push_str(&if enable_msrv { formatdoc!( @@ -353,7 +354,7 @@ fn create_lint_for_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::R let mut lint_file_contents = String::new(); if enable_msrv { - let _ = writedoc!( + let _: fmt::Result = writedoc!( lint_file_contents, r#" use clippy_utils::msrvs::{{self, Msrv}}; @@ -373,7 +374,7 @@ fn create_lint_for_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::R name_upper = name_upper, ); } else { - let _ = writedoc!( + let _: fmt::Result = writedoc!( lint_file_contents, r#" use rustc_lint::{{{context_import}, LintContext}}; @@ -521,7 +522,7 @@ fn setup_mod_file(path: &Path, lint: &LintData<'_>) -> io::Result<&'static str> .chain(std::iter::once(&*lint_name_upper)) .filter(|s| !s.is_empty()) { - let _ = write!(new_arr_content, "\n {ident},"); + let _: fmt::Result = write!(new_arr_content, "\n {ident},"); } new_arr_content.push('\n'); diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index 837618c9294b..779e4d0e1e30 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -5,7 +5,7 @@ use itertools::Itertools; use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind}; use std::collections::{HashMap, HashSet}; use std::ffi::OsStr; -use std::fmt::Write; +use std::fmt::{self, Write}; use std::fs::{self, OpenOptions}; use std::io::{self, Read, Seek, SeekFrom, Write as _}; use std::ops::Range; @@ -691,7 +691,7 @@ fn gen_deprecated(lints: &[DeprecatedLint]) -> String { let mut output = GENERATED_FILE_COMMENT.to_string(); output.push_str("{\n"); for lint in lints { - let _ = write!( + let _: fmt::Result = write!( output, concat!( " store.register_removed(\n", @@ -726,7 +726,7 @@ fn gen_declared_lints<'a>( if !is_public { output.push_str(" #[cfg(feature = \"internal\")]\n"); } - let _ = writeln!(output, " crate::{module_name}::{lint_name}_INFO,"); + let _: fmt::Result = writeln!(output, " crate::{module_name}::{lint_name}_INFO,"); } output.push_str("];\n"); diff --git a/clippy_lints/src/box_default.rs b/clippy_lints/src/box_default.rs index 9d98a6bab710..dfa949d1af2f 100644 --- a/clippy_lints/src/box_default.rs +++ b/clippy_lints/src/box_default.rs @@ -117,7 +117,8 @@ fn given_type(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { ) => { if let Some(index) = args.iter().position(|arg| arg.hir_id == expr.hir_id) && let Some(sig) = expr_sig(cx, path) && - let Some(input) = sig.input(index) + let Some(input) = sig.input(index) && + !cx.typeck_results().expr_ty_adjusted(expr).boxed_ty().is_trait() { input.no_bound_vars().is_some() } else { diff --git a/clippy_lints/src/casts/cast_possible_truncation.rs b/clippy_lints/src/casts/cast_possible_truncation.rs index f3f8b8d87982..823970e35abb 100644 --- a/clippy_lints/src/casts/cast_possible_truncation.rs +++ b/clippy_lints/src/casts/cast_possible_truncation.rs @@ -168,7 +168,7 @@ pub(super) fn check( let suggestion = format!("{cast_to_snip}::try_from({name_of_cast_from})"); span_lint_and_then(cx, CAST_POSSIBLE_TRUNCATION, expr.span, &msg, |diag| { - diag.help("if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ..."); + diag.help("if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ..."); diag.span_suggestion_with_style( expr.span, "... or use `try_from` and handle the error accordingly", diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 457a25826e79..cd5dd7a57065 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -179,6 +179,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::from_raw_with_void_ptr::FROM_RAW_WITH_VOID_PTR_INFO, crate::from_str_radix_10::FROM_STR_RADIX_10_INFO, crate::functions::DOUBLE_MUST_USE_INFO, + crate::functions::IMPL_TRAIT_IN_PARAMS_INFO, crate::functions::MISNAMED_GETTERS_INFO, crate::functions::MUST_USE_CANDIDATE_INFO, crate::functions::MUST_USE_UNIT_INFO, @@ -224,6 +225,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::let_underscore::LET_UNDERSCORE_FUTURE_INFO, crate::let_underscore::LET_UNDERSCORE_LOCK_INFO, crate::let_underscore::LET_UNDERSCORE_MUST_USE_INFO, + crate::let_underscore::LET_UNDERSCORE_UNTYPED_INFO, crate::lifetimes::EXTRA_UNUSED_LIFETIMES_INFO, crate::lifetimes::NEEDLESS_LIFETIMES_INFO, crate::literal_representation::DECIMAL_LITERAL_REPRESENTATION_INFO, @@ -378,6 +380,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::methods::SKIP_WHILE_NEXT_INFO, crate::methods::STABLE_SORT_PRIMITIVE_INFO, crate::methods::STRING_EXTEND_CHARS_INFO, + crate::methods::SUSPICIOUS_COMMAND_ARG_SPACE_INFO, crate::methods::SUSPICIOUS_MAP_INFO, crate::methods::SUSPICIOUS_SPLITN_INFO, crate::methods::SUSPICIOUS_TO_OWNED_INFO, @@ -447,6 +450,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::no_effect::NO_EFFECT_INFO, crate::no_effect::NO_EFFECT_UNDERSCORE_BINDING_INFO, crate::no_effect::UNNECESSARY_OPERATION_INFO, + crate::no_mangle_with_rust_abi::NO_MANGLE_WITH_RUST_ABI_INFO, crate::non_copy_const::BORROW_INTERIOR_MUTABLE_CONST_INFO, crate::non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST_INFO, crate::non_expressive_names::JUST_UNDERSCORES_AND_DIGITS_INFO, @@ -506,6 +510,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::ptr_offset_with_cast::PTR_OFFSET_WITH_CAST_INFO, crate::pub_use::PUB_USE_INFO, crate::question_mark::QUESTION_MARK_INFO, + crate::question_mark_used::QUESTION_MARK_USED_INFO, crate::ranges::MANUAL_RANGE_CONTAINS_INFO, crate::ranges::RANGE_MINUS_ONE_INFO, crate::ranges::RANGE_PLUS_ONE_INFO, @@ -536,6 +541,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::shadow::SHADOW_REUSE_INFO, crate::shadow::SHADOW_SAME_INFO, crate::shadow::SHADOW_UNRELATED_INFO, + crate::significant_drop_tightening::SIGNIFICANT_DROP_TIGHTENING_INFO, crate::single_char_lifetime_names::SINGLE_CHAR_LIFETIME_NAMES_INFO, crate::single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS_INFO, crate::size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT_INFO, @@ -573,6 +579,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::transmute::TRANSMUTE_INT_TO_BOOL_INFO, crate::transmute::TRANSMUTE_INT_TO_CHAR_INFO, crate::transmute::TRANSMUTE_INT_TO_FLOAT_INFO, + crate::transmute::TRANSMUTE_INT_TO_NON_ZERO_INFO, crate::transmute::TRANSMUTE_NULL_TO_FN_INFO, crate::transmute::TRANSMUTE_NUM_TO_BYTES_INFO, crate::transmute::TRANSMUTE_PTR_TO_PTR_INFO, diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index ef46e23123b9..5246a86cf849 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -3,7 +3,7 @@ use clippy_utils::mir::{enclosing_mir, expr_local, local_assignments, used_exact use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::has_enclosing_paren; -use clippy_utils::ty::{expr_sig, is_copy, peel_mid_ty_refs, ty_sig, variant_of_res}; +use clippy_utils::ty::{adt_and_variant_of_res, expr_sig, is_copy, peel_mid_ty_refs, ty_sig}; use clippy_utils::{ fn_def_id, get_parent_expr, get_parent_expr_for_hir, is_lint_allowed, path_to_local, walk_to_expr_usage, }; @@ -26,8 +26,8 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::mir::{Rvalue, StatementKind}; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability}; use rustc_middle::ty::{ - self, Binder, BoundVariableKind, Clause, EarlyBinder, FnSig, GenericArgKind, List, ParamTy, PredicateKind, - ProjectionPredicate, Ty, TyCtxt, TypeVisitableExt, TypeckResults, + self, Binder, BoundVariableKind, Clause, EarlyBinder, FnSig, GenericArgKind, List, ParamEnv, ParamTy, + PredicateKind, ProjectionPredicate, Ty, TyCtxt, TypeVisitableExt, TypeckResults, }; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::{symbol::sym, Span, Symbol}; @@ -736,7 +736,7 @@ fn walk_parents<'tcx>( .. }) if span.ctxt() == ctxt => { let ty = cx.tcx.type_of(owner_id.def_id).subst_identity(); - Some(ty_auto_deref_stability(cx, ty, precedence).position_for_result(cx)) + Some(ty_auto_deref_stability(cx.tcx, cx.param_env, ty, precedence).position_for_result(cx)) }, Node::Item(&Item { @@ -760,7 +760,7 @@ fn walk_parents<'tcx>( let output = cx .tcx .erase_late_bound_regions(cx.tcx.fn_sig(owner_id).subst_identity().output()); - Some(ty_auto_deref_stability(cx, output, precedence).position_for_result(cx)) + Some(ty_auto_deref_stability(cx.tcx, cx.param_env, output, precedence).position_for_result(cx)) }, Node::ExprField(field) if field.span.ctxt() == ctxt => match get_parent_expr_for_hir(cx, field.hir_id) { @@ -768,10 +768,23 @@ fn walk_parents<'tcx>( hir_id, kind: ExprKind::Struct(path, ..), .. - }) => variant_of_res(cx, cx.qpath_res(path, *hir_id)) - .and_then(|variant| variant.fields.iter().find(|f| f.name == field.ident.name)) - .map(|field_def| { - ty_auto_deref_stability(cx, cx.tcx.type_of(field_def.did).subst_identity(), precedence).position_for_arg() + }) => adt_and_variant_of_res(cx, cx.qpath_res(path, *hir_id)) + .and_then(|(adt, variant)| { + variant + .fields + .iter() + .find(|f| f.name == field.ident.name) + .map(|f| (adt, f)) + }) + .map(|(adt, field_def)| { + ty_auto_deref_stability( + cx.tcx, + // Use the param_env of the target type. + cx.tcx.param_env(adt.did()), + cx.tcx.type_of(field_def.did).subst_identity(), + precedence, + ) + .position_for_arg() }), _ => None, }, @@ -792,7 +805,7 @@ fn walk_parents<'tcx>( let output = cx .tcx .erase_late_bound_regions(cx.tcx.fn_sig(owner_id).subst_identity().output()); - ty_auto_deref_stability(cx, output, precedence).position_for_result(cx) + ty_auto_deref_stability(cx.tcx, cx.param_env, output, precedence).position_for_result(cx) }, ) }, @@ -835,15 +848,20 @@ fn walk_parents<'tcx>( msrv, ) } else { - ty_auto_deref_stability(cx, cx.tcx.erase_late_bound_regions(ty), precedence) - .position_for_arg() + ty_auto_deref_stability( + cx.tcx, + // Use the param_env of the target function. + sig.predicates_id().map_or(ParamEnv::empty(), |id| cx.tcx.param_env(id)), + cx.tcx.erase_late_bound_regions(ty), + precedence + ).position_for_arg() } }, } }) }), ExprKind::MethodCall(method, receiver, args, _) => { - let id = cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap(); + let fn_id = cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap(); if receiver.hir_id == child_id { // Check for calls to trait methods where the trait is implemented on a reference. // Two cases need to be handled: @@ -852,13 +870,17 @@ fn walk_parents<'tcx>( // priority. if e.hir_id != child_id { return Some(Position::ReborrowStable(precedence)) - } else if let Some(trait_id) = cx.tcx.trait_of_item(id) + } else if let Some(trait_id) = cx.tcx.trait_of_item(fn_id) && let arg_ty = cx.tcx.erase_regions(cx.typeck_results().expr_ty_adjusted(e)) && let ty::Ref(_, sub_ty, _) = *arg_ty.kind() && let subs = cx .typeck_results() .node_substs_opt(parent.hir_id).map(|subs| &subs[1..]).unwrap_or_default() - && let impl_ty = if cx.tcx.fn_sig(id).subst_identity().skip_binder().inputs()[0].is_ref() { + && let impl_ty = if cx.tcx.fn_sig(fn_id) + .subst_identity() + .skip_binder() + .inputs()[0].is_ref() + { // Trait methods taking `&self` sub_ty } else { @@ -879,10 +901,13 @@ fn walk_parents<'tcx>( return Some(Position::MethodReceiver); } args.iter().position(|arg| arg.hir_id == child_id).map(|i| { - let ty = cx.tcx.fn_sig(id).subst_identity().skip_binder().inputs()[i + 1]; + let ty = cx.tcx.fn_sig(fn_id).subst_identity().input(i + 1); // `e.hir_id == child_id` for https://github.com/rust-lang/rust-clippy/issues/9739 // `method.args.is_none()` for https://github.com/rust-lang/rust-clippy/issues/9782 - if e.hir_id == child_id && method.args.is_none() && let ty::Param(param_ty) = ty.kind() { + if e.hir_id == child_id + && method.args.is_none() + && let ty::Param(param_ty) = ty.skip_binder().kind() + { needless_borrow_impl_arg_position( cx, possible_borrowers, @@ -895,8 +920,10 @@ fn walk_parents<'tcx>( ) } else { ty_auto_deref_stability( - cx, - cx.tcx.erase_late_bound_regions(cx.tcx.fn_sig(id).subst_identity().input(i + 1)), + cx.tcx, + // Use the param_env of the target function. + cx.tcx.param_env(fn_id), + cx.tcx.erase_late_bound_regions(ty), precedence, ) .position_for_arg() @@ -1378,11 +1405,18 @@ impl<'tcx> TyPosition<'tcx> { } // Checks whether a type is stable when switching to auto dereferencing, -fn ty_auto_deref_stability<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, precedence: i8) -> TyPosition<'tcx> { +fn ty_auto_deref_stability<'tcx>( + tcx: TyCtxt<'tcx>, + param_env: ParamEnv<'tcx>, + ty: Ty<'tcx>, + precedence: i8, +) -> TyPosition<'tcx> { let ty::Ref(_, mut ty, _) = *ty.kind() else { return Position::Other(precedence).into(); }; + ty = tcx.try_normalize_erasing_regions(param_env, ty).unwrap_or(ty); + loop { break match *ty.kind() { ty::Ref(_, ref_ty, _) => { @@ -1423,9 +1457,7 @@ fn ty_auto_deref_stability<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, precedenc | ty::Closure(..) | ty::Never | ty::Tuple(_) - | ty::Alias(ty::Projection, _) => { - Position::DerefStable(precedence, ty.is_sized(cx.tcx, cx.param_env.without_caller_bounds())).into() - }, + | ty::Alias(ty::Projection, _) => Position::DerefStable(precedence, ty.is_sized(tcx, param_env)).into(), }; } } diff --git a/clippy_lints/src/doc.rs b/clippy_lints/src/doc.rs index 6fdb7de25ccc..384aca7feadd 100644 --- a/clippy_lints/src/doc.rs +++ b/clippy_lints/src/doc.rs @@ -6,6 +6,11 @@ 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 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::{BrokenLink, CodeBlockKind, CowStr, Options}; use rustc_ast::ast::{Async, AttrKind, Attribute, Fn, FnRetTy, ItemKind}; use rustc_ast::token::CommentKind; use rustc_data_structures::fx::FxHashSet; @@ -497,7 +502,6 @@ struct DocHeaders { } fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet, attrs: &[Attribute]) -> Option { - 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. @@ -538,8 +542,6 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet, attrs: &[ 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; @@ -564,12 +566,6 @@ fn check_doc<'a, Events: Iterator, Range 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::default(); let mut in_code = false; let mut in_link = None; @@ -660,6 +656,12 @@ fn check_doc<'a, Events: Iterator, Range, text: &str, edition: Edition, span: Span) { let filename = FileName::anon_source_code(&code); let sm = Lrc::new(SourceMap::new(FilePathMapping::empty())); - let fallback_bundle = rustc_errors::fallback_fluent_bundle( - rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(), - false - ); + let fallback_bundle = + rustc_errors::fallback_fluent_bundle(rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(), false); let emitter = EmitterWriter::new( Box::new(io::sink()), None, diff --git a/clippy_lints/src/entry.rs b/clippy_lints/src/entry.rs index b44e62435881..48a54f60253c 100644 --- a/clippy_lints/src/entry.rs +++ b/clippy_lints/src/entry.rs @@ -6,7 +6,7 @@ use clippy_utils::{ source::{reindent_multiline, snippet_indent, snippet_with_applicability, snippet_with_context}, SpanlessEq, }; -use core::fmt::Write; +use core::fmt::{self, Write}; use rustc_errors::Applicability; use rustc_hir::{ hir_id::HirIdSet, @@ -65,6 +65,10 @@ declare_lint_pass!(HashMapPass => [MAP_ENTRY]); impl<'tcx> LateLintPass<'tcx> for HashMapPass { #[expect(clippy::too_many_lines)] fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if expr.span.from_expansion() { + return; + } + let Some(higher::If { cond: cond_expr, then: then_expr, r#else: else_expr }) = higher::If::hir(expr) else { return }; @@ -532,7 +536,7 @@ impl<'tcx> InsertSearchResults<'tcx> { if is_expr_used_or_unified(cx.tcx, insertion.call) { write_wrapped(&mut res, insertion, ctxt, app); } else { - let _ = write!( + let _: fmt::Result = write!( res, "e.insert({})", snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0 @@ -548,7 +552,7 @@ impl<'tcx> InsertSearchResults<'tcx> { ( 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!( + let _: fmt::Result = write!( res, "Some(e.insert({}))", snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0 @@ -562,7 +566,7 @@ impl<'tcx> InsertSearchResults<'tcx> { ( 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) { + let _: fmt::Result = if is_expr_final_block_expr(cx.tcx, insertion.call) { write!( res, "e.insert({});\n{}None", diff --git a/clippy_lints/src/extra_unused_type_parameters.rs b/clippy_lints/src/extra_unused_type_parameters.rs index 2fdd8a71466c..20565e1d232e 100644 --- a/clippy_lints/src/extra_unused_type_parameters.rs +++ b/clippy_lints/src/extra_unused_type_parameters.rs @@ -4,12 +4,17 @@ use rustc_data_structures::fx::FxHashMap; use rustc_errors::MultiSpan; use rustc_hir::intravisit::{walk_impl_item, walk_item, walk_param_bound, walk_ty, Visitor}; use rustc_hir::{ - GenericParamKind, Generics, ImplItem, ImplItemKind, Item, ItemKind, PredicateOrigin, Ty, TyKind, WherePredicate, + BodyId, ExprKind, GenericBound, GenericParamKind, Generics, ImplItem, ImplItemKind, Item, ItemKind, + PredicateOrigin, Ty, TyKind, WherePredicate, }; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::hir::nested_filter; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::{def_id::DefId, Span}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::{ + def_id::{DefId, LocalDefId}, + Span, +}; declare_clippy_lint! { /// ### What it does @@ -21,7 +26,6 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // unused type parameters /// fn unused_ty(x: u8) { /// // .. /// } @@ -37,13 +41,35 @@ declare_clippy_lint! { complexity, "unused type parameters in function definitions" } -declare_lint_pass!(ExtraUnusedTypeParameters => [EXTRA_UNUSED_TYPE_PARAMETERS]); + +pub struct ExtraUnusedTypeParameters { + avoid_breaking_exported_api: bool, +} + +impl ExtraUnusedTypeParameters { + pub fn new(avoid_breaking_exported_api: bool) -> Self { + Self { + avoid_breaking_exported_api, + } + } + + /// Don't lint external macros or functions with empty bodies. Also, don't lint public items if + /// the `avoid_breaking_exported_api` config option is set. + fn check_false_positive(&self, cx: &LateContext<'_>, span: Span, def_id: LocalDefId, body_id: BodyId) -> bool { + let body = cx.tcx.hir().body(body_id).value; + let fn_empty = matches!(&body.kind, ExprKind::Block(blk, None) if blk.stmts.is_empty() && blk.expr.is_none()); + let is_exported = cx.effective_visibilities.is_exported(def_id); + in_external_macro(cx.sess(), span) || (self.avoid_breaking_exported_api && is_exported) || fn_empty + } +} + +impl_lint_pass!(ExtraUnusedTypeParameters => [EXTRA_UNUSED_TYPE_PARAMETERS]); /// A visitor struct that walks a given function and gathers generic type parameters, plus any /// trait bounds those parameters have. struct TypeWalker<'cx, 'tcx> { cx: &'cx LateContext<'tcx>, - /// Collection of all the type parameters and their spans. + /// Collection of all the function's type parameters. ty_params: FxHashMap, /// Collection of any (inline) trait bounds corresponding to each type parameter. bounds: FxHashMap, @@ -64,8 +90,8 @@ impl<'cx, 'tcx> TypeWalker<'cx, 'tcx> { .params .iter() .filter_map(|param| { - if let GenericParamKind::Type { .. } = param.kind { - Some((param.def_id.into(), param.span)) + if let GenericParamKind::Type { synthetic, .. } = param.kind { + (!synthetic).then_some((param.def_id.into(), param.span)) } else { if !param.is_elided_lifetime() { all_params_unused = false; @@ -74,6 +100,7 @@ impl<'cx, 'tcx> TypeWalker<'cx, 'tcx> { } }) .collect(); + Self { cx, ty_params, @@ -83,6 +110,12 @@ impl<'cx, 'tcx> TypeWalker<'cx, 'tcx> { } } + fn mark_param_used(&mut self, def_id: DefId) { + if self.ty_params.remove(&def_id).is_some() { + self.all_params_unused = false; + } + } + fn emit_lint(&self) { let (msg, help) = match self.ty_params.len() { 0 => return, @@ -96,7 +129,7 @@ impl<'cx, 'tcx> TypeWalker<'cx, 'tcx> { ), }; - let source_map = self.cx.tcx.sess.source_map(); + let source_map = self.cx.sess().source_map(); let span = if self.all_params_unused { self.generics.span.into() // Remove the entire list of generics } else { @@ -118,14 +151,18 @@ impl<'cx, 'tcx> TypeWalker<'cx, 'tcx> { } } +/// Given a generic bound, if the bound is for a trait that's not a `LangItem`, return the +/// `LocalDefId` for that trait. +fn bound_to_trait_def_id(bound: &GenericBound<'_>) -> Option { + bound.trait_ref()?.trait_def_id()?.as_local() +} + impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> { type NestedFilter = nested_filter::OnlyBodies; fn visit_ty(&mut self, t: &'tcx Ty<'tcx>) { if let Some((def_id, _)) = t.peel_refs().as_generic_param() { - if self.ty_params.remove(&def_id).is_some() { - self.all_params_unused = false; - } + self.mark_param_used(def_id); } else if let TyKind::OpaqueDef(id, _, _) = t.kind { // Explicitly walk OpaqueDef. Normally `walk_ty` would do the job, but it calls // `visit_nested_item`, which checks that `Self::NestedFilter::INTER` is set. We're @@ -139,12 +176,21 @@ impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> { fn visit_where_predicate(&mut self, predicate: &'tcx WherePredicate<'tcx>) { if let WherePredicate::BoundPredicate(predicate) = predicate { - // Collect spans for bounds that appear in the list of generics (not in a where-clause) - // for use in forming the help message - if let Some((def_id, _)) = predicate.bounded_ty.peel_refs().as_generic_param() - && let PredicateOrigin::GenericParam = predicate.origin - { - self.bounds.insert(def_id, predicate.span); + // Collect spans for any bounds on type parameters. We only keep bounds that appear in + // the list of generics (not in a where-clause). + if let Some((def_id, _)) = predicate.bounded_ty.peel_refs().as_generic_param() { + // If the bound contains non-public traits, err on the safe side and don't lint the + // corresponding parameter. + if !predicate + .bounds + .iter() + .filter_map(bound_to_trait_def_id) + .all(|id| self.cx.effective_visibilities.is_exported(id)) + { + self.mark_param_used(def_id); + } else if let PredicateOrigin::GenericParam = predicate.origin { + self.bounds.insert(def_id, predicate.span); + } } // Only walk the right-hand side of where-bounds for bound in predicate.bounds { @@ -160,7 +206,9 @@ impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> { impl<'tcx> LateLintPass<'tcx> for ExtraUnusedTypeParameters { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { - if let ItemKind::Fn(_, generics, _) = item.kind { + if let ItemKind::Fn(_, generics, body_id) = item.kind + && !self.check_false_positive(cx, item.span, item.owner_id.def_id, body_id) + { let mut walker = TypeWalker::new(cx, generics); walk_item(&mut walker, item); walker.emit_lint(); @@ -169,7 +217,10 @@ impl<'tcx> LateLintPass<'tcx> for ExtraUnusedTypeParameters { fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'tcx>) { // Only lint on inherent methods, not trait methods. - if let ImplItemKind::Fn(..) = item.kind && trait_ref_of_method(cx, item.owner_id.def_id).is_none() { + if let ImplItemKind::Fn(.., body_id) = item.kind + && trait_ref_of_method(cx, item.owner_id.def_id).is_none() + && !self.check_false_positive(cx, item.span, item.owner_id.def_id, body_id) + { let mut walker = TypeWalker::new(cx, item.generics); walk_impl_item(&mut walker, item); walker.emit_lint(); diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs index ea26b96ee074..c511d85e9cf2 100644 --- a/clippy_lints/src/format_args.rs +++ b/clippy_lints/src/format_args.rs @@ -340,6 +340,7 @@ fn check_one_arg( if matches!(param.kind, Implicit | Starred | Named(_) | Numbered) && let ExprKind::Path(QPath::Resolved(None, path)) = param.value.kind && let [segment] = path.segments + && segment.args.is_none() && let Some(arg_span) = args.value_with_prev_comma_span(param.value.hir_id) { let replacement = match param.usage { diff --git a/clippy_lints/src/functions/impl_trait_in_params.rs b/clippy_lints/src/functions/impl_trait_in_params.rs new file mode 100644 index 000000000000..2811a73f6c18 --- /dev/null +++ b/clippy_lints/src/functions/impl_trait_in_params.rs @@ -0,0 +1,50 @@ +use clippy_utils::{diagnostics::span_lint_and_then, is_in_test_function}; + +use rustc_hir::{intravisit::FnKind, Body, HirId}; +use rustc_lint::LateContext; +use rustc_span::Span; + +use super::IMPL_TRAIT_IN_PARAMS; + +pub(super) fn check_fn<'tcx>(cx: &LateContext<'_>, kind: &'tcx FnKind<'_>, body: &'tcx Body<'_>, hir_id: HirId) { + if cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public() && !is_in_test_function(cx.tcx, hir_id) + { + if let FnKind::ItemFn(ident, generics, _) = kind { + for param in generics.params { + if param.is_impl_trait() { + // No generics with nested generics, and no generics like FnMut(x) + span_lint_and_then( + cx, + IMPL_TRAIT_IN_PARAMS, + param.span, + "'`impl Trait` used as a function parameter'", + |diag| { + if let Some(gen_span) = generics.span_for_param_suggestion() { + diag.span_suggestion_with_style( + gen_span, + "add a type paremeter", + format!(", {{ /* Generic name */ }}: {}", ¶m.name.ident().as_str()[5..]), + rustc_errors::Applicability::HasPlaceholders, + rustc_errors::SuggestionStyle::ShowAlways, + ); + } else { + diag.span_suggestion_with_style( + Span::new( + body.params[0].span.lo() - rustc_span::BytePos(1), + ident.span.hi(), + ident.span.ctxt(), + ident.span.parent(), + ), + "add a type paremeter", + format!("<{{ /* Generic name */ }}: {}>", ¶m.name.ident().as_str()[5..]), + rustc_errors::Applicability::HasPlaceholders, + rustc_errors::SuggestionStyle::ShowAlways, + ); + } + }, + ); + } + } + } + } +} diff --git a/clippy_lints/src/functions/mod.rs b/clippy_lints/src/functions/mod.rs index 4399c68e130f..d2852b4acad1 100644 --- a/clippy_lints/src/functions/mod.rs +++ b/clippy_lints/src/functions/mod.rs @@ -1,3 +1,4 @@ +mod impl_trait_in_params; mod misnamed_getters; mod must_use; mod not_unsafe_ptr_arg_deref; @@ -327,6 +328,32 @@ declare_clippy_lint! { "getter method returning the wrong field" } +declare_clippy_lint! { + /// ### What it does + /// Lints when `impl Trait` is being used in a function's paremeters. + /// ### Why is this bad? + /// Turbofish syntax (`::<>`) cannot be used when `impl Trait` is being used, making `impl Trait` less powerful. Readability may also be a factor. + /// + /// ### Example + /// ```rust + /// trait MyTrait {} + /// fn foo(a: impl MyTrait) { + /// // [...] + /// } + /// ``` + /// Use instead: + /// ```rust + /// trait MyTrait {} + /// fn foo(a: T) { + /// // [...] + /// } + /// ``` + #[clippy::version = "1.68.0"] + pub IMPL_TRAIT_IN_PARAMS, + restriction, + "`impl Trait` is used in the function's parameters" +} + #[derive(Copy, Clone)] pub struct Functions { too_many_arguments_threshold: u64, @@ -354,6 +381,7 @@ impl_lint_pass!(Functions => [ RESULT_UNIT_ERR, RESULT_LARGE_ERR, MISNAMED_GETTERS, + IMPL_TRAIT_IN_PARAMS, ]); impl<'tcx> LateLintPass<'tcx> for Functions { @@ -371,6 +399,7 @@ impl<'tcx> LateLintPass<'tcx> for Functions { too_many_lines::check_fn(cx, kind, span, body, self.too_many_lines_threshold); not_unsafe_ptr_arg_deref::check_fn(cx, kind, decl, body, def_id); misnamed_getters::check_fn(cx, kind, decl, body, span); + impl_trait_in_params::check_fn(cx, &kind, body, hir_id); } fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { diff --git a/clippy_lints/src/inconsistent_struct_constructor.rs b/clippy_lints/src/inconsistent_struct_constructor.rs index e2f2d3d42e69..1ad886f2cf35 100644 --- a/clippy_lints/src/inconsistent_struct_constructor.rs +++ b/clippy_lints/src/inconsistent_struct_constructor.rs @@ -7,7 +7,7 @@ use rustc_hir::{self as hir, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::symbol::Symbol; -use std::fmt::Write as _; +use std::fmt::{self, Write as _}; declare_clippy_lint! { /// ### What it does @@ -90,7 +90,7 @@ impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor { let mut fields_snippet = String::new(); let (last_ident, idents) = ordered_fields.split_last().unwrap(); for ident in idents { - let _ = write!(fields_snippet, "{ident}, "); + let _: fmt::Result = write!(fields_snippet, "{ident}, "); } fields_snippet.push_str(&last_ident.to_string()); diff --git a/clippy_lints/src/let_underscore.rs b/clippy_lints/src/let_underscore.rs index f8e359509808..7600777fab97 100644 --- a/clippy_lints/src/let_underscore.rs +++ b/clippy_lints/src/let_underscore.rs @@ -90,7 +90,45 @@ declare_clippy_lint! { "non-binding `let` on a future" } -declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_MUST_USE, LET_UNDERSCORE_LOCK, LET_UNDERSCORE_FUTURE]); +declare_clippy_lint! { + /// ### What it does + /// Checks for `let _ = ` without a type annotation, and suggests to either provide one, + /// or remove the `let` keyword altogether. + /// + /// ### Why is this bad? + /// The `let _ = ` expression ignores the value of `` but will remain doing so even + /// if the type were to change, thus potentially introducing subtle bugs. By supplying a type + /// annotation, one will be forced to re-visit the decision to ignore the value in such cases. + /// + /// ### Known problems + /// The `_ = ` is not properly supported by some tools (e.g. IntelliJ) and may seem odd + /// to many developers. This lint also partially overlaps with the other `let_underscore_*` + /// lints. + /// + /// ### Example + /// ```rust + /// fn foo() -> Result { + /// Ok(123) + /// } + /// let _ = foo(); + /// ``` + /// Use instead: + /// ```rust + /// fn foo() -> Result { + /// Ok(123) + /// } + /// // Either provide a type annotation: + /// let _: Result = foo(); + /// // …or drop the let keyword: + /// _ = foo(); + /// ``` + #[clippy::version = "1.69.0"] + pub LET_UNDERSCORE_UNTYPED, + pedantic, + "non-binding `let` without a type annotation" +} + +declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_MUST_USE, LET_UNDERSCORE_LOCK, LET_UNDERSCORE_FUTURE, LET_UNDERSCORE_UNTYPED]); const SYNC_GUARD_PATHS: [&[&str]; 3] = [ &paths::PARKING_LOT_MUTEX_GUARD, @@ -148,6 +186,18 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore { "consider explicitly using function result", ); } + + if local.pat.default_binding_modes && local.ty.is_none() { + // When `default_binding_modes` is true, the `let` keyword is present. + span_lint_and_help( + cx, + LET_UNDERSCORE_UNTYPED, + local.span, + "non-binding `let` without a type annotation", + None, + "consider adding a type annotation or removing the `let` keyword", + ); + } } } } diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 9011f0896a05..145cf524652f 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -2,6 +2,7 @@ #![feature(binary_heap_into_iter_sorted)] #![feature(box_patterns)] #![feature(drain_filter)] +#![feature(if_let_guard)] #![feature(iter_intersperse)] #![feature(let_chains)] #![feature(lint_reasons)] @@ -219,6 +220,7 @@ mod neg_cmp_op_on_partial_ord; mod neg_multiply; mod new_without_default; mod no_effect; +mod no_mangle_with_rust_abi; mod non_copy_const; mod non_expressive_names; mod non_octal_unix_permissions; @@ -243,6 +245,7 @@ mod ptr; mod ptr_offset_with_cast; mod pub_use; mod question_mark; +mod question_mark_used; mod ranges; mod rc_clone_in_vec_init; mod read_zero_byte_vec; @@ -264,6 +267,7 @@ mod semicolon_block; mod semicolon_if_nothing_returned; mod serde_api; mod shadow; +mod significant_drop_tightening; mod single_char_lifetime_names; mod single_component_path_imports; mod size_of_in_element_count; @@ -559,6 +563,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|_| Box::new(eta_reduction::EtaReduction)); store.register_late_pass(|_| Box::new(mut_mut::MutMut)); store.register_late_pass(|_| Box::new(mut_reference::UnnecessaryMutPassed)); + store.register_late_pass(|_| Box::>::default()); 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)); @@ -665,12 +670,13 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: )) }); let doc_valid_idents = conf.doc_valid_idents.iter().cloned().collect::>(); + let missing_docs_in_crate_items = conf.missing_docs_in_crate_items; 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(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(move |_| Box::new(missing_doc::MissingDoc::new(missing_docs_in_crate_items))); 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)); @@ -694,6 +700,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: 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(question_mark::QuestionMark)); + store.register_late_pass(|_| Box::new(question_mark_used::QuestionMarkUsed)); 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)); @@ -911,7 +918,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|_| Box::new(permissions_set_readonly_false::PermissionsSetReadonlyFalse)); store.register_late_pass(|_| Box::new(size_of_ref::SizeOfRef)); store.register_late_pass(|_| Box::new(multiple_unsafe_ops_per_block::MultipleUnsafeOpsPerBlock)); - store.register_late_pass(|_| Box::new(extra_unused_type_parameters::ExtraUnusedTypeParameters)); + store.register_late_pass(move |_| { + Box::new(extra_unused_type_parameters::ExtraUnusedTypeParameters::new( + avoid_breaking_exported_api, + )) + }); + store.register_late_pass(|_| Box::new(no_mangle_with_rust_abi::NoMangleWithRustAbi)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/lifetimes.rs b/clippy_lints/src/lifetimes.rs index 43a1a65a43a9..986ffcad883d 100644 --- a/clippy_lints/src/lifetimes.rs +++ b/clippy_lints/src/lifetimes.rs @@ -144,6 +144,10 @@ fn check_fn_inner<'tcx>( .filter(|param| matches!(param.kind, GenericParamKind::Type { .. })); for typ in types { + if !typ.span.eq_ctxt(span) { + return; + } + for pred in generics.bounds_for_param(typ.def_id) { if pred.origin == PredicateOrigin::WhereClause { // has_where_lifetimes checked that this predicate contains no lifetime. @@ -181,6 +185,10 @@ fn check_fn_inner<'tcx>( } if let Some((elidable_lts, usages)) = could_use_elision(cx, sig.decl, body, trait_sig, generics.params) { + if usages.iter().any(|usage| !usage.ident.span.eq_ctxt(span)) { + return; + } + let lts = elidable_lts .iter() // In principle, the result of the call to `Node::ident` could be `unwrap`ped, as `DefId` should refer to a diff --git a/clippy_lints/src/literal_representation.rs b/clippy_lints/src/literal_representation.rs index 3a7b7835c990..dadcd9c5135c 100644 --- a/clippy_lints/src/literal_representation.rs +++ b/clippy_lints/src/literal_representation.rs @@ -210,7 +210,7 @@ impl WarningType { cx, UNUSUAL_BYTE_GROUPINGS, span, - "digits of hex or binary literal not grouped by four", + "digits of hex, binary or octal literal not in groups of equal size", "consider", suggested_format, Applicability::MachineApplicable, @@ -427,8 +427,12 @@ impl LiteralDigitGrouping { 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 radix == Radix::Binary || radix == Radix::Octal || radix == Radix::Hexadecimal { + if let Some(second_size) = groups.next() { + if !groups.all(|i| i == second_size) || first > second_size { + return Err(WarningType::UnusualByteGroupings); + } + } } if let Some(second) = groups.next() { @@ -484,7 +488,7 @@ impl DecimalLiteralRepresentation { then { let hex = format!("{val:#X}"); let num_lit = NumericLiteral::new(&hex, num_lit.suffix, false); - let _ = Self::do_lint(num_lit.integer).map_err(|warning_type| { + let _: Result<(), ()> = Self::do_lint(num_lit.integer).map_err(|warning_type| { warning_type.display(num_lit.format(), cx, span); }); } diff --git a/clippy_lints/src/loops/never_loop.rs b/clippy_lints/src/loops/never_loop.rs index 14f161f51026..ea7630ce56dd 100644 --- a/clippy_lints/src/loops/never_loop.rs +++ b/clippy_lints/src/loops/never_loop.rs @@ -39,6 +39,7 @@ pub(super) fn check( }); }, NeverLoopResult::MayContinueMainLoop | NeverLoopResult::Otherwise => (), + NeverLoopResult::IgnoreUntilEnd(_) => unreachable!(), } } @@ -48,6 +49,8 @@ enum NeverLoopResult { AlwaysBreak, // A continue may occur for the main loop. MayContinueMainLoop, + // Ignore everything until the end of the block with this id + IgnoreUntilEnd(HirId), Otherwise, } @@ -56,6 +59,7 @@ fn absorb_break(arg: NeverLoopResult) -> NeverLoopResult { match arg { NeverLoopResult::AlwaysBreak | NeverLoopResult::Otherwise => NeverLoopResult::Otherwise, NeverLoopResult::MayContinueMainLoop => NeverLoopResult::MayContinueMainLoop, + NeverLoopResult::IgnoreUntilEnd(id) => NeverLoopResult::IgnoreUntilEnd(id), } } @@ -63,27 +67,26 @@ fn absorb_break(arg: NeverLoopResult) -> NeverLoopResult { #[must_use] fn combine_seq(first: NeverLoopResult, second: NeverLoopResult) -> NeverLoopResult { match first { - NeverLoopResult::AlwaysBreak | NeverLoopResult::MayContinueMainLoop => first, - NeverLoopResult::Otherwise => second, - } -} - -// Combine two results where both parts are called but not necessarily in order. -#[must_use] -fn combine_both(left: NeverLoopResult, right: NeverLoopResult) -> NeverLoopResult { - match (left, right) { - (NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => { - NeverLoopResult::MayContinueMainLoop + NeverLoopResult::AlwaysBreak | NeverLoopResult::MayContinueMainLoop | NeverLoopResult::IgnoreUntilEnd(_) => { + first }, - (NeverLoopResult::AlwaysBreak, _) | (_, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak, - (NeverLoopResult::Otherwise, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise, + NeverLoopResult::Otherwise => second, } } // Combine two results where only one of the part may have been executed. #[must_use] -fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult { +fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult, ignore_ids: &[HirId]) -> NeverLoopResult { match (b1, b2) { + (NeverLoopResult::IgnoreUntilEnd(a), NeverLoopResult::IgnoreUntilEnd(b)) => { + if ignore_ids.iter().find(|&e| e == &a || e == &b).unwrap() == &a { + NeverLoopResult::IgnoreUntilEnd(b) + } else { + NeverLoopResult::IgnoreUntilEnd(a) + } + }, + (i @ NeverLoopResult::IgnoreUntilEnd(_), NeverLoopResult::AlwaysBreak) + | (NeverLoopResult::AlwaysBreak, i @ NeverLoopResult::IgnoreUntilEnd(_)) => i, (NeverLoopResult::AlwaysBreak, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak, (NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => { NeverLoopResult::MayContinueMainLoop @@ -103,7 +106,7 @@ fn never_loop_block(block: &Block<'_>, ignore_ids: &mut Vec, main_loop_id let e = never_loop_expr(e, ignore_ids, main_loop_id); // els is an else block in a let...else binding els.map_or(e, |els| { - combine_branches(e, never_loop_block(els, ignore_ids, main_loop_id)) + combine_branches(e, never_loop_block(els, ignore_ids, main_loop_id), ignore_ids) }) }) .fold(NeverLoopResult::Otherwise, combine_seq) @@ -139,7 +142,7 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec, main_loop_id: H ExprKind::Struct(_, fields, base) => { let fields = never_loop_expr_all(&mut fields.iter().map(|f| f.expr), ignore_ids, main_loop_id); if let Some(base) = base { - combine_both(fields, never_loop_expr(base, ignore_ids, main_loop_id)) + combine_seq(fields, never_loop_expr(base, ignore_ids, main_loop_id)) } else { fields } @@ -159,7 +162,7 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec, main_loop_id: H let e3 = e3.as_ref().map_or(NeverLoopResult::Otherwise, |e| { never_loop_expr(e, ignore_ids, main_loop_id) }); - combine_seq(e1, combine_branches(e2, e3)) + combine_seq(e1, combine_branches(e2, e3, ignore_ids)) }, ExprKind::Match(e, arms, _) => { let e = never_loop_expr(e, ignore_ids, main_loop_id); @@ -175,8 +178,13 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec, main_loop_id: H ignore_ids.push(b.hir_id); } let ret = never_loop_block(b, ignore_ids, main_loop_id); - ignore_ids.pop(); - ret + if l.is_some() { + ignore_ids.pop(); + } + match ret { + NeverLoopResult::IgnoreUntilEnd(a) if a == b.hir_id => NeverLoopResult::Otherwise, + _ => ret, + } }, ExprKind::Continue(d) => { let id = d @@ -190,8 +198,8 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec, main_loop_id: H }, // checks if break targets a block instead of a loop ExprKind::Break(Destination { target_id: Ok(t), .. }, e) if ignore_ids.contains(&t) => e - .map_or(NeverLoopResult::Otherwise, |e| { - combine_seq(never_loop_expr(e, ignore_ids, main_loop_id), NeverLoopResult::Otherwise) + .map_or(NeverLoopResult::IgnoreUntilEnd(t), |e| { + never_loop_expr(e, ignore_ids, main_loop_id) }), ExprKind::Break(_, e) | ExprKind::Ret(e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| { combine_seq( @@ -218,7 +226,7 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec, main_loop_id: H | InlineAsmOperand::SymFn { .. } | InlineAsmOperand::SymStatic { .. } => NeverLoopResult::Otherwise, }) - .fold(NeverLoopResult::Otherwise, combine_both), + .fold(NeverLoopResult::Otherwise, combine_seq), ExprKind::Yield(_, _) | ExprKind::Closure { .. } | ExprKind::Path(_) @@ -234,7 +242,7 @@ fn never_loop_expr_all<'a, T: Iterator>>( main_loop_id: HirId, ) -> NeverLoopResult { es.map(|e| never_loop_expr(e, ignore_ids, main_loop_id)) - .fold(NeverLoopResult::Otherwise, combine_both) + .fold(NeverLoopResult::Otherwise, combine_seq) } fn never_loop_expr_branch<'a, T: Iterator>>( @@ -242,8 +250,9 @@ fn never_loop_expr_branch<'a, T: Iterator>>( ignore_ids: &mut Vec, main_loop_id: HirId, ) -> NeverLoopResult { - e.map(|e| never_loop_expr(e, ignore_ids, main_loop_id)) - .fold(NeverLoopResult::AlwaysBreak, combine_branches) + e.fold(NeverLoopResult::AlwaysBreak, |a, b| { + combine_branches(a, never_loop_expr(b, ignore_ids, main_loop_id), ignore_ids) + }) } fn for_to_if_let_sugg(cx: &LateContext<'_>, iterator: &Expr<'_>, pat: &Pat<'_>) -> String { diff --git a/clippy_lints/src/manual_let_else.rs b/clippy_lints/src/manual_let_else.rs index 9c6f8b43c078..98e698c6c2a0 100644 --- a/clippy_lints/src/manual_let_else.rs +++ b/clippy_lints/src/manual_let_else.rs @@ -4,11 +4,12 @@ use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::peel_blocks; use clippy_utils::source::snippet_with_context; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::visitors::{for_each_expr, Descend}; +use clippy_utils::visitors::{Descend, Visitable}; use if_chain::if_chain; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, MatchSource, Pat, PatKind, QPath, Stmt, StmtKind}; +use rustc_hir::intravisit::{walk_expr, Visitor}; +use rustc_hir::{Expr, ExprKind, HirId, ItemId, Local, MatchSource, Pat, PatKind, QPath, Stmt, StmtKind, Ty}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_session::{declare_tool_lint, impl_lint_pass}; @@ -115,6 +116,13 @@ impl<'tcx> LateLintPass<'tcx> for ManualLetElse { .enumerate() .find(|(_, arm)| expr_diverges(cx, arm.body) && pat_allowed_for_else(cx, arm.pat, check_types)); let Some((idx, diverging_arm)) = diverging_arm_opt else { return; }; + // If the non-diverging arm is the first one, its pattern can be reused in a let/else statement. + // However, if it arrives in second position, its pattern may cover some cases already covered + // by the diverging one. + // TODO: accept the non-diverging arm as a second position if patterns are disjointed. + if idx == 0 { + return; + } let pat_arm = &arms[1 - idx]; if !expr_is_simple_identity(pat_arm.pat, pat_arm.body) { return; @@ -162,61 +170,102 @@ fn emit_manual_let_else(cx: &LateContext<'_>, span: Span, expr: &Expr<'_>, pat: ); } -fn expr_diverges(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool { - fn is_never(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool { - if let Some(ty) = cx.typeck_results().expr_ty_opt(expr) { - return ty.is_never(); - } - false +/// Check whether an expression is divergent. May give false negatives. +fn expr_diverges(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + struct V<'cx, 'tcx> { + cx: &'cx LateContext<'tcx>, + res: ControlFlow<(), Descend>, } - // We can't just call is_never on expr and be done, because the type system - // sometimes coerces the ! type to something different before we can get - // our hands on it. So instead, we do a manual search. We do fall back to - // is_never in some places when there is no better alternative. - for_each_expr(expr, |ex| { - match ex.kind { - ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => ControlFlow::Break(()), - ExprKind::Call(call, _) => { - if is_never(cx, ex) || is_never(cx, call) { - return ControlFlow::Break(()); + impl<'tcx> Visitor<'tcx> for V<'_, '_> { + fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) { + fn is_never(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool { + if let Some(ty) = cx.typeck_results().expr_ty_opt(expr) { + return ty.is_never(); } - ControlFlow::Continue(Descend::Yes) - }, - ExprKind::MethodCall(..) => { - if is_never(cx, ex) { - return ControlFlow::Break(()); - } - ControlFlow::Continue(Descend::Yes) - }, - ExprKind::If(if_expr, if_then, if_else) => { - let else_diverges = if_else.map_or(false, |ex| expr_diverges(cx, ex)); - let diverges = expr_diverges(cx, if_expr) || (else_diverges && expr_diverges(cx, if_then)); - if diverges { - return ControlFlow::Break(()); - } - ControlFlow::Continue(Descend::No) - }, - ExprKind::Match(match_expr, match_arms, _) => { - let diverges = expr_diverges(cx, match_expr) - || match_arms.iter().all(|arm| { - let guard_diverges = arm.guard.as_ref().map_or(false, |g| expr_diverges(cx, g.body())); - guard_diverges || expr_diverges(cx, arm.body) - }); - if diverges { - return ControlFlow::Break(()); - } - ControlFlow::Continue(Descend::No) - }, + false + } - // Don't continue into loops or labeled blocks, as they are breakable, - // and we'd have to start checking labels. - ExprKind::Block(_, Some(_)) | ExprKind::Loop(..) => ControlFlow::Continue(Descend::No), + if self.res.is_break() { + return; + } - // Default: descend - _ => ControlFlow::Continue(Descend::Yes), + // We can't just call is_never on expr and be done, because the type system + // sometimes coerces the ! type to something different before we can get + // our hands on it. So instead, we do a manual search. We do fall back to + // is_never in some places when there is no better alternative. + self.res = match e.kind { + ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => ControlFlow::Break(()), + ExprKind::Call(call, _) => { + if is_never(self.cx, e) || is_never(self.cx, call) { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(Descend::Yes) + } + }, + ExprKind::MethodCall(..) => { + if is_never(self.cx, e) { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(Descend::Yes) + } + }, + ExprKind::If(if_expr, if_then, if_else) => { + let else_diverges = if_else.map_or(false, |ex| expr_diverges(self.cx, ex)); + let diverges = + expr_diverges(self.cx, if_expr) || (else_diverges && expr_diverges(self.cx, if_then)); + if diverges { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(Descend::No) + } + }, + ExprKind::Match(match_expr, match_arms, _) => { + let diverges = expr_diverges(self.cx, match_expr) + || match_arms.iter().all(|arm| { + let guard_diverges = arm.guard.as_ref().map_or(false, |g| expr_diverges(self.cx, g.body())); + guard_diverges || expr_diverges(self.cx, arm.body) + }); + if diverges { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(Descend::No) + } + }, + + // Don't continue into loops or labeled blocks, as they are breakable, + // and we'd have to start checking labels. + ExprKind::Block(_, Some(_)) | ExprKind::Loop(..) => ControlFlow::Continue(Descend::No), + + // Default: descend + _ => ControlFlow::Continue(Descend::Yes), + }; + if let ControlFlow::Continue(Descend::Yes) = self.res { + walk_expr(self, e); + } } - }) - .is_some() + + fn visit_local(&mut self, local: &'tcx Local<'_>) { + // Don't visit the else block of a let/else statement as it will not make + // the statement divergent even though the else block is divergent. + if let Some(init) = local.init { + self.visit_expr(init); + } + } + + // Avoid unnecessary `walk_*` calls. + fn visit_ty(&mut self, _: &'tcx Ty<'tcx>) {} + fn visit_pat(&mut self, _: &'tcx Pat<'tcx>) {} + fn visit_qpath(&mut self, _: &'tcx QPath<'tcx>, _: HirId, _: Span) {} + // Avoid monomorphising all `visit_*` functions. + fn visit_nested_item(&mut self, _: ItemId) {} + } + + let mut v = V { + cx, + res: ControlFlow::Continue(Descend::Yes), + }; + expr.visit(&mut v); + v.res.is_break() } fn pat_allowed_for_else(cx: &LateContext<'_>, pat: &'_ Pat<'_>, check_types: bool) -> bool { diff --git a/clippy_lints/src/methods/bytes_nth.rs b/clippy_lints/src/methods/bytes_nth.rs index d512cc4eeae1..c5fc145b2890 100644 --- a/clippy_lints/src/methods/bytes_nth.rs +++ b/clippy_lints/src/methods/bytes_nth.rs @@ -5,6 +5,8 @@ use rustc_errors::Applicability; use rustc_hir::{Expr, LangItem}; use rustc_lint::LateContext; +use crate::methods::method_call; + use super::BYTES_NTH; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx Expr<'tcx>, n_arg: &'tcx Expr<'tcx>) { @@ -16,18 +18,32 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx E } else { return; }; + let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - BYTES_NTH, - expr.span, - &format!("called `.bytes().nth()` on a `{caller_type}`"), - "try", - format!( - "{}.as_bytes().get({})", - snippet_with_applicability(cx, recv.span, "..", &mut applicability), - snippet_with_applicability(cx, n_arg.span, "..", &mut applicability) - ), - applicability, - ); + let receiver = snippet_with_applicability(cx, recv.span, "..", &mut applicability); + let n = snippet_with_applicability(cx, n_arg.span, "..", &mut applicability); + + if let Some(parent) = clippy_utils::get_parent_expr(cx, expr) + && let Some((name, _, _, _, _)) = method_call(parent) + && name == "unwrap" { + span_lint_and_sugg( + cx, + BYTES_NTH, + parent.span, + &format!("called `.bytes().nth().unwrap()` on a `{caller_type}`"), + "try", + format!("{receiver}.as_bytes()[{n}]",), + applicability + ); + } else { + span_lint_and_sugg( + cx, + BYTES_NTH, + expr.span, + &format!("called `.bytes().nth()` on a `{caller_type}`"), + "try", + format!("{receiver}.as_bytes().get({n}).copied()"), + applicability + ); + }; } diff --git a/clippy_lints/src/methods/expect_used.rs b/clippy_lints/src/methods/expect_used.rs index cce8f797e98c..614610335a13 100644 --- a/clippy_lints/src/methods/expect_used.rs +++ b/clippy_lints/src/methods/expect_used.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::is_in_cfg_test; use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{is_in_cfg_test, is_in_test_function}; use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::sym; @@ -27,7 +27,7 @@ pub(super) fn check( let method = if is_err { "expect_err" } else { "expect" }; - if allow_expect_in_tests && is_in_cfg_test(cx.tcx, expr.hir_id) { + if allow_expect_in_tests && (is_in_test_function(cx.tcx, expr.hir_id) || is_in_cfg_test(cx.tcx, expr.hir_id)) { return; } diff --git a/clippy_lints/src/methods/implicit_clone.rs b/clippy_lints/src/methods/implicit_clone.rs index 374eb29fc527..5a78a4168772 100644 --- a/clippy_lints/src/methods/implicit_clone.rs +++ b/clippy_lints/src/methods/implicit_clone.rs @@ -53,7 +53,9 @@ pub fn is_clone_like(cx: &LateContext<'_>, method_name: &str, method_def_id: hir "to_vec" => cx .tcx .impl_of_method(method_def_id) - .filter(|&impl_did| cx.tcx.type_of(impl_did).subst_identity().is_slice() && cx.tcx.impl_trait_ref(impl_did).is_none()) + .filter(|&impl_did| { + cx.tcx.type_of(impl_did).subst_identity().is_slice() && cx.tcx.impl_trait_ref(impl_did).is_none() + }) .is_some(), _ => false, } diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 6301b3ded20d..702df4b282b8 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -80,6 +80,7 @@ mod skip_while_next; mod stable_sort_primitive; mod str_splitn; mod string_extend_chars; +mod suspicious_command_arg_space; mod suspicious_map; mod suspicious_splitn; mod suspicious_to_owned; @@ -3162,6 +3163,32 @@ declare_clippy_lint! { "collecting an iterator when collect is not needed" } +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for `Command::arg()` invocations that look like they + /// should be multiple arguments instead, such as `arg("-t ext2")`. + /// + /// ### Why is this bad? + /// + /// `Command::arg()` does not split arguments by space. An argument like `arg("-t ext2")` + /// will be passed as a single argument to the command, + /// which is likely not what was intended. + /// + /// ### Example + /// ```rust + /// std::process::Command::new("echo").arg("-n hello").spawn().unwrap(); + /// ``` + /// Use instead: + /// ```rust + /// std::process::Command::new("echo").args(["-n", "hello"]).spawn().unwrap(); + /// ``` + #[clippy::version = "1.67.0"] + pub SUSPICIOUS_COMMAND_ARG_SPACE, + suspicious, + "single command line argument that looks like it should be multiple arguments" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Msrv, @@ -3289,6 +3316,7 @@ impl_lint_pass!(Methods => [ SEEK_FROM_CURRENT, SEEK_TO_START_INSTEAD_OF_REWIND, NEEDLESS_COLLECT, + SUSPICIOUS_COMMAND_ARG_SPACE, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -3496,6 +3524,9 @@ impl Methods { unnecessary_lazy_eval::check(cx, expr, recv, arg, "and"); } }, + ("arg", [arg]) => { + suspicious_command_arg_space::check(cx, recv, arg, span); + } ("as_deref" | "as_deref_mut", []) => { needless_option_as_deref::check(cx, expr, recv, name); }, diff --git a/clippy_lints/src/methods/suspicious_command_arg_space.rs b/clippy_lints/src/methods/suspicious_command_arg_space.rs new file mode 100644 index 000000000000..73632c5a357d --- /dev/null +++ b/clippy_lints/src/methods/suspicious_command_arg_space.rs @@ -0,0 +1,39 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::paths; +use clippy_utils::ty::match_type; +use rustc_ast as ast; +use rustc_errors::{Applicability, Diagnostic}; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::Span; + +use super::SUSPICIOUS_COMMAND_ARG_SPACE; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, recv: &'tcx hir::Expr<'_>, arg: &'tcx hir::Expr<'_>, span: Span) { + let ty = cx.typeck_results().expr_ty(recv).peel_refs(); + + if match_type(cx, ty, &paths::STD_PROCESS_COMMAND) + && let hir::ExprKind::Lit(lit) = &arg.kind + && let ast::LitKind::Str(s, _) = &lit.node + && let Some((arg1, arg2)) = s.as_str().split_once(' ') + && arg1.starts_with('-') + && arg1.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-') + { + span_lint_and_then( + cx, + SUSPICIOUS_COMMAND_ARG_SPACE, + arg.span, + "single argument that looks like it should be multiple arguments", + |diag: &mut Diagnostic| { + diag.multipart_suggestion_verbose( + "consider splitting the argument", + vec![ + (span, "args".to_string()), + (arg.span, format!("[{arg1:?}, {arg2:?}]")), + ], + Applicability::MaybeIncorrect, + ); + } + ); + } +} diff --git a/clippy_lints/src/methods/unwrap_used.rs b/clippy_lints/src/methods/unwrap_used.rs index 90983f249cd5..5e4c3daee644 100644 --- a/clippy_lints/src/methods/unwrap_used.rs +++ b/clippy_lints/src/methods/unwrap_used.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{is_in_cfg_test, is_lint_allowed}; +use clippy_utils::{is_in_cfg_test, is_in_test_function, is_lint_allowed}; use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::sym; @@ -27,7 +27,7 @@ pub(super) fn check( let method_suffix = if is_err { "_err" } else { "" }; - if allow_unwrap_in_tests && is_in_cfg_test(cx.tcx, expr.hir_id) { + if allow_unwrap_in_tests && (is_in_test_function(cx.tcx, expr.hir_id) || is_in_cfg_test(cx.tcx, expr.hir_id)) { return; } diff --git a/clippy_lints/src/missing_doc.rs b/clippy_lints/src/missing_doc.rs index 6fd100762b49..9659ca8ced2e 100644 --- a/clippy_lints/src/missing_doc.rs +++ b/clippy_lints/src/missing_doc.rs @@ -8,10 +8,12 @@ use clippy_utils::attrs::is_doc_hidden; use clippy_utils::diagnostics::span_lint; use clippy_utils::is_from_proc_macro; +use hir::def_id::LocalDefId; +use if_chain::if_chain; use rustc_ast::ast::{self, MetaItem, MetaItemKind}; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::ty::DefIdTree; +use rustc_middle::ty::{DefIdTree, Visibility}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::def_id::CRATE_DEF_ID; use rustc_span::source_map::Span; @@ -34,6 +36,9 @@ declare_clippy_lint! { } pub struct MissingDoc { + /// Whether to **only** check for missing documentation in items visible within the current + /// crate. For example, `pub(crate)` items. + crate_items_only: bool, /// Stack of whether #[doc(hidden)] is set /// at each level which has lint attributes. doc_hidden_stack: Vec, @@ -42,14 +47,15 @@ pub struct MissingDoc { impl Default for MissingDoc { #[must_use] fn default() -> Self { - Self::new() + Self::new(false) } } impl MissingDoc { #[must_use] - pub fn new() -> Self { + pub fn new(crate_items_only: bool) -> Self { Self { + crate_items_only, doc_hidden_stack: vec![false], } } @@ -75,6 +81,7 @@ impl MissingDoc { fn check_missing_docs_attrs( &self, cx: &LateContext<'_>, + def_id: LocalDefId, attrs: &[ast::Attribute], sp: Span, article: &'static str, @@ -95,6 +102,13 @@ impl MissingDoc { return; } + if self.crate_items_only && def_id != CRATE_DEF_ID { + let vis = cx.tcx.visibility(def_id); + if vis == Visibility::Public || vis != Visibility::Restricted(CRATE_DEF_ID.into()) { + return; + } + } + let has_doc = attrs .iter() .any(|a| a.doc_str().is_some() || Self::has_include(a.meta())); @@ -123,7 +137,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc { fn check_crate(&mut self, cx: &LateContext<'tcx>) { let attrs = cx.tcx.hir().attrs(hir::CRATE_HIR_ID); - self.check_missing_docs_attrs(cx, attrs, cx.tcx.def_span(CRATE_DEF_ID), "the", "crate"); + self.check_missing_docs_attrs(cx, CRATE_DEF_ID, attrs, cx.tcx.def_span(CRATE_DEF_ID), "the", "crate"); } fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'_>) { @@ -159,7 +173,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc { let attrs = cx.tcx.hir().attrs(it.hir_id()); if !is_from_proc_macro(cx, it) { - self.check_missing_docs_attrs(cx, attrs, it.span, article, desc); + self.check_missing_docs_attrs(cx, it.owner_id.def_id, attrs, it.span, article, desc); } } @@ -168,7 +182,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc { let attrs = cx.tcx.hir().attrs(trait_item.hir_id()); if !is_from_proc_macro(cx, trait_item) { - self.check_missing_docs_attrs(cx, attrs, trait_item.span, article, desc); + self.check_missing_docs_attrs(cx, trait_item.owner_id.def_id, attrs, trait_item.span, article, desc); } } @@ -185,7 +199,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc { let (article, desc) = cx.tcx.article_and_description(impl_item.owner_id.to_def_id()); let attrs = cx.tcx.hir().attrs(impl_item.hir_id()); if !is_from_proc_macro(cx, impl_item) { - self.check_missing_docs_attrs(cx, attrs, impl_item.span, article, desc); + self.check_missing_docs_attrs(cx, impl_item.owner_id.def_id, attrs, impl_item.span, article, desc); } } @@ -193,7 +207,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc { if !sf.is_positional() { let attrs = cx.tcx.hir().attrs(sf.hir_id); if !is_from_proc_macro(cx, sf) { - self.check_missing_docs_attrs(cx, attrs, sf.span, "a", "struct field"); + self.check_missing_docs_attrs(cx, sf.def_id, attrs, sf.span, "a", "struct field"); } } } @@ -201,7 +215,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc { fn check_variant(&mut self, cx: &LateContext<'tcx>, v: &'tcx hir::Variant<'_>) { let attrs = cx.tcx.hir().attrs(v.hir_id); if !is_from_proc_macro(cx, v) { - self.check_missing_docs_attrs(cx, attrs, v.span, "a", "variant"); + self.check_missing_docs_attrs(cx, v.def_id, attrs, v.span, "a", "variant"); } } } diff --git a/clippy_lints/src/module_style.rs b/clippy_lints/src/module_style.rs index 0742943dff2b..349fcd2274d3 100644 --- a/clippy_lints/src/module_style.rs +++ b/clippy_lints/src/module_style.rs @@ -134,7 +134,7 @@ fn process_paths_for_mod_files<'a>( mod_folders: &mut FxHashSet<&'a OsStr>, ) { let mut comp = path.components().rev().peekable(); - let _ = comp.next(); + let _: Option<_> = comp.next(); if path.ends_with("mod.rs") { mod_folders.insert(comp.peek().map(|c| c.as_os_str()).unwrap_or_default()); } diff --git a/clippy_lints/src/mut_key.rs b/clippy_lints/src/mut_key.rs index 5a533261cad8..8aa814b74053 100644 --- a/clippy_lints/src/mut_key.rs +++ b/clippy_lints/src/mut_key.rs @@ -166,7 +166,8 @@ impl MutableKeyType { Ref(_, inner_ty, mutbl) => mutbl == hir::Mutability::Mut || self.is_interior_mutable_type(cx, inner_ty), Slice(inner_ty) => self.is_interior_mutable_type(cx, inner_ty), Array(inner_ty, size) => { - size.try_eval_target_usize(cx.tcx, cx.param_env).map_or(true, |u| u != 0) + size.try_eval_target_usize(cx.tcx, cx.param_env) + .map_or(true, |u| u != 0) && self.is_interior_mutable_type(cx, inner_ty) }, Tuple(fields) => fields.iter().any(|ty| self.is_interior_mutable_type(cx, ty)), diff --git a/clippy_lints/src/needless_pass_by_value.rs b/clippy_lints/src/needless_pass_by_value.rs index da3b6fa9899d..1ab81aee7b8d 100644 --- a/clippy_lints/src/needless_pass_by_value.rs +++ b/clippy_lints/src/needless_pass_by_value.rs @@ -149,7 +149,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { }; let fn_sig = cx.tcx.fn_sig(fn_def_id).subst_identity(); - let fn_sig = cx.tcx.erase_late_bound_regions(fn_sig); + let fn_sig = cx.tcx.liberate_late_bound_regions(fn_def_id.to_def_id(), 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... diff --git a/clippy_lints/src/no_mangle_with_rust_abi.rs b/clippy_lints/src/no_mangle_with_rust_abi.rs new file mode 100644 index 000000000000..bc64ccb295cb --- /dev/null +++ b/clippy_lints/src/no_mangle_with_rust_abi.rs @@ -0,0 +1,65 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_applicability; +use rustc_errors::Applicability; +use rustc_hir::{Item, ItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_target::spec::abi::Abi; + +declare_clippy_lint! { + /// ### What it does + /// Checks for Rust ABI functions with the `#[no_mangle]` attribute. + /// + /// ### Why is this bad? + /// The Rust ABI is not stable, but in many simple cases matches + /// enough with the C ABI that it is possible to forget to add + /// `extern "C"` to a function called from C. Changes to the + /// Rust ABI can break this at any point. + /// + /// ### Example + /// ```rust + /// #[no_mangle] + /// fn example(arg_one: u32, arg_two: usize) {} + /// ``` + /// + /// Use instead: + /// ```rust + /// #[no_mangle] + /// extern "C" fn example(arg_one: u32, arg_two: usize) {} + /// ``` + #[clippy::version = "1.69.0"] + pub NO_MANGLE_WITH_RUST_ABI, + pedantic, + "convert Rust ABI functions to C ABI" +} +declare_lint_pass!(NoMangleWithRustAbi => [NO_MANGLE_WITH_RUST_ABI]); + +impl<'tcx> LateLintPass<'tcx> for NoMangleWithRustAbi { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + if let ItemKind::Fn(fn_sig, _, _) = &item.kind { + let attrs = cx.tcx.hir().attrs(item.hir_id()); + let mut applicability = Applicability::MachineApplicable; + let snippet = snippet_with_applicability(cx, fn_sig.span, "..", &mut applicability); + for attr in attrs { + if let Some(ident) = attr.ident() + && ident.name == rustc_span::sym::no_mangle + && fn_sig.header.abi == Abi::Rust + && !snippet.contains("extern") { + + let suggestion = snippet.split_once("fn") + .map_or(String::new(), |(first, second)| format!(r#"{first}extern "C" fn{second}"#)); + + span_lint_and_sugg( + cx, + NO_MANGLE_WITH_RUST_ABI, + fn_sig.span, + "attribute #[no_mangle] set on a Rust ABI function", + "try", + suggestion, + applicability + ); + } + } + } + } +} diff --git a/clippy_lints/src/operators/arithmetic_side_effects.rs b/clippy_lints/src/operators/arithmetic_side_effects.rs index d592f6e814c1..87a8a2ed12b2 100644 --- a/clippy_lints/src/operators/arithmetic_side_effects.rs +++ b/clippy_lints/src/operators/arithmetic_side_effects.rs @@ -1,8 +1,8 @@ use super::ARITHMETIC_SIDE_EFFECTS; use clippy_utils::{ - consts::{constant, constant_simple}, + consts::{constant, constant_simple, Constant}, diagnostics::span_lint, - peel_hir_expr_refs, peel_hir_expr_unary, + is_lint_allowed, peel_hir_expr_refs, peel_hir_expr_unary, }; use rustc_ast as ast; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; @@ -97,17 +97,19 @@ impl ArithmeticSideEffects { self.expr_span = Some(expr.span); } - /// If `expr` is not a literal integer like `1`, returns `None`. + /// Returns the numeric value of a literal integer originated from `expr`, if any. /// - /// Returns the absolute value of the expression, if this is an integer literal. - fn literal_integer(expr: &hir::Expr<'_>) -> Option { + /// Literal integers can be originated from adhoc declarations like `1`, associated constants + /// like `i32::MAX` or constant references like `N` from `const N: i32 = 1;`, + fn literal_integer(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option { let actual = peel_hir_expr_unary(expr).0; if let hir::ExprKind::Lit(ref lit) = actual.kind && let ast::LitKind::Int(n, _) = lit.node { - Some(n) + return Some(n) } - else { - None + if let Some((Constant::Int(n), _)) = constant(cx, cx.typeck_results(), expr) { + return Some(n); } + None } /// Manages when the lint should be triggered. Operations in constant environments, hard coded @@ -143,7 +145,10 @@ impl ArithmeticSideEffects { let has_valid_op = if Self::is_integral(lhs_ty) && Self::is_integral(rhs_ty) { let (actual_lhs, lhs_ref_counter) = peel_hir_expr_refs(lhs); let (actual_rhs, rhs_ref_counter) = peel_hir_expr_refs(rhs); - match (Self::literal_integer(actual_lhs), Self::literal_integer(actual_rhs)) { + match ( + Self::literal_integer(cx, actual_lhs), + Self::literal_integer(cx, actual_rhs), + ) { (None, None) => false, (None, Some(n)) | (Some(n), None) => match (&op.node, n) { (hir::BinOpKind::Div | hir::BinOpKind::Rem, 0) => false, @@ -180,20 +185,22 @@ impl ArithmeticSideEffects { return; } let actual_un_expr = peel_hir_expr_refs(un_expr).0; - if Self::literal_integer(actual_un_expr).is_some() { + if Self::literal_integer(cx, actual_un_expr).is_some() { return; } self.issue_lint(cx, expr); } - fn should_skip_expr(&mut self, expr: &hir::Expr<'_>) -> bool { - self.expr_span.is_some() || self.const_span.map_or(false, |sp| sp.contains(expr.span)) + fn should_skip_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { + is_lint_allowed(cx, ARITHMETIC_SIDE_EFFECTS, expr.hir_id) + || self.expr_span.is_some() + || self.const_span.map_or(false, |sp| sp.contains(expr.span)) } } impl<'tcx> LateLintPass<'tcx> for ArithmeticSideEffects { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'tcx>) { - if self.should_skip_expr(expr) { + if self.should_skip_expr(cx, expr) { return; } match &expr.kind { diff --git a/clippy_lints/src/question_mark_used.rs b/clippy_lints/src/question_mark_used.rs new file mode 100644 index 000000000000..9b678e8d753c --- /dev/null +++ b/clippy_lints/src/question_mark_used.rs @@ -0,0 +1,52 @@ +use clippy_utils::diagnostics::span_lint_and_help; + +use clippy_utils::macros::span_is_local; +use rustc_hir::{Expr, ExprKind, MatchSource}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// ### What it does + /// Checks for expressions that use the question mark operator and rejects them. + /// + /// ### Why is this bad? + /// Sometimes code wants to avoid the question mark operator because for instance a local + /// block requires a macro to re-throw errors to attach additional information to the + /// error. + /// + /// ### Example + /// ```ignore + /// let result = expr?; + /// ``` + /// + /// Could be written: + /// + /// ```ignore + /// utility_macro!(expr); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub QUESTION_MARK_USED, + restriction, + "complains if the question mark operator is used" +} + +declare_lint_pass!(QuestionMarkUsed => [QUESTION_MARK_USED]); + +impl<'tcx> LateLintPass<'tcx> for QuestionMarkUsed { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Match(_, _, MatchSource::TryDesugar) = expr.kind { + if !span_is_local(expr.span) { + return; + } + + span_lint_and_help( + cx, + QUESTION_MARK_USED, + expr.span, + "question mark operator was used", + None, + "consider using a custom macro or match expression", + ); + } + } +} diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs index 84a0c6b95585..f0d7dd23a678 100644 --- a/clippy_lints/src/returns.rs +++ b/clippy_lints/src/returns.rs @@ -14,6 +14,7 @@ use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::def_id::LocalDefId; use rustc_span::source_map::Span; use rustc_span::{BytePos, Pos}; +use std::borrow::Cow; declare_clippy_lint! { /// ### What it does @@ -69,31 +70,41 @@ declare_clippy_lint! { "using a return statement like `return expr;` where an expression would suffice" } -#[derive(PartialEq, Eq, Copy, Clone)] -enum RetReplacement { +#[derive(PartialEq, Eq, Clone)] +enum RetReplacement<'tcx> { Empty, Block, Unit, + IfSequence(Cow<'tcx, str>, Applicability), + Expr(Cow<'tcx, str>, Applicability), } -impl RetReplacement { +impl<'tcx> RetReplacement<'tcx> { fn sugg_help(self) -> &'static str { match self { - Self::Empty => "remove `return`", + Self::Empty | Self::Expr(..) => "remove `return`", Self::Block => "replace `return` with an empty block", Self::Unit => "replace `return` with a unit value", + Self::IfSequence(..) => "remove `return` and wrap the sequence with parentheses", + } + } + fn applicability(&self) -> Option { + match self { + Self::Expr(_, ap) | Self::IfSequence(_, ap) => Some(*ap), + _ => None, } } } -impl ToString for RetReplacement { +impl<'tcx> ToString for RetReplacement<'tcx> { fn to_string(&self) -> String { - match *self { - Self::Empty => "", - Self::Block => "{}", - Self::Unit => "()", + match self { + Self::Empty => String::new(), + Self::Block => "{}".to_string(), + Self::Unit => "()".to_string(), + Self::IfSequence(inner, _) => format!("({inner})"), + Self::Expr(inner, _) => inner.to_string(), } - .to_string() } } @@ -204,26 +215,12 @@ fn check_final_expr<'tcx>( expr: &'tcx Expr<'tcx>, semi_spans: Vec, /* containing all the places where we would need to remove semicolons if finding an * needless return */ - replacement: RetReplacement, + replacement: RetReplacement<'tcx>, ) { let peeled_drop_expr = expr.peel_drop_temps(); match &peeled_drop_expr.kind { // simple return is always "bad" ExprKind::Ret(ref inner) => { - // if desugar of `do yeet`, don't lint - if let Some(inner_expr) = inner - && let ExprKind::Call(path_expr, _) = inner_expr.kind - && let ExprKind::Path(QPath::LangItem(LangItem::TryTraitFromYeet, _, _)) = path_expr.kind - { - return; - } - if !cx.tcx.hir().attrs(expr.hir_id).is_empty() { - return; - } - let borrows = inner.map_or(false, |inner| last_statement_borrows(cx, inner)); - if borrows { - return; - } // check if expr return nothing let ret_span = if inner.is_none() && replacement == RetReplacement::Empty { extend_span_to_previous_non_ws(cx, peeled_drop_expr.span) @@ -231,7 +228,34 @@ fn check_final_expr<'tcx>( peeled_drop_expr.span }; - emit_return_lint(cx, ret_span, semi_spans, inner.as_ref().map(|i| i.span), replacement); + let replacement = if let Some(inner_expr) = inner { + // if desugar of `do yeet`, don't lint + if let ExprKind::Call(path_expr, _) = inner_expr.kind + && let ExprKind::Path(QPath::LangItem(LangItem::TryTraitFromYeet, _, _)) = path_expr.kind + { + return; + } + + let mut applicability = Applicability::MachineApplicable; + let (snippet, _) = snippet_with_context(cx, inner_expr.span, ret_span.ctxt(), "..", &mut applicability); + if expr_contains_conjunctive_ifs(inner_expr) { + RetReplacement::IfSequence(snippet, applicability) + } else { + RetReplacement::Expr(snippet, applicability) + } + } else { + replacement + }; + + if !cx.tcx.hir().attrs(expr.hir_id).is_empty() { + return; + } + let borrows = inner.map_or(false, |inner| last_statement_borrows(cx, inner)); + if borrows { + return; + } + + emit_return_lint(cx, ret_span, semi_spans, replacement); }, ExprKind::If(_, then, else_clause_opt) => { check_block_return(cx, &then.kind, peeled_drop_expr.span, semi_spans.clone()); @@ -253,29 +277,25 @@ fn check_final_expr<'tcx>( } } -fn emit_return_lint( - cx: &LateContext<'_>, - ret_span: Span, - semi_spans: Vec, - inner_span: Option, - replacement: RetReplacement, -) { +fn expr_contains_conjunctive_ifs<'tcx>(expr: &'tcx Expr<'tcx>) -> bool { + fn contains_if(expr: &Expr<'_>, on_if: bool) -> bool { + match expr.kind { + ExprKind::If(..) => on_if, + ExprKind::Binary(_, left, right) => contains_if(left, true) || contains_if(right, true), + _ => false, + } + } + + contains_if(expr, false) +} + +fn emit_return_lint(cx: &LateContext<'_>, ret_span: Span, semi_spans: Vec, replacement: RetReplacement<'_>) { if ret_span.from_expansion() { return; } - let mut applicability = Applicability::MachineApplicable; - let return_replacement = inner_span.map_or_else( - || replacement.to_string(), - |inner_span| { - let (snippet, _) = snippet_with_context(cx, inner_span, ret_span.ctxt(), "..", &mut applicability); - snippet.to_string() - }, - ); - let sugg_help = if inner_span.is_some() { - "remove `return`" - } else { - replacement.sugg_help() - }; + let applicability = replacement.applicability().unwrap_or(Applicability::MachineApplicable); + let return_replacement = replacement.to_string(); + let sugg_help = replacement.sugg_help(); span_lint_and_then(cx, NEEDLESS_RETURN, ret_span, "unneeded `return` statement", |diag| { diag.span_suggestion_hidden(ret_span, sugg_help, return_replacement, applicability); // for each parent statement, we need to remove the semicolon diff --git a/clippy_lints/src/significant_drop_tightening.rs b/clippy_lints/src/significant_drop_tightening.rs new file mode 100644 index 000000000000..e2d90edec5a4 --- /dev/null +++ b/clippy_lints/src/significant_drop_tightening.rs @@ -0,0 +1,399 @@ +use crate::FxHashSet; +use clippy_utils::{ + diagnostics::span_lint_and_then, + get_attr, + source::{indent_of, snippet}, +}; +use rustc_errors::{Applicability, Diagnostic}; +use rustc_hir::{ + self as hir, + intravisit::{walk_expr, Visitor}, +}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::ty::{subst::GenericArgKind, Ty, TypeAndMut}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::{symbol::Ident, Span, DUMMY_SP}; + +declare_clippy_lint! { + /// ### What it does + /// + /// Searches for elements marked with `#[clippy::significant_drop]` that could be early + /// dropped but are in fact dropped at the end of their scopes. In other words, enforces the + /// "tightening" of their possible lifetimes. + /// + /// ### Why is this bad? + /// + /// Elements marked with `#[clippy::has_significant_drop]` are generally synchronizing + /// primitives that manage shared resources, as such, it is desired to release them as soon as + /// possible to avoid unnecessary resource contention. + /// + /// ### Example + /// + /// ```rust,ignore + /// fn main() { + /// let lock = some_sync_resource.lock(); + /// let owned_rslt = lock.do_stuff_with_resource(); + /// // Only `owned_rslt` is needed but `lock` is still held. + /// do_heavy_computation_that_takes_time(owned_rslt); + /// } + /// ``` + /// + /// Use instead: + /// + /// ```rust,ignore + /// fn main() { + /// let owned_rslt = some_sync_resource.lock().do_stuff_with_resource(); + /// do_heavy_computation_that_takes_time(owned_rslt); + /// } + /// ``` + #[clippy::version = "1.67.0"] + pub SIGNIFICANT_DROP_TIGHTENING, + nursery, + "Searches for elements marked with `#[clippy::has_significant_drop]` that could be early dropped but are in fact dropped at the end of their scopes" +} + +impl_lint_pass!(SignificantDropTightening<'_> => [SIGNIFICANT_DROP_TIGHTENING]); + +#[derive(Default)] +pub struct SignificantDropTightening<'tcx> { + /// Auxiliary structure used to avoid having to verify the same type multiple times. + seen_types: FxHashSet>, +} + +impl<'tcx> SignificantDropTightening<'tcx> { + /// Unifies the statements of a block with its return expression. + fn all_block_stmts<'ret, 'rslt, 'stmts>( + block_stmts: &'stmts [hir::Stmt<'tcx>], + dummy_ret_stmt: Option<&'ret hir::Stmt<'tcx>>, + ) -> impl Iterator> + where + 'ret: 'rslt, + 'stmts: 'rslt, + { + block_stmts.iter().chain(dummy_ret_stmt) + } + + /// Searches for at least one statement that could slow down the release of a significant drop. + fn at_least_one_stmt_is_expensive<'stmt>(stmts: impl Iterator>) -> bool + where + 'tcx: 'stmt, + { + for stmt in stmts { + match stmt.kind { + hir::StmtKind::Expr(expr) if let hir::ExprKind::Path(_) = expr.kind => {} + hir::StmtKind::Local(local) if let Some(expr) = local.init + && let hir::ExprKind::Path(_) = expr.kind => {}, + _ => return true + }; + } + false + } + + /// Verifies if the expression is of type `drop(some_lock_path)` to assert that the temporary + /// is already being dropped before the end of its scope. + fn has_drop(expr: &'tcx hir::Expr<'_>, init_bind_ident: Ident) -> bool { + if let hir::ExprKind::Call(fun, args) = expr.kind + && let hir::ExprKind::Path(hir::QPath::Resolved(_, fun_path)) = &fun.kind + && let [fun_ident, ..] = fun_path.segments + && fun_ident.ident.name == rustc_span::sym::drop + && let [first_arg, ..] = args + && let hir::ExprKind::Path(hir::QPath::Resolved(_, arg_path)) = &first_arg.kind + && let [first_arg_ps, .. ] = arg_path.segments + { + first_arg_ps.ident == init_bind_ident + } + else { + false + } + } + + /// Tries to find types marked with `#[has_significant_drop]` of an expression `expr` that is + /// originated from `stmt` and then performs common logic on `sdap`. + fn modify_sdap_if_sig_drop_exists( + &mut self, + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + idx: usize, + sdap: &mut SigDropAuxParams, + stmt: &hir::Stmt<'_>, + cb: impl Fn(&mut SigDropAuxParams), + ) { + let mut sig_drop_finder = SigDropFinder::new(cx, &mut self.seen_types); + sig_drop_finder.visit_expr(expr); + if sig_drop_finder.has_sig_drop { + cb(sdap); + if sdap.number_of_stmts > 0 { + sdap.last_use_stmt_idx = idx; + sdap.last_use_stmt_span = stmt.span; + if let hir::ExprKind::MethodCall(_, _, _, span) = expr.kind { + sdap.last_use_method_span = span; + } + } + sdap.number_of_stmts = sdap.number_of_stmts.wrapping_add(1); + } + } + + /// Shows generic overall messages as well as specialized messages depending on the usage. + fn set_suggestions(cx: &LateContext<'tcx>, block_span: Span, diag: &mut Diagnostic, sdap: &SigDropAuxParams) { + match sdap.number_of_stmts { + 0 | 1 => {}, + 2 => { + let indent = " ".repeat(indent_of(cx, sdap.last_use_stmt_span).unwrap_or(0)); + let init_method = snippet(cx, sdap.init_method_span, ".."); + let usage_method = snippet(cx, sdap.last_use_method_span, ".."); + let stmt = if let Some(last_use_bind_span) = sdap.last_use_bind_span { + format!( + "\n{indent}let {} = {init_method}.{usage_method};", + snippet(cx, last_use_bind_span, ".."), + ) + } else { + format!("\n{indent}{init_method}.{usage_method};") + }; + diag.span_suggestion_verbose( + sdap.init_stmt_span, + "merge the temporary construction with its single usage", + stmt, + Applicability::MaybeIncorrect, + ); + diag.span_suggestion( + sdap.last_use_stmt_span, + "remove separated single usage", + "", + Applicability::MaybeIncorrect, + ); + }, + _ => { + diag.span_suggestion( + sdap.last_use_stmt_span.shrink_to_hi(), + "drop the temporary after the end of its last usage", + format!( + "\n{}drop({});", + " ".repeat(indent_of(cx, sdap.last_use_stmt_span).unwrap_or(0)), + sdap.init_bind_ident + ), + Applicability::MaybeIncorrect, + ); + }, + } + diag.note("this might lead to unnecessary resource contention"); + diag.span_label( + block_span, + format!( + "temporary `{}` is currently being dropped at the end of its contained scope", + sdap.init_bind_ident + ), + ); + } +} + +impl<'tcx> LateLintPass<'tcx> for SignificantDropTightening<'tcx> { + fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) { + let dummy_ret_stmt = block.expr.map(|expr| hir::Stmt { + hir_id: hir::HirId::INVALID, + kind: hir::StmtKind::Expr(expr), + span: DUMMY_SP, + }); + let mut sdap = SigDropAuxParams::default(); + for (idx, stmt) in Self::all_block_stmts(block.stmts, dummy_ret_stmt.as_ref()).enumerate() { + match stmt.kind { + hir::StmtKind::Expr(expr) => self.modify_sdap_if_sig_drop_exists( + cx, + expr, + idx, + &mut sdap, + stmt, + |_| {} + ), + hir::StmtKind::Local(local) if let Some(expr) = local.init => self.modify_sdap_if_sig_drop_exists( + cx, + expr, + idx, + &mut sdap, + stmt, + |local_sdap| { + if local_sdap.number_of_stmts == 0 { + if let hir::PatKind::Binding(_, _, ident, _) = local.pat.kind { + local_sdap.init_bind_ident = ident; + } + if let hir::ExprKind::MethodCall(_, local_expr, _, span) = expr.kind { + local_sdap.init_method_span = local_expr.span.to(span); + } + local_sdap.init_stmt_span = stmt.span; + } + else if let hir::PatKind::Binding(_, _, ident, _) = local.pat.kind { + local_sdap.last_use_bind_span = Some(ident.span); + } + } + ), + hir::StmtKind::Semi(expr) => { + if Self::has_drop(expr, sdap.init_bind_ident) { + return; + } + self.modify_sdap_if_sig_drop_exists(cx, expr, idx, &mut sdap, stmt, |_| {}); + }, + _ => {} + }; + } + + let idx = sdap.last_use_stmt_idx.wrapping_add(1); + let stmts_after_last_use = Self::all_block_stmts(block.stmts, dummy_ret_stmt.as_ref()).skip(idx); + if sdap.number_of_stmts > 1 && Self::at_least_one_stmt_is_expensive(stmts_after_last_use) { + span_lint_and_then( + cx, + SIGNIFICANT_DROP_TIGHTENING, + sdap.init_bind_ident.span, + "temporary with significant `Drop` can be early dropped", + |diag| { + Self::set_suggestions(cx, block.span, diag, &sdap); + }, + ); + } + } +} + +/// Auxiliary parameters used on each block check. +struct SigDropAuxParams { + /// The binding or variable that references the initial construction of the type marked with + /// `#[has_significant_drop]`. + init_bind_ident: Ident, + /// Similar to `init_bind_ident` but encompasses the right-hand method call. + init_method_span: Span, + /// Similar to `init_bind_ident` but encompasses the whole contained statement. + init_stmt_span: Span, + + /// The last visited binding or variable span within a block that had any referenced inner type + /// marked with `#[has_significant_drop]`. + last_use_bind_span: Option, + /// Index of the last visited statement within a block that had any referenced inner type + /// marked with `#[has_significant_drop]`. + last_use_stmt_idx: usize, + /// Similar to `last_use_bind_span` but encompasses the whole contained statement. + last_use_stmt_span: Span, + /// Similar to `last_use_bind_span` but encompasses the right-hand method call. + last_use_method_span: Span, + + /// Total number of statements within a block that have any referenced inner type marked with + /// `#[has_significant_drop]`. + number_of_stmts: usize, +} + +impl Default for SigDropAuxParams { + fn default() -> Self { + Self { + init_bind_ident: Ident::empty(), + init_method_span: DUMMY_SP, + init_stmt_span: DUMMY_SP, + last_use_bind_span: None, + last_use_method_span: DUMMY_SP, + last_use_stmt_idx: 0, + last_use_stmt_span: DUMMY_SP, + number_of_stmts: 0, + } + } +} + +/// Checks the existence of the `#[has_significant_drop]` attribute +struct SigDropChecker<'cx, 'sdt, 'tcx> { + cx: &'cx LateContext<'tcx>, + seen_types: &'sdt mut FxHashSet>, +} + +impl<'cx, 'sdt, 'tcx> SigDropChecker<'cx, 'sdt, 'tcx> { + pub(crate) fn new(cx: &'cx LateContext<'tcx>, seen_types: &'sdt mut FxHashSet>) -> Self { + seen_types.clear(); + Self { cx, seen_types } + } + + pub(crate) fn has_sig_drop_attr(&mut self, ty: Ty<'tcx>) -> bool { + if let Some(adt) = ty.ty_adt_def() { + let mut iter = get_attr( + self.cx.sess(), + self.cx.tcx.get_attrs_unchecked(adt.did()), + "has_significant_drop", + ); + if iter.next().is_some() { + return true; + } + } + match ty.kind() { + rustc_middle::ty::Adt(a, b) => { + for f in a.all_fields() { + let ty = f.ty(self.cx.tcx, b); + if !self.has_seen_ty(ty) && self.has_sig_drop_attr(ty) { + return true; + } + } + for generic_arg in b.iter() { + if let GenericArgKind::Type(ty) = generic_arg.unpack() { + if self.has_sig_drop_attr(ty) { + return true; + } + } + } + false + }, + 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(*ty), + _ => false, + } + } + + fn has_seen_ty(&mut self, ty: Ty<'tcx>) -> bool { + !self.seen_types.insert(ty) + } +} + +/// Performs recursive calls to find any inner type marked with `#[has_significant_drop]`. +struct SigDropFinder<'cx, 'sdt, 'tcx> { + cx: &'cx LateContext<'tcx>, + has_sig_drop: bool, + sig_drop_checker: SigDropChecker<'cx, 'sdt, 'tcx>, +} + +impl<'cx, 'sdt, 'tcx> SigDropFinder<'cx, 'sdt, 'tcx> { + fn new(cx: &'cx LateContext<'tcx>, seen_types: &'sdt mut FxHashSet>) -> Self { + Self { + cx, + has_sig_drop: false, + sig_drop_checker: SigDropChecker::new(cx, seen_types), + } + } +} + +impl<'cx, 'sdt, 'tcx> Visitor<'tcx> for SigDropFinder<'cx, 'sdt, 'tcx> { + fn visit_expr(&mut self, ex: &'tcx hir::Expr<'_>) { + if self + .sig_drop_checker + .has_sig_drop_attr(self.cx.typeck_results().expr_ty(ex)) + { + self.has_sig_drop = true; + return; + } + + match ex.kind { + hir::ExprKind::MethodCall(_, expr, ..) => { + self.visit_expr(expr); + }, + hir::ExprKind::Array(..) + | hir::ExprKind::Assign(..) + | hir::ExprKind::AssignOp(..) + | hir::ExprKind::Binary(..) + | hir::ExprKind::Box(..) + | hir::ExprKind::Call(..) + | hir::ExprKind::Field(..) + | hir::ExprKind::If(..) + | hir::ExprKind::Index(..) + | hir::ExprKind::Match(..) + | hir::ExprKind::Repeat(..) + | hir::ExprKind::Ret(..) + | hir::ExprKind::Tup(..) + | hir::ExprKind::Unary(..) + | hir::ExprKind::Yield(..) => { + walk_expr(self, ex); + }, + _ => {}, + } + } +} diff --git a/clippy_lints/src/swap.rs b/clippy_lints/src/swap.rs index 17e9cc5f6b7c..0f062cecf886 100644 --- a/clippy_lints/src/swap.rs +++ b/clippy_lints/src/swap.rs @@ -10,7 +10,7 @@ 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_span::{sym, Span}; +use rustc_span::{sym, symbol::Ident, Span}; declare_clippy_lint! { /// ### What it does @@ -174,55 +174,76 @@ fn check_manual_swap(cx: &LateContext<'_>, block: &Block<'_>) { /// Implementation of the `ALMOST_SWAPPED` lint. fn check_suspicious_swap(cx: &LateContext<'_>, block: &Block<'_>) { - for w in block.stmts.windows(2) { - if_chain! { - if let StmtKind::Semi(first) = w[0].kind; - if let StmtKind::Semi(second) = w[1].kind; - if first.span.ctxt() == second.span.ctxt(); - if let ExprKind::Assign(lhs0, rhs0, _) = first.kind; - if let ExprKind::Assign(lhs1, rhs1, _) = second.kind; - if eq_expr_value(cx, lhs0, rhs1); - if eq_expr_value(cx, lhs1, rhs0); - then { - let lhs0 = Sugg::hir_opt(cx, lhs0); - let rhs0 = Sugg::hir_opt(cx, rhs0); - let (what, lhs, rhs) = if let (Some(first), Some(second)) = (lhs0, rhs0) { - ( - format!(" `{first}` and `{second}`"), - first.mut_addr().to_string(), - second.mut_addr().to_string(), - ) - } else { - (String::new(), String::new(), String::new()) - }; + for [first, second] in block.stmts.array_windows() { + if let Some((lhs0, rhs0)) = parse(first) + && let Some((lhs1, rhs1)) = parse(second) + && first.span.eq_ctxt(second.span) + && is_same(cx, lhs0, rhs1) + && is_same(cx, lhs1, rhs0) + && let Some(lhs_sugg) = match &lhs0 { + ExprOrIdent::Expr(expr) => Sugg::hir_opt(cx, expr), + ExprOrIdent::Ident(ident) => Some(Sugg::NonParen(ident.as_str().into())), + } + && let Some(rhs_sugg) = Sugg::hir_opt(cx, rhs0) + { + let span = first.span.to(rhs1.span); + let Some(sugg) = std_or_core(cx) else { return }; + span_lint_and_then( + cx, + ALMOST_SWAPPED, + span, + &format!("this looks like you are trying to swap `{lhs_sugg}` and `{rhs_sugg}`"), + |diag| { + diag.span_suggestion( + span, + "try", + format!("{sugg}::mem::swap({}, {})", lhs_sugg.mut_addr(), rhs_sugg.mut_addr()), + Applicability::MaybeIncorrect, + ); + diag.note(format!("or maybe you should use `{sugg}::mem::replace`?")); + }, + ); + } + } +} - let span = first.span.to(second.span); - let Some(sugg) = std_or_core(cx) else { return }; - - span_lint_and_then(cx, - ALMOST_SWAPPED, - span, - &format!("this looks like you are trying to swap{what}"), - |diag| { - if !what.is_empty() { - diag.span_suggestion( - span, - "try", - format!( - "{sugg}::mem::swap({lhs}, {rhs})", - ), - Applicability::MaybeIncorrect, - ); - diag.note( - format!("or maybe you should use `{sugg}::mem::replace`?") - ); - } - }); +fn is_same(cx: &LateContext<'_>, lhs: ExprOrIdent<'_>, rhs: &Expr<'_>) -> bool { + match lhs { + ExprOrIdent::Expr(expr) => eq_expr_value(cx, expr, rhs), + ExprOrIdent::Ident(ident) => { + if let ExprKind::Path(QPath::Resolved(None, path)) = rhs.kind + && let [segment] = &path.segments + && segment.ident == ident + { + true + } else { + false } } } } +#[derive(Debug, Clone, Copy)] +enum ExprOrIdent<'a> { + Expr(&'a Expr<'a>), + Ident(Ident), +} + +fn parse<'a, 'hir>(stmt: &'a Stmt<'hir>) -> Option<(ExprOrIdent<'hir>, &'a Expr<'hir>)> { + if let StmtKind::Semi(expr) = stmt.kind { + if let ExprKind::Assign(lhs, rhs, _) = expr.kind { + return Some((ExprOrIdent::Expr(lhs), rhs)); + } + } else if let StmtKind::Local(expr) = stmt.kind { + if let Some(rhs) = expr.init { + if let PatKind::Binding(_, _, ident_l, _) = expr.pat.kind { + return Some((ExprOrIdent::Ident(ident_l), rhs)); + } + } + } + None +} + /// Implementation of the xor case for `MANUAL_SWAP` lint. fn check_xor_swap(cx: &LateContext<'_>, block: &Block<'_>) { for window in block.stmts.windows(3) { diff --git a/clippy_lints/src/transmute/mod.rs b/clippy_lints/src/transmute/mod.rs index c0d290b5adc4..c01cbe5090f7 100644 --- a/clippy_lints/src/transmute/mod.rs +++ b/clippy_lints/src/transmute/mod.rs @@ -3,6 +3,7 @@ mod transmute_float_to_int; mod transmute_int_to_bool; mod transmute_int_to_char; mod transmute_int_to_float; +mod transmute_int_to_non_zero; mod transmute_null_to_fn; mod transmute_num_to_bytes; mod transmute_ptr_to_ptr; @@ -253,6 +254,31 @@ declare_clippy_lint! { "transmutes from an integer to a float" } +declare_clippy_lint! { + /// ### What it does + /// Checks for transmutes from integers to `NonZero*` types, and suggests their `new_unchecked` + /// method instead. + /// + /// ### Why is this bad? + /// Transmutes work on any types and thus might cause unsoundness when those types change + /// elsewhere. `new_unchecked` only works for the appropriate types instead. + /// + /// ### Example + /// ```rust + /// # use core::num::NonZeroU32; + /// let _non_zero: NonZeroU32 = unsafe { std::mem::transmute(123) }; + /// ``` + /// Use instead: + /// ```rust + /// # use core::num::NonZeroU32; + /// let _non_zero = unsafe { NonZeroU32::new_unchecked(123) }; + /// ``` + #[clippy::version = "1.69.0"] + pub TRANSMUTE_INT_TO_NON_ZERO, + complexity, + "transmutes from an integer to a non-zero wrapper" +} + declare_clippy_lint! { /// ### What it does /// Checks for transmutes from a float to an integer. @@ -451,6 +477,7 @@ impl_lint_pass!(Transmute => [ TRANSMUTE_BYTES_TO_STR, TRANSMUTE_INT_TO_BOOL, TRANSMUTE_INT_TO_FLOAT, + TRANSMUTE_INT_TO_NON_ZERO, TRANSMUTE_FLOAT_TO_INT, TRANSMUTE_NUM_TO_BYTES, UNSOUND_COLLECTION_TRANSMUTE, @@ -501,6 +528,7 @@ impl<'tcx> LateLintPass<'tcx> for Transmute { | 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_int_to_non_zero::check(cx, e, from_ty, to_ty, arg) | 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) | ( diff --git a/clippy_lints/src/transmute/transmute_int_to_non_zero.rs b/clippy_lints/src/transmute/transmute_int_to_non_zero.rs new file mode 100644 index 000000000000..5503653253c5 --- /dev/null +++ b/clippy_lints/src/transmute/transmute_int_to_non_zero.rs @@ -0,0 +1,61 @@ +use super::TRANSMUTE_INT_TO_NON_ZERO; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::sugg; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::{ + query::Key, + ty::{self, Ty}, +}; +use rustc_span::symbol::sym; + +/// Checks for `transmute_int_to_non_zero` lint. +/// Returns `true` if it's triggered, otherwise returns `false`. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + from_ty: Ty<'tcx>, + to_ty: Ty<'tcx>, + arg: &'tcx Expr<'_>, +) -> bool { + let (ty::Int(_) | ty::Uint(_), Some(to_ty_id)) = (&from_ty.kind(), to_ty.ty_adt_id()) else { + return false; + }; + let Some(to_type_sym) = cx.tcx.get_diagnostic_name(to_ty_id) else { + return false; + }; + + if !matches!( + to_type_sym, + sym::NonZeroU8 + | sym::NonZeroU16 + | sym::NonZeroU32 + | sym::NonZeroU64 + | sym::NonZeroU128 + | sym::NonZeroI8 + | sym::NonZeroI16 + | sym::NonZeroI32 + | sym::NonZeroI64 + | sym::NonZeroI128 + ) { + return false; + } + + span_lint_and_then( + cx, + TRANSMUTE_INT_TO_NON_ZERO, + e.span, + &format!("transmute from a `{from_ty}` to a `{to_type_sym}`"), + |diag| { + let arg = sugg::Sugg::hir(cx, arg, ".."); + diag.span_suggestion( + e.span, + "consider using", + format!("{to_type_sym}::{}({arg})", sym::new_unchecked), + Applicability::Unspecified, + ); + }, + ); + true +} diff --git a/clippy_lints/src/unnested_or_patterns.rs b/clippy_lints/src/unnested_or_patterns.rs index 06d248204c1f..a57bf7ee8225 100644 --- a/clippy_lints/src/unnested_or_patterns.rs +++ b/clippy_lints/src/unnested_or_patterns.rs @@ -400,7 +400,7 @@ fn drain_matching( // If `ThinVec` had the `drain_filter` method, this loop could be rewritten // like so: - // + // // for pat in alternatives.drain_filter(|p| { // // Check if we should extract, but only if `idx >= start`. // idx += 1; @@ -412,12 +412,12 @@ fn drain_matching( while i < alternatives.len() { idx += 1; // Check if we should extract, but only if `idx >= start`. - if idx > start && predicate(&alternatives[i].kind) { - let pat = alternatives.remove(i); + if idx > start && predicate(&alternatives[i].kind) { + let pat = alternatives.remove(i); tail_or.push(extract(pat.into_inner().kind)); - } else { - i += 1; - } + } else { + i += 1; + } } tail_or diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index 1d78c7cfae0d..5f74de5a2886 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -419,19 +419,19 @@ define_Conf! { (max_include_file_size: u64 = 1_000_000), /// Lint: EXPECT_USED. /// - /// Whether `expect` should be allowed within `#[cfg(test)]` + /// Whether `expect` should be allowed in test functions or `#[cfg(test)]` (allow_expect_in_tests: bool = false), /// Lint: UNWRAP_USED. /// - /// Whether `unwrap` should be allowed in test cfg + /// Whether `unwrap` should be allowed in test functions or `#[cfg(test)]` (allow_unwrap_in_tests: bool = false), /// Lint: DBG_MACRO. /// - /// Whether `dbg!` should be allowed in test functions + /// Whether `dbg!` should be allowed in test functions or `#[cfg(test)]` (allow_dbg_in_tests: bool = false), /// Lint: PRINT_STDOUT, PRINT_STDERR. /// - /// Whether print macros (ex. `println!`) should be allowed in test functions + /// Whether print macros (ex. `println!`) should be allowed in test functions or `#[cfg(test)]` (allow_print_in_tests: bool = false), /// Lint: RESULT_LARGE_ERR. /// @@ -454,6 +454,11 @@ define_Conf! { /// configuration will cause restriction lints to trigger even /// if no suggestion can be made. (suppress_restriction_lint_in_const: bool = false), + /// Lint: MISSING_DOCS_IN_PRIVATE_ITEMS. + /// + /// Whether to **only** check for missing documentation in items visible within the current + /// crate. For example, `pub(crate)` items. + (missing_docs_in_crate_items: bool = false), } /// Search for the configuration file. diff --git a/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs b/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs index ee5e42bae0f1..b59ef4086cd8 100644 --- a/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs +++ b/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs @@ -3,7 +3,7 @@ use clippy_utils::source::snippet_with_applicability; use clippy_utils::{def_path_def_ids, is_lint_allowed, match_any_def_paths, peel_hir_expr_refs}; use if_chain::if_chain; use rustc_ast::ast::LitKind; -use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; @@ -44,7 +44,7 @@ impl_lint_pass!(UnnecessaryDefPath => [UNNECESSARY_DEF_PATH]); #[derive(Default)] pub struct UnnecessaryDefPath { - array_def_ids: FxHashSet<(DefId, Span)>, + array_def_ids: FxIndexSet<(DefId, Span)>, linted_def_ids: FxHashSet, } diff --git a/clippy_utils/src/macros.rs b/clippy_utils/src/macros.rs index 63dccbf697c2..be6133d32024 100644 --- a/clippy_utils/src/macros.rs +++ b/clippy_utils/src/macros.rs @@ -391,11 +391,18 @@ impl FormatString { }; let mut unescaped = String::with_capacity(inner.len()); + // Sometimes the original string comes from a macro which accepts a malformed string, such as in a + // #[display(""somestring)] attribute (accepted by the `displaythis` crate). Reconstructing the + // string from the span will not be possible, so we will just return None here. + let mut unparsable = false; unescape_literal(inner, mode, &mut |_, ch| match ch { Ok(ch) => unescaped.push(ch), Err(e) if !e.is_fatal() => (), - Err(e) => panic!("{e:?}"), + Err(_) => unparsable = true, }); + if unparsable { + return None; + } let mut parts = Vec::new(); let _: Option = for_each_expr(pieces, |expr| { diff --git a/clippy_utils/src/numeric_literal.rs b/clippy_utils/src/numeric_literal.rs index 42bdfd4827f1..c225398ad2a8 100644 --- a/clippy_utils/src/numeric_literal.rs +++ b/clippy_utils/src/numeric_literal.rs @@ -186,7 +186,7 @@ impl<'a> NumericLiteral<'a> { // 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(); + let _: Option = digits.next(); output.push('-'); } diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index 95eebab75677..4aae0f7284e4 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -115,6 +115,7 @@ pub const STD_FS_CREATE_DIR: [&str; 3] = ["std", "fs", "create_dir"]; pub const STD_IO_SEEK: [&str; 3] = ["std", "io", "Seek"]; pub const STD_IO_SEEK_FROM_CURRENT: [&str; 4] = ["std", "io", "SeekFrom", "Current"]; pub const STD_IO_SEEKFROM_START: [&str; 4] = ["std", "io", "SeekFrom", "Start"]; +pub const STD_PROCESS_COMMAND: [&str; 3] = ["std", "process", "Command"]; 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"]; diff --git a/clippy_utils/src/sugg.rs b/clippy_utils/src/sugg.rs index 78fb2e0eb7e6..51e270d330c8 100644 --- a/clippy_utils/src/sugg.rs +++ b/clippy_utils/src/sugg.rs @@ -20,7 +20,7 @@ use rustc_middle::mir::{FakeReadCause, Mutability}; use rustc_middle::ty; use rustc_span::source_map::{BytePos, CharPos, Pos, Span, SyntaxContext}; use std::borrow::Cow; -use std::fmt::{Display, Write as _}; +use std::fmt::{self, Display, Write as _}; use std::ops::{Add, Neg, Not, Sub}; /// A helper type to build suggestion correctly handling parentheses. @@ -932,7 +932,7 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> { 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}"); + let _: fmt::Result = 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()` @@ -947,7 +947,7 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> { // 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}"); + let _: fmt::Result = write!(self.suggestion_start, "{start_snip}{ident_str_with_proj}"); self.next_pos = span.hi(); return; }, @@ -1055,7 +1055,7 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> { } } - let _ = write!(self.suggestion_start, "{start_snip}{replacement_str}"); + let _: fmt::Result = write!(self.suggestion_start, "{start_snip}{replacement_str}"); } self.next_pos = span.hi(); } diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index f8ec4bb54930..25654e6957b9 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -17,8 +17,8 @@ use rustc_lint::LateContext; use rustc_middle::mir::interpret::{ConstValue, Scalar}; use rustc_middle::ty::{ self, AdtDef, AliasTy, AssocKind, Binder, BoundRegion, DefIdTree, FnSig, IntTy, List, ParamEnv, Predicate, - PredicateKind, Region, RegionKind, SubstsRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor, UintTy, - VariantDef, VariantDiscr, TypeVisitableExt, + PredicateKind, Region, RegionKind, SubstsRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, + TypeVisitor, UintTy, VariantDef, VariantDiscr, }; use rustc_middle::ty::{GenericArg, GenericArgKind}; use rustc_span::symbol::Ident; @@ -894,16 +894,29 @@ impl AdtVariantInfo { } /// Gets the struct or enum variant from the given `Res` -pub fn variant_of_res<'tcx>(cx: &LateContext<'tcx>, res: Res) -> Option<&'tcx VariantDef> { +pub fn adt_and_variant_of_res<'tcx>(cx: &LateContext<'tcx>, res: Res) -> Option<(AdtDef<'tcx>, &'tcx VariantDef)> { match res { - Res::Def(DefKind::Struct, id) => Some(cx.tcx.adt_def(id).non_enum_variant()), - Res::Def(DefKind::Variant, id) => Some(cx.tcx.adt_def(cx.tcx.parent(id)).variant_with_id(id)), - Res::Def(DefKind::Ctor(CtorOf::Struct, _), id) => Some(cx.tcx.adt_def(cx.tcx.parent(id)).non_enum_variant()), + Res::Def(DefKind::Struct, id) => { + let adt = cx.tcx.adt_def(id); + Some((adt, adt.non_enum_variant())) + }, + Res::Def(DefKind::Variant, id) => { + let adt = cx.tcx.adt_def(cx.tcx.parent(id)); + Some((adt, adt.variant_with_id(id))) + }, + Res::Def(DefKind::Ctor(CtorOf::Struct, _), id) => { + let adt = cx.tcx.adt_def(cx.tcx.parent(id)); + Some((adt, adt.non_enum_variant())) + }, Res::Def(DefKind::Ctor(CtorOf::Variant, _), id) => { let var_id = cx.tcx.parent(id); - Some(cx.tcx.adt_def(cx.tcx.parent(var_id)).variant_with_id(var_id)) + let adt = cx.tcx.adt_def(cx.tcx.parent(var_id)); + Some((adt, adt.variant_with_id(var_id))) + }, + Res::SelfCtor(id) => { + let adt = cx.tcx.type_of(id).subst_identity().ty_adt_def().unwrap(); + Some((adt, adt.non_enum_variant())) }, - Res::SelfCtor(id) => Some(cx.tcx.type_of(id).subst_identity().ty_adt_def().unwrap().non_enum_variant()), _ => None, } } diff --git a/lintcheck/src/config.rs b/lintcheck/src/config.rs index b8824024e6c7..e0244ddcecb8 100644 --- a/lintcheck/src/config.rs +++ b/lintcheck/src/config.rs @@ -35,7 +35,7 @@ fn get_clap_config() -> ArgMatches { .long("markdown") .help("Change the reports table to use markdown links"), Arg::new("recursive") - .long("--recursive") + .long("recursive") .help("Run clippy on the dependencies of crates specified in crates-toml") .conflicts_with("threads") .conflicts_with("fix"), diff --git a/lintcheck/src/main.rs b/lintcheck/src/main.rs index bd49f0960726..23c852980275 100644 --- a/lintcheck/src/main.rs +++ b/lintcheck/src/main.rs @@ -17,9 +17,9 @@ use crate::recursive::LintcheckServer; use std::collections::{HashMap, HashSet}; use std::env; use std::env::consts::EXE_SUFFIX; -use std::fmt::Write as _; +use std::fmt::{self, Write as _}; use std::fs; -use std::io::ErrorKind; +use std::io::{self, ErrorKind}; use std::path::{Path, PathBuf}; use std::process::Command; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -145,8 +145,8 @@ impl ClippyWarning { } let mut output = String::from("| "); - let _ = write!(output, "[`{file_with_pos}`]({file}#L{})", self.line); - let _ = write!(output, r#" | `{:<50}` | "{}" |"#, self.lint_type, self.message); + let _: fmt::Result = write!(output, "[`{file_with_pos}`]({file}#L{})", self.line); + let _: fmt::Result = write!(output, r#" | `{:<50}` | "{}" |"#, self.lint_type, self.message); output.push('\n'); output } else { @@ -632,7 +632,7 @@ fn main() { .unwrap(); let server = config.recursive.then(|| { - let _ = fs::remove_dir_all("target/lintcheck/shared_target_dir/recursive"); + let _: io::Result<()> = fs::remove_dir_all("target/lintcheck/shared_target_dir/recursive"); LintcheckServer::spawn(recursive_options) }); @@ -689,7 +689,7 @@ fn main() { write!(text, "{}", all_msgs.join("")).unwrap(); text.push_str("\n\n### ICEs:\n"); for (cratename, msg) in &ices { - let _ = write!(text, "{cratename}: '{msg}'"); + let _: fmt::Result = write!(text, "{cratename}: '{msg}'"); } println!("Writing logs to {}", config.lintcheck_results_path.display()); diff --git a/rust-toolchain b/rust-toolchain index adea8c53df27..cfe845ec78f0 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2023-02-10" +channel = "nightly-2023-02-25" components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] diff --git a/src/driver.rs b/src/driver.rs index 9ac849aecf1a..dd183362f276 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -209,10 +209,7 @@ fn report_clippy_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) { // Separate the output with an empty line eprintln!(); - let fallback_bundle = rustc_errors::fallback_fluent_bundle( - rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(), - false - ); + let fallback_bundle = rustc_errors::fallback_fluent_bundle(rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(), false); let emitter = Box::new(rustc_errors::emitter::EmitterWriter::stderr( rustc_errors::ColorConfig::Auto, None, diff --git a/tests/ui-internal/custom_ice_message.rs b/tests/ui-internal/custom_ice_message.rs index 4be04f77f5bd..837811bdf1ef 100644 --- a/tests/ui-internal/custom_ice_message.rs +++ b/tests/ui-internal/custom_ice_message.rs @@ -1,8 +1,9 @@ // rustc-env:RUST_BACKTRACE=0 // normalize-stderr-test: "Clippy version: .*" -> "Clippy version: foo" -// normalize-stderr-test: "internal_lints.rs:\d*:\d*" -> "internal_lints.rs" +// normalize-stderr-test: "produce_ice.rs:\d*:\d*" -> "produce_ice.rs" // normalize-stderr-test: "', .*clippy_lints" -> "', clippy_lints" // normalize-stderr-test: "'rustc'" -> "''" +// normalize-stderr-test: "(?ms)query stack during panic:\n.*end of query stack\n" -> "" #![deny(clippy::internal)] #![allow(clippy::missing_clippy_version_attribute)] diff --git a/tests/ui-internal/custom_ice_message.stderr b/tests/ui-internal/custom_ice_message.stderr index 2ba5890660fc..7ed0ef0274fa 100644 --- a/tests/ui-internal/custom_ice_message.stderr +++ b/tests/ui-internal/custom_ice_message.stderr @@ -1,4 +1,4 @@ -thread '' panicked at 'Would you like some help with that?', clippy_lints/src/utils/internal_lints/produce_ice.rs:28:9 +thread '' panicked at 'Would you like some help with that?', clippy_lints/src/utils/internal_lints/produce_ice.rs note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace error: internal compiler error: unexpected panic @@ -9,5 +9,3 @@ note: we would appreciate a bug report: https://github.com/rust-lang/rust-clippy note: Clippy version: foo -query stack during panic: -end of query stack diff --git a/tests/ui-internal/unnecessary_def_path_hardcoded_path.stderr b/tests/ui-internal/unnecessary_def_path_hardcoded_path.stderr index c1a10ba55ef8..3ca45404e44b 100644 --- a/tests/ui-internal/unnecessary_def_path_hardcoded_path.stderr +++ b/tests/ui-internal/unnecessary_def_path_hardcoded_path.stderr @@ -1,12 +1,3 @@ -error: hardcoded path to a diagnostic item - --> $DIR/unnecessary_def_path_hardcoded_path.rs:12:43 - | -LL | const DEREF_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "Deref", "deref"]; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: convert all references to use `sym::deref_method` - = note: `-D clippy::unnecessary-def-path` implied by `-D warnings` - error: hardcoded path to a diagnostic item --> $DIR/unnecessary_def_path_hardcoded_path.rs:10:36 | @@ -14,6 +5,7 @@ LL | const DEREF_TRAIT: [&str; 4] = ["core", "ops", "deref", "Deref"]; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: convert all references to use `sym::Deref` + = note: `-D clippy::unnecessary-def-path` implied by `-D warnings` error: hardcoded path to a language item --> $DIR/unnecessary_def_path_hardcoded_path.rs:11:40 @@ -23,5 +15,13 @@ LL | const DEREF_MUT_TRAIT: [&str; 4] = ["core", "ops", "deref", "DerefMut"] | = help: convert all references to use `LangItem::DerefMut` +error: hardcoded path to a diagnostic item + --> $DIR/unnecessary_def_path_hardcoded_path.rs:12:43 + | +LL | const DEREF_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "Deref", "deref"]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: convert all references to use `sym::deref_method` + error: aborting due to 3 previous errors diff --git a/tests/ui-toml/expect_used/expect_used.rs b/tests/ui-toml/expect_used/expect_used.rs index bff97d97df77..89f142a150d9 100644 --- a/tests/ui-toml/expect_used/expect_used.rs +++ b/tests/ui-toml/expect_used/expect_used.rs @@ -16,6 +16,18 @@ fn main() { expect_result(); } +#[test] +fn test_expect_option() { + let opt = Some(0); + let _ = opt.expect(""); +} + +#[test] +fn test_expect_result() { + let res: Result = Ok(0); + let _ = res.expect(""); +} + #[cfg(test)] mod issue9612 { // should not lint in `#[cfg(test)]` modules diff --git a/tests/ui-toml/pub_crate_missing_docs/clippy.toml b/tests/ui-toml/pub_crate_missing_docs/clippy.toml new file mode 100644 index 000000000000..ec210a987830 --- /dev/null +++ b/tests/ui-toml/pub_crate_missing_docs/clippy.toml @@ -0,0 +1 @@ +missing-docs-in-crate-items = true diff --git a/tests/ui-toml/pub_crate_missing_docs/pub_crate_missing_doc.rs b/tests/ui-toml/pub_crate_missing_docs/pub_crate_missing_doc.rs new file mode 100644 index 000000000000..830d71f61dd5 --- /dev/null +++ b/tests/ui-toml/pub_crate_missing_docs/pub_crate_missing_doc.rs @@ -0,0 +1,59 @@ +//! this is crate +#![allow(missing_docs)] +#![warn(clippy::missing_docs_in_private_items)] + +/// this is mod +mod my_mod { + /// some docs + fn priv_with_docs() {} + fn priv_no_docs() {} + /// some docs + pub(crate) fn crate_with_docs() {} + pub(crate) fn crate_no_docs() {} + /// some docs + pub(super) fn super_with_docs() {} + pub(super) fn super_no_docs() {} + + mod my_sub { + /// some docs + fn sub_priv_with_docs() {} + fn sub_priv_no_docs() {} + /// some docs + pub(crate) fn sub_crate_with_docs() {} + pub(crate) fn sub_crate_no_docs() {} + /// some docs + pub(super) fn sub_super_with_docs() {} + pub(super) fn sub_super_no_docs() {} + } + + /// some docs + pub(crate) struct CrateStructWithDocs { + /// some docs + pub(crate) crate_field_with_docs: (), + pub(crate) crate_field_no_docs: (), + /// some docs + priv_field_with_docs: (), + priv_field_no_docs: (), + } + + pub(crate) struct CrateStructNoDocs { + /// some docs + pub(crate) crate_field_with_docs: (), + pub(crate) crate_field_no_docs: (), + /// some docs + priv_field_with_docs: (), + priv_field_no_docs: (), + } +} + +/// some docs +type CrateTypedefWithDocs = String; +type CrateTypedefNoDocs = String; +/// some docs +pub type PubTypedefWithDocs = String; +pub type PubTypedefNoDocs = String; + +fn main() { + my_mod::crate_with_docs(); + my_mod::crate_no_docs(); +} diff --git a/tests/ui-toml/pub_crate_missing_docs/pub_crate_missing_doc.stderr b/tests/ui-toml/pub_crate_missing_docs/pub_crate_missing_doc.stderr new file mode 100644 index 000000000000..a474187050c1 --- /dev/null +++ b/tests/ui-toml/pub_crate_missing_docs/pub_crate_missing_doc.stderr @@ -0,0 +1,52 @@ +error: missing documentation for a function + --> $DIR/pub_crate_missing_doc.rs:12:5 + | +LL | pub(crate) fn crate_no_docs() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::missing-docs-in-private-items` implied by `-D warnings` + +error: missing documentation for a function + --> $DIR/pub_crate_missing_doc.rs:15:5 + | +LL | pub(super) fn super_no_docs() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a function + --> $DIR/pub_crate_missing_doc.rs:23:9 + | +LL | pub(crate) fn sub_crate_no_docs() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a struct field + --> $DIR/pub_crate_missing_doc.rs:33:9 + | +LL | pub(crate) crate_field_no_docs: (), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a struct + --> $DIR/pub_crate_missing_doc.rs:39:5 + | +LL | / pub(crate) struct CrateStructNoDocs { +LL | | /// some docs +LL | | pub(crate) crate_field_with_docs: (), +LL | | pub(crate) crate_field_no_docs: (), +... | +LL | | priv_field_no_docs: (), +LL | | } + | |_____^ + +error: missing documentation for a struct field + --> $DIR/pub_crate_missing_doc.rs:42:9 + | +LL | pub(crate) crate_field_no_docs: (), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a type alias + --> $DIR/pub_crate_missing_doc.rs:51:1 + | +LL | type CrateTypedefNoDocs = String; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 7 previous errors + diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr index a22c6a5a0607..6a246afac76e 100644 --- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr +++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -33,6 +33,7 @@ error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown fie max-struct-bools max-suggested-slice-pattern-length max-trait-bounds + missing-docs-in-crate-items msrv pass-by-value-size-limit single-char-binding-names-threshold diff --git a/tests/ui-toml/unwrap_used/unwrap_used.rs b/tests/ui-toml/unwrap_used/unwrap_used.rs index bc8e8c1f0703..6525ea5bfc3f 100644 --- a/tests/ui-toml/unwrap_used/unwrap_used.rs +++ b/tests/ui-toml/unwrap_used/unwrap_used.rs @@ -66,6 +66,12 @@ fn main() { } } +#[test] +fn test() { + let boxed_slice: Box<[u8]> = Box::new([0, 1, 2, 3]); + let _ = boxed_slice.get(1).unwrap(); +} + #[cfg(test)] mod issue9612 { // should not lint in `#[cfg(test)]` modules diff --git a/tests/ui-toml/unwrap_used/unwrap_used.stderr b/tests/ui-toml/unwrap_used/unwrap_used.stderr index 94b5ef663add..8a32750e3c92 100644 --- a/tests/ui-toml/unwrap_used/unwrap_used.stderr +++ b/tests/ui-toml/unwrap_used/unwrap_used.stderr @@ -188,10 +188,16 @@ 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:84:17 + --> $DIR/unwrap_used.rs:72:13 + | +LL | let _ = boxed_slice.get(1).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&boxed_slice[1]` + +error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise + --> $DIR/unwrap_used.rs:90:17 | LL | let _ = Box::new([0]).get(1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&Box::new([0])[1]` -error: aborting due to 27 previous errors +error: aborting due to 28 previous errors diff --git a/tests/ui/arithmetic_side_effects.rs b/tests/ui/arithmetic_side_effects.rs index 918cf81c600a..2611e3a785f6 100644 --- a/tests/ui/arithmetic_side_effects.rs +++ b/tests/ui/arithmetic_side_effects.rs @@ -13,6 +13,9 @@ use core::num::{Saturating, Wrapping}; +const ONE: i32 = 1; +const ZERO: i32 = 0; + #[derive(Clone, Copy)] pub struct Custom; @@ -182,6 +185,10 @@ pub fn non_overflowing_ops_or_ops_already_handled_by_the_compiler_should_not_tri _n += &0; _n -= 0; _n -= &0; + _n += ZERO; + _n += &ZERO; + _n -= ZERO; + _n -= &ZERO; _n /= 99; _n /= &99; _n %= 99; @@ -190,10 +197,18 @@ pub fn non_overflowing_ops_or_ops_already_handled_by_the_compiler_should_not_tri _n *= &0; _n *= 1; _n *= &1; + _n *= ZERO; + _n *= &ZERO; + _n *= ONE; + _n *= &ONE; _n += -0; _n += &-0; _n -= -0; _n -= &-0; + _n += -ZERO; + _n += &-ZERO; + _n -= -ZERO; + _n -= &-ZERO; _n /= -99; _n /= &-99; _n %= -99; @@ -208,10 +223,18 @@ pub fn non_overflowing_ops_or_ops_already_handled_by_the_compiler_should_not_tri _n = _n + &0; _n = 0 + _n; _n = &0 + _n; + _n = _n + ZERO; + _n = _n + &ZERO; + _n = ZERO + _n; + _n = &ZERO + _n; _n = _n - 0; _n = _n - &0; _n = 0 - _n; _n = &0 - _n; + _n = _n - ZERO; + _n = _n - &ZERO; + _n = ZERO - _n; + _n = &ZERO - _n; _n = _n / 99; _n = _n / &99; _n = _n % 99; @@ -222,6 +245,10 @@ pub fn non_overflowing_ops_or_ops_already_handled_by_the_compiler_should_not_tri _n = &0 * _n; _n = _n * 1; _n = _n * &1; + _n = ZERO * _n; + _n = &ZERO * _n; + _n = _n * ONE; + _n = _n * &ONE; _n = 1 * _n; _n = &1 * _n; _n = 23 + 85; diff --git a/tests/ui/arithmetic_side_effects.stderr b/tests/ui/arithmetic_side_effects.stderr index 5e349f6b497c..17a2448fbfca 100644 --- a/tests/ui/arithmetic_side_effects.stderr +++ b/tests/ui/arithmetic_side_effects.stderr @@ -1,5 +1,5 @@ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:243:5 + --> $DIR/arithmetic_side_effects.rs:270:5 | LL | _n += 1; | ^^^^^^^ @@ -7,589 +7,589 @@ LL | _n += 1; = note: `-D clippy::arithmetic-side-effects` implied by `-D warnings` error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:244:5 + --> $DIR/arithmetic_side_effects.rs:271:5 | LL | _n += &1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:245:5 + --> $DIR/arithmetic_side_effects.rs:272:5 | LL | _n -= 1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:246:5 + --> $DIR/arithmetic_side_effects.rs:273:5 | LL | _n -= &1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:247:5 + --> $DIR/arithmetic_side_effects.rs:274:5 | LL | _n /= 0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:248:5 + --> $DIR/arithmetic_side_effects.rs:275:5 | LL | _n /= &0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:249:5 + --> $DIR/arithmetic_side_effects.rs:276:5 | LL | _n %= 0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:250:5 + --> $DIR/arithmetic_side_effects.rs:277:5 | LL | _n %= &0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:251:5 + --> $DIR/arithmetic_side_effects.rs:278:5 | LL | _n *= 2; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:252:5 + --> $DIR/arithmetic_side_effects.rs:279:5 | LL | _n *= &2; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:253:5 + --> $DIR/arithmetic_side_effects.rs:280:5 | LL | _n += -1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:254:5 + --> $DIR/arithmetic_side_effects.rs:281:5 | LL | _n += &-1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:255:5 + --> $DIR/arithmetic_side_effects.rs:282:5 | LL | _n -= -1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:256:5 + --> $DIR/arithmetic_side_effects.rs:283:5 | LL | _n -= &-1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:257:5 + --> $DIR/arithmetic_side_effects.rs:284:5 | LL | _n /= -0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:258:5 + --> $DIR/arithmetic_side_effects.rs:285:5 | LL | _n /= &-0; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:259:5 + --> $DIR/arithmetic_side_effects.rs:286:5 | LL | _n %= -0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:260:5 + --> $DIR/arithmetic_side_effects.rs:287:5 | LL | _n %= &-0; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:261:5 + --> $DIR/arithmetic_side_effects.rs:288:5 | LL | _n *= -2; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:262:5 + --> $DIR/arithmetic_side_effects.rs:289:5 | LL | _n *= &-2; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:263:5 + --> $DIR/arithmetic_side_effects.rs:290:5 | LL | _custom += Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:264:5 + --> $DIR/arithmetic_side_effects.rs:291:5 | LL | _custom += &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:265:5 + --> $DIR/arithmetic_side_effects.rs:292:5 | LL | _custom -= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:266:5 + --> $DIR/arithmetic_side_effects.rs:293:5 | LL | _custom -= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:267:5 + --> $DIR/arithmetic_side_effects.rs:294:5 | LL | _custom /= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:268:5 + --> $DIR/arithmetic_side_effects.rs:295:5 | LL | _custom /= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:269:5 + --> $DIR/arithmetic_side_effects.rs:296:5 | LL | _custom %= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:270:5 + --> $DIR/arithmetic_side_effects.rs:297:5 | LL | _custom %= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:271:5 + --> $DIR/arithmetic_side_effects.rs:298:5 | LL | _custom *= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:272:5 + --> $DIR/arithmetic_side_effects.rs:299:5 | LL | _custom *= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:273:5 + --> $DIR/arithmetic_side_effects.rs:300:5 | LL | _custom += -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:274:5 + --> $DIR/arithmetic_side_effects.rs:301:5 | LL | _custom += &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:275:5 + --> $DIR/arithmetic_side_effects.rs:302:5 | LL | _custom -= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:276:5 + --> $DIR/arithmetic_side_effects.rs:303:5 | LL | _custom -= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:277:5 + --> $DIR/arithmetic_side_effects.rs:304:5 | LL | _custom /= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:278:5 + --> $DIR/arithmetic_side_effects.rs:305:5 | LL | _custom /= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:279:5 + --> $DIR/arithmetic_side_effects.rs:306:5 | LL | _custom %= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:280:5 + --> $DIR/arithmetic_side_effects.rs:307:5 | LL | _custom %= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:281:5 + --> $DIR/arithmetic_side_effects.rs:308:5 | LL | _custom *= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:282:5 + --> $DIR/arithmetic_side_effects.rs:309:5 | LL | _custom *= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:285:10 + --> $DIR/arithmetic_side_effects.rs:312:10 | LL | _n = _n + 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:286:10 + --> $DIR/arithmetic_side_effects.rs:313:10 | LL | _n = _n + &1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:287:10 + --> $DIR/arithmetic_side_effects.rs:314:10 | LL | _n = 1 + _n; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:288:10 + --> $DIR/arithmetic_side_effects.rs:315:10 | LL | _n = &1 + _n; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:289:10 + --> $DIR/arithmetic_side_effects.rs:316:10 | LL | _n = _n - 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:290:10 + --> $DIR/arithmetic_side_effects.rs:317:10 | LL | _n = _n - &1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:291:10 + --> $DIR/arithmetic_side_effects.rs:318:10 | LL | _n = 1 - _n; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:292:10 + --> $DIR/arithmetic_side_effects.rs:319:10 | LL | _n = &1 - _n; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:293:10 + --> $DIR/arithmetic_side_effects.rs:320:10 | LL | _n = _n / 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:294:10 + --> $DIR/arithmetic_side_effects.rs:321:10 | LL | _n = _n / &0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:295:10 + --> $DIR/arithmetic_side_effects.rs:322:10 | LL | _n = _n % 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:296:10 + --> $DIR/arithmetic_side_effects.rs:323:10 | LL | _n = _n % &0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:297:10 + --> $DIR/arithmetic_side_effects.rs:324:10 | LL | _n = _n * 2; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:298:10 + --> $DIR/arithmetic_side_effects.rs:325:10 | LL | _n = _n * &2; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:299:10 + --> $DIR/arithmetic_side_effects.rs:326:10 | LL | _n = 2 * _n; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:300:10 + --> $DIR/arithmetic_side_effects.rs:327:10 | LL | _n = &2 * _n; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:301:10 + --> $DIR/arithmetic_side_effects.rs:328:10 | LL | _n = 23 + &85; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:302:10 + --> $DIR/arithmetic_side_effects.rs:329:10 | LL | _n = &23 + 85; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:303:10 + --> $DIR/arithmetic_side_effects.rs:330:10 | LL | _n = &23 + &85; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:304:15 + --> $DIR/arithmetic_side_effects.rs:331:15 | LL | _custom = _custom + _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:305:15 + --> $DIR/arithmetic_side_effects.rs:332:15 | LL | _custom = _custom + &_custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:306:15 + --> $DIR/arithmetic_side_effects.rs:333:15 | LL | _custom = Custom + _custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:307:15 + --> $DIR/arithmetic_side_effects.rs:334:15 | LL | _custom = &Custom + _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:308:15 + --> $DIR/arithmetic_side_effects.rs:335:15 | LL | _custom = _custom - Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:309:15 + --> $DIR/arithmetic_side_effects.rs:336:15 | LL | _custom = _custom - &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:310:15 + --> $DIR/arithmetic_side_effects.rs:337:15 | LL | _custom = Custom - _custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:311:15 + --> $DIR/arithmetic_side_effects.rs:338:15 | LL | _custom = &Custom - _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:312:15 + --> $DIR/arithmetic_side_effects.rs:339:15 | LL | _custom = _custom / Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:313:15 + --> $DIR/arithmetic_side_effects.rs:340:15 | LL | _custom = _custom / &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:314:15 + --> $DIR/arithmetic_side_effects.rs:341:15 | LL | _custom = _custom % Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:315:15 + --> $DIR/arithmetic_side_effects.rs:342:15 | LL | _custom = _custom % &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:316:15 + --> $DIR/arithmetic_side_effects.rs:343:15 | LL | _custom = _custom * Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:317:15 + --> $DIR/arithmetic_side_effects.rs:344:15 | LL | _custom = _custom * &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:318:15 + --> $DIR/arithmetic_side_effects.rs:345:15 | LL | _custom = Custom * _custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:319:15 + --> $DIR/arithmetic_side_effects.rs:346:15 | LL | _custom = &Custom * _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:320:15 + --> $DIR/arithmetic_side_effects.rs:347:15 | LL | _custom = Custom + &Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:321:15 + --> $DIR/arithmetic_side_effects.rs:348:15 | LL | _custom = &Custom + Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:322:15 + --> $DIR/arithmetic_side_effects.rs:349:15 | LL | _custom = &Custom + &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:325:10 + --> $DIR/arithmetic_side_effects.rs:352:10 | LL | _n = -_n; | ^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:326:10 + --> $DIR/arithmetic_side_effects.rs:353:10 | LL | _n = -&_n; | ^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:327:15 + --> $DIR/arithmetic_side_effects.rs:354:15 | LL | _custom = -_custom; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:328:15 + --> $DIR/arithmetic_side_effects.rs:355:15 | LL | _custom = -&_custom; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:337:5 + --> $DIR/arithmetic_side_effects.rs:364:5 | LL | 1 + i; | ^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:338:5 + --> $DIR/arithmetic_side_effects.rs:365:5 | LL | i * 2; | ^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:340:5 + --> $DIR/arithmetic_side_effects.rs:367:5 | LL | i - 2 + 2 - i; | ^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:341:5 + --> $DIR/arithmetic_side_effects.rs:368:5 | LL | -i; | ^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:342:5 + --> $DIR/arithmetic_side_effects.rs:369:5 | LL | i >> 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:343:5 + --> $DIR/arithmetic_side_effects.rs:370:5 | LL | i << 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:352:5 + --> $DIR/arithmetic_side_effects.rs:379:5 | LL | i += 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:353:5 + --> $DIR/arithmetic_side_effects.rs:380:5 | LL | i -= 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:354:5 + --> $DIR/arithmetic_side_effects.rs:381:5 | LL | i *= 2; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:356:5 + --> $DIR/arithmetic_side_effects.rs:383:5 | LL | i /= 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:358:5 + --> $DIR/arithmetic_side_effects.rs:385:5 | LL | i /= var1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:359:5 + --> $DIR/arithmetic_side_effects.rs:386:5 | LL | i /= var2; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:361:5 + --> $DIR/arithmetic_side_effects.rs:388:5 | LL | i %= 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:363:5 + --> $DIR/arithmetic_side_effects.rs:390:5 | LL | i %= var1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:364:5 + --> $DIR/arithmetic_side_effects.rs:391:5 | LL | i %= var2; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:365:5 + --> $DIR/arithmetic_side_effects.rs:392:5 | LL | i <<= 3; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:366:5 + --> $DIR/arithmetic_side_effects.rs:393:5 | LL | i >>= 2; | ^^^^^^^ diff --git a/tests/ui/box_default.fixed b/tests/ui/box_default.fixed index 7e9f074fdcab..59c0baf8718a 100644 --- a/tests/ui/box_default.fixed +++ b/tests/ui/box_default.fixed @@ -33,6 +33,7 @@ fn main() { let _vec4: Box<_> = Box::>::default(); let _more = ret_ty_fn(); call_ty_fn(Box::default()); + issue_10381(); } fn ret_ty_fn() -> Box { @@ -65,3 +66,20 @@ fn issue_10089() { let _ = Box::::default(); }; } + +fn issue_10381() { + #[derive(Default)] + pub struct Foo {} + pub trait Bar {} + impl Bar for Foo {} + + fn maybe_get_bar(i: u32) -> Option> { + if i % 2 == 0 { + Some(Box::::default()) + } else { + None + } + } + + assert!(maybe_get_bar(2).is_some()); +} diff --git a/tests/ui/box_default.rs b/tests/ui/box_default.rs index 5c8d0b8354cc..f7d832193a3a 100644 --- a/tests/ui/box_default.rs +++ b/tests/ui/box_default.rs @@ -33,6 +33,7 @@ fn main() { let _vec4: Box<_> = Box::new(Vec::from([false; 0])); let _more = ret_ty_fn(); call_ty_fn(Box::new(u8::default())); + issue_10381(); } fn ret_ty_fn() -> Box { @@ -65,3 +66,20 @@ fn issue_10089() { let _ = Box::new(WeirdPathed::default()); }; } + +fn issue_10381() { + #[derive(Default)] + pub struct Foo {} + pub trait Bar {} + impl Bar for Foo {} + + fn maybe_get_bar(i: u32) -> Option> { + if i % 2 == 0 { + Some(Box::new(Foo::default())) + } else { + None + } + } + + assert!(maybe_get_bar(2).is_some()); +} diff --git a/tests/ui/box_default.stderr b/tests/ui/box_default.stderr index 249eb340f96c..78e17b9f0359 100644 --- a/tests/ui/box_default.stderr +++ b/tests/ui/box_default.stderr @@ -73,22 +73,28 @@ LL | call_ty_fn(Box::new(u8::default())); | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Box::default()` error: `Box::new(_)` of default value - --> $DIR/box_default.rs:39:5 + --> $DIR/box_default.rs:40:5 | LL | Box::new(bool::default()) | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Box::::default()` error: `Box::new(_)` of default value - --> $DIR/box_default.rs:56:28 + --> $DIR/box_default.rs:57:28 | LL | let _: Box = Box::new(ImplementsDefault::default()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Box::::default()` error: `Box::new(_)` of default value - --> $DIR/box_default.rs:65:17 + --> $DIR/box_default.rs:66:17 | LL | let _ = Box::new(WeirdPathed::default()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Box::::default()` -error: aborting due to 15 previous errors +error: `Box::new(_)` of default value + --> $DIR/box_default.rs:78:18 + | +LL | Some(Box::new(Foo::default())) + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Box::::default()` + +error: aborting due to 16 previous errors diff --git a/tests/ui/bytes_nth.fixed b/tests/ui/bytes_nth.fixed index b1fb2e16bd58..a35c679afb71 100644 --- a/tests/ui/bytes_nth.fixed +++ b/tests/ui/bytes_nth.fixed @@ -5,7 +5,7 @@ fn main() { let s = String::from("String"); - let _ = s.as_bytes().get(3); - let _ = &s.as_bytes().get(3); - let _ = s[..].as_bytes().get(3); + let _ = s.as_bytes().get(3).copied(); + let _ = &s.as_bytes()[3]; + let _ = s[..].as_bytes().get(3).copied(); } diff --git a/tests/ui/bytes_nth.rs b/tests/ui/bytes_nth.rs index 034c54e6a420..1ecffea53035 100644 --- a/tests/ui/bytes_nth.rs +++ b/tests/ui/bytes_nth.rs @@ -6,6 +6,6 @@ fn main() { let s = String::from("String"); let _ = s.bytes().nth(3); - let _ = &s.bytes().nth(3); + let _ = &s.bytes().nth(3).unwrap(); let _ = s[..].bytes().nth(3); } diff --git a/tests/ui/bytes_nth.stderr b/tests/ui/bytes_nth.stderr index 9851d4791d8d..e8b15027829e 100644 --- a/tests/ui/bytes_nth.stderr +++ b/tests/ui/bytes_nth.stderr @@ -2,21 +2,21 @@ error: called `.bytes().nth()` on a `String` --> $DIR/bytes_nth.rs:8:13 | LL | let _ = s.bytes().nth(3); - | ^^^^^^^^^^^^^^^^ help: try: `s.as_bytes().get(3)` + | ^^^^^^^^^^^^^^^^ help: try: `s.as_bytes().get(3).copied()` | = note: `-D clippy::bytes-nth` implied by `-D warnings` -error: called `.bytes().nth()` on a `String` +error: called `.bytes().nth().unwrap()` on a `String` --> $DIR/bytes_nth.rs:9:14 | -LL | let _ = &s.bytes().nth(3); - | ^^^^^^^^^^^^^^^^ help: try: `s.as_bytes().get(3)` +LL | let _ = &s.bytes().nth(3).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `s.as_bytes()[3]` error: called `.bytes().nth()` on a `str` --> $DIR/bytes_nth.rs:10:13 | LL | let _ = s[..].bytes().nth(3); - | ^^^^^^^^^^^^^^^^^^^^ help: try: `s[..].as_bytes().get(3)` + | ^^^^^^^^^^^^^^^^^^^^ help: try: `s[..].as_bytes().get(3).copied()` error: aborting due to 3 previous errors diff --git a/tests/ui/cast.stderr b/tests/ui/cast.stderr index 4af1de9aa38d..451078de23b2 100644 --- a/tests/ui/cast.stderr +++ b/tests/ui/cast.stderr @@ -42,7 +42,7 @@ error: casting `f32` to `i32` may truncate the value LL | 1f32 as i32; | ^^^^^^^^^^^ | - = help: if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ... + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... = note: `-D clippy::cast-possible-truncation` implied by `-D warnings` help: ... or use `try_from` and handle the error accordingly | @@ -55,7 +55,7 @@ error: casting `f32` to `u32` may truncate the value LL | 1f32 as u32; | ^^^^^^^^^^^ | - = help: if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ... + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | LL | u32::try_from(1f32); @@ -75,7 +75,7 @@ error: casting `f64` to `f32` may truncate the value LL | 1f64 as f32; | ^^^^^^^^^^^ | - = help: if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ... + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | LL | f32::try_from(1f64); @@ -87,7 +87,7 @@ error: casting `i32` to `i8` may truncate the value LL | 1i32 as i8; | ^^^^^^^^^^ | - = help: if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ... + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | LL | i8::try_from(1i32); @@ -99,7 +99,7 @@ error: casting `i32` to `u8` may truncate the value LL | 1i32 as u8; | ^^^^^^^^^^ | - = help: if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ... + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | LL | u8::try_from(1i32); @@ -111,7 +111,7 @@ error: casting `f64` to `isize` may truncate the value LL | 1f64 as isize; | ^^^^^^^^^^^^^ | - = help: if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ... + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | LL | isize::try_from(1f64); @@ -123,7 +123,7 @@ error: casting `f64` to `usize` may truncate the value LL | 1f64 as usize; | ^^^^^^^^^^^^^ | - = help: if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ... + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | LL | usize::try_from(1f64); @@ -141,7 +141,7 @@ error: casting `u32` to `u16` may truncate the value LL | 1f32 as u32 as u16; | ^^^^^^^^^^^^^^^^^^ | - = help: if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ... + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | LL | u16::try_from(1f32 as u32); @@ -153,7 +153,7 @@ error: casting `f32` to `u32` may truncate the value LL | 1f32 as u32 as u16; | ^^^^^^^^^^^ | - = help: if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ... + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | LL | u32::try_from(1f32) as u16; @@ -215,7 +215,7 @@ error: casting `i64` to `i8` may truncate the value LL | (-99999999999i64).min(1) as i8; // should be linted because signed | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ... + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | LL | i8::try_from((-99999999999i64).min(1)); // should be linted because signed @@ -227,7 +227,7 @@ error: casting `u64` to `u8` may truncate the value LL | 999999u64.clamp(0, 256) as u8; // should still be linted | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ... + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | LL | u8::try_from(999999u64.clamp(0, 256)); // should still be linted @@ -239,7 +239,7 @@ error: casting `main::E2` to `u8` may truncate the value LL | let _ = self as u8; | ^^^^^^^^^^ | - = help: if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ... + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | LL | let _ = u8::try_from(self); @@ -259,7 +259,7 @@ error: casting `main::E5` to `i8` may truncate the value LL | let _ = self as i8; | ^^^^^^^^^^ | - = help: if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ... + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | LL | let _ = i8::try_from(self); @@ -277,7 +277,7 @@ error: casting `main::E6` to `i16` may truncate the value LL | let _ = self as i16; | ^^^^^^^^^^^ | - = help: if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ... + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | LL | let _ = i16::try_from(self); @@ -289,7 +289,7 @@ error: casting `main::E7` to `usize` may truncate the value on targets with 32-b LL | let _ = self as usize; | ^^^^^^^^^^^^^ | - = help: if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ... + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | LL | let _ = usize::try_from(self); @@ -301,7 +301,7 @@ error: casting `main::E10` to `u16` may truncate the value LL | let _ = self as u16; | ^^^^^^^^^^^ | - = help: if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ... + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | LL | let _ = u16::try_from(self); @@ -313,7 +313,7 @@ error: casting `u32` to `u8` may truncate the value LL | let c = (q >> 16) as u8; | ^^^^^^^^^^^^^^^ | - = help: if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ... + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | LL | let c = u8::try_from((q >> 16)); @@ -325,7 +325,7 @@ error: casting `u32` to `u8` may truncate the value LL | let c = (q / 1000) as u8; | ^^^^^^^^^^^^^^^^ | - = help: if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ... + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | LL | let c = u8::try_from((q / 1000)); diff --git a/tests/ui/cast_size.stderr b/tests/ui/cast_size.stderr index 8acf26049f4d..6d2d49d9ed20 100644 --- a/tests/ui/cast_size.stderr +++ b/tests/ui/cast_size.stderr @@ -4,7 +4,7 @@ error: casting `isize` to `i8` may truncate the value LL | 1isize as i8; | ^^^^^^^^^^^^ | - = help: if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ... + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... = note: `-D clippy::cast-possible-truncation` implied by `-D warnings` help: ... or use `try_from` and handle the error accordingly | @@ -43,7 +43,7 @@ error: casting `isize` to `i32` may truncate the value on targets with 64-bit wi LL | 1isize as i32; | ^^^^^^^^^^^^^ | - = help: if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ... + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | LL | i32::try_from(1isize); @@ -55,7 +55,7 @@ error: casting `isize` to `u32` may truncate the value on targets with 64-bit wi LL | 1isize as u32; | ^^^^^^^^^^^^^ | - = help: if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ... + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | LL | u32::try_from(1isize); @@ -67,7 +67,7 @@ error: casting `usize` to `u32` may truncate the value on targets with 64-bit wi LL | 1usize as u32; | ^^^^^^^^^^^^^ | - = help: if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ... + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | LL | u32::try_from(1usize); @@ -79,7 +79,7 @@ error: casting `usize` to `i32` may truncate the value on targets with 64-bit wi LL | 1usize as i32; | ^^^^^^^^^^^^^ | - = help: if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ... + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | LL | i32::try_from(1usize); @@ -99,7 +99,7 @@ error: casting `i64` to `isize` may truncate the value on targets with 32-bit wi LL | 1i64 as isize; | ^^^^^^^^^^^^^ | - = help: if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ... + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | LL | isize::try_from(1i64); @@ -111,7 +111,7 @@ error: casting `i64` to `usize` may truncate the value on targets with 32-bit wi LL | 1i64 as usize; | ^^^^^^^^^^^^^ | - = help: if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ... + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | LL | usize::try_from(1i64); @@ -123,7 +123,7 @@ error: casting `u64` to `isize` may truncate the value on targets with 32-bit wi LL | 1u64 as isize; | ^^^^^^^^^^^^^ | - = help: if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ... + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | LL | isize::try_from(1u64); @@ -141,7 +141,7 @@ error: casting `u64` to `usize` may truncate the value on targets with 32-bit wi LL | 1u64 as usize; | ^^^^^^^^^^^^^ | - = help: if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ... + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | LL | usize::try_from(1u64); diff --git a/tests/ui/crashes/needless_pass_by_value-w-late-bound.rs b/tests/ui/crashes/needless_pass_by_value-w-late-bound.rs new file mode 100644 index 000000000000..dd3d8b8b6d15 --- /dev/null +++ b/tests/ui/crashes/needless_pass_by_value-w-late-bound.rs @@ -0,0 +1,9 @@ +// https://github.com/rust-lang/rust/issues/107147 + +#![warn(clippy::needless_pass_by_value)] + +struct Foo<'a>(&'a [(); 100]); + +fn test(x: Foo<'_>) {} + +fn main() {} diff --git a/tests/ui/crashes/needless_pass_by_value-w-late-bound.stderr b/tests/ui/crashes/needless_pass_by_value-w-late-bound.stderr new file mode 100644 index 000000000000..7a0a648974fc --- /dev/null +++ b/tests/ui/crashes/needless_pass_by_value-w-late-bound.stderr @@ -0,0 +1,15 @@ +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value-w-late-bound.rs:7:12 + | +LL | fn test(x: Foo<'_>) {} + | ^^^^^^^ help: consider taking a reference instead: `&Foo<'_>` + | +help: consider marking this type as `Copy` + --> $DIR/needless_pass_by_value-w-late-bound.rs:5:1 + | +LL | struct Foo<'a>(&'a [(); 100]); + | ^^^^^^^^^^^^^^ + = note: `-D clippy::needless-pass-by-value` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/tests/ui/doc/doc-fixable.fixed b/tests/ui/doc/doc-fixable.fixed index 747801b40ee1..ecb0bf3644ee 100644 --- a/tests/ui/doc/doc-fixable.fixed +++ b/tests/ui/doc/doc-fixable.fixed @@ -78,7 +78,7 @@ fn test_allowed() { /// This test has [a `link_with_underscores`][chunked-example] inside it. See #823. /// See also [the issue tracker](https://github.com/rust-lang/rust-clippy/search?q=clippy::doc_markdown&type=Issues) /// on GitHub (which is a camel-cased word, but is OK). And here is another [inline link][inline_link]. -/// It can also be [`inline_link2`]. +/// It can also be [inline_link2]. A link to [StackOverflow](https://stackoverflow.com) is also acceptable. /// /// [chunked-example]: https://en.wikipedia.org/wiki/Chunked_transfer_encoding#Example /// [inline_link]: https://foobar diff --git a/tests/ui/doc/doc-fixable.rs b/tests/ui/doc/doc-fixable.rs index f3cf966157a6..11c48dd103d6 100644 --- a/tests/ui/doc/doc-fixable.rs +++ b/tests/ui/doc/doc-fixable.rs @@ -75,10 +75,10 @@ fn test_units() { fn test_allowed() { } -/// This test has [a link_with_underscores][chunked-example] inside it. See #823. +/// This test has [a `link_with_underscores`][chunked-example] inside it. See #823. /// See also [the issue tracker](https://github.com/rust-lang/rust-clippy/search?q=clippy::doc_markdown&type=Issues) /// on GitHub (which is a camel-cased word, but is OK). And here is another [inline link][inline_link]. -/// It can also be [inline_link2]. +/// It can also be [inline_link2]. A link to [StackOverflow](https://stackoverflow.com) is also acceptable. /// /// [chunked-example]: https://en.wikipedia.org/wiki/Chunked_transfer_encoding#Example /// [inline_link]: https://foobar diff --git a/tests/ui/doc/doc-fixable.stderr b/tests/ui/doc/doc-fixable.stderr index 40345370c049..6c67c903c750 100644 --- a/tests/ui/doc/doc-fixable.stderr +++ b/tests/ui/doc/doc-fixable.stderr @@ -142,28 +142,6 @@ help: try LL | /// `be_sure_we_got_to_the_end_of_it` | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -error: item in documentation is missing backticks - --> $DIR/doc-fixable.rs:78:22 - | -LL | /// This test has [a link_with_underscores][chunked-example] inside it. See #823. - | ^^^^^^^^^^^^^^^^^^^^^ - | -help: try - | -LL | /// This test has [a `link_with_underscores`][chunked-example] inside it. See #823. - | ~~~~~~~~~~~~~~~~~~~~~~~ - -error: item in documentation is missing backticks - --> $DIR/doc-fixable.rs:81:21 - | -LL | /// It can also be [inline_link2]. - | ^^^^^^^^^^^^ - | -help: try - | -LL | /// It can also be [`inline_link2`]. - | ~~~~~~~~~~~~~~ - error: item in documentation is missing backticks --> $DIR/doc-fixable.rs:91:5 | @@ -329,5 +307,5 @@ help: try LL | /// An iterator over `mycrate::Collection`'s values. | ~~~~~~~~~~~~~~~~~~~~~ -error: aborting due to 30 previous errors +error: aborting due to 28 previous errors diff --git a/tests/ui/entry.fixed b/tests/ui/entry.fixed index 79c29c04e059..dbe09e0ff3c6 100644 --- a/tests/ui/entry.fixed +++ b/tests/ui/entry.fixed @@ -152,4 +152,18 @@ fn hash_map(m: &mut HashMap, m2: &mut HashMa }); } +// Issue 10331 +// do not suggest a bad expansion because the compiler unrolls the first +// occurrence of the loop +pub fn issue_10331() { + let mut m = HashMap::new(); + let mut i = 0; + let mut x = 0; + while !m.contains_key(&x) { + m.insert(x, i); + i += 1; + x += 1; + } +} + fn main() {} diff --git a/tests/ui/entry.rs b/tests/ui/entry.rs index 2d7985457d8b..30fed34fc5de 100644 --- a/tests/ui/entry.rs +++ b/tests/ui/entry.rs @@ -156,4 +156,18 @@ fn hash_map(m: &mut HashMap, m2: &mut HashMa } } +// Issue 10331 +// do not suggest a bad expansion because the compiler unrolls the first +// occurrence of the loop +pub fn issue_10331() { + let mut m = HashMap::new(); + let mut i = 0; + let mut x = 0; + while !m.contains_key(&x) { + m.insert(x, i); + i += 1; + x += 1; + } +} + fn main() {} diff --git a/tests/ui/explicit_auto_deref.fixed b/tests/ui/explicit_auto_deref.fixed index 475fae5e823b..5d40c850424f 100644 --- a/tests/ui/explicit_auto_deref.fixed +++ b/tests/ui/explicit_auto_deref.fixed @@ -269,6 +269,9 @@ fn main() { trait WithAssoc { type Assoc: ?Sized; + fn to_assoc(&self) -> &Self::Assoc { + panic!() + } } impl WithAssoc for String { type Assoc = str; @@ -281,4 +284,15 @@ fn main() { // Issue #9901 fn takes_ref(_: &i32) {} takes_ref(*Box::new(&0i32)); + + // Issue #10384 + impl<'a> WithAssoc for &'a u32 { + type Assoc = dyn core::fmt::Display; + fn to_assoc(&self) -> &Self::Assoc { + *self + } + } + fn return_dyn_assoc<'a>(x: &'a &'a u32) -> &'a <&'a u32 as WithAssoc>::Assoc { + *x + } } diff --git a/tests/ui/explicit_auto_deref.rs b/tests/ui/explicit_auto_deref.rs index c1894258f4d8..79e03f4d76c1 100644 --- a/tests/ui/explicit_auto_deref.rs +++ b/tests/ui/explicit_auto_deref.rs @@ -269,6 +269,9 @@ fn main() { trait WithAssoc { type Assoc: ?Sized; + fn to_assoc(&self) -> &Self::Assoc { + panic!() + } } impl WithAssoc for String { type Assoc = str; @@ -281,4 +284,15 @@ fn main() { // Issue #9901 fn takes_ref(_: &i32) {} takes_ref(*Box::new(&0i32)); + + // Issue #10384 + impl<'a> WithAssoc for &'a u32 { + type Assoc = dyn core::fmt::Display; + fn to_assoc(&self) -> &Self::Assoc { + *self + } + } + fn return_dyn_assoc<'a>(x: &'a &'a u32) -> &'a <&'a u32 as WithAssoc>::Assoc { + *x + } } diff --git a/tests/ui/extra_unused_type_parameters.rs b/tests/ui/extra_unused_type_parameters.rs index 5cb80cb6233f..480174342765 100644 --- a/tests/ui/extra_unused_type_parameters.rs +++ b/tests/ui/extra_unused_type_parameters.rs @@ -1,11 +1,17 @@ #![allow(unused, clippy::needless_lifetimes)] #![warn(clippy::extra_unused_type_parameters)] -fn unused_ty(x: u8) {} +fn unused_ty(x: u8) { + unimplemented!() +} -fn unused_multi(x: u8) {} +fn unused_multi(x: u8) { + unimplemented!() +} -fn unused_with_lt<'a, T>(x: &'a u8) {} +fn unused_with_lt<'a, T>(x: &'a u8) { + unimplemented!() +} fn used_ty(x: T, y: u8) {} @@ -15,15 +21,20 @@ fn used_ret(x: u8) -> T { T::default() } -fn unused_bounded(x: U) {} +fn unused_bounded(x: U) { + unimplemented!(); +} fn unused_where_clause(x: U) where T: Default, { + unimplemented!(); } -fn some_unused, E>(b: B, c: C) {} +fn some_unused, E>(b: B, c: C) { + unimplemented!(); +} fn used_opaque(iter: impl Iterator) -> usize { iter.count() @@ -46,7 +57,9 @@ fn used_closure() -> impl Fn() { struct S; impl S { - fn unused_ty_impl(&self) {} + fn unused_ty_impl(&self) { + unimplemented!() + } } // Don't lint on trait methods @@ -66,4 +79,32 @@ where .filter_map(move |(i, a)| if i == index { None } else { Some(a) }) } +fn unused_opaque(dummy: impl Default) { + unimplemented!() +} + +mod unexported_trait_bounds { + mod private { + pub trait Private {} + } + + fn priv_trait_bound() { + unimplemented!(); + } + + fn unused_with_priv_trait_bound() { + unimplemented!(); + } +} + +mod issue10319 { + fn assert_send() {} + + fn assert_send_where() + where + T: Send, + { + } +} + fn main() {} diff --git a/tests/ui/extra_unused_type_parameters.stderr b/tests/ui/extra_unused_type_parameters.stderr index 1c8dd53e6385..86c88fc9bf00 100644 --- a/tests/ui/extra_unused_type_parameters.stderr +++ b/tests/ui/extra_unused_type_parameters.stderr @@ -1,38 +1,38 @@ error: type parameter goes unused in function definition --> $DIR/extra_unused_type_parameters.rs:4:13 | -LL | fn unused_ty(x: u8) {} +LL | fn unused_ty(x: u8) { | ^^^ | = help: consider removing the parameter = note: `-D clippy::extra-unused-type-parameters` implied by `-D warnings` error: type parameters go unused in function definition - --> $DIR/extra_unused_type_parameters.rs:6:16 + --> $DIR/extra_unused_type_parameters.rs:8:16 | -LL | fn unused_multi(x: u8) {} +LL | fn unused_multi(x: u8) { | ^^^^^^ | = help: consider removing the parameters error: type parameter goes unused in function definition - --> $DIR/extra_unused_type_parameters.rs:8:23 + --> $DIR/extra_unused_type_parameters.rs:12:23 | -LL | fn unused_with_lt<'a, T>(x: &'a u8) {} +LL | fn unused_with_lt<'a, T>(x: &'a u8) { | ^ | = help: consider removing the parameter error: type parameter goes unused in function definition - --> $DIR/extra_unused_type_parameters.rs:18:19 + --> $DIR/extra_unused_type_parameters.rs:24:19 | -LL | fn unused_bounded(x: U) {} +LL | fn unused_bounded(x: U) { | ^^^^^^^^^^^ | = help: consider removing the parameter error: type parameter goes unused in function definition - --> $DIR/extra_unused_type_parameters.rs:20:24 + --> $DIR/extra_unused_type_parameters.rs:28:24 | LL | fn unused_where_clause(x: U) | ^^ @@ -40,20 +40,36 @@ LL | fn unused_where_clause(x: U) = help: consider removing the parameter error: type parameters go unused in function definition - --> $DIR/extra_unused_type_parameters.rs:26:16 + --> $DIR/extra_unused_type_parameters.rs:35:16 | -LL | fn some_unused, E>(b: B, c: C) {} +LL | fn some_unused, E>(b: B, c: C) { | ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^ | = help: consider removing the parameters error: type parameter goes unused in function definition - --> $DIR/extra_unused_type_parameters.rs:49:22 + --> $DIR/extra_unused_type_parameters.rs:60:22 | -LL | fn unused_ty_impl(&self) {} +LL | fn unused_ty_impl(&self) { | ^^^ | = help: consider removing the parameter -error: aborting due to 7 previous errors +error: type parameters go unused in function definition + --> $DIR/extra_unused_type_parameters.rs:82:17 + | +LL | fn unused_opaque(dummy: impl Default) { + | ^^^^^^ + | + = help: consider removing the parameters + +error: type parameter goes unused in function definition + --> $DIR/extra_unused_type_parameters.rs:95:58 + | +LL | fn unused_with_priv_trait_bound() { + | ^ + | + = help: consider removing the parameter + +error: aborting due to 9 previous errors diff --git a/tests/ui/format.fixed b/tests/ui/format.fixed index beedf2c1db29..cd2f70ee8b02 100644 --- a/tests/ui/format.fixed +++ b/tests/ui/format.fixed @@ -1,4 +1,5 @@ // run-rustfix +// aux-build: proc_macro_with_span.rs #![warn(clippy::useless_format)] #![allow( unused_tuple_struct_fields, @@ -9,6 +10,8 @@ clippy::uninlined_format_args )] +extern crate proc_macro_with_span; + struct Foo(pub String); macro_rules! foo { @@ -87,4 +90,7 @@ fn main() { let _ = abc.to_string(); let xx = "xx"; let _ = xx.to_string(); + + // Issue #10148 + println!(proc_macro_with_span::with_span!(""something "")); } diff --git a/tests/ui/format.rs b/tests/ui/format.rs index e805f1818898..c22345a79d43 100644 --- a/tests/ui/format.rs +++ b/tests/ui/format.rs @@ -1,4 +1,5 @@ // run-rustfix +// aux-build: proc_macro_with_span.rs #![warn(clippy::useless_format)] #![allow( unused_tuple_struct_fields, @@ -9,6 +10,8 @@ clippy::uninlined_format_args )] +extern crate proc_macro_with_span; + struct Foo(pub String); macro_rules! foo { @@ -89,4 +92,7 @@ fn main() { let _ = format!("{abc}"); let xx = "xx"; let _ = format!("{xx}"); + + // Issue #10148 + println!(proc_macro_with_span::with_span!(""something "")); } diff --git a/tests/ui/format.stderr b/tests/ui/format.stderr index 0ef0ac655d39..a0e5d5c8ad21 100644 --- a/tests/ui/format.stderr +++ b/tests/ui/format.stderr @@ -1,5 +1,5 @@ error: useless use of `format!` - --> $DIR/format.rs:19:5 + --> $DIR/format.rs:22:5 | LL | format!("foo"); | ^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"foo".to_string()` @@ -7,19 +7,19 @@ LL | format!("foo"); = note: `-D clippy::useless-format` implied by `-D warnings` error: useless use of `format!` - --> $DIR/format.rs:20:5 + --> $DIR/format.rs:23:5 | LL | format!("{{}}"); | ^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"{}".to_string()` error: useless use of `format!` - --> $DIR/format.rs:21:5 + --> $DIR/format.rs:24:5 | LL | format!("{{}} abc {{}}"); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"{} abc {}".to_string()` error: useless use of `format!` - --> $DIR/format.rs:22:5 + --> $DIR/format.rs:25:5 | LL | / format!( LL | | r##"foo {{}} @@ -34,67 +34,67 @@ LL ~ " bar"##.to_string(); | error: useless use of `format!` - --> $DIR/format.rs:27:13 + --> $DIR/format.rs:30:13 | LL | let _ = format!(""); | ^^^^^^^^^^^ help: consider using `String::new()`: `String::new()` error: useless use of `format!` - --> $DIR/format.rs:29:5 + --> $DIR/format.rs:32:5 | LL | format!("{}", "foo"); | ^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"foo".to_string()` error: useless use of `format!` - --> $DIR/format.rs:37:5 + --> $DIR/format.rs:40:5 | LL | format!("{}", arg); | ^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `arg.to_string()` error: useless use of `format!` - --> $DIR/format.rs:67:5 + --> $DIR/format.rs:70:5 | LL | format!("{}", 42.to_string()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `42.to_string()` error: useless use of `format!` - --> $DIR/format.rs:69:5 + --> $DIR/format.rs:72:5 | LL | format!("{}", x.display().to_string()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `x.display().to_string()` error: useless use of `format!` - --> $DIR/format.rs:73:18 + --> $DIR/format.rs:76:18 | LL | let _ = Some(format!("{}", a + "bar")); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `a + "bar"` error: useless use of `format!` - --> $DIR/format.rs:77:22 + --> $DIR/format.rs:80:22 | LL | let _s: String = format!("{}", &*v.join("/n")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `(&*v.join("/n")).to_string()` error: useless use of `format!` - --> $DIR/format.rs:83:13 + --> $DIR/format.rs:86:13 | LL | let _ = format!("{x}"); | ^^^^^^^^^^^^^^ help: consider using `.to_string()`: `x.to_string()` error: useless use of `format!` - --> $DIR/format.rs:85:13 + --> $DIR/format.rs:88:13 | LL | let _ = format!("{y}", y = x); | ^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `x.to_string()` error: useless use of `format!` - --> $DIR/format.rs:89:13 + --> $DIR/format.rs:92:13 | LL | let _ = format!("{abc}"); | ^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `abc.to_string()` error: useless use of `format!` - --> $DIR/format.rs:91:13 + --> $DIR/format.rs:94:13 | LL | let _ = format!("{xx}"); | ^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `xx.to_string()` diff --git a/tests/ui/impl_trait_in_params.rs b/tests/ui/impl_trait_in_params.rs new file mode 100644 index 000000000000..07560101a416 --- /dev/null +++ b/tests/ui/impl_trait_in_params.rs @@ -0,0 +1,17 @@ +#![allow(unused)] +#![warn(clippy::impl_trait_in_params)] + +pub trait Trait {} +pub trait AnotherTrait {} + +// Should warn +pub fn a(_: impl Trait) {} +pub fn c(_: C, _: impl Trait) {} +fn d(_: impl AnotherTrait) {} + +// Shouldn't warn + +pub fn b(_: B) {} +fn e>(_: T) {} + +fn main() {} diff --git a/tests/ui/impl_trait_in_params.stderr b/tests/ui/impl_trait_in_params.stderr new file mode 100644 index 000000000000..acfcc21445eb --- /dev/null +++ b/tests/ui/impl_trait_in_params.stderr @@ -0,0 +1,25 @@ +error: '`impl Trait` used as a function parameter' + --> $DIR/impl_trait_in_params.rs:8:13 + | +LL | pub fn a(_: impl Trait) {} + | ^^^^^^^^^^ + | + = note: `-D clippy::impl-trait-in-params` implied by `-D warnings` +help: add a type paremeter + | +LL | pub fn a<{ /* Generic name */ }: Trait>(_: impl Trait) {} + | +++++++++++++++++++++++++++++++ + +error: '`impl Trait` used as a function parameter' + --> $DIR/impl_trait_in_params.rs:9:29 + | +LL | pub fn c(_: C, _: impl Trait) {} + | ^^^^^^^^^^ + | +help: add a type paremeter + | +LL | pub fn c(_: C, _: impl Trait) {} + | +++++++++++++++++++++++++++++++ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/large_digit_groups.fixed b/tests/ui/large_digit_groups.fixed index 3430c137ec22..ea18dac06833 100644 --- a/tests/ui/large_digit_groups.fixed +++ b/tests/ui/large_digit_groups.fixed @@ -11,7 +11,7 @@ fn main() { let _good = ( 0b1011_i64, 0o1_234_u32, - 0x0123_4567, + 0x1_234_567, 1_2345_6789, 1234_f32, 1_234.12_f32, @@ -19,7 +19,7 @@ fn main() { 1.123_4_f32, ); let _bad = ( - 0b11_0110_i64, + 0b1_10110_i64, 0xdead_beef_usize, 123_456_f32, 123_456.12_f32, diff --git a/tests/ui/large_digit_groups.stderr b/tests/ui/large_digit_groups.stderr index 13d108b56e02..19c0fae98a64 100644 --- a/tests/ui/large_digit_groups.stderr +++ b/tests/ui/large_digit_groups.stderr @@ -1,22 +1,10 @@ -error: digits of hex or binary literal not grouped by four - --> $DIR/large_digit_groups.rs:14:9 - | -LL | 0x1_234_567, - | ^^^^^^^^^^^ help: consider: `0x0123_4567` - | - = note: `-D clippy::unusual-byte-groupings` implied by `-D warnings` - -error: digits of hex or binary literal not grouped by four - --> $DIR/large_digit_groups.rs:22:9 - | -LL | 0b1_10110_i64, - | ^^^^^^^^^^^^^ help: consider: `0b11_0110_i64` - -error: digits of hex or binary literal not grouped by four +error: digits of hex, binary or octal literal not in groups of equal size --> $DIR/large_digit_groups.rs:23:9 | LL | 0xd_e_adbee_f_usize, | ^^^^^^^^^^^^^^^^^^^ help: consider: `0xdead_beef_usize` + | + = note: `-D clippy::unusual-byte-groupings` implied by `-D warnings` error: digit groups should be smaller --> $DIR/large_digit_groups.rs:24:9 @@ -44,5 +32,5 @@ error: digit groups should be smaller LL | 1_23456.12345_6_f64, | ^^^^^^^^^^^^^^^^^^^ help: consider: `123_456.123_456_f64` -error: aborting due to 7 previous errors +error: aborting due to 5 previous errors diff --git a/tests/ui/let_underscore_untyped.rs b/tests/ui/let_underscore_untyped.rs new file mode 100644 index 000000000000..bcb33c5c7e37 --- /dev/null +++ b/tests/ui/let_underscore_untyped.rs @@ -0,0 +1,54 @@ +#![allow(unused)] +#![warn(clippy::let_underscore_untyped)] + +use std::future::Future; +use std::{boxed::Box, fmt::Display}; + +fn a() -> u32 { + 1 +} + +fn b(x: T) -> T { + x +} + +fn c() -> impl Display { + 1 +} + +fn d(x: &u32) -> &u32 { + x +} + +fn e() -> Result { + Ok(1) +} + +fn f() -> Box { + Box::new(1) +} + +fn main() { + let _ = a(); + let _ = b(1); + let _ = c(); + let _ = d(&1); + let _ = e(); + let _ = f(); + + _ = a(); + _ = b(1); + _ = c(); + _ = d(&1); + _ = e(); + _ = f(); + + let _: u32 = a(); + let _: u32 = b(1); + let _: &u32 = d(&1); + let _: Result<_, _> = e(); + let _: Box<_> = f(); + + #[allow(clippy::let_underscore_untyped)] + let _ = a(); +} diff --git a/tests/ui/let_underscore_untyped.stderr b/tests/ui/let_underscore_untyped.stderr new file mode 100644 index 000000000000..36c3d1214d6b --- /dev/null +++ b/tests/ui/let_underscore_untyped.stderr @@ -0,0 +1,51 @@ +error: non-binding `let` without a type annotation + --> $DIR/let_underscore_untyped.rs:32:5 + | +LL | let _ = a(); + | ^^^^^^^^^^^^ + | + = help: consider adding a type annotation or removing the `let` keyword + = note: `-D clippy::let-underscore-untyped` implied by `-D warnings` + +error: non-binding `let` without a type annotation + --> $DIR/let_underscore_untyped.rs:33:5 + | +LL | let _ = b(1); + | ^^^^^^^^^^^^^ + | + = help: consider adding a type annotation or removing the `let` keyword + +error: non-binding `let` without a type annotation + --> $DIR/let_underscore_untyped.rs:34:5 + | +LL | let _ = c(); + | ^^^^^^^^^^^^ + | + = help: consider adding a type annotation or removing the `let` keyword + +error: non-binding `let` without a type annotation + --> $DIR/let_underscore_untyped.rs:35:5 + | +LL | let _ = d(&1); + | ^^^^^^^^^^^^^^ + | + = help: consider adding a type annotation or removing the `let` keyword + +error: non-binding `let` without a type annotation + --> $DIR/let_underscore_untyped.rs:36:5 + | +LL | let _ = e(); + | ^^^^^^^^^^^^ + | + = help: consider adding a type annotation or removing the `let` keyword + +error: non-binding `let` without a type annotation + --> $DIR/let_underscore_untyped.rs:37:5 + | +LL | let _ = f(); + | ^^^^^^^^^^^^ + | + = help: consider adding a type annotation or removing the `let` keyword + +error: aborting due to 6 previous errors + diff --git a/tests/ui/literals.stderr b/tests/ui/literals.stderr index 603d47bacca8..9bc7948c7cc1 100644 --- a/tests/ui/literals.stderr +++ b/tests/ui/literals.stderr @@ -121,7 +121,7 @@ error: digits grouped inconsistently by underscores LL | let fail23 = 3__16___23; | ^^^^^^^^^^ help: consider: `31_623` -error: digits of hex or binary literal not grouped by four +error: digits of hex, binary or octal literal not in groups of equal size --> $DIR/literals.rs:38:18 | LL | let fail24 = 0xAB_ABC_AB; @@ -129,12 +129,6 @@ LL | let fail24 = 0xAB_ABC_AB; | = note: `-D clippy::unusual-byte-groupings` implied by `-D warnings` -error: digits of hex or binary literal not grouped by four - --> $DIR/literals.rs:39:18 - | -LL | let fail25 = 0b01_100_101; - | ^^^^^^^^^^^^ help: consider: `0b0110_0101` - error: this is a decimal constant --> $DIR/literals.rs:46:13 | @@ -168,5 +162,5 @@ help: if you mean to use a decimal constant, remove the `0` to avoid confusion LL | let _ = 89; | ~~ -error: aborting due to 21 previous errors +error: aborting due to 20 previous errors diff --git a/tests/ui/manual_let_else.rs b/tests/ui/manual_let_else.rs index 48a162c13602..d175597a44a6 100644 --- a/tests/ui/manual_let_else.rs +++ b/tests/ui/manual_let_else.rs @@ -248,4 +248,15 @@ fn not_fire() { Some(value) => value, _ => macro_call!(), }; + + // Issue 10296 + // The let/else block in the else part is not divergent despite the presence of return + let _x = if let Some(x) = Some(1) { + x + } else { + let Some(_z) = Some(3) else { + return + }; + 1 + }; } diff --git a/tests/ui/manual_let_else_match.rs b/tests/ui/manual_let_else_match.rs index 28caed9d79df..73b746791259 100644 --- a/tests/ui/manual_let_else_match.rs +++ b/tests/ui/manual_let_else_match.rs @@ -42,13 +42,13 @@ fn fire() { loop { // More complex pattern for the identity arm and diverging arm let v = match h() { - (Some(_), Some(_)) | (None, None) => continue, (Some(v), None) | (None, Some(v)) => v, + (Some(_), Some(_)) | (None, None) => continue, }; // Custom enums are supported as long as the "else" arm is a simple _ let v = match build_enum() { - _ => continue, Variant::Bar(v) | Variant::Baz(v) => v, + _ => continue, }; } @@ -71,6 +71,12 @@ fn fire() { Variant::Bar(_) | Variant::Baz(_) => (), _ => return, }; + + let data = [1_u8, 2, 3, 4, 0, 0, 0, 0]; + let data = match data.as_slice() { + [data @ .., 0, 0, 0, 0] | [data @ .., 0, 0] | [data @ .., 0] => data, + _ => return, + }; } fn not_fire() { @@ -125,4 +131,23 @@ fn not_fire() { Ok(v) | Err(Variant::Bar(v) | Variant::Baz(v)) => v, Err(Variant::Foo) => return, }; + + // Issue 10241 + // The non-divergent arm arrives in second position and + // may cover values already matched in the first arm. + let v = match h() { + (Some(_), Some(_)) | (None, None) => return, + (Some(v), _) | (None, Some(v)) => v, + }; + + let v = match build_enum() { + _ => return, + Variant::Bar(v) | Variant::Baz(v) => v, + }; + + let data = [1_u8, 2, 3, 4, 0, 0, 0, 0]; + let data = match data.as_slice() { + [] | [0, 0] => return, + [data @ .., 0, 0, 0, 0] | [data @ .., 0, 0] | [data @ ..] => data, + }; } diff --git a/tests/ui/manual_let_else_match.stderr b/tests/ui/manual_let_else_match.stderr index cd5e9a9ac39c..7abaa0b85d23 100644 --- a/tests/ui/manual_let_else_match.stderr +++ b/tests/ui/manual_let_else_match.stderr @@ -22,8 +22,8 @@ error: this could be rewritten as `let...else` --> $DIR/manual_let_else_match.rs:44:9 | LL | / let v = match h() { -LL | | (Some(_), Some(_)) | (None, None) => continue, LL | | (Some(v), None) | (None, Some(v)) => v, +LL | | (Some(_), Some(_)) | (None, None) => continue, LL | | }; | |__________^ help: consider writing: `let ((Some(v), None) | (None, Some(v))) = h() else { continue };` @@ -31,8 +31,8 @@ error: this could be rewritten as `let...else` --> $DIR/manual_let_else_match.rs:49:9 | LL | / let v = match build_enum() { -LL | | _ => continue, LL | | Variant::Bar(v) | Variant::Baz(v) => v, +LL | | _ => continue, LL | | }; | |__________^ help: consider writing: `let (Variant::Bar(v) | Variant::Baz(v)) = build_enum() else { continue };` @@ -63,5 +63,14 @@ LL | | _ => return, LL | | }; | |______^ help: consider writing: `let (Variant::Bar(_) | Variant::Baz(_)) = f else { return };` -error: aborting due to 7 previous errors +error: this could be rewritten as `let...else` + --> $DIR/manual_let_else_match.rs:76:5 + | +LL | / let data = match data.as_slice() { +LL | | [data @ .., 0, 0, 0, 0] | [data @ .., 0, 0] | [data @ .., 0] => data, +LL | | _ => return, +LL | | }; + | |______^ help: consider writing: `let ([data @ .., 0, 0, 0, 0] | [data @ .., 0, 0] | [data @ .., 0]) = data.as_slice() else { return };` + +error: aborting due to 8 previous errors diff --git a/tests/ui/map_flatten_fixable.fixed b/tests/ui/map_flatten_fixable.fixed index 53628ef6531a..8e2f11389f89 100644 --- a/tests/ui/map_flatten_fixable.fixed +++ b/tests/ui/map_flatten_fixable.fixed @@ -1,6 +1,7 @@ // run-rustfix #![warn(clippy::all, clippy::pedantic)] +#![allow(clippy::let_underscore_untyped)] #![allow(clippy::missing_docs_in_private_items)] #![allow(clippy::map_identity)] #![allow(clippy::redundant_closure)] diff --git a/tests/ui/map_flatten_fixable.rs b/tests/ui/map_flatten_fixable.rs index 76016c8ed3cd..a783a99c4ffd 100644 --- a/tests/ui/map_flatten_fixable.rs +++ b/tests/ui/map_flatten_fixable.rs @@ -1,6 +1,7 @@ // run-rustfix #![warn(clippy::all, clippy::pedantic)] +#![allow(clippy::let_underscore_untyped)] #![allow(clippy::missing_docs_in_private_items)] #![allow(clippy::map_identity)] #![allow(clippy::redundant_closure)] diff --git a/tests/ui/map_flatten_fixable.stderr b/tests/ui/map_flatten_fixable.stderr index b6b0c4d09c37..c91f0b9ae94f 100644 --- a/tests/ui/map_flatten_fixable.stderr +++ b/tests/ui/map_flatten_fixable.stderr @@ -1,5 +1,5 @@ error: called `map(..).flatten()` on `Iterator` - --> $DIR/map_flatten_fixable.rs:17:47 + --> $DIR/map_flatten_fixable.rs:18:47 | LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id).flatten().collect(); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try replacing `map` with `filter_map` and remove the `.flatten()`: `filter_map(option_id)` @@ -7,43 +7,43 @@ LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id).flatten().coll = note: `-D clippy::map-flatten` implied by `-D warnings` error: called `map(..).flatten()` on `Iterator` - --> $DIR/map_flatten_fixable.rs:18:47 + --> $DIR/map_flatten_fixable.rs:19:47 | LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_ref).flatten().collect(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try replacing `map` with `filter_map` and remove the `.flatten()`: `filter_map(option_id_ref)` error: called `map(..).flatten()` on `Iterator` - --> $DIR/map_flatten_fixable.rs:19:47 + --> $DIR/map_flatten_fixable.rs:20:47 | LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_closure).flatten().collect(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try replacing `map` with `filter_map` and remove the `.flatten()`: `filter_map(option_id_closure)` error: called `map(..).flatten()` on `Iterator` - --> $DIR/map_flatten_fixable.rs:20:47 + --> $DIR/map_flatten_fixable.rs:21:47 | LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| x.checked_add(1)).flatten().collect(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try replacing `map` with `filter_map` and remove the `.flatten()`: `filter_map(|x| x.checked_add(1))` error: called `map(..).flatten()` on `Iterator` - --> $DIR/map_flatten_fixable.rs:23:47 + --> $DIR/map_flatten_fixable.rs:24:47 | LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect(); | ^^^^^^^^^^^^^^^^^^^^^^^ help: try replacing `map` with `flat_map` and remove the `.flatten()`: `flat_map(|x| 0..x)` error: called `map(..).flatten()` on `Option` - --> $DIR/map_flatten_fixable.rs:26:40 + --> $DIR/map_flatten_fixable.rs:27:40 | LL | let _: Option<_> = (Some(Some(1))).map(|x| x).flatten(); | ^^^^^^^^^^^^^^^^^^^^ help: try replacing `map` with `and_then` and remove the `.flatten()`: `and_then(|x| x)` error: called `map(..).flatten()` on `Result` - --> $DIR/map_flatten_fixable.rs:29:42 + --> $DIR/map_flatten_fixable.rs:30:42 | LL | let _: Result<_, &str> = (Ok(Ok(1))).map(|x| x).flatten(); | ^^^^^^^^^^^^^^^^^^^^ help: try replacing `map` with `and_then` and remove the `.flatten()`: `and_then(|x| x)` error: called `map(..).flatten()` on `Iterator` - --> $DIR/map_flatten_fixable.rs:38:10 + --> $DIR/map_flatten_fixable.rs:39:10 | LL | .map(|n| match n { | __________^ @@ -72,7 +72,7 @@ LL ~ }); | error: called `map(..).flatten()` on `Option` - --> $DIR/map_flatten_fixable.rs:58:10 + --> $DIR/map_flatten_fixable.rs:59:10 | LL | .map(|_| { | __________^ diff --git a/tests/ui/methods.rs b/tests/ui/methods.rs index 6f22366eab29..1519e4da9348 100644 --- a/tests/ui/methods.rs +++ b/tests/ui/methods.rs @@ -4,6 +4,7 @@ #![allow( clippy::disallowed_names, clippy::default_trait_access, + clippy::let_underscore_untyped, clippy::missing_docs_in_private_items, clippy::missing_safety_doc, clippy::non_ascii_literal, diff --git a/tests/ui/methods.stderr b/tests/ui/methods.stderr index b63672dd6fdb..4643e09e2702 100644 --- a/tests/ui/methods.stderr +++ b/tests/ui/methods.stderr @@ -1,5 +1,5 @@ error: methods called `new` usually return `Self` - --> $DIR/methods.rs:104:5 + --> $DIR/methods.rs:105:5 | LL | / fn new() -> i32 { LL | | 0 @@ -9,7 +9,7 @@ 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:125:13 + --> $DIR/methods.rs:126:13 | LL | let _ = v.iter().filter(|&x| { | _____________^ diff --git a/tests/ui/must_use_candidates.fixed b/tests/ui/must_use_candidates.fixed index 04a74a009e09..bbbb3cf621e4 100644 --- a/tests/ui/must_use_candidates.fixed +++ b/tests/ui/must_use_candidates.fixed @@ -84,7 +84,7 @@ pub unsafe fn mutates_static() -> usize { } #[no_mangle] -pub fn unmangled(i: bool) -> bool { +pub extern "C" fn unmangled(i: bool) -> bool { !i } diff --git a/tests/ui/must_use_candidates.rs b/tests/ui/must_use_candidates.rs index f04122f4eeab..94d3c83bdb93 100644 --- a/tests/ui/must_use_candidates.rs +++ b/tests/ui/must_use_candidates.rs @@ -84,7 +84,7 @@ pub unsafe fn mutates_static() -> usize { } #[no_mangle] -pub fn unmangled(i: bool) -> bool { +pub extern "C" fn unmangled(i: bool) -> bool { !i } diff --git a/tests/ui/needless_lifetimes.fixed b/tests/ui/needless_lifetimes.fixed index d286ef4ba378..f0f1f9298ac6 100644 --- a/tests/ui/needless_lifetimes.fixed +++ b/tests/ui/needless_lifetimes.fixed @@ -516,6 +516,16 @@ mod in_macro { // no lint on external macro macro_rules::needless_lifetime!(); + + macro_rules! expanded_lifetime { + ($l:lifetime) => { + fn f<$l>(arg: &$l str) -> &$l str { + arg + } + } + } + + expanded_lifetime!('a); } mod issue5787 { diff --git a/tests/ui/needless_lifetimes.rs b/tests/ui/needless_lifetimes.rs index 409528b291db..ddfd10430038 100644 --- a/tests/ui/needless_lifetimes.rs +++ b/tests/ui/needless_lifetimes.rs @@ -516,6 +516,16 @@ mod in_macro { // no lint on external macro macro_rules::needless_lifetime!(); + + macro_rules! expanded_lifetime { + ($l:lifetime) => { + fn f<$l>(arg: &$l str) -> &$l str { + arg + } + } + } + + expanded_lifetime!('a); } mod issue5787 { diff --git a/tests/ui/needless_return.fixed b/tests/ui/needless_return.fixed index 079e3531def1..0f525dd294c9 100644 --- a/tests/ui/needless_return.fixed +++ b/tests/ui/needless_return.fixed @@ -297,4 +297,14 @@ fn issue10051() -> Result { } } +mod issue10049 { + fn single() -> u32 { + if true { 1 } else { 2 } + } + + fn multiple(b1: bool, b2: bool, b3: bool) -> u32 { + (if b1 { 0 } else { 1 } | if b2 { 2 } else { 3 } | if b3 { 4 } else { 5 }) + } +} + fn main() {} diff --git a/tests/ui/needless_return.rs b/tests/ui/needless_return.rs index c1c48284f086..a1db8375d95b 100644 --- a/tests/ui/needless_return.rs +++ b/tests/ui/needless_return.rs @@ -307,4 +307,14 @@ fn issue10051() -> Result { } } +mod issue10049 { + fn single() -> u32 { + return if true { 1 } else { 2 }; + } + + fn multiple(b1: bool, b2: bool, b3: bool) -> u32 { + return if b1 { 0 } else { 1 } | if b2 { 2 } else { 3 } | if b3 { 4 } else { 5 }; + } +} + fn main() {} diff --git a/tests/ui/needless_return.stderr b/tests/ui/needless_return.stderr index 08b04bfe9d8b..87d0cd3e14cf 100644 --- a/tests/ui/needless_return.stderr +++ b/tests/ui/needless_return.stderr @@ -418,5 +418,21 @@ LL | return Err(format!("err!")); | = help: remove `return` -error: aborting due to 50 previous errors +error: unneeded `return` statement + --> $DIR/needless_return.rs:312:9 + | +LL | return if true { 1 } else { 2 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: remove `return` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:316:9 + | +LL | return if b1 { 0 } else { 1 } | if b2 { 2 } else { 3 } | if b3 { 4 } else { 5 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: remove `return` and wrap the sequence with parentheses + +error: aborting due to 52 previous errors diff --git a/tests/ui/never_loop.rs b/tests/ui/never_loop.rs index 28e8f459d442..29821ff96fc0 100644 --- a/tests/ui/never_loop.rs +++ b/tests/ui/never_loop.rs @@ -250,6 +250,51 @@ pub fn test20() { } } +pub fn test21() { + loop { + 'a: { + {} + break 'a; + } + } +} + +// Issue 10304: code after break from block was not considered +// unreachable code and was considered for further analysis of +// whether the loop would ever be executed or not. +pub fn test22() { + for _ in 0..10 { + 'block: { + break 'block; + return; + } + println!("looped"); + } +} + +pub fn test23() { + for _ in 0..10 { + 'block: { + for _ in 0..20 { + break 'block; + } + } + println!("looped"); + } +} + +pub fn test24() { + 'a: for _ in 0..10 { + 'b: { + let x = Some(1); + match x { + None => break 'a, + Some(_) => break 'b, + } + } + } +} + fn main() { test1(); test2(); diff --git a/tests/ui/never_loop.stderr b/tests/ui/never_loop.stderr index b7029bf8bed4..704d448644e2 100644 --- a/tests/ui/never_loop.stderr +++ b/tests/ui/never_loop.stderr @@ -126,5 +126,18 @@ LL | | } LL | | } | |_____^ -error: aborting due to 11 previous errors +error: this loop never actually loops + --> $DIR/never_loop.rs:278:13 + | +LL | / for _ in 0..20 { +LL | | break 'block; +LL | | } + | |_____________^ + | +help: if you need the first element of the iterator, try writing + | +LL | if let Some(_) = (0..20).next() { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: aborting due to 12 previous errors diff --git a/tests/ui/no_mangle_with_rust_abi.fixed b/tests/ui/no_mangle_with_rust_abi.fixed new file mode 100644 index 000000000000..d18dec22a8bb --- /dev/null +++ b/tests/ui/no_mangle_with_rust_abi.fixed @@ -0,0 +1,48 @@ +// run-rustfix + +#![allow(unused)] +#![warn(clippy::no_mangle_with_rust_abi)] + +#[no_mangle] +extern "C" fn rust_abi_fn_one(arg_one: u32, arg_two: usize) {} + +#[no_mangle] +pub extern "C" fn rust_abi_fn_two(arg_one: u32, arg_two: usize) {} + +/// # Safety +/// This function shouldn't be called unless the horsemen are ready +#[no_mangle] +pub unsafe extern "C" fn rust_abi_fn_three(arg_one: u32, arg_two: usize) {} + +/// # Safety +/// This function shouldn't be called unless the horsemen are ready +#[no_mangle] +unsafe extern "C" fn rust_abi_fn_four(arg_one: u32, arg_two: usize) {} + +#[no_mangle] +extern "C" fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( + arg_one: u32, + arg_two: usize, +) -> u32 { + 0 +} + +// Must not run on functions that explicitly opt in to Rust ABI with `extern "Rust"` +#[no_mangle] +#[rustfmt::skip] +extern "Rust" fn rust_abi_fn_explicit_opt_in(arg_one: u32, arg_two: usize) {} + +fn rust_abi_fn_again(arg_one: u32, arg_two: usize) {} + +#[no_mangle] +extern "C" fn c_abi_fn(arg_one: u32, arg_two: usize) {} + +extern "C" fn c_abi_fn_again(arg_one: u32, arg_two: usize) {} + +extern "C" { + fn c_abi_in_block(arg_one: u32, arg_two: usize); +} + +fn main() { + // test code goes here +} diff --git a/tests/ui/no_mangle_with_rust_abi.rs b/tests/ui/no_mangle_with_rust_abi.rs new file mode 100644 index 000000000000..481e1b6d9619 --- /dev/null +++ b/tests/ui/no_mangle_with_rust_abi.rs @@ -0,0 +1,48 @@ +// run-rustfix + +#![allow(unused)] +#![warn(clippy::no_mangle_with_rust_abi)] + +#[no_mangle] +fn rust_abi_fn_one(arg_one: u32, arg_two: usize) {} + +#[no_mangle] +pub fn rust_abi_fn_two(arg_one: u32, arg_two: usize) {} + +/// # Safety +/// This function shouldn't be called unless the horsemen are ready +#[no_mangle] +pub unsafe fn rust_abi_fn_three(arg_one: u32, arg_two: usize) {} + +/// # Safety +/// This function shouldn't be called unless the horsemen are ready +#[no_mangle] +unsafe fn rust_abi_fn_four(arg_one: u32, arg_two: usize) {} + +#[no_mangle] +fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( + arg_one: u32, + arg_two: usize, +) -> u32 { + 0 +} + +// Must not run on functions that explicitly opt in to Rust ABI with `extern "Rust"` +#[no_mangle] +#[rustfmt::skip] +extern "Rust" fn rust_abi_fn_explicit_opt_in(arg_one: u32, arg_two: usize) {} + +fn rust_abi_fn_again(arg_one: u32, arg_two: usize) {} + +#[no_mangle] +extern "C" fn c_abi_fn(arg_one: u32, arg_two: usize) {} + +extern "C" fn c_abi_fn_again(arg_one: u32, arg_two: usize) {} + +extern "C" { + fn c_abi_in_block(arg_one: u32, arg_two: usize); +} + +fn main() { + // test code goes here +} diff --git a/tests/ui/no_mangle_with_rust_abi.stderr b/tests/ui/no_mangle_with_rust_abi.stderr new file mode 100644 index 000000000000..71517d318095 --- /dev/null +++ b/tests/ui/no_mangle_with_rust_abi.stderr @@ -0,0 +1,45 @@ +error: attribute #[no_mangle] set on a Rust ABI function + --> $DIR/no_mangle_with_rust_abi.rs:7:1 + | +LL | fn rust_abi_fn_one(arg_one: u32, arg_two: usize) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `extern "C" fn rust_abi_fn_one(arg_one: u32, arg_two: usize)` + | + = note: `-D clippy::no-mangle-with-rust-abi` implied by `-D warnings` + +error: attribute #[no_mangle] set on a Rust ABI function + --> $DIR/no_mangle_with_rust_abi.rs:10:1 + | +LL | pub fn rust_abi_fn_two(arg_one: u32, arg_two: usize) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `pub extern "C" fn rust_abi_fn_two(arg_one: u32, arg_two: usize)` + +error: attribute #[no_mangle] set on a Rust ABI function + --> $DIR/no_mangle_with_rust_abi.rs:15:1 + | +LL | pub unsafe fn rust_abi_fn_three(arg_one: u32, arg_two: usize) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `pub unsafe extern "C" fn rust_abi_fn_three(arg_one: u32, arg_two: usize)` + +error: attribute #[no_mangle] set on a Rust ABI function + --> $DIR/no_mangle_with_rust_abi.rs:20:1 + | +LL | unsafe fn rust_abi_fn_four(arg_one: u32, arg_two: usize) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unsafe extern "C" fn rust_abi_fn_four(arg_one: u32, arg_two: usize)` + +error: attribute #[no_mangle] set on a Rust ABI function + --> $DIR/no_mangle_with_rust_abi.rs:23:1 + | +LL | / fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( +LL | | arg_one: u32, +LL | | arg_two: usize, +LL | | ) -> u32 { + | |________^ + | +help: try + | +LL + extern "C" fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( +LL + arg_one: u32, +LL + arg_two: usize, +LL ~ ) -> u32 { + | + +error: aborting due to 5 previous errors + diff --git a/tests/ui/question_mark_used.rs b/tests/ui/question_mark_used.rs new file mode 100644 index 000000000000..8c3ef789697f --- /dev/null +++ b/tests/ui/question_mark_used.rs @@ -0,0 +1,15 @@ +// non rustfixable +#![allow(unreachable_code)] +#![allow(dead_code)] +#![warn(clippy::question_mark_used)] + +fn other_function() -> Option { + Some(32) +} + +fn my_function() -> Option { + other_function()?; + None +} + +fn main() {} diff --git a/tests/ui/question_mark_used.stderr b/tests/ui/question_mark_used.stderr new file mode 100644 index 000000000000..8b5fcbcdbfd6 --- /dev/null +++ b/tests/ui/question_mark_used.stderr @@ -0,0 +1,11 @@ +error: question mark operator was used + --> $DIR/question_mark_used.rs:11:5 + | +LL | other_function()?; + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using a custom macro or match expression + = note: `-D clippy::question-mark-used` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/tests/ui/significant_drop_tightening.fixed b/tests/ui/significant_drop_tightening.fixed new file mode 100644 index 000000000000..da998c610bd2 --- /dev/null +++ b/tests/ui/significant_drop_tightening.fixed @@ -0,0 +1,84 @@ +// run-rustfix + +#![warn(clippy::significant_drop_tightening)] + +use std::sync::Mutex; + +pub fn complex_return_triggers_the_lint() -> i32 { + fn foo() -> i32 { + 1 + } + let mutex = Mutex::new(1); + let lock = mutex.lock().unwrap(); + let _ = *lock; + let _ = *lock; + drop(lock); + foo() +} + +pub fn path_return_can_be_ignored() -> i32 { + let mutex = Mutex::new(1); + let lock = mutex.lock().unwrap(); + let rslt = *lock; + let _ = *lock; + rslt +} + +pub fn post_bindings_can_be_ignored() { + let mutex = Mutex::new(1); + let lock = mutex.lock().unwrap(); + let rslt = *lock; + let another = rslt; + let _ = another; +} + +pub fn unnecessary_contention_with_multiple_owned_results() { + { + let mutex = Mutex::new(1i32); + let lock = mutex.lock().unwrap(); + let _ = lock.abs(); + let _ = lock.is_positive(); + } + + { + let mutex = Mutex::new(1i32); + let lock = mutex.lock().unwrap(); + let rslt0 = lock.abs(); + let rslt1 = lock.is_positive(); + drop(lock); + do_heavy_computation_that_takes_time((rslt0, rslt1)); + } +} + +pub fn unnecessary_contention_with_single_owned_results() { + { + let mutex = Mutex::new(1i32); + let lock = mutex.lock().unwrap(); + let _ = lock.abs(); + } + { + let mutex = Mutex::new(vec![1i32]); + let mut lock = mutex.lock().unwrap(); + lock.clear(); + } + + { + let mutex = Mutex::new(1i32); + + let rslt0 = mutex.lock().unwrap().abs(); + + do_heavy_computation_that_takes_time(rslt0); + } + { + let mutex = Mutex::new(vec![1i32]); + + mutex.lock().unwrap().clear(); + + do_heavy_computation_that_takes_time(()); + } +} + +// Marker used for illustration purposes. +pub fn do_heavy_computation_that_takes_time(_: T) {} + +fn main() {} diff --git a/tests/ui/significant_drop_tightening.rs b/tests/ui/significant_drop_tightening.rs new file mode 100644 index 000000000000..83823f95f68a --- /dev/null +++ b/tests/ui/significant_drop_tightening.rs @@ -0,0 +1,80 @@ +// run-rustfix + +#![warn(clippy::significant_drop_tightening)] + +use std::sync::Mutex; + +pub fn complex_return_triggers_the_lint() -> i32 { + fn foo() -> i32 { + 1 + } + let mutex = Mutex::new(1); + let lock = mutex.lock().unwrap(); + let _ = *lock; + let _ = *lock; + foo() +} + +pub fn path_return_can_be_ignored() -> i32 { + let mutex = Mutex::new(1); + let lock = mutex.lock().unwrap(); + let rslt = *lock; + let _ = *lock; + rslt +} + +pub fn post_bindings_can_be_ignored() { + let mutex = Mutex::new(1); + let lock = mutex.lock().unwrap(); + let rslt = *lock; + let another = rslt; + let _ = another; +} + +pub fn unnecessary_contention_with_multiple_owned_results() { + { + let mutex = Mutex::new(1i32); + let lock = mutex.lock().unwrap(); + let _ = lock.abs(); + let _ = lock.is_positive(); + } + + { + let mutex = Mutex::new(1i32); + let lock = mutex.lock().unwrap(); + let rslt0 = lock.abs(); + let rslt1 = lock.is_positive(); + do_heavy_computation_that_takes_time((rslt0, rslt1)); + } +} + +pub fn unnecessary_contention_with_single_owned_results() { + { + let mutex = Mutex::new(1i32); + let lock = mutex.lock().unwrap(); + let _ = lock.abs(); + } + { + let mutex = Mutex::new(vec![1i32]); + let mut lock = mutex.lock().unwrap(); + lock.clear(); + } + + { + let mutex = Mutex::new(1i32); + let lock = mutex.lock().unwrap(); + let rslt0 = lock.abs(); + do_heavy_computation_that_takes_time(rslt0); + } + { + let mutex = Mutex::new(vec![1i32]); + let mut lock = mutex.lock().unwrap(); + lock.clear(); + do_heavy_computation_that_takes_time(()); + } +} + +// Marker used for illustration purposes. +pub fn do_heavy_computation_that_takes_time(_: T) {} + +fn main() {} diff --git a/tests/ui/significant_drop_tightening.stderr b/tests/ui/significant_drop_tightening.stderr new file mode 100644 index 000000000000..ab8ce356ec7b --- /dev/null +++ b/tests/ui/significant_drop_tightening.stderr @@ -0,0 +1,94 @@ +error: temporary with significant `Drop` can be early dropped + --> $DIR/significant_drop_tightening.rs:12:9 + | +LL | pub fn complex_return_triggers_the_lint() -> i32 { + | __________________________________________________- +LL | | fn foo() -> i32 { +LL | | 1 +LL | | } +LL | | let mutex = Mutex::new(1); +LL | | let lock = mutex.lock().unwrap(); + | | ^^^^ +... | +LL | | foo() +LL | | } + | |_- temporary `lock` is currently being dropped at the end of its contained scope + | + = note: this might lead to unnecessary resource contention + = note: `-D clippy::significant-drop-tightening` implied by `-D warnings` +help: drop the temporary after the end of its last usage + | +LL ~ let _ = *lock; +LL + drop(lock); + | + +error: temporary with significant `Drop` can be early dropped + --> $DIR/significant_drop_tightening.rs:44:13 + | +LL | / { +LL | | let mutex = Mutex::new(1i32); +LL | | let lock = mutex.lock().unwrap(); + | | ^^^^ +LL | | let rslt0 = lock.abs(); +LL | | let rslt1 = lock.is_positive(); +LL | | do_heavy_computation_that_takes_time((rslt0, rslt1)); +LL | | } + | |_____- temporary `lock` is currently being dropped at the end of its contained scope + | + = note: this might lead to unnecessary resource contention +help: drop the temporary after the end of its last usage + | +LL ~ let rslt1 = lock.is_positive(); +LL + drop(lock); + | + +error: temporary with significant `Drop` can be early dropped + --> $DIR/significant_drop_tightening.rs:65:13 + | +LL | / { +LL | | let mutex = Mutex::new(1i32); +LL | | let lock = mutex.lock().unwrap(); + | | ^^^^ +LL | | let rslt0 = lock.abs(); +LL | | do_heavy_computation_that_takes_time(rslt0); +LL | | } + | |_____- temporary `lock` is currently being dropped at the end of its contained scope + | + = note: this might lead to unnecessary resource contention +help: merge the temporary construction with its single usage + | +LL ~ +LL + let rslt0 = mutex.lock().unwrap().abs(); + | +help: remove separated single usage + | +LL - let rslt0 = lock.abs(); +LL + + | + +error: temporary with significant `Drop` can be early dropped + --> $DIR/significant_drop_tightening.rs:71:17 + | +LL | / { +LL | | let mutex = Mutex::new(vec![1i32]); +LL | | let mut lock = mutex.lock().unwrap(); + | | ^^^^ +LL | | lock.clear(); +LL | | do_heavy_computation_that_takes_time(()); +LL | | } + | |_____- temporary `lock` is currently being dropped at the end of its contained scope + | + = note: this might lead to unnecessary resource contention +help: merge the temporary construction with its single usage + | +LL ~ +LL + mutex.lock().unwrap().clear(); + | +help: remove separated single usage + | +LL - lock.clear(); +LL + + | + +error: aborting due to 4 previous errors + diff --git a/tests/ui/suspicious_command_arg_space.rs b/tests/ui/suspicious_command_arg_space.rs new file mode 100644 index 000000000000..bdc6113a2500 --- /dev/null +++ b/tests/ui/suspicious_command_arg_space.rs @@ -0,0 +1,10 @@ +fn main() { + // Things it should warn about: + std::process::Command::new("echo").arg("-n hello").spawn().unwrap(); + std::process::Command::new("cat").arg("--number file").spawn().unwrap(); + + // Things it should not warn about: + std::process::Command::new("echo").arg("hello world").spawn().unwrap(); + std::process::Command::new("a").arg("--fmt=%a %b %c").spawn().unwrap(); + std::process::Command::new("b").arg("-ldflags=-s -w").spawn().unwrap(); +} diff --git a/tests/ui/suspicious_command_arg_space.stderr b/tests/ui/suspicious_command_arg_space.stderr new file mode 100644 index 000000000000..9bc0ca93aec9 --- /dev/null +++ b/tests/ui/suspicious_command_arg_space.stderr @@ -0,0 +1,25 @@ +error: single argument that looks like it should be multiple arguments + --> $DIR/suspicious_command_arg_space.rs:3:44 + | +LL | std::process::Command::new("echo").arg("-n hello").spawn().unwrap(); + | ^^^^^^^^^^ + | + = note: `-D clippy::suspicious-command-arg-space` implied by `-D warnings` +help: consider splitting the argument + | +LL | std::process::Command::new("echo").args(["-n", "hello"]).spawn().unwrap(); + | ~~~~ ~~~~~~~~~~~~~~~ + +error: single argument that looks like it should be multiple arguments + --> $DIR/suspicious_command_arg_space.rs:4:43 + | +LL | std::process::Command::new("cat").arg("--number file").spawn().unwrap(); + | ^^^^^^^^^^^^^^^ + | +help: consider splitting the argument + | +LL | std::process::Command::new("cat").args(["--number", "file"]).spawn().unwrap(); + | ~~~~ ~~~~~~~~~~~~~~~~~~~~ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/swap.fixed b/tests/ui/swap.fixed index 805a2ba5a598..fa89706a815a 100644 --- a/tests/ui/swap.fixed +++ b/tests/ui/swap.fixed @@ -7,7 +7,8 @@ clippy::redundant_clone, redundant_semicolons, dead_code, - unused_assignments + unused_assignments, + unused_variables )] struct Foo(u32); @@ -121,6 +122,27 @@ fn main() { std::mem::swap(&mut c.0, &mut a); ; std::mem::swap(&mut c.0, &mut a); + + std::mem::swap(&mut a, &mut b); + + let mut c = 1; + let mut d = 2; + std::mem::swap(&mut d, &mut c); + + let mut b = 1; + std::mem::swap(&mut a, &mut b); + + let b = 1; + let a = 2; + + let t = b; + let b = a; + let a = t; + + let mut b = 1; + let mut a = 2; + + std::mem::swap(&mut b, &mut a); } fn issue_8154() { diff --git a/tests/ui/swap.rs b/tests/ui/swap.rs index a8c878479523..ef8a81c8341b 100644 --- a/tests/ui/swap.rs +++ b/tests/ui/swap.rs @@ -7,7 +7,8 @@ clippy::redundant_clone, redundant_semicolons, dead_code, - unused_assignments + unused_assignments, + unused_variables )] struct Foo(u32); @@ -143,6 +144,32 @@ fn main() { ; let t = c.0; c.0 = a; a = t; + + let a = b; + let b = a; + + let mut c = 1; + let mut d = 2; + d = c; + c = d; + + let mut b = 1; + let a = b; + b = a; + + let b = 1; + let a = 2; + + let t = b; + let b = a; + let a = t; + + let mut b = 1; + let mut a = 2; + + let t = b; + b = a; + a = t; } fn issue_8154() { diff --git a/tests/ui/swap.stderr b/tests/ui/swap.stderr index ee4b7a508a5e..f0acbfe253f4 100644 --- a/tests/ui/swap.stderr +++ b/tests/ui/swap.stderr @@ -1,5 +1,5 @@ error: this looks like you are swapping `bar.a` and `bar.b` manually - --> $DIR/swap.rs:24:5 + --> $DIR/swap.rs:25:5 | LL | / let temp = bar.a; LL | | bar.a = bar.b; @@ -10,7 +10,7 @@ LL | | bar.b = temp; = note: `-D clippy::manual-swap` implied by `-D warnings` error: this looks like you are swapping elements of `foo` manually - --> $DIR/swap.rs:36:5 + --> $DIR/swap.rs:37:5 | LL | / let temp = foo[0]; LL | | foo[0] = foo[1]; @@ -18,7 +18,7 @@ LL | | foo[1] = temp; | |_________________^ help: try: `foo.swap(0, 1)` error: this looks like you are swapping elements of `foo` manually - --> $DIR/swap.rs:45:5 + --> $DIR/swap.rs:46:5 | LL | / let temp = foo[0]; LL | | foo[0] = foo[1]; @@ -26,7 +26,7 @@ LL | | foo[1] = temp; | |_________________^ help: try: `foo.swap(0, 1)` error: this looks like you are swapping elements of `foo` manually - --> $DIR/swap.rs:64:5 + --> $DIR/swap.rs:65:5 | LL | / let temp = foo[0]; LL | | foo[0] = foo[1]; @@ -34,7 +34,7 @@ LL | | foo[1] = temp; | |_________________^ help: try: `foo.swap(0, 1)` error: this looks like you are swapping `a` and `b` manually - --> $DIR/swap.rs:75:5 + --> $DIR/swap.rs:76:5 | LL | / a ^= b; LL | | b ^= a; @@ -42,7 +42,7 @@ LL | | a ^= b; | |___________^ help: try: `std::mem::swap(&mut a, &mut b)` error: this looks like you are swapping `bar.a` and `bar.b` manually - --> $DIR/swap.rs:83:5 + --> $DIR/swap.rs:84:5 | LL | / bar.a ^= bar.b; LL | | bar.b ^= bar.a; @@ -50,7 +50,7 @@ LL | | bar.a ^= bar.b; | |___________________^ help: try: `std::mem::swap(&mut bar.a, &mut bar.b)` error: this looks like you are swapping elements of `foo` manually - --> $DIR/swap.rs:91:5 + --> $DIR/swap.rs:92:5 | LL | / foo[0] ^= foo[1]; LL | | foo[1] ^= foo[0]; @@ -58,7 +58,7 @@ LL | | foo[0] ^= foo[1]; | |_____________________^ help: try: `foo.swap(0, 1)` error: this looks like you are swapping `foo[0][1]` and `bar[1][0]` manually - --> $DIR/swap.rs:120:5 + --> $DIR/swap.rs:121:5 | LL | / let temp = foo[0][1]; LL | | foo[0][1] = bar[1][0]; @@ -68,7 +68,7 @@ LL | | bar[1][0] = temp; = note: or maybe you should use `std::mem::replace`? error: this looks like you are swapping `a` and `b` manually - --> $DIR/swap.rs:134:7 + --> $DIR/swap.rs:135:7 | LL | ; let t = a; | _______^ @@ -79,7 +79,7 @@ LL | | b = t; = note: or maybe you should use `std::mem::replace`? error: this looks like you are swapping `c.0` and `a` manually - --> $DIR/swap.rs:143:7 + --> $DIR/swap.rs:144:7 | LL | ; let t = c.0; | _______^ @@ -89,8 +89,18 @@ LL | | a = t; | = note: or maybe you should use `std::mem::replace`? +error: this looks like you are swapping `b` and `a` manually + --> $DIR/swap.rs:170:5 + | +LL | / let t = b; +LL | | b = a; +LL | | a = t; + | |_________^ help: try: `std::mem::swap(&mut b, &mut a)` + | + = note: or maybe you should use `std::mem::replace`? + error: this looks like you are trying to swap `a` and `b` - --> $DIR/swap.rs:131:5 + --> $DIR/swap.rs:132:5 | LL | / a = b; LL | | b = a; @@ -100,7 +110,7 @@ LL | | b = a; = note: `-D clippy::almost-swapped` implied by `-D warnings` error: this looks like you are trying to swap `c.0` and `a` - --> $DIR/swap.rs:140:5 + --> $DIR/swap.rs:141:5 | LL | / c.0 = a; LL | | a = c.0; @@ -108,8 +118,35 @@ LL | | a = c.0; | = note: or maybe you should use `std::mem::replace`? +error: this looks like you are trying to swap `a` and `b` + --> $DIR/swap.rs:148:5 + | +LL | / let a = b; +LL | | let b = a; + | |_____________^ help: try: `std::mem::swap(&mut a, &mut b)` + | + = note: or maybe you should use `std::mem::replace`? + +error: this looks like you are trying to swap `d` and `c` + --> $DIR/swap.rs:153:5 + | +LL | / d = c; +LL | | c = d; + | |_________^ help: try: `std::mem::swap(&mut d, &mut c)` + | + = note: or maybe you should use `std::mem::replace`? + +error: this looks like you are trying to swap `a` and `b` + --> $DIR/swap.rs:157:5 + | +LL | / let a = b; +LL | | b = a; + | |_________^ help: try: `std::mem::swap(&mut a, &mut b)` + | + = note: or maybe you should use `std::mem::replace`? + error: this looks like you are swapping `s.0.x` and `s.0.y` manually - --> $DIR/swap.rs:178:5 + --> $DIR/swap.rs:205:5 | LL | / let t = s.0.x; LL | | s.0.x = s.0.y; @@ -118,5 +155,5 @@ LL | | s.0.y = t; | = note: or maybe you should use `std::mem::replace`? -error: aborting due to 13 previous errors +error: aborting due to 17 previous errors diff --git a/tests/ui/transmute_int_to_non_zero.rs b/tests/ui/transmute_int_to_non_zero.rs new file mode 100644 index 000000000000..a38406782506 --- /dev/null +++ b/tests/ui/transmute_int_to_non_zero.rs @@ -0,0 +1,41 @@ +#![warn(clippy::transmute_int_to_non_zero)] + +use core::num::*; + +fn main() { + let int_u8: u8 = 1; + let int_u16: u16 = 1; + let int_u32: u32 = 1; + let int_u64: u64 = 1; + let int_u128: u128 = 1; + + let int_i8: i8 = 1; + let int_i16: i16 = 1; + let int_i32: i32 = 1; + let int_i64: i64 = 1; + let int_i128: i128 = 1; + + let _: NonZeroU8 = unsafe { std::mem::transmute(int_u8) }; + let _: NonZeroU16 = unsafe { std::mem::transmute(int_u16) }; + let _: NonZeroU32 = unsafe { std::mem::transmute(int_u32) }; + let _: NonZeroU64 = unsafe { std::mem::transmute(int_u64) }; + let _: NonZeroU128 = unsafe { std::mem::transmute(int_u128) }; + + let _: NonZeroI8 = unsafe { std::mem::transmute(int_i8) }; + let _: NonZeroI16 = unsafe { std::mem::transmute(int_i16) }; + let _: NonZeroI32 = unsafe { std::mem::transmute(int_i32) }; + let _: NonZeroI64 = unsafe { std::mem::transmute(int_i64) }; + let _: NonZeroI128 = unsafe { std::mem::transmute(int_i128) }; + + let _: NonZeroU8 = unsafe { NonZeroU8::new_unchecked(int_u8) }; + let _: NonZeroU16 = unsafe { NonZeroU16::new_unchecked(int_u16) }; + let _: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(int_u32) }; + let _: NonZeroU64 = unsafe { NonZeroU64::new_unchecked(int_u64) }; + let _: NonZeroU128 = unsafe { NonZeroU128::new_unchecked(int_u128) }; + + let _: NonZeroI8 = unsafe { NonZeroI8::new_unchecked(int_i8) }; + let _: NonZeroI16 = unsafe { NonZeroI16::new_unchecked(int_i16) }; + let _: NonZeroI32 = unsafe { NonZeroI32::new_unchecked(int_i32) }; + let _: NonZeroI64 = unsafe { NonZeroI64::new_unchecked(int_i64) }; + let _: NonZeroI128 = unsafe { NonZeroI128::new_unchecked(int_i128) }; +} diff --git a/tests/ui/transmute_int_to_non_zero.stderr b/tests/ui/transmute_int_to_non_zero.stderr new file mode 100644 index 000000000000..33f8ce79ea78 --- /dev/null +++ b/tests/ui/transmute_int_to_non_zero.stderr @@ -0,0 +1,64 @@ +error: transmute from a `u8` to a `NonZeroU8` + --> $DIR/transmute_int_to_non_zero.rs:18:33 + | +LL | let _: NonZeroU8 = unsafe { std::mem::transmute(int_u8) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `NonZeroU8::new_unchecked(int_u8)` + | + = note: `-D clippy::transmute-int-to-non-zero` implied by `-D warnings` + +error: transmute from a `u16` to a `NonZeroU16` + --> $DIR/transmute_int_to_non_zero.rs:19:34 + | +LL | let _: NonZeroU16 = unsafe { std::mem::transmute(int_u16) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `NonZeroU16::new_unchecked(int_u16)` + +error: transmute from a `u32` to a `NonZeroU32` + --> $DIR/transmute_int_to_non_zero.rs:20:34 + | +LL | let _: NonZeroU32 = unsafe { std::mem::transmute(int_u32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `NonZeroU32::new_unchecked(int_u32)` + +error: transmute from a `u64` to a `NonZeroU64` + --> $DIR/transmute_int_to_non_zero.rs:21:34 + | +LL | let _: NonZeroU64 = unsafe { std::mem::transmute(int_u64) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `NonZeroU64::new_unchecked(int_u64)` + +error: transmute from a `u128` to a `NonZeroU128` + --> $DIR/transmute_int_to_non_zero.rs:22:35 + | +LL | let _: NonZeroU128 = unsafe { std::mem::transmute(int_u128) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `NonZeroU128::new_unchecked(int_u128)` + +error: transmute from a `i8` to a `NonZeroI8` + --> $DIR/transmute_int_to_non_zero.rs:24:33 + | +LL | let _: NonZeroI8 = unsafe { std::mem::transmute(int_i8) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `NonZeroI8::new_unchecked(int_i8)` + +error: transmute from a `i16` to a `NonZeroI16` + --> $DIR/transmute_int_to_non_zero.rs:25:34 + | +LL | let _: NonZeroI16 = unsafe { std::mem::transmute(int_i16) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `NonZeroI16::new_unchecked(int_i16)` + +error: transmute from a `i32` to a `NonZeroI32` + --> $DIR/transmute_int_to_non_zero.rs:26:34 + | +LL | let _: NonZeroI32 = unsafe { std::mem::transmute(int_i32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `NonZeroI32::new_unchecked(int_i32)` + +error: transmute from a `i64` to a `NonZeroI64` + --> $DIR/transmute_int_to_non_zero.rs:27:34 + | +LL | let _: NonZeroI64 = unsafe { std::mem::transmute(int_i64) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `NonZeroI64::new_unchecked(int_i64)` + +error: transmute from a `i128` to a `NonZeroI128` + --> $DIR/transmute_int_to_non_zero.rs:28:35 + | +LL | let _: NonZeroI128 = unsafe { std::mem::transmute(int_i128) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `NonZeroI128::new_unchecked(int_i128)` + +error: aborting due to 10 previous errors + diff --git a/tests/ui/uninlined_format_args.fixed b/tests/ui/uninlined_format_args.fixed index 9d08e80cf9a5..cbd5cc5fceef 100644 --- a/tests/ui/uninlined_format_args.fixed +++ b/tests/ui/uninlined_format_args.fixed @@ -174,3 +174,7 @@ fn _meets_msrv() { let local_i32 = 1; println!("expand='{local_i32}'"); } + +fn _do_not_fire() { + println!("{:?}", None::<()>); +} diff --git a/tests/ui/uninlined_format_args.rs b/tests/ui/uninlined_format_args.rs index 35b3677a8968..cf0ea5be4813 100644 --- a/tests/ui/uninlined_format_args.rs +++ b/tests/ui/uninlined_format_args.rs @@ -179,3 +179,7 @@ fn _meets_msrv() { let local_i32 = 1; println!("expand='{}'", local_i32); } + +fn _do_not_fire() { + println!("{:?}", None::<()>); +} diff --git a/tests/ui/unreadable_literal.fixed b/tests/ui/unreadable_literal.fixed index a67363b09ea5..13e5feb19263 100644 --- a/tests/ui/unreadable_literal.fixed +++ b/tests/ui/unreadable_literal.fixed @@ -23,7 +23,7 @@ fn main() { let _good = ( 0b1011_i64, 0o1_234_u32, - 0x0123_4567, + 0x1_234_567, 65536, 1_2345_6789, 1234_f32, diff --git a/tests/ui/unreadable_literal.stderr b/tests/ui/unreadable_literal.stderr index b51130c6a6ab..450121b1c5a9 100644 --- a/tests/ui/unreadable_literal.stderr +++ b/tests/ui/unreadable_literal.stderr @@ -1,11 +1,3 @@ -error: digits of hex or binary literal not grouped by four - --> $DIR/unreadable_literal.rs:26:9 - | -LL | 0x1_234_567, - | ^^^^^^^^^^^ help: consider: `0x0123_4567` - | - = note: `-D clippy::unusual-byte-groupings` implied by `-D warnings` - error: long literal lacking separators --> $DIR/unreadable_literal.rs:34:17 | @@ -68,5 +60,5 @@ error: long literal lacking separators LL | let _fail5 = 1.100300400; | ^^^^^^^^^^^ help: consider: `1.100_300_400` -error: aborting due to 11 previous errors +error: aborting due to 10 previous errors From e4b184a776ef20e33ff2922f583349928061a18b Mon Sep 17 00:00:00 2001 From: Tomoki Nakagawa Date: Fri, 24 Feb 2023 15:26:25 +0900 Subject: [PATCH 032/362] Respect $CARGO_HOME when looking up toolchains. --- crates/toolchain/src/lib.rs | 21 +++++++++++++++++---- editors/code/src/toolchain.ts | 30 ++++++++++++++++++------------ 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/crates/toolchain/src/lib.rs b/crates/toolchain/src/lib.rs index 67bdad2aadd8..729f84a8150c 100644 --- a/crates/toolchain/src/lib.rs +++ b/crates/toolchain/src/lib.rs @@ -31,8 +31,9 @@ fn get_path_for_executable(executable_name: &'static str) -> PathBuf { // example: for cargo, this checks $CARGO environment variable; for rustc, $RUSTC; etc // 2) `` // example: for cargo, this tries just `cargo`, which will succeed if `cargo` is on the $PATH - // 3) `~/.cargo/bin/` - // example: for cargo, this tries ~/.cargo/bin/cargo + // 3) `$CARGO_HOME/bin/` + // where $CARGO_HOME defaults to ~/.cargo (see https://doc.rust-lang.org/cargo/guide/cargo-home.html) + // example: for cargo, this tries $CARGO_HOME/bin/cargo, or ~/.cargo/bin/cargo if $CARGO_HOME is unset. // It seems that this is a reasonable place to try for cargo, rustc, and rustup let env_var = executable_name.to_ascii_uppercase(); if let Some(path) = env::var_os(env_var) { @@ -43,8 +44,7 @@ fn get_path_for_executable(executable_name: &'static str) -> PathBuf { return executable_name.into(); } - if let Some(mut path) = home::home_dir() { - path.push(".cargo"); + if let Some(mut path) = get_cargo_home() { path.push("bin"); path.push(executable_name); if let Some(path) = probe(path) { @@ -60,6 +60,19 @@ fn lookup_in_path(exec: &str) -> bool { env::split_paths(&paths).map(|path| path.join(exec)).find_map(probe).is_some() } +fn get_cargo_home() -> Option { + if let Some(path) = env::var_os("CARGO_HOME") { + return Some(path.into()); + } + + if let Some(mut path) = home::home_dir() { + path.push(".cargo"); + return Some(path); + } + + None +} + fn probe(path: PathBuf) -> Option { let with_extension = match env::consts::EXE_EXTENSION { "" => None, diff --git a/editors/code/src/toolchain.ts b/editors/code/src/toolchain.ts index e1ca49542802..eb70b88871e3 100644 --- a/editors/code/src/toolchain.ts +++ b/editors/code/src/toolchain.ts @@ -156,19 +156,10 @@ export const getPathForExecutable = memoizeAsync( if (await lookupInPath(executableName)) return executableName; - try { - // hmm, `os.homedir()` seems to be infallible - // it is not mentioned in docs and cannot be inferred by the type signature... - const standardPath = vscode.Uri.joinPath( - vscode.Uri.file(os.homedir()), - ".cargo", - "bin", - executableName - ); - + const cargoHome = getCargoHome(); + if (cargoHome) { + const standardPath = vscode.Uri.joinPath(cargoHome, "bin", executableName); if (await isFileAtUri(standardPath)) return standardPath.fsPath; - } catch (err) { - log.error("Failed to read the fs info", err); } return executableName; } @@ -190,6 +181,21 @@ async function lookupInPath(exec: string): Promise { return false; } +function getCargoHome(): vscode.Uri | null { + const envVar = process.env["CARGO_HOME"]; + if (envVar) return vscode.Uri.file(envVar); + + try { + // hmm, `os.homedir()` seems to be infallible + // it is not mentioned in docs and cannot be inferred by the type signature... + return vscode.Uri.joinPath(vscode.Uri.file(os.homedir()), ".cargo"); + } catch (err) { + log.error("Failed to read the fs info", err); + } + + return null; +} + async function isFileAtPath(path: string): Promise { return isFileAtUri(vscode.Uri.file(path)); } From 002e9341891c95886b3eb29dfa9be1d50d8096d4 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 26 Feb 2023 02:55:52 -0500 Subject: [PATCH 033/362] Don't assume paths work with `fn_sig` in `multiple_unsafe_ops_pre_block`. --- .../src/multiple_unsafe_ops_per_block.rs | 37 +++-------- tests/ui/multiple_unsafe_ops_per_block.rs | 28 +++++++++ tests/ui/multiple_unsafe_ops_per_block.stderr | 62 ++++++++++++++++++- 3 files changed, 99 insertions(+), 28 deletions(-) diff --git a/clippy_lints/src/multiple_unsafe_ops_per_block.rs b/clippy_lints/src/multiple_unsafe_ops_per_block.rs index 63c575fca30b..5418616ded01 100644 --- a/clippy_lints/src/multiple_unsafe_ops_per_block.rs +++ b/clippy_lints/src/multiple_unsafe_ops_per_block.rs @@ -11,6 +11,7 @@ use rustc_ast::Mutability; use rustc_hir as hir; 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::Span; @@ -120,33 +121,15 @@ fn collect_unsafe_exprs<'tcx>( unsafe_ops.push(("raw pointer dereference occurs here", expr.span)); }, - ExprKind::Call(path_expr, _) => match path_expr.kind { - ExprKind::Path(QPath::Resolved( - _, - hir::Path { - res: Res::Def(kind, def_id), - .. - }, - )) if kind.is_fn_like() => { - let sig = cx.tcx.fn_sig(*def_id); - if sig.0.unsafety() == Unsafety::Unsafe { - unsafe_ops.push(("unsafe function call occurs here", expr.span)); - } - }, - - ExprKind::Path(QPath::TypeRelative(..)) => { - if let Some(sig) = cx - .typeck_results() - .type_dependent_def_id(path_expr.hir_id) - .map(|def_id| cx.tcx.fn_sig(def_id)) - { - if sig.0.unsafety() == Unsafety::Unsafe { - unsafe_ops.push(("unsafe function call occurs here", expr.span)); - } - } - }, - - _ => {}, + ExprKind::Call(path_expr, _) => { + let sig = match *cx.typeck_results().expr_ty(path_expr).kind() { + ty::FnDef(id, _) => cx.tcx.fn_sig(id).skip_binder(), + ty::FnPtr(sig) => sig, + _ => return Continue(Descend::Yes), + }; + if sig.unsafety() == Unsafety::Unsafe { + unsafe_ops.push(("unsafe function call occurs here", expr.span)); + } }, ExprKind::MethodCall(..) => { diff --git a/tests/ui/multiple_unsafe_ops_per_block.rs b/tests/ui/multiple_unsafe_ops_per_block.rs index 4511bc99c3c7..5073685c9f06 100644 --- a/tests/ui/multiple_unsafe_ops_per_block.rs +++ b/tests/ui/multiple_unsafe_ops_per_block.rs @@ -116,4 +116,32 @@ fn issue10259() { unsafe_macro!(); } +fn _fn_ptr(x: unsafe fn()) { + unsafe { + x(); + x(); + } +} + +fn _assoc_const() { + trait X { + const X: unsafe fn(); + } + fn _f() { + unsafe { + T::X(); + T::X(); + } + } +} + +fn _field_fn_ptr(x: unsafe fn()) { + struct X(unsafe fn()); + let x = X(x); + unsafe { + x.0(); + x.0(); + } +} + fn main() {} diff --git a/tests/ui/multiple_unsafe_ops_per_block.stderr b/tests/ui/multiple_unsafe_ops_per_block.stderr index 303aeb7aee0c..e0c1d3801f7c 100644 --- a/tests/ui/multiple_unsafe_ops_per_block.stderr +++ b/tests/ui/multiple_unsafe_ops_per_block.stderr @@ -125,5 +125,65 @@ note: raw pointer dereference occurs here LL | unsafe { char::from_u32_unchecked(*ptr.cast::()) } | ^^^^^^^^^^^^^^^^^^ -error: aborting due to 5 previous errors +error: this `unsafe` block contains 2 unsafe operations, expected only one + --> $DIR/multiple_unsafe_ops_per_block.rs:120:5 + | +LL | / unsafe { +LL | | x(); +LL | | x(); +LL | | } + | |_____^ + | +note: unsafe function call occurs here + --> $DIR/multiple_unsafe_ops_per_block.rs:121:9 + | +LL | x(); + | ^^^ +note: unsafe function call occurs here + --> $DIR/multiple_unsafe_ops_per_block.rs:122:9 + | +LL | x(); + | ^^^ + +error: this `unsafe` block contains 2 unsafe operations, expected only one + --> $DIR/multiple_unsafe_ops_per_block.rs:131:9 + | +LL | / unsafe { +LL | | T::X(); +LL | | T::X(); +LL | | } + | |_________^ + | +note: unsafe function call occurs here + --> $DIR/multiple_unsafe_ops_per_block.rs:132:13 + | +LL | T::X(); + | ^^^^^^ +note: unsafe function call occurs here + --> $DIR/multiple_unsafe_ops_per_block.rs:133:13 + | +LL | T::X(); + | ^^^^^^ + +error: this `unsafe` block contains 2 unsafe operations, expected only one + --> $DIR/multiple_unsafe_ops_per_block.rs:141:5 + | +LL | / unsafe { +LL | | x.0(); +LL | | x.0(); +LL | | } + | |_____^ + | +note: unsafe function call occurs here + --> $DIR/multiple_unsafe_ops_per_block.rs:142:9 + | +LL | x.0(); + | ^^^^^ +note: unsafe function call occurs here + --> $DIR/multiple_unsafe_ops_per_block.rs:143:9 + | +LL | x.0(); + | ^^^^^ + +error: aborting due to 8 previous errors From 0f3446b371faaef8181c85f3acc2ee03276f2df2 Mon Sep 17 00:00:00 2001 From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Sun, 26 Feb 2023 08:45:32 +0000 Subject: [PATCH 034/362] Remove `from_fn` lang item It was probably a leftover from the old `?` desugaring but anyways, it's unused now except for clippy, which can just use a diagnostics item. --- clippy_lints/src/operators/cmp_owned.rs | 6 +++--- clippy_lints/src/unnecessary_owned_empty_strings.rs | 3 ++- clippy_lints/src/useless_conversion.rs | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/operators/cmp_owned.rs b/clippy_lints/src/operators/cmp_owned.rs index 24aeb82a37f3..d3de9699fe9d 100644 --- a/clippy_lints/src/operators/cmp_owned.rs +++ b/clippy_lints/src/operators/cmp_owned.rs @@ -49,10 +49,10 @@ fn check_op(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: bool) (arg, arg.span) }, ExprKind::Call(path, [arg]) - if path_def_id(cx, path).map_or(false, |id| { - if match_def_path(cx, id, &paths::FROM_STR_METHOD) { + if path_def_id(cx, path).map_or(false, |did| { + if match_def_path(cx, did, &paths::FROM_STR_METHOD) { true - } else if cx.tcx.lang_items().from_fn() == Some(id) { + } else if cx.tcx.is_diagnostic_item(sym::from_fn, did) { !is_copy(cx, typeck.expr_ty(expr)) } else { false diff --git a/clippy_lints/src/unnecessary_owned_empty_strings.rs b/clippy_lints/src/unnecessary_owned_empty_strings.rs index 9f207d32fcff..6e802794f5aa 100644 --- a/clippy_lints/src/unnecessary_owned_empty_strings.rs +++ b/clippy_lints/src/unnecessary_owned_empty_strings.rs @@ -7,6 +7,7 @@ use rustc_hir::{BorrowKind, Expr, ExprKind, LangItem, Mutability}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::sym; declare_clippy_lint! { /// ### What it does @@ -54,7 +55,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryOwnedEmptyStrings { ); } else { if_chain! { - if Some(fun_def_id) == cx.tcx.lang_items().from_fn(); + if cx.tcx.is_diagnostic_item(sym::from_fn, fun_def_id); if let [.., last_arg] = args; if let ExprKind::Lit(spanned) = &last_arg.kind; if let LitKind::Str(symbol, _) = spanned.node; diff --git a/clippy_lints/src/useless_conversion.rs b/clippy_lints/src/useless_conversion.rs index a95e7b613746..fede625f72a8 100644 --- a/clippy_lints/src/useless_conversion.rs +++ b/clippy_lints/src/useless_conversion.rs @@ -161,7 +161,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { } if_chain! { - if Some(def_id) == cx.tcx.lang_items().from_fn(); + if cx.tcx.is_diagnostic_item(sym::from_fn, def_id); if same_type_and_consts(a, b); then { From aa877645a6d5b16884fe1bd553d6f5ca921ea102 Mon Sep 17 00:00:00 2001 From: morine0122 Date: Sun, 26 Feb 2023 19:04:16 +0900 Subject: [PATCH 035/362] Fix resolving types when resolving HIR and add a related test --- crates/hir/src/source_analyzer.rs | 10 +++++----- crates/ide/src/goto_definition.rs | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index 3b39e9fa919a..ef5434771d94 100644 --- a/crates/hir/src/source_analyzer.rs +++ b/crates/hir/src/source_analyzer.rs @@ -943,17 +943,17 @@ fn resolve_hir_path_( res.map(|ty_ns| (ty_ns, path.segments().first())) } None => { - let (ty, remaining) = + let (ty, remaining_idx) = resolver.resolve_path_in_type_ns(db.upcast(), path.mod_path())?; - match remaining { - Some(remaining) if remaining > 1 => { - if remaining + 1 == path.segments().len() { + match remaining_idx { + Some(remaining_idx) => { + if remaining_idx + 1 == path.segments().len() { Some((ty, path.segments().last())) } else { None } } - _ => Some((ty, path.segments().get(1))), + None => Some((ty, None)), } } }?; diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 93019527f44d..65d1181d0924 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -1065,6 +1065,23 @@ fn f() -> impl Sub {} ); } + #[test] + fn goto_def_for_module_declaration_in_path_if_types_and_values_same_name() { + check( + r#" +mod bar { + pub struct Foo {} + //^^^ + pub fn Foo() {} +} + +fn baz() { + let _foo_enum: bar::Foo$0 = bar::Foo {}; +} + "#, + ) + } + #[test] fn unknown_assoc_ty() { check_unresolved( From c82ff00539c4441eddecd12151f54c30d1b03430 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sat, 25 Feb 2023 15:49:59 +0100 Subject: [PATCH 036/362] Do not suggest to derive Default on generics with implicit arguments --- clippy_lints/src/derivable_impls.rs | 21 +++++++++------- tests/ui/derivable_impls.fixed | 37 +++++++++++++++++++++++++++++ tests/ui/derivable_impls.rs | 37 +++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/derivable_impls.rs b/clippy_lints/src/derivable_impls.rs index f95b8ccf067b..549ef2d24cac 100644 --- a/clippy_lints/src/derivable_impls.rs +++ b/clippy_lints/src/derivable_impls.rs @@ -8,7 +8,7 @@ use rustc_hir::{ Body, Expr, ExprKind, GenericArg, Impl, ImplItemKind, Item, ItemKind, Node, PathSegment, QPath, Ty, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{AdtDef, DefIdTree}; +use rustc_middle::ty::{Adt, AdtDef, DefIdTree, SubstsRef}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::sym; @@ -81,13 +81,18 @@ fn check_struct<'tcx>( self_ty: &Ty<'_>, func_expr: &Expr<'_>, adt_def: AdtDef<'_>, + substs: SubstsRef<'_>, ) { if let TyKind::Path(QPath::Resolved(_, p)) = self_ty.kind { - if let Some(PathSegment { args: Some(a), .. }) = p.segments.last() { - for arg in a.args { - if !matches!(arg, GenericArg::Lifetime(_)) { - return; - } + if let Some(PathSegment { args, .. }) = p.segments.last() { + let args = args.map(|a| a.args).unwrap_or(&[]); + + // substs contains the generic parameters of the type declaration, while args contains the arguments + // used at instantiation time. If both len are not equal, it means that some parameters were not + // provided (which means that the default values were used); in this case we will not risk + // suggesting too broad a rewrite. We won't either if any argument is a type or a const. + if substs.len() != args.len() || args.iter().any(|arg| !matches!(arg, GenericArg::Lifetime(_))) { + return; } } } @@ -184,7 +189,7 @@ impl<'tcx> LateLintPass<'tcx> for DerivableImpls { if let Some(Node::ImplItem(impl_item)) = cx.tcx.hir().find(impl_item_hir); if let ImplItemKind::Fn(_, b) = &impl_item.kind; if let Body { value: func_expr, .. } = cx.tcx.hir().body(*b); - if let Some(adt_def) = cx.tcx.type_of(item.owner_id).subst_identity().ty_adt_def(); + if let &Adt(adt_def, substs) = cx.tcx.type_of(item.owner_id).subst_identity().kind(); if let attrs = cx.tcx.hir().attrs(item.hir_id()); if !attrs.iter().any(|attr| attr.doc_str().is_some()); if let child_attrs = cx.tcx.hir().attrs(impl_item_hir); @@ -192,7 +197,7 @@ impl<'tcx> LateLintPass<'tcx> for DerivableImpls { then { if adt_def.is_struct() { - check_struct(cx, item, self_ty, func_expr, adt_def); + check_struct(cx, item, self_ty, func_expr, adt_def, substs); } else if adt_def.is_enum() && self.msrv.meets(msrvs::DEFAULT_ENUM_ATTRIBUTE) { check_enum(cx, item, func_expr, adt_def); } diff --git a/tests/ui/derivable_impls.fixed b/tests/ui/derivable_impls.fixed index ee8456f5deb8..89ec33a0d8f7 100644 --- a/tests/ui/derivable_impls.fixed +++ b/tests/ui/derivable_impls.fixed @@ -231,4 +231,41 @@ impl Default for NonExhaustiveEnum { } } +// https://github.com/rust-lang/rust-clippy/issues/10396 + +#[derive(Default)] +struct DefaultType; + +struct GenericType { + t: T, +} + +impl Default for GenericType { + fn default() -> Self { + Self { t: Default::default() } + } +} + +struct InnerGenericType { + t: T, +} + +impl Default for InnerGenericType { + fn default() -> Self { + Self { t: Default::default() } + } +} + +struct OtherGenericType { + inner: InnerGenericType, +} + +impl Default for OtherGenericType { + fn default() -> Self { + Self { + inner: Default::default(), + } + } +} + fn main() {} diff --git a/tests/ui/derivable_impls.rs b/tests/ui/derivable_impls.rs index 14af419bcad1..def6e41162f1 100644 --- a/tests/ui/derivable_impls.rs +++ b/tests/ui/derivable_impls.rs @@ -267,4 +267,41 @@ impl Default for NonExhaustiveEnum { } } +// https://github.com/rust-lang/rust-clippy/issues/10396 + +#[derive(Default)] +struct DefaultType; + +struct GenericType { + t: T, +} + +impl Default for GenericType { + fn default() -> Self { + Self { t: Default::default() } + } +} + +struct InnerGenericType { + t: T, +} + +impl Default for InnerGenericType { + fn default() -> Self { + Self { t: Default::default() } + } +} + +struct OtherGenericType { + inner: InnerGenericType, +} + +impl Default for OtherGenericType { + fn default() -> Self { + Self { + inner: Default::default(), + } + } +} + fn main() {} From 5f3df6cfce616664177111df50f1e53c309cfbad Mon Sep 17 00:00:00 2001 From: Chris Down Date: Sun, 26 Feb 2023 17:43:24 +0000 Subject: [PATCH 037/362] exit lint: potentualy -> potentially --- clippy_lints/src/exit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/exit.rs b/clippy_lints/src/exit.rs index 9c8b0d076dfd..8ba6a9e48763 100644 --- a/clippy_lints/src/exit.rs +++ b/clippy_lints/src/exit.rs @@ -11,7 +11,7 @@ declare_clippy_lint! { /// /// ### Why is this bad? /// Exit terminates the program at the location it is called. For unrecoverable - /// errors `panics` should be used to provide a stacktrace and potentualy other + /// errors `panics` should be used to provide a stacktrace and potentially other /// information. A normal termination or one with an error code should happen in /// the main function. /// From daf31a113cd24b85e1c8731b0531063477a1f92b Mon Sep 17 00:00:00 2001 From: Stefan Lankes Date: Sun, 26 Feb 2023 23:13:58 +0100 Subject: [PATCH 038/362] remove unused imports --- library/std/src/sys/hermit/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/library/std/src/sys/hermit/mod.rs b/library/std/src/sys/hermit/mod.rs index d34a4cfedea7..743e93a2fd4d 100644 --- a/library/std/src/sys/hermit/mod.rs +++ b/library/std/src/sys/hermit/mod.rs @@ -15,7 +15,6 @@ #![allow(missing_docs, nonstandard_style, unsafe_op_in_unsafe_fn)] -use crate::intrinsics; use crate::os::raw::c_char; pub mod alloc; From 9a481d1ecf0f11b4a5bd0220eec3fd93c997b033 Mon Sep 17 00:00:00 2001 From: Swarnim Arun Date: Mon, 27 Feb 2023 10:59:20 +0530 Subject: [PATCH 039/362] add: clean api to get `raw_ptr` type --- crates/hir/src/lib.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 2cb4ed2c3351..fba61a2d5b51 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -3190,6 +3190,14 @@ impl Type { matches!(self.ty.kind(Interner), TyKind::Raw(..)) } + pub fn as_raw_ptr_ty(&self) -> Option { + if let TyKind::Raw(_, ty) = self.ty.kind(Interner) { + Some(self.derived(ty.clone())) + } else { + None + } + } + pub fn contains_unknown(&self) -> bool { // FIXME: When we get rid of `ConstScalar::Unknown`, we can just look at precomputed // `TypeFlags` in `TyData`. From 2a9c254e23989df2faaaeb102e5fea8ce948f45e Mon Sep 17 00:00:00 2001 From: Michael Schubart Date: Thu, 16 Feb 2023 18:12:11 +0000 Subject: [PATCH 040/362] Add `collection_is_never_read` --- CHANGELOG.md | 1 + clippy_lints/src/collection_is_never_read.rs | 123 +++++++++++++++++++ clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/lib.rs | 2 + tests/ui/collection_is_never_read.rs | 116 +++++++++++++++++ tests/ui/collection_is_never_read.stderr | 40 ++++++ 6 files changed, 283 insertions(+) create mode 100644 clippy_lints/src/collection_is_never_read.rs create mode 100644 tests/ui/collection_is_never_read.rs create mode 100644 tests/ui/collection_is_never_read.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 765826ed867d..f31e9825a17a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4307,6 +4307,7 @@ Released 2018-09-13 [`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 [`collapsible_str_replace`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_str_replace +[`collection_is_never_read`]: https://rust-lang.github.io/rust-clippy/master/index.html#collection_is_never_read [`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 diff --git a/clippy_lints/src/collection_is_never_read.rs b/clippy_lints/src/collection_is_never_read.rs new file mode 100644 index 000000000000..fbc66a2155f4 --- /dev/null +++ b/clippy_lints/src/collection_is_never_read.rs @@ -0,0 +1,123 @@ +use clippy_utils::diagnostics::span_lint; +use clippy_utils::get_enclosing_block; +use clippy_utils::get_parent_node; +use clippy_utils::path_to_local_id; +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::visitors::for_each_expr_with_closures; +use core::ops::ControlFlow; +use rustc_hir::Block; +use rustc_hir::ExprKind; +use rustc_hir::HirId; +use rustc_hir::Local; +use rustc_hir::Node; +use rustc_hir::PatKind; +use rustc_lint::LateContext; +use rustc_lint::LateLintPass; +use rustc_session::declare_lint_pass; +use rustc_session::declare_tool_lint; +use rustc_span::symbol::sym; +use rustc_span::Symbol; + +declare_clippy_lint! { + /// ### What it does + /// Checks for collections that are never queried. + /// + /// ### Why is this bad? + /// Putting effort into constructing a collection but then never querying it might indicate that + /// the author forgot to do whatever they intended to do with the collection. Example: Clone + /// a vector, sort it for iteration, but then mistakenly iterate the original vector + /// instead. + /// + /// ### Example + /// ```rust + /// let mut sorted_samples = samples.clone(); + /// sorted_samples.sort(); + /// for sample in &samples { // Oops, meant to use `sorted_samples`. + /// println!("{sample}"); + /// } + /// ``` + /// Use instead: + /// ```rust + /// let mut sorted_samples = samples.clone(); + /// sorted_samples.sort(); + /// for sample in &sorted_samples { + /// println!("{sample}"); + /// } + /// ``` + #[clippy::version = "1.69.0"] + pub COLLECTION_IS_NEVER_READ, + nursery, + "a collection is never queried" +} +declare_lint_pass!(CollectionIsNeverRead => [COLLECTION_IS_NEVER_READ]); + +static COLLECTIONS: [Symbol; 10] = [ + sym::BTreeMap, + sym::BTreeSet, + sym::BinaryHeap, + sym::HashMap, + sym::HashSet, + sym::LinkedList, + sym::Option, + sym::String, + sym::Vec, + sym::VecDeque, +]; + +impl<'tcx> LateLintPass<'tcx> for CollectionIsNeverRead { + fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) { + // Look for local variables whose type is a container. Search surrounding bock for read access. + let ty = cx.typeck_results().pat_ty(local.pat); + if COLLECTIONS.iter().any(|&sym| is_type_diagnostic_item(cx, ty, sym)) + && let PatKind::Binding(_, local_id, _, _) = local.pat.kind + && let Some(enclosing_block) = get_enclosing_block(cx, local.hir_id) + && has_no_read_access(cx, local_id, enclosing_block) + { + span_lint(cx, COLLECTION_IS_NEVER_READ, local.span, "collection is never read"); + } + } +} + +fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Block<'tcx>) -> bool { + let mut has_access = false; + let mut has_read_access = false; + + // Inspect all expressions and sub-expressions in the block. + for_each_expr_with_closures(cx, block, |expr| { + // Ignore expressions that are not simply `id`. + if !path_to_local_id(expr, id) { + return ControlFlow::Continue(()); + } + + // `id` is being accessed. Investigate if it's a read access. + has_access = true; + + // `id` appearing in the left-hand side of an assignment is not a read access: + // + // id = ...; // Not reading `id`. + if let Some(Node::Expr(parent)) = get_parent_node(cx.tcx, expr.hir_id) + && let ExprKind::Assign(lhs, ..) = parent.kind + && path_to_local_id(lhs, id) + { + return ControlFlow::Continue(()); + } + + // Method call on `id` in a statement ignores any return value, so it's not a read access: + // + // id.foo(...); // Not reading `id`. + if let Some(Node::Expr(parent)) = get_parent_node(cx.tcx, expr.hir_id) + && let ExprKind::MethodCall(_, receiver, _, _) = parent.kind + && path_to_local_id(receiver, id) + && let Some(Node::Stmt(..)) = get_parent_node(cx.tcx, parent.hir_id) + { + return ControlFlow::Continue(()); + } + + // Any other access to `id` is a read access. Stop searching. + has_read_access = true; + ControlFlow::Break(()) + }); + + // Ignore collections that have no access at all. Other lints should catch them. + has_access && !has_read_access +} diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index cd5dd7a57065..470a2e79e479 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -92,6 +92,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::cognitive_complexity::COGNITIVE_COMPLEXITY_INFO, crate::collapsible_if::COLLAPSIBLE_ELSE_IF_INFO, crate::collapsible_if::COLLAPSIBLE_IF_INFO, + crate::collection_is_never_read::COLLECTION_IS_NEVER_READ_INFO, crate::comparison_chain::COMPARISON_CHAIN_INFO, crate::copies::BRANCHES_SHARING_CODE_INFO, crate::copies::IFS_SAME_COND_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 145cf524652f..c6ad7940b81a 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -87,6 +87,7 @@ mod casts; mod checked_conversions; mod cognitive_complexity; mod collapsible_if; +mod collection_is_never_read; mod comparison_chain; mod copies; mod copy_iterator; @@ -924,6 +925,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: )) }); store.register_late_pass(|_| Box::new(no_mangle_with_rust_abi::NoMangleWithRustAbi)); + store.register_late_pass(|_| Box::new(collection_is_never_read::CollectionIsNeverRead)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/tests/ui/collection_is_never_read.rs b/tests/ui/collection_is_never_read.rs new file mode 100644 index 000000000000..2c37fc212b03 --- /dev/null +++ b/tests/ui/collection_is_never_read.rs @@ -0,0 +1,116 @@ +#![allow(unused)] +#![warn(clippy::collection_is_never_read)] + +use std::collections::HashMap; + +fn main() {} + +fn not_a_collection() { + // TODO: Expand `collection_is_never_read` beyond collections? + let mut x = 10; // Ok + x += 1; +} + +fn no_access_at_all() { + // Other lints should catch this. + let x = vec![1, 2, 3]; // Ok +} + +fn write_without_read() { + // The main use case for `collection_is_never_read`. + let mut x = HashMap::new(); // WARNING + x.insert(1, 2); +} + +fn read_without_write() { + let mut x = vec![1, 2, 3]; // Ok + let _ = x.len(); +} + +fn write_and_read() { + let mut x = vec![1, 2, 3]; // Ok + x.push(4); + let _ = x.len(); +} + +fn write_after_read() { + // TODO: Warn here, but this requires more extensive data flow analysis. + let mut x = vec![1, 2, 3]; // Ok + let _ = x.len(); + x.push(4); // Pointless +} + +fn write_before_reassign() { + // TODO: Warn here, but this requires more extensive data flow analysis. + let mut x = HashMap::new(); // Ok + x.insert(1, 2); // Pointless + x = HashMap::new(); + let _ = x.len(); +} + +fn read_in_closure() { + let mut x = HashMap::new(); // Ok + x.insert(1, 2); + let _ = || { + let _ = x.len(); + }; +} + +fn write_in_closure() { + let mut x = vec![1, 2, 3]; // WARNING + let _ = || { + x.push(4); + }; +} + +fn read_in_format() { + let mut x = HashMap::new(); // Ok + x.insert(1, 2); + format!("{x:?}"); +} + +fn shadowing_1() { + let x = HashMap::::new(); // Ok + let _ = x.len(); + let mut x = HashMap::new(); // WARNING + x.insert(1, 2); +} + +fn shadowing_2() { + let mut x = HashMap::new(); // WARNING + x.insert(1, 2); + let x = HashMap::::new(); // Ok + let _ = x.len(); +} + +#[allow(clippy::let_unit_value)] +fn fake_read() { + let mut x = vec![1, 2, 3]; // Ok + x.reverse(); + // `collection_is_never_read` gets fooled, but other lints should catch this. + let _: () = x.clear(); +} + +fn assignment() { + let mut x = vec![1, 2, 3]; // WARNING + let y = vec![4, 5, 6]; // Ok + x = y; +} + +#[allow(clippy::self_assignment)] +fn self_assignment() { + let mut x = vec![1, 2, 3]; // WARNING + x = x; +} + +fn method_argument_but_not_target() { + struct MyStruct; + impl MyStruct { + fn my_method(&self, _argument: &[usize]) {} + } + let my_struct = MyStruct; + + let mut x = vec![1, 2, 3]; // Ok + x.reverse(); + my_struct.my_method(&x); +} diff --git a/tests/ui/collection_is_never_read.stderr b/tests/ui/collection_is_never_read.stderr new file mode 100644 index 000000000000..43349f550a6c --- /dev/null +++ b/tests/ui/collection_is_never_read.stderr @@ -0,0 +1,40 @@ +error: collection is never read + --> $DIR/collection_is_never_read.rs:21:5 + | +LL | let mut x = HashMap::new(); // WARNING + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::collection-is-never-read` implied by `-D warnings` + +error: collection is never read + --> $DIR/collection_is_never_read.rs:60:5 + | +LL | let mut x = vec![1, 2, 3]; // WARNING + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: collection is never read + --> $DIR/collection_is_never_read.rs:75:5 + | +LL | let mut x = HashMap::new(); // WARNING + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: collection is never read + --> $DIR/collection_is_never_read.rs:80:5 + | +LL | let mut x = HashMap::new(); // WARNING + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: collection is never read + --> $DIR/collection_is_never_read.rs:95:5 + | +LL | let mut x = vec![1, 2, 3]; // WARNING + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: collection is never read + --> $DIR/collection_is_never_read.rs:102:5 + | +LL | let mut x = vec![1, 2, 3]; // WARNING + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 6 previous errors + From ae27762cebdcb441d85f05a906e6ae2e20c5ec47 Mon Sep 17 00:00:00 2001 From: Stefan Lankes Date: Sun, 26 Feb 2023 23:21:19 +0100 Subject: [PATCH 041/362] use `as_ptr` to determine the address of atomics --- library/std/src/sys/hermit/futex.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/std/src/sys/hermit/futex.rs b/library/std/src/sys/hermit/futex.rs index b64c174b06c6..427d8ff6f2e4 100644 --- a/library/std/src/sys/hermit/futex.rs +++ b/library/std/src/sys/hermit/futex.rs @@ -16,7 +16,7 @@ pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option) - let r = unsafe { abi::futex_wait( - futex.as_mut_ptr(), + futex.as_ptr(), expected, timespec.as_ref().map_or(null(), |t| t as *const abi::timespec), abi::FUTEX_RELATIVE_TIMEOUT, @@ -28,12 +28,12 @@ pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option) - #[inline] pub fn futex_wake(futex: &AtomicU32) -> bool { - unsafe { abi::futex_wake(futex.as_mut_ptr(), 1) > 0 } + unsafe { abi::futex_wake(futex.as_ptr(), 1) > 0 } } #[inline] pub fn futex_wake_all(futex: &AtomicU32) { unsafe { - abi::futex_wake(futex.as_mut_ptr(), i32::MAX); + abi::futex_wake(futex.as_ptr(), i32::MAX); } } From 832f8bfe2ba4fcd8ac36e01a8a99d84cbcf6c762 Mon Sep 17 00:00:00 2001 From: Swarnim Arun Date: Mon, 27 Feb 2023 15:57:26 +0530 Subject: [PATCH 042/362] rename: `as_raw_ptr_ty` to `remove_raw_ptr` --- crates/hir/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index fba61a2d5b51..163a31944358 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -3190,7 +3190,7 @@ impl Type { matches!(self.ty.kind(Interner), TyKind::Raw(..)) } - pub fn as_raw_ptr_ty(&self) -> Option { + pub fn remove_raw_ptr(&self) -> Option { if let TyKind::Raw(_, ty) = self.ty.kind(Interner) { Some(self.derived(ty.clone())) } else { From 9e5fa74279b939a752826a52e1c4693205377a26 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 27 Feb 2023 15:51:45 +0100 Subject: [PATCH 043/362] Simplify --- crates/hir-ty/src/method_resolution.rs | 41 +++++++------------ crates/hir/src/lib.rs | 22 ++++++++-- crates/hir/src/semantics.rs | 1 + .../handlers/convert_iter_for_each_to_for.rs | 19 +++------ .../handlers/generate_is_empty_from_len.rs | 9 +--- crates/ide-completion/src/completions/dot.rs | 2 +- crates/ide-db/src/imports/import_assets.rs | 2 +- 7 files changed, 44 insertions(+), 52 deletions(-) diff --git a/crates/hir-ty/src/method_resolution.rs b/crates/hir-ty/src/method_resolution.rs index 8c7714b9a697..6bdfca089207 100644 --- a/crates/hir-ty/src/method_resolution.rs +++ b/crates/hir-ty/src/method_resolution.rs @@ -867,16 +867,20 @@ fn iterate_method_candidates_with_autoref( return ControlFlow::Continue(()); } - iterate_method_candidates_by_receiver( - receiver_ty, - first_adjustment.clone(), - db, - env.clone(), - traits_in_scope, - visible_from_module, - name, - &mut callback, - )?; + let iterate_method_candidates_by_receiver = |receiver_ty, first_adjustment| { + iterate_method_candidates_by_receiver( + receiver_ty, + first_adjustment, + db, + env.clone(), + traits_in_scope, + visible_from_module, + name, + &mut callback, + ) + }; + + iterate_method_candidates_by_receiver(receiver_ty, first_adjustment.clone())?; let refed = Canonical { value: TyKind::Ref(Mutability::Not, static_lifetime(), receiver_ty.value.clone()) @@ -884,16 +888,7 @@ fn iterate_method_candidates_with_autoref( binders: receiver_ty.binders.clone(), }; - iterate_method_candidates_by_receiver( - &refed, - first_adjustment.with_autoref(Mutability::Not), - db, - env.clone(), - traits_in_scope, - visible_from_module, - name, - &mut callback, - )?; + iterate_method_candidates_by_receiver(&refed, first_adjustment.with_autoref(Mutability::Not))?; let ref_muted = Canonical { value: TyKind::Ref(Mutability::Mut, static_lifetime(), receiver_ty.value.clone()) @@ -904,12 +899,6 @@ fn iterate_method_candidates_with_autoref( iterate_method_candidates_by_receiver( &ref_muted, first_adjustment.with_autoref(Mutability::Mut), - db, - env, - traits_in_scope, - visible_from_module, - name, - &mut callback, ) } diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 163a31944358..1f4f3091dfcf 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -3339,12 +3339,10 @@ impl Type { .map(move |ty| self.derived(ty)) } - pub fn iterate_method_candidates( + pub fn iterate_method_candidates_with_traits( &self, db: &dyn HirDatabase, scope: &SemanticsScope<'_>, - // FIXME this can be retrieved from `scope`, except autoimport uses this - // to specify a different set, so the method needs to be split traits_in_scope: &FxHashSet, with_local_impls: Option, name: Option<&Name>, @@ -3372,6 +3370,24 @@ impl Type { slot } + pub fn iterate_method_candidates( + &self, + db: &dyn HirDatabase, + scope: &SemanticsScope<'_>, + with_local_impls: Option, + name: Option<&Name>, + mut callback: impl FnMut(Function) -> Option, + ) -> Option { + self.iterate_method_candidates_with_traits( + db, + scope, + &scope.visible_traits().0, + with_local_impls, + name, + callback, + ) + } + fn iterate_method_candidates_dyn( &self, db: &dyn HirDatabase, diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 486b7ee62ed3..b3ba1c7d349f 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -1673,6 +1673,7 @@ impl<'a> SemanticsScope<'a> { } } +#[derive(Debug)] pub struct VisibleTraits(pub FxHashSet); impl ops::Deref for VisibleTraits { diff --git a/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs b/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs index f32ef2d59d89..9e1d9a702a15 100644 --- a/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs +++ b/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs @@ -157,19 +157,12 @@ fn is_ref_and_impls_iter_method( let iter_trait = FamousDefs(sema, krate).core_iter_Iterator()?; let has_wanted_method = ty - .iterate_method_candidates( - sema.db, - &scope, - &scope.visible_traits().0, - None, - Some(&wanted_method), - |func| { - if func.ret_type(sema.db).impls_trait(sema.db, iter_trait, &[]) { - return Some(()); - } - None - }, - ) + .iterate_method_candidates(sema.db, &scope, None, Some(&wanted_method), |func| { + if func.ret_type(sema.db).impls_trait(sema.db, iter_trait, &[]) { + return Some(()); + } + None + }) .is_some(); if !has_wanted_method { return None; diff --git a/crates/ide-assists/src/handlers/generate_is_empty_from_len.rs b/crates/ide-assists/src/handlers/generate_is_empty_from_len.rs index 9ce525ca375c..442918619604 100644 --- a/crates/ide-assists/src/handlers/generate_is_empty_from_len.rs +++ b/crates/ide-assists/src/handlers/generate_is_empty_from_len.rs @@ -95,14 +95,7 @@ fn get_impl_method( let scope = ctx.sema.scope(impl_.syntax())?; let ty = impl_def.self_ty(db); - ty.iterate_method_candidates( - db, - &scope, - &scope.visible_traits().0, - None, - Some(fn_name), - |func| Some(func), - ) + ty.iterate_method_candidates(db, &scope, None, Some(fn_name), |func| Some(func)) } #[cfg(test)] diff --git a/crates/ide-completion/src/completions/dot.rs b/crates/ide-completion/src/completions/dot.rs index 7c6e5e100f63..09ac57153ae8 100644 --- a/crates/ide-completion/src/completions/dot.rs +++ b/crates/ide-completion/src/completions/dot.rs @@ -122,7 +122,7 @@ fn complete_methods( mut f: impl FnMut(hir::Function), ) { let mut seen_methods = FxHashSet::default(); - receiver.iterate_method_candidates( + receiver.iterate_method_candidates_with_traits( ctx.db, &ctx.scope, &ctx.traits_in_scope(), diff --git a/crates/ide-db/src/imports/import_assets.rs b/crates/ide-db/src/imports/import_assets.rs index 994d48385a0f..b26b0a9087ea 100644 --- a/crates/ide-db/src/imports/import_assets.rs +++ b/crates/ide-db/src/imports/import_assets.rs @@ -528,7 +528,7 @@ fn trait_applicable_items( }, ) } else { - trait_candidate.receiver_ty.iterate_method_candidates( + trait_candidate.receiver_ty.iterate_method_candidates_with_traits( db, scope, &trait_candidates, From 29a4453d5574577870e31fb6fee09fab2a0f6e67 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 27 Feb 2023 16:01:02 +0100 Subject: [PATCH 044/362] Merge the two autoref vecs into one in autoderef_method_receiver --- crates/hir-ty/src/method_resolution.rs | 39 +++++++++++++------------- crates/hir/src/lib.rs | 2 +- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/crates/hir-ty/src/method_resolution.rs b/crates/hir-ty/src/method_resolution.rs index 6bdfca089207..1a5ee97fd047 100644 --- a/crates/hir-ty/src/method_resolution.rs +++ b/crates/hir-ty/src/method_resolution.rs @@ -821,9 +821,9 @@ pub fn iterate_method_candidates_dyn( let mut table = InferenceTable::new(db, env.clone()); let ty = table.instantiate_canonical(ty.clone()); - let (deref_chain, adj) = autoderef_method_receiver(&mut table, ty); + let deref_chain = autoderef_method_receiver(&mut table, ty); - let result = deref_chain.into_iter().zip(adj).try_for_each(|(receiver_ty, adj)| { + let result = deref_chain.into_iter().try_for_each(|(receiver_ty, adj)| { iterate_method_candidates_with_autoref( &receiver_ty, adj, @@ -867,7 +867,7 @@ fn iterate_method_candidates_with_autoref( return ControlFlow::Continue(()); } - let iterate_method_candidates_by_receiver = |receiver_ty, first_adjustment| { + let mut iterate_method_candidates_by_receiver = move |receiver_ty, first_adjustment| { iterate_method_candidates_by_receiver( receiver_ty, first_adjustment, @@ -1199,8 +1199,8 @@ pub fn resolve_indexing_op( ) -> Option { let mut table = InferenceTable::new(db, env.clone()); let ty = table.instantiate_canonical(ty); - let (deref_chain, adj) = autoderef_method_receiver(&mut table, ty); - for (ty, adj) in deref_chain.into_iter().zip(adj) { + let deref_chain = autoderef_method_receiver(&mut table, ty); + for (ty, adj) in deref_chain { let goal = generic_implements_goal(db, env.clone(), index_trait, &ty); if db.trait_solve(env.krate, goal.cast(Interner)).is_some() { return Some(adj); @@ -1410,25 +1410,24 @@ fn generic_implements_goal( fn autoderef_method_receiver( table: &mut InferenceTable<'_>, ty: Ty, -) -> (Vec>, Vec) { - let (mut deref_chain, mut adjustments): (Vec<_>, Vec<_>) = (Vec::new(), Vec::new()); +) -> Vec<(Canonical, ReceiverAdjustments)> { + let mut deref_chain: Vec<_> = Vec::new(); let mut autoderef = autoderef::Autoderef::new(table, ty); while let Some((ty, derefs)) = autoderef.next() { - deref_chain.push(autoderef.table.canonicalize(ty).value); - adjustments.push(ReceiverAdjustments { - autoref: None, - autoderefs: derefs, - unsize_array: false, - }); + deref_chain.push(( + autoderef.table.canonicalize(ty).value, + ReceiverAdjustments { autoref: None, autoderefs: derefs, unsize_array: false }, + )); } // As a last step, we can do array unsizing (that's the only unsizing that rustc does for method receivers!) - if let (Some((TyKind::Array(parameters, _), binders)), Some(adj)) = ( - deref_chain.last().map(|ty| (ty.value.kind(Interner), ty.binders.clone())), - adjustments.last().cloned(), - ) { + if let Some((TyKind::Array(parameters, _), binders, adj)) = + deref_chain.last().map(|(ty, adj)| (ty.value.kind(Interner), ty.binders.clone(), adj)) + { let unsized_ty = TyKind::Slice(parameters.clone()).intern(Interner); - deref_chain.push(Canonical { value: unsized_ty, binders }); - adjustments.push(ReceiverAdjustments { unsize_array: true, ..adj }); + deref_chain.push(( + Canonical { value: unsized_ty, binders }, + ReceiverAdjustments { unsize_array: true, ..adj.clone() }, + )); } - (deref_chain, adjustments) + deref_chain } diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 1f4f3091dfcf..6206a541c1e8 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -3376,7 +3376,7 @@ impl Type { scope: &SemanticsScope<'_>, with_local_impls: Option, name: Option<&Name>, - mut callback: impl FnMut(Function) -> Option, + callback: impl FnMut(Function) -> Option, ) -> Option { self.iterate_method_candidates_with_traits( db, From 9942cc425ba31cf47ebf9498ed3cd58f064b2b89 Mon Sep 17 00:00:00 2001 From: Jonas Marcello Date: Sun, 19 Feb 2023 22:07:33 +0100 Subject: [PATCH 045/362] Fix 14142: Annotate lifetime paramaters in doctest runnables --- crates/hir-def/src/resolver.rs | 7 ++ crates/hir/src/lib.rs | 37 ++++++++++- crates/ide/src/runnables.rs | 117 +++++++++++++++++++++++++++++++-- 3 files changed, 155 insertions(+), 6 deletions(-) diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs index 86958e3daea4..b2323915c1fd 100644 --- a/crates/hir-def/src/resolver.rs +++ b/crates/hir-def/src/resolver.rs @@ -459,6 +459,13 @@ impl Resolver { }) } + pub fn generic_params(&self) -> Option<&Interned> { + self.scopes().find_map(|scope| match scope { + Scope::GenericParams { params, .. } => Some(params), + _ => None, + }) + } + pub fn body_owner(&self) -> Option { self.scopes().find_map(|scope| match scope { Scope::ExprScope(it) => Some(it.owner), diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 6206a541c1e8..28d87e14e15a 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -42,7 +42,7 @@ use hir_def::{ adt::VariantData, body::{BodyDiagnostic, SyntheticSyntax}, expr::{BindingAnnotation, ExprOrPatId, LabelId, Pat, PatId}, - generics::{TypeOrConstParamData, TypeParamProvenance}, + generics::{TypeOrConstParamData, TypeParamProvenance, LifetimeParamData}, item_tree::ItemTreeNode, lang_item::{LangItem, LangItemTarget}, layout::{Layout, LayoutError, ReprOptions}, @@ -1170,6 +1170,22 @@ impl Adt { } } + /// Returns the lifetime of the DataType + pub fn lifetime(&self, db: &dyn HirDatabase) -> Option { + let resolver = match self { + Adt::Struct(s) => s.id.resolver(db.upcast()), + Adt::Union(u) => u.id.resolver(db.upcast()), + Adt::Enum(e) => e.id.resolver(db.upcast()), + }; + resolver.generic_params().and_then(|gp| { + (&gp.lifetimes) + .iter() + // there should only be a single lifetime + // but `Arena` requires to use an iterator + .nth(0) + }).map(|arena| arena.1.clone()) + } + pub fn as_enum(&self) -> Option { if let Self::Enum(v) = self { Some(*v) @@ -3339,6 +3355,25 @@ impl Type { .map(move |ty| self.derived(ty)) } + /// Combines lifetime indicators and type arguments into a single `Vec` + pub fn lifetime_and_type_arguments<'a>(&'a self, db: &'a dyn HirDatabase) -> Vec { + let mut names = if let Some(lt) = self + .as_adt() + .and_then(|a| { + a.lifetime(db) + .and_then(|lt| Some((<.name).to_smol_str().clone())) + }) { + vec![lt] + } else { + vec![] + }; + + for ty in self.type_arguments() { + names.push(SmolStr::new(ty.display(db).to_string())) + } + names + } + pub fn iterate_method_candidates_with_traits( &self, db: &dyn HirDatabase, diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index af53adee898a..2e8f3906afc5 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs @@ -2,7 +2,7 @@ use std::fmt; use ast::HasName; use cfg::CfgExpr; -use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay, Semantics}; +use hir::{AsAssocItem, HasAttrs, HasSource, Semantics}; use ide_assists::utils::test_related_attribute; use ide_db::{ base_db::{FilePosition, FileRange}, @@ -370,9 +370,9 @@ pub(crate) fn runnable_impl( let nav = def.try_to_nav(sema.db)?; let ty = def.self_ty(sema.db); let adt_name = ty.as_adt()?.name(sema.db); - let mut ty_args = ty.type_arguments().peekable(); + let mut ty_args = ty.lifetime_and_type_arguments(sema.db).into_iter().peekable(); let params = if ty_args.peek().is_some() { - format!("<{}>", ty_args.format_with(",", |ty, cb| cb(&ty.display(sema.db)))) + format!("<{}>", ty_args.format_with(",", |ty, cb| cb(&ty))) } else { String::new() }; @@ -436,13 +436,13 @@ fn module_def_doctest(db: &RootDatabase, def: Definition) -> Option { let ty = imp.self_ty(db); if let Some(adt) = ty.as_adt() { let name = adt.name(db); - let mut ty_args = ty.type_arguments().peekable(); + let mut ty_args = ty.lifetime_and_type_arguments(db).into_iter().peekable(); format_to!(path, "{}", name); if ty_args.peek().is_some() { format_to!( path, "<{}>", - ty_args.format_with(",", |ty, cb| cb(&ty.display(db))) + ty_args.format_with(",", |ty, cb| cb(&ty)) ); } format_to!(path, "::{}", def_name); @@ -999,6 +999,113 @@ impl Data { ); } + #[test] + fn test_runnables_doc_test_in_impl_with_lifetime() { + check( + r#" +//- /lib.rs +$0 +fn main() {} + +struct Data<'a>; +impl Data<'a> { + /// ``` + /// let x = 5; + /// ``` + fn foo() {} +} +"#, + &[Bin, DocTest], + expect![[r#" + [ + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 1..13, + focus_range: 4..8, + name: "main", + kind: Function, + }, + kind: Bin, + cfg: None, + }, + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 52..106, + name: "foo", + }, + kind: DocTest { + test_id: Path( + "Data<'a>::foo", + ), + }, + cfg: None, + }, + ] + "#]], + ); + } + + #[test] + fn test_runnables_doc_test_in_impl_with_lifetime_and_types() { + check( + r#" +//- /lib.rs +$0 +fn main() {} + +struct Data<'a, T, U>; +impl Data<'a, T, U> { + /// ``` + /// let x = 5; + /// ``` + fn foo() {} +} +"#, + &[Bin, DocTest], + expect![[r#" + [ + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 1..13, + focus_range: 4..8, + name: "main", + kind: Function, + }, + kind: Bin, + cfg: None, + }, + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 70..124, + name: "foo", + }, + kind: DocTest { + test_id: Path( + "Data<'a,T,U>::foo", + ), + }, + cfg: None, + }, + ] + "#]], + ); + } #[test] fn test_runnables_module() { check( From 8bc75c4c28eff4664979905a7b75b890b3f84437 Mon Sep 17 00:00:00 2001 From: Jonas Marcello Date: Tue, 21 Feb 2023 21:25:56 +0100 Subject: [PATCH 046/362] return Iterator instead of Vec for combined lifetime and argument parameters --- crates/hir/src/lib.rs | 45 +++++++++++++++++-------------------- crates/ide/src/runnables.rs | 10 +++------ 2 files changed, 23 insertions(+), 32 deletions(-) diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 28d87e14e15a..883838293eda 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -42,7 +42,7 @@ use hir_def::{ adt::VariantData, body::{BodyDiagnostic, SyntheticSyntax}, expr::{BindingAnnotation, ExprOrPatId, LabelId, Pat, PatId}, - generics::{TypeOrConstParamData, TypeParamProvenance, LifetimeParamData}, + generics::{LifetimeParamData, TypeOrConstParamData, TypeParamProvenance}, item_tree::ItemTreeNode, lang_item::{LangItem, LangItemTarget}, layout::{Layout, LayoutError, ReprOptions}, @@ -1177,13 +1177,16 @@ impl Adt { Adt::Union(u) => u.id.resolver(db.upcast()), Adt::Enum(e) => e.id.resolver(db.upcast()), }; - resolver.generic_params().and_then(|gp| { - (&gp.lifetimes) - .iter() - // there should only be a single lifetime - // but `Arena` requires to use an iterator - .nth(0) - }).map(|arena| arena.1.clone()) + resolver + .generic_params() + .and_then(|gp| { + (&gp.lifetimes) + .iter() + // there should only be a single lifetime + // but `Arena` requires to use an iterator + .nth(0) + }) + .map(|arena| arena.1.clone()) } pub fn as_enum(&self) -> Option { @@ -3355,23 +3358,15 @@ impl Type { .map(move |ty| self.derived(ty)) } - /// Combines lifetime indicators and type arguments into a single `Vec` - pub fn lifetime_and_type_arguments<'a>(&'a self, db: &'a dyn HirDatabase) -> Vec { - let mut names = if let Some(lt) = self - .as_adt() - .and_then(|a| { - a.lifetime(db) - .and_then(|lt| Some((<.name).to_smol_str().clone())) - }) { - vec![lt] - } else { - vec![] - }; - - for ty in self.type_arguments() { - names.push(SmolStr::new(ty.display(db).to_string())) - } - names + /// Combines lifetime indicators and type arguments into a single `Iterator` + pub fn lifetime_and_type_arguments<'a>( + &'a self, + db: &'a dyn HirDatabase, + ) -> impl Iterator + 'a { + self.as_adt() + .and_then(|a| a.lifetime(db).and_then(|lt| Some((<.name).to_smol_str()))) + .into_iter() + .chain(self.type_arguments().map(|ty| SmolStr::new(ty.display(db).to_string()))) } pub fn iterate_method_candidates_with_traits( diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index 2e8f3906afc5..b0477e967854 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs @@ -370,7 +370,7 @@ pub(crate) fn runnable_impl( let nav = def.try_to_nav(sema.db)?; let ty = def.self_ty(sema.db); let adt_name = ty.as_adt()?.name(sema.db); - let mut ty_args = ty.lifetime_and_type_arguments(sema.db).into_iter().peekable(); + let mut ty_args = ty.lifetime_and_type_arguments(sema.db).peekable(); let params = if ty_args.peek().is_some() { format!("<{}>", ty_args.format_with(",", |ty, cb| cb(&ty))) } else { @@ -436,14 +436,10 @@ fn module_def_doctest(db: &RootDatabase, def: Definition) -> Option { let ty = imp.self_ty(db); if let Some(adt) = ty.as_adt() { let name = adt.name(db); - let mut ty_args = ty.lifetime_and_type_arguments(db).into_iter().peekable(); + let mut ty_args = ty.lifetime_and_type_arguments(db).peekable(); format_to!(path, "{}", name); if ty_args.peek().is_some() { - format_to!( - path, - "<{}>", - ty_args.format_with(",", |ty, cb| cb(&ty)) - ); + format_to!(path, "<{}>", ty_args.format_with(",", |ty, cb| cb(&ty))); } format_to!(path, "::{}", def_name); path.retain(|c| c != ' '); From 9957bb361dac0f1edcadf3753bef13f543d57228 Mon Sep 17 00:00:00 2001 From: Jonas Marcello Date: Fri, 24 Feb 2023 21:09:16 +0100 Subject: [PATCH 047/362] Add const generics to doctest names for structt --- crates/hir/src/lib.rs | 35 +++++++++++- crates/ide/src/runnables.rs | 108 ++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+), 2 deletions(-) diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 883838293eda..369f9192d15c 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -42,7 +42,7 @@ use hir_def::{ adt::VariantData, body::{BodyDiagnostic, SyntheticSyntax}, expr::{BindingAnnotation, ExprOrPatId, LabelId, Pat, PatId}, - generics::{LifetimeParamData, TypeOrConstParamData, TypeParamProvenance}, + generics::{ConstParamData, LifetimeParamData, TypeOrConstParamData, TypeParamProvenance}, item_tree::ItemTreeNode, lang_item::{LangItem, LangItemTarget}, layout::{Layout, LayoutError, ReprOptions}, @@ -1189,6 +1189,31 @@ impl Adt { .map(|arena| arena.1.clone()) } + /// Returns an iterator of all `const` generic paramaters + /// + /// This method is not well optimized, I could not statisfy the borrow + /// checker. I'm sure there are smarter ways to return the consts names + pub fn consts(&self, db: &dyn HirDatabase) -> impl Iterator { + let resolver = match self { + Adt::Struct(s) => s.id.resolver(db.upcast()), + Adt::Union(u) => u.id.resolver(db.upcast()), + Adt::Enum(e) => e.id.resolver(db.upcast()), + }; + resolver + .generic_params() + .map_or(vec![], |gp| { + gp.as_ref() + .type_or_consts + .iter() + .filter_map(|arena| match arena.1 { + TypeOrConstParamData::ConstParamData(consts) => Some(consts.clone()), + _ => None, + }) + .collect::>() + }) + .into_iter() + } + pub fn as_enum(&self) -> Option { if let Self::Enum(v) = self { Some(*v) @@ -3358,15 +3383,21 @@ impl Type { .map(move |ty| self.derived(ty)) } - /// Combines lifetime indicators and type arguments into a single `Iterator` + /// Combines lifetime indicators, type and constant parameters into a single `Iterator` pub fn lifetime_and_type_arguments<'a>( &'a self, db: &'a dyn HirDatabase, ) -> impl Iterator + 'a { + // iterate the lifetime self.as_adt() .and_then(|a| a.lifetime(db).and_then(|lt| Some((<.name).to_smol_str()))) .into_iter() + // add the type paramaters .chain(self.type_arguments().map(|ty| SmolStr::new(ty.display(db).to_string()))) + // add const paramameters + .chain(self.as_adt().map_or(vec![], |a| { + a.consts(db).map(|cs| cs.name.to_smol_str()).collect::>() + })) } pub fn iterate_method_candidates_with_traits( diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index b0477e967854..7af969c5d096 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs @@ -1102,6 +1102,114 @@ impl Data<'a, T, U> { "#]], ); } + + #[test] + fn test_runnables_doc_test_in_impl_with_const() { + check( + r#" +//- /lib.rs +$0 +fn main() {} + +struct Data; +impl Data { + /// ``` + /// let x = 5; + /// ``` + fn foo() {} +} +"#, + &[Bin, DocTest], + expect![[r#" + [ + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 1..13, + focus_range: 4..8, + name: "main", + kind: Function, + }, + kind: Bin, + cfg: None, + }, + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 79..133, + name: "foo", + }, + kind: DocTest { + test_id: Path( + "Data::foo", + ), + }, + cfg: None, + }, + ] + "#]], + ); + } + + #[test] + fn test_runnables_doc_test_in_impl_with_lifetime_types_and_const() { + check( + r#" +//- /lib.rs +$0 +fn main() {} + +struct Data<'a, T, const N: usize>; +impl<'a, T, const N: usize> Data<'a, T, N> { + /// ``` + /// let x = 5; + /// ``` + fn foo() {} +} +"#, + &[Bin, DocTest], + expect![[r#" + [ + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 1..13, + focus_range: 4..8, + name: "main", + kind: Function, + }, + kind: Bin, + cfg: None, + }, + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 100..154, + name: "foo", + }, + kind: DocTest { + test_id: Path( + "Data<'a,T,N>::foo", + ), + }, + cfg: None, + }, + ] + "#]], + ); + } #[test] fn test_runnables_module() { check( From 4ee2e469a211e55741239f1830d41a72ec18f409 Mon Sep 17 00:00:00 2001 From: Jonas Marcello Date: Fri, 24 Feb 2023 21:16:05 +0100 Subject: [PATCH 048/362] Rename the method that returns struct paramaters --- crates/hir/src/lib.rs | 2 +- crates/ide/src/runnables.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 369f9192d15c..d280ff8815a5 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -3384,7 +3384,7 @@ impl Type { } /// Combines lifetime indicators, type and constant parameters into a single `Iterator` - pub fn lifetime_and_type_arguments<'a>( + pub fn lifetime_type_const_paramaters<'a>( &'a self, db: &'a dyn HirDatabase, ) -> impl Iterator + 'a { diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index 7af969c5d096..7fc396f023fc 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs @@ -370,7 +370,7 @@ pub(crate) fn runnable_impl( let nav = def.try_to_nav(sema.db)?; let ty = def.self_ty(sema.db); let adt_name = ty.as_adt()?.name(sema.db); - let mut ty_args = ty.lifetime_and_type_arguments(sema.db).peekable(); + let mut ty_args = ty.lifetime_type_const_paramaters(sema.db).peekable(); let params = if ty_args.peek().is_some() { format!("<{}>", ty_args.format_with(",", |ty, cb| cb(&ty))) } else { @@ -436,7 +436,7 @@ fn module_def_doctest(db: &RootDatabase, def: Definition) -> Option { let ty = imp.self_ty(db); if let Some(adt) = ty.as_adt() { let name = adt.name(db); - let mut ty_args = ty.lifetime_and_type_arguments(db).peekable(); + let mut ty_args = ty.lifetime_type_const_paramaters(db).peekable(); format_to!(path, "{}", name); if ty_args.peek().is_some() { format_to!(path, "<{}>", ty_args.format_with(",", |ty, cb| cb(&ty))); From 7abcc7d8624bb82ecf1292362726ea2c011fe554 Mon Sep 17 00:00:00 2001 From: Jonas Marcello Date: Mon, 27 Feb 2023 07:11:35 +0100 Subject: [PATCH 049/362] Add const to doctest runnable definition Refactor method to get type parameters to add const parameters Remove unused methods --- crates/hir/src/lib.rs | 93 ++++++++++++++++++++++++------------- crates/ide/src/runnables.rs | 54 +++++++++++++++++++++ 2 files changed, 115 insertions(+), 32 deletions(-) diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index d280ff8815a5..82718b2f82cf 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -42,7 +42,7 @@ use hir_def::{ adt::VariantData, body::{BodyDiagnostic, SyntheticSyntax}, expr::{BindingAnnotation, ExprOrPatId, LabelId, Pat, PatId}, - generics::{ConstParamData, LifetimeParamData, TypeOrConstParamData, TypeParamProvenance}, + generics::{LifetimeParamData, TypeOrConstParamData, TypeParamProvenance}, item_tree::ItemTreeNode, lang_item::{LangItem, LangItemTarget}, layout::{Layout, LayoutError, ReprOptions}, @@ -1189,31 +1189,6 @@ impl Adt { .map(|arena| arena.1.clone()) } - /// Returns an iterator of all `const` generic paramaters - /// - /// This method is not well optimized, I could not statisfy the borrow - /// checker. I'm sure there are smarter ways to return the consts names - pub fn consts(&self, db: &dyn HirDatabase) -> impl Iterator { - let resolver = match self { - Adt::Struct(s) => s.id.resolver(db.upcast()), - Adt::Union(u) => u.id.resolver(db.upcast()), - Adt::Enum(e) => e.id.resolver(db.upcast()), - }; - resolver - .generic_params() - .map_or(vec![], |gp| { - gp.as_ref() - .type_or_consts - .iter() - .filter_map(|arena| match arena.1 { - TypeOrConstParamData::ConstParamData(consts) => Some(consts.clone()), - _ => None, - }) - .collect::>() - }) - .into_iter() - } - pub fn as_enum(&self) -> Option { if let Self::Enum(v) = self { Some(*v) @@ -3373,6 +3348,24 @@ impl Type { } } + /// Iterates its type arguments + /// + /// It iterates the actual type arguments when concrete types are used + /// and otherwise the generic names. + /// It does not include `const` arguments. + /// + /// For code, such as: + /// ```text + /// struct Foo + /// + /// impl Foo + /// ``` + /// + /// It iterates: + /// ```text + /// - "String" + /// - "U" + /// ``` pub fn type_arguments(&self) -> impl Iterator + '_ { self.ty .strip_references() @@ -3383,6 +3376,46 @@ impl Type { .map(move |ty| self.derived(ty)) } + /// Iterates its type and const arguments + /// + /// It iterates the actual type and const arguments when concrete types + /// are used and otherwise the generic names. + /// + /// For code, such as: + /// ```text + /// struct Foo + /// + /// impl Foo + /// ``` + /// + /// It iterates: + /// ```text + /// - "String" + /// - "U" + /// - "12" + /// ``` + pub fn type_and_const_arguments<'a>( + &'a self, + db: &'a dyn HirDatabase, + ) -> impl Iterator + 'a { + self.ty + .strip_references() + .as_adt() + .into_iter() + .flat_map(|(_, substs)| substs.iter(Interner)) + .filter_map(|arg| { + // arg can be either a `Ty` or `constant` + if let Some(ty) = arg.ty(Interner) { + Some(SmolStr::new(ty.display(db).to_string())) + // Some(ty) + } else if let Some(const_) = arg.constant(Interner) { + Some(SmolStr::new_inline(&const_.display(db).to_string())) + } else { + None + } + }) + } + /// Combines lifetime indicators, type and constant parameters into a single `Iterator` pub fn lifetime_type_const_paramaters<'a>( &'a self, @@ -3392,12 +3425,8 @@ impl Type { self.as_adt() .and_then(|a| a.lifetime(db).and_then(|lt| Some((<.name).to_smol_str()))) .into_iter() - // add the type paramaters - .chain(self.type_arguments().map(|ty| SmolStr::new(ty.display(db).to_string()))) - // add const paramameters - .chain(self.as_adt().map_or(vec![], |a| { - a.consts(db).map(|cs| cs.name.to_smol_str()).collect::>() - })) + // add the type and const paramaters + .chain(self.type_and_const_arguments(db)) } pub fn iterate_method_candidates_with_traits( diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index 7fc396f023fc..b4fa2f9b9f0a 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs @@ -2272,6 +2272,60 @@ mod tests { ); } + #[test] + fn test_runnables_doc_test_in_impl_with_lifetime_type_const_value() { + check( + r#" +//- /lib.rs +$0 +fn main() {} + +struct Data<'a, A, const B: usize, C, const D: u32>; +impl Data<'a, A, 12, C, D> { + /// ``` + /// ``` + fn foo() {} +} +"#, + &[Bin, DocTest], + expect![[r#" + [ + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 1..13, + focus_range: 4..8, + name: "main", + kind: Function, + }, + kind: Bin, + cfg: None, + }, + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 121..156, + name: "foo", + }, + kind: DocTest { + test_id: Path( + "Data<'a,A,12,C,D>::foo", + ), + }, + cfg: None, + }, + ] + "#]], + ); + } + + #[test] fn doc_test_type_params() { check( From f494d1199d79a515275c8c82206ba0d426c7e3f6 Mon Sep 17 00:00:00 2001 From: Jonas Marcello Date: Mon, 27 Feb 2023 07:15:46 +0100 Subject: [PATCH 050/362] Remove empty line --- crates/hir/src/lib.rs | 1 - crates/ide/src/runnables.rs | 1 - 2 files changed, 2 deletions(-) diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 82718b2f82cf..394ef4a9e098 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -3407,7 +3407,6 @@ impl Type { // arg can be either a `Ty` or `constant` if let Some(ty) = arg.ty(Interner) { Some(SmolStr::new(ty.display(db).to_string())) - // Some(ty) } else if let Some(const_) = arg.constant(Interner) { Some(SmolStr::new_inline(&const_.display(db).to_string())) } else { diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index b4fa2f9b9f0a..8de81206b6fd 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs @@ -2325,7 +2325,6 @@ impl Data<'a, A, 12, C, D> { ); } - #[test] fn doc_test_type_params() { check( From 08708198fbb8eb4bd8df76bded9fcdb32adb8163 Mon Sep 17 00:00:00 2001 From: Michael Schubart Date: Mon, 27 Feb 2023 19:31:59 +0000 Subject: [PATCH 051/362] Fix lint documentation --- clippy_lints/src/collection_is_never_read.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/clippy_lints/src/collection_is_never_read.rs b/clippy_lints/src/collection_is_never_read.rs index fbc66a2155f4..f20c09a44482 100644 --- a/clippy_lints/src/collection_is_never_read.rs +++ b/clippy_lints/src/collection_is_never_read.rs @@ -30,6 +30,7 @@ declare_clippy_lint! { /// /// ### Example /// ```rust + /// # let samples = vec![3, 1, 2]; /// let mut sorted_samples = samples.clone(); /// sorted_samples.sort(); /// for sample in &samples { // Oops, meant to use `sorted_samples`. @@ -38,6 +39,7 @@ declare_clippy_lint! { /// ``` /// Use instead: /// ```rust + /// # let samples = vec![3, 1, 2]; /// let mut sorted_samples = samples.clone(); /// sorted_samples.sort(); /// for sample in &sorted_samples { From cd67589f63bff4356ef933dd090f97de4acb9b52 Mon Sep 17 00:00:00 2001 From: hkalbasi Date: Fri, 3 Feb 2023 14:46:25 +0330 Subject: [PATCH 052/362] beginning of MIR --- crates/hir-def/src/expr.rs | 10 +- crates/hir-def/src/lang_item.rs | 6 +- .../macro_expansion_tests/builtin_fn_macro.rs | 2 +- crates/hir-def/src/path.rs | 4 +- crates/hir-def/src/path/lower.rs | 4 +- crates/hir-def/src/type_ref.rs | 62 +- crates/hir-expand/src/builtin_fn_macro.rs | 2 +- crates/hir-ty/src/builder.rs | 9 + crates/hir-ty/src/chalk_db.rs | 3 +- crates/hir-ty/src/consteval.rs | 503 ++----- crates/hir-ty/src/consteval/tests.rs | 700 ++++++++- crates/hir-ty/src/db.rs | 22 +- crates/hir-ty/src/display.rs | 134 +- crates/hir-ty/src/infer.rs | 12 +- crates/hir-ty/src/infer/expr.rs | 21 +- crates/hir-ty/src/infer/pat.rs | 21 +- crates/hir-ty/src/infer/unify.rs | 5 +- crates/hir-ty/src/inhabitedness.rs | 19 +- crates/hir-ty/src/interner.rs | 4 +- crates/hir-ty/src/layout.rs | 16 +- crates/hir-ty/src/layout/adt.rs | 13 +- crates/hir-ty/src/layout/tests.rs | 9 +- crates/hir-ty/src/lib.rs | 47 +- crates/hir-ty/src/lower.rs | 18 +- crates/hir-ty/src/method_resolution.rs | 39 +- crates/hir-ty/src/mir.rs | 811 +++++++++++ crates/hir-ty/src/mir/eval.rs | 1245 +++++++++++++++++ crates/hir-ty/src/mir/lower.rs | 1209 ++++++++++++++++ crates/hir/src/lib.rs | 31 +- crates/hir/src/source_analyzer.rs | 4 +- .../src/handlers/add_explicit_type.rs | 4 +- crates/ide/src/hover.rs | 1 + crates/ide/src/hover/render.rs | 22 +- crates/ide/src/hover/tests.rs | 113 +- crates/ide/src/inlay_hints/discriminant.rs | 10 +- crates/ide/src/static_index.rs | 1 + crates/rust-analyzer/src/config.rs | 3 + crates/test-utils/src/fixture.rs | 4 +- crates/test-utils/src/minicore.rs | 1 + docs/user/generated_config.adoc | 5 + editors/code/package.json | 5 + 41 files changed, 4452 insertions(+), 702 deletions(-) create mode 100644 crates/hir-ty/src/mir.rs create mode 100644 crates/hir-ty/src/mir/eval.rs create mode 100644 crates/hir-ty/src/mir/lower.rs diff --git a/crates/hir-def/src/expr.rs b/crates/hir-def/src/expr.rs index 48028b7c6a82..8d6f0be2648e 100644 --- a/crates/hir-def/src/expr.rs +++ b/crates/hir-def/src/expr.rs @@ -52,13 +52,21 @@ pub type LabelId = Idx
{ + Ok(self.place_addr_and_ty(p, locals)?.0) + } + + fn ptr_size(&self) -> usize { + match self.db.target_data_layout(self.crate_id) { + Some(x) => x.pointer_size.bytes_usize(), + None => 8, + } + } + + fn place_addr_and_ty<'a>(&'a self, p: &Place, locals: &'a Locals<'a>) -> Result<(Address, Ty)> { + let mut addr = locals.ptr[p.local]; + let mut ty: Ty = + self.ty_filler(&locals.body.locals[p.local].ty, locals.subst, locals.body.owner)?; + for proj in &p.projection { + match proj { + ProjectionElem::Deref => { + match &ty.data(Interner).kind { + TyKind::Ref(_, _, inner) => { + ty = inner.clone(); + } + _ => not_supported!("dereferencing smart pointers"), + } + let x = from_bytes!(usize, self.read_memory(addr, self.ptr_size())?); + addr = Address::from_usize(x); + } + ProjectionElem::Index(op) => { + let offset = + from_bytes!(usize, self.read_memory(locals.ptr[*op], self.ptr_size())?); + match &ty.data(Interner).kind { + TyKind::Ref(_, _, inner) => match &inner.data(Interner).kind { + TyKind::Slice(inner) => { + ty = inner.clone(); + let ty_size = self.size_of_sized( + &ty, + locals, + "slice inner type should be sized", + )?; + let value = self.read_memory(addr, self.ptr_size() * 2)?; + addr = Address::from_bytes(&value[0..8])?.offset(ty_size * offset); + } + x => not_supported!("MIR index for ref type {x:?}"), + }, + TyKind::Array(inner, _) | TyKind::Slice(inner) => { + ty = inner.clone(); + let ty_size = self.size_of_sized( + &ty, + locals, + "array inner type should be sized", + )?; + addr = addr.offset(ty_size * offset); + } + x => not_supported!("MIR index for type {x:?}"), + } + } + &ProjectionElem::TupleField(f) => match &ty.data(Interner).kind { + TyKind::Tuple(_, subst) => { + let layout = self.layout(&ty)?; + ty = subst + .as_slice(Interner) + .get(f) + .ok_or(MirEvalError::TypeError("not enough tuple fields"))? + .assert_ty_ref(Interner) + .clone(); + let offset = layout.fields.offset(f).bytes_usize(); + addr = addr.offset(offset); + } + _ => return Err(MirEvalError::TypeError("Only tuple has tuple fields")), + }, + ProjectionElem::Field(f) => match &ty.data(Interner).kind { + TyKind::Adt(adt, subst) => { + let layout = self.layout_adt(adt.0, subst.clone())?; + let variant_layout = match &layout.variants { + Variants::Single { .. } => &layout, + Variants::Multiple { variants, .. } => { + &variants[match f.parent { + hir_def::VariantId::EnumVariantId(x) => { + RustcEnumVariantIdx(x.local_id) + } + _ => { + return Err(MirEvalError::TypeError( + "Multivariant layout only happens for enums", + )) + } + }] + } + }; + ty = self.db.field_types(f.parent)[f.local_id] + .clone() + .substitute(Interner, subst); + let offset = variant_layout + .fields + .offset(u32::from(f.local_id.into_raw()) as usize) + .bytes_usize(); + addr = addr.offset(offset); + } + _ => return Err(MirEvalError::TypeError("Only adt has fields")), + }, + ProjectionElem::ConstantIndex { .. } => { + not_supported!("constant index") + } + ProjectionElem::Subslice { .. } => not_supported!("subslice"), + ProjectionElem::OpaqueCast(_) => not_supported!("opaque cast"), + } + } + Ok((addr, ty)) + } + + fn layout(&self, ty: &Ty) -> Result { + layout_of_ty(self.db, ty, self.crate_id) + .map_err(|e| MirEvalError::LayoutError(e, ty.clone())) + } + + fn layout_adt(&self, adt: AdtId, subst: Substitution) -> Result { + self.db.layout_of_adt(adt, subst.clone()).map_err(|e| { + MirEvalError::LayoutError(e, TyKind::Adt(chalk_ir::AdtId(adt), subst).intern(Interner)) + }) + } + + fn place_ty<'a>(&'a self, p: &Place, locals: &'a Locals<'a>) -> Result { + Ok(self.place_addr_and_ty(p, locals)?.1) + } + + fn operand_ty<'a>(&'a self, o: &'a Operand, locals: &'a Locals<'a>) -> Result { + Ok(match o { + Operand::Copy(p) | Operand::Move(p) => self.place_ty(p, locals)?, + Operand::Constant(c) => c.data(Interner).ty.clone(), + }) + } + + fn interpret_mir( + &mut self, + body: &MirBody, + args: impl Iterator>, + subst: Substitution, + ) -> Result> { + if let Some(x) = self.stack_depth_limit.checked_sub(1) { + self.stack_depth_limit = x; + } else { + return Err(MirEvalError::StackOverflow); + } + let mut current_block_idx = body.start_block; + let mut locals = Locals { ptr: &ArenaMap::new(), body: &body, subst: &subst }; + let (locals_ptr, stack_size) = { + let mut stack_ptr = self.stack.len(); + let addr = body + .locals + .iter() + .map(|(id, x)| { + let size = self.size_of_sized(&x.ty, &locals, "no unsized local")?; + let my_ptr = stack_ptr; + stack_ptr += size; + Ok((id, Stack(my_ptr))) + }) + .collect::>>()?; + let stack_size = stack_ptr - self.stack.len(); + (addr, stack_size) + }; + locals.ptr = &locals_ptr; + self.stack.extend(iter::repeat(0).take(stack_size)); + let mut remain_args = body.arg_count; + for ((_, addr), value) in locals_ptr.iter().skip(1).zip(args) { + self.write_memory(*addr, &value)?; + if remain_args == 0 { + return Err(MirEvalError::TypeError("more arguments provided")); + } + remain_args -= 1; + } + if remain_args > 0 { + return Err(MirEvalError::TypeError("not enough arguments provided")); + } + loop { + let current_block = &body.basic_blocks[current_block_idx]; + if let Some(x) = self.execution_limit.checked_sub(1) { + self.execution_limit = x; + } else { + return Err(MirEvalError::ExecutionLimitExceeded); + } + for statement in ¤t_block.statements { + match statement { + Statement::Assign(l, r) => { + let addr = self.place_addr(l, &locals)?; + let result = self.eval_rvalue(r, &locals)?.to_vec(&self)?; + self.write_memory(addr, &result)?; + } + Statement::Deinit(_) => not_supported!("de-init statement"), + Statement::StorageLive(_) => not_supported!("storage-live statement"), + Statement::StorageDead(_) => not_supported!("storage-dead statement"), + Statement::Nop => (), + } + } + let Some(terminator) = current_block.terminator.as_ref() else { + not_supported!("block without terminator"); + }; + match terminator { + Terminator::Goto { target } => { + current_block_idx = *target; + } + Terminator::Call { + func, + args, + destination, + target, + cleanup: _, + from_hir_call: _, + } => { + let fn_ty = self.operand_ty(func, &locals)?; + match &fn_ty.data(Interner).kind { + TyKind::FnDef(def, generic_args) => { + let def: CallableDefId = from_chalk(self.db, *def); + let generic_args = self.subst_filler(generic_args, &locals); + match def { + CallableDefId::FunctionId(def) => { + let arg_bytes = args + .iter() + .map(|x| { + Ok(self + .eval_operand(x, &locals)? + .get(&self)? + .to_owned()) + }) + .collect::>>()? + .into_iter(); + let function_data = self.db.function_data(def); + let is_intrinsic = match &function_data.abi { + Some(abi) => *abi == Interned::new_str("rust-intrinsic"), + None => match def.lookup(self.db.upcast()).container { + hir_def::ItemContainerId::ExternBlockId(block) => { + let id = block.lookup(self.db.upcast()).id; + id.item_tree(self.db.upcast())[id.value] + .abi + .as_deref() + == Some("rust-intrinsic") + } + _ => false, + }, + }; + let result = if is_intrinsic { + self.exec_intrinsic( + function_data + .name + .as_text() + .unwrap_or_default() + .as_str(), + arg_bytes, + generic_args, + &locals, + )? + } else if let Some(x) = self.detect_lang_function(def) { + self.exec_lang_item(x, arg_bytes)? + } else { + let trait_env = { + let Some(d) = body.owner.as_generic_def_id() else { + not_supported!("trait resolving in non generic def id"); + }; + self.db.trait_environment(d) + }; + let (imp, generic_args) = lookup_impl_method( + self.db, + trait_env, + def, + generic_args.clone(), + ); + let generic_args = + self.subst_filler(&generic_args, &locals); + let def = imp.into(); + let mir_body = self + .db + .mir_body(def) + .map_err(|e| MirEvalError::MirLowerError(imp, e))?; + self.interpret_mir(&mir_body, arg_bytes, generic_args) + .map_err(|e| { + MirEvalError::InFunction(imp, Box::new(e)) + })? + }; + let dest_addr = self.place_addr(destination, &locals)?; + self.write_memory(dest_addr, &result)?; + } + CallableDefId::StructId(id) => { + let (size, variant_layout, tag) = self.layout_of_variant( + id.into(), + generic_args.clone(), + &locals, + )?; + let result = self.make_by_layout( + size, + &variant_layout, + tag, + args, + &locals, + )?; + let dest_addr = self.place_addr(destination, &locals)?; + self.write_memory(dest_addr, &result)?; + } + CallableDefId::EnumVariantId(id) => { + let (size, variant_layout, tag) = self.layout_of_variant( + id.into(), + generic_args.clone(), + &locals, + )?; + let result = self.make_by_layout( + size, + &variant_layout, + tag, + args, + &locals, + )?; + let dest_addr = self.place_addr(destination, &locals)?; + self.write_memory(dest_addr, &result)?; + } + } + current_block_idx = + target.expect("broken mir, function without target"); + } + _ => not_supported!("unknown function type"), + } + } + Terminator::SwitchInt { discr, targets } => { + let val = u128::from_le_bytes(pad16( + self.eval_operand(discr, &locals)?.get(&self)?, + false, + )); + current_block_idx = targets.target_for_value(val); + } + Terminator::Return => { + let ty = body.locals[return_slot()].ty.clone(); + self.stack_depth_limit += 1; + return Ok(self + .read_memory( + locals.ptr[return_slot()], + self.size_of_sized(&ty, &locals, "return type")?, + )? + .to_owned()); + } + Terminator::Unreachable => { + return Err(MirEvalError::UndefinedBehavior("unreachable executed")) + } + _ => not_supported!("unknown terminator"), + } + } + } + + fn eval_rvalue<'a>( + &'a mut self, + r: &'a Rvalue, + locals: &'a Locals<'a>, + ) -> Result { + use IntervalOrOwned::*; + Ok(match r { + Rvalue::Use(x) => Borrowed(self.eval_operand(x, locals)?), + Rvalue::Ref(_, p) => { + let addr = self.place_addr(p, locals)?; + Owned(addr.to_bytes()) + } + Rvalue::Len(_) => not_supported!("rvalue len"), + Rvalue::UnaryOp(op, val) => { + let mut c = self.eval_operand(val, locals)?.get(&self)?; + let mut ty = self.operand_ty(val, locals)?; + while let TyKind::Ref(_, _, z) = ty.kind(Interner) { + ty = z.clone(); + let size = self.size_of_sized(&ty, locals, "operand of unary op")?; + c = self.read_memory(Address::from_bytes(c)?, size)?; + } + let mut c = c.to_vec(); + if ty.as_builtin() == Some(BuiltinType::Bool) { + c[0] = 1 - c[0]; + } else { + match op { + UnOp::Not => c.iter_mut().for_each(|x| *x = !*x), + UnOp::Neg => { + c.iter_mut().for_each(|x| *x = !*x); + for k in c.iter_mut() { + let o; + (*k, o) = k.overflowing_add(1); + if !o { + break; + } + } + } + } + } + Owned(c) + } + Rvalue::CheckedBinaryOp(op, lhs, rhs) => { + let lc = self.eval_operand(lhs, locals)?; + let rc = self.eval_operand(rhs, locals)?; + let mut lc = lc.get(&self)?; + let mut rc = rc.get(&self)?; + let mut ty = self.operand_ty(lhs, locals)?; + while let TyKind::Ref(_, _, z) = ty.kind(Interner) { + ty = z.clone(); + let size = self.size_of_sized(&ty, locals, "operand of binary op")?; + lc = self.read_memory(Address::from_bytes(lc)?, size)?; + rc = self.read_memory(Address::from_bytes(rc)?, size)?; + } + let is_signed = matches!(ty.as_builtin(), Some(BuiltinType::Int(_))); + let l128 = i128::from_le_bytes(pad16(lc, is_signed)); + let r128 = i128::from_le_bytes(pad16(rc, is_signed)); + match op { + BinOp::Ge | BinOp::Gt | BinOp::Le | BinOp::Lt | BinOp::Eq | BinOp::Ne => { + let r = match op { + BinOp::Ge => l128 >= r128, + BinOp::Gt => l128 > r128, + BinOp::Le => l128 <= r128, + BinOp::Lt => l128 < r128, + BinOp::Eq => l128 == r128, + BinOp::Ne => l128 != r128, + _ => unreachable!(), + }; + let r = r as u8; + Owned(vec![r]) + } + BinOp::BitAnd + | BinOp::BitOr + | BinOp::BitXor + | BinOp::Add + | BinOp::Mul + | BinOp::Div + | BinOp::Rem + | BinOp::Sub => { + let r = match op { + BinOp::Add => l128.overflowing_add(r128).0, + BinOp::Mul => l128.overflowing_mul(r128).0, + BinOp::Div => l128.checked_div(r128).ok_or(MirEvalError::Panic)?, + BinOp::Rem => l128.checked_rem(r128).ok_or(MirEvalError::Panic)?, + BinOp::Sub => l128.overflowing_sub(r128).0, + BinOp::BitAnd => l128 & r128, + BinOp::BitOr => l128 | r128, + BinOp::BitXor => l128 ^ r128, + _ => unreachable!(), + }; + let r = r.to_le_bytes(); + for &k in &r[lc.len()..] { + if k != 0 && (k != 255 || !is_signed) { + return Err(MirEvalError::Panic); + } + } + Owned(r[0..lc.len()].into()) + } + BinOp::Shl | BinOp::Shr => { + let shift_amout = if r128 < 0 { + return Err(MirEvalError::Panic); + } else if r128 > 128 { + return Err(MirEvalError::Panic); + } else { + r128 as u8 + }; + let r = match op { + BinOp::Shl => l128 << shift_amout, + BinOp::Shr => l128 >> shift_amout, + _ => unreachable!(), + }; + Owned(r.to_le_bytes()[0..lc.len()].into()) + } + BinOp::Offset => not_supported!("offset binop"), + } + } + Rvalue::Discriminant(p) => { + let ty = self.place_ty(p, locals)?; + let bytes = self.eval_place(p, locals)?.get(&self)?; + let layout = self.layout(&ty)?; + match layout.variants { + Variants::Single { .. } => Owned(0u128.to_le_bytes().to_vec()), + Variants::Multiple { tag, tag_encoding, .. } => { + let Some(target_data_layout) = self.db.target_data_layout(self.crate_id) else { + not_supported!("missing target data layout"); + }; + let size = tag.size(&*target_data_layout).bytes_usize(); + let offset = layout.fields.offset(0).bytes_usize(); // The only field on enum variants is the tag field + match tag_encoding { + TagEncoding::Direct => { + let tag = &bytes[offset..offset + size]; + Owned(pad16(tag, false).to_vec()) + } + TagEncoding::Niche { untagged_variant, niche_start, .. } => { + let tag = &bytes[offset..offset + size]; + let candidate_discriminant = i128::from_le_bytes(pad16(tag, false)) + .wrapping_sub(niche_start as i128); + let enum_id = match ty.kind(Interner) { + TyKind::Adt(e, _) => match e.0 { + AdtId::EnumId(e) => e, + _ => not_supported!("Non enum with multi variant layout"), + }, + _ => not_supported!("Non adt with multi variant layout"), + }; + let enum_data = self.db.enum_data(enum_id); + let result = 'b: { + for (local_id, _) in enum_data.variants.iter() { + if candidate_discriminant + == self.db.const_eval_discriminant(EnumVariantId { + parent: enum_id, + local_id, + })? + { + break 'b candidate_discriminant; + } + } + self.db.const_eval_discriminant(EnumVariantId { + parent: enum_id, + local_id: untagged_variant.0, + })? + }; + Owned(result.to_le_bytes().to_vec()) + } + } + } + } + } + Rvalue::ShallowInitBox(_, _) => not_supported!("shallow init box"), + Rvalue::CopyForDeref(_) => not_supported!("copy for deref"), + Rvalue::Aggregate(kind, values) => match kind { + AggregateKind::Array(_) => { + let mut r = vec![]; + for x in values { + let value = self.eval_operand(x, locals)?.get(&self)?; + r.extend(value); + } + Owned(r) + } + AggregateKind::Tuple(ty) => { + let layout = self.layout(&ty)?; + Owned(self.make_by_layout( + layout.size.bytes_usize(), + &layout, + None, + values, + locals, + )?) + } + AggregateKind::Union(x, f) => { + let layout = self.layout_adt((*x).into(), Substitution::empty(Interner))?; + let offset = layout + .fields + .offset(u32::from(f.local_id.into_raw()) as usize) + .bytes_usize(); + let op = self.eval_operand(&values[0], locals)?.get(&self)?; + let mut result = vec![0; layout.size.bytes_usize()]; + result[offset..offset + op.len()].copy_from_slice(op); + Owned(result) + } + AggregateKind::Adt(x, subst) => { + let (size, variant_layout, tag) = + self.layout_of_variant(*x, subst.clone(), locals)?; + Owned(self.make_by_layout(size, &variant_layout, tag, values, locals)?) + } + }, + Rvalue::Cast(kind, operand, target_ty) => match kind { + CastKind::PointerExposeAddress => not_supported!("exposing pointer address"), + CastKind::PointerFromExposedAddress => { + not_supported!("creating pointer from exposed address") + } + CastKind::Pointer(cast) => match cast { + PointerCast::Unsize => { + let current_ty = self.operand_ty(operand, locals)?; + match &target_ty.data(Interner).kind { + TyKind::Raw(_, ty) | TyKind::Ref(_, _, ty) => { + match &ty.data(Interner).kind { + TyKind::Slice(_) => match ¤t_ty.data(Interner).kind { + TyKind::Raw(_, ty) | TyKind::Ref(_, _, ty) => { + match &ty.data(Interner).kind { + TyKind::Array(_, size) => { + let addr = self + .eval_operand(operand, locals)? + .get(&self)?; + let len = const_as_usize(size); + let mut r = Vec::with_capacity(16); + r.extend(addr.iter().copied()); + r.extend(len.to_le_bytes().into_iter()); + Owned(r) + } + _ => { + not_supported!("slice unsizing from non arrays") + } + } + } + _ => not_supported!("slice unsizing from non pointers"), + }, + TyKind::Dyn(_) => not_supported!("dyn pointer unsize cast"), + _ => not_supported!("unknown unsized cast"), + } + } + _ => not_supported!("unsized cast on unknown pointer type"), + } + } + x => not_supported!("pointer cast {x:?}"), + }, + CastKind::DynStar => not_supported!("dyn star cast"), + CastKind::IntToInt => { + // FIXME: handle signed cast + let current = pad16(self.eval_operand(operand, locals)?.get(&self)?, false); + let dest_size = + self.size_of_sized(target_ty, locals, "destination of int to int cast")?; + Owned(current[0..dest_size].to_vec()) + } + CastKind::FloatToInt => not_supported!("float to int cast"), + CastKind::FloatToFloat => not_supported!("float to float cast"), + CastKind::IntToFloat => not_supported!("float to int cast"), + CastKind::PtrToPtr => not_supported!("ptr to ptr cast"), + CastKind::FnPtrToPtr => not_supported!("fn ptr to ptr cast"), + }, + }) + } + + fn layout_of_variant( + &mut self, + x: VariantId, + subst: Substitution, + locals: &Locals<'_>, + ) -> Result<(usize, Layout, Option<(usize, usize, i128)>)> { + let adt = x.adt_id(); + if let DefWithBodyId::VariantId(f) = locals.body.owner { + if let VariantId::EnumVariantId(x) = x { + if AdtId::from(f.parent) == adt { + // Computing the exact size of enums require resolving the enum discriminants. In order to prevent loops (and + // infinite sized type errors) we use a dummy layout + let i = self.db.const_eval_discriminant(x)?; + return Ok((16, self.layout(&TyBuilder::unit())?, Some((0, 16, i)))); + } + } + } + let layout = self.layout_adt(adt, subst)?; + Ok(match layout.variants { + Variants::Single { .. } => (layout.size.bytes_usize(), layout, None), + Variants::Multiple { variants, tag, tag_encoding, .. } => { + let cx = self + .db + .target_data_layout(self.crate_id) + .ok_or(MirEvalError::TargetDataLayoutNotAvailable)?; + let enum_variant_id = match x { + VariantId::EnumVariantId(x) => x, + _ => not_supported!("multi variant layout for non-enums"), + }; + let rustc_enum_variant_idx = RustcEnumVariantIdx(enum_variant_id.local_id); + let mut discriminant = self.db.const_eval_discriminant(enum_variant_id)?; + let variant_layout = variants[rustc_enum_variant_idx].clone(); + let have_tag = match tag_encoding { + TagEncoding::Direct => true, + TagEncoding::Niche { untagged_variant, niche_variants: _, niche_start } => { + discriminant = discriminant.wrapping_add(niche_start as i128); + untagged_variant != rustc_enum_variant_idx + } + }; + ( + layout.size.bytes_usize(), + variant_layout, + if have_tag { + Some(( + layout.fields.offset(0).bytes_usize(), + tag.size(&*cx).bytes_usize(), + discriminant, + )) + } else { + None + }, + ) + } + }) + } + + fn make_by_layout( + &mut self, + size: usize, // Not neccessarily equal to variant_layout.size + variant_layout: &Layout, + tag: Option<(usize, usize, i128)>, + values: &Vec, + locals: &Locals<'_>, + ) -> Result> { + let mut result = vec![0; size]; + if let Some((offset, size, value)) = tag { + result[offset..offset + size].copy_from_slice(&value.to_le_bytes()[0..size]); + } + for (i, op) in values.iter().enumerate() { + let offset = variant_layout.fields.offset(i).bytes_usize(); + let op = self.eval_operand(op, locals)?.get(&self)?; + result[offset..offset + op.len()].copy_from_slice(op); + } + Ok(result) + } + + fn eval_operand(&mut self, x: &Operand, locals: &Locals<'_>) -> Result { + Ok(match x { + Operand::Copy(p) | Operand::Move(p) => self.eval_place(p, locals)?, + Operand::Constant(konst) => { + let data = &konst.data(Interner); + match &data.value { + chalk_ir::ConstValue::BoundVar(b) => { + let c = locals + .subst + .as_slice(Interner) + .get(b.index) + .ok_or(MirEvalError::TypeError("missing generic arg"))? + .assert_const_ref(Interner); + self.eval_operand(&Operand::Constant(c.clone()), locals)? + } + chalk_ir::ConstValue::InferenceVar(_) => { + not_supported!("inference var constant") + } + chalk_ir::ConstValue::Placeholder(_) => not_supported!("placeholder constant"), + chalk_ir::ConstValue::Concrete(c) => match &c.interned { + ConstScalar::Bytes(v, memory_map) => { + let mut v: Cow<'_, [u8]> = Cow::Borrowed(v); + let patch_map = memory_map.transform_addresses(|b| { + let addr = self.heap_allocate(b.len()); + self.write_memory(addr, b)?; + Ok(addr.to_usize()) + })?; + let size = self.size_of(&data.ty, locals)?.unwrap_or(v.len()); + if size != v.len() { + // Handle self enum + if size == 16 && v.len() < 16 { + v = Cow::Owned(pad16(&v, false).to_vec()); + } else if size < 16 && v.len() == 16 { + v = Cow::Owned(v[0..size].to_vec()); + } else { + return Err(MirEvalError::InvalidConst(konst.clone())); + } + } + let addr = self.heap_allocate(size); + self.write_memory(addr, &v)?; + self.patch_addresses(&patch_map, addr, &data.ty, locals)?; + Interval::new(addr, size) + } + ConstScalar::Unknown => not_supported!("evaluating unknown const"), + }, + } + } + }) + } + + fn eval_place(&mut self, p: &Place, locals: &Locals<'_>) -> Result { + let addr = self.place_addr(p, locals)?; + Ok(Interval::new( + addr, + self.size_of_sized(&self.place_ty(p, locals)?, locals, "type of this place")?, + )) + } + + fn read_memory(&self, addr: Address, size: usize) -> Result<&[u8]> { + let (mem, pos) = match addr { + Stack(x) => (&self.stack, x), + Heap(x) => (&self.heap, x), + }; + mem.get(pos..pos + size).ok_or(MirEvalError::UndefinedBehavior("out of bound memory read")) + } + + fn write_memory(&mut self, addr: Address, r: &[u8]) -> Result<()> { + let (mem, pos) = match addr { + Stack(x) => (&mut self.stack, x), + Heap(x) => (&mut self.heap, x), + }; + mem.get_mut(pos..pos + r.len()) + .ok_or(MirEvalError::UndefinedBehavior("out of bound memory write"))? + .copy_from_slice(r); + Ok(()) + } + + fn size_of(&self, ty: &Ty, locals: &Locals<'_>) -> Result> { + if let DefWithBodyId::VariantId(f) = locals.body.owner { + if let Some((adt, _)) = ty.as_adt() { + if AdtId::from(f.parent) == adt { + // Computing the exact size of enums require resolving the enum discriminants. In order to prevent loops (and + // infinite sized type errors) we use a dummy size + return Ok(Some(16)); + } + } + } + let ty = &self.ty_filler(ty, locals.subst, locals.body.owner)?; + let layout = self.layout(ty); + if self.assert_placeholder_ty_is_unused { + if matches!(layout, Err(MirEvalError::LayoutError(LayoutError::HasPlaceholder, _))) { + return Ok(Some(0)); + } + } + let layout = layout?; + Ok(layout.is_sized().then(|| layout.size.bytes_usize())) + } + + /// A version of `self.size_of` which returns error if the type is unsized. `what` argument should + /// be something that complete this: `error: type {ty} was unsized. {what} should be sized` + fn size_of_sized(&self, ty: &Ty, locals: &Locals<'_>, what: &'static str) -> Result { + match self.size_of(ty, locals)? { + Some(x) => Ok(x), + None => Err(MirEvalError::TypeIsUnsized(ty.clone(), what)), + } + } + + /// Uses `ty_filler` to fill an entire subst + fn subst_filler(&self, subst: &Substitution, locals: &Locals<'_>) -> Substitution { + Substitution::from_iter( + Interner, + subst.iter(Interner).map(|x| match x.data(Interner) { + chalk_ir::GenericArgData::Ty(ty) => { + let Ok(ty) = self.ty_filler(ty, locals.subst, locals.body.owner) else { + return x.clone(); + }; + chalk_ir::GenericArgData::Ty(ty).intern(Interner) + } + _ => x.clone(), + }), + ) + } + + /// This function substitutes placeholders of the body with the provided subst, effectively plays + /// the rule of monomorphization. In addition to placeholders, it substitutes opaque types (return + /// position impl traits) with their underlying type. + fn ty_filler(&self, ty: &Ty, subst: &Substitution, owner: DefWithBodyId) -> Result { + struct Filler<'a> { + db: &'a dyn HirDatabase, + subst: &'a Substitution, + skip_params: usize, + } + impl FallibleTypeFolder for Filler<'_> { + type Error = MirEvalError; + + fn as_dyn(&mut self) -> &mut dyn FallibleTypeFolder { + self + } + + fn interner(&self) -> Interner { + Interner + } + + fn try_fold_ty( + &mut self, + ty: Ty, + outer_binder: DebruijnIndex, + ) -> std::result::Result { + match ty.kind(Interner) { + TyKind::OpaqueType(id, subst) => { + let impl_trait_id = self.db.lookup_intern_impl_trait_id((*id).into()); + match impl_trait_id { + crate::ImplTraitId::ReturnTypeImplTrait(func, idx) => { + let infer = self.db.infer(func.into()); + let filler = &mut Filler { db: self.db, subst, skip_params: 0 }; + filler.try_fold_ty(infer.type_of_rpit[idx].clone(), outer_binder) + } + crate::ImplTraitId::AsyncBlockTypeImplTrait(_, _) => { + not_supported!("async block impl trait"); + } + } + } + _ => ty.try_super_fold_with(self.as_dyn(), outer_binder), + } + } + + fn try_fold_free_placeholder_ty( + &mut self, + idx: chalk_ir::PlaceholderIndex, + _outer_binder: DebruijnIndex, + ) -> std::result::Result { + let x = from_placeholder_idx(self.db, idx); + Ok(self + .subst + .as_slice(Interner) + .get((u32::from(x.local_id.into_raw()) as usize) + self.skip_params) + .and_then(|x| x.ty(Interner)) + .ok_or(MirEvalError::TypeError("Generic arg not provided"))? + .clone()) + } + } + let filler = &mut Filler { db: self.db, subst, skip_params: 0 }; + Ok(normalize(self.db, owner, ty.clone().try_fold_with(filler, DebruijnIndex::INNERMOST)?)) + } + + fn heap_allocate(&mut self, s: usize) -> Address { + let pos = self.heap.len(); + self.heap.extend(iter::repeat(0).take(s)); + Address::Heap(pos) + } + + pub fn interpret_mir_with_no_arg(&mut self, body: &MirBody) -> Result> { + self.interpret_mir(&body, vec![].into_iter(), Substitution::empty(Interner)) + } + + fn detect_lang_function(&self, def: FunctionId) -> Option { + lang_attr(self.db.upcast(), def) + } + + fn create_memory_map(&self, bytes: &[u8], ty: &Ty, locals: &Locals<'_>) -> Result { + // FIXME: support indirect references + let mut mm = MemoryMap::default(); + match ty.kind(Interner) { + TyKind::Ref(_, _, t) => { + let size = self.size_of(t, locals)?; + match size { + Some(size) => { + let addr_usize = from_bytes!(usize, bytes); + mm.insert( + addr_usize, + self.read_memory(Address::from_usize(addr_usize), size)?.to_vec(), + ) + } + None => { + let element_size = match t.kind(Interner) { + TyKind::Str => 1, + TyKind::Slice(t) => { + self.size_of_sized(t, locals, "slice inner type")? + } + _ => return Ok(mm), // FIXME: support other kind of unsized types + }; + let (addr, meta) = bytes.split_at(bytes.len() / 2); + let size = element_size * from_bytes!(usize, meta); + let addr = Address::from_bytes(addr)?; + mm.insert(addr.to_usize(), self.read_memory(addr, size)?.to_vec()); + } + } + } + _ => (), + } + Ok(mm) + } + + fn patch_addresses( + &mut self, + patch_map: &HashMap, + addr: Address, + ty: &Ty, + locals: &Locals<'_>, + ) -> Result<()> { + // FIXME: support indirect references + let my_size = self.size_of_sized(ty, locals, "value to patch address")?; + match ty.kind(Interner) { + TyKind::Ref(_, _, t) => { + let size = self.size_of(t, locals)?; + match size { + Some(_) => { + let current = from_bytes!(usize, self.read_memory(addr, my_size)?); + if let Some(x) = patch_map.get(¤t) { + self.write_memory(addr, &x.to_le_bytes())?; + } + } + None => { + let current = from_bytes!(usize, self.read_memory(addr, my_size / 2)?); + if let Some(x) = patch_map.get(¤t) { + self.write_memory(addr, &x.to_le_bytes())?; + } + } + } + } + _ => (), + } + Ok(()) + } + + fn exec_intrinsic( + &self, + as_str: &str, + _arg_bytes: impl Iterator>, + generic_args: Substitution, + locals: &Locals<'_>, + ) -> Result> { + match as_str { + "size_of" => { + let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|x| x.ty(Interner)) else { + return Err(MirEvalError::TypeError("size_of generic arg is not provided")); + }; + let size = self.size_of(ty, locals)?; + match size { + Some(x) => Ok(x.to_le_bytes().to_vec()), + None => return Err(MirEvalError::TypeError("size_of arg is unsized")), + } + } + _ => not_supported!("unknown intrinsic {as_str}"), + } + } + + pub(crate) fn exec_lang_item( + &self, + x: LangItem, + mut args: std::vec::IntoIter>, + ) -> Result> { + use LangItem::*; + match x { + PanicFmt | BeginPanic => Err(MirEvalError::Panic), + SliceLen => { + let arg = args + .next() + .ok_or(MirEvalError::TypeError("argument of <[T]>::len() is not provided"))?; + let ptr_size = arg.len() / 2; + Ok(arg[ptr_size..].into()) + } + x => not_supported!("Executing lang item {x:?}"), + } + } +} + +pub fn pad16(x: &[u8], is_signed: bool) -> [u8; 16] { + let is_negative = is_signed && x.last().unwrap_or(&0) > &128; + let fill_with = if is_negative { 255 } else { 0 }; + x.iter() + .copied() + .chain(iter::repeat(fill_with)) + .take(16) + .collect::>() + .try_into() + .expect("iterator take is not working") +} diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs new file mode 100644 index 000000000000..1fa21e230c4c --- /dev/null +++ b/crates/hir-ty/src/mir/lower.rs @@ -0,0 +1,1209 @@ +//! This module generates a polymorphic MIR from a hir body + +use std::{iter, mem, sync::Arc}; + +use chalk_ir::{BoundVar, ConstData, DebruijnIndex, TyKind}; +use hir_def::{ + body::Body, + expr::{ + Array, BindingAnnotation, ExprId, LabelId, Literal, MatchArm, Pat, PatId, RecordLitField, + }, + layout::LayoutError, + resolver::{resolver_for_expr, ResolveValueResult, ValueNs}, + DefWithBodyId, EnumVariantId, HasModule, +}; +use la_arena::ArenaMap; + +use crate::{ + consteval::ConstEvalError, db::HirDatabase, layout::layout_of_ty, mapping::ToChalk, + utils::generics, Adjust, AutoBorrow, CallableDefId, TyBuilder, TyExt, +}; + +use super::*; + +#[derive(Debug, Clone, Copy)] +struct LoopBlocks { + begin: BasicBlockId, + end: BasicBlockId, +} + +struct MirLowerCtx<'a> { + result: MirBody, + owner: DefWithBodyId, + binding_locals: ArenaMap, + current_loop_blocks: Option, + discr_temp: Option, + db: &'a dyn HirDatabase, + body: &'a Body, + infer: &'a InferenceResult, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum MirLowerError { + ConstEvalError(Box), + LayoutError(LayoutError), + IncompleteExpr, + UnresolvedName, + MissingFunctionDefinition, + TypeError(&'static str), + NotSupported(String), + ContinueWithoutLoop, + BreakWithoutLoop, + Loop, +} + +macro_rules! not_supported { + ($x: expr) => { + return Err(MirLowerError::NotSupported(format!($x))) + }; +} + +impl From for MirLowerError { + fn from(value: ConstEvalError) -> Self { + match value { + ConstEvalError::MirLowerError(e) => e, + _ => MirLowerError::ConstEvalError(Box::new(value)), + } + } +} + +impl From for MirLowerError { + fn from(value: LayoutError) -> Self { + MirLowerError::LayoutError(value) + } +} + +type Result = std::result::Result; + +impl MirLowerCtx<'_> { + fn temp(&mut self, ty: Ty) -> Result { + if matches!(ty.kind(Interner), TyKind::Slice(_) | TyKind::Dyn(_)) { + not_supported!("unsized temporaries"); + } + Ok(self.result.locals.alloc(Local { mutability: Mutability::Not, ty })) + } + + fn lower_expr_as_place(&self, expr_id: ExprId) -> Option { + let adjustments = self.infer.expr_adjustments.get(&expr_id); + let mut r = self.lower_expr_as_place_without_adjust(expr_id)?; + for adjustment in adjustments.iter().flat_map(|x| x.iter()) { + match adjustment.kind { + Adjust::NeverToAny => return Some(r), + Adjust::Deref(None) => { + r.projection.push(ProjectionElem::Deref); + } + Adjust::Deref(Some(_)) => return None, + Adjust::Borrow(_) => return None, + Adjust::Pointer(_) => return None, + } + } + Some(r) + } + + fn lower_expr_as_place_without_adjust(&self, expr_id: ExprId) -> Option { + match &self.body.exprs[expr_id] { + Expr::Path(p) => { + let resolver = resolver_for_expr(self.db.upcast(), self.owner, expr_id); + let pr = resolver.resolve_path_in_value_ns(self.db.upcast(), p.mod_path())?; + let pr = match pr { + ResolveValueResult::ValueNs(v) => v, + ResolveValueResult::Partial(..) => return None, + }; + match pr { + ValueNs::LocalBinding(pat_id) => Some(self.binding_locals[pat_id].into()), + _ => None, + } + } + Expr::UnaryOp { expr, op } => match op { + hir_def::expr::UnaryOp::Deref => { + let mut r = self.lower_expr_as_place(*expr)?; + r.projection.push(ProjectionElem::Deref); + Some(r) + } + _ => None, + }, + _ => None, + } + } + + fn lower_expr_to_some_operand( + &mut self, + expr_id: ExprId, + current: BasicBlockId, + ) -> Result<(Operand, BasicBlockId)> { + if !self.has_adjustments(expr_id) { + match &self.body.exprs[expr_id] { + Expr::Literal(l) => { + let ty = self.expr_ty(expr_id); + return Ok((self.lower_literal_to_operand(ty, l)?, current)); + } + _ => (), + } + } + let (p, current) = self.lower_expr_to_some_place(expr_id, current)?; + Ok((Operand::Copy(p), current)) + } + + fn lower_expr_to_some_place( + &mut self, + expr_id: ExprId, + prev_block: BasicBlockId, + ) -> Result<(Place, BasicBlockId)> { + if let Some(p) = self.lower_expr_as_place(expr_id) { + return Ok((p, prev_block)); + } + let mut ty = self.expr_ty(expr_id); + if let Some(x) = self.infer.expr_adjustments.get(&expr_id) { + if let Some(x) = x.last() { + ty = x.target.clone(); + } + } + let place = self.temp(ty)?; + Ok((place.into(), self.lower_expr_to_place(expr_id, place.into(), prev_block)?)) + } + + fn lower_expr_to_place( + &mut self, + expr_id: ExprId, + place: Place, + prev_block: BasicBlockId, + ) -> Result { + if let Some(x) = self.infer.expr_adjustments.get(&expr_id) { + if x.len() > 0 { + let tmp = self.temp(self.expr_ty(expr_id))?; + let current = + self.lower_expr_to_place_without_adjust(expr_id, tmp.into(), prev_block)?; + let mut r = Place::from(tmp); + for adjustment in x { + match &adjustment.kind { + Adjust::NeverToAny => (), + Adjust::Deref(None) => { + r.projection.push(ProjectionElem::Deref); + } + Adjust::Deref(Some(_)) => not_supported!("overloaded dereference"), + Adjust::Borrow(AutoBorrow::Ref(m) | AutoBorrow::RawPtr(m)) => { + let tmp = self.temp(adjustment.target.clone())?; + self.push_assignment( + current, + tmp.into(), + Rvalue::Ref(BorrowKind::from_chalk(*m), r), + ); + r = tmp.into(); + } + Adjust::Pointer(cast) => { + let target = &adjustment.target; + let tmp = self.temp(target.clone())?; + self.push_assignment( + current, + tmp.into(), + Rvalue::Cast( + CastKind::Pointer(cast.clone()), + Operand::Copy(r).into(), + target.clone(), + ), + ); + r = tmp.into(); + } + } + } + self.push_assignment(current, place, Operand::Copy(r).into()); + return Ok(current); + } + } + self.lower_expr_to_place_without_adjust(expr_id, place, prev_block) + } + + fn lower_expr_to_place_without_adjust( + &mut self, + expr_id: ExprId, + place: Place, + mut current: BasicBlockId, + ) -> Result { + match &self.body.exprs[expr_id] { + Expr::Missing => Err(MirLowerError::IncompleteExpr), + Expr::Path(p) => { + let resolver = resolver_for_expr(self.db.upcast(), self.owner, expr_id); + let pr = resolver + .resolve_path_in_value_ns(self.db.upcast(), p.mod_path()) + .ok_or(MirLowerError::UnresolvedName)?; + let pr = match pr { + ResolveValueResult::ValueNs(v) => v, + ResolveValueResult::Partial(..) => { + return match self + .infer + .assoc_resolutions_for_expr(expr_id) + .ok_or(MirLowerError::UnresolvedName)? + .0 + //.ok_or(ConstEvalError::SemanticError("unresolved assoc item"))? + { + hir_def::AssocItemId::ConstId(c) => self.lower_const(c, current, place), + _ => return Err(MirLowerError::UnresolvedName), + }; + } + }; + match pr { + ValueNs::LocalBinding(pat_id) => { + self.push_assignment( + current, + place, + Operand::Copy(self.binding_locals[pat_id].into()).into(), + ); + Ok(current) + } + ValueNs::ConstId(const_id) => self.lower_const(const_id, current, place), + ValueNs::EnumVariantId(variant_id) => { + let ty = self.infer.type_of_expr[expr_id].clone(); + self.lower_enum_variant(variant_id, current, place, ty, vec![]) + } + ValueNs::GenericParam(p) => { + let Some(def) = self.owner.as_generic_def_id() else { + not_supported!("owner without generic def id"); + }; + let gen = generics(self.db.upcast(), def); + let ty = self.expr_ty(expr_id); + self.push_assignment( + current, + place, + Operand::Constant( + ConstData { + ty, + value: chalk_ir::ConstValue::BoundVar(BoundVar::new( + DebruijnIndex::INNERMOST, + gen.param_idx(p.into()).ok_or(MirLowerError::TypeError( + "fail to lower const generic param", + ))?, + )), + } + .intern(Interner), + ) + .into(), + ); + Ok(current) + } + ValueNs::StructId(_) => { + // It's probably a unit struct or a zero sized function, so no action is needed. + Ok(current) + } + x => { + not_supported!("unknown name {x:?} in value name space"); + } + } + } + Expr::If { condition, then_branch, else_branch } => { + let (discr, current) = self.lower_expr_to_some_operand(*condition, current)?; + let start_of_then = self.new_basic_block(); + let end = self.new_basic_block(); + let end_of_then = + self.lower_expr_to_place(*then_branch, place.clone(), start_of_then)?; + self.set_goto(end_of_then, end); + let mut start_of_else = end; + if let Some(else_branch) = else_branch { + start_of_else = self.new_basic_block(); + let end_of_else = + self.lower_expr_to_place(*else_branch, place, start_of_else)?; + self.set_goto(end_of_else, end); + } + self.set_terminator( + current, + Terminator::SwitchInt { + discr, + targets: SwitchTargets::static_if(1, start_of_then, start_of_else), + }, + ); + Ok(end) + } + Expr::Let { pat, expr } => { + let (cond_place, current) = self.lower_expr_to_some_place(*expr, current)?; + let result = self.new_basic_block(); + let (then_target, else_target) = self.pattern_match( + current, + None, + cond_place, + self.expr_ty(*expr), + *pat, + BindingAnnotation::Unannotated, + )?; + self.write_bytes_to_place(then_target, place.clone(), vec![1], TyBuilder::bool())?; + self.set_goto(then_target, result); + if let Some(else_target) = else_target { + self.write_bytes_to_place(else_target, place, vec![0], TyBuilder::bool())?; + self.set_goto(else_target, result); + } + Ok(result) + } + Expr::Block { id: _, statements, tail, label } => { + if label.is_some() { + not_supported!("block with label"); + } + for statement in statements.iter() { + match statement { + hir_def::expr::Statement::Let { + pat, + initializer, + else_branch, + type_ref: _, + } => match initializer { + Some(expr_id) => { + let else_block; + let init_place; + (init_place, current) = + self.lower_expr_to_some_place(*expr_id, current)?; + (current, else_block) = self.pattern_match( + current, + None, + init_place, + self.expr_ty(*expr_id), + *pat, + BindingAnnotation::Unannotated, + )?; + match (else_block, else_branch) { + (None, _) => (), + (Some(else_block), None) => { + self.set_terminator(else_block, Terminator::Unreachable); + } + (Some(else_block), Some(else_branch)) => { + let (_, b) = self + .lower_expr_to_some_place(*else_branch, else_block)?; + self.set_terminator(b, Terminator::Unreachable); + } + } + } + None => continue, + }, + hir_def::expr::Statement::Expr { expr, has_semi: _ } => { + let ty = self.expr_ty(*expr); + let temp = self.temp(ty)?; + current = self.lower_expr_to_place(*expr, temp.into(), current)?; + } + } + } + match tail { + Some(tail) => self.lower_expr_to_place(*tail, place, current), + None => Ok(current), + } + } + Expr::Loop { body, label } => self.lower_loop(current, *label, |this, begin, _| { + let (_, block) = this.lower_expr_to_some_place(*body, begin)?; + this.set_goto(block, begin); + Ok(()) + }), + Expr::While { condition, body, label } => { + self.lower_loop(current, *label, |this, begin, end| { + let (discr, to_switch) = this.lower_expr_to_some_operand(*condition, begin)?; + let after_cond = this.new_basic_block(); + this.set_terminator( + to_switch, + Terminator::SwitchInt { + discr, + targets: SwitchTargets::static_if(1, after_cond, end), + }, + ); + let (_, block) = this.lower_expr_to_some_place(*body, after_cond)?; + this.set_goto(block, begin); + Ok(()) + }) + } + Expr::For { .. } => not_supported!("for loop"), + Expr::Call { callee, args, .. } => { + let callee_ty = self.expr_ty(*callee); + match &callee_ty.data(Interner).kind { + chalk_ir::TyKind::FnDef(..) => { + let func = Operand::from_bytes(vec![], callee_ty.clone()); + self.lower_call(func, args.iter().copied(), place, current) + } + TyKind::Scalar(_) + | TyKind::Tuple(_, _) + | TyKind::Array(_, _) + | TyKind::Adt(_, _) + | TyKind::Str + | TyKind::Foreign(_) + | TyKind::Slice(_) => { + return Err(MirLowerError::TypeError("function call on data type")) + } + TyKind::Error => return Err(MirLowerError::MissingFunctionDefinition), + TyKind::AssociatedType(_, _) + | TyKind::Raw(_, _) + | TyKind::Ref(_, _, _) + | TyKind::OpaqueType(_, _) + | TyKind::Never + | TyKind::Closure(_, _) + | TyKind::Generator(_, _) + | TyKind::GeneratorWitness(_, _) + | TyKind::Placeholder(_) + | TyKind::Dyn(_) + | TyKind::Alias(_) + | TyKind::Function(_) + | TyKind::BoundVar(_) + | TyKind::InferenceVar(_, _) => not_supported!("dynamic function call"), + } + } + Expr::MethodCall { receiver, args, .. } => { + let (func_id, generic_args) = + self.infer.method_resolution(expr_id).ok_or(MirLowerError::UnresolvedName)?; + let ty = chalk_ir::TyKind::FnDef( + CallableDefId::FunctionId(func_id).to_chalk(self.db), + generic_args, + ) + .intern(Interner); + let func = Operand::from_bytes(vec![], ty); + self.lower_call( + func, + iter::once(*receiver).chain(args.iter().copied()), + place, + current, + ) + } + Expr::Match { expr, arms } => { + let (cond_place, mut current) = self.lower_expr_to_some_place(*expr, current)?; + let cond_ty = self.expr_ty(*expr); + let end = self.new_basic_block(); + for MatchArm { pat, guard, expr } in arms.iter() { + if guard.is_some() { + not_supported!("pattern matching with guard"); + } + let (then, otherwise) = self.pattern_match( + current, + None, + cond_place.clone(), + cond_ty.clone(), + *pat, + BindingAnnotation::Unannotated, + )?; + let block = self.lower_expr_to_place(*expr, place.clone(), then)?; + self.set_goto(block, end); + match otherwise { + Some(o) => current = o, + None => { + // The current pattern was irrefutable, so there is no need to generate code + // for the rest of patterns + break; + } + } + } + if self.is_unterminated(current) { + self.set_terminator(current, Terminator::Unreachable); + } + Ok(end) + } + Expr::Continue { label } => match label { + Some(_) => not_supported!("continue with label"), + None => { + let loop_data = + self.current_loop_blocks.ok_or(MirLowerError::ContinueWithoutLoop)?; + self.set_goto(current, loop_data.begin); + let otherwise = self.new_basic_block(); + Ok(otherwise) + } + }, + Expr::Break { expr, label } => { + if expr.is_some() { + not_supported!("break with value"); + } + match label { + Some(_) => not_supported!("break with label"), + None => { + let loop_data = + self.current_loop_blocks.ok_or(MirLowerError::BreakWithoutLoop)?; + self.set_goto(current, loop_data.end); + Ok(self.new_basic_block()) + } + } + } + Expr::Return { expr } => { + if let Some(expr) = expr { + current = self.lower_expr_to_place(*expr, return_slot().into(), current)?; + } + self.set_terminator(current, Terminator::Return); + Ok(self.new_basic_block()) + } + Expr::Yield { .. } => not_supported!("yield"), + Expr::RecordLit { fields, .. } => { + let variant_id = self + .infer + .variant_resolution_for_expr(expr_id) + .ok_or(MirLowerError::UnresolvedName)?; + let subst = match self.expr_ty(expr_id).kind(Interner) { + TyKind::Adt(_, s) => s.clone(), + _ => not_supported!("Non ADT record literal"), + }; + let variant_data = variant_id.variant_data(self.db.upcast()); + match variant_id { + VariantId::EnumVariantId(_) | VariantId::StructId(_) => { + let mut operands = vec![None; variant_data.fields().len()]; + for RecordLitField { name, expr } in fields.iter() { + let field_id = + variant_data.field(name).ok_or(MirLowerError::UnresolvedName)?; + let op; + (op, current) = self.lower_expr_to_some_operand(*expr, current)?; + operands[u32::from(field_id.into_raw()) as usize] = Some(op); + } + self.push_assignment( + current, + place, + Rvalue::Aggregate( + AggregateKind::Adt(variant_id, subst), + operands.into_iter().map(|x| x).collect::>().ok_or( + MirLowerError::TypeError("missing field in record literal"), + )?, + ), + ); + Ok(current) + } + VariantId::UnionId(union_id) => { + let [RecordLitField { name, expr }] = fields.as_ref() else { + not_supported!("Union record literal with more than one field"); + }; + let local_id = + variant_data.field(name).ok_or(MirLowerError::UnresolvedName)?; + let mut place = place; + place + .projection + .push(PlaceElem::Field(FieldId { parent: union_id.into(), local_id })); + self.lower_expr_to_place(*expr, place, current) + } + } + } + Expr::Field { expr, name } => { + let (mut current_place, current) = self.lower_expr_to_some_place(*expr, current)?; + if let TyKind::Tuple(..) = self.expr_ty(*expr).kind(Interner) { + let index = name + .as_tuple_index() + .ok_or(MirLowerError::TypeError("named field on tuple"))?; + current_place.projection.push(ProjectionElem::TupleField(index)) + } else { + let field = self + .infer + .field_resolution(expr_id) + .ok_or(MirLowerError::UnresolvedName)?; + current_place.projection.push(ProjectionElem::Field(field)); + } + self.push_assignment(current, place, Operand::Copy(current_place).into()); + Ok(current) + } + Expr::Await { .. } => not_supported!("await"), + Expr::Try { .. } => not_supported!("? operator"), + Expr::Yeet { .. } => not_supported!("yeet"), + Expr::TryBlock { .. } => not_supported!("try block"), + Expr::Async { .. } => not_supported!("async block"), + Expr::Const { .. } => not_supported!("anonymous const block"), + Expr::Cast { expr, type_ref: _ } => { + let (x, current) = self.lower_expr_to_some_operand(*expr, current)?; + let source_ty = self.infer[*expr].clone(); + let target_ty = self.infer[expr_id].clone(); + self.push_assignment( + current, + place, + Rvalue::Cast(cast_kind(&source_ty, &target_ty)?, x, target_ty), + ); + Ok(current) + } + Expr::Ref { expr, rawness: _, mutability } => { + let p; + (p, current) = self.lower_expr_to_some_place(*expr, current)?; + let bk = BorrowKind::from_hir(*mutability); + self.push_assignment(current, place, Rvalue::Ref(bk, p)); + Ok(current) + } + Expr::Box { .. } => not_supported!("box expression"), + Expr::UnaryOp { expr, op } => match op { + hir_def::expr::UnaryOp::Deref => { + let (mut tmp, current) = self.lower_expr_to_some_place(*expr, current)?; + tmp.projection.push(ProjectionElem::Deref); + self.push_assignment(current, place, Operand::Copy(tmp).into()); + Ok(current) + } + hir_def::expr::UnaryOp::Not => { + let (op, current) = self.lower_expr_to_some_operand(*expr, current)?; + self.push_assignment(current, place, Rvalue::UnaryOp(UnOp::Not, op)); + Ok(current) + } + hir_def::expr::UnaryOp::Neg => { + let (op, current) = self.lower_expr_to_some_operand(*expr, current)?; + self.push_assignment(current, place, Rvalue::UnaryOp(UnOp::Neg, op)); + Ok(current) + } + }, + Expr::BinaryOp { lhs, rhs, op } => { + let op = op.ok_or(MirLowerError::IncompleteExpr)?; + if let hir_def::expr::BinaryOp::Assignment { op } = op { + if op.is_some() { + not_supported!("assignment with arith op (like +=)"); + } + let Some(lhs_place) = self.lower_expr_as_place(*lhs) else { + not_supported!("assignment to complex place"); + }; + let rhs_op; + (rhs_op, current) = self.lower_expr_to_some_operand(*rhs, current)?; + self.push_assignment(current, lhs_place, rhs_op.into()); + return Ok(current); + } + let lhs_op; + (lhs_op, current) = self.lower_expr_to_some_operand(*lhs, current)?; + let rhs_op; + (rhs_op, current) = self.lower_expr_to_some_operand(*rhs, current)?; + self.push_assignment( + current, + place, + Rvalue::CheckedBinaryOp( + match op { + hir_def::expr::BinaryOp::LogicOp(op) => match op { + hir_def::expr::LogicOp::And => BinOp::BitAnd, // FIXME: make these short circuit + hir_def::expr::LogicOp::Or => BinOp::BitOr, + }, + hir_def::expr::BinaryOp::ArithOp(op) => BinOp::from(op), + hir_def::expr::BinaryOp::CmpOp(op) => BinOp::from(op), + hir_def::expr::BinaryOp::Assignment { .. } => unreachable!(), // handled above + }, + lhs_op, + rhs_op, + ), + ); + Ok(current) + } + Expr::Range { .. } => not_supported!("range"), + Expr::Index { base, index } => { + let mut p_base; + (p_base, current) = self.lower_expr_to_some_place(*base, current)?; + let l_index = self.temp(self.expr_ty(*index))?; + current = self.lower_expr_to_place(*index, l_index.into(), current)?; + p_base.projection.push(ProjectionElem::Index(l_index)); + self.push_assignment(current, place, Operand::Copy(p_base).into()); + Ok(current) + } + Expr::Closure { .. } => not_supported!("closure"), + Expr::Tuple { exprs, is_assignee_expr: _ } => { + let r = Rvalue::Aggregate( + AggregateKind::Tuple(self.expr_ty(expr_id)), + exprs + .iter() + .map(|x| { + let o; + (o, current) = self.lower_expr_to_some_operand(*x, current)?; + Ok(o) + }) + .collect::>()?, + ); + self.push_assignment(current, place, r); + Ok(current) + } + Expr::Unsafe { body } => self.lower_expr_to_place(*body, place, current), + Expr::Array(l) => match l { + Array::ElementList { elements, .. } => { + let elem_ty = match &self.expr_ty(expr_id).data(Interner).kind { + TyKind::Array(ty, _) => ty.clone(), + _ => { + return Err(MirLowerError::TypeError( + "Array expression with non array type", + )) + } + }; + let r = Rvalue::Aggregate( + AggregateKind::Array(elem_ty), + elements + .iter() + .map(|x| { + let o; + (o, current) = self.lower_expr_to_some_operand(*x, current)?; + Ok(o) + }) + .collect::>()?, + ); + self.push_assignment(current, place, r); + Ok(current) + } + Array::Repeat { .. } => not_supported!("array repeat"), + }, + Expr::Literal(l) => { + let ty = self.expr_ty(expr_id); + let op = self.lower_literal_to_operand(ty, l)?; + self.push_assignment(current, place, op.into()); + Ok(current) + } + Expr::Underscore => not_supported!("underscore"), + } + } + + fn lower_literal_to_operand(&mut self, ty: Ty, l: &Literal) -> Result { + let size = layout_of_ty(self.db, &ty, self.owner.module(self.db.upcast()).krate())? + .size + .bytes_usize(); + let bytes = match l { + hir_def::expr::Literal::String(b) => { + let b = b.as_bytes(); + let mut data = vec![]; + data.extend(0usize.to_le_bytes()); + data.extend(b.len().to_le_bytes()); + let mut mm = MemoryMap::default(); + mm.insert(0, b.to_vec()); + return Ok(Operand::from_concrete_const(data, mm, ty)); + } + hir_def::expr::Literal::ByteString(b) => { + let mut data = vec![]; + data.extend(0usize.to_le_bytes()); + data.extend(b.len().to_le_bytes()); + let mut mm = MemoryMap::default(); + mm.insert(0, b.to_vec()); + return Ok(Operand::from_concrete_const(data, mm, ty)); + } + hir_def::expr::Literal::Char(c) => u32::from(*c).to_le_bytes().into(), + hir_def::expr::Literal::Bool(b) => vec![*b as u8], + hir_def::expr::Literal::Int(x, _) => x.to_le_bytes()[0..size].into(), + hir_def::expr::Literal::Uint(x, _) => x.to_le_bytes()[0..size].into(), + hir_def::expr::Literal::Float(f, _) => match size { + 8 => f.into_f64().to_le_bytes().into(), + 4 => f.into_f32().to_le_bytes().into(), + _ => { + return Err(MirLowerError::TypeError("float with size other than 4 or 8 bytes")) + } + }, + }; + Ok(Operand::from_concrete_const(bytes, MemoryMap::default(), ty)) + } + + fn new_basic_block(&mut self) -> BasicBlockId { + self.result.basic_blocks.alloc(BasicBlock::default()) + } + + fn lower_const( + &mut self, + const_id: hir_def::ConstId, + prev_block: BasicBlockId, + place: Place, + ) -> Result { + let c = self.db.const_eval(const_id)?; + self.write_const_to_place(c, prev_block, place) + } + + fn write_const_to_place( + &mut self, + c: Const, + prev_block: BasicBlockId, + place: Place, + ) -> Result { + self.push_assignment(prev_block, place, Operand::Constant(c).into()); + Ok(prev_block) + } + + fn write_bytes_to_place( + &mut self, + prev_block: BasicBlockId, + place: Place, + cv: Vec, + ty: Ty, + ) -> Result { + self.push_assignment(prev_block, place, Operand::from_bytes(cv, ty).into()); + Ok(prev_block) + } + + fn lower_enum_variant( + &mut self, + variant_id: EnumVariantId, + prev_block: BasicBlockId, + place: Place, + ty: Ty, + fields: Vec, + ) -> Result { + let subst = match ty.kind(Interner) { + TyKind::Adt(_, subst) => subst.clone(), + _ => not_supported!("Non ADT enum"), + }; + self.push_assignment( + prev_block, + place, + Rvalue::Aggregate(AggregateKind::Adt(variant_id.into(), subst), fields), + ); + Ok(prev_block) + } + + fn lower_call( + &mut self, + func: Operand, + args: impl Iterator, + place: Place, + mut current: BasicBlockId, + ) -> Result { + let args = args + .map(|arg| { + let temp; + (temp, current) = self.lower_expr_to_some_operand(arg, current)?; + Ok(temp) + }) + .collect::>>()?; + let b = self.result.basic_blocks.alloc(BasicBlock { + statements: vec![], + terminator: None, + is_cleanup: false, + }); + self.set_terminator( + current, + Terminator::Call { + func, + args, + destination: place, + target: Some(b), + cleanup: None, + from_hir_call: true, + }, + ); + Ok(b) + } + + fn is_unterminated(&mut self, source: BasicBlockId) -> bool { + self.result.basic_blocks[source].terminator.is_none() + } + + fn set_terminator(&mut self, source: BasicBlockId, terminator: Terminator) { + self.result.basic_blocks[source].terminator = Some(terminator); + } + + fn set_goto(&mut self, source: BasicBlockId, target: BasicBlockId) { + self.set_terminator(source, Terminator::Goto { target }); + } + + fn expr_ty(&self, e: ExprId) -> Ty { + self.infer[e].clone() + } + + fn push_assignment(&mut self, block: BasicBlockId, place: Place, rvalue: Rvalue) { + self.result.basic_blocks[block].statements.push(Statement::Assign(place, rvalue)); + } + + /// It gets a `current` unterminated block, appends some statements and possibly a terminator to it to check if + /// the pattern matches and write bindings, and returns two unterminated blocks, one for the matched path (which + /// can be the `current` block) and one for the mismatched path. If the input pattern is irrefutable, the + /// mismatched path block is `None`. + /// + /// By default, it will create a new block for mismatched path. If you already have one, you can provide it with + /// `current_else` argument to save an unneccessary jump. If `current_else` isn't `None`, the result mismatched path + /// wouldn't be `None` as well. Note that this function will add jumps to the beginning of the `current_else` block, + /// so it should be an empty block. + fn pattern_match( + &mut self, + mut current: BasicBlockId, + mut current_else: Option, + mut cond_place: Place, + mut cond_ty: Ty, + pattern: PatId, + mut binding_mode: BindingAnnotation, + ) -> Result<(BasicBlockId, Option)> { + Ok(match &self.body.pats[pattern] { + Pat::Missing => return Err(MirLowerError::IncompleteExpr), + Pat::Wild => (current, current_else), + Pat::Tuple { args, ellipsis } => { + pattern_matching_dereference(&mut cond_ty, &mut binding_mode, &mut cond_place); + let subst = match cond_ty.kind(Interner) { + TyKind::Tuple(_, s) => s, + _ => { + return Err(MirLowerError::TypeError( + "non tuple type matched with tuple pattern", + )) + } + }; + self.pattern_match_tuple_like( + current, + current_else, + args.iter().enumerate().map(|(i, x)| { + ( + PlaceElem::TupleField(i), + *x, + subst.at(Interner, i).assert_ty_ref(Interner).clone(), + ) + }), + *ellipsis, + &cond_place, + binding_mode, + )? + } + Pat::Or(_) => not_supported!("or pattern"), + Pat::Record { .. } => not_supported!("record pattern"), + Pat::Range { .. } => not_supported!("range pattern"), + Pat::Slice { .. } => not_supported!("slice pattern"), + Pat::Path(_) => not_supported!("path pattern"), + Pat::Lit(l) => { + let then_target = self.new_basic_block(); + let else_target = current_else.unwrap_or_else(|| self.new_basic_block()); + match &self.body.exprs[*l] { + Expr::Literal(l) => match l { + hir_def::expr::Literal::Int(x, _) => { + self.set_terminator( + current, + Terminator::SwitchInt { + discr: Operand::Copy(cond_place), + targets: SwitchTargets::static_if( + *x as u128, + then_target, + else_target, + ), + }, + ); + } + hir_def::expr::Literal::Uint(x, _) => { + self.set_terminator( + current, + Terminator::SwitchInt { + discr: Operand::Copy(cond_place), + targets: SwitchTargets::static_if(*x, then_target, else_target), + }, + ); + } + _ => not_supported!("non int path literal"), + }, + _ => not_supported!("expression path literal"), + } + (then_target, Some(else_target)) + } + Pat::Bind { mode, name: _, subpat } => { + let target_place = self.binding_locals[pattern]; + if let Some(subpat) = subpat { + (current, current_else) = self.pattern_match( + current, + current_else, + cond_place.clone(), + cond_ty, + *subpat, + binding_mode, + )? + } + if matches!(mode, BindingAnnotation::Ref | BindingAnnotation::RefMut) { + binding_mode = *mode; + } + self.push_assignment( + current, + target_place.into(), + match binding_mode { + BindingAnnotation::Unannotated | BindingAnnotation::Mutable => { + Operand::Copy(cond_place).into() + } + BindingAnnotation::Ref => Rvalue::Ref(BorrowKind::Shared, cond_place), + BindingAnnotation::RefMut => Rvalue::Ref( + BorrowKind::Mut { allow_two_phase_borrow: false }, + cond_place, + ), + }, + ); + (current, current_else) + } + Pat::TupleStruct { path: _, args, ellipsis } => { + let Some(variant) = self.infer.variant_resolution_for_pat(pattern) else { + not_supported!("unresolved variant"); + }; + pattern_matching_dereference(&mut cond_ty, &mut binding_mode, &mut cond_place); + let subst = match cond_ty.kind(Interner) { + TyKind::Adt(_, s) => s, + _ => { + return Err(MirLowerError::TypeError( + "non adt type matched with tuple struct", + )) + } + }; + let fields_type = self.db.field_types(variant); + match variant { + VariantId::EnumVariantId(v) => { + let e = self.db.const_eval_discriminant(v)? as u128; + let next = self.new_basic_block(); + let tmp = self.discr_temp_place(); + self.push_assignment( + current, + tmp.clone(), + Rvalue::Discriminant(cond_place.clone()), + ); + let else_target = current_else.unwrap_or_else(|| self.new_basic_block()); + self.set_terminator( + current, + Terminator::SwitchInt { + discr: Operand::Copy(tmp), + targets: SwitchTargets::static_if(e, next, else_target), + }, + ); + let enum_data = self.db.enum_data(v.parent); + let fields = + enum_data.variants[v.local_id].variant_data.fields().iter().map( + |(x, _)| { + ( + PlaceElem::Field(FieldId { parent: v.into(), local_id: x }), + fields_type[x].clone().substitute(Interner, subst), + ) + }, + ); + self.pattern_match_tuple_like( + next, + Some(else_target), + args.iter().zip(fields).map(|(x, y)| (y.0, *x, y.1)), + *ellipsis, + &cond_place, + binding_mode, + )? + } + VariantId::StructId(s) => { + let struct_data = self.db.struct_data(s); + let fields = struct_data.variant_data.fields().iter().map(|(x, _)| { + ( + PlaceElem::Field(FieldId { parent: s.into(), local_id: x }), + fields_type[x].clone().substitute(Interner, subst), + ) + }); + self.pattern_match_tuple_like( + current, + current_else, + args.iter().zip(fields).map(|(x, y)| (y.0, *x, y.1)), + *ellipsis, + &cond_place, + binding_mode, + )? + } + VariantId::UnionId(_) => { + return Err(MirLowerError::TypeError("pattern matching on union")) + } + } + } + Pat::Ref { .. } => not_supported!("& pattern"), + Pat::Box { .. } => not_supported!("box pattern"), + Pat::ConstBlock(_) => not_supported!("const block pattern"), + }) + } + + fn pattern_match_tuple_like( + &mut self, + mut current: BasicBlockId, + mut current_else: Option, + args: impl Iterator, + ellipsis: Option, + cond_place: &Place, + binding_mode: BindingAnnotation, + ) -> Result<(BasicBlockId, Option)> { + if ellipsis.is_some() { + not_supported!("tuple like pattern with ellipsis"); + } + for (proj, arg, ty) in args { + let mut cond_place = cond_place.clone(); + cond_place.projection.push(proj); + (current, current_else) = + self.pattern_match(current, current_else, cond_place, ty, arg, binding_mode)?; + } + Ok((current, current_else)) + } + + fn discr_temp_place(&mut self) -> Place { + match &self.discr_temp { + Some(x) => x.clone(), + None => { + let tmp: Place = + self.temp(TyBuilder::discr_ty()).expect("discr_ty is never unsized").into(); + self.discr_temp = Some(tmp.clone()); + tmp + } + } + } + + fn lower_loop( + &mut self, + prev_block: BasicBlockId, + label: Option, + f: impl FnOnce(&mut MirLowerCtx<'_>, BasicBlockId, BasicBlockId) -> Result<()>, + ) -> Result { + if label.is_some() { + not_supported!("loop with label"); + } + let begin = self.new_basic_block(); + let end = self.new_basic_block(); + let prev = mem::replace(&mut self.current_loop_blocks, Some(LoopBlocks { begin, end })); + self.set_goto(prev_block, begin); + f(self, begin, end)?; + self.current_loop_blocks = prev; + Ok(end) + } + + fn has_adjustments(&self, expr_id: ExprId) -> bool { + !self.infer.expr_adjustments.get(&expr_id).map(|x| x.is_empty()).unwrap_or(true) + } +} + +fn pattern_matching_dereference( + cond_ty: &mut Ty, + binding_mode: &mut BindingAnnotation, + cond_place: &mut Place, +) { + while let Some((ty, _, mu)) = cond_ty.as_reference() { + if mu == Mutability::Mut && *binding_mode != BindingAnnotation::Ref { + *binding_mode = BindingAnnotation::RefMut; + } else { + *binding_mode = BindingAnnotation::Ref; + } + *cond_ty = ty.clone(); + cond_place.projection.push(ProjectionElem::Deref); + } +} + +fn cast_kind(source_ty: &Ty, target_ty: &Ty) -> Result { + Ok(match (source_ty.kind(Interner), target_ty.kind(Interner)) { + (TyKind::Scalar(s), TyKind::Scalar(t)) => match (s, t) { + (chalk_ir::Scalar::Float(_), chalk_ir::Scalar::Float(_)) => CastKind::FloatToFloat, + (chalk_ir::Scalar::Float(_), _) => CastKind::FloatToInt, + (_, chalk_ir::Scalar::Float(_)) => CastKind::IntToFloat, + (_, _) => CastKind::IntToInt, + }, + // Enum to int casts + (TyKind::Scalar(_), TyKind::Adt(..)) | (TyKind::Adt(..), TyKind::Scalar(_)) => { + CastKind::IntToInt + } + (a, b) => not_supported!("Unknown cast between {a:?} and {b:?}"), + }) +} + +pub fn mir_body_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Result> { + let body = db.body(def); + let infer = db.infer(def); + Ok(Arc::new(lower_to_mir(db, def, &body, &infer, body.body_expr)?)) +} + +pub fn mir_body_recover( + _db: &dyn HirDatabase, + _cycle: &[String], + _def: &DefWithBodyId, +) -> Result> { + Err(MirLowerError::Loop) +} + +pub fn lower_to_mir( + db: &dyn HirDatabase, + owner: DefWithBodyId, + body: &Body, + infer: &InferenceResult, + // FIXME: root_expr should always be the body.body_expr, but since `X` in `[(); X]` doesn't have its own specific body yet, we + // need to take this input explicitly. + root_expr: ExprId, +) -> Result { + let mut basic_blocks = Arena::new(); + let start_block = + basic_blocks.alloc(BasicBlock { statements: vec![], terminator: None, is_cleanup: false }); + let mut locals = Arena::new(); + // 0 is return local + locals.alloc(Local { mutability: Mutability::Mut, ty: infer[root_expr].clone() }); + let mut create_local_of_path = |p: PatId| { + // FIXME: mutablity is broken + locals.alloc(Local { mutability: Mutability::Not, ty: infer[p].clone() }) + }; + // 1 to param_len is for params + let mut binding_locals: ArenaMap = + body.params.iter().map(|&x| (x, create_local_of_path(x))).collect(); + // and then rest of bindings + for (pat_id, _) in body.pats.iter() { + if !binding_locals.contains_idx(pat_id) { + binding_locals.insert(pat_id, create_local_of_path(pat_id)); + } + } + let mir = MirBody { basic_blocks, locals, start_block, owner, arg_count: body.params.len() }; + let mut ctx = MirLowerCtx { + result: mir, + db, + infer, + body, + binding_locals, + owner, + current_loop_blocks: None, + discr_temp: None, + }; + let b = ctx.lower_expr_to_place(root_expr, return_slot().into(), start_block)?; + ctx.result.basic_blocks[b].terminator = Some(Terminator::Return); + Ok(ctx.result) +} diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 2cb4ed2c3351..b712ff97aba3 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -50,7 +50,6 @@ use hir_def::{ per_ns::PerNs, resolver::{HasResolver, Resolver}, src::HasSource as _, - type_ref::ConstScalar, AdtId, AssocItemId, AssocItemLoc, AttrDefId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, FunctionId, GenericDefId, HasModule, ImplId, ItemContainerId, LifetimeParamId, LocalEnumVariantId, LocalFieldId, Lookup, MacroExpander, MacroId, ModuleId, StaticId, StructId, @@ -59,16 +58,16 @@ use hir_def::{ use hir_expand::{name::name, MacroCallKind}; use hir_ty::{ all_super_traits, autoderef, - consteval::{unknown_const_as_generic, ComputedExpr, ConstEvalError, ConstExt}, + consteval::{try_const_usize, unknown_const_as_generic, ConstEvalError, ConstExt}, diagnostics::BodyValidationDiagnostic, layout::layout_of_ty, method_resolution::{self, TyFingerprint}, + mir::interpret_mir, primitive::UintTy, traits::FnTrait, AliasTy, CallableDefId, CallableSig, Canonical, CanonicalVarKinds, Cast, ClosureId, - ConcreteConst, ConstValue, GenericArgData, Interner, ParamKind, QuantifiedWhereClause, Scalar, - Substitution, TraitEnvironment, TraitRefExt, Ty, TyBuilder, TyDefId, TyExt, TyKind, - WhereClause, + GenericArgData, Interner, ParamKind, QuantifiedWhereClause, Scalar, Substitution, + TraitEnvironment, TraitRefExt, Ty, TyBuilder, TyDefId, TyExt, TyKind, WhereClause, }; use itertools::Itertools; use nameres::diagnostics::DefDiagnosticKind; @@ -130,6 +129,7 @@ pub use { }, hir_ty::{ display::{HirDisplay, HirDisplayError, HirWrite}, + mir::MirEvalError, PointerCast, Safety, }, }; @@ -1092,8 +1092,8 @@ impl Variant { self.source(db)?.value.expr() } - pub fn eval(self, db: &dyn HirDatabase) -> Result { - db.const_eval_variant(self.into()) + pub fn eval(self, db: &dyn HirDatabase) -> Result { + db.const_eval_discriminant(self.into()) } } @@ -1639,6 +1639,14 @@ impl Function { let def_map = db.crate_def_map(loc.krate(db).into()); def_map.fn_as_proc_macro(self.id).map(|id| Macro { id: id.into() }) } + + pub fn eval(self, db: &dyn HirDatabase) -> Result<(), MirEvalError> { + let body = db + .mir_body(self.id.into()) + .map_err(|e| MirEvalError::MirLowerError(self.id.into(), e))?; + interpret_mir(db, &body, false)?; + Ok(()) + } } // Note: logically, this belongs to `hir_ty`, but we are not using it there yet. @@ -1781,7 +1789,7 @@ impl Const { Type::new_with_resolver_inner(db, &resolver, ty) } - pub fn eval(self, db: &dyn HirDatabase) -> Result { + pub fn eval(self, db: &dyn HirDatabase) -> Result { db.const_eval(self.id) } } @@ -3260,12 +3268,7 @@ impl Type { pub fn as_array(&self, _db: &dyn HirDatabase) -> Option<(Type, usize)> { if let TyKind::Array(ty, len) = &self.ty.kind(Interner) { - match len.data(Interner).value { - ConstValue::Concrete(ConcreteConst { interned: ConstScalar::UInt(len) }) => { - Some((self.derived(ty.clone()), len as usize)) - } - _ => None, - } + try_const_usize(len).map(|x| (self.derived(ty.clone()), x as usize)) } else { None } diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index 3b39e9fa919a..e687da255ed1 100644 --- a/crates/hir/src/source_analyzer.rs +++ b/crates/hir/src/source_analyzer.rs @@ -791,7 +791,7 @@ impl SourceAnalyzer { || Arc::new(hir_ty::TraitEnvironment::empty(krate)), |d| db.trait_environment(d), ); - method_resolution::lookup_impl_method(db, env, func, substs) + method_resolution::lookup_impl_method(db, env, func, substs).0 } fn resolve_impl_const_or_trait_def( @@ -809,7 +809,7 @@ impl SourceAnalyzer { || Arc::new(hir_ty::TraitEnvironment::empty(krate)), |d| db.trait_environment(d), ); - method_resolution::lookup_impl_const(db, env, const_id, subs) + method_resolution::lookup_impl_const(db, env, const_id, subs).0 } fn lang_trait_fn( diff --git a/crates/ide-assists/src/handlers/add_explicit_type.rs b/crates/ide-assists/src/handlers/add_explicit_type.rs index 0057f439f1af..785ae3d09c6e 100644 --- a/crates/ide-assists/src/handlers/add_explicit_type.rs +++ b/crates/ide-assists/src/handlers/add_explicit_type.rs @@ -211,10 +211,8 @@ fn main() { check_assist_not_applicable( add_explicit_type, r#" -//- minicore: option - fn main() { - let $0l = [0.0; Some(2).unwrap()]; + let $0l = [0.0; unresolved_function(5)]; } "#, ); diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 5f2c61f5b5f8..64b2221bdeab 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -30,6 +30,7 @@ pub struct HoverConfig { pub documentation: bool, pub keywords: bool, pub format: HoverDocFormat, + pub interpret_tests: bool, } #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs index 22611cfb892f..749c224c4620 100644 --- a/crates/ide/src/hover/render.rs +++ b/crates/ide/src/hover/render.rs @@ -3,7 +3,8 @@ use std::fmt::Display; use either::Either; use hir::{ - Adt, AsAssocItem, AttributeTemplate, HasAttrs, HasSource, HirDisplay, Semantics, TypeInfo, + db::DefDatabase, Adt, AsAssocItem, AttributeTemplate, HasAttrs, HasSource, HirDisplay, + MirEvalError, Semantics, TypeInfo, }; use ide_db::{ base_db::SourceDatabase, @@ -402,7 +403,20 @@ pub(super) fn definition( )) }), Definition::Module(it) => label_and_docs(db, it), - Definition::Function(it) => label_and_docs(db, it), + Definition::Function(it) => label_and_layout_info_and_docs(db, it, |_| { + if !config.interpret_tests { + return None; + } + match it.eval(db) { + Ok(()) => Some("pass".into()), + Err(MirEvalError::Panic) => Some("fail".into()), + Err(MirEvalError::MirLowerError(f, e)) => { + let name = &db.function_data(f).name; + Some(format!("error: fail to lower {name} due {e:?}")) + } + Err(e) => Some(format!("error: {e:?}")), + } + }), Definition::Adt(it) => label_and_layout_info_and_docs(db, it, |&it| { let layout = it.layout(db).ok()?; Some(format!("size = {}, align = {}", layout.size.bytes(), layout.align.abi.bytes())) @@ -410,7 +424,7 @@ pub(super) fn definition( Definition::Variant(it) => label_value_and_docs(db, it, |&it| { if !it.parent_enum(db).is_data_carrying(db) { match it.eval(db) { - Ok(x) => Some(format!("{x}")), + Ok(x) => Some(if x >= 10 { format!("{x} ({x:#X})") } else { format!("{x}") }), Err(_) => it.value(db).map(|x| format!("{x:?}")), } } else { @@ -420,7 +434,7 @@ pub(super) fn definition( Definition::Const(it) => label_value_and_docs(db, it, |it| { let body = it.eval(db); match body { - Ok(x) => Some(format!("{x}")), + Ok(x) => Some(format!("{}", x.display(db))), Err(_) => { let source = it.source(db)?; let mut body = source.value.body()?.syntax().clone(); diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index bd7ce2f1d0d0..d4eb314a381b 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -4,16 +4,19 @@ use syntax::TextRange; use crate::{fixture, HoverConfig, HoverDocFormat}; +const HOVER_BASE_CONFIG: HoverConfig = HoverConfig { + links_in_hover: false, + documentation: true, + format: HoverDocFormat::Markdown, + keywords: true, + interpret_tests: false, +}; + fn check_hover_no_result(ra_fixture: &str) { let (analysis, position) = fixture::position(ra_fixture); let hover = analysis .hover( - &HoverConfig { - links_in_hover: true, - documentation: true, - keywords: true, - format: HoverDocFormat::Markdown, - }, + &HoverConfig { links_in_hover: true, ..HOVER_BASE_CONFIG }, FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) }, ) .unwrap(); @@ -25,12 +28,7 @@ fn check(ra_fixture: &str, expect: Expect) { let (analysis, position) = fixture::position(ra_fixture); let hover = analysis .hover( - &HoverConfig { - links_in_hover: true, - documentation: true, - keywords: true, - format: HoverDocFormat::Markdown, - }, + &HoverConfig { links_in_hover: true, ..HOVER_BASE_CONFIG }, FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) }, ) .unwrap() @@ -47,12 +45,7 @@ fn check_hover_no_links(ra_fixture: &str, expect: Expect) { let (analysis, position) = fixture::position(ra_fixture); let hover = analysis .hover( - &HoverConfig { - links_in_hover: false, - documentation: true, - keywords: true, - format: HoverDocFormat::Markdown, - }, + &HOVER_BASE_CONFIG, FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) }, ) .unwrap() @@ -71,9 +64,8 @@ fn check_hover_no_markdown(ra_fixture: &str, expect: Expect) { .hover( &HoverConfig { links_in_hover: true, - documentation: true, - keywords: true, format: HoverDocFormat::PlainText, + ..HOVER_BASE_CONFIG }, FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) }, ) @@ -91,12 +83,7 @@ fn check_actions(ra_fixture: &str, expect: Expect) { let (analysis, file_id, position) = fixture::range_or_position(ra_fixture); let hover = analysis .hover( - &HoverConfig { - links_in_hover: true, - documentation: true, - keywords: true, - format: HoverDocFormat::Markdown, - }, + &HoverConfig { links_in_hover: true, ..HOVER_BASE_CONFIG }, FileRange { file_id, range: position.range_or_empty() }, ) .unwrap() @@ -106,34 +93,13 @@ fn check_actions(ra_fixture: &str, expect: Expect) { fn check_hover_range(ra_fixture: &str, expect: Expect) { let (analysis, range) = fixture::range(ra_fixture); - let hover = analysis - .hover( - &HoverConfig { - links_in_hover: false, - documentation: true, - keywords: true, - format: HoverDocFormat::Markdown, - }, - range, - ) - .unwrap() - .unwrap(); + let hover = analysis.hover(&HOVER_BASE_CONFIG, range).unwrap().unwrap(); expect.assert_eq(hover.info.markup.as_str()) } fn check_hover_range_no_results(ra_fixture: &str) { let (analysis, range) = fixture::range(ra_fixture); - let hover = analysis - .hover( - &HoverConfig { - links_in_hover: false, - documentation: true, - keywords: true, - format: HoverDocFormat::Markdown, - }, - range, - ) - .unwrap(); + let hover = analysis.hover(&HOVER_BASE_CONFIG, range).unwrap(); assert!(hover.is_none()); } @@ -490,7 +456,6 @@ fn hover_field_offset() { // Hovering over the field when instantiating check( r#" -//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128 struct Foo { fiel$0d_a: u8, field_b: i32, field_c: i16 } "#, expect![[r#" @@ -512,7 +477,6 @@ fn hover_shows_struct_field_info() { // Hovering over the field when instantiating check( r#" -//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128 struct Foo { field_a: u32 } fn main() { @@ -535,7 +499,6 @@ fn main() { // Hovering over the field in the definition check( r#" -//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128 struct Foo { field_a$0: u32 } fn main() { @@ -568,7 +531,7 @@ fn hover_const_static() { ``` ```rust - const foo: u32 = 123 (0x7B) + const foo: u32 = 123 ``` "#]], ); @@ -1467,8 +1430,6 @@ fn my() {} fn test_hover_struct_doc_comment() { check( r#" -//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128 - /// This is an example /// multiline doc /// @@ -1527,7 +1488,7 @@ fn foo() { let bar = Ba$0r; } ``` ```rust - struct Bar + struct Bar // size = 0, align = 1 ``` --- @@ -1556,7 +1517,7 @@ fn foo() { let bar = Ba$0r; } ``` ```rust - struct Bar + struct Bar // size = 0, align = 1 ``` --- @@ -1584,7 +1545,7 @@ pub struct B$0ar ``` ```rust - pub struct Bar + pub struct Bar // size = 0, align = 1 ``` --- @@ -1611,7 +1572,7 @@ pub struct B$0ar ``` ```rust - pub struct Bar + pub struct Bar // size = 0, align = 1 ``` --- @@ -2913,8 +2874,6 @@ fn main() { let foo_test = name_with_dashes::wrapper::Thing::new$0(); } fn hover_field_pat_shorthand_ref_match_ergonomics() { check( r#" -//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128 - struct S { f: i32, } @@ -3506,8 +3465,8 @@ impl Foo {} } #[test] -fn hover_const_eval_variant() { - // show hex for <10 +fn hover_const_eval_discriminant() { + // Don't show hex for <10 check( r#" #[repr(u8)] @@ -3532,7 +3491,7 @@ enum E { This is a doc "#]], ); - // show hex for >10 + // Show hex for >10 check( r#" #[repr(u8)] @@ -3656,7 +3615,7 @@ trait T { } impl T for i32 { const AA: A = A { - i: 2 + i: 2 + 3 } } fn main() { @@ -3671,9 +3630,7 @@ fn main() { ``` ```rust - const AA: A = A { - i: 2 - } + const AA: A = A { i: 5 } ``` "#]], ); @@ -3792,7 +3749,7 @@ const FOO$0: usize = 1 << 3; This is a doc "#]], ); - // show hex for >10 + // FIXME: show hex for >10 check( r#" /// This is a doc @@ -3806,7 +3763,7 @@ const FOO$0: usize = (1 << 3) + (1 << 2); ``` ```rust - const FOO: usize = 12 (0xC) + const FOO: usize = 12 ``` --- @@ -3937,7 +3894,7 @@ const FOO$0: u8 = b'a'; ``` ```rust - const FOO: u8 = 97 (0x61) + const FOO: u8 = 97 ``` --- @@ -3959,7 +3916,7 @@ const FOO$0: u8 = b'\x61'; ``` ```rust - const FOO: u8 = 97 (0x61) + const FOO: u8 = 97 ``` --- @@ -4354,8 +4311,6 @@ fn main() { fn hover_intra_doc_links() { check( r#" -//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128 - pub mod theitem { /// This is the item. Cool! pub struct TheItem; @@ -4496,7 +4451,7 @@ trait A where fn string_shadowed_with_inner_items() { check( r#" -//- /main.rs crate:main deps:alloc target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128 +//- /main.rs crate:main deps:alloc /// Custom `String` type. struct String; @@ -5191,7 +5146,7 @@ foo_macro!( ``` ```rust - pub struct Foo + pub struct Foo // size = 0, align = 1 ``` --- @@ -5205,8 +5160,6 @@ foo_macro!( fn hover_intra_in_attr() { check( r#" -//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128 - #[doc = "Doc comment for [`Foo$0`]"] pub struct Foo(i32); "#, @@ -5295,7 +5248,7 @@ pub struct Type; ``` ```rust - const KONST: dep::Type = $crate::Type + const KONST: dep::Type = Type ``` "#]], ); @@ -5327,8 +5280,6 @@ enum Enum { fn hover_record_variant_field() { check( r#" -//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128 - enum Enum { RecordV { field$0: u32 } } diff --git a/crates/ide/src/inlay_hints/discriminant.rs b/crates/ide/src/inlay_hints/discriminant.rs index 5dd51ad11f44..67eaa553ada4 100644 --- a/crates/ide/src/inlay_hints/discriminant.rs +++ b/crates/ide/src/inlay_hints/discriminant.rs @@ -59,8 +59,14 @@ fn variant_hints( }, kind: InlayKind::Discriminant, label: InlayHintLabel::simple( - match &d { - Ok(v) => format!("{}", v), + match d { + Ok(x) => { + if x >= 10 { + format!("{x} ({x:#X})") + } else { + format!("{x}") + } + } Err(_) => "?".into(), }, Some(InlayTooltip::String(match &d { diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs index 3f7f6885f611..c97691b14a57 100644 --- a/crates/ide/src/static_index.rs +++ b/crates/ide/src/static_index.rs @@ -139,6 +139,7 @@ impl StaticIndex<'_> { documentation: true, keywords: true, format: crate::HoverDocFormat::Markdown, + interpret_tests: false, }; let tokens = tokens.filter(|token| { matches!( diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index f609a50a05fa..29fd65c0aef6 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -366,6 +366,8 @@ config_data! { inlayHints_typeHints_hideClosureInitialization: bool = "false", /// Whether to hide inlay type hints for constructors. inlayHints_typeHints_hideNamedConstructor: bool = "false", + /// Enables the experimental support for interpreting tests. + interpret_tests: bool = "false", /// Join lines merges consecutive declaration and initialization of an assignment. joinLines_joinAssignments: bool = "true", @@ -1441,6 +1443,7 @@ impl Config { } }, keywords: self.data.hover_documentation_keywords_enable, + interpret_tests: self.data.interpret_tests, } } diff --git a/crates/test-utils/src/fixture.rs b/crates/test-utils/src/fixture.rs index d1afd0039aa4..cd1235fa6dc4 100644 --- a/crates/test-utils/src/fixture.rs +++ b/crates/test-utils/src/fixture.rs @@ -180,7 +180,9 @@ impl Fixture { let mut cfg_key_values = Vec::new(); let mut env = FxHashMap::default(); let mut introduce_new_source_root = None; - let mut target_data_layout = None; + let mut target_data_layout = Some( + "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128".to_string(), + ); for component in components[1..].iter() { let (key, value) = component.split_once(':').unwrap_or_else(|| panic!("invalid meta line: {meta:?}")); diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs index 3b033e1aae53..7b48e42489ca 100644 --- a/crates/test-utils/src/minicore.rs +++ b/crates/test-utils/src/minicore.rs @@ -510,6 +510,7 @@ pub mod fmt { pub mod slice { #[lang = "slice"] impl [T] { + #[lang = "slice_len_fn"] pub fn len(&self) -> usize { loop {} } diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index 50e3670a7a87..153baa3e7779 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -537,6 +537,11 @@ Only applies to closures with blocks, same as `#rust-analyzer.inlayHints.closure -- Whether to hide inlay type hints for constructors. -- +[[rust-analyzer.interpret.tests]]rust-analyzer.interpret.tests (default: `false`):: ++ +-- +Enables the experimental support for interpreting tests. +-- [[rust-analyzer.joinLines.joinAssignments]]rust-analyzer.joinLines.joinAssignments (default: `true`):: + -- diff --git a/editors/code/package.json b/editors/code/package.json index 3610e993f822..181dff76a292 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -1110,6 +1110,11 @@ "default": false, "type": "boolean" }, + "rust-analyzer.interpret.tests": { + "markdownDescription": "Enables the experimental support for interpreting tests.", + "default": false, + "type": "boolean" + }, "rust-analyzer.joinLines.joinAssignments": { "markdownDescription": "Join lines merges consecutive declaration and initialization of an assignment.", "default": true, From 03a3f743652e3909d27f8b9efcba19ce0ac02206 Mon Sep 17 00:00:00 2001 From: Andreas Deininger Date: Mon, 27 Feb 2023 21:17:25 +0100 Subject: [PATCH 053/362] Fixing typos --- .../development/proposals/syntax-tree-patterns.md | 14 +++++++------- clippy_lints/src/functions/impl_trait_in_params.rs | 4 ++-- clippy_lints/src/functions/misnamed_getters.rs | 2 +- clippy_lints/src/matches/mod.rs | 2 +- clippy_utils/src/lib.rs | 2 +- tests/ui/impl_trait_in_params.stderr | 4 ++-- tests/ui/implicit_clone.fixed | 2 +- tests/ui/implicit_clone.rs | 2 +- tests/ui/new_ret_no_self.rs | 2 +- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/book/src/development/proposals/syntax-tree-patterns.md b/book/src/development/proposals/syntax-tree-patterns.md index c5587c4bf908..ea4978011b19 100644 --- a/book/src/development/proposals/syntax-tree-patterns.md +++ b/book/src/development/proposals/syntax-tree-patterns.md @@ -68,13 +68,13 @@ The second part of the motivation is clippy's dependence on unstable compiler-internal data structures. Clippy lints are currently written against the compiler's AST / HIR which means that even small changes in these data structures might break a lot of lints. The second goal of this RFC is to **make -lints independant of the compiler's AST / HIR data structures**. +lints independent of the compiler's AST / HIR data structures**. # Approach A lot of complexity in writing lints currently seems to come from having to manually implement the matching logic (see code samples above). It's an -imparative style that describes *how* to match a syntax tree node instead of +imperative style that describes *how* to match a syntax tree node instead of specifying *what* should be matched against declaratively. In other areas, it's common to use declarative patterns to describe desired information and let the implementation do the actual matching. A well-known example of this approach are @@ -270,7 +270,7 @@ pattern!{ // matches if expressions that **may or may not** have an else block // Attn: `If(_, _, _)` matches only ifs that **have** an else block // - // | if with else block | if witout else block + // | if with else block | if without else block // If(_, _, _) | match | no match // If(_, _, _?) | match | match // If(_, _, ()) | no match | match @@ -568,7 +568,7 @@ another example, `Array( Lit(_)* )` is a valid pattern because the parameter of ## The IsMatch Trait -The pattern syntax and the *PatternTree* are independant of specific syntax tree +The pattern syntax and the *PatternTree* are independent of specific syntax tree implementations (rust ast / hir, syn, ...). When looking at the different pattern examples in the previous sections, it can be seen that the patterns don't contain any information specific to a certain syntax tree implementation. @@ -717,7 +717,7 @@ if false { #### Problems Extending Rust syntax (which is quite complex by itself) with additional syntax -needed for specifying patterns (alternations, sequences, repetisions, named +needed for specifying patterns (alternations, sequences, repetitions, named submatches, ...) might become difficult to read and really hard to parse properly. @@ -858,7 +858,7 @@ would be evaluated as soon as the `Block(_)#then` was matched. Another idea in this area would be to introduce a syntax for backreferences. They could be used to require that multiple parts of a pattern should match the same value. For example, the `assign_op_pattern` lint that searches for `a = a -op b` and recommends changing it to `a op= b` requires that both occurrances of +op b` and recommends changing it to `a op= b` requires that both occurrences of `a` are the same. Using `=#...` as syntax for backreferences, the lint could be implemented like this: @@ -882,7 +882,7 @@ least two return statements" could be a practical addition. For patterns like "a literal that is not a boolean literal" one currently needs to list all alternatives except the boolean case. Introducing a negation operator that allows to write `Lit(!Bool(_))` might be a good idea. This pattern -would be eqivalent to `Lit( Char(_) | Int(_) )` (given that currently only three +would be equivalent to `Lit( Char(_) | Int(_) )` (given that currently only three literal types are implemented). #### Functional composition diff --git a/clippy_lints/src/functions/impl_trait_in_params.rs b/clippy_lints/src/functions/impl_trait_in_params.rs index 2811a73f6c18..d3d0d91c1be7 100644 --- a/clippy_lints/src/functions/impl_trait_in_params.rs +++ b/clippy_lints/src/functions/impl_trait_in_params.rs @@ -22,7 +22,7 @@ pub(super) fn check_fn<'tcx>(cx: &LateContext<'_>, kind: &'tcx FnKind<'_>, body: if let Some(gen_span) = generics.span_for_param_suggestion() { diag.span_suggestion_with_style( gen_span, - "add a type paremeter", + "add a type parameter", format!(", {{ /* Generic name */ }}: {}", ¶m.name.ident().as_str()[5..]), rustc_errors::Applicability::HasPlaceholders, rustc_errors::SuggestionStyle::ShowAlways, @@ -35,7 +35,7 @@ pub(super) fn check_fn<'tcx>(cx: &LateContext<'_>, kind: &'tcx FnKind<'_>, body: ident.span.ctxt(), ident.span.parent(), ), - "add a type paremeter", + "add a type parameter", format!("<{{ /* Generic name */ }}: {}>", ¶m.name.ident().as_str()[5..]), rustc_errors::Applicability::HasPlaceholders, rustc_errors::SuggestionStyle::ShowAlways, diff --git a/clippy_lints/src/functions/misnamed_getters.rs b/clippy_lints/src/functions/misnamed_getters.rs index 8b53ee68ebdf..e5945939e60b 100644 --- a/clippy_lints/src/functions/misnamed_getters.rs +++ b/clippy_lints/src/functions/misnamed_getters.rs @@ -97,7 +97,7 @@ pub fn check_fn(cx: &LateContext<'_>, kind: FnKind<'_>, decl: &FnDecl<'_>, body: let Some(correct_field) = correct_field else { // There is no field corresponding to the getter name. - // FIXME: This can be a false positive if the correct field is reachable trought deeper autodereferences than used_field is + // FIXME: This can be a false positive if the correct field is reachable through deeper autodereferences than used_field is return; }; diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index 7b15a307fecf..97ecca450fac 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -925,7 +925,7 @@ declare_clippy_lint! { #[clippy::version = "1.66.0"] pub MANUAL_FILTER, complexity, - "reimplentation of `filter`" + "reimplementation of `filter`" } #[derive(Default)] diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index f02f8ecb43d7..213e5b33503e 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -617,7 +617,7 @@ fn item_children_by_name(tcx: TyCtxt<'_>, def_id: DefId, name: Symbol) -> Vec(_: impl Trait) {} | +++++++++++++++++++++++++++++++ @@ -16,7 +16,7 @@ error: '`impl Trait` used as a function parameter' LL | pub fn c(_: C, _: impl Trait) {} | ^^^^^^^^^^ | -help: add a type paremeter +help: add a type parameter | LL | pub fn c(_: C, _: impl Trait) {} | +++++++++++++++++++++++++++++++ diff --git a/tests/ui/implicit_clone.fixed b/tests/ui/implicit_clone.fixed index 51b1afbe5ac8..8ccc3da7b47c 100644 --- a/tests/ui/implicit_clone.fixed +++ b/tests/ui/implicit_clone.fixed @@ -87,7 +87,7 @@ fn main() { let kitten = Kitten {}; let _ = kitten.clone(); let _ = own_same_from_ref(&kitten); - // this shouln't lint + // this shouldn't lint let _ = kitten.to_vec(); // we expect no lints for this diff --git a/tests/ui/implicit_clone.rs b/tests/ui/implicit_clone.rs index 8a9027433d95..593333126077 100644 --- a/tests/ui/implicit_clone.rs +++ b/tests/ui/implicit_clone.rs @@ -87,7 +87,7 @@ fn main() { let kitten = Kitten {}; let _ = kitten.to_owned(); let _ = own_same_from_ref(&kitten); - // this shouln't lint + // this shouldn't lint let _ = kitten.to_vec(); // we expect no lints for this diff --git a/tests/ui/new_ret_no_self.rs b/tests/ui/new_ret_no_self.rs index beec42f08bb0..a2a30c8b931c 100644 --- a/tests/ui/new_ret_no_self.rs +++ b/tests/ui/new_ret_no_self.rs @@ -406,7 +406,7 @@ mod issue10041 { struct Bomb; impl Bomb { - // Hidden default generic paramter. + // Hidden default generic parameter. pub fn new() -> impl PartialOrd { 0i32 } From af79491ae62ebba739d3375c425b71c48f4b4905 Mon Sep 17 00:00:00 2001 From: Jonas Marcello Date: Tue, 28 Feb 2023 10:32:42 +0100 Subject: [PATCH 054/362] Rename method to generic_parameters --- crates/hir/src/lib.rs | 2 +- crates/ide/src/runnables.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 394ef4a9e098..c59e17af1f8a 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -3416,7 +3416,7 @@ impl Type { } /// Combines lifetime indicators, type and constant parameters into a single `Iterator` - pub fn lifetime_type_const_paramaters<'a>( + pub fn generic_parameters<'a>( &'a self, db: &'a dyn HirDatabase, ) -> impl Iterator + 'a { diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index 8de81206b6fd..77aef710ad1d 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs @@ -370,7 +370,7 @@ pub(crate) fn runnable_impl( let nav = def.try_to_nav(sema.db)?; let ty = def.self_ty(sema.db); let adt_name = ty.as_adt()?.name(sema.db); - let mut ty_args = ty.lifetime_type_const_paramaters(sema.db).peekable(); + let mut ty_args = ty.generic_parameters(sema.db).peekable(); let params = if ty_args.peek().is_some() { format!("<{}>", ty_args.format_with(",", |ty, cb| cb(&ty))) } else { @@ -436,7 +436,7 @@ fn module_def_doctest(db: &RootDatabase, def: Definition) -> Option { let ty = imp.self_ty(db); if let Some(adt) = ty.as_adt() { let name = adt.name(db); - let mut ty_args = ty.lifetime_type_const_paramaters(db).peekable(); + let mut ty_args = ty.generic_parameters(db).peekable(); format_to!(path, "{}", name); if ty_args.peek().is_some() { format_to!(path, "<{}>", ty_args.format_with(",", |ty, cb| cb(&ty))); From 47a567b833b19a1e2f7e487e9a17d1e8da81b44e Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 28 Feb 2023 12:08:23 +0100 Subject: [PATCH 055/362] Deduplicate source roots that have overlapping include paths --- crates/flycheck/src/lib.rs | 41 ++++++++------ crates/project-model/src/workspace.rs | 42 ++++++++------- crates/rust-analyzer/src/config.rs | 28 +++++----- crates/rust-analyzer/src/reload.rs | 78 ++++++++++++++++++++++++--- 4 files changed, 135 insertions(+), 54 deletions(-) diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs index 11f7b068ecb1..19800224db60 100644 --- a/crates/flycheck/src/lib.rs +++ b/crates/flycheck/src/lib.rs @@ -76,7 +76,7 @@ impl fmt::Display for FlycheckConfig { #[derive(Debug)] pub struct FlycheckHandle { // XXX: drop order is significant - sender: Sender, + sender: Sender, _thread: jod_thread::JoinHandle, id: usize, } @@ -89,7 +89,7 @@ impl FlycheckHandle { workspace_root: AbsPathBuf, ) -> FlycheckHandle { let actor = FlycheckActor::new(id, sender, config, workspace_root); - let (sender, receiver) = unbounded::(); + let (sender, receiver) = unbounded::(); let thread = jod_thread::Builder::new() .name("Flycheck".to_owned()) .spawn(move || actor.run(receiver)) @@ -99,12 +99,12 @@ impl FlycheckHandle { /// Schedule a re-start of the cargo check worker. pub fn restart(&self) { - self.sender.send(Restart::Yes).unwrap(); + self.sender.send(StateChange::Restart).unwrap(); } /// Stop this cargo check worker. pub fn cancel(&self) { - self.sender.send(Restart::No).unwrap(); + self.sender.send(StateChange::Cancel).unwrap(); } pub fn id(&self) -> usize { @@ -149,9 +149,9 @@ pub enum Progress { DidFailToRestart(String), } -enum Restart { - Yes, - No, +enum StateChange { + Restart, + Cancel, } /// A [`FlycheckActor`] is a single check instance of a workspace. @@ -172,7 +172,7 @@ struct FlycheckActor { } enum Event { - Restart(Restart), + RequestStateChange(StateChange), CheckEvent(Option), } @@ -191,30 +191,31 @@ impl FlycheckActor { self.send(Message::Progress { id: self.id, progress }); } - fn next_event(&self, inbox: &Receiver) -> Option { + fn next_event(&self, inbox: &Receiver) -> Option { let check_chan = self.cargo_handle.as_ref().map(|cargo| &cargo.receiver); if let Ok(msg) = inbox.try_recv() { // give restarts a preference so check outputs don't block a restart or stop - return Some(Event::Restart(msg)); + return Some(Event::RequestStateChange(msg)); } select! { - recv(inbox) -> msg => msg.ok().map(Event::Restart), + recv(inbox) -> msg => msg.ok().map(Event::RequestStateChange), recv(check_chan.unwrap_or(&never())) -> msg => Some(Event::CheckEvent(msg.ok())), } } - fn run(mut self, inbox: Receiver) { + fn run(mut self, inbox: Receiver) { 'event: while let Some(event) = self.next_event(&inbox) { match event { - Event::Restart(Restart::No) => { + Event::RequestStateChange(StateChange::Cancel) => { + tracing::debug!(flycheck_id = self.id, "flycheck cancelled"); self.cancel_check_process(); } - Event::Restart(Restart::Yes) => { + Event::RequestStateChange(StateChange::Restart) => { // Cancel the previously spawned process self.cancel_check_process(); while let Ok(restart) = inbox.recv_timeout(Duration::from_millis(50)) { // restart chained with a stop, so just cancel - if let Restart::No = restart { + if let StateChange::Cancel = restart { continue 'event; } } @@ -255,10 +256,20 @@ impl FlycheckActor { } Event::CheckEvent(Some(message)) => match message { CargoMessage::CompilerArtifact(msg) => { + tracing::trace!( + flycheck_id = self.id, + artifact = msg.target.name, + "artifact received" + ); self.report_progress(Progress::DidCheckCrate(msg.target.name)); } CargoMessage::Diagnostic(msg) => { + tracing::trace!( + flycheck_id = self.id, + message = msg.message, + "diagnostic received" + ); self.send(Message::AddDiagnostic { id: self.id, workspace_root: self.root.clone(), diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs index 2a11f1e8eb82..45cb89619615 100644 --- a/crates/project-model/src/workspace.rs +++ b/crates/project-model/src/workspace.rs @@ -237,7 +237,7 @@ impl ProjectWorkspace { }; if let Some(sysroot) = &sysroot { - tracing::info!(src_root = %sysroot.src_root().display(), root = %sysroot.root().display(), "Using sysroot"); + tracing::info!(workspace = %cargo_toml.display(), src_root = %sysroot.src_root().display(), root = %sysroot.root().display(), "Using sysroot"); } let rustc_dir = match &config.rustc_source { @@ -247,27 +247,31 @@ impl ProjectWorkspace { } None => None, }; - if let Some(rustc_dir) = &rustc_dir { - tracing::info!(rustc_dir = %rustc_dir.display(), "Using rustc source"); - } let rustc = match rustc_dir { - Some(rustc_dir) => match CargoWorkspace::fetch_metadata( - &rustc_dir, - cargo_toml.parent(), - config, - progress, - ) { - Ok(meta) => Some(CargoWorkspace::new(meta)), - Err(e) => { - tracing::error!( - %e, - "Failed to read Cargo metadata from rustc source at {}", - rustc_dir.display() - ); - None + Some(rustc_dir) if rustc_dir == cargo_toml => { + tracing::info!(rustc_dir = %rustc_dir.display(), "Workspace is the rustc workspace itself, not adding the rustc workspace separately"); + None + } + Some(rustc_dir) => { + tracing::info!(workspace = %cargo_toml.display(), rustc_dir = %rustc_dir.display(), "Using rustc source"); + match CargoWorkspace::fetch_metadata( + &rustc_dir, + cargo_toml.parent(), + config, + progress, + ) { + Ok(meta) => Some(CargoWorkspace::new(meta)), + Err(e) => { + tracing::error!( + %e, + "Failed to read Cargo metadata from rustc source at {}", + rustc_dir.display() + ); + None + } } - }, + } None => None, }; diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 48d3fd0e2b9f..c643a218a079 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -852,27 +852,27 @@ impl Config { } pub fn linked_projects(&self) -> Vec { match self.data.linkedProjects.as_slice() { - [] => match self.discovered_projects.as_ref() { - Some(discovered_projects) => { - let exclude_dirs: Vec<_> = self - .data - .files_excludeDirs + [] => { + match self.discovered_projects.as_ref() { + Some(discovered_projects) => { + let exclude_dirs: Vec<_> = self + .data + .files_excludeDirs + .iter() + .map(|p| self.root_path.join(p)) + .collect(); + discovered_projects .iter() - .map(|p| self.root_path.join(p)) - .collect(); - discovered_projects - .iter() - .filter(|p| { - let (ProjectManifest::ProjectJson(path) - | ProjectManifest::CargoToml(path)) = p; + .filter(|(ProjectManifest::ProjectJson(path) | ProjectManifest::CargoToml(path))| { !exclude_dirs.iter().any(|p| path.starts_with(p)) }) .cloned() .map(LinkedProject::from) .collect() + } + None => Vec::new(), } - None => Vec::new(), - }, + } linked_projects => linked_projects .iter() .filter_map(|linked_project| match linked_project { diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index abce0d737827..1a396bb06a3d 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -12,17 +12,21 @@ //! correct. Instead, we try to provide a best-effort service. Even if the //! project is currently loading and we don't have a full project model, we //! still want to respond to various requests. -use std::{mem, sync::Arc}; +use std::{collections::hash_map::Entry, mem, sync::Arc}; use flycheck::{FlycheckConfig, FlycheckHandle}; use hir::db::DefDatabase; use ide::Change; -use ide_db::base_db::{ - CrateGraph, Env, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacroKind, - ProcMacroLoadResult, SourceRoot, VfsPath, +use ide_db::{ + base_db::{ + CrateGraph, Env, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacroKind, + ProcMacroLoadResult, SourceRoot, VfsPath, + }, + FxHashMap, }; +use itertools::Itertools; use proc_macro_api::{MacroDylib, ProcMacroServer}; -use project_model::{ProjectWorkspace, WorkspaceBuildScripts}; +use project_model::{PackageRoot, ProjectWorkspace, WorkspaceBuildScripts}; use syntax::SmolStr; use vfs::{file_set::FileSetConfig, AbsPath, AbsPathBuf, ChangeKind}; @@ -494,7 +498,69 @@ impl ProjectFolders { let mut fsc = FileSetConfig::builder(); let mut local_filesets = vec![]; - for root in workspaces.iter().flat_map(|ws| ws.to_roots()) { + // Dedup source roots + // Depending on the project setup, we can have duplicated source roots, or for example in + // the case of the rustc workspace, we can end up with two source roots that are almost the + // same but not quite, like: + // PackageRoot { is_local: false, include: [AbsPathBuf(".../rust/src/tools/miri/cargo-miri")], exclude: [] } + // PackageRoot { + // is_local: true, + // include: [AbsPathBuf(".../rust/src/tools/miri/cargo-miri"), AbsPathBuf(".../rust/build/x86_64-pc-windows-msvc/stage0-tools/x86_64-pc-windows-msvc/release/build/cargo-miri-85801cd3d2d1dae4/out")], + // exclude: [AbsPathBuf(".../rust/src/tools/miri/cargo-miri/.git"), AbsPathBuf(".../rust/src/tools/miri/cargo-miri/target")] + // } + // + // The first one comes from the explicit rustc workspace which points to the rustc workspace itself + // The second comes from the rustc workspace that we load as the actual project workspace + // These `is_local` differing in this kind of way gives us problems, especially when trying to filter diagnostics as we don't report diagnostics for external libraries. + // So we need to deduplicate these, usually it would be enough to deduplicate by `include`, but as the rustc example shows here that doesn't work, + // so we need to also coalesce the includes if they overlap. + + let mut roots: Vec<_> = workspaces + .iter() + .flat_map(|ws| ws.to_roots()) + .update(|root| root.include.sort()) + .sorted_by(|a, b| a.include.cmp(&b.include)) + .collect(); + + // map that tracks indices of overlapping roots + let mut overlap_map = FxHashMap::<_, Vec<_>>::default(); + let mut done = false; + + while !mem::replace(&mut done, true) { + // maps include paths to indices of the corresponding root + let mut include_to_idx = FxHashMap::default(); + // Find and note down the indices of overlapping roots + for (idx, root) in roots.iter().filter(|it| !it.include.is_empty()).enumerate() { + for include in &root.include { + match include_to_idx.entry(include) { + Entry::Occupied(e) => { + overlap_map.entry(*e.get()).or_default().push(idx); + } + Entry::Vacant(e) => { + e.insert(idx); + } + } + } + } + for (k, v) in overlap_map.drain() { + done = false; + for v in v { + let r = mem::replace( + &mut roots[v], + PackageRoot { is_local: false, include: vec![], exclude: vec![] }, + ); + roots[k].is_local |= r.is_local; + roots[k].include.extend(r.include); + roots[k].exclude.extend(r.exclude); + } + roots[k].include.sort(); + roots[k].exclude.sort(); + roots[k].include.dedup(); + roots[k].exclude.dedup(); + } + } + + for root in roots.into_iter().filter(|it| !it.include.is_empty()) { let file_set_roots: Vec = root.include.iter().cloned().map(VfsPath::from).collect(); From b5a1ddf77cc0e0713af90100b3cdd80421f00435 Mon Sep 17 00:00:00 2001 From: Ryo Yoshida Date: Tue, 28 Feb 2023 21:19:40 +0900 Subject: [PATCH 056/362] Don't use unstable `pointer` link --- crates/hir-ty/src/mir.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/hir-ty/src/mir.rs b/crates/hir-ty/src/mir.rs index 00fa6b795222..140caad54566 100644 --- a/crates/hir-ty/src/mir.rs +++ b/crates/hir-ty/src/mir.rs @@ -676,7 +676,8 @@ pub enum Rvalue { /// `ArrayToPointer` and `MutToConstPointer` are special. Cast(CastKind, Operand, Ty), - /// * `Offset` has the same semantics as [`offset`](pointer::offset), except that the second + // FIXME link to `pointer::offset` when it hits stable. + /// * `Offset` has the same semantics as `pointer::offset`, except that the second /// parameter may be a `usize` as well. /// * The comparison operations accept `bool`s, `char`s, signed or unsigned integers, floats, /// raw pointers, or function pointers and return a `bool`. The types of the operands must be From 2cadea5dc5db67212e264ec0a66b3c04e38e41c1 Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Tue, 28 Feb 2023 12:45:19 +0000 Subject: [PATCH 057/362] Fix array-size-threshold config deserialization error --- book/src/lint_configuration.md | 2 +- clippy_lints/src/lib.rs | 2 +- clippy_lints/src/utils/conf.rs | 2 +- .../array_size_threshold.rs | 10 +++++++ .../array_size_threshold.stderr | 29 +++++++++++++++++++ .../ui-toml/array_size_threshold/clippy.toml | 1 + tests/ui/crashes/ice-10044.rs | 3 -- tests/ui/crashes/ice-10044.stderr | 10 ------- tests/ui/large_stack_arrays.rs | 1 + tests/ui/large_stack_arrays.stderr | 10 ++++++- 10 files changed, 53 insertions(+), 17 deletions(-) create mode 100644 tests/ui-toml/array_size_threshold/array_size_threshold.rs create mode 100644 tests/ui-toml/array_size_threshold/array_size_threshold.stderr create mode 100644 tests/ui-toml/array_size_threshold/clippy.toml delete mode 100644 tests/ui/crashes/ice-10044.rs delete mode 100644 tests/ui/crashes/ice-10044.stderr diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index 33f2b5c1de99..995dd2f04b1e 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -306,7 +306,7 @@ The maximum number of lines a function or method can have ### array-size-threshold The maximum allowed size for arrays on the stack -**Default Value:** `512000` (`u128`) +**Default Value:** `512000` (`u64`) * [large_stack_arrays](https://rust-lang.github.io/rust-clippy/master/index.html#large_stack_arrays) * [large_const_arrays](https://rust-lang.github.io/rust-clippy/master/index.html#large_const_arrays) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 145cf524652f..c626e0bd9985 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -777,7 +777,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: 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; + let array_size_threshold = u128::from(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)); diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index 5f74de5a2886..1c7f3e96db89 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -334,7 +334,7 @@ define_Conf! { /// Lint: LARGE_STACK_ARRAYS, LARGE_CONST_ARRAYS. /// /// The maximum allowed size for arrays on the stack - (array_size_threshold: u128 = 512_000), + (array_size_threshold: u64 = 512_000), /// Lint: VEC_BOX. /// /// The size of the boxed type in bytes, where boxing in a `Vec` is allowed diff --git a/tests/ui-toml/array_size_threshold/array_size_threshold.rs b/tests/ui-toml/array_size_threshold/array_size_threshold.rs new file mode 100644 index 000000000000..7f623c7a9ec5 --- /dev/null +++ b/tests/ui-toml/array_size_threshold/array_size_threshold.rs @@ -0,0 +1,10 @@ +#![allow(unused)] +#![warn(clippy::large_const_arrays, clippy::large_stack_arrays)] + +const ABOVE: [u8; 11] = [0; 11]; +const BELOW: [u8; 10] = [0; 10]; + +fn main() { + let above = [0u8; 11]; + let below = [0u8; 10]; +} diff --git a/tests/ui-toml/array_size_threshold/array_size_threshold.stderr b/tests/ui-toml/array_size_threshold/array_size_threshold.stderr new file mode 100644 index 000000000000..ac017b20916d --- /dev/null +++ b/tests/ui-toml/array_size_threshold/array_size_threshold.stderr @@ -0,0 +1,29 @@ +error: large array defined as const + --> $DIR/array_size_threshold.rs:4:1 + | +LL | const ABOVE: [u8; 11] = [0; 11]; + | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: make this a static item: `static` + | + = note: `-D clippy::large-const-arrays` implied by `-D warnings` + +error: allocating a local array larger than 10 bytes + --> $DIR/array_size_threshold.rs:4:25 + | +LL | const ABOVE: [u8; 11] = [0; 11]; + | ^^^^^^^ + | + = help: consider allocating on the heap with `vec![0; 11].into_boxed_slice()` + = note: `-D clippy::large-stack-arrays` implied by `-D warnings` + +error: allocating a local array larger than 10 bytes + --> $DIR/array_size_threshold.rs:8:17 + | +LL | let above = [0u8; 11]; + | ^^^^^^^^^ + | + = help: consider allocating on the heap with `vec![0u8; 11].into_boxed_slice()` + +error: aborting due to 3 previous errors + diff --git a/tests/ui-toml/array_size_threshold/clippy.toml b/tests/ui-toml/array_size_threshold/clippy.toml new file mode 100644 index 000000000000..3f1fe9a12099 --- /dev/null +++ b/tests/ui-toml/array_size_threshold/clippy.toml @@ -0,0 +1 @@ +array-size-threshold = 10 diff --git a/tests/ui/crashes/ice-10044.rs b/tests/ui/crashes/ice-10044.rs deleted file mode 100644 index 65f38fe71188..000000000000 --- a/tests/ui/crashes/ice-10044.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - [0; usize::MAX]; -} diff --git a/tests/ui/crashes/ice-10044.stderr b/tests/ui/crashes/ice-10044.stderr deleted file mode 100644 index 731f8265ad6c..000000000000 --- a/tests/ui/crashes/ice-10044.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error: statement with no effect - --> $DIR/ice-10044.rs:2:5 - | -LL | [0; usize::MAX]; - | ^^^^^^^^^^^^^^^^ - | - = note: `-D clippy::no-effect` implied by `-D warnings` - -error: aborting due to previous error - diff --git a/tests/ui/large_stack_arrays.rs b/tests/ui/large_stack_arrays.rs index 6790765f803e..99787ffd3d39 100644 --- a/tests/ui/large_stack_arrays.rs +++ b/tests/ui/large_stack_arrays.rs @@ -24,6 +24,7 @@ fn main() { [S { data: [0; 32] }; 5000], [Some(""); 20_000_000], [E::T(0); 5000], + [0u8; usize::MAX], ); let good = ( diff --git a/tests/ui/large_stack_arrays.stderr b/tests/ui/large_stack_arrays.stderr index c7bf941ad009..24e90094982a 100644 --- a/tests/ui/large_stack_arrays.stderr +++ b/tests/ui/large_stack_arrays.stderr @@ -31,5 +31,13 @@ LL | [E::T(0); 5000], | = help: consider allocating on the heap with `vec![E::T(0); 5000].into_boxed_slice()` -error: aborting due to 4 previous errors +error: allocating a local array larger than 512000 bytes + --> $DIR/large_stack_arrays.rs:27:9 + | +LL | [0u8; usize::MAX], + | ^^^^^^^^^^^^^^^^^ + | + = help: consider allocating on the heap with `vec![0u8; usize::MAX].into_boxed_slice()` + +error: aborting due to 5 previous errors From 69a11007762717d88701453bab64fcd24e08ac14 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Mon, 27 Feb 2023 15:04:27 -0500 Subject: [PATCH 058/362] Run dogfood on all packages before failing --- tests/dogfood.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/tests/dogfood.rs b/tests/dogfood.rs index 6d0022f7a5cc..9643c2c97070 100644 --- a/tests/dogfood.rs +++ b/tests/dogfood.rs @@ -7,6 +7,7 @@ #![cfg_attr(feature = "deny-warnings", deny(warnings))] #![warn(rust_2018_idioms, unused_lifetimes)] +use itertools::Itertools; use std::path::PathBuf; use std::process::Command; use test_utils::IS_RUSTC_TEST_SUITE; @@ -19,8 +20,10 @@ fn dogfood_clippy() { return; } + let mut failed_packages = Vec::new(); + // "" is the root package - for package in &[ + for package in [ "", "clippy_dev", "clippy_lints", @@ -28,8 +31,16 @@ fn dogfood_clippy() { "lintcheck", "rustc_tools_util", ] { - run_clippy_for_package(package, &["-D", "clippy::all", "-D", "clippy::pedantic"]); + if !run_clippy_for_package(package, &["-D", "clippy::all", "-D", "clippy::pedantic"]) { + failed_packages.push(if package.is_empty() { "root" } else { package }); + } } + + assert!( + !failed_packages.is_empty(), + "Dogfood failed for packages `{}`", + failed_packages.iter().format(", "), + ) } #[test] @@ -71,7 +82,7 @@ fn run_metadata_collection_lint() { run_clippy_for_package("clippy_lints", &["-A", "unfulfilled_lint_expectations"]); } -fn run_clippy_for_package(project: &str, args: &[&str]) { +fn run_clippy_for_package(project: &str, args: &[&str]) -> bool { let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let mut command = Command::new(&*test_utils::CARGO_CLIPPY_PATH); @@ -107,5 +118,5 @@ fn run_clippy_for_package(project: &str, args: &[&str]) { println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); - assert!(output.status.success()); + output.status.success() } From 90ba82bd396cf95cb02318e43a66015cc3f65d7b Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Tue, 28 Feb 2023 09:50:07 -0500 Subject: [PATCH 059/362] Two small documentation improvements --- clippy_lints/src/functions/mod.rs | 2 +- clippy_lints/src/methods/mod.rs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/functions/mod.rs b/clippy_lints/src/functions/mod.rs index d2852b4acad1..7c5e44bb7dca 100644 --- a/clippy_lints/src/functions/mod.rs +++ b/clippy_lints/src/functions/mod.rs @@ -185,7 +185,7 @@ declare_clippy_lint! { /// ### Examples /// ```rust /// // this could be annotated with `#[must_use]`. - /// fn id(t: T) -> T { t } + /// pub fn id(t: T) -> T { t } /// ``` #[clippy::version = "1.40.0"] pub MUST_USE_CANDIDATE, diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 702df4b282b8..56e3988bf097 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -340,8 +340,9 @@ declare_clippy_lint! { 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: + /// Checks for methods with certain name prefixes or suffixes, and which + /// do not adhere to standard conventions regarding how `self` is taken. + /// The actual rules are: /// /// |Prefix |Postfix |`self` taken | `self` type | /// |-------|------------|-------------------------------|--------------| From f64fe66c2a4a8e721ccb1f8971051c7caad8c100 Mon Sep 17 00:00:00 2001 From: hkalbasi Date: Tue, 28 Feb 2023 23:12:30 +0330 Subject: [PATCH 060/362] Add tuple to render_const_scalar --- crates/hir-ty/src/display.rs | 34 +++++++++++++++++++++++++++++++--- crates/ide/src/hover/tests.rs | 21 +++++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs index b6165f629e7e..3123ddb985b2 100644 --- a/crates/hir-ty/src/display.rs +++ b/crates/hir-ty/src/display.rs @@ -425,8 +425,36 @@ fn render_const_scalar( let s = std::str::from_utf8(bytes).unwrap_or(""); write!(f, "{s:?}") } - _ => f.write_str(""), + _ => f.write_str(""), }, + chalk_ir::TyKind::Tuple(_, subst) => { + // FIXME: Remove this line. If the target data layout is independent + // of the krate, the `db.target_data_layout` and its callers like `layout_of_ty` don't need + // to get krate. Otherwise, we need to get krate from the final callers of the hir display + // infrastructure and have it here as a field on `f`. + let krate = *f.db.crate_graph().crates_in_topological_order().last().unwrap(); + let Ok(layout) = layout_of_ty(f.db, ty, krate) else { + return f.write_str(""); + }; + f.write_str("(")?; + let mut first = true; + for (id, ty) in subst.iter(Interner).enumerate() { + if first { + first = false; + } else { + f.write_str(", ")?; + } + let ty = ty.assert_ty_ref(Interner); // Tuple only has type argument + let offset = layout.fields.offset(id).bytes_usize(); + let Ok(layout) = layout_of_ty(f.db, &ty, krate) else { + f.write_str("")?; + continue; + }; + let size = layout.size.bytes_usize(); + render_const_scalar(f, &b[offset..offset + size], memory_map, &ty)?; + } + f.write_str(")") + } chalk_ir::TyKind::Adt(adt, subst) => match adt.0 { hir_def::AdtId::StructId(s) => { let data = f.db.struct_data(s); @@ -457,7 +485,7 @@ fn render_const_scalar( render_field(f, id)?; } for (id, data) in it { - write!(f, ", {}: ", data.name)?; + write!(f, ", {}: ", data.name)?; render_field(f, id)?; } write!(f, " }}")?; @@ -481,7 +509,7 @@ fn render_const_scalar( hir_def::AdtId::UnionId(u) => write!(f, "{}", f.db.union_data(u).name), hir_def::AdtId::EnumId(_) => f.write_str(""), }, - _ => f.write_str(""), + _ => f.write_str(""), } } diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index d4eb314a381b..b29b57d134ed 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -573,6 +573,27 @@ const foo$0: u32 = { ); } +#[test] +fn hover_eval_complex_constants() { + check( + r#" + struct X { f1: (), f2: i32 } + const foo$0: (i8, X, i64) = (1, X { f2: 5 - 1, f1: () }, 1 - 2); + "#, + expect![[r#" + *foo* + + ```rust + test + ``` + + ```rust + const foo: (i8, X, i64) = (1, X { f1: (), f2: 4 }, -1) + ``` + "#]], + ); +} + #[test] fn hover_default_generic_types() { check( From 7fb76a1ea72b777d8faf3bc82719435002e7083c Mon Sep 17 00:00:00 2001 From: xFrednet Date: Tue, 28 Feb 2023 21:55:51 +0100 Subject: [PATCH 061/362] Update version attribute for 1.68 lints --- clippy_lints/src/almost_complete_range.rs | 2 +- clippy_lints/src/fn_null_check.rs | 2 +- clippy_lints/src/permissions_set_readonly_false.rs | 2 +- clippy_lints/src/size_of_ref.rs | 2 +- clippy_lints/src/transmute/mod.rs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/almost_complete_range.rs b/clippy_lints/src/almost_complete_range.rs index 42e14b5cd945..32d80f42e7e0 100644 --- a/clippy_lints/src/almost_complete_range.rs +++ b/clippy_lints/src/almost_complete_range.rs @@ -24,7 +24,7 @@ declare_clippy_lint! { /// ```rust /// let _ = 'a'..='z'; /// ``` - #[clippy::version = "1.63.0"] + #[clippy::version = "1.68.0"] pub ALMOST_COMPLETE_RANGE, suspicious, "almost complete range" diff --git a/clippy_lints/src/fn_null_check.rs b/clippy_lints/src/fn_null_check.rs index 91c8c340ce28..d8f4a5fe221b 100644 --- a/clippy_lints/src/fn_null_check.rs +++ b/clippy_lints/src/fn_null_check.rs @@ -25,7 +25,7 @@ declare_clippy_lint! { /// /// if fn_ptr.is_none() { ... } /// ``` - #[clippy::version = "1.67.0"] + #[clippy::version = "1.68.0"] pub FN_NULL_CHECK, correctness, "`fn()` type assumed to be nullable" diff --git a/clippy_lints/src/permissions_set_readonly_false.rs b/clippy_lints/src/permissions_set_readonly_false.rs index e7095ec191f7..664d44d6504d 100644 --- a/clippy_lints/src/permissions_set_readonly_false.rs +++ b/clippy_lints/src/permissions_set_readonly_false.rs @@ -21,7 +21,7 @@ declare_clippy_lint! { /// let mut permissions = metadata.permissions(); /// permissions.set_readonly(false); /// ``` - #[clippy::version = "1.66.0"] + #[clippy::version = "1.68.0"] pub PERMISSIONS_SET_READONLY_FALSE, suspicious, "Checks for calls to `std::fs::Permissions.set_readonly` with argument `false`" diff --git a/clippy_lints/src/size_of_ref.rs b/clippy_lints/src/size_of_ref.rs index 3fcdb4288ce6..8abec06c641c 100644 --- a/clippy_lints/src/size_of_ref.rs +++ b/clippy_lints/src/size_of_ref.rs @@ -45,7 +45,7 @@ declare_clippy_lint! { /// } /// } /// ``` - #[clippy::version = "1.67.0"] + #[clippy::version = "1.68.0"] pub SIZE_OF_REF, suspicious, "Argument to `std::mem::size_of_val()` is a double-reference, which is almost certainly unintended" diff --git a/clippy_lints/src/transmute/mod.rs b/clippy_lints/src/transmute/mod.rs index c01cbe5090f7..0dc30f7a9355 100644 --- a/clippy_lints/src/transmute/mod.rs +++ b/clippy_lints/src/transmute/mod.rs @@ -458,7 +458,7 @@ declare_clippy_lint! { /// ```rust /// let null_fn: Option = None; /// ``` - #[clippy::version = "1.67.0"] + #[clippy::version = "1.68.0"] pub TRANSMUTE_NULL_TO_FN, correctness, "transmute results in a null function pointer, which is undefined behavior" From 917ebc3af65abc067262b3e987d12603c9002841 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Tue, 28 Feb 2023 21:50:44 +0100 Subject: [PATCH 062/362] Changelog for Rust 1.68 :dog: --- CHANGELOG.md | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 146 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 765826ed867d..915a12fc8cb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,152 @@ document. ## Unreleased / Beta / In Rust Nightly -[d822110d...master](https://github.com/rust-lang/rust-clippy/compare/d822110d...master) +[7f27e2e7...master](https://github.com/rust-lang/rust-clippy/compare/7f27e2e7...master) + +## Rust 1.68 + +Current beta, released 2023-03-09 + +[d822110d...7f27e2e7](https://github.com/rust-lang/rust-clippy/compare/d822110d...7f27e2e7) + +### New Lints + +* [`permissions_set_readonly_false`] + [#10063](https://github.com/rust-lang/rust-clippy/pull/10063) +* [`almost_complete_range`] + [#10043](https://github.com/rust-lang/rust-clippy/pull/10043) +* [`size_of_ref`] + [#10098](https://github.com/rust-lang/rust-clippy/pull/10098) +* [`semicolon_outside_block`] + [#9826](https://github.com/rust-lang/rust-clippy/pull/9826) +* [`semicolon_inside_block`] + [#9826](https://github.com/rust-lang/rust-clippy/pull/9826) +* [`transmute_null_to_fn`] + [#10099](https://github.com/rust-lang/rust-clippy/pull/10099) +* [`fn_null_check`] + [#10099](https://github.com/rust-lang/rust-clippy/pull/10099) + +### Moves and Deprecations + +* Moved [`manual_clamp`] to `nursery` (Now allow-by-default) + [#10101](https://github.com/rust-lang/rust-clippy/pull/10101) +* Moved [`mutex_atomic`] to `restriction` + [#10115](https://github.com/rust-lang/rust-clippy/pull/10115) +* Renamed `derive_hash_xor_eq` to [`derived_hash_with_manual_eq`] + [#10184](https://github.com/rust-lang/rust-clippy/pull/10184) + +### Enhancements + +* [`collapsible_str_replace`]: Now takes MSRV into consideration. The minimal version is 1.58 + [#10047](https://github.com/rust-lang/rust-clippy/pull/10047) +* [`unused_self`]: No longer lints, if the method body contains a `todo!()` call + [#10166](https://github.com/rust-lang/rust-clippy/pull/10166) +* [`derivable_impls`]: Now suggests deriving `Default` for enums with default unit variants + [#10161](https://github.com/rust-lang/rust-clippy/pull/10161) +* [`arithmetic_side_effects`]: Added two new config values + `arithmetic-side-effects-allowed-binary` and `arithmetic-side-effects-allowed-unary` + to allow operation on user types + [#9840](https://github.com/rust-lang/rust-clippy/pull/9840) +* [`large_const_arrays`], [`large_stack_arrays`]: avoid integer overflow when calculating + total array size + [#10103](https://github.com/rust-lang/rust-clippy/pull/10103) +* [`indexing_slicing`]: add new config `suppress-restriction-lint-in-const` to enable + restriction lints, even if the suggestion might not be applicable + [#9920](https://github.com/rust-lang/rust-clippy/pull/9920) +* [`needless_borrow`], [`redundant_clone`]: Now track references better and detect more cases + [#9701](https://github.com/rust-lang/rust-clippy/pull/9701) +* [`derived_hash_with_manual_eq`]: Now allows `#[derive(PartialEq)]` with custom `Hash` + implementations + [#10184](https://github.com/rust-lang/rust-clippy/pull/10184) +* [`manual_is_ascii_check`]: Now detects ranges with `.contains()` calls + [#10053](https://github.com/rust-lang/rust-clippy/pull/10053) +* [`transmuting_null`]: Now detects `const` pointers to all types + [#10099](https://github.com/rust-lang/rust-clippy/pull/10099) +* [`needless_return`]: Now detects more cases for returns of owned values + [#10110](https://github.com/rust-lang/rust-clippy/pull/10110) + +### False Positive Fixes + +* [`field_reassign_with_default`]: No longer lints cases, where values are initializes from + closures capturing struct values + [#10143](https://github.com/rust-lang/rust-clippy/pull/10143) +* [`seek_to_start_instead_of_rewind`]: No longer lints, if the return of `seek` is used. + [#10096](https://github.com/rust-lang/rust-clippy/pull/10096) +* [`manual_filter`]: Now ignores if expressions where the else branch has side effects or + doesn't return `None` + [#10091](https://github.com/rust-lang/rust-clippy/pull/10091) +* [`implicit_clone`]: No longer lints if the type doesn't implement clone + [#10022](https://github.com/rust-lang/rust-clippy/pull/10022) +* [`match_wildcard_for_single_variants`]: No longer lints on wildcards with a guard + [#10056](https://github.com/rust-lang/rust-clippy/pull/10056) +* [`drop_ref`]: No longer lints idiomatic expression in `match` arms + [#10142](https://github.com/rust-lang/rust-clippy/pull/10142) +* [`arithmetic_side_effects`]: No longer lints on corner cases with negative number literals + [#9867](https://github.com/rust-lang/rust-clippy/pull/9867) +* [`string_lit_as_bytes`]: No longer lints in scrutinies of `match` statements + [#10012](https://github.com/rust-lang/rust-clippy/pull/10012) +* [`manual_assert`]: No longer lints in `else if` statements + [#10013](https://github.com/rust-lang/rust-clippy/pull/10013) +* [`needless_return`]: don't lint when using `do yeet` + [#10109](https://github.com/rust-lang/rust-clippy/pull/10109) +* All lints: No longer lint in enum discriminant values when the suggestion won't work in a + const context + [#10008](https://github.com/rust-lang/rust-clippy/pull/10008) +* [`single_element_loop`]: No longer lints, if the loop contains a `break` or `continue` + [#10162](https://github.com/rust-lang/rust-clippy/pull/10162) +* [`uninlined_format_args`]: No longer suggests inlining arguments in `assert!` and + `debug_assert!` macros before 2021 edition + [#10055](https://github.com/rust-lang/rust-clippy/pull/10055) +* [`explicit_counter_loop`]: No longer ignores counter changes after `continue` expressions + [#10094](https://github.com/rust-lang/rust-clippy/pull/10094) +* [`from_over_into`]: No longer lints on opaque types + [#9982](https://github.com/rust-lang/rust-clippy/pull/9982) +* [`expl_impl_clone_on_copy`]: No longer lints on `#[repr(packed)]` structs with generic + parameters + [#10189](https://github.com/rust-lang/rust-clippy/pull/10189) + +### Suggestion Fixes/Improvements + +* [`zero_ptr`]: Now suggests `core::` paths for `no_std` crates + [#10023](https://github.com/rust-lang/rust-clippy/pull/10023) +* [`useless_conversion`]: Now suggests removing calls to `into_iter()` on an expression + implementing `Iterator` + [#10020](https://github.com/rust-lang/rust-clippy/pull/10020) +* [`box_default`]: The suggestion now uses short paths + [#10153](https://github.com/rust-lang/rust-clippy/pull/10153) +* [`default_trait_access`], [`clone_on_copy`]: The suggestion now uses short paths + [#10160](https://github.com/rust-lang/rust-clippy/pull/10160) +* [`comparison_to_empty`]: The suggestion now removes unused deref operations + [#9962](https://github.com/rust-lang/rust-clippy/pull/9962) +* [`manual_let_else`]: Suggestions for or-patterns now include required brackets. + [#9966](https://github.com/rust-lang/rust-clippy/pull/9966) +* [`match_single_binding`]: suggestion no longer introduces unneeded semicolons + [#10060](https://github.com/rust-lang/rust-clippy/pull/10060) +* [`case_sensitive_file_extension_comparisons`]: Now displays a suggestion with `Path` + [#10107](https://github.com/rust-lang/rust-clippy/pull/10107) +* [`empty_structs_with_brackets`]: The suggestion is no longer machine applicable, to avoid + errors when accessing struct fields + [#10141](https://github.com/rust-lang/rust-clippy/pull/10141) +* [`identity_op`]: Removes borrows in the suggestion when needed + [#10004](https://github.com/rust-lang/rust-clippy/pull/10004) +* [`suboptimal_flops`]: The suggestion now includes parentheses when required + [#10113](https://github.com/rust-lang/rust-clippy/pull/10113) +* [`iter_kv_map`]: Now handles `mut` and reference annotations in the suggestion + [#10159](https://github.com/rust-lang/rust-clippy/pull/10159) +* [`redundant_static_lifetimes`]: The suggestion no longer removes `mut` from references + [#10006](https://github.com/rust-lang/rust-clippy/pull/10006) + +### ICE Fixes + +* [`new_ret_no_self`]: Now avoids a stack overflow for `impl Trait` types + [#10086](https://github.com/rust-lang/rust-clippy/pull/10086) +* [`unnecessary_to_owned`]: Now handles compiler generated notes better + [#10027](https://github.com/rust-lang/rust-clippy/pull/10027) + +### Others + +* `SYSROOT` and `--sysroot` can now be set at the same time + [#10149](https://github.com/rust-lang/rust-clippy/pull/10149) ## Rust 1.67 From 5770d40d8ea3ba8d5a41db5e5914b45f32d80a9f Mon Sep 17 00:00:00 2001 From: Michael Schubart Date: Tue, 28 Feb 2023 21:47:07 +0000 Subject: [PATCH 063/362] More compact `use` statements --- clippy_lints/src/collection_is_never_read.rs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/clippy_lints/src/collection_is_never_read.rs b/clippy_lints/src/collection_is_never_read.rs index f20c09a44482..c0c231bb2c79 100644 --- a/clippy_lints/src/collection_is_never_read.rs +++ b/clippy_lints/src/collection_is_never_read.rs @@ -1,20 +1,11 @@ use clippy_utils::diagnostics::span_lint; -use clippy_utils::get_enclosing_block; -use clippy_utils::get_parent_node; -use clippy_utils::path_to_local_id; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::visitors::for_each_expr_with_closures; +use clippy_utils::{get_enclosing_block, get_parent_node, path_to_local_id}; use core::ops::ControlFlow; -use rustc_hir::Block; -use rustc_hir::ExprKind; -use rustc_hir::HirId; -use rustc_hir::Local; -use rustc_hir::Node; -use rustc_hir::PatKind; -use rustc_lint::LateContext; -use rustc_lint::LateLintPass; -use rustc_session::declare_lint_pass; -use rustc_session::declare_tool_lint; +use rustc_hir::{Block, ExprKind, HirId, Local, Node, PatKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::symbol::sym; use rustc_span::Symbol; From cb3bb8a5b5449f330cba8544a30d1f7e1d5f16d3 Mon Sep 17 00:00:00 2001 From: Michael Schubart Date: Tue, 28 Feb 2023 22:01:48 +0000 Subject: [PATCH 064/362] Add tests that show: insert() can be a read or not --- tests/ui/collection_is_never_read.rs | 14 +++++++++++++- tests/ui/collection_is_never_read.stderr | 8 +++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/tests/ui/collection_is_never_read.rs b/tests/ui/collection_is_never_read.rs index 2c37fc212b03..28ec4bf0aeb4 100644 --- a/tests/ui/collection_is_never_read.rs +++ b/tests/ui/collection_is_never_read.rs @@ -1,7 +1,7 @@ #![allow(unused)] #![warn(clippy::collection_is_never_read)] -use std::collections::HashMap; +use std::collections::{HashSet, HashMap}; fn main() {} @@ -114,3 +114,15 @@ fn method_argument_but_not_target() { x.reverse(); my_struct.my_method(&x); } + +fn insert_is_not_a_read() { + let mut x = HashSet::new(); // WARNING + x.insert(5); +} + +fn insert_is_a_read() { + let mut x = HashSet::new(); // Ok + if x.insert(5) { + println!("5 was inserted"); + } +} diff --git a/tests/ui/collection_is_never_read.stderr b/tests/ui/collection_is_never_read.stderr index 43349f550a6c..d66b833c5225 100644 --- a/tests/ui/collection_is_never_read.stderr +++ b/tests/ui/collection_is_never_read.stderr @@ -36,5 +36,11 @@ error: collection is never read LL | let mut x = vec![1, 2, 3]; // WARNING | ^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 6 previous errors +error: collection is never read + --> $DIR/collection_is_never_read.rs:119:5 + | +LL | let mut x = HashSet::new(); // WARNING + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 7 previous errors From fbb7fd59c35095645fafc17ba573817cef68ffcf Mon Sep 17 00:00:00 2001 From: Michael Schubart Date: Tue, 28 Feb 2023 22:08:15 +0000 Subject: [PATCH 065/362] Add test for an interesting edge case --- tests/ui/collection_is_never_read.rs | 9 ++++++++- tests/ui/collection_is_never_read.stderr | 8 +++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/ui/collection_is_never_read.rs b/tests/ui/collection_is_never_read.rs index 28ec4bf0aeb4..8f5ceb06b898 100644 --- a/tests/ui/collection_is_never_read.rs +++ b/tests/ui/collection_is_never_read.rs @@ -1,7 +1,7 @@ #![allow(unused)] #![warn(clippy::collection_is_never_read)] -use std::collections::{HashSet, HashMap}; +use std::collections::{HashMap, HashSet}; fn main() {} @@ -126,3 +126,10 @@ fn insert_is_a_read() { println!("5 was inserted"); } } + +fn not_read_if_return_value_not_used() { + // `is_empty` does not modify the set, so it's a query. But since the return value is not used, the + // lint does not consider it a read here. + let x = vec![1, 2, 3]; // WARNING + x.is_empty(); +} diff --git a/tests/ui/collection_is_never_read.stderr b/tests/ui/collection_is_never_read.stderr index d66b833c5225..7654b74be3d1 100644 --- a/tests/ui/collection_is_never_read.stderr +++ b/tests/ui/collection_is_never_read.stderr @@ -42,5 +42,11 @@ error: collection is never read LL | let mut x = HashSet::new(); // WARNING | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 7 previous errors +error: collection is never read + --> $DIR/collection_is_never_read.rs:133:5 + | +LL | let x = vec![1, 2, 3]; // WARNING + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 8 previous errors From 09566cc3d843abdda23c5856bf725314cb957533 Mon Sep 17 00:00:00 2001 From: Michael Woerister Date: Tue, 21 Feb 2023 15:18:10 +0100 Subject: [PATCH 066/362] Use UnordSet instead of FxHashSet for names_imported_by_glob_use query. --- clippy_lints/src/wildcard_imports.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/clippy_lints/src/wildcard_imports.rs b/clippy_lints/src/wildcard_imports.rs index e4d1ee195c4d..e105452e1c5f 100644 --- a/clippy_lints/src/wildcard_imports.rs +++ b/clippy_lints/src/wildcard_imports.rs @@ -155,14 +155,10 @@ impl LateLintPass<'_> for WildcardImports { ) }; - let imports_string = if used_imports.len() == 1 { - used_imports.iter().next().unwrap().to_string() + let mut imports = used_imports.items().map(ToString::to_string).into_sorted_stable_ord(false); + let imports_string = if imports.len() == 1 { + imports.pop().unwrap() } else { - let mut imports = used_imports - .iter() - .map(ToString::to_string) - .collect::>(); - imports.sort(); if braced_glob { imports.join(", ") } else { From de4a8961dcc9900df8f2610f8cd1c0756f13d622 Mon Sep 17 00:00:00 2001 From: Ryo Yoshida Date: Wed, 1 Mar 2023 18:43:50 +0900 Subject: [PATCH 067/362] Support removing nested `dbg!()`s in `remove_dbg` --- crates/ide-assists/src/handlers/remove_dbg.rs | 134 +++++++++++++++--- crates/ide-assists/src/tests/generated.rs | 4 +- 2 files changed, 116 insertions(+), 22 deletions(-) diff --git a/crates/ide-assists/src/handlers/remove_dbg.rs b/crates/ide-assists/src/handlers/remove_dbg.rs index 52dd670ec2a4..1361cdf00cc6 100644 --- a/crates/ide-assists/src/handlers/remove_dbg.rs +++ b/crates/ide-assists/src/handlers/remove_dbg.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use syntax::{ - ast::{self, AstNode, AstToken}, - match_ast, NodeOrToken, SyntaxElement, TextRange, TextSize, T, + ast::{self, make, AstNode, AstToken}, + match_ast, ted, NodeOrToken, SyntaxElement, TextRange, TextSize, T, }; use crate::{AssistContext, AssistId, AssistKind, Assists}; @@ -12,24 +12,28 @@ use crate::{AssistContext, AssistId, AssistKind, Assists}; // // ``` // fn main() { -// $0dbg!(92); +// let x = $0dbg!(42 * dbg!(4 + 2));$0 // } // ``` // -> // ``` // fn main() { -// 92; +// let x = 42 * (4 + 2); // } // ``` pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let macro_calls = if ctx.has_empty_selection() { - vec![ctx.find_node_at_offset::()?] + vec![ctx.find_node_at_offset::()?] } else { ctx.covering_element() .as_node()? .descendants() .filter(|node| ctx.selection_trimmed().contains_range(node.text_range())) + // When the selection exactly covers the macro call to be removed, `covering_element()` + // returns `ast::MacroCall` instead of its parent `ast::MacroExpr` that we want. So + // first try finding `ast::MacroCall`s and then retrieve their parent. .filter_map(ast::MacroCall::cast) + .filter_map(|it| it.syntax().parent().and_then(ast::MacroExpr::cast)) .collect() }; @@ -44,14 +48,25 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<( "Remove dbg!()", ctx.selection_trimmed(), |builder| { - for (range, text) in replacements { - builder.replace(range, text); + for (range, expr) in replacements { + if let Some(expr) = expr { + builder.replace(range, expr.to_string()); + } else { + builder.delete(range); + } } }, ) } -fn compute_dbg_replacement(macro_call: ast::MacroCall) -> Option<(TextRange, String)> { +/// Returns `None` when either +/// - macro call is not `dbg!()` +/// - any node inside `dbg!()` could not be parsed as an expression +/// - (`macro_expr` has no parent - is that possible?) +/// +/// Returns `Some(_, None)` when the macro call should just be removed. +fn compute_dbg_replacement(macro_expr: ast::MacroExpr) -> Option<(TextRange, Option)> { + let macro_call = macro_expr.macro_call()?; let tt = macro_call.token_tree()?; let r_delim = NodeOrToken::Token(tt.right_delimiter_token()?); if macro_call.path()?.segment()?.name_ref()?.text() != "dbg" @@ -68,20 +83,19 @@ fn compute_dbg_replacement(macro_call: ast::MacroCall) -> Option<(TextRange, Str .map(|mut tokens| syntax::hacks::parse_expr_from_str(&tokens.join(""))) .collect::>>()?; - let macro_expr = ast::MacroExpr::cast(macro_call.syntax().parent()?)?; let parent = macro_expr.syntax().parent()?; Some(match &*input_expressions { // dbg!() [] => { match_ast! { match parent { - ast::StmtList(__) => { + ast::StmtList(_) => { let range = macro_expr.syntax().text_range(); let range = match whitespace_start(macro_expr.syntax().prev_sibling_or_token()) { Some(start) => range.cover_offset(start), None => range, }; - (range, String::new()) + (range, None) }, ast::ExprStmt(it) => { let range = it.syntax().text_range(); @@ -89,19 +103,23 @@ fn compute_dbg_replacement(macro_call: ast::MacroCall) -> Option<(TextRange, Str Some(start) => range.cover_offset(start), None => range, }; - (range, String::new()) + (range, None) }, - _ => (macro_call.syntax().text_range(), "()".to_owned()) + _ => (macro_call.syntax().text_range(), Some(make::expr_unit())), } } } // dbg!(expr0) [expr] => { + // dbg!(expr, &parent); let wrap = match ast::Expr::cast(parent) { Some(parent) => match (expr, parent) { (ast::Expr::CastExpr(_), ast::Expr::CastExpr(_)) => false, ( - ast::Expr::BoxExpr(_) | ast::Expr::PrefixExpr(_) | ast::Expr::RefExpr(_), + ast::Expr::BoxExpr(_) + | ast::Expr::PrefixExpr(_) + | ast::Expr::RefExpr(_) + | ast::Expr::MacroExpr(_), ast::Expr::AwaitExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::CastExpr(_) @@ -112,7 +130,10 @@ fn compute_dbg_replacement(macro_call: ast::MacroCall) -> Option<(TextRange, Str | ast::Expr::TryExpr(_), ) => true, ( - ast::Expr::BinExpr(_) | ast::Expr::CastExpr(_) | ast::Expr::RangeExpr(_), + ast::Expr::BinExpr(_) + | ast::Expr::CastExpr(_) + | ast::Expr::RangeExpr(_) + | ast::Expr::MacroExpr(_), ast::Expr::AwaitExpr(_) | ast::Expr::BinExpr(_) | ast::Expr::CallExpr(_) @@ -129,16 +150,61 @@ fn compute_dbg_replacement(macro_call: ast::MacroCall) -> Option<(TextRange, Str }, None => false, }; - ( - macro_call.syntax().text_range(), - if wrap { format!("({expr})") } else { expr.to_string() }, - ) + let expr = replace_nested_dbgs(expr.clone()); + let expr = if wrap { make::expr_paren(expr) } else { expr.clone_subtree() }; + (macro_call.syntax().text_range(), Some(expr)) } // dbg!(expr0, expr1, ...) - exprs => (macro_call.syntax().text_range(), format!("({})", exprs.iter().format(", "))), + exprs => { + let exprs = exprs.iter().cloned().map(replace_nested_dbgs); + let expr = make::expr_tuple(exprs); + (macro_call.syntax().text_range(), Some(expr)) + } }) } +fn replace_nested_dbgs(expanded: ast::Expr) -> ast::Expr { + if let ast::Expr::MacroExpr(mac) = &expanded { + // Special-case when `expanded` itself is `dbg!()` since we cannot replace the whole tree + // with `ted`. It should be fairly rare as it means the user wrote `dbg!(dbg!(..))` but you + // never know how code ends up being! + let replaced = if let Some((_, expr_opt)) = compute_dbg_replacement(mac.clone()) { + match expr_opt { + Some(expr) => expr, + None => { + stdx::never!("dbg! inside dbg! should not be just removed"); + expanded + } + } + } else { + expanded + }; + + return replaced; + } + + let expanded = expanded.clone_for_update(); + + // We need to collect to avoid mutation during traversal. + let macro_exprs: Vec<_> = + expanded.syntax().descendants().filter_map(ast::MacroExpr::cast).collect(); + + for mac in macro_exprs { + let expr_opt = match compute_dbg_replacement(mac.clone()) { + Some((_, expr)) => expr, + None => continue, + }; + + if let Some(expr) = expr_opt { + ted::replace(mac.syntax(), expr.syntax().clone_for_update()); + } else { + ted::remove(mac.syntax()); + } + } + + expanded +} + fn whitespace_start(it: Option) -> Option { Some(it?.into_token().and_then(ast::Whitespace::cast)?.syntax().text_range().start()) } @@ -287,4 +353,32 @@ fn f() { check_assist_not_applicable(remove_dbg, r#"$0dbg$0!(0)"#); check_assist_not_applicable(remove_dbg, r#"$0dbg!(0$0)"#); } + + #[test] + fn test_nested_dbg() { + check( + r#"$0let x = dbg!(dbg!(dbg!(dbg!(0 + 1)) * 2) + dbg!(3));$0"#, + r#"let x = ((0 + 1) * 2) + 3;"#, + ); + check(r#"$0dbg!(10, dbg!(), dbg!(20, 30))$0"#, r#"(10, (), (20, 30))"#); + } + + #[test] + fn test_multiple_nested_dbg() { + check( + r#" +fn f() { + $0dbg!(); + let x = dbg!(dbg!(dbg!(0 + 1)) + 2) + dbg!(3); + dbg!(10, dbg!(), dbg!(20, 30));$0 +} +"#, + r#" +fn f() { + let x = ((0 + 1) + 2) + 3; + (10, (), (20, 30)); +} +"#, + ); + } } diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index 8a25e1f648ae..524af200130c 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -2006,12 +2006,12 @@ fn doctest_remove_dbg() { "remove_dbg", r#####" fn main() { - $0dbg!(92); + let x = $0dbg!(42 * dbg!(4 + 2));$0 } "#####, r#####" fn main() { - 92; + let x = 42 * (4 + 2); } "#####, ) From 702a83b1a1ef3a8e85a0d4b56fdfc07f50c13f0d Mon Sep 17 00:00:00 2001 From: clubby789 Date: Mon, 27 Feb 2023 01:32:07 +0000 Subject: [PATCH 068/362] Restrict `#[rustc_box]` to `Box::new` calls --- clippy_utils/src/higher.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/clippy_utils/src/higher.rs b/clippy_utils/src/higher.rs index 4604ae5c2c7f..50bef3709309 100644 --- a/clippy_utils/src/higher.rs +++ b/clippy_utils/src/higher.rs @@ -287,15 +287,12 @@ impl<'a> VecArgs<'a> { 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)); - } + if let hir::ExprKind::Call(_, [arg]) = &args[0].kind + && let hir::ExprKind::Array(args) = arg.kind { + Some(VecArgs::Vec(args)) + } else { + None } - - None } else if match_def_path(cx, fun_def_id, &paths::VEC_NEW) && args.is_empty() { Some(VecArgs::Vec(&[])) } else { From 88f2abb8f7a4bb838352d07e58bf41020045931a Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 2 Mar 2023 12:55:41 +0100 Subject: [PATCH 069/362] Don't drop rustc crates in the rustc workspace --- crates/project-model/src/workspace.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs index 45cb89619615..691c9a275d91 100644 --- a/crates/project-model/src/workspace.rs +++ b/crates/project-model/src/workspace.rs @@ -249,10 +249,6 @@ impl ProjectWorkspace { }; let rustc = match rustc_dir { - Some(rustc_dir) if rustc_dir == cargo_toml => { - tracing::info!(rustc_dir = %rustc_dir.display(), "Workspace is the rustc workspace itself, not adding the rustc workspace separately"); - None - } Some(rustc_dir) => { tracing::info!(workspace = %cargo_toml.display(), rustc_dir = %rustc_dir.display(), "Using rustc source"); match CargoWorkspace::fetch_metadata( From 943de55214b510902cf0cfc961776190adbda4cf Mon Sep 17 00:00:00 2001 From: Ryo Yoshida Date: Wed, 1 Mar 2023 21:55:21 +0900 Subject: [PATCH 070/362] Fix typo --- crates/ide-assists/src/handlers/generate_new.rs | 2 +- crates/ide-db/src/lib.rs | 2 +- .../{use_trivial_contructor.rs => use_trivial_constructor.rs} | 0 crates/ide-diagnostics/src/handlers/missing_fields.rs | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename crates/ide-db/src/{use_trivial_contructor.rs => use_trivial_constructor.rs} (100%) diff --git a/crates/ide-assists/src/handlers/generate_new.rs b/crates/ide-assists/src/handlers/generate_new.rs index 8d311262a753..e30a3e942c4c 100644 --- a/crates/ide-assists/src/handlers/generate_new.rs +++ b/crates/ide-assists/src/handlers/generate_new.rs @@ -1,5 +1,5 @@ use ide_db::{ - imports::import_assets::item_for_path_search, use_trivial_contructor::use_trivial_constructor, + imports::import_assets::item_for_path_search, use_trivial_constructor::use_trivial_constructor, }; use itertools::Itertools; use stdx::format_to; diff --git a/crates/ide-db/src/lib.rs b/crates/ide-db/src/lib.rs index 156bbb634e4d..e75694e50fda 100644 --- a/crates/ide-db/src/lib.rs +++ b/crates/ide-db/src/lib.rs @@ -22,7 +22,7 @@ pub mod source_change; pub mod symbol_index; pub mod traits; pub mod ty_filter; -pub mod use_trivial_contructor; +pub mod use_trivial_constructor; pub mod imports { pub mod import_assets; diff --git a/crates/ide-db/src/use_trivial_contructor.rs b/crates/ide-db/src/use_trivial_constructor.rs similarity index 100% rename from crates/ide-db/src/use_trivial_contructor.rs rename to crates/ide-db/src/use_trivial_constructor.rs diff --git a/crates/ide-diagnostics/src/handlers/missing_fields.rs b/crates/ide-diagnostics/src/handlers/missing_fields.rs index 43af4d4f16aa..14039087b3fd 100644 --- a/crates/ide-diagnostics/src/handlers/missing_fields.rs +++ b/crates/ide-diagnostics/src/handlers/missing_fields.rs @@ -5,7 +5,7 @@ use hir::{ }; use ide_db::{ assists::Assist, famous_defs::FamousDefs, imports::import_assets::item_for_path_search, - source_change::SourceChange, use_trivial_contructor::use_trivial_constructor, FxHashMap, + source_change::SourceChange, use_trivial_constructor::use_trivial_constructor, FxHashMap, }; use stdx::format_to; use syntax::{ From 79359cbbcff5049b88e40e3e66b60ef9dda39505 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Wed, 22 Feb 2023 19:51:17 +0400 Subject: [PATCH 071/362] rustc_middle: Remove trait `DefIdTree` This trait was a way to generalize over both `TyCtxt` and `Resolver`, but now `Resolver` has access to `TyCtxt`, so this trait is no longer necessary. --- clippy_lints/src/derivable_impls.rs | 2 +- clippy_lints/src/loops/manual_flatten.rs | 2 +- clippy_lints/src/manual_non_exhaustive.rs | 1 - clippy_lints/src/matches/manual_unwrap_or.rs | 1 - clippy_lints/src/matches/redundant_pattern_match.rs | 2 +- clippy_lints/src/methods/bind_instead_of_map.rs | 1 - clippy_lints/src/methods/chars_cmp.rs | 2 +- clippy_lints/src/methods/option_map_or_none.rs | 1 - clippy_lints/src/missing_doc.rs | 2 +- clippy_lints/src/needless_question_mark.rs | 1 - clippy_lints/src/std_instead_of_core.rs | 1 - .../src/utils/internal_lints/interning_defined_symbol.rs | 2 +- clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs | 2 +- clippy_utils/src/lib.rs | 2 +- clippy_utils/src/ty.rs | 2 +- 15 files changed, 9 insertions(+), 15 deletions(-) diff --git a/clippy_lints/src/derivable_impls.rs b/clippy_lints/src/derivable_impls.rs index f95b8ccf067b..c5f4e943f4f0 100644 --- a/clippy_lints/src/derivable_impls.rs +++ b/clippy_lints/src/derivable_impls.rs @@ -8,7 +8,7 @@ use rustc_hir::{ Body, Expr, ExprKind, GenericArg, Impl, ImplItemKind, Item, ItemKind, Node, PathSegment, QPath, Ty, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{AdtDef, DefIdTree}; +use rustc_middle::ty::AdtDef; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::sym; diff --git a/clippy_lints/src/loops/manual_flatten.rs b/clippy_lints/src/loops/manual_flatten.rs index 8c27c09404b1..1e02a30e35fe 100644 --- a/clippy_lints/src/loops/manual_flatten.rs +++ b/clippy_lints/src/loops/manual_flatten.rs @@ -9,7 +9,7 @@ use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Expr, Pat, PatKind}; use rustc_lint::LateContext; -use rustc_middle::ty::{self, DefIdTree}; +use rustc_middle::ty; use rustc_span::source_map::Span; /// Check for unnecessary `if let` usage in a for loop where only the `Some` or `Ok` variant of the diff --git a/clippy_lints/src/manual_non_exhaustive.rs b/clippy_lints/src/manual_non_exhaustive.rs index 9a84068d4487..0e22485db2c5 100644 --- a/clippy_lints/src/manual_non_exhaustive.rs +++ b/clippy_lints/src/manual_non_exhaustive.rs @@ -8,7 +8,6 @@ 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_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::def_id::{DefId, LocalDefId}; use rustc_span::{sym, Span}; diff --git a/clippy_lints/src/matches/manual_unwrap_or.rs b/clippy_lints/src/matches/manual_unwrap_or.rs index 587c926dc01c..6447899f2b94 100644 --- a/clippy_lints/src/matches/manual_unwrap_or.rs +++ b/clippy_lints/src/matches/manual_unwrap_or.rs @@ -10,7 +10,6 @@ use rustc_hir::def::{DefKind, Res}; use rustc_hir::LangItem::{OptionNone, ResultErr}; use rustc_hir::{Arm, Expr, PatKind}; use rustc_lint::LateContext; -use rustc_middle::ty::DefIdTree; use rustc_span::sym; use super::MANUAL_UNWRAP_OR; diff --git a/clippy_lints/src/matches/redundant_pattern_match.rs b/clippy_lints/src/matches/redundant_pattern_match.rs index 81bebff34c82..df0ea7f5b863 100644 --- a/clippy_lints/src/matches/redundant_pattern_match.rs +++ b/clippy_lints/src/matches/redundant_pattern_match.rs @@ -12,7 +12,7 @@ use rustc_hir::def::{DefKind, Res}; use rustc_hir::LangItem::{self, OptionNone, OptionSome, PollPending, PollReady, ResultErr, ResultOk}; use rustc_hir::{Arm, Expr, ExprKind, Node, Pat, PatKind, QPath, UnOp}; use rustc_lint::LateContext; -use rustc_middle::ty::{self, subst::GenericArgKind, DefIdTree, Ty}; +use rustc_middle::ty::{self, subst::GenericArgKind, Ty}; use rustc_span::{sym, Symbol}; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { diff --git a/clippy_lints/src/methods/bind_instead_of_map.rs b/clippy_lints/src/methods/bind_instead_of_map.rs index 4720a6e6888b..8e1130cf8dfa 100644 --- a/clippy_lints/src/methods/bind_instead_of_map.rs +++ b/clippy_lints/src/methods/bind_instead_of_map.rs @@ -8,7 +8,6 @@ use rustc_hir as hir; use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; use rustc_hir::{LangItem, QPath}; use rustc_lint::LateContext; -use rustc_middle::ty::DefIdTree; use rustc_span::Span; pub(crate) struct OptionAndThenSome; diff --git a/clippy_lints/src/methods/chars_cmp.rs b/clippy_lints/src/methods/chars_cmp.rs index 56b7fbb9d4bc..079df2226d1e 100644 --- a/clippy_lints/src/methods/chars_cmp.rs +++ b/clippy_lints/src/methods/chars_cmp.rs @@ -6,7 +6,7 @@ use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; use rustc_lint::Lint; -use rustc_middle::ty::{self, DefIdTree}; +use rustc_middle::ty; /// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints. pub(super) fn check( diff --git a/clippy_lints/src/methods/option_map_or_none.rs b/clippy_lints/src/methods/option_map_or_none.rs index 3a23ecc50dc1..41ceef19e3a9 100644 --- a/clippy_lints/src/methods/option_map_or_none.rs +++ b/clippy_lints/src/methods/option_map_or_none.rs @@ -6,7 +6,6 @@ use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_lint::LateContext; -use rustc_middle::ty::DefIdTree; use rustc_span::symbol::sym; use super::OPTION_MAP_OR_NONE; diff --git a/clippy_lints/src/missing_doc.rs b/clippy_lints/src/missing_doc.rs index 9659ca8ced2e..5b1f03fc16c2 100644 --- a/clippy_lints/src/missing_doc.rs +++ b/clippy_lints/src/missing_doc.rs @@ -13,7 +13,7 @@ use if_chain::if_chain; use rustc_ast::ast::{self, MetaItem, MetaItemKind}; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::ty::{DefIdTree, Visibility}; +use rustc_middle::ty::Visibility; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::def_id::CRATE_DEF_ID; use rustc_span::source_map::Span; diff --git a/clippy_lints/src/needless_question_mark.rs b/clippy_lints/src/needless_question_mark.rs index 97c8cfbd3eb7..e2a7ba02a043 100644 --- a/clippy_lints/src/needless_question_mark.rs +++ b/clippy_lints/src/needless_question_mark.rs @@ -6,7 +6,6 @@ use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{AsyncGeneratorKind, Block, Body, Expr, ExprKind, GeneratorKind, LangItem, MatchSource, QPath}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::DefIdTree; use rustc_session::{declare_lint_pass, declare_tool_lint}; declare_clippy_lint! { diff --git a/clippy_lints/src/std_instead_of_core.rs b/clippy_lints/src/std_instead_of_core.rs index d6b336bef943..a13bc7a51887 100644 --- a/clippy_lints/src/std_instead_of_core.rs +++ b/clippy_lints/src/std_instead_of_core.rs @@ -2,7 +2,6 @@ use clippy_utils::diagnostics::span_lint_and_help; use rustc_hir::def_id::DefId; use rustc_hir::{def::Res, HirId, Path, PathSegment}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::DefIdTree; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::{sym, symbol::kw, Span}; diff --git a/clippy_lints/src/utils/internal_lints/interning_defined_symbol.rs b/clippy_lints/src/utils/internal_lints/interning_defined_symbol.rs index 688a8b865f32..f8978e30a8e2 100644 --- a/clippy_lints/src/utils/internal_lints/interning_defined_symbol.rs +++ b/clippy_lints/src/utils/internal_lints/interning_defined_symbol.rs @@ -11,7 +11,7 @@ use rustc_hir::def_id::DefId; use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::mir::interpret::ConstValue; -use rustc_middle::ty::{self}; +use rustc_middle::ty; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::symbol::Symbol; diff --git a/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs b/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs index b59ef4086cd8..14ed1368e03e 100644 --- a/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs +++ b/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs @@ -11,7 +11,7 @@ use rustc_hir::def_id::DefId; use rustc_hir::{Expr, ExprKind, Local, Mutability, Node}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::mir::interpret::{Allocation, ConstValue, GlobalAlloc}; -use rustc_middle::ty::{self, DefIdTree, Ty}; +use rustc_middle::ty::{self, Ty}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::symbol::Symbol; use rustc_span::Span; diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index f02f8ecb43d7..bcfedd07ed11 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -104,7 +104,7 @@ use rustc_middle::ty::fast_reject::SimplifiedType::{ PtrSimplifiedType, SliceSimplifiedType, StrSimplifiedType, UintSimplifiedType, }; use rustc_middle::ty::{ - layout::IntegerExt, BorrowKind, ClosureKind, DefIdTree, Ty, TyCtxt, TypeAndMut, TypeVisitableExt, UpvarCapture, + layout::IntegerExt, BorrowKind, ClosureKind, Ty, TyCtxt, TypeAndMut, TypeVisitableExt, UpvarCapture, }; use rustc_middle::ty::{FloatTy, IntTy, UintTy}; use rustc_span::hygiene::{ExpnKind, MacroKind}; diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index 25654e6957b9..41e34eba0ad3 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -16,7 +16,7 @@ use rustc_infer::infer::{ use rustc_lint::LateContext; use rustc_middle::mir::interpret::{ConstValue, Scalar}; use rustc_middle::ty::{ - self, AdtDef, AliasTy, AssocKind, Binder, BoundRegion, DefIdTree, FnSig, IntTy, List, ParamEnv, Predicate, + self, AdtDef, AliasTy, AssocKind, Binder, BoundRegion, FnSig, IntTy, List, ParamEnv, Predicate, PredicateKind, Region, RegionKind, SubstsRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, UintTy, VariantDef, VariantDiscr, }; From 64b54ef9507b42e0e0758c4fda2258c6753964da Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Tue, 28 Feb 2023 12:45:19 +0000 Subject: [PATCH 072/362] Fix array-size-threshold config deserialization error --- book/src/lint_configuration.md | 2 +- clippy_lints/src/lib.rs | 2 +- clippy_lints/src/utils/conf.rs | 2 +- tests/ui/crashes/ice-10044.rs | 3 --- tests/ui/crashes/ice-10044.stderr | 10 ---------- tests/ui/large_stack_arrays.rs | 1 + tests/ui/large_stack_arrays.stderr | 10 +++++++++- 7 files changed, 13 insertions(+), 17 deletions(-) delete mode 100644 tests/ui/crashes/ice-10044.rs delete mode 100644 tests/ui/crashes/ice-10044.stderr diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index 33f2b5c1de99..995dd2f04b1e 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -306,7 +306,7 @@ The maximum number of lines a function or method can have ### array-size-threshold The maximum allowed size for arrays on the stack -**Default Value:** `512000` (`u128`) +**Default Value:** `512000` (`u64`) * [large_stack_arrays](https://rust-lang.github.io/rust-clippy/master/index.html#large_stack_arrays) * [large_const_arrays](https://rust-lang.github.io/rust-clippy/master/index.html#large_const_arrays) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 145cf524652f..c626e0bd9985 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -777,7 +777,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: 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; + let array_size_threshold = u128::from(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)); diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index 5f74de5a2886..1c7f3e96db89 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -334,7 +334,7 @@ define_Conf! { /// Lint: LARGE_STACK_ARRAYS, LARGE_CONST_ARRAYS. /// /// The maximum allowed size for arrays on the stack - (array_size_threshold: u128 = 512_000), + (array_size_threshold: u64 = 512_000), /// Lint: VEC_BOX. /// /// The size of the boxed type in bytes, where boxing in a `Vec` is allowed diff --git a/tests/ui/crashes/ice-10044.rs b/tests/ui/crashes/ice-10044.rs deleted file mode 100644 index 65f38fe71188..000000000000 --- a/tests/ui/crashes/ice-10044.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - [0; usize::MAX]; -} diff --git a/tests/ui/crashes/ice-10044.stderr b/tests/ui/crashes/ice-10044.stderr deleted file mode 100644 index 731f8265ad6c..000000000000 --- a/tests/ui/crashes/ice-10044.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error: statement with no effect - --> $DIR/ice-10044.rs:2:5 - | -LL | [0; usize::MAX]; - | ^^^^^^^^^^^^^^^^ - | - = note: `-D clippy::no-effect` implied by `-D warnings` - -error: aborting due to previous error - diff --git a/tests/ui/large_stack_arrays.rs b/tests/ui/large_stack_arrays.rs index 6790765f803e..99787ffd3d39 100644 --- a/tests/ui/large_stack_arrays.rs +++ b/tests/ui/large_stack_arrays.rs @@ -24,6 +24,7 @@ fn main() { [S { data: [0; 32] }; 5000], [Some(""); 20_000_000], [E::T(0); 5000], + [0u8; usize::MAX], ); let good = ( diff --git a/tests/ui/large_stack_arrays.stderr b/tests/ui/large_stack_arrays.stderr index c7bf941ad009..24e90094982a 100644 --- a/tests/ui/large_stack_arrays.stderr +++ b/tests/ui/large_stack_arrays.stderr @@ -31,5 +31,13 @@ LL | [E::T(0); 5000], | = help: consider allocating on the heap with `vec![E::T(0); 5000].into_boxed_slice()` -error: aborting due to 4 previous errors +error: allocating a local array larger than 512000 bytes + --> $DIR/large_stack_arrays.rs:27:9 + | +LL | [0u8; usize::MAX], + | ^^^^^^^^^^^^^^^^^ + | + = help: consider allocating on the heap with `vec![0u8; usize::MAX].into_boxed_slice()` + +error: aborting due to 5 previous errors From 832b33e6b1e0359130b67f0c83f2def0420a6832 Mon Sep 17 00:00:00 2001 From: bwmf2 Date: Sun, 26 Feb 2023 23:22:50 +0100 Subject: [PATCH 073/362] Force parentheses around `match` expression in binary expression --- compiler/rustc_ast_pretty/src/pprust/state/expr.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs index cacfe9eb2f10..a4d91a956627 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs @@ -244,6 +244,10 @@ impl<'a> State<'a> { (&ast::ExprKind::Let { .. }, _) if !parser::needs_par_as_let_scrutinee(prec) => { parser::PREC_FORCE_PAREN } + // For a binary expression like `(match () { _ => a }) OP b`, the parens are required + // otherwise the parser would interpret `match () { _ => a }` as a statement, + // with the remaining `OP b` not making sense. So we force parens. + (&ast::ExprKind::Match(..), _) => parser::PREC_FORCE_PAREN, _ => left_prec, }; From cdeb0e3e02cc70ca4efb41fa67b55b5722a81121 Mon Sep 17 00:00:00 2001 From: bwmf2 Date: Tue, 28 Feb 2023 06:04:56 +0100 Subject: [PATCH 074/362] Fix UI test --- .../ui/macros/rfc-2011-nicer-assert-messages/all-expr-kinds.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ui/macros/rfc-2011-nicer-assert-messages/all-expr-kinds.rs b/tests/ui/macros/rfc-2011-nicer-assert-messages/all-expr-kinds.rs index b8b6f0846bb4..39ba1714cc70 100644 --- a/tests/ui/macros/rfc-2011-nicer-assert-messages/all-expr-kinds.rs +++ b/tests/ui/macros/rfc-2011-nicer-assert-messages/all-expr-kinds.rs @@ -164,7 +164,7 @@ fn main() { // mac call // match - [ match elem { _ => elem } == 3 ] => "Assertion failed: match elem { _ => elem, } == 3" + [ match elem { _ => elem } == 3 ] => "Assertion failed: (match elem { _ => elem, }) == 3" // ret [ (|| { return elem; })() == 3 ] => "Assertion failed: (|| { return elem; })() == 3" From 219195fc4c98649785154e2c66075be716bc0c01 Mon Sep 17 00:00:00 2001 From: bwmf2 Date: Tue, 28 Feb 2023 06:05:45 +0100 Subject: [PATCH 075/362] Add UI test --- tests/ui/macros/issue-98790.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tests/ui/macros/issue-98790.rs diff --git a/tests/ui/macros/issue-98790.rs b/tests/ui/macros/issue-98790.rs new file mode 100644 index 000000000000..8fe6fc41d10b --- /dev/null +++ b/tests/ui/macros/issue-98790.rs @@ -0,0 +1,24 @@ +// run-pass + +macro_rules! stringify_item { + ($item:item) => { + stringify!($item) + }; +} + +macro_rules! repro { + ($expr:expr) => { + stringify_item! { + pub fn repro() -> bool { + $expr + } + } + }; +} + +fn main() { + assert_eq!( + repro!(match () { () => true } | true), + "pub fn repro() -> bool { (match () { () => true, }) | true }" + ); +} From d3cbedd49eeb3d4495902ba7fc0f593460b18599 Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Sun, 5 Feb 2023 19:27:35 -0500 Subject: [PATCH 076/362] Stabilize movbe target feature --- compiler/rustc_codegen_ssa/src/target_features.rs | 3 +-- compiler/rustc_feature/src/accepted.rs | 4 +++- compiler/rustc_feature/src/active.rs | 1 - 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs index e59fad99ad79..d52e858833f4 100644 --- a/compiler/rustc_codegen_ssa/src/target_features.rs +++ b/compiler/rustc_codegen_ssa/src/target_features.rs @@ -192,7 +192,7 @@ const X86_ALLOWED_FEATURES: &[(&str, Option)] = &[ ("fxsr", None), ("gfni", Some(sym::avx512_target_feature)), ("lzcnt", None), - ("movbe", Some(sym::movbe_target_feature)), + ("movbe", None), ("pclmulqdq", None), ("popcnt", None), ("rdrand", None), @@ -394,7 +394,6 @@ pub fn from_target_feature( Some(sym::sse4a_target_feature) => rust_features.sse4a_target_feature, Some(sym::tbm_target_feature) => rust_features.tbm_target_feature, Some(sym::wasm_target_feature) => rust_features.wasm_target_feature, - Some(sym::movbe_target_feature) => rust_features.movbe_target_feature, Some(sym::rtm_target_feature) => rust_features.rtm_target_feature, Some(sym::ermsb_target_feature) => rust_features.ermsb_target_feature, Some(sym::bpf_target_feature) => rust_features.bpf_target_feature, diff --git a/compiler/rustc_feature/src/accepted.rs b/compiler/rustc_feature/src/accepted.rs index e7b2df34ccca..ac93dd555b7a 100644 --- a/compiler/rustc_feature/src/accepted.rs +++ b/compiler/rustc_feature/src/accepted.rs @@ -90,7 +90,7 @@ declare_features! ( (accepted, clone_closures, "1.26.0", Some(44490), None), /// Allows coercing non capturing closures to function pointers. (accepted, closure_to_fn_coercion, "1.19.0", Some(39817), None), - /// Allows using `cmpxchg16b` from `core::arch::x86_64`. + /// Allows using the CMPXCHG16B target feature. (accepted, cmpxchg16b_target_feature, "CURRENT_RUSTC_VERSION", Some(44839), None), /// Allows usage of the `compile_error!` macro. (accepted, compile_error, "1.20.0", Some(40872), None), @@ -238,6 +238,8 @@ declare_features! ( (accepted, min_const_unsafe_fn, "1.33.0", Some(55607), None), /// Allows using `Self` and associated types in struct expressions and patterns. (accepted, more_struct_aliases, "1.16.0", Some(37544), None), + /// Allows using the MOVBE target feature. + (accepted, movbe_target_feature, "CURRENT_RUSTC_VERSION", Some(44839), None), /// Allows patterns with concurrent by-move and by-ref bindings. /// For example, you can write `Foo(a, ref b)` where `a` is by-move and `b` is by-ref. (accepted, move_ref_pattern, "1.49.0", Some(68354), None), diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs index a01914f969eb..2fa7bceb8bc0 100644 --- a/compiler/rustc_feature/src/active.rs +++ b/compiler/rustc_feature/src/active.rs @@ -259,7 +259,6 @@ declare_features! ( (active, ermsb_target_feature, "1.49.0", Some(44839), None), (active, hexagon_target_feature, "1.27.0", Some(44839), None), (active, mips_target_feature, "1.27.0", Some(44839), None), - (active, movbe_target_feature, "1.34.0", Some(44839), None), (active, powerpc_target_feature, "1.27.0", Some(44839), None), (active, riscv_target_feature, "1.45.0", Some(44839), None), (active, rtm_target_feature, "1.35.0", Some(44839), None), From efbcb99b73d3180f6d667fdad2efdbe6317c30d4 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Thu, 2 Mar 2023 14:29:45 -0500 Subject: [PATCH 077/362] Use `snippet_with_context` more --- .../src/casts/cast_slice_from_raw_parts.rs | 8 +- .../src/default_instead_of_iter_empty.rs | 16 ++- clippy_lints/src/format.rs | 6 +- clippy_lints/src/implicit_saturating_add.rs | 16 ++- clippy_lints/src/instant_subtraction.rs | 13 +- clippy_lints/src/len_zero.rs | 17 +-- clippy_lints/src/manual_bits.rs | 11 +- clippy_lints/src/manual_is_ascii_check.rs | 15 +-- clippy_lints/src/manual_rem_euclid.rs | 8 +- clippy_lints/src/match_result_ok.rs | 25 ++-- clippy_lints/src/neg_multiply.rs | 6 +- .../src/non_octal_unix_permissions.rs | 2 + clippy_lints/src/swap.rs | 111 ++++++++++-------- tests/ui/match_result_ok.fixed | 2 +- tests/ui/match_result_ok.stderr | 2 +- tests/ui/swap.fixed | 6 +- tests/ui/swap.stderr | 24 ++-- 17 files changed, 159 insertions(+), 129 deletions(-) diff --git a/clippy_lints/src/casts/cast_slice_from_raw_parts.rs b/clippy_lints/src/casts/cast_slice_from_raw_parts.rs index 627b795d6edd..1233c632a794 100644 --- a/clippy_lints/src/casts/cast_slice_from_raw_parts.rs +++ b/clippy_lints/src/casts/cast_slice_from_raw_parts.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::snippet_with_context; use clippy_utils::{match_def_path, paths}; use if_chain::if_chain; use rustc_errors::Applicability; @@ -34,6 +34,8 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, 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 let Some(rpk) = raw_parts_kind(cx, fun_def_id); + let ctxt = expr.span.ctxt(); + if cast_expr.span.ctxt() == ctxt; then { let func = match rpk { RawPartsKind::Immutable => "from_raw_parts", @@ -41,8 +43,8 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, }; let span = expr.span; let mut applicability = Applicability::MachineApplicable; - let ptr = snippet_with_applicability(cx, ptr_arg.span, "ptr", &mut applicability); - let len = snippet_with_applicability(cx, len_arg.span, "len", &mut applicability); + let ptr = snippet_with_context(cx, ptr_arg.span, ctxt, "ptr", &mut applicability).0; + let len = snippet_with_context(cx, len_arg.span, ctxt, "len", &mut applicability).0; span_lint_and_sugg( cx, CAST_SLICE_FROM_RAW_PARTS, diff --git a/clippy_lints/src/default_instead_of_iter_empty.rs b/clippy_lints/src/default_instead_of_iter_empty.rs index 1ad929864b2a..f296b80d283d 100644 --- a/clippy_lints/src/default_instead_of_iter_empty.rs +++ b/clippy_lints/src/default_instead_of_iter_empty.rs @@ -1,11 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::last_path_segment; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::snippet_with_context; use clippy_utils::{match_def_path, paths}; use rustc_errors::Applicability; use rustc_hir::{def, Expr, ExprKind, GenericArg, QPath, TyKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::SyntaxContext; declare_clippy_lint! { /// ### What it does @@ -38,9 +39,11 @@ impl<'tcx> LateLintPass<'tcx> for DefaultIterEmpty { && let QPath::Resolved(None, path) = ty_path && let def::Res::Def(_, def_id) = &path.res && match_def_path(cx, *def_id, &paths::ITER_EMPTY) + && let ctxt = expr.span.ctxt() + && ty.span.ctxt() == ctxt { let mut applicability = Applicability::MachineApplicable; - let sugg = make_sugg(cx, ty_path, &mut applicability); + let sugg = make_sugg(cx, ty_path, ctxt, &mut applicability); span_lint_and_sugg( cx, DEFAULT_INSTEAD_OF_ITER_EMPTY, @@ -54,14 +57,19 @@ impl<'tcx> LateLintPass<'tcx> for DefaultIterEmpty { } } -fn make_sugg(cx: &LateContext<'_>, ty_path: &rustc_hir::QPath<'_>, applicability: &mut Applicability) -> String { +fn make_sugg( + cx: &LateContext<'_>, + ty_path: &rustc_hir::QPath<'_>, + ctxt: SyntaxContext, + applicability: &mut Applicability, +) -> String { if let Some(last) = last_path_segment(ty_path).args && let Some(iter_ty) = last.args.iter().find_map(|arg| match arg { GenericArg::Type(ty) => Some(ty), _ => None, }) { - format!("std::iter::empty::<{}>()", snippet_with_applicability(cx, iter_ty.span, "..", applicability)) + format!("std::iter::empty::<{}>()", snippet_with_context(cx, iter_ty.span, ctxt, "..", applicability).0) } else { "std::iter::empty()".to_owned() } diff --git a/clippy_lints/src/format.rs b/clippy_lints/src/format.rs index d0fab6949604..8040938c6263 100644 --- a/clippy_lints/src/format.rs +++ b/clippy_lints/src/format.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::macros::{root_macro_call_first_node, FormatArgsExpn}; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::snippet_with_context; use clippy_utils::sugg::Sugg; use if_chain::if_chain; use rustc_errors::Applicability; @@ -84,9 +84,9 @@ impl<'tcx> LateLintPass<'tcx> for UselessFormat { _ => false, }; let sugg = if is_new_string { - snippet_with_applicability(cx, value.span, "..", &mut applicability).into_owned() + snippet_with_context(cx, value.span, call_site.ctxt(), "..", &mut applicability).0.into_owned() } else { - let sugg = Sugg::hir_with_applicability(cx, value, "", &mut applicability); + let sugg = Sugg::hir_with_context(cx, value, call_site.ctxt(), "", &mut applicability); format!("{}.to_string()", sugg.maybe_par()) }; span_useless_format(cx, call_site, sugg, applicability); diff --git a/clippy_lints/src/implicit_saturating_add.rs b/clippy_lints/src/implicit_saturating_add.rs index 6e19343931ec..57e6caa87111 100644 --- a/clippy_lints/src/implicit_saturating_add.rs +++ b/clippy_lints/src/implicit_saturating_add.rs @@ -1,7 +1,7 @@ use clippy_utils::consts::{constant, Constant}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::get_parent_expr; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::snippet_with_context; use if_chain::if_chain; use rustc_ast::ast::{LitIntType, LitKind}; use rustc_errors::Applicability; @@ -55,6 +55,9 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingAdd { if let ExprKind::AssignOp(op1, target, value) = ex.kind; let ty = cx.typeck_results().expr_ty(target); if Some(c) == get_int_max(ty); + let ctxt = expr.span.ctxt(); + if ex.span.ctxt() == ctxt; + if expr1.span.ctxt() == ctxt; if clippy_utils::SpanlessEq::new(cx).eq_expr(l, target); if BinOpKind::Add == op1.node; if let ExprKind::Lit(ref lit) = value.kind; @@ -62,8 +65,15 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingAdd { if block.expr.is_none(); then { let mut app = Applicability::MachineApplicable; - let code = snippet_with_applicability(cx, target.span, "_", &mut app); - let sugg = if let Some(parent) = get_parent_expr(cx, expr) && let ExprKind::If(_cond, _then, Some(else_)) = parent.kind && else_.hir_id == expr.hir_id {format!("{{{code} = {code}.saturating_add(1); }}")} else {format!("{code} = {code}.saturating_add(1);")}; + let code = snippet_with_context(cx, target.span, ctxt, "_", &mut app).0; + let sugg = if let Some(parent) = get_parent_expr(cx, expr) + && let ExprKind::If(_cond, _then, Some(else_)) = parent.kind + && else_.hir_id == expr.hir_id + { + format!("{{{code} = {code}.saturating_add(1); }}") + } else { + format!("{code} = {code}.saturating_add(1);") + }; span_lint_and_sugg(cx, IMPLICIT_SATURATING_ADD, expr.span, "manual saturating add detected", "use instead", sugg, app); } } diff --git a/clippy_lints/src/instant_subtraction.rs b/clippy_lints/src/instant_subtraction.rs index 668110c7cc08..34e9991582c0 100644 --- a/clippy_lints/src/instant_subtraction.rs +++ b/clippy_lints/src/instant_subtraction.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::{self, span_lint_and_sugg}; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::source; +use clippy_utils::source::snippet_with_context; use clippy_utils::sugg::Sugg; use clippy_utils::ty; use rustc_errors::Applicability; @@ -161,14 +161,9 @@ fn print_unchecked_duration_subtraction_sugg( ) { let mut applicability = Applicability::MachineApplicable; - let left_expr = - source::snippet_with_applicability(cx, left_expr.span, "std::time::Instant::now()", &mut applicability); - let right_expr = source::snippet_with_applicability( - cx, - right_expr.span, - "std::time::Duration::from_secs(1)", - &mut applicability, - ); + let ctxt = expr.span.ctxt(); + let left_expr = snippet_with_context(cx, left_expr.span, ctxt, "", &mut applicability).0; + let right_expr = snippet_with_context(cx, right_expr.span, ctxt, "", &mut applicability).0; diagnostics::span_lint_and_sugg( cx, diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs index e13bc47973b1..6c32393dc013 100644 --- a/clippy_lints/src/len_zero.rs +++ b/clippy_lints/src/len_zero.rs @@ -1,13 +1,13 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then}; -use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{get_item_name, get_parent_as_impl, is_lint_allowed, peel_ref_operators}; +use clippy_utils::source::snippet_with_context; +use clippy_utils::{get_item_name, get_parent_as_impl, is_lint_allowed, peel_ref_operators, sugg::Sugg}; use if_chain::if_chain; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::def_id::DefIdSet; use rustc_hir::{ def_id::DefId, AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, ImplItem, ImplItemKind, ImplicitSelfKind, Item, - ItemKind, Mutability, Node, TraitItemRef, TyKind, UnOp, + ItemKind, Mutability, Node, TraitItemRef, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, AssocKind, FnSig, Ty}; @@ -16,7 +16,6 @@ use rustc_span::{ source_map::{Span, Spanned, Symbol}, symbol::sym, }; -use std::borrow::Cow; declare_clippy_lint! { /// ### What it does @@ -431,7 +430,7 @@ fn check_len( &format!("using `{op}is_empty` is clearer and more explicit"), format!( "{op}{}.is_empty()", - snippet_with_applicability(cx, receiver.span, "_", &mut applicability) + snippet_with_context(cx, receiver.span, span.ctxt(), "_", &mut applicability).0, ), applicability, ); @@ -444,13 +443,7 @@ fn check_empty_expr(cx: &LateContext<'_>, span: Span, lit1: &Expr<'_>, lit2: &Ex let mut applicability = Applicability::MachineApplicable; let lit1 = peel_ref_operators(cx, lit1); - let mut lit_str = snippet_with_applicability(cx, lit1.span, "_", &mut applicability); - - // Wrap the expression in parentheses if it's a deref expression. Otherwise operator precedence will - // cause the code to dereference boolean(won't compile). - if let ExprKind::Unary(UnOp::Deref, _) = lit1.kind { - lit_str = Cow::from(format!("({lit_str})")); - } + let lit_str = Sugg::hir_with_context(cx, lit1, span.ctxt(), "_", &mut applicability).maybe_par(); span_lint_and_sugg( cx, diff --git a/clippy_lints/src/manual_bits.rs b/clippy_lints/src/manual_bits.rs index 462d73cf0b97..bc815dc4a260 100644 --- a/clippy_lints/src/manual_bits.rs +++ b/clippy_lints/src/manual_bits.rs @@ -1,11 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::get_parent_expr; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::snippet_with_context; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind, GenericArg, QPath}; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; use rustc_middle::ty::{self, Ty}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::sym; @@ -55,13 +56,17 @@ impl<'tcx> LateLintPass<'tcx> for ManualBits { if_chain! { if let ExprKind::Binary(bin_op, left_expr, right_expr) = expr.kind; if let BinOpKind::Mul = &bin_op.node; + if !in_external_macro(cx.sess(), expr.span); + let ctxt = expr.span.ctxt(); + if left_expr.span.ctxt() == ctxt; + if right_expr.span.ctxt() == ctxt; 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 ty_snip = snippet_with_context(cx, real_ty.span, ctxt, "..", &mut app).0; let sugg = create_sugg(cx, expr, format!("{ty_snip}::BITS")); span_lint_and_sugg( diff --git a/clippy_lints/src/manual_is_ascii_check.rs b/clippy_lints/src/manual_is_ascii_check.rs index 2fd32c009eaa..31264261f5db 100644 --- a/clippy_lints/src/manual_is_ascii_check.rs +++ b/clippy_lints/src/manual_is_ascii_check.rs @@ -1,5 +1,5 @@ use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::{diagnostics::span_lint_and_sugg, higher, in_constant, macros::root_macro_call, source::snippet}; +use clippy_utils::{diagnostics::span_lint_and_sugg, higher, in_constant, macros::root_macro_call, sugg::Sugg}; use rustc_ast::ast::RangeLimits; use rustc_ast::LitKind::{Byte, Char}; use rustc_errors::Applicability; @@ -115,15 +115,8 @@ fn check_is_ascii(cx: &LateContext<'_>, span: Span, recv: &Expr<'_>, range: &Cha CharRange::Otherwise => None, } { let default_snip = ".."; - // `snippet_with_applicability` may set applicability to `MaybeIncorrect` for - // macro span, so we check applicability manually by comparing `recv` is not default. - let recv = snippet(cx, recv.span, default_snip); - - let applicability = if recv == default_snip { - Applicability::HasPlaceholders - } else { - Applicability::MachineApplicable - }; + let mut app = Applicability::MachineApplicable; + let recv = Sugg::hir_with_context(cx, recv, span.ctxt(), default_snip, &mut app).maybe_par(); span_lint_and_sugg( cx, @@ -132,7 +125,7 @@ fn check_is_ascii(cx: &LateContext<'_>, span: Span, recv: &Expr<'_>, range: &Cha "manual check for common ascii range", "try", format!("{recv}.{sugg}()"), - applicability, + app, ); } } diff --git a/clippy_lints/src/manual_rem_euclid.rs b/clippy_lints/src/manual_rem_euclid.rs index 38f41d077c16..aafee92713fe 100644 --- a/clippy_lints/src/manual_rem_euclid.rs +++ b/clippy_lints/src/manual_rem_euclid.rs @@ -1,7 +1,7 @@ use clippy_utils::consts::{constant_full_int, FullInt}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::snippet_with_context; use clippy_utils::{in_constant, path_to_local}; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind, Node, TyKind}; @@ -60,12 +60,16 @@ impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid { return; } + // (x % c + c) % c if let ExprKind::Binary(op1, expr1, right) = expr.kind && op1.node == BinOpKind::Rem + && let ctxt = expr.span.ctxt() + && expr1.span.ctxt() == ctxt && let Some(const1) = check_for_unsigned_int_constant(cx, right) && let ExprKind::Binary(op2, left, right) = expr1.kind && op2.node == BinOpKind::Add && let Some((const2, expr2)) = check_for_either_unsigned_int_constant(cx, left, right) + && expr2.span.ctxt() == ctxt && let ExprKind::Binary(op3, expr3, right) = expr2.kind && op3.node == BinOpKind::Rem && let Some(const3) = check_for_unsigned_int_constant(cx, right) @@ -86,7 +90,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid { }; let mut app = Applicability::MachineApplicable; - let rem_of = snippet_with_applicability(cx, expr3.span, "_", &mut app); + let rem_of = snippet_with_context(cx, expr3.span, ctxt, "_", &mut app).0; span_lint_and_sugg( cx, MANUAL_REM_EUCLID, diff --git a/clippy_lints/src/match_result_ok.rs b/clippy_lints/src/match_result_ok.rs index a020282d234f..6ec9784038c3 100644 --- a/clippy_lints/src/match_result_ok.rs +++ b/clippy_lints/src/match_result_ok.rs @@ -1,11 +1,11 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::higher; -use clippy_utils::method_chain_args; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::is_res_lang_ctor; +use clippy_utils::source::snippet_with_context; use clippy_utils::ty::is_type_diagnostic_item; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, PatKind, QPath}; +use rustc_hir::{Expr, ExprKind, LangItem, PatKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::sym; @@ -58,17 +58,18 @@ impl<'tcx> LateLintPass<'tcx> for MatchResultOk { }; if_chain! { - if let ExprKind::MethodCall(ok_path, result_types_0, ..) = let_expr.kind; //check is expr.ok() has type Result.ok(, _) - if let PatKind::TupleStruct(QPath::Resolved(_, x), y, _) = let_pat.kind; //get operation - if method_chain_args(let_expr, &["ok"]).is_some(); //test to see if using ok() method use std::marker::Sized; - if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(result_types_0), sym::Result); - if rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_path(x, false)) == "Some"; - + if let ExprKind::MethodCall(ok_path, recv, [], ..) = let_expr.kind; //check is expr.ok() has type Result.ok(, _) + if let PatKind::TupleStruct(ref pat_path, [ok_pat], _) = let_pat.kind; //get operation + if ok_path.ident.as_str() == "ok"; + if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result); + if is_res_lang_ctor(cx, cx.qpath_res(pat_path, let_pat.hir_id), LangItem::OptionSome); + let ctxt = expr.span.ctxt(); + if let_expr.span.ctxt() == ctxt; + if let_pat.span.ctxt() == ctxt; then { - let mut applicability = Applicability::MachineApplicable; - let some_expr_string = snippet_with_applicability(cx, y[0].span, "", &mut applicability); - let trimmed_ok = snippet_with_applicability(cx, let_expr.span.until(ok_path.ident.span), "", &mut applicability); + let some_expr_string = snippet_with_context(cx, ok_pat.span, ctxt, "", &mut applicability).0; + let trimmed_ok = snippet_with_context(cx, recv.span, ctxt, "", &mut applicability).0; let sugg = format!( "{ifwhile} let Ok({some_expr_string}) = {}", trimmed_ok.trim().trim_end_matches('.'), diff --git a/clippy_lints/src/neg_multiply.rs b/clippy_lints/src/neg_multiply.rs index fb9a4abd0b4b..ed3e2c6e7f49 100644 --- a/clippy_lints/src/neg_multiply.rs +++ b/clippy_lints/src/neg_multiply.rs @@ -1,6 +1,6 @@ use clippy_utils::consts::{self, Constant}; use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::snippet_with_context; use clippy_utils::sugg::has_enclosing_paren; use if_chain::if_chain; use rustc_ast::util::parser::PREC_PREFIX; @@ -60,8 +60,8 @@ fn check_mul(cx: &LateContext<'_>, span: Span, lit: &Expr<'_>, exp: &Expr<'_>) { then { let mut applicability = Applicability::MachineApplicable; - let snip = snippet_with_applicability(cx, exp.span, "..", &mut applicability); - let suggestion = if exp.precedence().order() < PREC_PREFIX && !has_enclosing_paren(&snip) { + let (snip, from_macro) = snippet_with_context(cx, exp.span, span.ctxt(), "..", &mut applicability); + let suggestion = if !from_macro && exp.precedence().order() < PREC_PREFIX && !has_enclosing_paren(&snip) { format!("-({snip})") } else { format!("-{snip}") diff --git a/clippy_lints/src/non_octal_unix_permissions.rs b/clippy_lints/src/non_octal_unix_permissions.rs index 2ecb04874842..e1de494eb41c 100644 --- a/clippy_lints/src/non_octal_unix_permissions.rs +++ b/clippy_lints/src/non_octal_unix_permissions.rs @@ -53,6 +53,7 @@ impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions { || is_type_diagnostic_item(cx, obj_ty, sym::DirBuilder))) || (path.ident.name == sym!(set_mode) && match_type(cx, obj_ty, &paths::PERMISSIONS)); if let ExprKind::Lit(_) = param.kind; + if param.span.ctxt() == expr.span.ctxt(); then { let Some(snip) = snippet_opt(cx, param.span) else { @@ -71,6 +72,7 @@ impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions { if let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id(); if match_def_path(cx, def_id, &paths::PERMISSIONS_FROM_MODE); if let ExprKind::Lit(_) = param.kind; + if param.span.ctxt() == expr.span.ctxt(); if let Some(snip) = snippet_opt(cx, param.span); if !snip.starts_with("0o"); then { diff --git a/clippy_lints/src/swap.rs b/clippy_lints/src/swap.rs index 0f062cecf886..1aeac724ab13 100644 --- a/clippy_lints/src/swap.rs +++ b/clippy_lints/src/swap.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::snippet_with_context; use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{can_mut_borrow_both, eq_expr_value, in_constant, std_or_core}; @@ -10,6 +10,7 @@ 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_span::SyntaxContext; use rustc_span::{sym, symbol::Ident, Span}; declare_clippy_lint! { @@ -80,43 +81,45 @@ impl<'tcx> LateLintPass<'tcx> for Swap { } fn generate_swap_warning(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>, span: Span, is_xor_based: bool) { + let ctxt = span.ctxt(); let mut applicability = Applicability::MachineApplicable; if !can_mut_borrow_both(cx, e1, e2) { - if let ExprKind::Index(lhs1, idx1) = e1.kind { - if let ExprKind::Index(lhs2, idx2) = e2.kind { - if eq_expr_value(cx, lhs1, lhs2) { - let ty = cx.typeck_results().expr_ty(lhs1).peel_refs(); + if let ExprKind::Index(lhs1, idx1) = e1.kind + && let ExprKind::Index(lhs2, idx2) = e2.kind + && eq_expr_value(cx, lhs1, lhs2) + && e1.span.ctxt() == ctxt + && e2.span.ctxt() == ctxt + { + let ty = cx.typeck_results().expr_ty(lhs1).peel_refs(); - if matches!(ty.kind(), ty::Slice(_)) - || matches!(ty.kind(), ty::Array(_, _)) - || is_type_diagnostic_item(cx, ty, sym::Vec) - || is_type_diagnostic_item(cx, ty, sym::VecDeque) - { - let slice = Sugg::hir_with_applicability(cx, lhs1, "", &mut applicability); - span_lint_and_sugg( - cx, - MANUAL_SWAP, - span, - &format!("this looks like you are swapping elements of `{slice}` manually"), - "try", - format!( - "{}.swap({}, {})", - slice.maybe_par(), - snippet_with_applicability(cx, idx1.span, "..", &mut applicability), - snippet_with_applicability(cx, idx2.span, "..", &mut applicability), - ), - applicability, - ); - } - } + if matches!(ty.kind(), ty::Slice(_)) + || matches!(ty.kind(), ty::Array(_, _)) + || is_type_diagnostic_item(cx, ty, sym::Vec) + || is_type_diagnostic_item(cx, ty, sym::VecDeque) + { + let slice = Sugg::hir_with_applicability(cx, lhs1, "", &mut applicability); + span_lint_and_sugg( + cx, + MANUAL_SWAP, + span, + &format!("this looks like you are swapping elements of `{slice}` manually"), + "try", + format!( + "{}.swap({}, {});", + slice.maybe_par(), + snippet_with_context(cx, idx1.span, ctxt, "..", &mut applicability).0, + snippet_with_context(cx, idx2.span, ctxt, "..", &mut applicability).0, + ), + applicability, + ); } } return; } - let first = Sugg::hir_with_applicability(cx, e1, "..", &mut applicability); - let second = Sugg::hir_with_applicability(cx, e2, "..", &mut applicability); + let first = Sugg::hir_with_context(cx, e1, ctxt, "..", &mut applicability); + let second = Sugg::hir_with_context(cx, e2, ctxt, "..", &mut applicability); let Some(sugg) = std_or_core(cx) else { return }; span_lint_and_then( @@ -128,7 +131,7 @@ fn generate_swap_warning(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>, spa diag.span_suggestion( span, "try", - format!("{sugg}::mem::swap({}, {})", first.mut_addr(), second.mut_addr()), + format!("{sugg}::mem::swap({}, {});", first.mut_addr(), second.mut_addr()), applicability, ); if !is_xor_based { @@ -144,19 +147,19 @@ fn check_manual_swap(cx: &LateContext<'_>, block: &Block<'_>) { return; } - for w in block.stmts.windows(3) { + for [s1, s2, s3] in block.stmts.array_windows::<3>() { if_chain! { // let t = foo(); - if let StmtKind::Local(tmp) = w[0].kind; + if let StmtKind::Local(tmp) = s1.kind; if let Some(tmp_init) = tmp.init; if let PatKind::Binding(.., ident, None) = tmp.pat.kind; // foo() = bar(); - if let StmtKind::Semi(first) = w[1].kind; + if let StmtKind::Semi(first) = s2.kind; if let ExprKind::Assign(lhs1, rhs1, _) = first.kind; // bar() = t; - if let StmtKind::Semi(second) = w[2].kind; + if let StmtKind::Semi(second) = s3.kind; if let ExprKind::Assign(lhs2, rhs2, _) = second.kind; if let ExprKind::Path(QPath::Resolved(None, rhs2)) = rhs2.kind; if rhs2.segments.len() == 1; @@ -164,8 +167,15 @@ fn check_manual_swap(cx: &LateContext<'_>, block: &Block<'_>) { if ident.name == rhs2.segments[0].ident.name; if eq_expr_value(cx, tmp_init, lhs1); if eq_expr_value(cx, rhs1, lhs2); + + let ctxt = s1.span.ctxt(); + if s2.span.ctxt() == ctxt; + if s3.span.ctxt() == ctxt; + if first.span.ctxt() == ctxt; + if second.span.ctxt() == ctxt; + then { - let span = w[0].span.to(second.span); + let span = s1.span.to(s3.span); generate_swap_warning(cx, lhs1, lhs2, span, false); } } @@ -246,17 +256,20 @@ fn parse<'a, 'hir>(stmt: &'a Stmt<'hir>) -> Option<(ExprOrIdent<'hir>, &'a Expr< /// Implementation of the xor case for `MANUAL_SWAP` lint. fn check_xor_swap(cx: &LateContext<'_>, block: &Block<'_>) { - for window in block.stmts.windows(3) { + for [s1, s2, s3] in block.stmts.array_windows::<3>() { if_chain! { - if let Some((lhs0, rhs0)) = extract_sides_of_xor_assign(&window[0]); - if let Some((lhs1, rhs1)) = extract_sides_of_xor_assign(&window[1]); - if let Some((lhs2, rhs2)) = extract_sides_of_xor_assign(&window[2]); + let ctxt = s1.span.ctxt(); + if let Some((lhs0, rhs0)) = extract_sides_of_xor_assign(s1, ctxt); + if let Some((lhs1, rhs1)) = extract_sides_of_xor_assign(s2, ctxt); + if let Some((lhs2, rhs2)) = extract_sides_of_xor_assign(s3, ctxt); if eq_expr_value(cx, lhs0, rhs1); if eq_expr_value(cx, lhs2, rhs1); if eq_expr_value(cx, lhs1, rhs0); if eq_expr_value(cx, lhs1, rhs2); + if s2.span.ctxt() == ctxt; + if s3.span.ctxt() == ctxt; then { - let span = window[0].span.to(window[2].span); + let span = s1.span.to(s3.span); generate_swap_warning(cx, lhs0, rhs0, span, true); } }; @@ -264,9 +277,12 @@ fn check_xor_swap(cx: &LateContext<'_>, block: &Block<'_>) { } /// Returns the lhs and rhs of an xor assignment statement. -fn extract_sides_of_xor_assign<'a, 'hir>(stmt: &'a Stmt<'hir>) -> Option<(&'a Expr<'hir>, &'a Expr<'hir>)> { - if let StmtKind::Semi(expr) = stmt.kind { - if let ExprKind::AssignOp( +fn extract_sides_of_xor_assign<'a, 'hir>( + stmt: &'a Stmt<'hir>, + ctxt: SyntaxContext, +) -> Option<(&'a Expr<'hir>, &'a Expr<'hir>)> { + if let StmtKind::Semi(expr) = stmt.kind + && let ExprKind::AssignOp( Spanned { node: BinOpKind::BitXor, .. @@ -274,9 +290,10 @@ fn extract_sides_of_xor_assign<'a, 'hir>(stmt: &'a Stmt<'hir>) -> Option<(&'a Ex lhs, rhs, ) = expr.kind - { - return Some((lhs, rhs)); - } + && expr.span.ctxt() == ctxt + { + Some((lhs, rhs)) + } else { + None } - None } diff --git a/tests/ui/match_result_ok.fixed b/tests/ui/match_result_ok.fixed index 8b91b9854a04..10ae1ee5245e 100644 --- a/tests/ui/match_result_ok.fixed +++ b/tests/ui/match_result_ok.fixed @@ -16,7 +16,7 @@ fn str_to_int_ok(x: &str) -> i32 { #[rustfmt::skip] fn strange_some_no_else(x: &str) -> i32 { { - if let Ok(y) = x . parse() { + if let Ok(y) = x . parse() { return y; }; 0 diff --git a/tests/ui/match_result_ok.stderr b/tests/ui/match_result_ok.stderr index 98a95705ca59..cbdc56aa28c4 100644 --- a/tests/ui/match_result_ok.stderr +++ b/tests/ui/match_result_ok.stderr @@ -18,7 +18,7 @@ LL | if let Some(y) = x . parse() . ok () { | help: consider matching on `Ok(y)` and removing the call to `ok` instead | -LL | if let Ok(y) = x . parse() { +LL | if let Ok(y) = x . parse() { | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: matching on `Some` with `ok()` is redundant diff --git a/tests/ui/swap.fixed b/tests/ui/swap.fixed index fa89706a815a..04008c0d9b31 100644 --- a/tests/ui/swap.fixed +++ b/tests/ui/swap.fixed @@ -65,19 +65,19 @@ fn xor_swap_locals() { // This is an xor-based swap of local variables. let mut a = 0; let mut b = 1; - std::mem::swap(&mut a, &mut b) + std::mem::swap(&mut a, &mut b); } fn xor_field_swap() { // This is an xor-based swap of fields in a struct. let mut bar = Bar { a: 0, b: 1 }; - std::mem::swap(&mut bar.a, &mut bar.b) + std::mem::swap(&mut bar.a, &mut bar.b); } fn xor_slice_swap() { // This is an xor-based swap of a slice let foo = &mut [1, 2]; - foo.swap(0, 1) + foo.swap(0, 1); } fn xor_no_swap() { diff --git a/tests/ui/swap.stderr b/tests/ui/swap.stderr index f0acbfe253f4..825c9261e198 100644 --- a/tests/ui/swap.stderr +++ b/tests/ui/swap.stderr @@ -4,7 +4,7 @@ error: this looks like you are swapping `bar.a` and `bar.b` manually LL | / let temp = bar.a; LL | | bar.a = bar.b; LL | | bar.b = temp; - | |________________^ help: try: `std::mem::swap(&mut bar.a, &mut bar.b)` + | |_________________^ help: try: `std::mem::swap(&mut bar.a, &mut bar.b);` | = note: or maybe you should use `std::mem::replace`? = note: `-D clippy::manual-swap` implied by `-D warnings` @@ -15,7 +15,7 @@ error: this looks like you are swapping elements of `foo` manually LL | / let temp = foo[0]; LL | | foo[0] = foo[1]; LL | | foo[1] = temp; - | |_________________^ help: try: `foo.swap(0, 1)` + | |__________________^ help: try: `foo.swap(0, 1);` error: this looks like you are swapping elements of `foo` manually --> $DIR/swap.rs:46:5 @@ -23,7 +23,7 @@ error: this looks like you are swapping elements of `foo` manually LL | / let temp = foo[0]; LL | | foo[0] = foo[1]; LL | | foo[1] = temp; - | |_________________^ help: try: `foo.swap(0, 1)` + | |__________________^ help: try: `foo.swap(0, 1);` error: this looks like you are swapping elements of `foo` manually --> $DIR/swap.rs:65:5 @@ -31,7 +31,7 @@ error: this looks like you are swapping elements of `foo` manually LL | / let temp = foo[0]; LL | | foo[0] = foo[1]; LL | | foo[1] = temp; - | |_________________^ help: try: `foo.swap(0, 1)` + | |__________________^ help: try: `foo.swap(0, 1);` error: this looks like you are swapping `a` and `b` manually --> $DIR/swap.rs:76:5 @@ -39,7 +39,7 @@ error: this looks like you are swapping `a` and `b` manually LL | / a ^= b; LL | | b ^= a; LL | | a ^= b; - | |___________^ help: try: `std::mem::swap(&mut a, &mut b)` + | |___________^ help: try: `std::mem::swap(&mut a, &mut b);` error: this looks like you are swapping `bar.a` and `bar.b` manually --> $DIR/swap.rs:84:5 @@ -47,7 +47,7 @@ error: this looks like you are swapping `bar.a` and `bar.b` manually LL | / bar.a ^= bar.b; LL | | bar.b ^= bar.a; LL | | bar.a ^= bar.b; - | |___________________^ help: try: `std::mem::swap(&mut bar.a, &mut bar.b)` + | |___________________^ help: try: `std::mem::swap(&mut bar.a, &mut bar.b);` error: this looks like you are swapping elements of `foo` manually --> $DIR/swap.rs:92:5 @@ -55,7 +55,7 @@ error: this looks like you are swapping elements of `foo` manually LL | / foo[0] ^= foo[1]; LL | | foo[1] ^= foo[0]; LL | | foo[0] ^= foo[1]; - | |_____________________^ help: try: `foo.swap(0, 1)` + | |_____________________^ help: try: `foo.swap(0, 1);` error: this looks like you are swapping `foo[0][1]` and `bar[1][0]` manually --> $DIR/swap.rs:121:5 @@ -63,7 +63,7 @@ error: this looks like you are swapping `foo[0][1]` and `bar[1][0]` manually LL | / let temp = foo[0][1]; LL | | foo[0][1] = bar[1][0]; LL | | bar[1][0] = temp; - | |____________________^ help: try: `std::mem::swap(&mut foo[0][1], &mut bar[1][0])` + | |_____________________^ help: try: `std::mem::swap(&mut foo[0][1], &mut bar[1][0]);` | = note: or maybe you should use `std::mem::replace`? @@ -74,7 +74,7 @@ LL | ; let t = a; | _______^ LL | | a = b; LL | | b = t; - | |_________^ help: try: `std::mem::swap(&mut a, &mut b)` + | |__________^ help: try: `std::mem::swap(&mut a, &mut b);` | = note: or maybe you should use `std::mem::replace`? @@ -85,7 +85,7 @@ LL | ; let t = c.0; | _______^ LL | | c.0 = a; LL | | a = t; - | |_________^ help: try: `std::mem::swap(&mut c.0, &mut a)` + | |__________^ help: try: `std::mem::swap(&mut c.0, &mut a);` | = note: or maybe you should use `std::mem::replace`? @@ -95,7 +95,7 @@ error: this looks like you are swapping `b` and `a` manually LL | / let t = b; LL | | b = a; LL | | a = t; - | |_________^ help: try: `std::mem::swap(&mut b, &mut a)` + | |__________^ help: try: `std::mem::swap(&mut b, &mut a);` | = note: or maybe you should use `std::mem::replace`? @@ -151,7 +151,7 @@ error: this looks like you are swapping `s.0.x` and `s.0.y` manually LL | / let t = s.0.x; LL | | s.0.x = s.0.y; LL | | s.0.y = t; - | |_____________^ help: try: `std::mem::swap(&mut s.0.x, &mut s.0.y)` + | |______________^ help: try: `std::mem::swap(&mut s.0.x, &mut s.0.y);` | = note: or maybe you should use `std::mem::replace`? From bda2af71c61d1fd558e1a96a1425bc55020c45d0 Mon Sep 17 00:00:00 2001 From: Ryo Yoshida Date: Thu, 2 Mar 2023 23:51:12 +0900 Subject: [PATCH 078/362] feat: allow `generate_function` to generate in different local crate --- crates/hir/src/lib.rs | 2 +- .../src/handlers/generate_function.rs | 148 +++++++++++++++--- crates/ide-completion/src/context.rs | 11 +- crates/ide-db/src/helpers.rs | 10 +- 4 files changed, 140 insertions(+), 31 deletions(-) diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index c64106d3afde..26f678e5a9b7 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -489,7 +489,7 @@ impl Module { } /// Finds nearest non-block ancestor `Module` (`self` included). - fn nearest_non_block_module(self, db: &dyn HirDatabase) -> Module { + pub fn nearest_non_block_module(self, db: &dyn HirDatabase) -> Module { let mut id = self.id; loop { let def_map = id.def_map(db.upcast()); diff --git a/crates/ide-assists/src/handlers/generate_function.rs b/crates/ide-assists/src/handlers/generate_function.rs index 45b27a63ce26..eef037f98754 100644 --- a/crates/ide-assists/src/handlers/generate_function.rs +++ b/crates/ide-assists/src/handlers/generate_function.rs @@ -5,6 +5,7 @@ use ide_db::{ base_db::FileId, defs::{Definition, NameRefClass}, famous_defs::FamousDefs, + helpers::is_editable_crate, path_transform::PathTransform, FxHashMap, FxHashSet, RootDatabase, SnippetCap, }; @@ -65,6 +66,13 @@ fn gen_fn(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let fn_name = &*name_ref.text(); let TargetInfo { target_module, adt_name, target, file, insert_offset } = fn_target_info(ctx, path, &call, fn_name)?; + + if let Some(m) = target_module { + if !is_editable_crate(m.krate(), ctx.db()) { + return None; + } + } + let function_builder = FunctionBuilder::from_call(ctx, &call, fn_name, target_module, target)?; let text_range = call.syntax().text_range(); let label = format!("Generate {} function", function_builder.fn_name); @@ -141,12 +149,11 @@ fn gen_method(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let receiver_ty = ctx.sema.type_of_expr(&call.receiver()?)?.original().strip_references(); let adt = receiver_ty.as_adt()?; - let current_module = ctx.sema.scope(call.syntax())?.module(); let target_module = adt.module(ctx.sema.db); - - if current_module.krate() != target_module.krate() { + if !is_editable_crate(target_module.krate(), ctx.db()) { return None; } + let (impl_, file) = get_adt_source(ctx, &adt, fn_name.text().as_str())?; let (target, insert_offset) = get_method_target(ctx, &impl_, &adt)?; @@ -253,7 +260,7 @@ struct FunctionBuilder { params: ast::ParamList, ret_type: Option, should_focus_return_type: bool, - needs_pub: bool, + visibility: Visibility, is_async: bool, } @@ -264,12 +271,14 @@ impl FunctionBuilder { ctx: &AssistContext<'_>, call: &ast::CallExpr, fn_name: &str, - target_module: Option, + target_module: Option, target: GeneratedFunctionTarget, ) -> Option { - let needs_pub = target_module.is_some(); let target_module = target_module.or_else(|| ctx.sema.scope(target.syntax()).map(|it| it.module()))?; + + let current_module = ctx.sema.scope(call.syntax())?.module(); + let visibility = calculate_necessary_visibility(current_module, target_module, ctx); let fn_name = make::name(fn_name); let mut necessary_generic_params = FxHashSet::default(); let params = fn_args( @@ -300,7 +309,7 @@ impl FunctionBuilder { params, ret_type, should_focus_return_type, - needs_pub, + visibility, is_async, }) } @@ -313,8 +322,9 @@ impl FunctionBuilder { target_module: Module, target: GeneratedFunctionTarget, ) -> Option { - let needs_pub = - !module_is_descendant(&ctx.sema.scope(call.syntax())?.module(), &target_module, ctx); + let current_module = ctx.sema.scope(call.syntax())?.module(); + let visibility = calculate_necessary_visibility(current_module, target_module, ctx); + let fn_name = make::name(&name.text()); let mut necessary_generic_params = FxHashSet::default(); necessary_generic_params.extend(receiver_ty.generic_params(ctx.db())); @@ -346,7 +356,7 @@ impl FunctionBuilder { params, ret_type, should_focus_return_type, - needs_pub, + visibility, is_async, }) } @@ -354,7 +364,11 @@ impl FunctionBuilder { fn render(self, is_method: bool) -> FunctionTemplate { let placeholder_expr = make::ext::expr_todo(); let fn_body = make::block_expr(vec![], Some(placeholder_expr)); - let visibility = if self.needs_pub { Some(make::visibility_pub_crate()) } else { None }; + let visibility = match self.visibility { + Visibility::None => None, + Visibility::Crate => Some(make::visibility_pub_crate()), + Visibility::Pub => Some(make::visibility_pub()), + }; let mut fn_def = make::fn_( visibility, self.fn_name, @@ -527,7 +541,7 @@ impl GeneratedFunctionTarget { /// Computes parameter list for the generated function. fn fn_args( ctx: &AssistContext<'_>, - target_module: hir::Module, + target_module: Module, call: ast::CallableExpr, necessary_generic_params: &mut FxHashSet, ) -> Option { @@ -957,13 +971,13 @@ fn fn_arg_name(sema: &Semantics<'_, RootDatabase>, arg_expr: &ast::Expr) -> Stri fn fn_arg_type( ctx: &AssistContext<'_>, - target_module: hir::Module, + target_module: Module, fn_arg: &ast::Expr, generic_params: &mut FxHashSet, ) -> String { fn maybe_displayed_type( ctx: &AssistContext<'_>, - target_module: hir::Module, + target_module: Module, fn_arg: &ast::Expr, generic_params: &mut FxHashSet, ) -> Option { @@ -1048,16 +1062,29 @@ fn next_space_for_fn_in_impl(impl_: &ast::Impl) -> Option) -> bool { - if module == ans { - return true; +#[derive(Clone, Copy)] +enum Visibility { + None, + Crate, + Pub, +} + +fn calculate_necessary_visibility( + current_module: Module, + target_module: Module, + ctx: &AssistContext<'_>, +) -> Visibility { + let db = ctx.db(); + let current_module = current_module.nearest_non_block_module(db); + let target_module = target_module.nearest_non_block_module(db); + + if target_module.krate() != current_module.krate() { + Visibility::Pub + } else if current_module.path_to_root(db).contains(&target_module) { + Visibility::None + } else { + Visibility::Crate } - for c in ans.children(ctx.sema.db) { - if module_is_descendant(module, &c, ctx) { - return true; - } - } - false } // This is never intended to be used as a generic graph strucuture. If there's ever another need of @@ -2656,4 +2683,79 @@ fn main() { ", ) } + + #[test] + fn applicable_in_different_local_crate() { + check_assist( + generate_function, + r" +//- /lib.rs crate:lib new_source_root:local +fn dummy() {} +//- /main.rs crate:main deps:lib new_source_root:local +fn main() { + lib::foo$0(); +} +", + r" +fn dummy() {} + +pub fn foo() ${0:-> _} { + todo!() +} +", + ); + } + + #[test] + fn applicable_in_different_local_crate_method() { + check_assist( + generate_function, + r" +//- /lib.rs crate:lib new_source_root:local +pub struct S; +//- /main.rs crate:main deps:lib new_source_root:local +fn main() { + lib::S.foo$0(); +} +", + r" +pub struct S; +impl S { + pub fn foo(&self) ${0:-> _} { + todo!() + } +} +", + ); + } + + #[test] + fn not_applicable_in_different_library_crate() { + check_assist_not_applicable( + generate_function, + r" +//- /lib.rs crate:lib new_source_root:library +fn dummy() {} +//- /main.rs crate:main deps:lib new_source_root:local +fn main() { + lib::foo$0(); +} +", + ); + } + + #[test] + fn not_applicable_in_different_library_crate_method() { + check_assist_not_applicable( + generate_function, + r" +//- /lib.rs crate:lib new_source_root:library +pub struct S; +//- /main.rs crate:main deps:lib new_source_root:local +fn main() { + lib::S.foo$0(); +} +", + ); + } } diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index ea54068b0f8b..d5dda6dae4f6 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -6,13 +6,13 @@ mod tests; use std::iter; -use base_db::SourceDatabaseExt; use hir::{ HasAttrs, Local, Name, PathResolution, ScopeDef, Semantics, SemanticsScope, Type, TypeInfo, }; use ide_db::{ base_db::{FilePosition, SourceDatabase}, famous_defs::FamousDefs, + helpers::is_editable_crate, FxHashMap, FxHashSet, RootDatabase, }; use syntax::{ @@ -525,10 +525,11 @@ impl<'a> CompletionContext<'a> { return Visible::No; } // If the definition location is editable, also show private items - let root_file = defining_crate.root_file(self.db); - let source_root_id = self.db.file_source_root(root_file); - let is_editable = !self.db.source_root(source_root_id).is_library; - return if is_editable { Visible::Editable } else { Visible::No }; + return if is_editable_crate(defining_crate, self.db) { + Visible::Editable + } else { + Visible::No + }; } if self.is_doc_hidden(attrs, defining_crate) { diff --git a/crates/ide-db/src/helpers.rs b/crates/ide-db/src/helpers.rs index 6e56efe344d7..8e3b1eef15b2 100644 --- a/crates/ide-db/src/helpers.rs +++ b/crates/ide-db/src/helpers.rs @@ -2,8 +2,8 @@ use std::collections::VecDeque; -use base_db::FileId; -use hir::{ItemInNs, ModuleDef, Name, Semantics}; +use base_db::{FileId, SourceDatabaseExt}; +use hir::{Crate, ItemInNs, ModuleDef, Name, Semantics}; use syntax::{ ast::{self, make}, AstToken, SyntaxKind, SyntaxToken, TokenAtOffset, @@ -103,3 +103,9 @@ pub fn lint_eq_or_in_group(lint: &str, lint_is: &str) -> bool { false } } + +pub fn is_editable_crate(krate: Crate, db: &RootDatabase) -> bool { + let root_file = krate.root_file(db); + let source_root_id = db.file_source_root(root_file); + !db.source_root(source_root_id).is_library +} From fc2b395e0095236e3c312974fa1e52a467c26324 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 28 Feb 2023 15:13:45 +0100 Subject: [PATCH 079/362] Show pattern mismatch diagnostics --- crates/hir-expand/src/lib.rs | 9 ++ crates/hir-ty/src/infer.rs | 9 +- crates/hir-ty/src/infer/pat.rs | 7 +- crates/hir-ty/src/tests.rs | 29 +----- crates/hir-ty/src/tests/patterns.rs | 16 +++ crates/hir/src/diagnostics.rs | 3 +- crates/hir/src/lib.rs | 18 +++- .../src/handlers/type_mismatch.rs | 97 +++++++++++++------ 8 files changed, 117 insertions(+), 71 deletions(-) diff --git a/crates/hir-expand/src/lib.rs b/crates/hir-expand/src/lib.rs index a52716cc02c2..e4719237a2aa 100644 --- a/crates/hir-expand/src/lib.rs +++ b/crates/hir-expand/src/lib.rs @@ -771,6 +771,15 @@ impl InFile> { } } +impl InFile> { + pub fn transpose(self) -> Either, InFile> { + match self.value { + Either::Left(l) => Either::Left(InFile::new(self.file_id, l)), + Either::Right(r) => Either::Right(InFile::new(self.file_id, r)), + } + } +} + impl<'a> InFile<&'a SyntaxNode> { pub fn ancestors_with_macros( self, diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index c77a5e073757..6790be64c5c7 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -389,18 +389,15 @@ impl InferenceResult { pub fn type_mismatch_for_pat(&self, pat: PatId) -> Option<&TypeMismatch> { self.type_mismatches.get(&pat.into()) } + pub fn type_mismatches(&self) -> impl Iterator { + self.type_mismatches.iter().map(|(expr_or_pat, mismatch)| (*expr_or_pat, mismatch)) + } pub fn expr_type_mismatches(&self) -> impl Iterator { self.type_mismatches.iter().filter_map(|(expr_or_pat, mismatch)| match *expr_or_pat { ExprOrPatId::ExprId(expr) => Some((expr, mismatch)), _ => None, }) } - pub fn pat_type_mismatches(&self) -> impl Iterator { - self.type_mismatches.iter().filter_map(|(expr_or_pat, mismatch)| match *expr_or_pat { - ExprOrPatId::PatId(pat) => Some((pat, mismatch)), - _ => None, - }) - } } impl Index for InferenceResult { diff --git a/crates/hir-ty/src/infer/pat.rs b/crates/hir-ty/src/infer/pat.rs index 2c935733c061..8b381f0d1fce 100644 --- a/crates/hir-ty/src/infer/pat.rs +++ b/crates/hir-ty/src/infer/pat.rs @@ -196,12 +196,7 @@ impl<'a> InferenceContext<'a> { Pat::Ref { pat, mutability } => { let mutability = lower_to_chalk_mutability(*mutability); let expectation = match expected.as_reference() { - Some((inner_ty, _lifetime, exp_mut)) => { - if mutability != exp_mut { - // FIXME: emit type error? - } - inner_ty.clone() - } + Some((inner_ty, _lifetime, exp_mut)) => inner_ty.clone(), _ => self.result.standard_types.unknown.clone(), }; let subty = self.infer_pat(*pat, &expectation, default_bm); diff --git a/crates/hir-ty/src/tests.rs b/crates/hir-ty/src/tests.rs index ba5d9c241267..ab848a18eb38 100644 --- a/crates/hir-ty/src/tests.rs +++ b/crates/hir-ty/src/tests.rs @@ -191,30 +191,11 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour } } - for (pat, mismatch) in inference_result.pat_type_mismatches() { - let node = match pat_node(&body_source_map, pat, &db) { - Some(value) => value, - None => continue, - }; - let range = node.as_ref().original_file_range(&db); - let actual = format!( - "expected {}, got {}", - mismatch.expected.display_test(&db), - mismatch.actual.display_test(&db) - ); - match mismatches.remove(&range) { - Some(annotation) => assert_eq!(actual, annotation), - None => format_to!(unexpected_type_mismatches, "{:?}: {}\n", range.range, actual), - } - } - for (expr, mismatch) in inference_result.expr_type_mismatches() { - let node = match body_source_map.expr_syntax(expr) { - Ok(sp) => { - let root = db.parse_or_expand(sp.file_id).unwrap(); - sp.map(|ptr| ptr.to_node(&root).syntax().clone()) - } - Err(SyntheticSyntax) => continue, - }; + for (expr_or_pat, mismatch) in inference_result.type_mismatches() { + let Some(node) = (match expr_or_pat { + hir_def::expr::ExprOrPatId::ExprId(expr) => expr_node(&body_source_map, expr, &db), + hir_def::expr::ExprOrPatId::PatId(pat) => pat_node(&body_source_map, pat, &db), + }) else { continue; }; let range = node.as_ref().original_file_range(&db); let actual = format!( "expected {}, got {}", diff --git a/crates/hir-ty/src/tests/patterns.rs b/crates/hir-ty/src/tests/patterns.rs index 9333e2693522..aa1b2a1d9be0 100644 --- a/crates/hir-ty/src/tests/patterns.rs +++ b/crates/hir-ty/src/tests/patterns.rs @@ -1092,3 +1092,19 @@ fn my_fn(foo: ...) {} "#, ); } + +#[test] +fn ref_pat_mutability() { + check( + r#" +fn foo() { + let &() = &(); + let &mut () = &mut (); + let &mut () = &(); + //^^^^^^^ expected &(), got &mut () + let &() = &mut (); + //^^^ expected &mut (), got &() +} +"#, + ); +} diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index 54d43fa8dc73..3b2591e8a106 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -178,8 +178,7 @@ pub struct MissingMatchArms { #[derive(Debug)] pub struct TypeMismatch { - // FIXME: add mismatches in patterns as well - pub expr: InFile>, + pub expr_or_pat: Either>, InFile>>, pub expected: Type, pub actual: Type, } diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index c64106d3afde..c103244b4e6a 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -1413,14 +1413,22 @@ impl DefWithBody { } } } - for (expr, mismatch) in infer.expr_type_mismatches() { - let expr = match source_map.expr_syntax(expr) { - Ok(expr) => expr, - Err(SyntheticSyntax) => continue, + for (pat_or_expr, mismatch) in infer.type_mismatches() { + let expr_or_pat = match pat_or_expr { + ExprOrPatId::ExprId(expr) => source_map.expr_syntax(expr).map(Either::Left), + ExprOrPatId::PatId(pat) => source_map.pat_syntax(pat).map(Either::Right), }; + let expr_or_pat = match expr_or_pat { + Ok(Either::Left(expr)) => Either::Left(expr), + Ok(Either::Right(InFile { file_id, value: Either::Left(pat) })) => { + Either::Right(InFile { file_id, value: pat }) + } + Ok(Either::Right(_)) | Err(SyntheticSyntax) => continue, + }; + acc.push( TypeMismatch { - expr, + expr_or_pat, expected: Type::new(db, DefWithBodyId::from(self), mismatch.expected.clone()), actual: Type::new(db, DefWithBodyId::from(self), mismatch.actual.clone()), } diff --git a/crates/ide-diagnostics/src/handlers/type_mismatch.rs b/crates/ide-diagnostics/src/handlers/type_mismatch.rs index 2adae165e4d2..948ca4f63287 100644 --- a/crates/ide-diagnostics/src/handlers/type_mismatch.rs +++ b/crates/ide-diagnostics/src/handlers/type_mismatch.rs @@ -1,8 +1,9 @@ -use hir::{db::AstDatabase, HirDisplay, Type}; +use either::Either; +use hir::{db::AstDatabase, HirDisplay, InFile, Type}; use ide_db::{famous_defs::FamousDefs, source_change::SourceChange}; use syntax::{ ast::{self, BlockExpr, ExprStmt}, - AstNode, + AstNode, AstPtr, }; use text_edit::TextEdit; @@ -10,19 +11,23 @@ use crate::{adjusted_display_range, fix, Assist, Diagnostic, DiagnosticsContext} // Diagnostic: type-mismatch // -// This diagnostic is triggered when the type of an expression does not match +// This diagnostic is triggered when the type of an expression or pattern does not match // the expected type. pub(crate) fn type_mismatch(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Diagnostic { - let display_range = adjusted_display_range::( - ctx, - d.expr.clone().map(|it| it.into()), - &|block| { - let r_curly_range = block.stmt_list()?.r_curly_token()?.text_range(); - cov_mark::hit!(type_mismatch_on_block); - Some(r_curly_range) - }, - ); - + let display_range = match &d.expr_or_pat { + Either::Left(expr) => adjusted_display_range::( + ctx, + expr.clone().map(|it| it.into()), + &|block| { + let r_curly_range = block.stmt_list()?.r_curly_token()?.text_range(); + cov_mark::hit!(type_mismatch_on_block); + Some(r_curly_range) + }, + ), + Either::Right(pat) => { + ctx.sema.diagnostics_display_range(pat.clone().map(|it| it.into())).range + } + }; let mut diag = Diagnostic::new( "type-mismatch", format!( @@ -42,10 +47,15 @@ pub(crate) fn type_mismatch(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Option> { let mut fixes = Vec::new(); - add_reference(ctx, d, &mut fixes); - add_missing_ok_or_some(ctx, d, &mut fixes); - remove_semicolon(ctx, d, &mut fixes); - str_ref_to_owned(ctx, d, &mut fixes); + match &d.expr_or_pat { + Either::Left(expr_ptr) => { + add_reference(ctx, d, expr_ptr, &mut fixes); + add_missing_ok_or_some(ctx, d, expr_ptr, &mut fixes); + remove_semicolon(ctx, d, expr_ptr, &mut fixes); + str_ref_to_owned(ctx, d, expr_ptr, &mut fixes); + } + Either::Right(_pat_ptr) => (), + } if fixes.is_empty() { None @@ -53,13 +63,22 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Option, + d: &hir::TypeMismatch, + expr_ptr: &InFile>, + acc: &mut Vec, +) -> Option<()> { + None +} fn add_reference( ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch, + expr_ptr: &InFile>, acc: &mut Vec, ) -> Option<()> { - let range = ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range; + let range = ctx.sema.diagnostics_display_range(expr_ptr.clone().map(|it| it.into())).range; let (_, mutability) = d.expected.as_reference()?; let actual_with_ref = Type::reference(&d.actual, mutability); @@ -71,7 +90,7 @@ fn add_reference( let edit = TextEdit::insert(range.start(), ampersands); let source_change = - SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit); + SourceChange::from_text_edit(expr_ptr.file_id.original_file(ctx.sema.db), edit); acc.push(fix("add_reference_here", "Add reference here", source_change, range)); Some(()) } @@ -79,10 +98,11 @@ fn add_reference( fn add_missing_ok_or_some( ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch, + expr_ptr: &InFile>, acc: &mut Vec, ) -> Option<()> { - let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?; - let expr = d.expr.value.to_node(&root); + let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id)?; + let expr = expr_ptr.value.to_node(&root); let expr_range = expr.syntax().text_range(); let scope = ctx.sema.scope(expr.syntax())?; @@ -109,7 +129,7 @@ fn add_missing_ok_or_some( builder.insert(expr.syntax().text_range().start(), format!("{variant_name}(")); builder.insert(expr.syntax().text_range().end(), ")".to_string()); let source_change = - SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), builder.finish()); + SourceChange::from_text_edit(expr_ptr.file_id.original_file(ctx.sema.db), builder.finish()); let name = format!("Wrap in {variant_name}"); acc.push(fix("wrap_in_constructor", &name, source_change, expr_range)); Some(()) @@ -118,10 +138,11 @@ fn add_missing_ok_or_some( fn remove_semicolon( ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch, + expr_ptr: &InFile>, acc: &mut Vec, ) -> Option<()> { - let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?; - let expr = d.expr.value.to_node(&root); + let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id)?; + let expr = expr_ptr.value.to_node(&root); if !d.actual.is_unit() { return None; } @@ -136,7 +157,7 @@ fn remove_semicolon( let edit = TextEdit::delete(semicolon_range); let source_change = - SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit); + SourceChange::from_text_edit(expr_ptr.file_id.original_file(ctx.sema.db), edit); acc.push(fix("remove_semicolon", "Remove this semicolon", source_change, semicolon_range)); Some(()) @@ -145,24 +166,26 @@ fn remove_semicolon( fn str_ref_to_owned( ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch, + expr_ptr: &InFile>, acc: &mut Vec, ) -> Option<()> { let expected = d.expected.display(ctx.sema.db); let actual = d.actual.display(ctx.sema.db); + // FIXME do this properly if expected.to_string() != "String" || actual.to_string() != "&str" { return None; } - let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?; - let expr = d.expr.value.to_node(&root); + let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id)?; + let expr = expr_ptr.value.to_node(&root); let expr_range = expr.syntax().text_range(); let to_owned = format!(".to_owned()"); let edit = TextEdit::insert(expr.syntax().text_range().end(), to_owned); let source_change = - SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit); + SourceChange::from_text_edit(expr_ptr.file_id.original_file(ctx.sema.db), edit); acc.push(fix("str_ref_to_owned", "Add .to_owned() here", source_change, expr_range)); Some(()) @@ -592,6 +615,24 @@ fn f() -> i32 { let _ = x + y; } //^ error: expected i32, found () +"#, + ); + } + + #[test] + fn type_mismatch_pat_smoke_test() { + check_diagnostics( + r#" +fn f() { + let &() = &mut (); + //^^^ error: expected &mut (), found &() + match &() { + &9 => () + //^^ error: expected &(), found &i32 + //^ error: expected (), found i32 + //^ error: expected (), found i32 + } +} "#, ); } From ec273c3d12b7393f6b81e793ce1c7abd59e3dc67 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 3 Mar 2023 10:23:20 +0100 Subject: [PATCH 080/362] Split pattern inference into more functions --- crates/hir-def/src/resolver.rs | 2 +- crates/hir-ty/src/infer.rs | 40 +-- crates/hir-ty/src/infer/expr.rs | 47 ++-- crates/hir-ty/src/infer/pat.rs | 300 +++++++++++++++-------- crates/hir-ty/src/tests/patterns.rs | 12 +- crates/ide/src/inlay_hints/adjustment.rs | 5 +- 6 files changed, 234 insertions(+), 172 deletions(-) diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs index 36d8b24e9c33..fdb236c11146 100644 --- a/crates/hir-def/src/resolver.rs +++ b/crates/hir-def/src/resolver.rs @@ -85,7 +85,7 @@ pub enum ResolveValueResult { Partial(TypeNs, usize), } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum ValueNs { ImplSelf(ImplId), LocalBinding(PatId), diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 6790be64c5c7..f229bf2f644d 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -144,44 +144,6 @@ impl Default for BindingMode { } } -/// Used to generalize patterns and assignee expressions. -trait PatLike: Into + Copy { - type BindingMode: Copy; - - fn infer( - this: &mut InferenceContext<'_>, - id: Self, - expected_ty: &Ty, - default_bm: Self::BindingMode, - ) -> Ty; -} - -impl PatLike for ExprId { - type BindingMode = (); - - fn infer( - this: &mut InferenceContext<'_>, - id: Self, - expected_ty: &Ty, - _: Self::BindingMode, - ) -> Ty { - this.infer_assignee_expr(id, expected_ty) - } -} - -impl PatLike for PatId { - type BindingMode = BindingMode; - - fn infer( - this: &mut InferenceContext<'_>, - id: Self, - expected_ty: &Ty, - default_bm: Self::BindingMode, - ) -> Ty { - this.infer_pat(id, expected_ty, default_bm) - } -} - #[derive(Debug)] pub(crate) struct InferOk { value: T, @@ -581,7 +543,7 @@ impl<'a> InferenceContext<'a> { let ty = self.insert_type_vars(ty); let ty = self.normalize_associated_types_in(ty); - self.infer_pat(*pat, &ty, BindingMode::default()); + self.infer_top_pat(*pat, &ty); } let error_ty = &TypeRef::Error; let return_ty = if data.has_async_kw() { diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index e169cbef4979..a186ae836d07 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -25,7 +25,9 @@ use syntax::ast::RangeOp; use crate::{ autoderef::{self, Autoderef}, consteval, - infer::{coerce::CoerceMany, find_continuable, BreakableKind}, + infer::{ + coerce::CoerceMany, find_continuable, pat::contains_explicit_ref_binding, BreakableKind, + }, lower::{ const_or_path_to_chalk, generic_arg_to_chalk, lower_to_chalk_mutability, ParamLoweringMode, }, @@ -39,8 +41,8 @@ use crate::{ }; use super::{ - coerce::auto_deref_adjust_steps, find_breakable, BindingMode, BreakableContext, Diverges, - Expectation, InferenceContext, InferenceDiagnostic, TypeMismatch, + coerce::auto_deref_adjust_steps, find_breakable, BreakableContext, Diverges, Expectation, + InferenceContext, InferenceDiagnostic, TypeMismatch, }; impl<'a> InferenceContext<'a> { @@ -111,7 +113,7 @@ impl<'a> InferenceContext<'a> { } &Expr::Let { pat, expr } => { let input_ty = self.infer_expr(expr, &Expectation::none()); - self.infer_pat(pat, &input_ty, BindingMode::default()); + self.infer_top_pat(pat, &input_ty); self.result.standard_types.bool_.clone() } Expr::Block { statements, tail, label, id: _ } => { @@ -223,7 +225,7 @@ impl<'a> InferenceContext<'a> { let pat_ty = self.resolve_associated_type(into_iter_ty, self.resolve_iterator_item()); - self.infer_pat(pat, &pat_ty, BindingMode::default()); + self.infer_top_pat(pat, &pat_ty); self.with_breakable_ctx(BreakableKind::Loop, self.err_ty(), label, |this| { this.infer_expr(body, &Expectation::HasType(TyBuilder::unit())); }); @@ -298,7 +300,7 @@ impl<'a> InferenceContext<'a> { // Now go through the argument patterns for (arg_pat, arg_ty) in args.iter().zip(sig_tys) { - self.infer_pat(*arg_pat, &arg_ty, BindingMode::default()); + self.infer_top_pat(*arg_pat, &arg_ty); } let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); @@ -395,7 +397,8 @@ impl<'a> InferenceContext<'a> { for arm in arms.iter() { self.diverges = Diverges::Maybe; - let _pat_ty = self.infer_pat(arm.pat, &input_ty, BindingMode::default()); + let input_ty = self.resolve_ty_shallow(&input_ty); + let _pat_ty = self.infer_top_pat(arm.pat, &input_ty); if let Some(guard_expr) = arm.guard { self.infer_expr( guard_expr, @@ -1142,27 +1145,33 @@ impl<'a> InferenceContext<'a> { let decl_ty = type_ref .as_ref() .map(|tr| self.make_ty(tr)) - .unwrap_or_else(|| self.err_ty()); + .unwrap_or_else(|| self.table.new_type_var()); - // Always use the declared type when specified - let mut ty = decl_ty.clone(); - - if let Some(expr) = initializer { - let actual_ty = - self.infer_expr_coerce(*expr, &Expectation::has_type(decl_ty.clone())); - if decl_ty.is_unknown() { - ty = actual_ty; + let ty = if let Some(expr) = initializer { + let ty = if contains_explicit_ref_binding(&self.body, *pat) { + self.infer_expr(*expr, &Expectation::has_type(decl_ty.clone())) + } else { + self.infer_expr_coerce(*expr, &Expectation::has_type(decl_ty.clone())) + }; + if type_ref.is_some() { + decl_ty + } else { + ty } - } + } else { + decl_ty + }; + + self.infer_top_pat(*pat, &ty); if let Some(expr) = else_branch { + let previous_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); self.infer_expr_coerce( *expr, &Expectation::HasType(self.result.standard_types.never.clone()), ); + self.diverges = previous_diverges; } - - self.infer_pat(*pat, &ty, BindingMode::default()); } Statement::Expr { expr, .. } => { self.infer_expr(*expr, &Expectation::none()); diff --git a/crates/hir-ty/src/infer/pat.rs b/crates/hir-ty/src/infer/pat.rs index 8b381f0d1fce..6481e0b7a719 100644 --- a/crates/hir-ty/src/infer/pat.rs +++ b/crates/hir-ty/src/infer/pat.rs @@ -4,7 +4,8 @@ use std::iter::repeat_with; use chalk_ir::Mutability; use hir_def::{ - expr::{BindingAnnotation, Expr, Literal, Pat, PatId}, + body::Body, + expr::{BindingAnnotation, Expr, ExprId, ExprOrPatId, Literal, Pat, PatId, RecordFieldPat}, path::Path, }; use hir_expand::name::Name; @@ -17,7 +18,43 @@ use crate::{ static_lifetime, Interner, Scalar, Substitution, Ty, TyBuilder, TyExt, TyKind, }; -use super::PatLike; +/// Used to generalize patterns and assignee expressions. +pub(super) trait PatLike: Into + Copy { + type BindingMode: Copy; + + fn infer( + this: &mut InferenceContext<'_>, + id: Self, + expected_ty: &Ty, + default_bm: Self::BindingMode, + ) -> Ty; +} + +impl PatLike for ExprId { + type BindingMode = (); + + fn infer( + this: &mut InferenceContext<'_>, + id: Self, + expected_ty: &Ty, + (): Self::BindingMode, + ) -> Ty { + this.infer_assignee_expr(id, expected_ty) + } +} + +impl PatLike for PatId { + type BindingMode = BindingMode; + + fn infer( + this: &mut InferenceContext<'_>, + id: Self, + expected_ty: &Ty, + default_bm: Self::BindingMode, + ) -> Ty { + this.infer_pat(id, expected_ty, default_bm) + } +} impl<'a> InferenceContext<'a> { /// Infers type for tuple struct pattern or its corresponding assignee expression. @@ -110,6 +147,7 @@ impl<'a> InferenceContext<'a> { ellipsis: Option, subs: &[T], ) -> Ty { + let expected = self.resolve_ty_shallow(expected); let expectations = match expected.as_tuple() { Some(parameters) => &*parameters.as_slice(Interner), _ => &[], @@ -143,12 +181,11 @@ impl<'a> InferenceContext<'a> { .intern(Interner) } - pub(super) fn infer_pat( - &mut self, - pat: PatId, - expected: &Ty, - mut default_bm: BindingMode, - ) -> Ty { + pub(super) fn infer_top_pat(&mut self, pat: PatId, expected: &Ty) -> Ty { + self.infer_pat(pat, expected, BindingMode::default()) + } + + fn infer_pat(&mut self, pat: PatId, expected: &Ty, mut default_bm: BindingMode) -> Ty { let mut expected = self.resolve_ty_shallow(expected); if is_non_ref_pat(self.body, pat) { @@ -183,25 +220,17 @@ impl<'a> InferenceContext<'a> { self.infer_tuple_pat_like(&expected, default_bm, *ellipsis, args) } Pat::Or(pats) => { - if let Some((first_pat, rest)) = pats.split_first() { - let ty = self.infer_pat(*first_pat, &expected, default_bm); - for pat in rest { - self.infer_pat(*pat, &expected, default_bm); - } - ty - } else { - self.err_ty() + for pat in pats.iter() { + self.infer_pat(*pat, &expected, default_bm); } + expected.clone() } - Pat::Ref { pat, mutability } => { - let mutability = lower_to_chalk_mutability(*mutability); - let expectation = match expected.as_reference() { - Some((inner_ty, _lifetime, exp_mut)) => inner_ty.clone(), - _ => self.result.standard_types.unknown.clone(), - }; - let subty = self.infer_pat(*pat, &expectation, default_bm); - TyKind::Ref(mutability, static_lifetime(), subty).intern(Interner) - } + &Pat::Ref { pat, mutability } => self.infer_ref_pat( + pat, + lower_to_chalk_mutability(mutability), + &expected, + default_bm, + ), Pat::TupleStruct { path: p, args: subpats, ellipsis } => self .infer_tuple_struct_pat_like( p.as_deref(), @@ -221,91 +250,17 @@ impl<'a> InferenceContext<'a> { self.infer_path(&resolver, path, pat.into()).unwrap_or_else(|| self.err_ty()) } Pat::Bind { mode, name: _, subpat } => { - let mode = if mode == &BindingAnnotation::Unannotated { - default_bm - } else { - BindingMode::convert(*mode) - }; - self.result.pat_binding_modes.insert(pat, mode); - - let inner_ty = match subpat { - Some(subpat) => self.infer_pat(*subpat, &expected, default_bm), - None => expected, - }; - let inner_ty = self.insert_type_vars_shallow(inner_ty); - - let bound_ty = match mode { - BindingMode::Ref(mutability) => { - TyKind::Ref(mutability, static_lifetime(), inner_ty.clone()) - .intern(Interner) - } - BindingMode::Move => inner_ty.clone(), - }; - self.write_pat_ty(pat, bound_ty); - return inner_ty; + return self.infer_bind_pat(pat, *mode, default_bm, *subpat, &expected); } Pat::Slice { prefix, slice, suffix } => { - let elem_ty = match expected.kind(Interner) { - TyKind::Array(st, _) | TyKind::Slice(st) => st.clone(), - _ => self.err_ty(), - }; - - for &pat_id in prefix.iter().chain(suffix.iter()) { - self.infer_pat(pat_id, &elem_ty, default_bm); - } - - if let &Some(slice_pat_id) = slice { - let rest_pat_ty = match expected.kind(Interner) { - TyKind::Array(_, length) => { - let len = try_const_usize(length); - let len = len.and_then(|len| { - len.checked_sub((prefix.len() + suffix.len()) as u128) - }); - TyKind::Array( - elem_ty.clone(), - usize_const(self.db, len, self.resolver.krate()), - ) - } - _ => TyKind::Slice(elem_ty.clone()), - } - .intern(Interner); - self.infer_pat(slice_pat_id, &rest_pat_ty, default_bm); - } - - match expected.kind(Interner) { - TyKind::Array(_, const_) => TyKind::Array(elem_ty, const_.clone()), - _ => TyKind::Slice(elem_ty), - } - .intern(Interner) + self.infer_slice_pat(&expected, prefix, slice, suffix, default_bm) } Pat::Wild => expected.clone(), Pat::Range { start, end } => { let start_ty = self.infer_expr(*start, &Expectation::has_type(expected.clone())); self.infer_expr(*end, &Expectation::has_type(start_ty)) } - &Pat::Lit(expr) => { - // FIXME: using `Option` here is a workaround until we can use if-let chains in stable. - let mut pat_ty = None; - - // Like slice patterns, byte string patterns can denote both `&[u8; N]` and `&[u8]`. - if let Expr::Literal(Literal::ByteString(_)) = self.body[expr] { - if let Some((inner, ..)) = expected.as_reference() { - let inner = self.resolve_ty_shallow(inner); - if matches!(inner.kind(Interner), TyKind::Slice(_)) { - let elem_ty = TyKind::Scalar(Scalar::Uint(UintTy::U8)).intern(Interner); - let slice_ty = TyKind::Slice(elem_ty).intern(Interner); - let ty = TyKind::Ref(Mutability::Not, static_lifetime(), slice_ty) - .intern(Interner); - self.write_expr_ty(expr, ty.clone()); - pat_ty = Some(ty); - } - } - } - - pat_ty.unwrap_or_else(|| { - self.infer_expr(expr, &Expectation::has_type(expected.clone())) - }) - } + &Pat::Lit(expr) => self.infer_lit_pat(expr, &expected), Pat::Box { inner } => match self.resolve_boxed_box() { Some(box_adt) => { let (inner_ty, alloc_ty) = match expected.as_adt() { @@ -341,6 +296,109 @@ impl<'a> InferenceContext<'a> { self.write_pat_ty(pat, ty.clone()); ty } + + fn infer_ref_pat( + &mut self, + pat: PatId, + mutability: Mutability, + expected: &Ty, + default_bm: BindingMode, + ) -> Ty { + let expectation = match expected.as_reference() { + Some((inner_ty, _lifetime, _exp_mut)) => inner_ty.clone(), + _ => self.result.standard_types.unknown.clone(), + }; + let subty = self.infer_pat(pat, &expectation, default_bm); + TyKind::Ref(mutability, static_lifetime(), subty).intern(Interner) + } + + fn infer_bind_pat( + &mut self, + pat: PatId, + mode: BindingAnnotation, + default_bm: BindingMode, + subpat: Option, + expected: &Ty, + ) -> Ty { + let mode = if mode == BindingAnnotation::Unannotated { + default_bm + } else { + BindingMode::convert(mode) + }; + self.result.pat_binding_modes.insert(pat, mode); + + let inner_ty = match subpat { + Some(subpat) => self.infer_pat(subpat, &expected, default_bm), + None => expected.clone(), + }; + let inner_ty = self.insert_type_vars_shallow(inner_ty); + + let bound_ty = match mode { + BindingMode::Ref(mutability) => { + TyKind::Ref(mutability, static_lifetime(), inner_ty.clone()).intern(Interner) + } + BindingMode::Move => inner_ty.clone(), + }; + self.write_pat_ty(pat, bound_ty); + return inner_ty; + } + + fn infer_slice_pat( + &mut self, + expected: &Ty, + prefix: &[PatId], + slice: &Option, + suffix: &[PatId], + default_bm: BindingMode, + ) -> Ty { + let elem_ty = match expected.kind(Interner) { + TyKind::Array(st, _) | TyKind::Slice(st) => st.clone(), + _ => self.err_ty(), + }; + + for &pat_id in prefix.iter().chain(suffix.iter()) { + self.infer_pat(pat_id, &elem_ty, default_bm); + } + + if let &Some(slice_pat_id) = slice { + let rest_pat_ty = match expected.kind(Interner) { + TyKind::Array(_, length) => { + let len = try_const_usize(length); + let len = + len.and_then(|len| len.checked_sub((prefix.len() + suffix.len()) as u128)); + TyKind::Array(elem_ty.clone(), usize_const(self.db, len, self.resolver.krate())) + } + _ => TyKind::Slice(elem_ty.clone()), + } + .intern(Interner); + self.infer_pat(slice_pat_id, &rest_pat_ty, default_bm); + } + + match expected.kind(Interner) { + TyKind::Array(_, const_) => TyKind::Array(elem_ty, const_.clone()), + _ => TyKind::Slice(elem_ty), + } + .intern(Interner) + } + + fn infer_lit_pat(&mut self, expr: ExprId, expected: &Ty) -> Ty { + // Like slice patterns, byte string patterns can denote both `&[u8; N]` and `&[u8]`. + if let Expr::Literal(Literal::ByteString(_)) = self.body[expr] { + if let Some((inner, ..)) = expected.as_reference() { + let inner = self.resolve_ty_shallow(inner); + if matches!(inner.kind(Interner), TyKind::Slice(_)) { + let elem_ty = TyKind::Scalar(Scalar::Uint(UintTy::U8)).intern(Interner); + let slice_ty = TyKind::Slice(elem_ty).intern(Interner); + let ty = + TyKind::Ref(Mutability::Not, static_lifetime(), slice_ty).intern(Interner); + self.write_expr_ty(expr, ty.clone()); + return ty; + } + } + } + + self.infer_expr(expr, &Expectation::has_type(expected.clone())) + } } fn is_non_ref_pat(body: &hir_def::body::Body, pat: PatId) -> bool { @@ -365,3 +423,41 @@ fn is_non_ref_pat(body: &hir_def::body::Body, pat: PatId) -> bool { Pat::Wild | Pat::Bind { .. } | Pat::Ref { .. } | Pat::Box { .. } | Pat::Missing => false, } } + +pub(super) fn contains_explicit_ref_binding(body: &Body, pat_id: PatId) -> bool { + let mut res = false; + walk_pats(body, pat_id, &mut |pat| { + res |= matches!(pat, Pat::Bind { mode: BindingAnnotation::Ref, .. }) + }); + res +} + +fn walk_pats(body: &Body, pat_id: PatId, f: &mut impl FnMut(&Pat)) { + let pat = &body[pat_id]; + f(pat); + match pat { + Pat::Range { .. } + | Pat::Lit(..) + | Pat::Path(..) + | Pat::ConstBlock(..) + | Pat::Wild + | Pat::Missing => {} + &Pat::Bind { subpat, .. } => { + if let Some(subpat) = subpat { + walk_pats(body, subpat, f); + } + } + Pat::Or(args) | Pat::Tuple { args, .. } | Pat::TupleStruct { args, .. } => { + args.iter().copied().for_each(|p| walk_pats(body, p, f)); + } + Pat::Ref { pat, .. } => walk_pats(body, *pat, f), + Pat::Slice { prefix, slice, suffix } => { + let total_iter = prefix.iter().chain(slice.iter()).chain(suffix.iter()); + total_iter.copied().for_each(|p| walk_pats(body, p, f)); + } + Pat::Record { args, .. } => { + args.iter().for_each(|RecordFieldPat { pat, .. }| walk_pats(body, *pat, f)); + } + Pat::Box { inner } => walk_pats(body, *inner, f), + } +} diff --git a/crates/hir-ty/src/tests/patterns.rs b/crates/hir-ty/src/tests/patterns.rs index aa1b2a1d9be0..be67329fee44 100644 --- a/crates/hir-ty/src/tests/patterns.rs +++ b/crates/hir-ty/src/tests/patterns.rs @@ -953,9 +953,9 @@ fn main() { 42..51 'true | ()': bool 49..51 '()': () 57..59 '{}': () - 68..80 '(() | true,)': ((),) + 68..80 '(() | true,)': (bool,) 69..71 '()': () - 69..78 '() | true': () + 69..78 '() | true': bool 74..78 'true': bool 74..78 'true': bool 84..86 '{}': () @@ -964,19 +964,15 @@ fn main() { 96..102 '_ | ()': bool 100..102 '()': () 108..110 '{}': () - 119..128 '(() | _,)': ((),) + 119..128 '(() | _,)': (bool,) 120..122 '()': () - 120..126 '() | _': () + 120..126 '() | _': bool 125..126 '_': bool 132..134 '{}': () 49..51: expected bool, got () - 68..80: expected (bool,), got ((),) 69..71: expected bool, got () - 69..78: expected bool, got () 100..102: expected bool, got () - 119..128: expected (bool,), got ((),) 120..122: expected bool, got () - 120..126: expected bool, got () "#]], ); } diff --git a/crates/ide/src/inlay_hints/adjustment.rs b/crates/ide/src/inlay_hints/adjustment.rs index 188eb7f977ba..729780fa0c91 100644 --- a/crates/ide/src/inlay_hints/adjustment.rs +++ b/crates/ide/src/inlay_hints/adjustment.rs @@ -606,14 +606,13 @@ fn a() { } #[test] - fn bug() { + fn let_stmt_explicit_ty() { check_with_config( InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG }, r#" fn main() { - // These should be identical, but they are not... - let () = return; + //^^^^^^ let (): () = return; //^^^^^^ } From 44e2c6ea9207264a86d05344b67999d77687edb6 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 3 Mar 2023 10:41:09 +0100 Subject: [PATCH 081/362] Don't emit two type mismatches for literal pattern mismatches --- crates/hir-ty/src/infer/pat.rs | 11 ++++++++--- crates/ide-diagnostics/src/handlers/type_mismatch.rs | 11 +---------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/crates/hir-ty/src/infer/pat.rs b/crates/hir-ty/src/infer/pat.rs index 6481e0b7a719..4c97eabd9ce0 100644 --- a/crates/hir-ty/src/infer/pat.rs +++ b/crates/hir-ty/src/infer/pat.rs @@ -181,8 +181,8 @@ impl<'a> InferenceContext<'a> { .intern(Interner) } - pub(super) fn infer_top_pat(&mut self, pat: PatId, expected: &Ty) -> Ty { - self.infer_pat(pat, expected, BindingMode::default()) + pub(super) fn infer_top_pat(&mut self, pat: PatId, expected: &Ty) { + self.infer_pat(pat, expected, BindingMode::default()); } fn infer_pat(&mut self, pat: PatId, expected: &Ty, mut default_bm: BindingMode) -> Ty { @@ -260,7 +260,12 @@ impl<'a> InferenceContext<'a> { let start_ty = self.infer_expr(*start, &Expectation::has_type(expected.clone())); self.infer_expr(*end, &Expectation::has_type(start_ty)) } - &Pat::Lit(expr) => self.infer_lit_pat(expr, &expected), + &Pat::Lit(expr) => { + // Don't emit type mismatches again, the expression lowering already did that. + let ty = self.infer_lit_pat(expr, &expected); + self.write_pat_ty(pat, ty.clone()); + return ty; + } Pat::Box { inner } => match self.resolve_boxed_box() { Some(box_adt) => { let (inner_ty, alloc_ty) = match expected.as_adt() { diff --git a/crates/ide-diagnostics/src/handlers/type_mismatch.rs b/crates/ide-diagnostics/src/handlers/type_mismatch.rs index 948ca4f63287..2026a2d4b8a0 100644 --- a/crates/ide-diagnostics/src/handlers/type_mismatch.rs +++ b/crates/ide-diagnostics/src/handlers/type_mismatch.rs @@ -54,7 +54,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Option (), + Either::Right(_pat_ptr) => {} } if fixes.is_empty() { @@ -63,14 +63,6 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Option, - d: &hir::TypeMismatch, - expr_ptr: &InFile>, - acc: &mut Vec, -) -> Option<()> { - None -} fn add_reference( ctx: &DiagnosticsContext<'_>, @@ -630,7 +622,6 @@ fn f() { &9 => () //^^ error: expected &(), found &i32 //^ error: expected (), found i32 - //^ error: expected (), found i32 } } "#, From 522823f610391932e2f4162a8c929af2dacaefc4 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 3 Mar 2023 11:13:06 +0100 Subject: [PATCH 082/362] Fix text fixtures of missing_match_arms diagnostics --- crates/hir-ty/src/infer/expr.rs | 2 +- crates/hir-ty/src/infer/pat.rs | 3 ++- .../ide-diagnostics/src/handlers/missing_match_arms.rs | 9 ++++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index a186ae836d07..6f20f0dc893e 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -398,7 +398,7 @@ impl<'a> InferenceContext<'a> { for arm in arms.iter() { self.diverges = Diverges::Maybe; let input_ty = self.resolve_ty_shallow(&input_ty); - let _pat_ty = self.infer_top_pat(arm.pat, &input_ty); + self.infer_top_pat(arm.pat, &input_ty); if let Some(guard_expr) = arm.guard { self.infer_expr( guard_expr, diff --git a/crates/hir-ty/src/infer/pat.rs b/crates/hir-ty/src/infer/pat.rs index 4c97eabd9ce0..3d03c2a527cb 100644 --- a/crates/hir-ty/src/infer/pat.rs +++ b/crates/hir-ty/src/infer/pat.rs @@ -293,7 +293,8 @@ impl<'a> InferenceContext<'a> { }; // use a new type variable if we got error type here let ty = self.insert_type_vars_shallow(ty); - if !self.unify(&ty, &expected) { + // FIXME: This never check is odd, but required with out we do inference right now + if !expected.is_never() && !self.unify(&ty, &expected) { self.result .type_mismatches .insert(pat.into(), TypeMismatch { expected, actual: ty.clone() }); diff --git a/crates/ide-diagnostics/src/handlers/missing_match_arms.rs b/crates/ide-diagnostics/src/handlers/missing_match_arms.rs index c24430ce6046..6594eed26d17 100644 --- a/crates/ide-diagnostics/src/handlers/missing_match_arms.rs +++ b/crates/ide-diagnostics/src/handlers/missing_match_arms.rs @@ -273,15 +273,20 @@ enum Either2 { C, D } fn main() { match Either::A { Either2::C => (), + // ^^^^^^^^^^ error: expected Either, found Either2 Either2::D => (), + // ^^^^^^^^^^ error: expected Either, found Either2 } match (true, false) { (true, false, true) => (), + // ^^^^^^^^^^^^^^^^^^^ error: expected (bool, bool), found (bool, bool, bool) (true) => (), // ^^^^ error: expected (bool, bool), found bool } match (true, false) { (true,) => {} } + // ^^^^^^^ error: expected (bool, bool), found (bool,) match (0) { () => () } + // ^^ error: expected i32, found () match Unresolved::Bar { Unresolved::Baz => () } } "#, @@ -295,7 +300,9 @@ fn main() { r#" fn main() { match false { true | () => {} } + // ^^ error: expected bool, found () match (false,) { (true | (),) => {} } + // ^^ error: expected bool, found () } "#, ); @@ -1038,12 +1045,12 @@ fn main() { #[test] fn reference_patterns_in_fields() { cov_mark::check_count!(validate_match_bailed_out, 2); - check_diagnostics( r#" fn main() { match (&false,) { (true,) => {} + // ^^^^^^^ error: expected (&bool,), found (bool,) } match (&false,) { (&true,) => {} From 80ddfb89a2c12b9017f5a82424796d4ab9d6da37 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 3 Mar 2023 11:21:27 +0100 Subject: [PATCH 083/362] fix: Load the sysroot in all CLI commands --- crates/rust-analyzer/src/cli/diagnostics.rs | 4 +++- crates/rust-analyzer/src/cli/lsif.rs | 5 +++-- crates/rust-analyzer/src/cli/scip.rs | 5 +++-- crates/rust-analyzer/src/cli/ssr.rs | 5 +++-- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/crates/rust-analyzer/src/cli/diagnostics.rs b/crates/rust-analyzer/src/cli/diagnostics.rs index ff821be53d83..0721d486ef1f 100644 --- a/crates/rust-analyzer/src/cli/diagnostics.rs +++ b/crates/rust-analyzer/src/cli/diagnostics.rs @@ -1,6 +1,7 @@ //! Analyze all modules in a project for diagnostics. Exits with a non-zero //! status code if any errors are found. +use project_model::{CargoConfig, RustcSource}; use rustc_hash::FxHashSet; use hir::{db::HirDatabase, Crate, Module}; @@ -14,7 +15,8 @@ use crate::cli::{ impl flags::Diagnostics { pub fn run(self) -> anyhow::Result<()> { - let cargo_config = Default::default(); + let mut cargo_config = CargoConfig::default(); + cargo_config.sysroot = Some(RustcSource::Discover); let load_cargo_config = LoadCargoConfig { load_out_dirs_from_check: !self.disable_build_scripts, with_proc_macro_server: ProcMacroServerChoice::Sysroot, diff --git a/crates/rust-analyzer/src/cli/lsif.rs b/crates/rust-analyzer/src/cli/lsif.rs index 3fc1aa4eaeb4..9b5451496c6c 100644 --- a/crates/rust-analyzer/src/cli/lsif.rs +++ b/crates/rust-analyzer/src/cli/lsif.rs @@ -13,7 +13,7 @@ use ide_db::LineIndexDatabase; use ide_db::base_db::salsa::{self, ParallelDatabase}; use ide_db::line_index::WideEncoding; use lsp_types::{self, lsif}; -use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace}; +use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace, RustcSource}; use vfs::{AbsPathBuf, Vfs}; use crate::cli::load_cargo::ProcMacroServerChoice; @@ -289,7 +289,8 @@ impl flags::Lsif { pub fn run(self) -> Result<()> { eprintln!("Generating LSIF started..."); let now = Instant::now(); - let cargo_config = CargoConfig::default(); + let mut cargo_config = CargoConfig::default(); + cargo_config.sysroot = Some(RustcSource::Discover); let no_progress = &|_| (); let load_cargo_config = LoadCargoConfig { load_out_dirs_from_check: true, diff --git a/crates/rust-analyzer/src/cli/scip.rs b/crates/rust-analyzer/src/cli/scip.rs index 9a04fbea7747..df5c26cf77a9 100644 --- a/crates/rust-analyzer/src/cli/scip.rs +++ b/crates/rust-analyzer/src/cli/scip.rs @@ -15,7 +15,7 @@ use ide::{ TokenStaticData, }; use ide_db::LineIndexDatabase; -use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace}; +use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace, RustcSource}; use scip::types as scip_types; use std::env; @@ -29,7 +29,8 @@ impl flags::Scip { pub fn run(self) -> Result<()> { eprintln!("Generating SCIP start..."); let now = Instant::now(); - let cargo_config = CargoConfig::default(); + let mut cargo_config = CargoConfig::default(); + cargo_config.sysroot = Some(RustcSource::Discover); let no_progress = &|s| (eprintln!("rust-analyzer: Loading {s}")); let load_cargo_config = LoadCargoConfig { diff --git a/crates/rust-analyzer/src/cli/ssr.rs b/crates/rust-analyzer/src/cli/ssr.rs index 3552f840a1b7..35a874f89207 100644 --- a/crates/rust-analyzer/src/cli/ssr.rs +++ b/crates/rust-analyzer/src/cli/ssr.rs @@ -1,7 +1,7 @@ //! Applies structured search replace rules from the command line. use ide_ssr::MatchFinder; -use project_model::CargoConfig; +use project_model::{CargoConfig, RustcSource}; use crate::cli::{ flags, @@ -12,7 +12,8 @@ use crate::cli::{ impl flags::Ssr { pub fn run(self) -> Result<()> { use ide_db::base_db::SourceDatabaseExt; - let cargo_config = CargoConfig::default(); + let mut cargo_config = CargoConfig::default(); + cargo_config.sysroot = Some(RustcSource::Discover); let load_cargo_config = LoadCargoConfig { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::Sysroot, From 9ac0da8f39680f1b09321980187bc502580b526a Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Sun, 13 Nov 2022 13:55:04 +0000 Subject: [PATCH 084/362] Make `unused_allocation` lint warn against `Box::new` --- compiler/rustc_lint/src/unused.rs | 8 ++++++-- compiler/rustc_span/src/symbol.rs | 1 + library/alloc/src/boxed.rs | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs index 3a92f5806c9e..b32fc96164a3 100644 --- a/compiler/rustc_lint/src/unused.rs +++ b/compiler/rustc_lint/src/unused.rs @@ -1349,9 +1349,8 @@ declare_lint! { /// ### Example /// /// ```rust - /// #![feature(box_syntax)] /// fn main() { - /// let a = (box [1, 2, 3]).len(); + /// let a = Box::new([1, 2, 3]).len(); /// } /// ``` /// @@ -1373,6 +1372,11 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAllocation { fn check_expr(&mut self, cx: &LateContext<'_>, e: &hir::Expr<'_>) { match e.kind { hir::ExprKind::Box(_) => {} + hir::ExprKind::Call(path_expr, [_]) + if let hir::ExprKind::Path(qpath) = &path_expr.kind + && let Some(did) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id() + && cx.tcx.is_diagnostic_item(sym::box_new, did) + => {} _ => return, } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index fb579e4ff772..a76bf45ec890 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -429,6 +429,7 @@ symbols! { borrowck_graphviz_format, borrowck_graphviz_postflow, box_free, + box_new, box_patterns, box_syntax, bpf_target_feature, diff --git a/library/alloc/src/boxed.rs b/library/alloc/src/boxed.rs index 44a37899007f..241b11c3f5f5 100644 --- a/library/alloc/src/boxed.rs +++ b/library/alloc/src/boxed.rs @@ -214,6 +214,7 @@ impl Box { #[inline(always)] #[stable(feature = "rust1", since = "1.0.0")] #[must_use] + #[rustc_diagnostic_item = "box_new"] pub fn new(x: T) -> Self { #[rustc_box] Box::new(x) From 027f19c8b911458722511be4ffc469f505766ffa Mon Sep 17 00:00:00 2001 From: Takayuki Nakata Date: Fri, 3 Mar 2023 22:41:39 +0900 Subject: [PATCH 085/362] Fix FP for `let_unit_value` when `await` used --- clippy_lints/src/unit_types/let_unit_value.rs | 6 +++++- tests/ui/let_unit.fixed | 4 ++++ tests/ui/let_unit.rs | 4 ++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/unit_types/let_unit_value.rs b/clippy_lints/src/unit_types/let_unit_value.rs index d6167a62169d..3430b6e37341 100644 --- a/clippy_lints/src/unit_types/let_unit_value.rs +++ b/clippy_lints/src/unit_types/let_unit_value.rs @@ -5,7 +5,7 @@ use clippy_utils::visitors::{for_each_local_assignment, for_each_value_source}; use core::ops::ControlFlow; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{Expr, ExprKind, HirId, HirIdSet, Local, Node, PatKind, QPath, TyKind}; +use rustc_hir::{Expr, ExprKind, HirId, HirIdSet, Local, MatchSource, Node, PatKind, QPath, TyKind}; use rustc_lint::{LateContext, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_middle::ty; @@ -41,6 +41,10 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx Local<'_>) { ); } } else { + if let ExprKind::Match(_, _, MatchSource::AwaitDesugar) = init.kind { + return + } + span_lint_and_then( cx, LET_UNIT_VALUE, diff --git a/tests/ui/let_unit.fixed b/tests/ui/let_unit.fixed index 6343cff0f7ff..76ff0645f417 100644 --- a/tests/ui/let_unit.fixed +++ b/tests/ui/let_unit.fixed @@ -175,3 +175,7 @@ fn attributes() { #[expect(clippy::let_unit_value)] let _ = f(); } + +async fn issue10433() { + let _pending: () = std::future::pending().await; +} diff --git a/tests/ui/let_unit.rs b/tests/ui/let_unit.rs index c9bb2849f5cf..895ccfe366a2 100644 --- a/tests/ui/let_unit.rs +++ b/tests/ui/let_unit.rs @@ -175,3 +175,7 @@ fn attributes() { #[expect(clippy::let_unit_value)] let _ = f(); } + +async fn issue10433() { + let _pending: () = std::future::pending().await; +} From 2e7d2c2d045e2a66a4e309cbb71a3264004f8678 Mon Sep 17 00:00:00 2001 From: Ryo Yoshida Date: Sat, 4 Mar 2023 00:23:56 +0900 Subject: [PATCH 086/362] Parse trait alias as a distinct AST type --- crates/hir-def/src/item_tree/lower.rs | 1 + crates/parser/src/grammar/items/traits.rs | 2 +- crates/parser/src/syntax_kind/generated.rs | 1 + .../parser/inline/ok/0151_trait_alias.rast | 2 +- .../ok/0177_trait_alias_where_clause.rast | 4 +- crates/syntax/rust.ungram | 10 +++-- crates/syntax/src/ast.rs | 3 +- crates/syntax/src/ast/generated/nodes.rs | 43 +++++++++++++++++- crates/syntax/src/ast/node_ext.rs | 45 +++++++++++++++++++ crates/syntax/src/tests/ast_src.rs | 1 + crates/syntax/src/tests/sourcegen_ast.rs | 1 + 11 files changed, 103 insertions(+), 10 deletions(-) diff --git a/crates/hir-def/src/item_tree/lower.rs b/crates/hir-def/src/item_tree/lower.rs index d4d3c5ef19a6..bd7c556ae69d 100644 --- a/crates/hir-def/src/item_tree/lower.rs +++ b/crates/hir-def/src/item_tree/lower.rs @@ -110,6 +110,7 @@ impl<'a> Ctx<'a> { ast::Item::Const(ast) => self.lower_const(ast).into(), ast::Item::Module(ast) => self.lower_module(ast)?.into(), ast::Item::Trait(ast) => self.lower_trait(ast)?.into(), + ast::Item::TraitAlias(_) => return None, ast::Item::Impl(ast) => self.lower_impl(ast)?.into(), ast::Item::Use(ast) => self.lower_use(ast)?.into(), ast::Item::ExternCrate(ast) => self.lower_extern_crate(ast)?.into(), diff --git a/crates/parser/src/grammar/items/traits.rs b/crates/parser/src/grammar/items/traits.rs index c982e2d564c9..a8a1ccb15e6c 100644 --- a/crates/parser/src/grammar/items/traits.rs +++ b/crates/parser/src/grammar/items/traits.rs @@ -20,7 +20,7 @@ pub(super) fn trait_(p: &mut Parser<'_>, m: Marker) { // trait Z = where Self: T; generic_params::opt_where_clause(p); p.expect(T![;]); - m.complete(p, TRAIT); + m.complete(p, TRAIT_ALIAS); return; } diff --git a/crates/parser/src/syntax_kind/generated.rs b/crates/parser/src/syntax_kind/generated.rs index 52b3fc23d59c..cd87b304a2fb 100644 --- a/crates/parser/src/syntax_kind/generated.rs +++ b/crates/parser/src/syntax_kind/generated.rs @@ -135,6 +135,7 @@ pub enum SyntaxKind { STATIC, CONST, TRAIT, + TRAIT_ALIAS, IMPL, TYPE_ALIAS, MACRO_CALL, diff --git a/crates/parser/test_data/parser/inline/ok/0151_trait_alias.rast b/crates/parser/test_data/parser/inline/ok/0151_trait_alias.rast index 2ef66484ae48..c45f87089800 100644 --- a/crates/parser/test_data/parser/inline/ok/0151_trait_alias.rast +++ b/crates/parser/test_data/parser/inline/ok/0151_trait_alias.rast @@ -1,5 +1,5 @@ SOURCE_FILE - TRAIT + TRAIT_ALIAS TRAIT_KW "trait" WHITESPACE " " NAME diff --git a/crates/parser/test_data/parser/inline/ok/0177_trait_alias_where_clause.rast b/crates/parser/test_data/parser/inline/ok/0177_trait_alias_where_clause.rast index 4443d9d14263..8f678247731d 100644 --- a/crates/parser/test_data/parser/inline/ok/0177_trait_alias_where_clause.rast +++ b/crates/parser/test_data/parser/inline/ok/0177_trait_alias_where_clause.rast @@ -1,5 +1,5 @@ SOURCE_FILE - TRAIT + TRAIT_ALIAS TRAIT_KW "trait" WHITESPACE " " NAME @@ -50,7 +50,7 @@ SOURCE_FILE IDENT "Copy" SEMICOLON ";" WHITESPACE "\n" - TRAIT + TRAIT_ALIAS TRAIT_KW "trait" WHITESPACE " " NAME diff --git a/crates/syntax/rust.ungram b/crates/syntax/rust.ungram index 36ad5fddfd0c..548b5ba8b8b6 100644 --- a/crates/syntax/rust.ungram +++ b/crates/syntax/rust.ungram @@ -97,6 +97,7 @@ Item = | Static | Struct | Trait +| TraitAlias | TypeAlias | Union | Use @@ -240,10 +241,11 @@ Trait = Attr* Visibility? 'unsafe'? 'auto'? 'trait' Name GenericParamList? - ( - (':' TypeBoundList?)? WhereClause? AssocItemList - | '=' TypeBoundList? WhereClause? ';' - ) + (':' TypeBoundList?)? WhereClause? AssocItemList + +TraitAlias = + Attr* Visibility? + 'trait' Name GenericParamList? '=' TypeBoundList? WhereClause? ';' AssocItemList = '{' Attr* AssocItem* '}' diff --git a/crates/syntax/src/ast.rs b/crates/syntax/src/ast.rs index 679536fe27af..745f2e14e95c 100644 --- a/crates/syntax/src/ast.rs +++ b/crates/syntax/src/ast.rs @@ -25,7 +25,8 @@ pub use self::{ generated::{nodes::*, tokens::*}, node_ext::{ AttrKind, FieldKind, Macro, NameLike, NameOrNameRef, PathSegmentKind, SelfParamKind, - SlicePatComponents, StructKind, TypeBoundKind, TypeOrConstParam, VisibilityKind, + SlicePatComponents, StructKind, TraitOrAlias, TypeBoundKind, TypeOrConstParam, + VisibilityKind, }, operators::{ArithOp, BinaryOp, CmpOp, LogicOp, Ordering, RangeOp, UnaryOp}, token_ext::{CommentKind, CommentPlacement, CommentShape, IsString, QuoteOffsets, Radix}, diff --git a/crates/syntax/src/ast/generated/nodes.rs b/crates/syntax/src/ast/generated/nodes.rs index 642a3bfc35d1..fe324845360d 100644 --- a/crates/syntax/src/ast/generated/nodes.rs +++ b/crates/syntax/src/ast/generated/nodes.rs @@ -407,7 +407,21 @@ impl Trait { pub fn auto_token(&self) -> Option { support::token(&self.syntax, T![auto]) } pub fn trait_token(&self) -> Option { support::token(&self.syntax, T![trait]) } pub fn assoc_item_list(&self) -> Option { support::child(&self.syntax) } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct TraitAlias { + pub(crate) syntax: SyntaxNode, +} +impl ast::HasAttrs for TraitAlias {} +impl ast::HasName for TraitAlias {} +impl ast::HasVisibility for TraitAlias {} +impl ast::HasGenericParams for TraitAlias {} +impl ast::HasDocComments for TraitAlias {} +impl TraitAlias { + pub fn trait_token(&self) -> Option { support::token(&self.syntax, T![trait]) } pub fn eq_token(&self) -> Option { support::token(&self.syntax, T![=]) } + pub fn type_bound_list(&self) -> Option { support::child(&self.syntax) } pub fn semicolon_token(&self) -> Option { support::token(&self.syntax, T![;]) } } @@ -1573,6 +1587,7 @@ pub enum Item { Static(Static), Struct(Struct), Trait(Trait), + TraitAlias(TraitAlias), TypeAlias(TypeAlias), Union(Union), Use(Use), @@ -2058,6 +2073,17 @@ impl AstNode for Trait { } fn syntax(&self) -> &SyntaxNode { &self.syntax } } +impl AstNode for TraitAlias { + fn can_cast(kind: SyntaxKind) -> bool { kind == TRAIT_ALIAS } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} impl AstNode for TypeAlias { fn can_cast(kind: SyntaxKind) -> bool { kind == TYPE_ALIAS } fn cast(syntax: SyntaxNode) -> Option { @@ -3570,6 +3596,9 @@ impl From for Item { impl From for Item { fn from(node: Trait) -> Item { Item::Trait(node) } } +impl From for Item { + fn from(node: TraitAlias) -> Item { Item::TraitAlias(node) } +} impl From for Item { fn from(node: TypeAlias) -> Item { Item::TypeAlias(node) } } @@ -3596,6 +3625,7 @@ impl AstNode for Item { | STATIC | STRUCT | TRAIT + | TRAIT_ALIAS | TYPE_ALIAS | UNION | USE @@ -3616,6 +3646,7 @@ impl AstNode for Item { STATIC => Item::Static(Static { syntax }), STRUCT => Item::Struct(Struct { syntax }), TRAIT => Item::Trait(Trait { syntax }), + TRAIT_ALIAS => Item::TraitAlias(TraitAlias { syntax }), TYPE_ALIAS => Item::TypeAlias(TypeAlias { syntax }), UNION => Item::Union(Union { syntax }), USE => Item::Use(Use { syntax }), @@ -3638,6 +3669,7 @@ impl AstNode for Item { Item::Static(it) => &it.syntax, Item::Struct(it) => &it.syntax, Item::Trait(it) => &it.syntax, + Item::TraitAlias(it) => &it.syntax, Item::TypeAlias(it) => &it.syntax, Item::Union(it) => &it.syntax, Item::Use(it) => &it.syntax, @@ -3950,6 +3982,7 @@ impl AstNode for AnyHasAttrs { | STATIC | STRUCT | TRAIT + | TRAIT_ALIAS | TYPE_ALIAS | UNION | USE @@ -4035,6 +4068,7 @@ impl AstNode for AnyHasDocComments { | STATIC | STRUCT | TRAIT + | TRAIT_ALIAS | TYPE_ALIAS | UNION | USE @@ -4056,7 +4090,7 @@ impl AnyHasGenericParams { } impl AstNode for AnyHasGenericParams { fn can_cast(kind: SyntaxKind) -> bool { - matches!(kind, ENUM | FN | IMPL | STRUCT | TRAIT | TYPE_ALIAS | UNION) + matches!(kind, ENUM | FN | IMPL | STRUCT | TRAIT | TRAIT_ALIAS | TYPE_ALIAS | UNION) } fn cast(syntax: SyntaxNode) -> Option { Self::can_cast(syntax.kind()).then_some(AnyHasGenericParams { syntax }) @@ -4108,6 +4142,7 @@ impl AstNode for AnyHasName { | STATIC | STRUCT | TRAIT + | TRAIT_ALIAS | TYPE_ALIAS | UNION | RENAME @@ -4163,6 +4198,7 @@ impl AstNode for AnyHasVisibility { | STATIC | STRUCT | TRAIT + | TRAIT_ALIAS | TYPE_ALIAS | UNION | USE @@ -4391,6 +4427,11 @@ impl std::fmt::Display for Trait { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for TraitAlias { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for TypeAlias { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs index fe82aa907222..301fbcebf1c8 100644 --- a/crates/syntax/src/ast/node_ext.rs +++ b/crates/syntax/src/ast/node_ext.rs @@ -680,6 +680,51 @@ impl TypeOrConstParam { } } +#[derive(Debug, Clone)] +pub enum TraitOrAlias { + Trait(ast::Trait), + TraitAlias(ast::TraitAlias), +} + +impl TraitOrAlias { + pub fn name(&self) -> Option { + match self { + TraitOrAlias::Trait(x) => x.name(), + TraitOrAlias::TraitAlias(x) => x.name(), + } + } +} + +impl AstNode for TraitOrAlias { + fn can_cast(kind: SyntaxKind) -> bool + where + Self: Sized, + { + matches!(kind, SyntaxKind::TRAIT | SyntaxKind::TRAIT_ALIAS) + } + + fn cast(syntax: SyntaxNode) -> Option + where + Self: Sized, + { + let res = match syntax.kind() { + SyntaxKind::TRAIT => TraitOrAlias::Trait(ast::Trait { syntax }), + SyntaxKind::TRAIT_ALIAS => TraitOrAlias::TraitAlias(ast::TraitAlias { syntax }), + _ => return None, + }; + Some(res) + } + + fn syntax(&self) -> &SyntaxNode { + match self { + TraitOrAlias::Trait(it) => it.syntax(), + TraitOrAlias::TraitAlias(it) => it.syntax(), + } + } +} + +impl HasAttrs for TraitOrAlias {} + pub enum VisibilityKind { In(ast::Path), PubCrate, diff --git a/crates/syntax/src/tests/ast_src.rs b/crates/syntax/src/tests/ast_src.rs index 3ff6e03006b5..ccce71966ff8 100644 --- a/crates/syntax/src/tests/ast_src.rs +++ b/crates/syntax/src/tests/ast_src.rs @@ -86,6 +86,7 @@ pub(crate) const KINDS_SRC: KindsSrc<'_> = KindsSrc { "STATIC", "CONST", "TRAIT", + "TRAIT_ALIAS", "IMPL", "TYPE_ALIAS", "MACRO_CALL", diff --git a/crates/syntax/src/tests/sourcegen_ast.rs b/crates/syntax/src/tests/sourcegen_ast.rs index 03aa2c451e84..e954b58251fa 100644 --- a/crates/syntax/src/tests/sourcegen_ast.rs +++ b/crates/syntax/src/tests/sourcegen_ast.rs @@ -783,6 +783,7 @@ fn extract_struct_traits(ast: &mut AstSrc) { "Enum", "Variant", "Trait", + "TraitAlias", "Module", "Static", "Const", From 356d12eae4b8985dc933c13e9281546d532d0845 Mon Sep 17 00:00:00 2001 From: Ryo Yoshida Date: Sat, 4 Mar 2023 00:24:03 +0900 Subject: [PATCH 087/362] refactor: leverage `HasAttrs` for code brevity --- crates/hir-def/src/attr.rs | 46 +++++++------------------------ crates/syntax/src/ast.rs | 7 +++++ crates/syntax/src/ast/node_ext.rs | 30 ++++++++++++++++++++ 3 files changed, 47 insertions(+), 36 deletions(-) diff --git a/crates/hir-def/src/attr.rs b/crates/hir-def/src/attr.rs index fcd92ad33858..79c2c188ff12 100644 --- a/crates/hir-def/src/attr.rs +++ b/crates/hir-def/src/attr.rs @@ -315,26 +315,14 @@ impl AttrsWithOwner { let src = it.parent().child_source(db); RawAttrs::from_attrs_owner( db.upcast(), - src.with_value(src.value[it.local_id()].as_ref().either( - |it| match it { - ast::TypeOrConstParam::Type(it) => it as _, - ast::TypeOrConstParam::Const(it) => it as _, - }, - |it| it as _, - )), + src.with_value(&src.value[it.local_id()]), ) } GenericParamId::TypeParamId(it) => { let src = it.parent().child_source(db); RawAttrs::from_attrs_owner( db.upcast(), - src.with_value(src.value[it.local_id()].as_ref().either( - |it| match it { - ast::TypeOrConstParam::Type(it) => it as _, - ast::TypeOrConstParam::Const(it) => it as _, - }, - |it| it as _, - )), + src.with_value(&src.value[it.local_id()]), ) } GenericParamId::LifetimeParamId(it) => { @@ -412,28 +400,14 @@ impl AttrsWithOwner { }, AttrDefId::ImplId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), AttrDefId::GenericParamId(id) => match id { - GenericParamId::ConstParamId(id) => { - id.parent().child_source(db).map(|source| match &source[id.local_id()] { - Either::Left(ast::TypeOrConstParam::Type(id)) => { - ast::AnyHasAttrs::new(id.clone()) - } - Either::Left(ast::TypeOrConstParam::Const(id)) => { - ast::AnyHasAttrs::new(id.clone()) - } - Either::Right(id) => ast::AnyHasAttrs::new(id.clone()), - }) - } - GenericParamId::TypeParamId(id) => { - id.parent().child_source(db).map(|source| match &source[id.local_id()] { - Either::Left(ast::TypeOrConstParam::Type(id)) => { - ast::AnyHasAttrs::new(id.clone()) - } - Either::Left(ast::TypeOrConstParam::Const(id)) => { - ast::AnyHasAttrs::new(id.clone()) - } - Either::Right(id) => ast::AnyHasAttrs::new(id.clone()), - }) - } + GenericParamId::ConstParamId(id) => id + .parent() + .child_source(db) + .map(|source| ast::AnyHasAttrs::new(source[id.local_id()].clone())), + GenericParamId::TypeParamId(id) => id + .parent() + .child_source(db) + .map(|source| ast::AnyHasAttrs::new(source[id.local_id()].clone())), GenericParamId::LifetimeParamId(id) => id .parent .child_source(db) diff --git a/crates/syntax/src/ast.rs b/crates/syntax/src/ast.rs index 745f2e14e95c..1e691befff62 100644 --- a/crates/syntax/src/ast.rs +++ b/crates/syntax/src/ast.rs @@ -129,6 +129,13 @@ where } } +impl HasAttrs for Either +where + L: HasAttrs, + R: HasAttrs, +{ +} + mod support { use super::{AstChildren, AstNode, SyntaxKind, SyntaxNode, SyntaxToken}; diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs index 301fbcebf1c8..15bd5ab3c729 100644 --- a/crates/syntax/src/ast/node_ext.rs +++ b/crates/syntax/src/ast/node_ext.rs @@ -680,6 +680,36 @@ impl TypeOrConstParam { } } +impl AstNode for TypeOrConstParam { + fn can_cast(kind: SyntaxKind) -> bool + where + Self: Sized, + { + matches!(kind, SyntaxKind::TYPE_PARAM | SyntaxKind::CONST_PARAM) + } + + fn cast(syntax: SyntaxNode) -> Option + where + Self: Sized, + { + let res = match syntax.kind() { + SyntaxKind::TYPE_PARAM => TypeOrConstParam::Type(ast::TypeParam { syntax }), + SyntaxKind::CONST_PARAM => TypeOrConstParam::Const(ast::ConstParam { syntax }), + _ => return None, + }; + Some(res) + } + + fn syntax(&self) -> &SyntaxNode { + match self { + TypeOrConstParam::Type(it) => it.syntax(), + TypeOrConstParam::Const(it) => it.syntax(), + } + } +} + +impl HasAttrs for TypeOrConstParam {} + #[derive(Debug, Clone)] pub enum TraitOrAlias { Trait(ast::Trait), From e2ec3a6561d73cc4b530c69a9bafc30c4f34b1ec Mon Sep 17 00:00:00 2001 From: Ryo Yoshida Date: Sat, 4 Mar 2023 00:24:05 +0900 Subject: [PATCH 088/362] Refactor generic parameter lowering Since we moved impl trait handling to other place, there are only two cases now: those that introduce implicit `Self` parameter and those that don't. --- crates/hir-def/src/generics.rs | 18 ++++-- crates/hir-def/src/item_tree/lower.rs | 84 +++++++++++---------------- 2 files changed, 45 insertions(+), 57 deletions(-) diff --git a/crates/hir-def/src/generics.rs b/crates/hir-def/src/generics.rs index b2ab0c30e037..5a0aa1933880 100644 --- a/crates/hir-def/src/generics.rs +++ b/crates/hir-def/src/generics.rs @@ -207,12 +207,10 @@ impl GenericParams { pub(crate) fn fill_bounds( &mut self, lower_ctx: &LowerCtx<'_>, - node: &dyn ast::HasTypeBounds, + type_bounds: Option, target: Either, ) { - for bound in - node.type_bound_list().iter().flat_map(|type_bound_list| type_bound_list.bounds()) - { + for bound in type_bounds.iter().flat_map(|type_bound_list| type_bound_list.bounds()) { self.add_where_predicate_from_bound(lower_ctx, bound, None, target.clone()); } } @@ -233,7 +231,11 @@ impl GenericParams { }; self.type_or_consts.alloc(param.into()); let type_ref = TypeRef::Path(name.into()); - self.fill_bounds(lower_ctx, &type_param, Either::Left(type_ref)); + self.fill_bounds( + lower_ctx, + type_param.type_bound_list(), + Either::Left(type_ref), + ); } ast::TypeOrConstParam::Const(const_param) => { let name = const_param.name().map_or_else(Name::missing, |it| it.as_name()); @@ -255,7 +257,11 @@ impl GenericParams { let param = LifetimeParamData { name: name.clone() }; self.lifetimes.alloc(param); let lifetime_ref = LifetimeRef::new_name(name); - self.fill_bounds(lower_ctx, &lifetime_param, Either::Right(lifetime_ref)); + self.fill_bounds( + lower_ctx, + lifetime_param.type_bound_list(), + Either::Right(lifetime_ref), + ); } } diff --git a/crates/hir-def/src/item_tree/lower.rs b/crates/hir-def/src/item_tree/lower.rs index bd7c556ae69d..b64b4d11e373 100644 --- a/crates/hir-def/src/item_tree/lower.rs +++ b/crates/hir-def/src/item_tree/lower.rs @@ -3,7 +3,7 @@ use std::{collections::hash_map::Entry, sync::Arc}; use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, HirFileId}; -use syntax::ast::{self, HasModuleItem}; +use syntax::ast::{self, HasModuleItem, HasTypeBounds}; use crate::{ generics::{GenericParams, TypeParamData, TypeParamProvenance}, @@ -148,7 +148,7 @@ impl<'a> Ctx<'a> { fn lower_struct(&mut self, strukt: &ast::Struct) -> Option> { let visibility = self.lower_visibility(strukt); let name = strukt.name()?.as_name(); - let generic_params = self.lower_generic_params(GenericsOwner::Struct, strukt); + let generic_params = self.lower_generic_params(HasImplicitSelf::No, strukt); let fields = self.lower_fields(&strukt.kind()); let ast_id = self.source_ast_id_map.ast_id(strukt); let res = Struct { name, visibility, generic_params, fields, ast_id }; @@ -212,7 +212,7 @@ impl<'a> Ctx<'a> { fn lower_union(&mut self, union: &ast::Union) -> Option> { let visibility = self.lower_visibility(union); let name = union.name()?.as_name(); - let generic_params = self.lower_generic_params(GenericsOwner::Union, union); + let generic_params = self.lower_generic_params(HasImplicitSelf::No, union); let fields = match union.record_field_list() { Some(record_field_list) => self.lower_fields(&StructKind::Record(record_field_list)), None => Fields::Record(IdxRange::new(self.next_field_idx()..self.next_field_idx())), @@ -225,7 +225,7 @@ impl<'a> Ctx<'a> { fn lower_enum(&mut self, enum_: &ast::Enum) -> Option> { let visibility = self.lower_visibility(enum_); let name = enum_.name()?.as_name(); - let generic_params = self.lower_generic_params(GenericsOwner::Enum, enum_); + let generic_params = self.lower_generic_params(HasImplicitSelf::No, enum_); let variants = match &enum_.variant_list() { Some(variant_list) => self.lower_variants(variant_list), None => IdxRange::new(self.next_variant_idx()..self.next_variant_idx()), @@ -373,8 +373,7 @@ impl<'a> Ctx<'a> { ast_id, flags, }; - res.explicit_generic_params = - self.lower_generic_params(GenericsOwner::Function(&res), func); + res.explicit_generic_params = self.lower_generic_params(HasImplicitSelf::No, func); Some(id(self.data().functions.alloc(res))) } @@ -387,7 +386,7 @@ impl<'a> Ctx<'a> { let type_ref = type_alias.ty().map(|it| self.lower_type_ref(&it)); let visibility = self.lower_visibility(type_alias); let bounds = self.lower_type_bounds(type_alias); - let generic_params = self.lower_generic_params(GenericsOwner::TypeAlias, type_alias); + let generic_params = self.lower_generic_params(HasImplicitSelf::No, type_alias); let ast_id = self.source_ast_id_map.ast_id(type_alias); let res = TypeAlias { name, @@ -443,7 +442,8 @@ impl<'a> Ctx<'a> { fn lower_trait(&mut self, trait_def: &ast::Trait) -> Option> { let name = trait_def.name()?.as_name(); let visibility = self.lower_visibility(trait_def); - let generic_params = self.lower_generic_params(GenericsOwner::Trait(trait_def), trait_def); + let generic_params = + self.lower_generic_params(HasImplicitSelf::Yes(trait_def.type_bound_list()), trait_def); let is_auto = trait_def.auto_token().is_some(); let is_unsafe = trait_def.unsafe_token().is_some(); let items = trait_def.assoc_item_list().map(|list| { @@ -463,7 +463,9 @@ impl<'a> Ctx<'a> { } fn lower_impl(&mut self, impl_def: &ast::Impl) -> Option> { - let generic_params = self.lower_generic_params(GenericsOwner::Impl, impl_def); + // Note that trait impls don't get implicit `Self` unlike traits, because here they are a + // type alias rather than a type parameter, so this is handled by the resolver. + let generic_params = self.lower_generic_params(HasImplicitSelf::No, impl_def); // FIXME: If trait lowering fails, due to a non PathType for example, we treat this impl // as if it was an non-trait impl. Ideally we want to create a unique missing ref that only // equals itself. @@ -567,42 +569,29 @@ impl<'a> Ctx<'a> { fn lower_generic_params( &mut self, - owner: GenericsOwner<'_>, + has_implicit_self: HasImplicitSelf, node: &dyn ast::HasGenericParams, ) -> Interned { let mut generics = GenericParams::default(); - match owner { - GenericsOwner::Function(_) - | GenericsOwner::Struct - | GenericsOwner::Enum - | GenericsOwner::Union - | GenericsOwner::TypeAlias => { - generics.fill(&self.body_ctx, node); - } - GenericsOwner::Trait(trait_def) => { - // traits get the Self type as an implicit first type parameter - generics.type_or_consts.alloc( - TypeParamData { - name: Some(name![Self]), - default: None, - provenance: TypeParamProvenance::TraitSelf, - } - .into(), - ); - // add super traits as bounds on Self - // i.e., trait Foo: Bar is equivalent to trait Foo where Self: Bar - let self_param = TypeRef::Path(name![Self].into()); - generics.fill_bounds(&self.body_ctx, trait_def, Either::Left(self_param)); - generics.fill(&self.body_ctx, node); - } - GenericsOwner::Impl => { - // Note that we don't add `Self` here: in `impl`s, `Self` is not a - // type-parameter, but rather is a type-alias for impl's target - // type, so this is handled by the resolver. - generics.fill(&self.body_ctx, node); - } + + if let HasImplicitSelf::Yes(bounds) = has_implicit_self { + // Traits and trait aliases get the Self type as an implicit first type parameter. + generics.type_or_consts.alloc( + TypeParamData { + name: Some(name![Self]), + default: None, + provenance: TypeParamProvenance::TraitSelf, + } + .into(), + ); + // add super traits as bounds on Self + // i.e., `trait Foo: Bar` is equivalent to `trait Foo where Self: Bar` + let self_param = TypeRef::Path(name![Self].into()); + generics.fill_bounds(&self.body_ctx, bounds, Either::Left(self_param)); } + generics.fill(&self.body_ctx, node); + generics.shrink_to_fit(); Interned::new(generics) } @@ -674,17 +663,10 @@ fn desugar_future_path(orig: TypeRef) -> Path { Path::from_known_path(path, generic_args) } -enum GenericsOwner<'a> { - /// We need access to the partially-lowered `Function` for lowering `impl Trait` in argument - /// position. - Function(&'a Function), - Struct, - Enum, - Union, - /// The `TraitDef` is needed to fill the source map for the implicit `Self` parameter. - Trait(&'a ast::Trait), - TypeAlias, - Impl, +enum HasImplicitSelf { + /// Inner list is a type bound list for the implicit `Self`. + Yes(Option), + No, } fn lower_abi(abi: ast::Abi) -> Interned { From 29c957f97388afd2a21ec3364ea4ce8c269cfbcd Mon Sep 17 00:00:00 2001 From: Ryo Yoshida Date: Sat, 4 Mar 2023 00:24:07 +0900 Subject: [PATCH 089/362] Lower and handle trait aliases in HIR --- crates/hir-def/src/attr.rs | 2 + crates/hir-def/src/child_by_source.rs | 3 +- crates/hir-def/src/data.rs | 37 +++++++++------ crates/hir-def/src/db.rs | 11 +++-- crates/hir-def/src/generics.rs | 26 +++++++--- crates/hir-def/src/import_map.rs | 2 + crates/hir-def/src/item_scope.rs | 1 + crates/hir-def/src/item_tree.rs | 17 ++++++- crates/hir-def/src/item_tree/lower.rs | 47 +++++++++++++------ crates/hir-def/src/item_tree/pretty.rs | 29 ++++++------ crates/hir-def/src/keys.rs | 5 +- crates/hir-def/src/lib.rs | 15 +++++- crates/hir-def/src/nameres/collector.rs | 17 ++++++- .../hir-def/src/nameres/tests/incremental.rs | 1 + crates/hir-def/src/resolver.rs | 16 ++++++- crates/hir-ty/src/diagnostics/decl_check.rs | 1 + crates/hir-ty/src/infer.rs | 5 +- crates/hir-ty/src/lower.rs | 6 +++ crates/hir-ty/src/utils.rs | 5 +- crates/hir/src/attrs.rs | 5 +- crates/hir/src/display.rs | 20 +++++++- crates/hir/src/from_id.rs | 5 ++ crates/hir/src/has_source.rs | 10 +++- crates/hir/src/lib.rs | 39 ++++++++++++++- crates/hir/src/semantics.rs | 5 +- crates/hir/src/semantics/source_to_def.rs | 17 ++++++- crates/hir/src/source_analyzer.rs | 4 +- crates/hir/src/symbols.rs | 4 ++ .../src/handlers/fix_visibility.rs | 4 ++ crates/ide-completion/src/completions/type.rs | 4 +- crates/ide-completion/src/context.rs | 1 + crates/ide-completion/src/item.rs | 3 +- crates/ide-completion/src/render.rs | 3 ++ crates/ide-db/src/active_parameter.rs | 1 + crates/ide-db/src/defs.rs | 12 ++++- crates/ide-db/src/lib.rs | 2 + crates/ide-db/src/rename.rs | 1 + crates/ide-db/src/search.rs | 1 + crates/ide/src/doc_links.rs | 2 + crates/ide/src/hover/render.rs | 1 + crates/ide/src/moniker.rs | 3 ++ crates/ide/src/navigation_target.rs | 5 ++ crates/ide/src/signature_help.rs | 4 ++ .../ide/src/syntax_highlighting/highlight.rs | 1 + crates/ide/src/syntax_highlighting/inject.rs | 1 + crates/ide/src/syntax_highlighting/tags.rs | 1 + crates/rust-analyzer/src/to_proto.rs | 4 +- 47 files changed, 334 insertions(+), 75 deletions(-) diff --git a/crates/hir-def/src/attr.rs b/crates/hir-def/src/attr.rs index 79c2c188ff12..200072c172eb 100644 --- a/crates/hir-def/src/attr.rs +++ b/crates/hir-def/src/attr.rs @@ -300,6 +300,7 @@ impl AttrsWithOwner { AdtId::UnionId(it) => attrs_from_item_tree(it.lookup(db).id, db), }, AttrDefId::TraitId(it) => attrs_from_item_tree(it.lookup(db).id, db), + AttrDefId::TraitAliasId(it) => attrs_from_item_tree(it.lookup(db).id, db), AttrDefId::MacroId(it) => match it { MacroId::Macro2Id(it) => attrs_from_item_tree(it.lookup(db).id, db), MacroId::MacroRulesId(it) => attrs_from_item_tree(it.lookup(db).id, db), @@ -392,6 +393,7 @@ impl AttrsWithOwner { AttrDefId::StaticId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), AttrDefId::ConstId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), AttrDefId::TraitId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), + AttrDefId::TraitAliasId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), AttrDefId::TypeAliasId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), AttrDefId::MacroId(id) => match id { MacroId::Macro2Id(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), diff --git a/crates/hir-def/src/child_by_source.rs b/crates/hir-def/src/child_by_source.rs index 19d2fe956f09..68b57acca2ad 100644 --- a/crates/hir-def/src/child_by_source.rs +++ b/crates/hir-def/src/child_by_source.rs @@ -1,7 +1,7 @@ //! When *constructing* `hir`, we start at some parent syntax node and recursively //! lower the children. //! -//! This modules allows one to go in the opposite direction: start with a syntax +//! This module allows one to go in the opposite direction: start with a syntax //! node for a *child*, and get its hir. use either::Either; @@ -145,6 +145,7 @@ impl ChildBySource for ItemScope { ModuleDefId::StaticId(id) => insert!(map[keys::STATIC].insert(id)), ModuleDefId::TypeAliasId(id) => insert!(map[keys::TYPE_ALIAS].insert(id)), ModuleDefId::TraitId(id) => insert!(map[keys::TRAIT].insert(id)), + ModuleDefId::TraitAliasId(id) => insert!(map[keys::TRAIT_ALIAS].insert(id)), ModuleDefId::AdtId(adt) => match adt { AdtId::StructId(id) => insert!(map[keys::STRUCT].insert(id)), AdtId::UnionId(id) => insert!(map[keys::UNION].insert(id)), diff --git a/crates/hir-def/src/data.rs b/crates/hir-def/src/data.rs index c3c1dfd39ac0..ee6e269fe558 100644 --- a/crates/hir-def/src/data.rs +++ b/crates/hir-def/src/data.rs @@ -22,7 +22,7 @@ use crate::{ visibility::RawVisibility, AssocItemId, AstIdWithPath, ConstId, ConstLoc, FunctionId, FunctionLoc, HasModule, ImplId, Intern, ItemContainerId, ItemLoc, Lookup, Macro2Id, MacroRulesId, ModuleId, ProcMacroId, - StaticId, TraitId, TypeAliasId, TypeAliasLoc, + StaticId, TraitAliasId, TraitId, TypeAliasId, TypeAliasLoc, }; #[derive(Debug, Clone, PartialEq, Eq)] @@ -245,19 +245,11 @@ impl TraitData { attrs.by_key("rustc_skip_array_during_method_dispatch").exists(); let rustc_has_incoherent_inherent_impls = attrs.by_key("rustc_has_incoherent_inherent_impls").exists(); - let (items, attribute_calls, diagnostics) = match &tr_def.items { - Some(items) => { - let mut collector = AssocItemCollector::new( - db, - module_id, - tree_id.file_id(), - ItemContainerId::TraitId(tr), - ); - collector.collect(&item_tree, tree_id.tree_id(), items); - collector.finish() - } - None => Default::default(), - }; + let mut collector = + AssocItemCollector::new(db, module_id, tree_id.file_id(), ItemContainerId::TraitId(tr)); + collector.collect(&item_tree, tree_id.tree_id(), &tr_def.items); + let (items, attribute_calls, diagnostics) = collector.finish(); + ( Arc::new(TraitData { name, @@ -299,6 +291,23 @@ impl TraitData { } } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TraitAliasData { + pub name: Name, + pub visibility: RawVisibility, +} + +impl TraitAliasData { + pub(crate) fn trait_alias_query(db: &dyn DefDatabase, id: TraitAliasId) -> Arc { + let loc = id.lookup(db); + let item_tree = loc.id.item_tree(db); + let alias = &item_tree[loc.id.value]; + let visibility = item_tree[alias.visibility].clone(); + + Arc::new(TraitAliasData { name: alias.name.clone(), visibility }) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct ImplData { pub target_trait: Option>, diff --git a/crates/hir-def/src/db.rs b/crates/hir-def/src/db.rs index b23427a73b34..270cfa06e581 100644 --- a/crates/hir-def/src/db.rs +++ b/crates/hir-def/src/db.rs @@ -14,7 +14,7 @@ use crate::{ body::{scope::ExprScopes, Body, BodySourceMap}, data::{ ConstData, FunctionData, ImplData, Macro2Data, MacroRulesData, ProcMacroData, StaticData, - TraitData, TypeAliasData, + TraitAliasData, TraitData, TypeAliasData, }, generics::GenericParams, import_map::ImportMap, @@ -25,8 +25,8 @@ use crate::{ AttrDefId, BlockId, BlockLoc, ConstId, ConstLoc, DefWithBodyId, EnumId, EnumLoc, ExternBlockId, ExternBlockLoc, FunctionId, FunctionLoc, GenericDefId, ImplId, ImplLoc, LocalEnumVariantId, LocalFieldId, Macro2Id, Macro2Loc, MacroRulesId, MacroRulesLoc, ProcMacroId, ProcMacroLoc, - StaticId, StaticLoc, StructId, StructLoc, TraitId, TraitLoc, TypeAliasId, TypeAliasLoc, - UnionId, UnionLoc, VariantId, + StaticId, StaticLoc, StructId, StructLoc, TraitAliasId, TraitAliasLoc, TraitId, TraitLoc, + TypeAliasId, TypeAliasLoc, UnionId, UnionLoc, VariantId, }; #[salsa::query_group(InternDatabaseStorage)] @@ -46,6 +46,8 @@ pub trait InternDatabase: SourceDatabase { #[salsa::interned] fn intern_trait(&self, loc: TraitLoc) -> TraitId; #[salsa::interned] + fn intern_trait_alias(&self, loc: TraitAliasLoc) -> TraitAliasId; + #[salsa::interned] fn intern_type_alias(&self, loc: TypeAliasLoc) -> TypeAliasId; #[salsa::interned] fn intern_impl(&self, loc: ImplLoc) -> ImplId; @@ -125,6 +127,9 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast { #[salsa::invoke(TraitData::trait_data_with_diagnostics_query)] fn trait_data_with_diagnostics(&self, tr: TraitId) -> (Arc, Arc<[DefDiagnostic]>); + #[salsa::invoke(TraitAliasData::trait_alias_query)] + fn trait_alias_data(&self, e: TraitAliasId) -> Arc; + #[salsa::invoke(TypeAliasData::type_alias_data_query)] fn type_alias_data(&self, e: TypeAliasId) -> Arc; diff --git a/crates/hir-def/src/generics.rs b/crates/hir-def/src/generics.rs index 5a0aa1933880..e4912fa8a64a 100644 --- a/crates/hir-def/src/generics.rs +++ b/crates/hir-def/src/generics.rs @@ -187,6 +187,7 @@ impl GenericParams { GenericDefId::AdtId(AdtId::EnumId(id)) => id_to_generics!(id), GenericDefId::AdtId(AdtId::UnionId(id)) => id_to_generics!(id), GenericDefId::TraitId(id) => id_to_generics!(id), + GenericDefId::TraitAliasId(id) => id_to_generics!(id), GenericDefId::TypeAliasId(id) => id_to_generics!(id), GenericDefId::ImplId(id) => id_to_generics!(id), GenericDefId::EnumVariantId(_) | GenericDefId::ConstId(_) => { @@ -427,6 +428,10 @@ fn file_id_and_params_of( let src = it.lookup(db).source(db); (src.file_id, src.value.generic_param_list()) } + GenericDefId::TraitAliasId(it) => { + let src = it.lookup(db).source(db); + (src.file_id, src.value.generic_param_list()) + } GenericDefId::TypeAliasId(it) => { let src = it.lookup(db).source(db); (src.file_id, src.value.generic_param_list()) @@ -441,7 +446,7 @@ fn file_id_and_params_of( } impl HasChildSource for GenericDefId { - type Value = Either; + type Value = Either; fn child_source( &self, db: &dyn DefDatabase, @@ -453,11 +458,20 @@ impl HasChildSource for GenericDefId { let mut params = ArenaMap::default(); - // For traits the first type index is `Self`, we need to add it before the other params. - if let GenericDefId::TraitId(id) = *self { - let trait_ref = id.lookup(db).source(db).value; - let idx = idx_iter.next().unwrap(); - params.insert(idx, Either::Right(trait_ref)); + // For traits and trait aliases the first type index is `Self`, we need to add it before + // the other params. + match *self { + GenericDefId::TraitId(id) => { + let trait_ref = id.lookup(db).source(db).value; + let idx = idx_iter.next().unwrap(); + params.insert(idx, Either::Right(ast::TraitOrAlias::Trait(trait_ref))); + } + GenericDefId::TraitAliasId(id) => { + let alias = id.lookup(db).source(db).value; + let idx = idx_iter.next().unwrap(); + params.insert(idx, Either::Right(ast::TraitOrAlias::TraitAlias(alias))); + } + _ => {} } if let Some(generic_params_list) = generic_params_list { diff --git a/crates/hir-def/src/import_map.rs b/crates/hir-def/src/import_map.rs index 63e92df0e720..07a68d1598c5 100644 --- a/crates/hir-def/src/import_map.rs +++ b/crates/hir-def/src/import_map.rs @@ -268,6 +268,7 @@ pub enum ImportKind { Const, Static, Trait, + TraitAlias, TypeAlias, BuiltinType, AssociatedItem, @@ -463,6 +464,7 @@ fn item_import_kind(item: ItemInNs) -> Option { ModuleDefId::ConstId(_) => ImportKind::Const, ModuleDefId::StaticId(_) => ImportKind::Static, ModuleDefId::TraitId(_) => ImportKind::Trait, + ModuleDefId::TraitAliasId(_) => ImportKind::TraitAlias, ModuleDefId::TypeAliasId(_) => ImportKind::TypeAlias, ModuleDefId::BuiltinType(_) => ImportKind::BuiltinType, ModuleDefId::MacroId(_) => ImportKind::Macro, diff --git a/crates/hir-def/src/item_scope.rs b/crates/hir-def/src/item_scope.rs index 53a4173ff423..991e447033fe 100644 --- a/crates/hir-def/src/item_scope.rs +++ b/crates/hir-def/src/item_scope.rs @@ -431,6 +431,7 @@ impl PerNs { ModuleDefId::EnumVariantId(_) => PerNs::both(def, def, v), ModuleDefId::ConstId(_) | ModuleDefId::StaticId(_) => PerNs::values(def, v), ModuleDefId::TraitId(_) => PerNs::types(def, v), + ModuleDefId::TraitAliasId(_) => PerNs::types(def, v), ModuleDefId::TypeAliasId(_) => PerNs::types(def, v), ModuleDefId::BuiltinType(_) => PerNs::types(def, v), ModuleDefId::MacroId(mac) => PerNs::macros(mac, v), diff --git a/crates/hir-def/src/item_tree.rs b/crates/hir-def/src/item_tree.rs index 19d01630ef08..9da5b2d47c87 100644 --- a/crates/hir-def/src/item_tree.rs +++ b/crates/hir-def/src/item_tree.rs @@ -204,6 +204,7 @@ impl ItemTree { consts, statics, traits, + trait_aliases, impls, type_aliases, mods, @@ -226,6 +227,7 @@ impl ItemTree { consts.shrink_to_fit(); statics.shrink_to_fit(); traits.shrink_to_fit(); + trait_aliases.shrink_to_fit(); impls.shrink_to_fit(); type_aliases.shrink_to_fit(); mods.shrink_to_fit(); @@ -276,6 +278,7 @@ struct ItemTreeData { consts: Arena, statics: Arena, traits: Arena, + trait_aliases: Arena, impls: Arena, type_aliases: Arena, mods: Arena, @@ -496,6 +499,7 @@ mod_items! { Const in consts -> ast::Const, Static in statics -> ast::Static, Trait in traits -> ast::Trait, + TraitAlias in trait_aliases -> ast::TraitAlias, Impl in impls -> ast::Impl, TypeAlias in type_aliases -> ast::TypeAlias, Mod in mods -> ast::Module, @@ -672,11 +676,18 @@ pub struct Trait { pub generic_params: Interned, pub is_auto: bool, pub is_unsafe: bool, - /// This is [`None`] if this Trait is a trait alias. - pub items: Option>, + pub items: Box<[AssocItem]>, pub ast_id: FileAstId, } +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct TraitAlias { + pub name: Name, + pub visibility: RawVisibilityId, + pub generic_params: Interned, + pub ast_id: FileAstId, +} + #[derive(Debug, Clone, Eq, PartialEq)] pub struct Impl { pub generic_params: Interned, @@ -872,6 +883,7 @@ impl ModItem { | ModItem::Enum(_) | ModItem::Static(_) | ModItem::Trait(_) + | ModItem::TraitAlias(_) | ModItem::Impl(_) | ModItem::Mod(_) | ModItem::MacroRules(_) @@ -899,6 +911,7 @@ impl ModItem { ModItem::Const(it) => tree[it.index].ast_id().upcast(), ModItem::Static(it) => tree[it.index].ast_id().upcast(), ModItem::Trait(it) => tree[it.index].ast_id().upcast(), + ModItem::TraitAlias(it) => tree[it.index].ast_id().upcast(), ModItem::Impl(it) => tree[it.index].ast_id().upcast(), ModItem::TypeAlias(it) => tree[it.index].ast_id().upcast(), ModItem::Mod(it) => tree[it.index].ast_id().upcast(), diff --git a/crates/hir-def/src/item_tree/lower.rs b/crates/hir-def/src/item_tree/lower.rs index b64b4d11e373..495a8878c334 100644 --- a/crates/hir-def/src/item_tree/lower.rs +++ b/crates/hir-def/src/item_tree/lower.rs @@ -110,7 +110,7 @@ impl<'a> Ctx<'a> { ast::Item::Const(ast) => self.lower_const(ast).into(), ast::Item::Module(ast) => self.lower_module(ast)?.into(), ast::Item::Trait(ast) => self.lower_trait(ast)?.into(), - ast::Item::TraitAlias(_) => return None, + ast::Item::TraitAlias(ast) => self.lower_trait_alias(ast)?.into(), ast::Item::Impl(ast) => self.lower_impl(ast)?.into(), ast::Item::Use(ast) => self.lower_use(ast)?.into(), ast::Item::ExternCrate(ast) => self.lower_extern_crate(ast)?.into(), @@ -446,20 +446,39 @@ impl<'a> Ctx<'a> { self.lower_generic_params(HasImplicitSelf::Yes(trait_def.type_bound_list()), trait_def); let is_auto = trait_def.auto_token().is_some(); let is_unsafe = trait_def.unsafe_token().is_some(); - let items = trait_def.assoc_item_list().map(|list| { - list.assoc_items() - .filter_map(|item| { - let attrs = RawAttrs::new(self.db.upcast(), &item, self.hygiene()); - self.lower_assoc_item(&item).map(|item| { - self.add_attrs(ModItem::from(item).into(), attrs); - item - }) - }) - .collect() - }); let ast_id = self.source_ast_id_map.ast_id(trait_def); - let res = Trait { name, visibility, generic_params, is_auto, is_unsafe, items, ast_id }; - Some(id(self.data().traits.alloc(res))) + + let items = trait_def + .assoc_item_list() + .into_iter() + .flat_map(|list| list.assoc_items()) + .filter_map(|item| { + let attrs = RawAttrs::new(self.db.upcast(), &item, self.hygiene()); + self.lower_assoc_item(&item).map(|item| { + self.add_attrs(ModItem::from(item).into(), attrs); + item + }) + }) + .collect(); + + let def = Trait { name, visibility, generic_params, is_auto, is_unsafe, items, ast_id }; + Some(id(self.data().traits.alloc(def))) + } + + fn lower_trait_alias( + &mut self, + trait_alias_def: &ast::TraitAlias, + ) -> Option> { + let name = trait_alias_def.name()?.as_name(); + let visibility = self.lower_visibility(trait_alias_def); + let generic_params = self.lower_generic_params( + HasImplicitSelf::Yes(trait_alias_def.type_bound_list()), + trait_alias_def, + ); + let ast_id = self.source_ast_id_map.ast_id(trait_alias_def); + + let alias = TraitAlias { name, visibility, generic_params, ast_id }; + Some(id(self.data().trait_aliases.alloc(alias))) } fn lower_impl(&mut self, impl_def: &ast::Impl) -> Option> { diff --git a/crates/hir-def/src/item_tree/pretty.rs b/crates/hir-def/src/item_tree/pretty.rs index 8f230b87d010..5f29997964b9 100644 --- a/crates/hir-def/src/item_tree/pretty.rs +++ b/crates/hir-def/src/item_tree/pretty.rs @@ -374,23 +374,24 @@ impl<'a> Printer<'a> { } w!(self, "trait {}", name); self.print_generic_params(generic_params); - match items { - Some(items) => { - self.print_where_clause_and_opening_brace(generic_params); - self.indented(|this| { - for item in &**items { - this.print_mod_item((*item).into()); - } - }); + self.print_where_clause_and_opening_brace(generic_params); + self.indented(|this| { + for item in &**items { + this.print_mod_item((*item).into()); } - None => { - w!(self, " = "); - // FIXME: Print the aliased traits - self.print_where_clause_and_opening_brace(generic_params); - } - } + }); wln!(self, "}}"); } + ModItem::TraitAlias(it) => { + let TraitAlias { name, visibility, generic_params, ast_id: _ } = &self.tree[it]; + self.print_visibility(*visibility); + w!(self, "trait {}", name); + self.print_generic_params(generic_params); + w!(self, " = "); + self.print_where_clause(generic_params); + w!(self, ";"); + wln!(self); + } ModItem::Impl(it) => { let Impl { target_trait, self_ty, is_negative, items, generic_params, ast_id: _ } = &self.tree[it]; diff --git a/crates/hir-def/src/keys.rs b/crates/hir-def/src/keys.rs index 72beec8186c1..f30be6b64e3d 100644 --- a/crates/hir-def/src/keys.rs +++ b/crates/hir-def/src/keys.rs @@ -9,8 +9,8 @@ use syntax::{ast, AstNode, AstPtr}; use crate::{ dyn_map::{DynMap, Policy}, ConstId, EnumId, EnumVariantId, FieldId, FunctionId, ImplId, LifetimeParamId, Macro2Id, - MacroRulesId, ProcMacroId, StaticId, StructId, TraitId, TypeAliasId, TypeOrConstParamId, - UnionId, + MacroRulesId, ProcMacroId, StaticId, StructId, TraitAliasId, TraitId, TypeAliasId, + TypeOrConstParamId, UnionId, }; pub type Key = crate::dyn_map::Key>; @@ -21,6 +21,7 @@ pub const STATIC: Key = Key::new(); pub const TYPE_ALIAS: Key = Key::new(); pub const IMPL: Key = Key::new(); pub const TRAIT: Key = Key::new(); +pub const TRAIT_ALIAS: Key = Key::new(); pub const STRUCT: Key = Key::new(); pub const UNION: Key = Key::new(); pub const ENUM: Key = Key::new(); diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs index 2aab1ccd914c..8c2e93f09059 100644 --- a/crates/hir-def/src/lib.rs +++ b/crates/hir-def/src/lib.rs @@ -86,7 +86,7 @@ use crate::{ builtin_type::BuiltinType, item_tree::{ Const, Enum, Function, Impl, ItemTreeId, ItemTreeNode, MacroDef, MacroRules, ModItem, - Static, Struct, Trait, TypeAlias, Union, + Static, Struct, Trait, TraitAlias, TypeAlias, Union, }, }; @@ -261,6 +261,11 @@ pub struct TraitId(salsa::InternId); pub type TraitLoc = ItemLoc; impl_intern!(TraitId, TraitLoc, intern_trait, lookup_intern_trait); +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct TraitAliasId(salsa::InternId); +pub type TraitAliasLoc = ItemLoc; +impl_intern!(TraitAliasId, TraitAliasLoc, intern_trait_alias, lookup_intern_trait_alias); + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct TypeAliasId(salsa::InternId); type TypeAliasLoc = AssocItemLoc; @@ -453,6 +458,7 @@ pub enum ModuleDefId { ConstId(ConstId), StaticId(StaticId), TraitId(TraitId), + TraitAliasId(TraitAliasId), TypeAliasId(TypeAliasId), BuiltinType(BuiltinType), MacroId(MacroId), @@ -466,6 +472,7 @@ impl_from!( ConstId, StaticId, TraitId, + TraitAliasId, TypeAliasId, BuiltinType for ModuleDefId @@ -516,6 +523,7 @@ pub enum GenericDefId { FunctionId(FunctionId), AdtId(AdtId), TraitId(TraitId), + TraitAliasId(TraitAliasId), TypeAliasId(TypeAliasId), ImplId(ImplId), // enum variants cannot have generics themselves, but their parent enums @@ -528,6 +536,7 @@ impl_from!( FunctionId, AdtId(StructId, EnumId, UnionId), TraitId, + TraitAliasId, TypeAliasId, ImplId, EnumVariantId, @@ -555,6 +564,7 @@ pub enum AttrDefId { StaticId(StaticId), ConstId(ConstId), TraitId(TraitId), + TraitAliasId(TraitAliasId), TypeAliasId(TypeAliasId), MacroId(MacroId), ImplId(ImplId), @@ -714,6 +724,7 @@ impl HasModule for GenericDefId { GenericDefId::FunctionId(it) => it.lookup(db).module(db), GenericDefId::AdtId(it) => it.module(db), GenericDefId::TraitId(it) => it.lookup(db).container, + GenericDefId::TraitAliasId(it) => it.lookup(db).container, GenericDefId::TypeAliasId(it) => it.lookup(db).module(db), GenericDefId::ImplId(it) => it.lookup(db).container, GenericDefId::EnumVariantId(it) => it.parent.lookup(db).container, @@ -747,6 +758,7 @@ impl ModuleDefId { ModuleDefId::ConstId(id) => id.lookup(db).container.module(db), ModuleDefId::StaticId(id) => id.lookup(db).module(db), ModuleDefId::TraitId(id) => id.lookup(db).container, + ModuleDefId::TraitAliasId(id) => id.lookup(db).container, ModuleDefId::TypeAliasId(id) => id.lookup(db).module(db), ModuleDefId::MacroId(id) => id.module(db), ModuleDefId::BuiltinType(_) => return None, @@ -765,6 +777,7 @@ impl AttrDefId { AttrDefId::StaticId(it) => it.lookup(db).module(db).krate, AttrDefId::ConstId(it) => it.lookup(db).module(db).krate, AttrDefId::TraitId(it) => it.lookup(db).container.krate, + AttrDefId::TraitAliasId(it) => it.lookup(db).container.krate, AttrDefId::TypeAliasId(it) => it.lookup(db).module(db).krate, AttrDefId::ImplId(it) => it.lookup(db).container.krate, AttrDefId::ExternBlockId(it) => it.lookup(db).container.krate, diff --git a/crates/hir-def/src/nameres/collector.rs b/crates/hir-def/src/nameres/collector.rs index e3704bf2164b..70acc3442c30 100644 --- a/crates/hir-def/src/nameres/collector.rs +++ b/crates/hir-def/src/nameres/collector.rs @@ -51,7 +51,8 @@ use crate::{ AdtId, AstId, AstIdWithPath, ConstLoc, EnumLoc, EnumVariantId, ExternBlockLoc, FunctionId, FunctionLoc, ImplLoc, Intern, ItemContainerId, LocalModuleId, Macro2Id, Macro2Loc, MacroExpander, MacroId, MacroRulesId, MacroRulesLoc, ModuleDefId, ModuleId, ProcMacroId, - ProcMacroLoc, StaticLoc, StructLoc, TraitLoc, TypeAliasLoc, UnionLoc, UnresolvedMacro, + ProcMacroLoc, StaticLoc, StructLoc, TraitAliasLoc, TraitLoc, TypeAliasLoc, UnionLoc, + UnresolvedMacro, }; static GLOB_RECURSION_LIMIT: Limit = Limit::new(100); @@ -1709,6 +1710,20 @@ impl ModCollector<'_, '_> { false, ); } + ModItem::TraitAlias(id) => { + let it = &self.item_tree[id]; + + let vis = resolve_vis(def_map, &self.item_tree[it.visibility]); + update_def( + self.def_collector, + TraitAliasLoc { container: module, id: ItemTreeId::new(self.tree_id, id) } + .intern(db) + .into(), + &it.name, + vis, + false, + ); + } ModItem::TypeAlias(id) => { let it = &self.item_tree[id]; diff --git a/crates/hir-def/src/nameres/tests/incremental.rs b/crates/hir-def/src/nameres/tests/incremental.rs index f5190b76db05..13e6825f8210 100644 --- a/crates/hir-def/src/nameres/tests/incremental.rs +++ b/crates/hir-def/src/nameres/tests/incremental.rs @@ -223,6 +223,7 @@ pub type Ty = (); ModuleDefId::ConstId(it) => drop(db.const_data(it)), ModuleDefId::StaticId(it) => drop(db.static_data(it)), ModuleDefId::TraitId(it) => drop(db.trait_data(it)), + ModuleDefId::TraitAliasId(it) => drop(db.trait_alias_data(it)), ModuleDefId::TypeAliasId(it) => drop(db.type_alias_data(it)), ModuleDefId::EnumVariantId(_) | ModuleDefId::ModuleId(_) diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs index 36d8b24e9c33..043d49f768fc 100644 --- a/crates/hir-def/src/resolver.rs +++ b/crates/hir-def/src/resolver.rs @@ -22,7 +22,8 @@ use crate::{ AdtId, AssocItemId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, ExternBlockId, FunctionId, GenericDefId, GenericParamId, HasModule, ImplId, ItemContainerId, LifetimeParamId, LocalModuleId, Lookup, Macro2Id, MacroId, MacroRulesId, ModuleDefId, ModuleId, ProcMacroId, - StaticId, StructId, TraitId, TypeAliasId, TypeOrConstParamId, TypeParamId, VariantId, + StaticId, StructId, TraitAliasId, TraitId, TypeAliasId, TypeOrConstParamId, TypeParamId, + VariantId, }; #[derive(Debug, Clone)] @@ -74,6 +75,7 @@ pub enum TypeNs { TypeAliasId(TypeAliasId), BuiltinType(BuiltinType), TraitId(TraitId), + TraitAliasId(TraitAliasId), // Module belong to type ns, but the resolver is used when all module paths // are fully resolved. // ModuleId(ModuleId) @@ -402,6 +404,8 @@ impl Resolver { } pub fn traits_in_scope(&self, db: &dyn DefDatabase) -> FxHashSet { + // FIXME(trait_alias): Trait alias brings aliased traits in scope! Note that supertraits of + // aliased traits are NOT brought in scope (unless also aliased). let mut traits = FxHashSet::default(); for scope in self.scopes() { @@ -650,6 +654,7 @@ impl ModuleItemMap { let ty = match module_def.take_types()? { ModuleDefId::AdtId(it) => TypeNs::AdtId(it), ModuleDefId::TraitId(it) => TypeNs::TraitId(it), + ModuleDefId::TraitAliasId(it) => TypeNs::TraitAliasId(it), ModuleDefId::TypeAliasId(it) => TypeNs::TypeAliasId(it), ModuleDefId::BuiltinType(it) => TypeNs::BuiltinType(it), @@ -687,6 +692,7 @@ fn to_value_ns(per_ns: PerNs) -> Option { ModuleDefId::AdtId(AdtId::EnumId(_) | AdtId::UnionId(_)) | ModuleDefId::TraitId(_) + | ModuleDefId::TraitAliasId(_) | ModuleDefId::TypeAliasId(_) | ModuleDefId::BuiltinType(_) | ModuleDefId::MacroId(_) @@ -704,6 +710,7 @@ fn to_type_ns(per_ns: PerNs) -> Option { ModuleDefId::BuiltinType(it) => TypeNs::BuiltinType(it), ModuleDefId::TraitId(it) => TypeNs::TraitId(it), + ModuleDefId::TraitAliasId(it) => TypeNs::TraitAliasId(it), ModuleDefId::FunctionId(_) | ModuleDefId::ConstId(_) @@ -788,6 +795,12 @@ impl HasResolver for TraitId { } } +impl HasResolver for TraitAliasId { + fn resolver(self, db: &dyn DefDatabase) -> Resolver { + self.lookup(db).container.resolver(db).push_generic_params_scope(db, self.into()) + } +} + impl + Copy> HasResolver for T { fn resolver(self, db: &dyn DefDatabase) -> Resolver { let def = self.into(); @@ -867,6 +880,7 @@ impl HasResolver for GenericDefId { GenericDefId::FunctionId(inner) => inner.resolver(db), GenericDefId::AdtId(adt) => adt.resolver(db), GenericDefId::TraitId(inner) => inner.resolver(db), + GenericDefId::TraitAliasId(inner) => inner.resolver(db), GenericDefId::TypeAliasId(inner) => inner.resolver(db), GenericDefId::ImplId(inner) => inner.resolver(db), GenericDefId::EnumVariantId(inner) => inner.parent.resolver(db), diff --git a/crates/hir-ty/src/diagnostics/decl_check.rs b/crates/hir-ty/src/diagnostics/decl_check.rs index f7031a8546a2..f4d1013ceb04 100644 --- a/crates/hir-ty/src/diagnostics/decl_check.rs +++ b/crates/hir-ty/src/diagnostics/decl_check.rs @@ -178,6 +178,7 @@ impl<'a> DeclValidator<'a> { AttrDefId::StaticId(sid) => Some(sid.lookup(self.db.upcast()).container.into()), AttrDefId::ConstId(cid) => Some(cid.lookup(self.db.upcast()).container.into()), AttrDefId::TraitId(tid) => Some(tid.lookup(self.db.upcast()).container.into()), + AttrDefId::TraitAliasId(taid) => Some(taid.lookup(self.db.upcast()).container.into()), AttrDefId::ImplId(iid) => Some(iid.lookup(self.db.upcast()).container.into()), AttrDefId::ExternBlockId(id) => Some(id.lookup(self.db.upcast()).container.into()), // These warnings should not explore macro definitions at all diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index c77a5e073757..535e0ab11554 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -868,7 +868,10 @@ impl<'a> InferenceContext<'a> { // FIXME potentially resolve assoc type (self.err_ty(), None) } - TypeNs::AdtId(AdtId::EnumId(_)) | TypeNs::BuiltinType(_) | TypeNs::TraitId(_) => { + TypeNs::AdtId(AdtId::EnumId(_)) + | TypeNs::BuiltinType(_) + | TypeNs::TraitId(_) + | TypeNs::TraitAliasId(_) => { // FIXME diagnostic (self.err_ty(), None) } diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs index 296c6c5b21fa..23b15087e316 100644 --- a/crates/hir-ty/src/lower.rs +++ b/crates/hir-ty/src/lower.rs @@ -522,6 +522,10 @@ impl<'a> TyLoweringContext<'a> { }; return (ty, None); } + TypeNs::TraitAliasId(_) => { + // FIXME(trait_alias): Implement trait alias. + return (TyKind::Error.intern(Interner), None); + } TypeNs::GenericParam(param_id) => { let generics = generics( self.db.upcast(), @@ -877,6 +881,7 @@ impl<'a> TyLoweringContext<'a> { ) -> Option { let resolved = match self.resolver.resolve_path_in_type_ns_fully(self.db.upcast(), path.mod_path())? { + // FIXME(trait_alias): We need to handle trait alias here. TypeNs::TraitId(tr) => tr, _ => return None, }; @@ -1442,6 +1447,7 @@ pub(crate) fn trait_environment_query( GenericDefId::FunctionId(f) => Some(f.lookup(db.upcast()).container), GenericDefId::AdtId(_) => None, GenericDefId::TraitId(_) => None, + GenericDefId::TraitAliasId(_) => None, GenericDefId::TypeAliasId(t) => Some(t.lookup(db.upcast()).container), GenericDefId::ImplId(_) => None, GenericDefId::EnumVariantId(_) => None, diff --git a/crates/hir-ty/src/utils.rs b/crates/hir-ty/src/utils.rs index f69cb6bacbd1..34d957e26ef5 100644 --- a/crates/hir-ty/src/utils.rs +++ b/crates/hir-ty/src/utils.rs @@ -315,7 +315,10 @@ fn parent_generic_def(db: &dyn DefDatabase, def: GenericDefId) -> Option it.lookup(db).container, GenericDefId::ConstId(it) => it.lookup(db).container, GenericDefId::EnumVariantId(it) => return Some(it.parent.into()), - GenericDefId::AdtId(_) | GenericDefId::TraitId(_) | GenericDefId::ImplId(_) => return None, + GenericDefId::AdtId(_) + | GenericDefId::TraitId(_) + | GenericDefId::ImplId(_) + | GenericDefId::TraitAliasId(_) => return None, }; match container { diff --git a/crates/hir/src/attrs.rs b/crates/hir/src/attrs.rs index 54425d69b6b7..db0b84ef0887 100644 --- a/crates/hir/src/attrs.rs +++ b/crates/hir/src/attrs.rs @@ -14,7 +14,8 @@ use syntax::{ast, AstNode}; use crate::{ Adt, AssocItem, Const, ConstParam, Enum, Field, Function, GenericParam, Impl, LifetimeParam, - Macro, Module, ModuleDef, Static, Struct, Trait, TypeAlias, TypeParam, Union, Variant, + Macro, Module, ModuleDef, Static, Struct, Trait, TraitAlias, TypeAlias, TypeParam, Union, + Variant, }; pub trait HasAttrs { @@ -60,6 +61,7 @@ impl_has_attrs![ (Static, StaticId), (Const, ConstId), (Trait, TraitId), + (TraitAlias, TraitAliasId), (TypeAlias, TypeAliasId), (Macro, MacroId), (Function, FunctionId), @@ -134,6 +136,7 @@ fn resolve_doc_path( AttrDefId::StaticId(it) => it.resolver(db.upcast()), AttrDefId::ConstId(it) => it.resolver(db.upcast()), AttrDefId::TraitId(it) => it.resolver(db.upcast()), + AttrDefId::TraitAliasId(it) => it.resolver(db.upcast()), AttrDefId::TypeAliasId(it) => it.resolver(db.upcast()), AttrDefId::ImplId(it) => it.resolver(db.upcast()), AttrDefId::ExternBlockId(it) => it.resolver(db.upcast()), diff --git a/crates/hir/src/display.rs b/crates/hir/src/display.rs index 66bf2a2900e8..5aae92efd19e 100644 --- a/crates/hir/src/display.rs +++ b/crates/hir/src/display.rs @@ -18,8 +18,8 @@ use hir_ty::{ use crate::{ Adt, AsAssocItem, AssocItemContainer, Const, ConstParam, Enum, Field, Function, GenericParam, - HasCrate, HasVisibility, LifetimeParam, Macro, Module, Static, Struct, Trait, TyBuilder, Type, - TypeAlias, TypeOrConstParam, TypeParam, Union, Variant, + HasCrate, HasVisibility, LifetimeParam, Macro, Module, Static, Struct, Trait, TraitAlias, + TyBuilder, Type, TypeAlias, TypeOrConstParam, TypeParam, Union, Variant, }; impl HirDisplay for Function { @@ -501,6 +501,22 @@ impl HirDisplay for Trait { } } +impl HirDisplay for TraitAlias { + fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { + write_visibility(self.module(f.db).id, self.visibility(f.db), f)?; + let data = f.db.trait_alias_data(self.id); + write!(f, "trait {}", data.name)?; + let def_id = GenericDefId::TraitAliasId(self.id); + write_generic_params(def_id, f)?; + f.write_str(" = ")?; + // FIXME: Currently we lower every bounds in a trait alias as a trait bound on `Self` i.e. + // `trait Foo = Bar` is stored and displayed as `trait Foo = where Self: Bar`, which might + // be less readable. + write_where_clause(def_id, f)?; + Ok(()) + } +} + impl HirDisplay for TypeAlias { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { write_visibility(self.module(f.db).id, self.visibility(f.db), f)?; diff --git a/crates/hir/src/from_id.rs b/crates/hir/src/from_id.rs index f825a72c0f58..432769195675 100644 --- a/crates/hir/src/from_id.rs +++ b/crates/hir/src/from_id.rs @@ -37,6 +37,7 @@ from_id![ (hir_def::EnumId, crate::Enum), (hir_def::TypeAliasId, crate::TypeAlias), (hir_def::TraitId, crate::Trait), + (hir_def::TraitAliasId, crate::TraitAlias), (hir_def::StaticId, crate::Static), (hir_def::ConstId, crate::Const), (hir_def::FunctionId, crate::Function), @@ -110,6 +111,7 @@ impl From for ModuleDef { ModuleDefId::ConstId(it) => ModuleDef::Const(it.into()), ModuleDefId::StaticId(it) => ModuleDef::Static(it.into()), ModuleDefId::TraitId(it) => ModuleDef::Trait(it.into()), + ModuleDefId::TraitAliasId(it) => ModuleDef::TraitAlias(it.into()), ModuleDefId::TypeAliasId(it) => ModuleDef::TypeAlias(it.into()), ModuleDefId::BuiltinType(it) => ModuleDef::BuiltinType(it.into()), ModuleDefId::MacroId(it) => ModuleDef::Macro(it.into()), @@ -127,6 +129,7 @@ impl From for ModuleDefId { ModuleDef::Const(it) => ModuleDefId::ConstId(it.into()), ModuleDef::Static(it) => ModuleDefId::StaticId(it.into()), ModuleDef::Trait(it) => ModuleDefId::TraitId(it.into()), + ModuleDef::TraitAlias(it) => ModuleDefId::TraitAliasId(it.into()), ModuleDef::TypeAlias(it) => ModuleDefId::TypeAliasId(it.into()), ModuleDef::BuiltinType(it) => ModuleDefId::BuiltinType(it.into()), ModuleDef::Macro(it) => ModuleDefId::MacroId(it.into()), @@ -172,6 +175,7 @@ impl From for GenericDefId { GenericDef::Function(it) => GenericDefId::FunctionId(it.id), GenericDef::Adt(it) => GenericDefId::AdtId(it.into()), GenericDef::Trait(it) => GenericDefId::TraitId(it.id), + GenericDef::TraitAlias(it) => GenericDefId::TraitAliasId(it.id), GenericDef::TypeAlias(it) => GenericDefId::TypeAliasId(it.id), GenericDef::Impl(it) => GenericDefId::ImplId(it.id), GenericDef::Variant(it) => GenericDefId::EnumVariantId(it.into()), @@ -186,6 +190,7 @@ impl From for GenericDef { GenericDefId::FunctionId(it) => GenericDef::Function(it.into()), GenericDefId::AdtId(it) => GenericDef::Adt(it.into()), GenericDefId::TraitId(it) => GenericDef::Trait(it.into()), + GenericDefId::TraitAliasId(it) => GenericDef::TraitAlias(it.into()), GenericDefId::TypeAliasId(it) => GenericDef::TypeAlias(it.into()), GenericDefId::ImplId(it) => GenericDef::Impl(it.into()), GenericDefId::EnumVariantId(it) => GenericDef::Variant(it.into()), diff --git a/crates/hir/src/has_source.rs b/crates/hir/src/has_source.rs index f8b01db3e328..a28b2d577aa1 100644 --- a/crates/hir/src/has_source.rs +++ b/crates/hir/src/has_source.rs @@ -11,7 +11,7 @@ use syntax::ast; use crate::{ db::HirDatabase, Adt, Const, Enum, Field, FieldSource, Function, Impl, LifetimeParam, Macro, - Module, Static, Struct, Trait, TypeAlias, TypeOrConstParam, Union, Variant, + Module, Static, Struct, Trait, TraitAlias, TypeAlias, TypeOrConstParam, Union, Variant, }; pub trait HasSource { @@ -122,6 +122,12 @@ impl HasSource for Trait { Some(self.id.lookup(db.upcast()).source(db.upcast())) } } +impl HasSource for TraitAlias { + type Ast = ast::TraitAlias; + fn source(self, db: &dyn HirDatabase) -> Option> { + Some(self.id.lookup(db.upcast()).source(db.upcast())) + } +} impl HasSource for TypeAlias { type Ast = ast::TypeAlias; fn source(self, db: &dyn HirDatabase) -> Option> { @@ -158,7 +164,7 @@ impl HasSource for Impl { } impl HasSource for TypeOrConstParam { - type Ast = Either; + type Ast = Either; fn source(self, db: &dyn HirDatabase) -> Option> { let child_source = self.id.parent.child_source(db.upcast()); Some(child_source.map(|it| it[self.id.local_id].clone())) diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index c64106d3afde..27f35bd26446 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -53,7 +53,7 @@ use hir_def::{ AdtId, AssocItemId, AssocItemLoc, AttrDefId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, FunctionId, GenericDefId, HasModule, ImplId, ItemContainerId, LifetimeParamId, LocalEnumVariantId, LocalFieldId, Lookup, MacroExpander, MacroId, ModuleId, StaticId, StructId, - TraitId, TypeAliasId, TypeOrConstParamId, TypeParamId, UnionId, + TraitAliasId, TraitId, TypeAliasId, TypeOrConstParamId, TypeParamId, UnionId, }; use hir_expand::{name::name, MacroCallKind}; use hir_ty::{ @@ -272,6 +272,7 @@ pub enum ModuleDef { Const(Const), Static(Static), Trait(Trait), + TraitAlias(TraitAlias), TypeAlias(TypeAlias), BuiltinType(BuiltinType), Macro(Macro), @@ -284,6 +285,7 @@ impl_from!( Const, Static, Trait, + TraitAlias, TypeAlias, BuiltinType, Macro @@ -310,6 +312,7 @@ impl ModuleDef { ModuleDef::Const(it) => Some(it.module(db)), ModuleDef::Static(it) => Some(it.module(db)), ModuleDef::Trait(it) => Some(it.module(db)), + ModuleDef::TraitAlias(it) => Some(it.module(db)), ModuleDef::TypeAlias(it) => Some(it.module(db)), ModuleDef::Macro(it) => Some(it.module(db)), ModuleDef::BuiltinType(_) => None, @@ -338,6 +341,7 @@ impl ModuleDef { ModuleDef::Const(it) => it.name(db)?, ModuleDef::Adt(it) => it.name(db), ModuleDef::Trait(it) => it.name(db), + ModuleDef::TraitAlias(it) => it.name(db), ModuleDef::Function(it) => it.name(db), ModuleDef::Variant(it) => it.name(db), ModuleDef::TypeAlias(it) => it.name(db), @@ -356,6 +360,7 @@ impl ModuleDef { Adt::Union(it) => it.id.into(), }, ModuleDef::Trait(it) => it.id.into(), + ModuleDef::TraitAlias(it) => it.id.into(), ModuleDef::Function(it) => it.id.into(), ModuleDef::TypeAlias(it) => it.id.into(), ModuleDef::Module(it) => it.id.into(), @@ -398,6 +403,7 @@ impl ModuleDef { ModuleDef::Module(_) | ModuleDef::Adt(_) | ModuleDef::Trait(_) + | ModuleDef::TraitAlias(_) | ModuleDef::TypeAlias(_) | ModuleDef::Macro(_) | ModuleDef::BuiltinType(_) => None, @@ -413,6 +419,7 @@ impl ModuleDef { ModuleDef::Const(it) => it.attrs(db), ModuleDef::Static(it) => it.attrs(db), ModuleDef::Trait(it) => it.attrs(db), + ModuleDef::TraitAlias(it) => it.attrs(db), ModuleDef::TypeAlias(it) => it.attrs(db), ModuleDef::Macro(it) => it.attrs(db), ModuleDef::BuiltinType(_) => return None, @@ -429,6 +436,7 @@ impl HasVisibility for ModuleDef { ModuleDef::Const(it) => it.visibility(db), ModuleDef::Static(it) => it.visibility(db), ModuleDef::Trait(it) => it.visibility(db), + ModuleDef::TraitAlias(it) => it.visibility(db), ModuleDef::TypeAlias(it) => it.visibility(db), ModuleDef::Variant(it) => it.visibility(db), ModuleDef::Macro(it) => it.visibility(db), @@ -1934,6 +1942,27 @@ impl HasVisibility for Trait { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct TraitAlias { + pub(crate) id: TraitAliasId, +} + +impl TraitAlias { + pub fn module(self, db: &dyn HirDatabase) -> Module { + Module { id: self.id.lookup(db.upcast()).container } + } + + pub fn name(self, db: &dyn HirDatabase) -> Name { + db.trait_alias_data(self.id).name.clone() + } +} + +impl HasVisibility for TraitAlias { + fn visibility(&self, db: &dyn HirDatabase) -> Visibility { + db.trait_alias_data(self.id).visibility.resolve(db.upcast(), &self.id.resolver(db.upcast())) + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct TypeAlias { pub(crate) id: TypeAliasId, @@ -2306,6 +2335,7 @@ pub enum GenericDef { Function(Function), Adt(Adt), Trait(Trait), + TraitAlias(TraitAlias), TypeAlias(TypeAlias), Impl(Impl), // enum variants cannot have generics themselves, but their parent enums @@ -2318,6 +2348,7 @@ impl_from!( Function, Adt(Struct, Enum, Union), Trait, + TraitAlias, TypeAlias, Impl, Variant, @@ -4066,6 +4097,12 @@ impl HasCrate for Trait { } } +impl HasCrate for TraitAlias { + fn krate(&self, db: &dyn HirDatabase) -> Crate { + self.module(db).krate() + } +} + impl HasCrate for Static { fn krate(&self, db: &dyn HirDatabase) -> Crate { self.module(db).krate() diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index b3ba1c7d349f..be2dffc29ccc 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -68,7 +68,8 @@ impl PathResolution { | ModuleDef::Function(_) | ModuleDef::Module(_) | ModuleDef::Static(_) - | ModuleDef::Trait(_), + | ModuleDef::Trait(_) + | ModuleDef::TraitAlias(_), ) => None, PathResolution::Def(ModuleDef::TypeAlias(alias)) => { Some(TypeNs::TypeAliasId((*alias).into())) @@ -1330,6 +1331,7 @@ impl<'db> SemanticsImpl<'db> { }) } ChildContainer::TraitId(it) => it.resolver(self.db.upcast()), + ChildContainer::TraitAliasId(it) => it.resolver(self.db.upcast()), ChildContainer::ImplId(it) => it.resolver(self.db.upcast()), ChildContainer::ModuleId(it) => it.resolver(self.db.upcast()), ChildContainer::EnumId(it) => it.resolver(self.db.upcast()), @@ -1556,6 +1558,7 @@ to_def_impls![ (crate::Enum, ast::Enum, enum_to_def), (crate::Union, ast::Union, union_to_def), (crate::Trait, ast::Trait, trait_to_def), + (crate::TraitAlias, ast::TraitAlias, trait_alias_to_def), (crate::Impl, ast::Impl, impl_to_def), (crate::TypeAlias, ast::TypeAlias, type_alias_to_def), (crate::Const, ast::Const, const_to_def), diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs index 2b5bfda1d434..ddfec20e3f9a 100644 --- a/crates/hir/src/semantics/source_to_def.rs +++ b/crates/hir/src/semantics/source_to_def.rs @@ -93,7 +93,7 @@ use hir_def::{ keys::{self, Key}, AdtId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, FieldId, FunctionId, GenericDefId, GenericParamId, ImplId, LifetimeParamId, MacroId, ModuleId, StaticId, StructId, - TraitId, TypeAliasId, TypeParamId, UnionId, VariantId, + TraitAliasId, TraitId, TypeAliasId, TypeParamId, UnionId, VariantId, }; use hir_expand::{attrs::AttrId, name::AsName, HirFileId, MacroCallId}; use rustc_hash::FxHashMap; @@ -159,6 +159,12 @@ impl SourceToDefCtx<'_, '_> { pub(super) fn trait_to_def(&mut self, src: InFile) -> Option { self.to_def(src, keys::TRAIT) } + pub(super) fn trait_alias_to_def( + &mut self, + src: InFile, + ) -> Option { + self.to_def(src, keys::TRAIT_ALIAS) + } pub(super) fn impl_to_def(&mut self, src: InFile) -> Option { self.to_def(src, keys::IMPL) } @@ -353,6 +359,9 @@ impl SourceToDefCtx<'_, '_> { match item { ast::Item::Module(it) => self.module_to_def(container.with_value(it))?.into(), ast::Item::Trait(it) => self.trait_to_def(container.with_value(it))?.into(), + ast::Item::TraitAlias(it) => { + self.trait_alias_to_def(container.with_value(it))?.into() + } ast::Item::Impl(it) => self.impl_to_def(container.with_value(it))?.into(), ast::Item::Enum(it) => self.enum_to_def(container.with_value(it))?.into(), ast::Item::TypeAlias(it) => { @@ -400,6 +409,9 @@ impl SourceToDefCtx<'_, '_> { ast::Item::Struct(it) => self.struct_to_def(InFile::new(file_id, it))?.into(), ast::Item::Enum(it) => self.enum_to_def(InFile::new(file_id, it))?.into(), ast::Item::Trait(it) => self.trait_to_def(InFile::new(file_id, it))?.into(), + ast::Item::TraitAlias(it) => { + self.trait_alias_to_def(InFile::new(file_id, it))?.into() + } ast::Item::TypeAlias(it) => { self.type_alias_to_def(InFile::new(file_id, it))?.into() } @@ -435,6 +447,7 @@ pub(crate) enum ChildContainer { DefWithBodyId(DefWithBodyId), ModuleId(ModuleId), TraitId(TraitId), + TraitAliasId(TraitAliasId), ImplId(ImplId), EnumId(EnumId), VariantId(VariantId), @@ -447,6 +460,7 @@ impl_from! { DefWithBodyId, ModuleId, TraitId, + TraitAliasId, ImplId, EnumId, VariantId, @@ -462,6 +476,7 @@ impl ChildContainer { ChildContainer::DefWithBodyId(it) => it.child_by_source(db, file_id), ChildContainer::ModuleId(it) => it.child_by_source(db, file_id), ChildContainer::TraitId(it) => it.child_by_source(db, file_id), + ChildContainer::TraitAliasId(_) => DynMap::default(), ChildContainer::ImplId(it) => it.child_by_source(db, file_id), ChildContainer::EnumId(it) => it.child_by_source(db, file_id), ChildContainer::VariantId(it) => it.child_by_source(db, file_id), diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index 61e58cb1c4a7..ada529fa74c2 100644 --- a/crates/hir/src/source_analyzer.rs +++ b/crates/hir/src/source_analyzer.rs @@ -51,7 +51,7 @@ use syntax::{ use crate::{ db::HirDatabase, semantics::PathResolution, Adt, AssocItem, BindingMode, BuiltinAttr, BuiltinType, Callable, Const, DeriveHelper, Field, Function, Local, Macro, ModuleDef, Static, - Struct, ToolModule, Trait, Type, TypeAlias, Variant, + Struct, ToolModule, Trait, TraitAlias, Type, TypeAlias, Variant, }; /// `SourceAnalyzer` is a convenience wrapper which exposes HIR API in terms of @@ -978,6 +978,7 @@ fn resolve_hir_path_( TypeNs::TypeAliasId(it) => PathResolution::Def(TypeAlias::from(it).into()), TypeNs::BuiltinType(it) => PathResolution::Def(BuiltinType::from(it).into()), TypeNs::TraitId(it) => PathResolution::Def(Trait::from(it).into()), + TypeNs::TraitAliasId(it) => PathResolution::Def(TraitAlias::from(it).into()), }; match unresolved { Some(unresolved) => resolver @@ -1065,6 +1066,7 @@ fn resolve_hir_path_qualifier( TypeNs::TypeAliasId(it) => PathResolution::Def(TypeAlias::from(it).into()), TypeNs::BuiltinType(it) => PathResolution::Def(BuiltinType::from(it).into()), TypeNs::TraitId(it) => PathResolution::Def(Trait::from(it).into()), + TypeNs::TraitAliasId(it) => PathResolution::Def(TraitAlias::from(it).into()), }) .or_else(|| { resolver diff --git a/crates/hir/src/symbols.rs b/crates/hir/src/symbols.rs index fd78decda4e6..a9afa1c6f451 100644 --- a/crates/hir/src/symbols.rs +++ b/crates/hir/src/symbols.rs @@ -68,6 +68,7 @@ pub enum FileSymbolKind { Static, Struct, Trait, + TraitAlias, TypeAlias, Union, } @@ -153,6 +154,9 @@ impl<'a> SymbolCollector<'a> { self.push_decl(id, FileSymbolKind::Trait); self.collect_from_trait(id); } + ModuleDefId::TraitAliasId(id) => { + self.push_decl(id, FileSymbolKind::TraitAlias); + } ModuleDefId::TypeAliasId(id) => { self.push_decl_assoc(id, FileSymbolKind::TypeAlias); } diff --git a/crates/ide-assists/src/handlers/fix_visibility.rs b/crates/ide-assists/src/handlers/fix_visibility.rs index d9e00435ecf5..4c61678eab4f 100644 --- a/crates/ide-assists/src/handlers/fix_visibility.rs +++ b/crates/ide-assists/src/handlers/fix_visibility.rs @@ -192,6 +192,10 @@ fn target_data_for_def( target_name = Some(t.name(db)); offset_target_and_file_id(db, t)? } + hir::ModuleDef::TraitAlias(t) => { + target_name = Some(t.name(db)); + offset_target_and_file_id(db, t)? + } hir::ModuleDef::TypeAlias(t) => { target_name = Some(t.name(db)); offset_target_and_file_id(db, t)? diff --git a/crates/ide-completion/src/completions/type.rs b/crates/ide-completion/src/completions/type.rs index 37849c251a48..69c05a76df4c 100644 --- a/crates/ide-completion/src/completions/type.rs +++ b/crates/ide-completion/src/completions/type.rs @@ -33,7 +33,9 @@ pub(crate) fn complete_type_path( // Don't suggest attribute macros and derives. ScopeDef::ModuleDef(Macro(mac)) => mac.is_fn_like(ctx.db), // Type things are fine - ScopeDef::ModuleDef(BuiltinType(_) | Adt(_) | Module(_) | Trait(_) | TypeAlias(_)) + ScopeDef::ModuleDef( + BuiltinType(_) | Adt(_) | Module(_) | Trait(_) | TraitAlias(_) | TypeAlias(_), + ) | ScopeDef::AdtSelfType(_) | ScopeDef::Unknown | ScopeDef::GenericParam(TypeParam(_)) => true, diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index ea54068b0f8b..92e64cd12383 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -416,6 +416,7 @@ impl<'a> CompletionContext<'a> { hir::ModuleDef::Const(it) => self.is_visible(it), hir::ModuleDef::Static(it) => self.is_visible(it), hir::ModuleDef::Trait(it) => self.is_visible(it), + hir::ModuleDef::TraitAlias(it) => self.is_visible(it), hir::ModuleDef::TypeAlias(it) => self.is_visible(it), hir::ModuleDef::Macro(it) => self.is_visible(it), hir::ModuleDef::BuiltinType(_) => Visible::Yes, diff --git a/crates/ide-completion/src/item.rs b/crates/ide-completion/src/item.rs index 2f65491d85e1..bb9fa7ccaccc 100644 --- a/crates/ide-completion/src/item.rs +++ b/crates/ide-completion/src/item.rs @@ -288,7 +288,7 @@ impl_from!(SymbolKind for CompletionItemKind); impl CompletionItemKind { #[cfg(test)] - pub(crate) fn tag(&self) -> &'static str { + pub(crate) fn tag(self) -> &'static str { match self { CompletionItemKind::SymbolKind(kind) => match kind { SymbolKind::Attribute => "at", @@ -312,6 +312,7 @@ impl CompletionItemKind { SymbolKind::Struct => "st", SymbolKind::ToolModule => "tm", SymbolKind::Trait => "tt", + SymbolKind::TraitAlias => "tr", SymbolKind::TypeAlias => "ta", SymbolKind::TypeParam => "tp", SymbolKind::Union => "un", diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs index d99ad5f9f04b..c1f51aabb967 100644 --- a/crates/ide-completion/src/render.rs +++ b/crates/ide-completion/src/render.rs @@ -367,6 +367,9 @@ fn res_to_kind(resolution: ScopeDef) -> CompletionItemKind { ScopeDef::ModuleDef(Const(..)) => CompletionItemKind::SymbolKind(SymbolKind::Const), ScopeDef::ModuleDef(Static(..)) => CompletionItemKind::SymbolKind(SymbolKind::Static), ScopeDef::ModuleDef(Trait(..)) => CompletionItemKind::SymbolKind(SymbolKind::Trait), + ScopeDef::ModuleDef(TraitAlias(..)) => { + CompletionItemKind::SymbolKind(SymbolKind::TraitAlias) + } ScopeDef::ModuleDef(TypeAlias(..)) => CompletionItemKind::SymbolKind(SymbolKind::TypeAlias), ScopeDef::ModuleDef(BuiltinType(..)) => CompletionItemKind::BuiltinType, ScopeDef::GenericParam(param) => CompletionItemKind::SymbolKind(match param { diff --git a/crates/ide-db/src/active_parameter.rs b/crates/ide-db/src/active_parameter.rs index 2b6b60547b35..0da4e729a8dd 100644 --- a/crates/ide-db/src/active_parameter.rs +++ b/crates/ide-db/src/active_parameter.rs @@ -96,6 +96,7 @@ pub fn generic_def_for_node( hir::PathResolution::Def(hir::ModuleDef::Adt(it)) => it.into(), hir::PathResolution::Def(hir::ModuleDef::Function(it)) => it.into(), hir::PathResolution::Def(hir::ModuleDef::Trait(it)) => it.into(), + hir::PathResolution::Def(hir::ModuleDef::TraitAlias(it)) => it.into(), hir::PathResolution::Def(hir::ModuleDef::TypeAlias(it)) => it.into(), hir::PathResolution::Def(hir::ModuleDef::Variant(it)) => it.into(), hir::PathResolution::Def(hir::ModuleDef::BuiltinType(_)) diff --git a/crates/ide-db/src/defs.rs b/crates/ide-db/src/defs.rs index ed7f04fd8e7f..cce33dcfe469 100644 --- a/crates/ide-db/src/defs.rs +++ b/crates/ide-db/src/defs.rs @@ -9,7 +9,8 @@ use arrayvec::ArrayVec; use hir::{ Adt, AsAssocItem, AssocItem, BuiltinAttr, BuiltinType, Const, Crate, DeriveHelper, Field, Function, GenericParam, HasVisibility, Impl, ItemInNs, Label, Local, Macro, Module, ModuleDef, - Name, PathResolution, Semantics, Static, ToolModule, Trait, TypeAlias, Variant, Visibility, + Name, PathResolution, Semantics, Static, ToolModule, Trait, TraitAlias, TypeAlias, Variant, + Visibility, }; use stdx::impl_from; use syntax::{ @@ -31,6 +32,7 @@ pub enum Definition { Const(Const), Static(Static), Trait(Trait), + TraitAlias(TraitAlias), TypeAlias(TypeAlias), BuiltinType(BuiltinType), SelfType(Impl), @@ -64,6 +66,7 @@ impl Definition { Definition::Const(it) => it.module(db), Definition::Static(it) => it.module(db), Definition::Trait(it) => it.module(db), + Definition::TraitAlias(it) => it.module(db), Definition::TypeAlias(it) => it.module(db), Definition::Variant(it) => it.module(db), Definition::SelfType(it) => it.module(db), @@ -87,6 +90,7 @@ impl Definition { Definition::Const(it) => it.visibility(db), Definition::Static(it) => it.visibility(db), Definition::Trait(it) => it.visibility(db), + Definition::TraitAlias(it) => it.visibility(db), Definition::TypeAlias(it) => it.visibility(db), Definition::Variant(it) => it.visibility(db), Definition::BuiltinType(_) => Visibility::Public, @@ -113,6 +117,7 @@ impl Definition { Definition::Const(it) => it.name(db)?, Definition::Static(it) => it.name(db), Definition::Trait(it) => it.name(db), + Definition::TraitAlias(it) => it.name(db), Definition::TypeAlias(it) => it.name(db), Definition::BuiltinType(it) => it.name(), Definition::SelfType(_) => return None, @@ -300,6 +305,7 @@ impl NameClass { ast::Item::Module(it) => Definition::Module(sema.to_def(&it)?), ast::Item::Static(it) => Definition::Static(sema.to_def(&it)?), ast::Item::Trait(it) => Definition::Trait(sema.to_def(&it)?), + ast::Item::TraitAlias(it) => Definition::TraitAlias(sema.to_def(&it)?), ast::Item::TypeAlias(it) => Definition::TypeAlias(sema.to_def(&it)?), ast::Item::Enum(it) => Definition::Adt(hir::Adt::Enum(sema.to_def(&it)?)), ast::Item::Struct(it) => Definition::Adt(hir::Adt::Struct(sema.to_def(&it)?)), @@ -542,7 +548,7 @@ impl NameRefClass { } impl_from!( - Field, Module, Function, Adt, Variant, Const, Static, Trait, TypeAlias, BuiltinType, Local, + Field, Module, Function, Adt, Variant, Const, Static, Trait, TraitAlias, TypeAlias, BuiltinType, Local, GenericParam, Label, Macro for Definition ); @@ -599,6 +605,7 @@ impl From for Definition { ModuleDef::Const(it) => Definition::Const(it), ModuleDef::Static(it) => Definition::Static(it), ModuleDef::Trait(it) => Definition::Trait(it), + ModuleDef::TraitAlias(it) => Definition::TraitAlias(it), ModuleDef::TypeAlias(it) => Definition::TypeAlias(it), ModuleDef::Macro(it) => Definition::Macro(it), ModuleDef::BuiltinType(it) => Definition::BuiltinType(it), @@ -616,6 +623,7 @@ impl From for Option { Definition::Const(it) => ModuleDef::Const(it), Definition::Static(it) => ModuleDef::Static(it), Definition::Trait(it) => ModuleDef::Trait(it), + Definition::TraitAlias(it) => ModuleDef::TraitAlias(it), Definition::TypeAlias(it) => ModuleDef::TypeAlias(it), Definition::BuiltinType(it) => ModuleDef::BuiltinType(it), _ => return None, diff --git a/crates/ide-db/src/lib.rs b/crates/ide-db/src/lib.rs index 156bbb634e4d..4c53ba3b8303 100644 --- a/crates/ide-db/src/lib.rs +++ b/crates/ide-db/src/lib.rs @@ -191,6 +191,7 @@ pub enum SymbolKind { Struct, ToolModule, Trait, + TraitAlias, TypeAlias, TypeParam, Union, @@ -221,6 +222,7 @@ impl From for SymbolKind { FileSymbolKind::Static => SymbolKind::Static, FileSymbolKind::Struct => SymbolKind::Struct, FileSymbolKind::Trait => SymbolKind::Trait, + FileSymbolKind::TraitAlias => SymbolKind::TraitAlias, FileSymbolKind::TypeAlias => SymbolKind::TypeAlias, FileSymbolKind::Union => SymbolKind::Union, } diff --git a/crates/ide-db/src/rename.rs b/crates/ide-db/src/rename.rs index 84d70b258ff8..4179f1bd4f36 100644 --- a/crates/ide-db/src/rename.rs +++ b/crates/ide-db/src/rename.rs @@ -119,6 +119,7 @@ impl Definition { Definition::Const(it) => name_range(it, sema), Definition::Static(it) => name_range(it, sema), Definition::Trait(it) => name_range(it, sema), + Definition::TraitAlias(it) => name_range(it, sema), Definition::TypeAlias(it) => name_range(it, sema), Definition::Local(local) => { let src = local.source(sema.db); diff --git a/crates/ide-db/src/search.rs b/crates/ide-db/src/search.rs index c18a27f17d22..bcdaac4cf826 100644 --- a/crates/ide-db/src/search.rs +++ b/crates/ide-db/src/search.rs @@ -261,6 +261,7 @@ impl Definition { hir::GenericDef::Function(it) => it.source(db).map(|src| src.syntax().cloned()), hir::GenericDef::Adt(it) => it.source(db).map(|src| src.syntax().cloned()), hir::GenericDef::Trait(it) => it.source(db).map(|src| src.syntax().cloned()), + hir::GenericDef::TraitAlias(it) => it.source(db).map(|src| src.syntax().cloned()), hir::GenericDef::TypeAlias(it) => it.source(db).map(|src| src.syntax().cloned()), hir::GenericDef::Impl(it) => it.source(db).map(|src| src.syntax().cloned()), hir::GenericDef::Variant(it) => it.source(db).map(|src| src.syntax().cloned()), diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index 339bb2818022..fae25f310233 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs @@ -192,6 +192,7 @@ pub(crate) fn resolve_doc_path_for_def( Definition::Const(it) => it.resolve_doc_path(db, link, ns), Definition::Static(it) => it.resolve_doc_path(db, link, ns), Definition::Trait(it) => it.resolve_doc_path(db, link, ns), + Definition::TraitAlias(it) => it.resolve_doc_path(db, link, ns), Definition::TypeAlias(it) => it.resolve_doc_path(db, link, ns), Definition::Macro(it) => it.resolve_doc_path(db, link, ns), Definition::Field(it) => it.resolve_doc_path(db, link, ns), @@ -504,6 +505,7 @@ fn filename_and_frag_for_def( None => String::from("index.html"), }, Definition::Trait(t) => format!("trait.{}.html", t.name(db)), + Definition::TraitAlias(t) => format!("traitalias.{}.html", t.name(db)), Definition::TypeAlias(t) => format!("type.{}.html", t.name(db)), Definition::BuiltinType(t) => format!("primitive.{}.html", t.name()), Definition::Function(f) => format!("fn.{}.html", f.name(db)), diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs index 749c224c4620..c64b60d2936a 100644 --- a/crates/ide/src/hover/render.rs +++ b/crates/ide/src/hover/render.rs @@ -454,6 +454,7 @@ pub(super) fn definition( Some(body.to_string()) }), Definition::Trait(it) => label_and_docs(db, it), + Definition::TraitAlias(it) => label_and_docs(db, it), Definition::TypeAlias(it) => label_and_docs(db, it), Definition::BuiltinType(it) => { return famous_defs diff --git a/crates/ide/src/moniker.rs b/crates/ide/src/moniker.rs index af5e96d2381a..349e79ecfdda 100644 --- a/crates/ide/src/moniker.rs +++ b/crates/ide/src/moniker.rs @@ -208,6 +208,9 @@ pub(crate) fn def_to_moniker( Definition::Trait(trait_) => { MonikerDescriptor { name: trait_.name(db), desc: MonikerDescriptorKind::Type } } + Definition::TraitAlias(ta) => { + MonikerDescriptor { name: ta.name(db), desc: MonikerDescriptorKind::Type } + } Definition::TypeAlias(ta) => { MonikerDescriptor { name: ta.name(db), desc: MonikerDescriptorKind::TypeParameter } } diff --git a/crates/ide/src/navigation_target.rs b/crates/ide/src/navigation_target.rs index 3aa799d43a8a..9787724b45ec 100644 --- a/crates/ide/src/navigation_target.rs +++ b/crates/ide/src/navigation_target.rs @@ -192,6 +192,7 @@ impl TryToNav for Definition { Definition::Const(it) => it.try_to_nav(db), Definition::Static(it) => it.try_to_nav(db), Definition::Trait(it) => it.try_to_nav(db), + Definition::TraitAlias(it) => it.try_to_nav(db), Definition::TypeAlias(it) => it.try_to_nav(db), Definition::BuiltinType(_) => None, Definition::ToolModule(_) => None, @@ -212,6 +213,7 @@ impl TryToNav for hir::ModuleDef { hir::ModuleDef::Const(it) => it.try_to_nav(db), hir::ModuleDef::Static(it) => it.try_to_nav(db), hir::ModuleDef::Trait(it) => it.try_to_nav(db), + hir::ModuleDef::TraitAlias(it) => it.try_to_nav(db), hir::ModuleDef::TypeAlias(it) => it.try_to_nav(db), hir::ModuleDef::Macro(it) => it.try_to_nav(db), hir::ModuleDef::BuiltinType(_) => None, @@ -249,6 +251,9 @@ impl ToNavFromAst for hir::TypeAlias { impl ToNavFromAst for hir::Trait { const KIND: SymbolKind = SymbolKind::Trait; } +impl ToNavFromAst for hir::TraitAlias { + const KIND: SymbolKind = SymbolKind::TraitAlias; +} impl TryToNav for D where diff --git a/crates/ide/src/signature_help.rs b/crates/ide/src/signature_help.rs index f70ca55a508d..d32ae83c8f7b 100644 --- a/crates/ide/src/signature_help.rs +++ b/crates/ide/src/signature_help.rs @@ -252,6 +252,10 @@ fn signature_help_for_generics( res.doc = it.docs(db).map(|it| it.into()); format_to!(res.signature, "trait {}", it.name(db)); } + hir::GenericDef::TraitAlias(it) => { + res.doc = it.docs(db).map(|it| it.into()); + format_to!(res.signature, "trait {}", it.name(db)); + } hir::GenericDef::TypeAlias(it) => { res.doc = it.docs(db).map(|it| it.into()); format_to!(res.signature, "type {}", it.name(db)); diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs index 892e6a9bb0ab..390b7efdeb0b 100644 --- a/crates/ide/src/syntax_highlighting/highlight.rs +++ b/crates/ide/src/syntax_highlighting/highlight.rs @@ -410,6 +410,7 @@ fn highlight_def( h } Definition::Trait(_) => Highlight::new(HlTag::Symbol(SymbolKind::Trait)), + Definition::TraitAlias(_) => Highlight::new(HlTag::Symbol(SymbolKind::TraitAlias)), Definition::TypeAlias(type_) => { let mut h = Highlight::new(HlTag::Symbol(SymbolKind::TypeAlias)); diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs index 9139528c7ed9..3c4cfc78152d 100644 --- a/crates/ide/src/syntax_highlighting/inject.rs +++ b/crates/ide/src/syntax_highlighting/inject.rs @@ -274,6 +274,7 @@ fn module_def_to_hl_tag(def: Definition) -> HlTag { Definition::Const(_) => SymbolKind::Const, Definition::Static(_) => SymbolKind::Static, Definition::Trait(_) => SymbolKind::Trait, + Definition::TraitAlias(_) => SymbolKind::TraitAlias, Definition::TypeAlias(_) => SymbolKind::TypeAlias, Definition::BuiltinType(_) => return HlTag::BuiltinType, Definition::Macro(_) => SymbolKind::Macro, diff --git a/crates/ide/src/syntax_highlighting/tags.rs b/crates/ide/src/syntax_highlighting/tags.rs index 3949f1189bd5..a81c4ee0cbd4 100644 --- a/crates/ide/src/syntax_highlighting/tags.rs +++ b/crates/ide/src/syntax_highlighting/tags.rs @@ -150,6 +150,7 @@ impl HlTag { SymbolKind::Struct => "struct", SymbolKind::ToolModule => "tool_module", SymbolKind::Trait => "trait", + SymbolKind::TraitAlias => "trait_alias", SymbolKind::TypeAlias => "type_alias", SymbolKind::TypeParam => "type_param", SymbolKind::Union => "union", diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index 92029dc1de78..7d97b69f8ea0 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -50,7 +50,7 @@ pub(crate) fn symbol_kind(symbol_kind: SymbolKind) -> lsp_types::SymbolKind { SymbolKind::Struct => lsp_types::SymbolKind::STRUCT, SymbolKind::Enum => lsp_types::SymbolKind::ENUM, SymbolKind::Variant => lsp_types::SymbolKind::ENUM_MEMBER, - SymbolKind::Trait => lsp_types::SymbolKind::INTERFACE, + SymbolKind::Trait | SymbolKind::TraitAlias => lsp_types::SymbolKind::INTERFACE, SymbolKind::Macro | SymbolKind::BuiltinAttr | SymbolKind::Attribute @@ -135,6 +135,7 @@ pub(crate) fn completion_item_kind( SymbolKind::Static => lsp_types::CompletionItemKind::VALUE, SymbolKind::Struct => lsp_types::CompletionItemKind::STRUCT, SymbolKind::Trait => lsp_types::CompletionItemKind::INTERFACE, + SymbolKind::TraitAlias => lsp_types::CompletionItemKind::INTERFACE, SymbolKind::TypeAlias => lsp_types::CompletionItemKind::STRUCT, SymbolKind::TypeParam => lsp_types::CompletionItemKind::TYPE_PARAMETER, SymbolKind::Union => lsp_types::CompletionItemKind::STRUCT, @@ -656,6 +657,7 @@ fn semantic_token_type_and_modifiers( SymbolKind::Union => semantic_tokens::UNION, SymbolKind::TypeAlias => semantic_tokens::TYPE_ALIAS, SymbolKind::Trait => semantic_tokens::INTERFACE, + SymbolKind::TraitAlias => semantic_tokens::INTERFACE, SymbolKind::Macro => semantic_tokens::MACRO, SymbolKind::BuiltinAttr => semantic_tokens::BUILTIN_ATTRIBUTE, SymbolKind::ToolModule => semantic_tokens::TOOL_MODULE, From f8eac19b3354722a6fa0177968af54a58bb5b9e1 Mon Sep 17 00:00:00 2001 From: Ryo Yoshida Date: Sat, 4 Mar 2023 00:24:08 +0900 Subject: [PATCH 090/362] Support trait aliases in IDE where type support isn't needed --- crates/ide/src/file_structure.rs | 79 +++++++++++++++++++---------- crates/ide/src/goto_definition.rs | 8 ++- crates/ide/src/markup.rs | 2 +- crates/ide/src/move_item.rs | 1 + crates/ide/src/navigation_target.rs | 1 + crates/ide/src/references.rs | 32 ++++++++++++ crates/ide/src/runnables.rs | 1 + 7 files changed, 95 insertions(+), 29 deletions(-) diff --git a/crates/ide/src/file_structure.rs b/crates/ide/src/file_structure.rs index b23763dce867..a32ac35496aa 100644 --- a/crates/ide/src/file_structure.rs +++ b/crates/ide/src/file_structure.rs @@ -149,6 +149,7 @@ fn structure_node(node: &SyntaxNode) -> Option { ast::Enum(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Enum)), ast::Variant(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Variant)), ast::Trait(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Trait)), + ast::TraitAlias(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::TraitAlias)), ast::Module(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Module)), ast::TypeAlias(it) => decl_with_type_ref(&it, it.ty(), StructureNodeKind::SymbolKind(SymbolKind::TypeAlias)), ast::RecordField(it) => decl_with_type_ref(&it, it.ty(), StructureNodeKind::SymbolKind(SymbolKind::Field)), @@ -262,6 +263,8 @@ enum E { X, Y(i32) } type T = (); static S: i32 = 92; const C: i32 = 92; +trait Tr {} +trait Alias = Tr; impl E {} @@ -457,11 +460,33 @@ fn g() {} ), deprecated: false, }, + StructureNode { + parent: None, + label: "Tr", + navigation_range: 239..241, + node_range: 233..244, + kind: SymbolKind( + Trait, + ), + detail: None, + deprecated: false, + }, + StructureNode { + parent: None, + label: "Alias", + navigation_range: 251..256, + node_range: 245..262, + kind: SymbolKind( + TraitAlias, + ), + detail: None, + deprecated: false, + }, StructureNode { parent: None, label: "impl E", - navigation_range: 239..240, - node_range: 234..243, + navigation_range: 269..270, + node_range: 264..273, kind: SymbolKind( Impl, ), @@ -471,8 +496,8 @@ fn g() {} StructureNode { parent: None, label: "impl fmt::Debug for E", - navigation_range: 265..266, - node_range: 245..269, + navigation_range: 295..296, + node_range: 275..299, kind: SymbolKind( Impl, ), @@ -482,8 +507,8 @@ fn g() {} StructureNode { parent: None, label: "mc", - navigation_range: 284..286, - node_range: 271..303, + navigation_range: 314..316, + node_range: 301..333, kind: SymbolKind( Macro, ), @@ -493,8 +518,8 @@ fn g() {} StructureNode { parent: None, label: "mcexp", - navigation_range: 334..339, - node_range: 305..356, + navigation_range: 364..369, + node_range: 335..386, kind: SymbolKind( Macro, ), @@ -504,8 +529,8 @@ fn g() {} StructureNode { parent: None, label: "mcexp", - navigation_range: 387..392, - node_range: 358..409, + navigation_range: 417..422, + node_range: 388..439, kind: SymbolKind( Macro, ), @@ -515,8 +540,8 @@ fn g() {} StructureNode { parent: None, label: "obsolete", - navigation_range: 428..436, - node_range: 411..441, + navigation_range: 458..466, + node_range: 441..471, kind: SymbolKind( Function, ), @@ -528,8 +553,8 @@ fn g() {} StructureNode { parent: None, label: "very_obsolete", - navigation_range: 481..494, - node_range: 443..499, + navigation_range: 511..524, + node_range: 473..529, kind: SymbolKind( Function, ), @@ -541,8 +566,8 @@ fn g() {} StructureNode { parent: None, label: "Some region name", - navigation_range: 501..528, - node_range: 501..528, + navigation_range: 531..558, + node_range: 531..558, kind: Region, detail: None, deprecated: false, @@ -550,8 +575,8 @@ fn g() {} StructureNode { parent: None, label: "m", - navigation_range: 568..569, - node_range: 543..606, + navigation_range: 598..599, + node_range: 573..636, kind: SymbolKind( Module, ), @@ -560,22 +585,22 @@ fn g() {} }, StructureNode { parent: Some( - 20, + 22, ), label: "dontpanic", - navigation_range: 543..563, - node_range: 543..563, + navigation_range: 573..593, + node_range: 573..593, kind: Region, detail: None, deprecated: false, }, StructureNode { parent: Some( - 20, + 22, ), label: "f", - navigation_range: 575..576, - node_range: 572..581, + navigation_range: 605..606, + node_range: 602..611, kind: SymbolKind( Function, ), @@ -586,11 +611,11 @@ fn g() {} }, StructureNode { parent: Some( - 20, + 22, ), label: "g", - navigation_range: 598..599, - node_range: 582..604, + navigation_range: 628..629, + node_range: 612..634, kind: SymbolKind( Function, ), diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 65d1181d0924..cf0819a2524b 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -764,6 +764,13 @@ trait Foo$0 { } "#, ); + check( + r#" +trait Foo$0 = ; + //^^^ +"#, + ); + check( r#" mod bar$0 { } @@ -1423,7 +1430,6 @@ include!("included.rs$0"); ); } - #[cfg(test)] mod goto_impl_of_trait_fn { use super::check; #[test] diff --git a/crates/ide/src/markup.rs b/crates/ide/src/markup.rs index de9fef61a78e..411eb695fbf2 100644 --- a/crates/ide/src/markup.rs +++ b/crates/ide/src/markup.rs @@ -32,7 +32,7 @@ impl Markup { pub fn as_str(&self) -> &str { self.text.as_str() } - pub fn fenced_block(contents: &impl fmt::Display) -> Markup { + pub fn fenced_block(contents: impl fmt::Display) -> Markup { format!("```rust\n{contents}\n```").into() } } diff --git a/crates/ide/src/move_item.rs b/crates/ide/src/move_item.rs index ffc4bdd7da33..b955ea99f0c0 100644 --- a/crates/ide/src/move_item.rs +++ b/crates/ide/src/move_item.rs @@ -73,6 +73,7 @@ fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) - SyntaxKind::MACRO_CALL, SyntaxKind::TYPE_ALIAS, SyntaxKind::TRAIT, + SyntaxKind::TRAIT_ALIAS, SyntaxKind::IMPL, SyntaxKind::MACRO_DEF, SyntaxKind::STRUCT, diff --git a/crates/ide/src/navigation_target.rs b/crates/ide/src/navigation_target.rs index 9787724b45ec..11d10d2b8542 100644 --- a/crates/ide/src/navigation_target.rs +++ b/crates/ide/src/navigation_target.rs @@ -549,6 +549,7 @@ pub(crate) fn description_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> ast::Struct(it) => sema.to_def(&it).map(|it| it.display(db).to_string()), ast::Enum(it) => sema.to_def(&it).map(|it| it.display(db).to_string()), ast::Trait(it) => sema.to_def(&it).map(|it| it.display(db).to_string()), + ast::TraitAlias(it) => sema.to_def(&it).map(|it| it.display(db).to_string()), ast::Module(it) => sema.to_def(&it).map(|it| it.display(db).to_string()), ast::TypeAlias(it) => sema.to_def(&it).map(|it| it.display(db).to_string()), ast::Const(it) => sema.to_def(&it).map(|it| it.display(db).to_string()), diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index cabbc287279f..3684c1033f32 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs @@ -1355,6 +1355,38 @@ impl Foo { ); } + #[test] + fn test_trait_alias() { + check( + r#" +trait Foo {} +trait Bar$0 = Foo where Self: ; +fn foo(_: impl Bar, _: &dyn Bar) {} +"#, + expect![[r#" + Bar TraitAlias FileId(0) 13..42 19..22 + + FileId(0) 53..56 + FileId(0) 66..69 + FileId(0) 79..82 + "#]], + ); + } + + #[test] + fn test_trait_alias_self() { + check( + r#" +trait Foo = where Self$0: ; +"#, + expect![[r#" + Self TypeParam FileId(0) 6..9 6..9 + + FileId(0) 18..22 + "#]], + ); + } + #[test] fn test_attr_differs_from_fn_with_same_name() { check( diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index 77aef710ad1d..8a8a9151c425 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs @@ -416,6 +416,7 @@ fn module_def_doctest(db: &RootDatabase, def: Definition) -> Option { Definition::Const(it) => it.attrs(db), Definition::Static(it) => it.attrs(db), Definition::Trait(it) => it.attrs(db), + Definition::TraitAlias(it) => it.attrs(db), Definition::TypeAlias(it) => it.attrs(db), Definition::Macro(it) => it.attrs(db), Definition::SelfType(it) => it.attrs(db), From 02eb2d758e88ab1afb7b04ea0e8dbf0310c4e5a6 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 3 Mar 2023 16:44:25 +0100 Subject: [PATCH 091/362] Distinguish between expected and final type in CoerceMany --- crates/hir-ty/src/infer.rs | 23 +++- crates/hir-ty/src/infer/coerce.rs | 58 ++++++--- crates/hir-ty/src/infer/expr.rs | 194 +++++++++++++++++++----------- 3 files changed, 187 insertions(+), 88 deletions(-) diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index f229bf2f644d..d3e7f6e7dcaf 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -66,8 +66,10 @@ pub(crate) fn infer_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc { + ctx.collect_fn(f); + } DefWithBodyId::ConstId(c) => ctx.collect_const(&db.const_data(c)), - DefWithBodyId::FunctionId(f) => ctx.collect_fn(f), DefWithBodyId::StaticId(s) => ctx.collect_static(&db.static_data(s)), DefWithBodyId::VariantId(v) => { ctx.return_ty = TyBuilder::builtin(match db.enum_data(v.parent).variant_body_type() { @@ -392,9 +394,12 @@ pub(crate) struct InferenceContext<'a> { /// currently within one. /// /// We might consider using a nested inference context for checking - /// closures, but currently this is the only field that will change there, - /// so it doesn't make sense. + /// closures so we can swap all shared things out at once. return_ty: Ty, + /// If `Some`, this stores coercion information for returned + /// expressions. If `None`, this is in a context where return is + /// inappropriate, such as a const expression. + return_coercion: Option, /// The resume type and the yield type, respectively, of the generator being inferred. resume_yield_tys: Option<(Ty, Ty)>, diverges: Diverges, @@ -462,6 +467,7 @@ impl<'a> InferenceContext<'a> { trait_env, return_ty: TyKind::Error.intern(Interner), // set in collect_* calls resume_yield_tys: None, + return_coercion: None, db, owner, body, @@ -595,10 +601,19 @@ impl<'a> InferenceContext<'a> { }; self.return_ty = self.normalize_associated_types_in(return_ty); + self.return_coercion = Some(CoerceMany::new(self.return_ty.clone())); } fn infer_body(&mut self) { - self.infer_expr_coerce(self.body.body_expr, &Expectation::has_type(self.return_ty.clone())); + match self.return_coercion { + Some(_) => self.infer_return(self.body.body_expr), + None => { + _ = self.infer_expr_coerce( + self.body.body_expr, + &Expectation::has_type(self.return_ty.clone()), + ) + } + } } fn write_expr_ty(&mut self, expr: ExprId, ty: Ty) { diff --git a/crates/hir-ty/src/infer/coerce.rs b/crates/hir-ty/src/infer/coerce.rs index 3293534a068b..8bce47d71cba 100644 --- a/crates/hir-ty/src/infer/coerce.rs +++ b/crates/hir-ty/src/infer/coerce.rs @@ -50,11 +50,44 @@ fn success( #[derive(Clone, Debug)] pub(super) struct CoerceMany { expected_ty: Ty, + final_ty: Option, } impl CoerceMany { pub(super) fn new(expected: Ty) -> Self { - CoerceMany { expected_ty: expected } + CoerceMany { expected_ty: expected, final_ty: None } + } + + /// Returns the "expected type" with which this coercion was + /// constructed. This represents the "downward propagated" type + /// that was given to us at the start of typing whatever construct + /// we are typing (e.g., the match expression). + /// + /// Typically, this is used as the expected type when + /// type-checking each of the alternative expressions whose types + /// we are trying to merge. + pub(super) fn expected_ty(&self) -> Ty { + self.expected_ty.clone() + } + + /// Returns the current "merged type", representing our best-guess + /// at the LUB of the expressions we've seen so far (if any). This + /// isn't *final* until you call `self.complete()`, which will return + /// the merged type. + pub(super) fn merged_ty(&self) -> Ty { + self.final_ty.clone().unwrap_or_else(|| self.expected_ty.clone()) + } + + pub(super) fn complete(self, ctx: &mut InferenceContext<'_>) -> Ty { + if let Some(final_ty) = self.final_ty { + final_ty + } else { + ctx.result.standard_types.never.clone() + } + } + + pub(super) fn coerce_forced_unit(&mut self, ctx: &mut InferenceContext<'_>) { + self.coerce(ctx, None, &ctx.result.standard_types.unit.clone()) } /// Merge two types from different branches, with possible coercion. @@ -76,25 +109,25 @@ impl CoerceMany { // Special case: two function types. Try to coerce both to // pointers to have a chance at getting a match. See // https://github.com/rust-lang/rust/blob/7b805396bf46dce972692a6846ce2ad8481c5f85/src/librustc_typeck/check/coercion.rs#L877-L916 - let sig = match (self.expected_ty.kind(Interner), expr_ty.kind(Interner)) { + let sig = match (self.merged_ty().kind(Interner), expr_ty.kind(Interner)) { (TyKind::FnDef(..) | TyKind::Closure(..), TyKind::FnDef(..) | TyKind::Closure(..)) => { // FIXME: we're ignoring safety here. To be more correct, if we have one FnDef and one Closure, // we should be coercing the closure to a fn pointer of the safety of the FnDef cov_mark::hit!(coerce_fn_reification); let sig = - self.expected_ty.callable_sig(ctx.db).expect("FnDef without callable sig"); + self.merged_ty().callable_sig(ctx.db).expect("FnDef without callable sig"); Some(sig) } _ => None, }; if let Some(sig) = sig { let target_ty = TyKind::Function(sig.to_fn_ptr()).intern(Interner); - let result1 = ctx.table.coerce_inner(self.expected_ty.clone(), &target_ty); + let result1 = ctx.table.coerce_inner(self.merged_ty(), &target_ty); let result2 = ctx.table.coerce_inner(expr_ty.clone(), &target_ty); if let (Ok(result1), Ok(result2)) = (result1, result2) { ctx.table.register_infer_ok(result1); ctx.table.register_infer_ok(result2); - return self.expected_ty = target_ty; + return self.final_ty = Some(target_ty); } } @@ -102,25 +135,20 @@ impl CoerceMany { // type is a type variable and the new one is `!`, trying it the other // way around first would mean we make the type variable `!`, instead of // just marking it as possibly diverging. - if ctx.coerce(expr, &expr_ty, &self.expected_ty).is_ok() { - /* self.expected_ty is already correct */ - } else if ctx.coerce(expr, &self.expected_ty, &expr_ty).is_ok() { - self.expected_ty = expr_ty; + if let Ok(res) = ctx.coerce(expr, &expr_ty, &self.merged_ty()) { + self.final_ty = Some(res); + } else if let Ok(res) = ctx.coerce(expr, &self.merged_ty(), &expr_ty) { + self.final_ty = Some(res); } else { if let Some(id) = expr { ctx.result.type_mismatches.insert( id.into(), - TypeMismatch { expected: self.expected_ty.clone(), actual: expr_ty }, + TypeMismatch { expected: self.merged_ty().clone(), actual: expr_ty.clone() }, ); } cov_mark::hit!(coerce_merge_fail_fallback); - /* self.expected_ty is already correct */ } } - - pub(super) fn complete(self) -> Ty { - self.expected_ty - } } pub fn could_coerce( diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 6f20f0dc893e..b650afe9963c 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -60,6 +60,10 @@ impl<'a> InferenceContext<'a> { ty } + pub(crate) fn infer_expr_no_expect(&mut self, tgt_expr: ExprId) -> Ty { + self.infer_expr_inner(tgt_expr, &Expectation::None) + } + /// Infer type of expression with possibly implicit coerce to the expected type. /// Return the type after possible coercion. pub(super) fn infer_expr_coerce(&mut self, expr: ExprId, expected: &Expectation) -> Ty { @@ -99,17 +103,20 @@ impl<'a> InferenceContext<'a> { both_arms_diverge &= mem::replace(&mut self.diverges, Diverges::Maybe); let mut coerce = CoerceMany::new(expected.coercion_target_type(&mut self.table)); coerce.coerce(self, Some(then_branch), &then_ty); - let else_ty = match else_branch { - Some(else_branch) => self.infer_expr_inner(else_branch, expected), - None => TyBuilder::unit(), - }; + match else_branch { + Some(else_branch) => { + let else_ty = self.infer_expr_inner(else_branch, expected); + coerce.coerce(self, Some(else_branch), &else_ty); + } + None => { + coerce.coerce_forced_unit(self); + } + } both_arms_diverge &= self.diverges; - // FIXME: create a synthetic `else {}` so we have something to refer to here instead of None? - coerce.coerce(self, else_branch, &else_ty); self.diverges = condition_diverges | both_arms_diverge; - coerce.complete() + coerce.complete(self) } &Expr::Let { pat, expr } => { let input_ty = self.infer_expr(expr, &Expectation::none()); @@ -172,6 +179,8 @@ impl<'a> InferenceContext<'a> { let ret_ty = self.table.new_type_var(); let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone()); + let prev_ret_coercion = + mem::replace(&mut self.return_coercion, Some(CoerceMany::new(ret_ty.clone()))); let (_, inner_ty) = self.with_breakable_ctx(BreakableKind::Border, self.err_ty(), None, |this| { @@ -180,6 +189,7 @@ impl<'a> InferenceContext<'a> { self.diverges = prev_diverges; self.return_ty = prev_ret_ty; + self.return_coercion = prev_ret_coercion; // Use the first type parameter as the output type of future. // existential type AsyncBlockImplTrait: Future @@ -303,17 +313,21 @@ impl<'a> InferenceContext<'a> { self.infer_top_pat(*arg_pat, &arg_ty); } + // FIXME: lift these out into a struct let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone()); + let prev_ret_coercion = + mem::replace(&mut self.return_coercion, Some(CoerceMany::new(ret_ty.clone()))); let prev_resume_yield_tys = mem::replace(&mut self.resume_yield_tys, resume_yield_tys); self.with_breakable_ctx(BreakableKind::Border, self.err_ty(), None, |this| { - this.infer_expr_coerce(*body, &Expectation::has_type(ret_ty)); + this.infer_return(*body); }); self.diverges = prev_diverges; self.return_ty = prev_ret_ty; + self.return_coercion = prev_ret_coercion; self.resume_yield_tys = prev_resume_yield_tys; ty @@ -413,7 +427,7 @@ impl<'a> InferenceContext<'a> { self.diverges = matchee_diverges | all_arms_diverge; - coerce.complete() + coerce.complete(self) } Expr::Path(p) => { // FIXME this could be more efficient... @@ -431,7 +445,12 @@ impl<'a> InferenceContext<'a> { } Expr::Break { expr, label } => { let val_ty = if let Some(expr) = *expr { - self.infer_expr(expr, &Expectation::none()) + let opt_coerce_to = find_breakable(&mut self.breakables, label.as_ref()) + .map(|ctxt| ctxt.coerce.expected_ty()); + self.infer_expr_inner( + expr, + &Expectation::HasType(opt_coerce_to.unwrap_or_else(|| self.err_ty())), + ) } else { TyBuilder::unit() }; @@ -461,15 +480,7 @@ impl<'a> InferenceContext<'a> { } self.result.standard_types.never.clone() } - Expr::Return { expr } => { - if let Some(expr) = expr { - self.infer_expr_coerce(*expr, &Expectation::has_type(self.return_ty.clone())); - } else { - let unit = TyBuilder::unit(); - let _ = self.coerce(Some(tgt_expr), &unit, &self.return_ty.clone()); - } - self.result.standard_types.never.clone() - } + &Expr::Return { expr } => self.infer_expr_return(expr), Expr::Yield { expr } => { if let Some((resume_ty, yield_ty)) = self.resume_yield_tys.clone() { if let Some(expr) = expr { @@ -486,7 +497,7 @@ impl<'a> InferenceContext<'a> { } Expr::Yeet { expr } => { if let &Some(expr) = expr { - self.infer_expr_inner(expr, &Expectation::None); + self.infer_expr_no_expect(expr); } self.result.standard_types.never.clone() } @@ -614,7 +625,7 @@ impl<'a> InferenceContext<'a> { Expr::Cast { expr, type_ref } => { let cast_ty = self.make_ty(type_ref); // FIXME: propagate the "castable to" expectation - let _inner_ty = self.infer_expr_inner(*expr, &Expectation::None); + let _inner_ty = self.infer_expr_no_expect(*expr); // FIXME check the cast... cast_ty } @@ -810,53 +821,7 @@ impl<'a> InferenceContext<'a> { TyKind::Tuple(tys.len(), Substitution::from_iter(Interner, tys)).intern(Interner) } - Expr::Array(array) => { - let elem_ty = - match expected.to_option(&mut self.table).as_ref().map(|t| t.kind(Interner)) { - Some(TyKind::Array(st, _) | TyKind::Slice(st)) => st.clone(), - _ => self.table.new_type_var(), - }; - let mut coerce = CoerceMany::new(elem_ty.clone()); - - let expected = Expectation::has_type(elem_ty.clone()); - let len = match array { - Array::ElementList { elements, .. } => { - for &expr in elements.iter() { - let cur_elem_ty = self.infer_expr_inner(expr, &expected); - coerce.coerce(self, Some(expr), &cur_elem_ty); - } - consteval::usize_const( - self.db, - Some(elements.len() as u128), - self.resolver.krate(), - ) - } - &Array::Repeat { initializer, repeat } => { - self.infer_expr_coerce(initializer, &Expectation::has_type(elem_ty)); - self.infer_expr( - repeat, - &Expectation::HasType( - TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(Interner), - ), - ); - - if let Some(g_def) = self.owner.as_generic_def_id() { - let generics = generics(self.db.upcast(), g_def); - consteval::eval_to_const( - repeat, - ParamLoweringMode::Placeholder, - self, - || generics, - DebruijnIndex::INNERMOST, - ) - } else { - consteval::usize_const(self.db, None, self.resolver.krate()) - } - } - }; - - TyKind::Array(coerce.complete(), len).intern(Interner) - } + Expr::Array(array) => self.infer_expr_array(array, expected), Expr::Literal(lit) => match lit { Literal::Bool(..) => self.result.standard_types.bool_.clone(), Literal::String(..) => { @@ -915,6 +880,97 @@ impl<'a> InferenceContext<'a> { ty } + fn infer_expr_array( + &mut self, + array: &Array, + expected: &Expectation, + ) -> chalk_ir::Ty { + let elem_ty = match expected.to_option(&mut self.table).as_ref().map(|t| t.kind(Interner)) { + Some(TyKind::Array(st, _) | TyKind::Slice(st)) => st.clone(), + _ => self.table.new_type_var(), + }; + + let krate = self.resolver.krate(); + + let expected = Expectation::has_type(elem_ty.clone()); + let (elem_ty, len) = match array { + Array::ElementList { elements, .. } if elements.is_empty() => { + (elem_ty, consteval::usize_const(self.db, Some(0), krate)) + } + Array::ElementList { elements, .. } => { + let mut coerce = CoerceMany::new(elem_ty.clone()); + for &expr in elements.iter() { + let cur_elem_ty = self.infer_expr_inner(expr, &expected); + coerce.coerce(self, Some(expr), &cur_elem_ty); + } + ( + coerce.complete(self), + consteval::usize_const(self.db, Some(elements.len() as u128), krate), + ) + } + &Array::Repeat { initializer, repeat } => { + self.infer_expr_coerce(initializer, &Expectation::has_type(elem_ty.clone())); + self.infer_expr( + repeat, + &Expectation::HasType( + TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(Interner), + ), + ); + + ( + elem_ty, + if let Some(g_def) = self.owner.as_generic_def_id() { + let generics = generics(self.db.upcast(), g_def); + consteval::eval_to_const( + repeat, + ParamLoweringMode::Placeholder, + self, + || generics, + DebruijnIndex::INNERMOST, + ) + } else { + consteval::usize_const(self.db, None, krate) + }, + ) + } + }; + + TyKind::Array(elem_ty, len).intern(Interner) + } + + pub(super) fn infer_return(&mut self, expr: ExprId) { + let ret_ty = self + .return_coercion + .as_mut() + .expect("infer_return called outside function body") + .expected_ty(); + let return_expr_ty = self.infer_expr_inner(expr, &Expectation::HasType(ret_ty)); + let mut coerce_many = self.return_coercion.take().unwrap(); + coerce_many.coerce(self, Some(expr), &return_expr_ty); + self.return_coercion = Some(coerce_many); + } + + fn infer_expr_return(&mut self, expr: Option) -> Ty { + match self.return_coercion { + Some(_) => { + if let Some(expr) = expr { + self.infer_return(expr); + } else { + let mut coerce = self.return_coercion.take().unwrap(); + coerce.coerce_forced_unit(self); + self.return_coercion = Some(coerce); + } + } + None => { + // FIXME: diagnose return outside of function + if let Some(expr) = expr { + self.infer_expr_no_expect(expr); + } + } + } + self.result.standard_types.never.clone() + } + fn infer_expr_box(&mut self, inner_expr: ExprId, expected: &Expectation) -> Ty { if let Some(box_id) = self.resolve_boxed_box() { let table = &mut self.table; @@ -1666,6 +1722,6 @@ impl<'a> InferenceContext<'a> { }); let res = cb(self); let ctx = self.breakables.pop().expect("breakable stack broken"); - (ctx.may_break.then(|| ctx.coerce.complete()), res) + (ctx.may_break.then(|| ctx.coerce.complete(self)), res) } } From 41f234df09440dcd9420cc752649c68135fc09ed Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 3 Mar 2023 17:28:57 +0100 Subject: [PATCH 092/362] Diagnose value breaks in incorrect breakables --- crates/hir-ty/src/infer.rs | 5 +- crates/hir-ty/src/infer/expr.rs | 70 +++++++++++-------- crates/hir/src/diagnostics.rs | 1 + crates/hir/src/lib.rs | 8 ++- .../src/handlers/break_outside_of_loop.rs | 23 +++++- 5 files changed, 70 insertions(+), 37 deletions(-) diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index d3e7f6e7dcaf..cca787befee3 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -167,7 +167,8 @@ pub enum InferenceDiagnostic { NoSuchField { expr: ExprId }, PrivateField { expr: ExprId, field: FieldId }, PrivateAssocItem { id: ExprOrPatId, item: AssocItemId }, - BreakOutsideOfLoop { expr: ExprId, is_break: bool }, + // FIXME: Make this proper + BreakOutsideOfLoop { expr: ExprId, is_break: bool, bad_value_break: bool }, MismatchedArgCount { call_expr: ExprId, expected: usize, found: usize }, } @@ -411,7 +412,7 @@ struct BreakableContext { /// Whether this context contains at least one break expression. may_break: bool, /// The coercion target of the context. - coerce: CoerceMany, + coerce: Option, /// The optional label of the context. label: Option, kind: BreakableKind, diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index b650afe9963c..e64b020c7fbd 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -133,7 +133,7 @@ impl<'a> InferenceContext<'a> { let break_ty = self.table.new_type_var(); let (breaks, ty) = self.with_breakable_ctx( BreakableKind::Block, - break_ty.clone(), + Some(break_ty.clone()), *label, |this| { this.infer_block( @@ -153,7 +153,7 @@ impl<'a> InferenceContext<'a> { } Expr::Unsafe { body } => self.infer_expr(*body, expected), Expr::Const { body } => { - self.with_breakable_ctx(BreakableKind::Border, self.err_ty(), None, |this| { + self.with_breakable_ctx(BreakableKind::Border, None, None, |this| { this.infer_expr(*body, expected) }) .1 @@ -169,7 +169,7 @@ impl<'a> InferenceContext<'a> { let ok_ty = self.resolve_associated_type(try_ty.clone(), self.resolve_ops_try_output()); - self.with_breakable_ctx(BreakableKind::Block, ok_ty.clone(), None, |this| { + self.with_breakable_ctx(BreakableKind::Block, Some(ok_ty.clone()), None, |this| { this.infer_expr(*body, &Expectation::has_type(ok_ty)); }); @@ -183,7 +183,7 @@ impl<'a> InferenceContext<'a> { mem::replace(&mut self.return_coercion, Some(CoerceMany::new(ret_ty.clone()))); let (_, inner_ty) = - self.with_breakable_ctx(BreakableKind::Border, self.err_ty(), None, |this| { + self.with_breakable_ctx(BreakableKind::Border, None, None, |this| { this.infer_expr_coerce(*body, &Expectation::has_type(ret_ty)) }); @@ -203,7 +203,7 @@ impl<'a> InferenceContext<'a> { // let ty = expected.coercion_target_type(&mut self.table); let ty = self.table.new_type_var(); let (breaks, ()) = - self.with_breakable_ctx(BreakableKind::Loop, ty, label, |this| { + self.with_breakable_ctx(BreakableKind::Loop, Some(ty), label, |this| { this.infer_expr(body, &Expectation::HasType(TyBuilder::unit())); }); @@ -216,7 +216,7 @@ impl<'a> InferenceContext<'a> { } } &Expr::While { condition, body, label } => { - self.with_breakable_ctx(BreakableKind::Loop, self.err_ty(), label, |this| { + self.with_breakable_ctx(BreakableKind::Loop, None, label, |this| { this.infer_expr( condition, &Expectation::HasType(this.result.standard_types.bool_.clone()), @@ -236,7 +236,7 @@ impl<'a> InferenceContext<'a> { self.resolve_associated_type(into_iter_ty, self.resolve_iterator_item()); self.infer_top_pat(pat, &pat_ty); - self.with_breakable_ctx(BreakableKind::Loop, self.err_ty(), label, |this| { + self.with_breakable_ctx(BreakableKind::Loop, None, label, |this| { this.infer_expr(body, &Expectation::HasType(TyBuilder::unit())); }); @@ -321,7 +321,7 @@ impl<'a> InferenceContext<'a> { let prev_resume_yield_tys = mem::replace(&mut self.resume_yield_tys, resume_yield_tys); - self.with_breakable_ctx(BreakableKind::Border, self.err_ty(), None, |this| { + self.with_breakable_ctx(BreakableKind::Border, None, None, |this| { this.infer_return(*body); }); @@ -439,42 +439,50 @@ impl<'a> InferenceContext<'a> { self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop { expr: tgt_expr, is_break: false, + bad_value_break: false, }); }; self.result.standard_types.never.clone() } Expr::Break { expr, label } => { let val_ty = if let Some(expr) = *expr { - let opt_coerce_to = find_breakable(&mut self.breakables, label.as_ref()) - .map(|ctxt| ctxt.coerce.expected_ty()); - self.infer_expr_inner( - expr, - &Expectation::HasType(opt_coerce_to.unwrap_or_else(|| self.err_ty())), - ) + let opt_coerce_to = match find_breakable(&mut self.breakables, label.as_ref()) { + Some(ctxt) => match &ctxt.coerce { + Some(coerce) => coerce.expected_ty(), + None => { + self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop { + expr: tgt_expr, + is_break: true, + bad_value_break: true, + }); + self.err_ty() + } + }, + None => self.err_ty(), + }; + self.infer_expr_inner(expr, &Expectation::HasType(opt_coerce_to)) } else { TyBuilder::unit() }; match find_breakable(&mut self.breakables, label.as_ref()) { - Some(ctxt) => { - // avoiding the borrowck - let mut coerce = mem::replace( - &mut ctxt.coerce, - CoerceMany::new(expected.coercion_target_type(&mut self.table)), - ); + Some(ctxt) => match ctxt.coerce.take() { + Some(mut coerce) => { + coerce.coerce(self, *expr, &val_ty); - // FIXME: create a synthetic `()` during lowering so we have something to refer to here? - coerce.coerce(self, *expr, &val_ty); - - let ctxt = find_breakable(&mut self.breakables, label.as_ref()) - .expect("breakable stack changed during coercion"); - ctxt.coerce = coerce; - ctxt.may_break = true; - } + // Avoiding borrowck + let ctxt = find_breakable(&mut self.breakables, label.as_ref()) + .expect("breakable stack changed during coercion"); + ctxt.may_break = true; + ctxt.coerce = Some(coerce); + } + None => ctxt.may_break = true, + }, None => { self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop { expr: tgt_expr, is_break: true, + bad_value_break: false, }); } } @@ -1712,16 +1720,16 @@ impl<'a> InferenceContext<'a> { fn with_breakable_ctx( &mut self, kind: BreakableKind, - ty: Ty, + ty: Option, label: Option, cb: impl FnOnce(&mut Self) -> T, ) -> (Option, T) { self.breakables.push({ let label = label.map(|label| self.body[label].name.clone()); - BreakableContext { kind, may_break: false, coerce: CoerceMany::new(ty), label } + BreakableContext { kind, may_break: false, coerce: ty.map(CoerceMany::new), label } }); let res = cb(self); let ctx = self.breakables.pop().expect("breakable stack broken"); - (ctx.may_break.then(|| ctx.coerce.complete(self)), res) + (if ctx.may_break { ctx.coerce.map(|ctx| ctx.complete(self)) } else { None }, res) } } diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index 3b2591e8a106..bb7468d46604 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -140,6 +140,7 @@ pub struct PrivateField { pub struct BreakOutsideOfLoop { pub expr: InFile>, pub is_break: bool, + pub bad_value_break: bool, } #[derive(Debug)] diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 846ad4007ce2..c9ae12d8d330 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -1373,11 +1373,15 @@ impl DefWithBody { let field = source_map.field_syntax(*expr); acc.push(NoSuchField { field }.into()) } - &hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { expr, is_break } => { + &hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { + expr, + is_break, + bad_value_break, + } => { let expr = source_map .expr_syntax(expr) .expect("break outside of loop in synthetic syntax"); - acc.push(BreakOutsideOfLoop { expr, is_break }.into()) + acc.push(BreakOutsideOfLoop { expr, is_break, bad_value_break }.into()) } hir_ty::InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => { match source_map.expr_syntax(*call_expr) { diff --git a/crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs b/crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs index 10e637979f2c..114face2dca8 100644 --- a/crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs +++ b/crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs @@ -7,10 +7,15 @@ pub(crate) fn break_outside_of_loop( ctx: &DiagnosticsContext<'_>, d: &hir::BreakOutsideOfLoop, ) -> Diagnostic { - let construct = if d.is_break { "break" } else { "continue" }; + let message = if d.bad_value_break { + "can't break with a value in this position".to_owned() + } else { + let construct = if d.is_break { "break" } else { "continue" }; + format!("{construct} outside of loop") + }; Diagnostic::new( "break-outside-of-loop", - format!("{construct} outside of loop"), + message, ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range, ) } @@ -132,6 +137,20 @@ fn foo() { //^^^^^^^^^^^ error: continue outside of loop } } +"#, + ); + } + + #[test] + fn value_break_in_for_loop() { + check_diagnostics( + r#" +fn test() { + for _ in [()] { + break 3; + // ^^^^^^^ error: can't break with a value in this position + } +} "#, ); } From ff5f7841401df797d94818fac43777327dbecf9a Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Sun, 13 Nov 2022 14:05:04 +0000 Subject: [PATCH 093/362] Add a test for `unused_allocation` lint (how come we didn't have one already??) --- tests/ui/lint/unused/unused-allocation.rs | 7 +++++++ tests/ui/lint/unused/unused-allocation.stderr | 20 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 tests/ui/lint/unused/unused-allocation.rs create mode 100644 tests/ui/lint/unused/unused-allocation.stderr diff --git a/tests/ui/lint/unused/unused-allocation.rs b/tests/ui/lint/unused/unused-allocation.rs new file mode 100644 index 000000000000..df51dd194d5e --- /dev/null +++ b/tests/ui/lint/unused/unused-allocation.rs @@ -0,0 +1,7 @@ +#![feature(box_syntax)] +#![deny(unused_allocation)] + +fn main() { + _ = (box [1]).len(); //~ error: unnecessary allocation, use `&` instead + _ = Box::new([1]).len(); //~ error: unnecessary allocation, use `&` instead +} diff --git a/tests/ui/lint/unused/unused-allocation.stderr b/tests/ui/lint/unused/unused-allocation.stderr new file mode 100644 index 000000000000..91bd1da35a38 --- /dev/null +++ b/tests/ui/lint/unused/unused-allocation.stderr @@ -0,0 +1,20 @@ +error: unnecessary allocation, use `&` instead + --> $DIR/unused-allocation.rs:5:9 + | +LL | _ = (box [1]).len(); + | ^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/unused-allocation.rs:2:9 + | +LL | #![deny(unused_allocation)] + | ^^^^^^^^^^^^^^^^^ + +error: unnecessary allocation, use `&` instead + --> $DIR/unused-allocation.rs:6:9 + | +LL | _ = Box::new([1]).len(); + | ^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + From 6f3c25a6310bf9a7d13cec0beb11ad155f55407e Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Sun, 13 Nov 2022 14:08:38 +0000 Subject: [PATCH 094/362] Remove some useless `#[allow()]`s in tests --- tests/ui/issues/issue-3029.rs | 2 -- .../ui/macros/rfc-2011-nicer-assert-messages/all-expr-kinds.rs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/ui/issues/issue-3029.rs b/tests/ui/issues/issue-3029.rs index a5d30960a4cd..43c8a0a23fb5 100644 --- a/tests/ui/issues/issue-3029.rs +++ b/tests/ui/issues/issue-3029.rs @@ -2,9 +2,7 @@ // error-pattern:so long // ignore-emscripten no processes -#![allow(unused_allocation)] #![allow(unreachable_code)] -#![allow(unused_variables)] fn main() { let mut x = Vec::new(); diff --git a/tests/ui/macros/rfc-2011-nicer-assert-messages/all-expr-kinds.rs b/tests/ui/macros/rfc-2011-nicer-assert-messages/all-expr-kinds.rs index b8b6f0846bb4..fcbf45419d17 100644 --- a/tests/ui/macros/rfc-2011-nicer-assert-messages/all-expr-kinds.rs +++ b/tests/ui/macros/rfc-2011-nicer-assert-messages/all-expr-kinds.rs @@ -4,7 +4,7 @@ // run-pass // needs-unwind Asserting on contents of error message -#![allow(path_statements, unused_allocation)] +#![allow(path_statements)] #![feature(box_syntax, core_intrinsics, generic_assert, generic_assert_internals)] macro_rules! test { From 214e65c2eb788b50d827d64e7bee4c915ce4ddd2 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Sun, 13 Nov 2022 17:31:58 +0000 Subject: [PATCH 095/362] Add unuseless `#[allow(unused_allocation)]` --- .../iterators/into-iter-on-arrays-lint.fixed | 2 +- .../ui/iterators/into-iter-on-arrays-lint.rs | 2 +- .../all-expr-kinds.rs | 2 +- tests/ui/self/arbitrary_self_types_trait.rs | 5 ++-- tests/ui/structs-enums/align-struct.rs | 26 ++++++++----------- 5 files changed, 17 insertions(+), 20 deletions(-) diff --git a/tests/ui/iterators/into-iter-on-arrays-lint.fixed b/tests/ui/iterators/into-iter-on-arrays-lint.fixed index 6e02a7024b94..5b91aaf9ea55 100644 --- a/tests/ui/iterators/into-iter-on-arrays-lint.fixed +++ b/tests/ui/iterators/into-iter-on-arrays-lint.fixed @@ -2,7 +2,7 @@ // run-rustfix // rustfix-only-machine-applicable -#[allow(unused_must_use)] +#[allow(unused_must_use, unused_allocation)] fn main() { let small = [1, 2]; let big = [0u8; 33]; diff --git a/tests/ui/iterators/into-iter-on-arrays-lint.rs b/tests/ui/iterators/into-iter-on-arrays-lint.rs index 582d5cadd065..25b0cef73d77 100644 --- a/tests/ui/iterators/into-iter-on-arrays-lint.rs +++ b/tests/ui/iterators/into-iter-on-arrays-lint.rs @@ -2,7 +2,7 @@ // run-rustfix // rustfix-only-machine-applicable -#[allow(unused_must_use)] +#[allow(unused_must_use, unused_allocation)] fn main() { let small = [1, 2]; let big = [0u8; 33]; diff --git a/tests/ui/macros/rfc-2011-nicer-assert-messages/all-expr-kinds.rs b/tests/ui/macros/rfc-2011-nicer-assert-messages/all-expr-kinds.rs index fcbf45419d17..b8b6f0846bb4 100644 --- a/tests/ui/macros/rfc-2011-nicer-assert-messages/all-expr-kinds.rs +++ b/tests/ui/macros/rfc-2011-nicer-assert-messages/all-expr-kinds.rs @@ -4,7 +4,7 @@ // run-pass // needs-unwind Asserting on contents of error message -#![allow(path_statements)] +#![allow(path_statements, unused_allocation)] #![feature(box_syntax, core_intrinsics, generic_assert, generic_assert_internals)] macro_rules! test { diff --git a/tests/ui/self/arbitrary_self_types_trait.rs b/tests/ui/self/arbitrary_self_types_trait.rs index 973c7cae85a9..c4651ec71778 100644 --- a/tests/ui/self/arbitrary_self_types_trait.rs +++ b/tests/ui/self/arbitrary_self_types_trait.rs @@ -1,4 +1,5 @@ // run-pass +#![allow(unused_allocation)] use std::rc::Rc; @@ -13,7 +14,7 @@ impl Trait for Vec { } fn main() { - let v = vec![1,2,3]; + let v = vec![1, 2, 3]; - assert_eq!(&[1,2,3], Box::new(Rc::new(v)).trait_method()); + assert_eq!(&[1, 2, 3], Box::new(Rc::new(v)).trait_method()); } diff --git a/tests/ui/structs-enums/align-struct.rs b/tests/ui/structs-enums/align-struct.rs index f5418e754b23..54092542f98f 100644 --- a/tests/ui/structs-enums/align-struct.rs +++ b/tests/ui/structs-enums/align-struct.rs @@ -1,5 +1,5 @@ // run-pass -#![allow(dead_code)] +#![allow(dead_code, unused_allocation)] use std::mem; @@ -20,7 +20,6 @@ struct AlignMany(i32); // Raising alignment may not alter size. #[repr(align(8))] -#[allow(dead_code)] struct Align8Many { a: i32, b: i32, @@ -29,9 +28,8 @@ struct Align8Many { } enum Enum { - #[allow(dead_code)] A(i32), - B(Align16) + B(Align16), } // Nested alignment - use `#[repr(C)]` to suppress field reordering for sizeof test @@ -73,7 +71,7 @@ struct AlignLarge { union UnionContainsAlign { a: Align16, - b: f32 + b: f32, } impl Align16 { @@ -158,7 +156,7 @@ pub fn main() { // Note that the size of Nested may change if struct field re-ordering is enabled assert_eq!(mem::align_of::(), 16); assert_eq!(mem::size_of::(), 48); - let a = Nested{ a: 1, b: 2, c: Align16(3), d: 4}; + let a = Nested { a: 1, b: 2, c: Align16(3), d: 4 }; assert_eq!(mem::align_of_val(&a), 16); assert_eq!(mem::align_of_val(&a.b), 4); assert_eq!(mem::align_of_val(&a.c), 16); @@ -179,8 +177,8 @@ pub fn main() { assert_eq!(a.0, 15); assert_eq!(mem::align_of_val(a), 16); assert_eq!(mem::size_of_val(a), 16); - }, - _ => () + } + _ => (), } assert!(is_aligned_to(&e, 16)); @@ -197,8 +195,8 @@ pub fn main() { } // arrays of aligned elements should also be aligned - assert_eq!(mem::align_of::<[Align16;2]>(), 16); - assert_eq!(mem::size_of::<[Align16;2]>(), 32); + assert_eq!(mem::align_of::<[Align16; 2]>(), 16); + assert_eq!(mem::size_of::<[Align16; 2]>(), 32); let a = [Align16(0), Align16(1)]; assert_eq!(mem::align_of_val(&a[0]), 16); @@ -209,7 +207,7 @@ pub fn main() { assert_eq!(mem::align_of_val(Box::new(Align16(0)).as_ref()), 16); // check heap array is aligned - let a = vec!(Align16(0), Align16(1)); + let a = vec![Align16(0), Align16(1)]; assert_eq!(mem::align_of_val(&a[0]), 16); assert_eq!(mem::align_of_val(&a[1]), 16); @@ -224,16 +222,14 @@ pub fn main() { assert_eq!(mem::align_of::(), 16); assert_eq!(mem::size_of::(), 32); - let a = AlignContainsPacked4C { a: Packed4C{ a: 1, b: 2 }, b: 3 }; + let a = AlignContainsPacked4C { a: Packed4C { a: 1, b: 2 }, b: 3 }; assert_eq!(mem::align_of_val(&a), 16); assert_eq!(mem::align_of_val(&a.a), 4); assert_eq!(mem::align_of_val(&a.b), mem::align_of::()); assert_eq!(mem::size_of_val(&a), 32); assert!(is_aligned_to(&a, 16)); - let mut large = Box::new(AlignLarge { - stuff: [0; 0x10000], - }); + let mut large = Box::new(AlignLarge { stuff: [0; 0x10000] }); large.stuff[0] = 132; *large.stuff.last_mut().unwrap() = 102; assert_eq!(large.stuff[0], 132); From 7f5338a1223555e59b7fa096087f0af9455a1af4 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Sun, 13 Nov 2022 23:13:50 +0000 Subject: [PATCH 096/362] fix an alloc test --- library/alloc/src/tests.rs | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/library/alloc/src/tests.rs b/library/alloc/src/tests.rs index 299ed156a5d2..b1d3a9fa8ac9 100644 --- a/library/alloc/src/tests.rs +++ b/library/alloc/src/tests.rs @@ -4,7 +4,6 @@ use core::any::Any; use core::clone::Clone; use core::convert::TryInto; use core::ops::Deref; -use core::result::Result::{Err, Ok}; use std::boxed::Box; @@ -15,7 +14,7 @@ fn test_owned_clone() { assert!(a == b); } -#[derive(PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq)] struct Test; #[test] @@ -23,24 +22,17 @@ fn any_move() { let a = Box::new(8) as Box; let b = Box::new(Test) as Box; - match a.downcast::() { - Ok(a) => { - assert!(a == Box::new(8)); - } - Err(..) => panic!(), - } - match b.downcast::() { - Ok(a) => { - assert!(a == Box::new(Test)); - } - Err(..) => panic!(), - } + let a: Box = a.downcast::().unwrap(); + assert_eq!(*a, 8); + + let b: Box = b.downcast::().unwrap(); + assert_eq!(*b, Test); let a = Box::new(8) as Box; let b = Box::new(Test) as Box; - assert!(a.downcast::>().is_err()); - assert!(b.downcast::>().is_err()); + assert!(a.downcast::>().is_err()); + assert!(b.downcast::>().is_err()); } #[test] From a90abd64fbb0764007d93d60823161f05f564259 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Fri, 3 Mar 2023 19:02:35 +0000 Subject: [PATCH 097/362] Remove `feature(box_syntax)` from unused allocation list test --- tests/ui/lint/unused/unused-allocation.rs | 4 ++-- tests/ui/lint/unused/unused-allocation.stderr | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/ui/lint/unused/unused-allocation.rs b/tests/ui/lint/unused/unused-allocation.rs index df51dd194d5e..c1a6f5ceaf17 100644 --- a/tests/ui/lint/unused/unused-allocation.rs +++ b/tests/ui/lint/unused/unused-allocation.rs @@ -1,7 +1,7 @@ -#![feature(box_syntax)] +#![feature(rustc_attrs, stmt_expr_attributes)] #![deny(unused_allocation)] fn main() { - _ = (box [1]).len(); //~ error: unnecessary allocation, use `&` instead + _ = (#[rustc_box] Box::new([1])).len(); //~ error: unnecessary allocation, use `&` instead _ = Box::new([1]).len(); //~ error: unnecessary allocation, use `&` instead } diff --git a/tests/ui/lint/unused/unused-allocation.stderr b/tests/ui/lint/unused/unused-allocation.stderr index 91bd1da35a38..c9ccfbd30e5d 100644 --- a/tests/ui/lint/unused/unused-allocation.stderr +++ b/tests/ui/lint/unused/unused-allocation.stderr @@ -1,8 +1,8 @@ error: unnecessary allocation, use `&` instead --> $DIR/unused-allocation.rs:5:9 | -LL | _ = (box [1]).len(); - | ^^^^^^^^^ +LL | _ = (#[rustc_box] Box::new([1])).len(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: the lint level is defined here --> $DIR/unused-allocation.rs:2:9 From 3c7a0aa00ee4d574af36a1ab982a63488acb211a Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 3 Mar 2023 18:04:24 +0100 Subject: [PATCH 098/362] Diagnose call expression on non-callable things --- crates/hir-ty/src/infer.rs | 9 +++++ crates/hir-ty/src/infer/expr.rs | 8 +++- crates/hir/src/diagnostics.rs | 7 ++++ crates/hir/src/lib.rs | 33 +++++++++------- .../src/handlers/expected_function.rs | 38 +++++++++++++++++++ crates/ide-diagnostics/src/lib.rs | 2 + 6 files changed, 83 insertions(+), 14 deletions(-) create mode 100644 crates/ide-diagnostics/src/handlers/expected_function.rs diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 336de1428214..0c7529cffee2 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -170,6 +170,7 @@ pub enum InferenceDiagnostic { // FIXME: Make this proper BreakOutsideOfLoop { expr: ExprId, is_break: bool, bad_value_break: bool }, MismatchedArgCount { call_expr: ExprId, expected: usize, found: usize }, + ExpectedFunction { call_expr: ExprId, found: Ty }, } /// A mismatch between an expected and an inferred type. @@ -505,6 +506,14 @@ impl<'a> InferenceContext<'a> { mismatch.expected = table.resolve_completely(mismatch.expected.clone()); mismatch.actual = table.resolve_completely(mismatch.actual.clone()); } + for diagnostic in &mut result.diagnostics { + match diagnostic { + InferenceDiagnostic::ExpectedFunction { found, .. } => { + *found = table.resolve_completely(found.clone()) + } + _ => (), + } + } for (_, subst) in result.method_resolutions.values_mut() { *subst = table.resolve_completely(subst.clone()); } diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index e64b020c7fbd..9d75b67bc789 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -364,7 +364,13 @@ impl<'a> InferenceContext<'a> { } (params, ret_ty) } - None => (Vec::new(), self.err_ty()), // FIXME diagnostic + None => { + self.result.diagnostics.push(InferenceDiagnostic::ExpectedFunction { + call_expr: tgt_expr, + found: callee_ty.clone(), + }); + (Vec::new(), self.err_ty()) + } }; let indices_to_skip = self.check_legacy_const_generics(derefed_callee, args); self.register_obligations_for_call(&callee_ty); diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index bb7468d46604..3a6f26a4ea08 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -31,6 +31,7 @@ macro_rules! diagnostics { diagnostics![ BreakOutsideOfLoop, + ExpectedFunction, InactiveCode, IncorrectCase, InvalidDeriveTarget, @@ -130,6 +131,12 @@ pub struct PrivateAssocItem { pub item: AssocItem, } +#[derive(Debug)] +pub struct ExpectedFunction { + pub call: InFile>, + pub found: Type, +} + #[derive(Debug)] pub struct PrivateField { pub expr: InFile>, diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index bfc0d58cc783..08ccf38b654d 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -84,9 +84,9 @@ use crate::db::{DefDatabase, HirDatabase}; pub use crate::{ attrs::{HasAttrs, Namespace}, diagnostics::{ - AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, IncorrectCase, InvalidDeriveTarget, - MacroError, MalformedDerive, MismatchedArgCount, MissingFields, MissingMatchArms, - MissingUnsafe, NoSuchField, PrivateAssocItem, PrivateField, + AnyDiagnostic, BreakOutsideOfLoop, ExpectedFunction, InactiveCode, IncorrectCase, + InvalidDeriveTarget, MacroError, MalformedDerive, MismatchedArgCount, MissingFields, + MissingMatchArms, MissingUnsafe, NoSuchField, PrivateAssocItem, PrivateField, ReplaceFilterMapNextWithFindMap, TypeMismatch, UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, UnresolvedModule, UnresolvedProcMacro, @@ -1377,8 +1377,8 @@ impl DefWithBody { let source_map = Lazy::new(|| db.body_with_source_map(self.into()).1); for d in &infer.diagnostics { match d { - hir_ty::InferenceDiagnostic::NoSuchField { expr } => { - let field = source_map.field_syntax(*expr); + &hir_ty::InferenceDiagnostic::NoSuchField { expr } => { + let field = source_map.field_syntax(expr); acc.push(NoSuchField { field }.into()) } &hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { @@ -1391,15 +1391,10 @@ impl DefWithBody { .expect("break outside of loop in synthetic syntax"); acc.push(BreakOutsideOfLoop { expr, is_break, bad_value_break }.into()) } - hir_ty::InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => { - match source_map.expr_syntax(*call_expr) { + &hir_ty::InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => { + match source_map.expr_syntax(call_expr) { Ok(source_ptr) => acc.push( - MismatchedArgCount { - call_expr: source_ptr, - expected: *expected, - found: *found, - } - .into(), + MismatchedArgCount { call_expr: source_ptr, expected, found }.into(), ), Err(SyntheticSyntax) => (), } @@ -1423,6 +1418,18 @@ impl DefWithBody { let item = item.into(); acc.push(PrivateAssocItem { expr_or_pat, item }.into()) } + hir_ty::InferenceDiagnostic::ExpectedFunction { call_expr, found } => { + let call_expr = + source_map.expr_syntax(*call_expr).expect("unexpected synthetic"); + + acc.push( + ExpectedFunction { + call: call_expr, + found: Type::new(db, DefWithBodyId::from(self), found.clone()), + } + .into(), + ) + } } } for (pat_or_expr, mismatch) in infer.type_mismatches() { diff --git a/crates/ide-diagnostics/src/handlers/expected_function.rs b/crates/ide-diagnostics/src/handlers/expected_function.rs new file mode 100644 index 000000000000..23bc778da290 --- /dev/null +++ b/crates/ide-diagnostics/src/handlers/expected_function.rs @@ -0,0 +1,38 @@ +use hir::HirDisplay; + +use crate::{Diagnostic, DiagnosticsContext}; + +// Diagnostic: expected-function +// +// This diagnostic is triggered if a call is made on something that is not callable. +pub(crate) fn expected_function( + ctx: &DiagnosticsContext<'_>, + d: &hir::ExpectedFunction, +) -> Diagnostic { + Diagnostic::new( + "expected-function", + format!("expected function, found {}", d.found.display(ctx.sema.db)), + ctx.sema.diagnostics_display_range(d.call.clone().map(|it| it.into())).range, + ) +} + +#[cfg(test)] +mod tests { + use crate::tests::check_diagnostics; + + #[test] + fn smoke_test() { + check_diagnostics( + r#" +fn foo() { + let x = 3; + x(); + // ^^^ error: expected function, found i32 + ""(); + // ^^^^ error: expected function, found &str + foo(); +} +"#, + ); + } +} diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs index 64ba08ac883b..b878119fee1f 100644 --- a/crates/ide-diagnostics/src/lib.rs +++ b/crates/ide-diagnostics/src/lib.rs @@ -27,6 +27,7 @@ mod handlers { pub(crate) mod break_outside_of_loop; + pub(crate) mod expected_function; pub(crate) mod inactive_code; pub(crate) mod incorrect_case; pub(crate) mod invalid_derive_target; @@ -248,6 +249,7 @@ pub fn diagnostics( #[rustfmt::skip] let d = match diag { AnyDiagnostic::BreakOutsideOfLoop(d) => handlers::break_outside_of_loop::break_outside_of_loop(&ctx, &d), + AnyDiagnostic::ExpectedFunction(d) => handlers::expected_function::expected_function(&ctx, &d), AnyDiagnostic::IncorrectCase(d) => handlers::incorrect_case::incorrect_case(&ctx, &d), AnyDiagnostic::MacroError(d) => handlers::macro_error::macro_error(&ctx, &d), AnyDiagnostic::MalformedDerive(d) => handlers::malformed_derive::malformed_derive(&ctx, &d), From 78b2dd813a927012328bf870d680fe952c8157b9 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 3 Mar 2023 19:32:18 +0100 Subject: [PATCH 099/362] Diagnose unresolved field accesses --- crates/hir-ty/src/infer.rs | 18 ++- crates/hir-ty/src/infer/expr.rs | 152 ++++++++++-------- crates/hir/src/diagnostics.rs | 9 ++ crates/hir/src/lib.rs | 47 +++--- .../src/completions/flyimport.rs | 5 +- .../src/handlers/unresolved_field.rs | 134 +++++++++++++++ crates/ide-diagnostics/src/lib.rs | 2 + 7 files changed, 273 insertions(+), 94 deletions(-) create mode 100644 crates/ide-diagnostics/src/handlers/unresolved_field.rs diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 0c7529cffee2..a663a568b919 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -31,7 +31,7 @@ use hir_def::{ AdtId, AssocItemId, DefWithBodyId, EnumVariantId, FieldId, FunctionId, HasModule, ItemContainerId, Lookup, TraitId, TypeAliasId, VariantId, }; -use hir_expand::name::name; +use hir_expand::name::{name, Name}; use la_arena::ArenaMap; use rustc_hash::FxHashMap; use stdx::always; @@ -167,6 +167,7 @@ pub enum InferenceDiagnostic { NoSuchField { expr: ExprId }, PrivateField { expr: ExprId, field: FieldId }, PrivateAssocItem { id: ExprOrPatId, item: AssocItemId }, + UnresolvedField { expr: ExprId, receiver: Ty, name: Name, method_with_same_name_exists: bool }, // FIXME: Make this proper BreakOutsideOfLoop { expr: ExprId, is_break: bool, bad_value_break: bool }, MismatchedArgCount { call_expr: ExprId, expected: usize, found: usize }, @@ -506,14 +507,17 @@ impl<'a> InferenceContext<'a> { mismatch.expected = table.resolve_completely(mismatch.expected.clone()); mismatch.actual = table.resolve_completely(mismatch.actual.clone()); } - for diagnostic in &mut result.diagnostics { - match diagnostic { - InferenceDiagnostic::ExpectedFunction { found, .. } => { - *found = table.resolve_completely(found.clone()) + result.diagnostics.retain_mut(|diagnostic| { + if let InferenceDiagnostic::ExpectedFunction { found: ty, .. } + | InferenceDiagnostic::UnresolvedField { receiver: ty, .. } = diagnostic + { + *ty = table.resolve_completely(ty.clone()); + if ty.is_unknown() { + return false; } - _ => (), } - } + true + }); for (_, subst) in result.method_resolutions.values_mut() { *subst = table.resolve_completely(subst.clone()); } diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 9d75b67bc789..531a359a96a4 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -552,71 +552,7 @@ impl<'a> InferenceContext<'a> { } ty } - Expr::Field { expr, name } => { - let receiver_ty = self.infer_expr_inner(*expr, &Expectation::none()); - - let mut autoderef = Autoderef::new(&mut self.table, receiver_ty); - let mut private_field = None; - let ty = autoderef.by_ref().find_map(|(derefed_ty, _)| { - let (field_id, parameters) = match derefed_ty.kind(Interner) { - TyKind::Tuple(_, substs) => { - return name.as_tuple_index().and_then(|idx| { - substs - .as_slice(Interner) - .get(idx) - .map(|a| a.assert_ty_ref(Interner)) - .cloned() - }); - } - TyKind::Adt(AdtId(hir_def::AdtId::StructId(s)), parameters) => { - let local_id = self.db.struct_data(*s).variant_data.field(name)?; - let field = FieldId { parent: (*s).into(), local_id }; - (field, parameters.clone()) - } - TyKind::Adt(AdtId(hir_def::AdtId::UnionId(u)), parameters) => { - let local_id = self.db.union_data(*u).variant_data.field(name)?; - let field = FieldId { parent: (*u).into(), local_id }; - (field, parameters.clone()) - } - _ => return None, - }; - let is_visible = self.db.field_visibilities(field_id.parent)[field_id.local_id] - .is_visible_from(self.db.upcast(), self.resolver.module()); - if !is_visible { - if private_field.is_none() { - private_field = Some(field_id); - } - return None; - } - // can't have `write_field_resolution` here because `self.table` is borrowed :( - self.result.field_resolutions.insert(tgt_expr, field_id); - let ty = self.db.field_types(field_id.parent)[field_id.local_id] - .clone() - .substitute(Interner, ¶meters); - Some(ty) - }); - let ty = match ty { - Some(ty) => { - let adjustments = auto_deref_adjust_steps(&autoderef); - self.write_expr_adj(*expr, adjustments); - let ty = self.insert_type_vars(ty); - let ty = self.normalize_associated_types_in(ty); - ty - } - _ => { - // Write down the first private field resolution if we found no field - // This aids IDE features for private fields like goto def - if let Some(field) = private_field { - self.result.field_resolutions.insert(tgt_expr, field); - self.result - .diagnostics - .push(InferenceDiagnostic::PrivateField { expr: tgt_expr, field }); - } - self.err_ty() - } - }; - ty - } + Expr::Field { expr, name } => self.infer_field_access(tgt_expr, *expr, name), Expr::Await { expr } => { let inner_ty = self.infer_expr_inner(*expr, &Expectation::none()); self.resolve_associated_type(inner_ty, self.resolve_future_future_output()) @@ -1276,6 +1212,92 @@ impl<'a> InferenceContext<'a> { } } + fn infer_field_access(&mut self, tgt_expr: ExprId, expr: ExprId, name: &Name) -> Ty { + let receiver_ty = self.infer_expr_inner(expr, &Expectation::none()); + + let mut autoderef = Autoderef::new(&mut self.table, receiver_ty.clone()); + let mut private_field = None; + let ty = autoderef.by_ref().find_map(|(derefed_ty, _)| { + let (field_id, parameters) = match derefed_ty.kind(Interner) { + TyKind::Tuple(_, substs) => { + return name.as_tuple_index().and_then(|idx| { + substs + .as_slice(Interner) + .get(idx) + .map(|a| a.assert_ty_ref(Interner)) + .cloned() + }); + } + TyKind::Adt(AdtId(hir_def::AdtId::StructId(s)), parameters) => { + let local_id = self.db.struct_data(*s).variant_data.field(name)?; + let field = FieldId { parent: (*s).into(), local_id }; + (field, parameters.clone()) + } + TyKind::Adt(AdtId(hir_def::AdtId::UnionId(u)), parameters) => { + let local_id = self.db.union_data(*u).variant_data.field(name)?; + let field = FieldId { parent: (*u).into(), local_id }; + (field, parameters.clone()) + } + _ => return None, + }; + let is_visible = self.db.field_visibilities(field_id.parent)[field_id.local_id] + .is_visible_from(self.db.upcast(), self.resolver.module()); + if !is_visible { + if private_field.is_none() { + private_field = Some(field_id); + } + return None; + } + // can't have `write_field_resolution` here because `self.table` is borrowed :( + self.result.field_resolutions.insert(tgt_expr, field_id); + let ty = self.db.field_types(field_id.parent)[field_id.local_id] + .clone() + .substitute(Interner, ¶meters); + Some(ty) + }); + let ty = match ty { + Some(ty) => { + let adjustments = auto_deref_adjust_steps(&autoderef); + self.write_expr_adj(expr, adjustments); + let ty = self.insert_type_vars(ty); + let ty = self.normalize_associated_types_in(ty); + ty + } + _ => { + // Write down the first private field resolution if we found no field + // This aids IDE features for private fields like goto def + if let Some(field) = private_field { + self.result.field_resolutions.insert(tgt_expr, field); + // FIXME: Merge this diagnostic into UnresolvedField + self.result + .diagnostics + .push(InferenceDiagnostic::PrivateField { expr: tgt_expr, field }); + } else { + // no field found, try looking for a method of the same name + let canonicalized_receiver = self.canonicalize(receiver_ty.clone()); + let traits_in_scope = self.resolver.traits_in_scope(self.db.upcast()); + + let resolved = method_resolution::lookup_method( + self.db, + &canonicalized_receiver.value, + self.trait_env.clone(), + &traits_in_scope, + VisibleFromModule::Filter(self.resolver.module()), + name, + ); + self.result.diagnostics.push(InferenceDiagnostic::UnresolvedField { + expr: tgt_expr, + receiver: receiver_ty, + name: name.clone(), + method_with_same_name_exists: resolved.is_some(), + }); + } + self.err_ty() + } + }; + ty + } + fn infer_method_call( &mut self, tgt_expr: ExprId, diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index 3a6f26a4ea08..58d02479e57c 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -48,6 +48,7 @@ diagnostics![ TypeMismatch, UnimplementedBuiltinMacro, UnresolvedExternCrate, + UnresolvedField, UnresolvedImport, UnresolvedMacroCall, UnresolvedModule, @@ -137,6 +138,14 @@ pub struct ExpectedFunction { pub found: Type, } +#[derive(Debug)] +pub struct UnresolvedField { + pub expr: InFile>, + pub receiver: Type, + pub name: Name, + pub method_with_same_name_exists: bool, +} + #[derive(Debug)] pub struct PrivateField { pub expr: InFile>, diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 08ccf38b654d..bac6d4cd8540 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -88,8 +88,8 @@ pub use crate::{ InvalidDeriveTarget, MacroError, MalformedDerive, MismatchedArgCount, MissingFields, MissingMatchArms, MissingUnsafe, NoSuchField, PrivateAssocItem, PrivateField, ReplaceFilterMapNextWithFindMap, TypeMismatch, UnimplementedBuiltinMacro, - UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, UnresolvedModule, - UnresolvedProcMacro, + UnresolvedExternCrate, UnresolvedField, UnresolvedImport, UnresolvedMacroCall, + UnresolvedModule, UnresolvedProcMacro, }, has_source::HasSource, semantics::{PathResolution, Semantics, SemanticsScope, TypeInfo, VisibleTraits}, @@ -1375,6 +1375,7 @@ impl DefWithBody { let infer = db.infer(self.into()); let source_map = Lazy::new(|| db.body_with_source_map(self.into()).1); + let expr_syntax = |expr| source_map.expr_syntax(expr).expect("unexpected synthetic"); for d in &infer.diagnostics { match d { &hir_ty::InferenceDiagnostic::NoSuchField { expr } => { @@ -1386,30 +1387,23 @@ impl DefWithBody { is_break, bad_value_break, } => { - let expr = source_map - .expr_syntax(expr) - .expect("break outside of loop in synthetic syntax"); + let expr = expr_syntax(expr); acc.push(BreakOutsideOfLoop { expr, is_break, bad_value_break }.into()) } &hir_ty::InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => { - match source_map.expr_syntax(call_expr) { - Ok(source_ptr) => acc.push( - MismatchedArgCount { call_expr: source_ptr, expected, found }.into(), - ), - Err(SyntheticSyntax) => (), - } + acc.push( + MismatchedArgCount { call_expr: expr_syntax(call_expr), expected, found } + .into(), + ) } &hir_ty::InferenceDiagnostic::PrivateField { expr, field } => { - let expr = source_map.expr_syntax(expr).expect("unexpected synthetic"); + let expr = expr_syntax(expr); let field = field.into(); acc.push(PrivateField { expr, field }.into()) } &hir_ty::InferenceDiagnostic::PrivateAssocItem { id, item } => { let expr_or_pat = match id { - ExprOrPatId::ExprId(expr) => source_map - .expr_syntax(expr) - .expect("unexpected synthetic") - .map(Either::Left), + ExprOrPatId::ExprId(expr) => expr_syntax(expr).map(Either::Left), ExprOrPatId::PatId(pat) => source_map .pat_syntax(pat) .expect("unexpected synthetic") @@ -1419,8 +1413,7 @@ impl DefWithBody { acc.push(PrivateAssocItem { expr_or_pat, item }.into()) } hir_ty::InferenceDiagnostic::ExpectedFunction { call_expr, found } => { - let call_expr = - source_map.expr_syntax(*call_expr).expect("unexpected synthetic"); + let call_expr = expr_syntax(*call_expr); acc.push( ExpectedFunction { @@ -1430,6 +1423,24 @@ impl DefWithBody { .into(), ) } + hir_ty::InferenceDiagnostic::UnresolvedField { + expr, + receiver, + name, + method_with_same_name_exists, + } => { + let expr = expr_syntax(*expr); + + acc.push( + UnresolvedField { + expr, + name: name.clone(), + receiver: Type::new(db, DefWithBodyId::from(self), receiver.clone()), + method_with_same_name_exists: *method_with_same_name_exists, + } + .into(), + ) + } } } for (pat_or_expr, mismatch) in infer.type_mismatches() { diff --git a/crates/ide-completion/src/completions/flyimport.rs b/crates/ide-completion/src/completions/flyimport.rs index 364969af9c9a..0979f6a6dfc7 100644 --- a/crates/ide-completion/src/completions/flyimport.rs +++ b/crates/ide-completion/src/completions/flyimport.rs @@ -5,10 +5,7 @@ use ide_db::imports::{ insert_use::ImportScope, }; use itertools::Itertools; -use syntax::{ - ast::{self}, - AstNode, SyntaxNode, T, -}; +use syntax::{ast, AstNode, SyntaxNode, T}; use crate::{ context::{ diff --git a/crates/ide-diagnostics/src/handlers/unresolved_field.rs b/crates/ide-diagnostics/src/handlers/unresolved_field.rs new file mode 100644 index 000000000000..33c39de085d8 --- /dev/null +++ b/crates/ide-diagnostics/src/handlers/unresolved_field.rs @@ -0,0 +1,134 @@ +use hir::{db::AstDatabase, HirDisplay, InFile}; +use ide_db::{ + assists::{Assist, AssistId, AssistKind}, + base_db::FileRange, + label::Label, + source_change::SourceChange, +}; +use syntax::{ast, AstNode, AstPtr}; +use text_edit::TextEdit; + +use crate::{Diagnostic, DiagnosticsContext}; + +// Diagnostic: unresolved-field +// +// This diagnostic is triggered if a field does not exist on a given type. +pub(crate) fn unresolved_field( + ctx: &DiagnosticsContext<'_>, + d: &hir::UnresolvedField, +) -> Diagnostic { + let method_suffix = if d.method_with_same_name_exists { + ", but a method with a similar name exists" + } else { + "" + }; + Diagnostic::new( + "unresolved-field", + format!( + "no field `{}` on type `{}`{method_suffix}", + d.name, + d.receiver.display(ctx.sema.db) + ), + ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range, + ) + .with_fixes(fixes(ctx, d)) +} + +fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedField) -> Option> { + if d.method_with_same_name_exists { + method_fix(ctx, &d.expr) + } else { + // FIXME: add quickfix + + None + } +} + +// FIXME: We should fill out the call here, mvoe the cursor and trigger signature help +fn method_fix( + ctx: &DiagnosticsContext<'_>, + expr_ptr: &InFile>, +) -> Option> { + let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id)?; + let expr = expr_ptr.value.to_node(&root); + let FileRange { range, file_id } = ctx.sema.original_range_opt(expr.syntax())?; + Some(vec![Assist { + id: AssistId("expected-field-found-method-call-fix", AssistKind::QuickFix), + label: Label::new("Use parentheses to call the method".to_string()), + group: None, + target: range, + source_change: Some(SourceChange::from_text_edit( + file_id, + TextEdit::insert(range.end(), "()".to_owned()), + )), + trigger_signature_help: false, + }]) +} +#[cfg(test)] +mod tests { + use crate::tests::check_diagnostics; + + #[test] + fn smoke_test() { + check_diagnostics( + r#" +fn main() { + ().foo; + // ^^^^^^ error: no field `foo` on type `()` +} +"#, + ); + } + + #[test] + fn method_clash() { + check_diagnostics( + r#" +struct Foo; +impl Foo { + fn bar(&self) {} +} +fn foo() { + Foo.bar; + // ^^^^^^^ 💡 error: no field `bar` on type `Foo`, but a method with a similar name exists +} +"#, + ); + } + + #[test] + fn method_trait_() { + check_diagnostics( + r#" +struct Foo; +trait Bar { + fn bar(&self) {} +} +impl Bar for Foo {} +fn foo() { + Foo.bar; + // ^^^^^^^ 💡 error: no field `bar` on type `Foo`, but a method with a similar name exists +} +"#, + ); + } + + #[test] + fn method_trait_2() { + check_diagnostics( + r#" +struct Foo; +trait Bar { + fn bar(&self); +} +impl Bar for Foo { + fn bar(&self) {} +} +fn foo() { + Foo.bar; + // ^^^^^^^ 💡 error: no field `bar` on type `Foo`, but a method with a similar name exists +} +"#, + ); + } +} diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs index b878119fee1f..a0e8bca5cb90 100644 --- a/crates/ide-diagnostics/src/lib.rs +++ b/crates/ide-diagnostics/src/lib.rs @@ -44,6 +44,7 @@ mod handlers { pub(crate) mod type_mismatch; pub(crate) mod unimplemented_builtin_macro; pub(crate) mod unresolved_extern_crate; + pub(crate) mod unresolved_field; pub(crate) mod unresolved_import; pub(crate) mod unresolved_macro_call; pub(crate) mod unresolved_module; @@ -269,6 +270,7 @@ pub fn diagnostics( AnyDiagnostic::UnresolvedModule(d) => handlers::unresolved_module::unresolved_module(&ctx, &d), AnyDiagnostic::UnresolvedProcMacro(d) => handlers::unresolved_proc_macro::unresolved_proc_macro(&ctx, &d, config.proc_macros_enabled, config.proc_attr_macros_enabled), AnyDiagnostic::InvalidDeriveTarget(d) => handlers::invalid_derive_target::invalid_derive_target(&ctx, &d), + AnyDiagnostic::UnresolvedField(d) => handlers::unresolved_field::unresolved_field(&ctx, &d), AnyDiagnostic::InactiveCode(d) => match handlers::inactive_code::inactive_code(&ctx, &d) { Some(it) => it, From e7485a04169f79c63333f2b0d39e159d10b92cee Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 3 Mar 2023 20:41:17 +0100 Subject: [PATCH 100/362] Diagnose unresolved method calls --- crates/hir-ty/src/infer.rs | 63 +++++++-- crates/hir-ty/src/infer/expr.rs | 121 +++++++++++----- crates/hir/src/diagnostics.rs | 9 ++ crates/hir/src/lib.rs | 22 ++- crates/ide-db/src/source_change.rs | 8 ++ .../replace_filter_map_next_with_find_map.rs | 13 +- .../src/handlers/unresolved_method.rs | 130 ++++++++++++++++++ crates/ide-diagnostics/src/lib.rs | 2 + 8 files changed, 320 insertions(+), 48 deletions(-) create mode 100644 crates/ide-diagnostics/src/handlers/unresolved_method.rs diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index a663a568b919..22dcea8fcd45 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -164,14 +164,45 @@ pub(crate) type InferResult = Result, TypeError>; #[derive(Debug, PartialEq, Eq, Clone)] pub enum InferenceDiagnostic { - NoSuchField { expr: ExprId }, - PrivateField { expr: ExprId, field: FieldId }, - PrivateAssocItem { id: ExprOrPatId, item: AssocItemId }, - UnresolvedField { expr: ExprId, receiver: Ty, name: Name, method_with_same_name_exists: bool }, + NoSuchField { + expr: ExprId, + }, + PrivateField { + expr: ExprId, + field: FieldId, + }, + PrivateAssocItem { + id: ExprOrPatId, + item: AssocItemId, + }, + UnresolvedField { + expr: ExprId, + receiver: Ty, + name: Name, + method_with_same_name_exists: bool, + }, + UnresolvedMethodCall { + expr: ExprId, + receiver: Ty, + name: Name, + /// Contains the type the field resolves to + field_with_same_name: Option, + }, // FIXME: Make this proper - BreakOutsideOfLoop { expr: ExprId, is_break: bool, bad_value_break: bool }, - MismatchedArgCount { call_expr: ExprId, expected: usize, found: usize }, - ExpectedFunction { call_expr: ExprId, found: Ty }, + BreakOutsideOfLoop { + expr: ExprId, + is_break: bool, + bad_value_break: bool, + }, + MismatchedArgCount { + call_expr: ExprId, + expected: usize, + found: usize, + }, + ExpectedFunction { + call_expr: ExprId, + found: Ty, + }, } /// A mismatch between an expected and an inferred type. @@ -509,12 +540,28 @@ impl<'a> InferenceContext<'a> { } result.diagnostics.retain_mut(|diagnostic| { if let InferenceDiagnostic::ExpectedFunction { found: ty, .. } - | InferenceDiagnostic::UnresolvedField { receiver: ty, .. } = diagnostic + | InferenceDiagnostic::UnresolvedField { receiver: ty, .. } + | InferenceDiagnostic::UnresolvedMethodCall { receiver: ty, .. } = diagnostic { *ty = table.resolve_completely(ty.clone()); + // FIXME: Remove this when we are on par with rustc in terms of inference if ty.is_unknown() { return false; } + + if let InferenceDiagnostic::UnresolvedMethodCall { field_with_same_name, .. } = + diagnostic + { + let clear = if let Some(ty) = field_with_same_name { + *ty = table.resolve_completely(ty.clone()); + ty.is_unknown() + } else { + false + }; + if clear { + *field_with_same_name = None; + } + } } true }); diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 531a359a96a4..02024e1ea780 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -1212,12 +1212,14 @@ impl<'a> InferenceContext<'a> { } } - fn infer_field_access(&mut self, tgt_expr: ExprId, expr: ExprId, name: &Name) -> Ty { - let receiver_ty = self.infer_expr_inner(expr, &Expectation::none()); - + fn lookup_field( + &mut self, + receiver_ty: &Ty, + name: &Name, + ) -> Option<(Ty, Option, Vec, bool)> { let mut autoderef = Autoderef::new(&mut self.table, receiver_ty.clone()); let mut private_field = None; - let ty = autoderef.by_ref().find_map(|(derefed_ty, _)| { + let res = autoderef.by_ref().find_map(|(derefed_ty, _)| { let (field_id, parameters) = match derefed_ty.kind(Interner) { TyKind::Tuple(_, substs) => { return name.as_tuple_index().and_then(|idx| { @@ -1226,6 +1228,7 @@ impl<'a> InferenceContext<'a> { .get(idx) .map(|a| a.assert_ty_ref(Interner)) .cloned() + .map(|ty| (None, ty)) }); } TyKind::Adt(AdtId(hir_def::AdtId::StructId(s)), parameters) => { @@ -1244,58 +1247,81 @@ impl<'a> InferenceContext<'a> { .is_visible_from(self.db.upcast(), self.resolver.module()); if !is_visible { if private_field.is_none() { - private_field = Some(field_id); + private_field = Some((field_id, parameters)); } return None; } - // can't have `write_field_resolution` here because `self.table` is borrowed :( - self.result.field_resolutions.insert(tgt_expr, field_id); let ty = self.db.field_types(field_id.parent)[field_id.local_id] .clone() .substitute(Interner, ¶meters); - Some(ty) + Some((Some(field_id), ty)) }); - let ty = match ty { - Some(ty) => { + + Some(match res { + Some((field_id, ty)) => { let adjustments = auto_deref_adjust_steps(&autoderef); - self.write_expr_adj(expr, adjustments); let ty = self.insert_type_vars(ty); let ty = self.normalize_associated_types_in(ty); + + (ty, field_id, adjustments, true) + } + None => { + let (field_id, subst) = private_field?; + let adjustments = auto_deref_adjust_steps(&autoderef); + let ty = self.db.field_types(field_id.parent)[field_id.local_id] + .clone() + .substitute(Interner, &subst); + let ty = self.insert_type_vars(ty); + let ty = self.normalize_associated_types_in(ty); + + (ty, Some(field_id), adjustments, false) + } + }) + } + + fn infer_field_access(&mut self, tgt_expr: ExprId, receiver: ExprId, name: &Name) -> Ty { + let receiver_ty = self.infer_expr_inner(receiver, &Expectation::none()); + match self.lookup_field(&receiver_ty, name) { + Some((ty, field_id, adjustments, is_public)) => { + self.write_expr_adj(receiver, adjustments); + if let Some(field_id) = field_id { + self.result.field_resolutions.insert(tgt_expr, field_id); + } + if !is_public { + if let Some(field) = field_id { + // FIXME: Merge this diagnostic into UnresolvedField? + self.result + .diagnostics + .push(InferenceDiagnostic::PrivateField { expr: tgt_expr, field }); + } + } ty } - _ => { - // Write down the first private field resolution if we found no field - // This aids IDE features for private fields like goto def - if let Some(field) = private_field { - self.result.field_resolutions.insert(tgt_expr, field); - // FIXME: Merge this diagnostic into UnresolvedField - self.result - .diagnostics - .push(InferenceDiagnostic::PrivateField { expr: tgt_expr, field }); - } else { - // no field found, try looking for a method of the same name + None => { + // no field found, + let method_with_same_name_exists = { let canonicalized_receiver = self.canonicalize(receiver_ty.clone()); let traits_in_scope = self.resolver.traits_in_scope(self.db.upcast()); - let resolved = method_resolution::lookup_method( + method_resolution::lookup_method( self.db, &canonicalized_receiver.value, self.trait_env.clone(), &traits_in_scope, VisibleFromModule::Filter(self.resolver.module()), name, - ); - self.result.diagnostics.push(InferenceDiagnostic::UnresolvedField { - expr: tgt_expr, - receiver: receiver_ty, - name: name.clone(), - method_with_same_name_exists: resolved.is_some(), - }); - } + ) + .is_some() + }; + self.result.diagnostics.push(InferenceDiagnostic::UnresolvedField { + expr: tgt_expr, + receiver: receiver_ty, + name: name.clone(), + method_with_same_name_exists, + }); self.err_ty() } - }; - ty + } } fn infer_method_call( @@ -1335,11 +1361,30 @@ impl<'a> InferenceContext<'a> { } (ty, self.db.value_ty(func.into()), substs) } - None => ( - receiver_ty, - Binders::empty(Interner, self.err_ty()), - Substitution::empty(Interner), - ), + None => { + let field_with_same_name_exists = match self.lookup_field(&receiver_ty, method_name) + { + Some((ty, field_id, adjustments, _public)) => { + self.write_expr_adj(receiver, adjustments); + if let Some(field_id) = field_id { + self.result.field_resolutions.insert(tgt_expr, field_id); + } + Some(ty) + } + None => None, + }; + self.result.diagnostics.push(InferenceDiagnostic::UnresolvedMethodCall { + expr: tgt_expr, + receiver: receiver_ty.clone(), + name: method_name.clone(), + field_with_same_name: field_with_same_name_exists, + }); + ( + receiver_ty, + Binders::empty(Interner, self.err_ty()), + Substitution::empty(Interner), + ) + } }; let method_ty = method_ty.substitute(Interner, &substs); self.register_obligations_for_call(&method_ty); diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index 58d02479e57c..b30c664e24f1 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -51,6 +51,7 @@ diagnostics![ UnresolvedField, UnresolvedImport, UnresolvedMacroCall, + UnresolvedMethodCall, UnresolvedModule, UnresolvedProcMacro, ]; @@ -146,6 +147,14 @@ pub struct UnresolvedField { pub method_with_same_name_exists: bool, } +#[derive(Debug)] +pub struct UnresolvedMethodCall { + pub expr: InFile>, + pub receiver: Type, + pub name: Name, + pub field_with_same_name: Option, +} + #[derive(Debug)] pub struct PrivateField { pub expr: InFile>, diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index bac6d4cd8540..269c45943e51 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -89,7 +89,7 @@ pub use crate::{ MissingMatchArms, MissingUnsafe, NoSuchField, PrivateAssocItem, PrivateField, ReplaceFilterMapNextWithFindMap, TypeMismatch, UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedField, UnresolvedImport, UnresolvedMacroCall, - UnresolvedModule, UnresolvedProcMacro, + UnresolvedMethodCall, UnresolvedModule, UnresolvedProcMacro, }, has_source::HasSource, semantics::{PathResolution, Semantics, SemanticsScope, TypeInfo, VisibleTraits}, @@ -1441,6 +1441,26 @@ impl DefWithBody { .into(), ) } + hir_ty::InferenceDiagnostic::UnresolvedMethodCall { + expr, + receiver, + name, + field_with_same_name, + } => { + let expr = expr_syntax(*expr); + + acc.push( + UnresolvedMethodCall { + expr, + name: name.clone(), + receiver: Type::new(db, DefWithBodyId::from(self), receiver.clone()), + field_with_same_name: field_with_same_name + .clone() + .map(|ty| Type::new(db, DefWithBodyId::from(self), ty)), + } + .into(), + ) + } } } for (pat_or_expr, mismatch) in infer.type_mismatches() { diff --git a/crates/ide-db/src/source_change.rs b/crates/ide-db/src/source_change.rs index 8e338061df43..936354f29613 100644 --- a/crates/ide-db/src/source_change.rs +++ b/crates/ide-db/src/source_change.rs @@ -83,6 +83,14 @@ impl From> for SourceChange { } } +impl FromIterator<(FileId, TextEdit)> for SourceChange { + fn from_iter>(iter: T) -> Self { + let mut this = SourceChange::default(); + this.extend(iter); + this + } +} + pub struct SourceChangeBuilder { pub edit: TextEditBuilder, pub file_id: FileId, diff --git a/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs b/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs index 9826e1c707ea..a0c276cc3328 100644 --- a/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs +++ b/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs @@ -55,7 +55,18 @@ fn fixes( #[cfg(test)] mod tests { - use crate::tests::{check_diagnostics, check_fix}; + use crate::{ + tests::{check_diagnostics_with_config, check_fix}, + DiagnosticsConfig, + }; + + #[track_caller] + pub(crate) fn check_diagnostics(ra_fixture: &str) { + let mut config = DiagnosticsConfig::test_sample(); + config.disabled.insert("inactive-code".to_string()); + config.disabled.insert("unresolved-method".to_string()); + check_diagnostics_with_config(config, ra_fixture) + } #[test] fn replace_filter_map_next_with_find_map2() { diff --git a/crates/ide-diagnostics/src/handlers/unresolved_method.rs b/crates/ide-diagnostics/src/handlers/unresolved_method.rs new file mode 100644 index 000000000000..0d1f91f02c3b --- /dev/null +++ b/crates/ide-diagnostics/src/handlers/unresolved_method.rs @@ -0,0 +1,130 @@ +use hir::{db::AstDatabase, HirDisplay}; +use ide_db::{ + assists::{Assist, AssistId, AssistKind}, + base_db::FileRange, + label::Label, + source_change::SourceChange, +}; +use syntax::{ast, AstNode, TextRange}; +use text_edit::TextEdit; + +use crate::{Diagnostic, DiagnosticsContext}; + +// Diagnostic: unresolved-method +// +// This diagnostic is triggered if a method does not exist on a given type. +pub(crate) fn unresolved_method( + ctx: &DiagnosticsContext<'_>, + d: &hir::UnresolvedMethodCall, +) -> Diagnostic { + let field_suffix = if d.field_with_same_name.is_some() { + ", but a field with a similar name exists" + } else { + "" + }; + Diagnostic::new( + "unresolved-method", + format!( + "no method `{}` on type `{}`{field_suffix}", + d.name, + d.receiver.display(ctx.sema.db) + ), + ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range, + ) + .with_fixes(fixes(ctx, d)) +} + +fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedMethodCall) -> Option> { + if let Some(ty) = &d.field_with_same_name { + field_fix(ctx, d, ty) + } else { + // FIXME: add quickfix + None + } +} + +fn field_fix( + ctx: &DiagnosticsContext<'_>, + d: &hir::UnresolvedMethodCall, + ty: &hir::Type, +) -> Option> { + if !ty.impls_fnonce(ctx.sema.db) { + return None; + } + let expr_ptr = &d.expr; + let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id)?; + let expr = expr_ptr.value.to_node(&root); + let (file_id, range) = match expr { + ast::Expr::MethodCallExpr(mcall) => { + let FileRange { range, file_id } = + ctx.sema.original_range_opt(mcall.receiver()?.syntax())?; + let FileRange { range: range2, file_id: file_id2 } = + ctx.sema.original_range_opt(mcall.name_ref()?.syntax())?; + if file_id != file_id2 { + return None; + } + (file_id, TextRange::new(range.start(), range2.end())) + } + _ => return None, + }; + Some(vec![Assist { + id: AssistId("expected-method-found-field-fix", AssistKind::QuickFix), + label: Label::new("Use parentheses to call the value of the field".to_string()), + group: None, + target: range, + source_change: Some(SourceChange::from_iter([ + (file_id, TextEdit::insert(range.start(), "(".to_owned())), + (file_id, TextEdit::insert(range.end(), ")".to_owned())), + ])), + trigger_signature_help: false, + }]) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_diagnostics, check_fix}; + + #[test] + fn smoke_test() { + check_diagnostics( + r#" +fn main() { + ().foo(); + // ^^^^^^^^ error: no method `foo` on type `()` +} +"#, + ); + } + + #[test] + fn field() { + check_diagnostics( + r#" +struct Foo { bar: i32 } +fn foo() { + Foo { bar: i32 }.bar(); + // ^^^^^^^^^^^^^^^^^^^^^^ error: no method `bar` on type `Foo`, but a field with a similar name exists +} +"#, + ); + } + + #[test] + fn callable_field() { + check_fix( + r#" +//- minicore: fn +struct Foo { bar: fn() } +fn foo() { + Foo { bar: foo }.b$0ar(); +} +"#, + r#" +struct Foo { bar: fn() } +fn foo() { + (Foo { bar: foo }.bar)(); +} +"#, + ); + } +} diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs index a0e8bca5cb90..c8635ff80110 100644 --- a/crates/ide-diagnostics/src/lib.rs +++ b/crates/ide-diagnostics/src/lib.rs @@ -45,6 +45,7 @@ mod handlers { pub(crate) mod unimplemented_builtin_macro; pub(crate) mod unresolved_extern_crate; pub(crate) mod unresolved_field; + pub(crate) mod unresolved_method; pub(crate) mod unresolved_import; pub(crate) mod unresolved_macro_call; pub(crate) mod unresolved_module; @@ -271,6 +272,7 @@ pub fn diagnostics( AnyDiagnostic::UnresolvedProcMacro(d) => handlers::unresolved_proc_macro::unresolved_proc_macro(&ctx, &d, config.proc_macros_enabled, config.proc_attr_macros_enabled), AnyDiagnostic::InvalidDeriveTarget(d) => handlers::invalid_derive_target::invalid_derive_target(&ctx, &d), AnyDiagnostic::UnresolvedField(d) => handlers::unresolved_field::unresolved_field(&ctx, &d), + AnyDiagnostic::UnresolvedMethodCall(d) => handlers::unresolved_method::unresolved_method(&ctx, &d), AnyDiagnostic::InactiveCode(d) => match handlers::inactive_code::inactive_code(&ctx, &d) { Some(it) => it, From c12fac698f33e76a94a1c38a5763bfc419df0507 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 3 Mar 2023 21:06:48 +0100 Subject: [PATCH 101/362] Report type metrics for patterns --- .../rust-analyzer/src/cli/analysis_stats.rs | 187 +++++++++++++++++- 1 file changed, 178 insertions(+), 9 deletions(-) diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs index 93297faa664d..d8f632791b9d 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -12,7 +12,7 @@ use hir::{ }; use hir_def::{ body::{BodySourceMap, SyntheticSyntax}, - expr::ExprId, + expr::{ExprId, PatId}, FunctionId, }; use hir_ty::{Interner, TyExt, TypeFlags}; @@ -222,7 +222,11 @@ impl flags::AnalysisStats { let mut num_exprs = 0; let mut num_exprs_unknown = 0; let mut num_exprs_partially_unknown = 0; - let mut num_type_mismatches = 0; + let mut num_expr_type_mismatches = 0; + let mut num_pats = 0; + let mut num_pats_unknown = 0; + let mut num_pats_partially_unknown = 0; + let mut num_pat_type_mismatches = 0; let analysis = host.analysis(); for f in funcs.iter().copied() { let name = f.name(db); @@ -255,6 +259,8 @@ impl flags::AnalysisStats { let f_id = FunctionId::from(f); let (body, sm) = db.body_with_source_map(f_id.into()); let inference_result = db.infer(f_id.into()); + + // region:expressions let (previous_exprs, previous_unknown, previous_partially_unknown) = (num_exprs, num_exprs_unknown, num_exprs_partially_unknown); for (expr_id, _) in body.exprs.iter() { @@ -307,12 +313,12 @@ impl flags::AnalysisStats { if unknown_or_partial && self.output == Some(OutputFormat::Csv) { println!( r#"{},type,"{}""#, - location_csv(db, &analysis, vfs, &sm, expr_id), + location_csv_expr(db, &analysis, vfs, &sm, expr_id), ty.display(db) ); } if let Some(mismatch) = inference_result.type_mismatch_for_expr(expr_id) { - num_type_mismatches += 1; + num_expr_type_mismatches += 1; if verbosity.is_verbose() { if let Some((path, start, end)) = expr_syntax_range(db, &analysis, vfs, &sm, expr_id) @@ -339,7 +345,7 @@ impl flags::AnalysisStats { if self.output == Some(OutputFormat::Csv) { println!( r#"{},mismatch,"{}","{}""#, - location_csv(db, &analysis, vfs, &sm, expr_id), + location_csv_expr(db, &analysis, vfs, &sm, expr_id), mismatch.expected.display(db), mismatch.actual.display(db) ); @@ -355,6 +361,109 @@ impl flags::AnalysisStats { num_exprs_partially_unknown - previous_partially_unknown )); } + // endregion:expressions + + // region:patterns + let (previous_pats, previous_unknown, previous_partially_unknown) = + (num_pats, num_pats_unknown, num_pats_partially_unknown); + for (pat_id, _) in body.pats.iter() { + let ty = &inference_result[pat_id]; + num_pats += 1; + let unknown_or_partial = if ty.is_unknown() { + num_pats_unknown += 1; + if verbosity.is_spammy() { + if let Some((path, start, end)) = + pat_syntax_range(db, &analysis, vfs, &sm, pat_id) + { + bar.println(format!( + "{} {}:{}-{}:{}: Unknown type", + path, + start.line + 1, + start.col, + end.line + 1, + end.col, + )); + } else { + bar.println(format!("{name}: Unknown type",)); + } + } + true + } else { + let is_partially_unknown = + ty.data(Interner).flags.contains(TypeFlags::HAS_ERROR); + if is_partially_unknown { + num_pats_partially_unknown += 1; + } + is_partially_unknown + }; + if self.only.is_some() && verbosity.is_spammy() { + // in super-verbose mode for just one function, we print every single pattern + if let Some((_, start, end)) = pat_syntax_range(db, &analysis, vfs, &sm, pat_id) + { + bar.println(format!( + "{}:{}-{}:{}: {}", + start.line + 1, + start.col, + end.line + 1, + end.col, + ty.display(db) + )); + } else { + bar.println(format!("unknown location: {}", ty.display(db))); + } + } + if unknown_or_partial && self.output == Some(OutputFormat::Csv) { + println!( + r#"{},type,"{}""#, + location_csv_pat(db, &analysis, vfs, &sm, pat_id), + ty.display(db) + ); + } + if let Some(mismatch) = inference_result.type_mismatch_for_pat(pat_id) { + num_pat_type_mismatches += 1; + if verbosity.is_verbose() { + if let Some((path, start, end)) = + pat_syntax_range(db, &analysis, vfs, &sm, pat_id) + { + bar.println(format!( + "{} {}:{}-{}:{}: Expected {}, got {}", + path, + start.line + 1, + start.col, + end.line + 1, + end.col, + mismatch.expected.display(db), + mismatch.actual.display(db) + )); + } else { + bar.println(format!( + "{}: Expected {}, got {}", + name, + mismatch.expected.display(db), + mismatch.actual.display(db) + )); + } + } + if self.output == Some(OutputFormat::Csv) { + println!( + r#"{},mismatch,"{}","{}""#, + location_csv_pat(db, &analysis, vfs, &sm, pat_id), + mismatch.expected.display(db), + mismatch.actual.display(db) + ); + } + } + } + if verbosity.is_spammy() { + bar.println(format!( + "In {}: {} pats, {} unknown, {} partial", + full_name, + num_pats - previous_pats, + num_pats_unknown - previous_unknown, + num_pats_partially_unknown - previous_partially_unknown + )); + } + // endregion:patterns bar.inc(1); } @@ -366,10 +475,19 @@ impl flags::AnalysisStats { percentage(num_exprs_unknown, num_exprs), num_exprs_partially_unknown, percentage(num_exprs_partially_unknown, num_exprs), - num_type_mismatches + num_expr_type_mismatches ); - report_metric("unknown type", num_exprs_unknown, "#"); - report_metric("type mismatches", num_type_mismatches, "#"); + eprintln!( + " pats: {}, ??ty: {} ({}%), ?ty: {} ({}%), !ty: {}", + num_pats, + num_pats_unknown, + percentage(num_pats_unknown, num_pats), + num_pats_partially_unknown, + percentage(num_pats_partially_unknown, num_pats), + num_pat_type_mismatches + ); + report_metric("unknown type", num_exprs_unknown + num_pats_unknown, "#"); + report_metric("type mismatches", num_expr_type_mismatches + num_pat_type_mismatches, "#"); eprintln!("{:<20} {}", "Inference:", inference_sw.elapsed()); } @@ -379,7 +497,7 @@ impl flags::AnalysisStats { } } -fn location_csv( +fn location_csv_expr( db: &RootDatabase, analysis: &Analysis, vfs: &Vfs, @@ -401,6 +519,30 @@ fn location_csv( format!("{path},{}:{},{}:{}", start.line + 1, start.col, end.line + 1, end.col) } +fn location_csv_pat( + db: &RootDatabase, + analysis: &Analysis, + vfs: &Vfs, + sm: &BodySourceMap, + pat_id: PatId, +) -> String { + let src = match sm.pat_syntax(pat_id) { + Ok(s) => s, + Err(SyntheticSyntax) => return "synthetic,,".to_string(), + }; + let root = db.parse_or_expand(src.file_id).unwrap(); + let node = src.map(|e| { + e.either(|it| it.to_node(&root).syntax().clone(), |it| it.to_node(&root).syntax().clone()) + }); + let original_range = node.as_ref().original_file_range(db); + let path = vfs.file_path(original_range.file_id); + let line_index = analysis.file_line_index(original_range.file_id).unwrap(); + let text_range = original_range.range; + let (start, end) = + (line_index.line_col(text_range.start()), line_index.line_col(text_range.end())); + format!("{path},{}:{},{}:{}", start.line + 1, start.col, end.line + 1, end.col) +} + fn expr_syntax_range( db: &RootDatabase, analysis: &Analysis, @@ -423,6 +565,33 @@ fn expr_syntax_range( None } } +fn pat_syntax_range( + db: &RootDatabase, + analysis: &Analysis, + vfs: &Vfs, + sm: &BodySourceMap, + pat_id: PatId, +) -> Option<(VfsPath, LineCol, LineCol)> { + let src = sm.pat_syntax(pat_id); + if let Ok(src) = src { + let root = db.parse_or_expand(src.file_id).unwrap(); + let node = src.map(|e| { + e.either( + |it| it.to_node(&root).syntax().clone(), + |it| it.to_node(&root).syntax().clone(), + ) + }); + let original_range = node.as_ref().original_file_range(db); + let path = vfs.file_path(original_range.file_id); + let line_index = analysis.file_line_index(original_range.file_id).unwrap(); + let text_range = original_range.range; + let (start, end) = + (line_index.line_col(text_range.start()), line_index.line_col(text_range.end())); + Some((path, start, end)) + } else { + None + } +} fn shuffle(rng: &mut Rand32, slice: &mut [T]) { for i in 0..slice.len() { From 03b9940b29917204d46a06393f3b4196e5efd772 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Fri, 3 Mar 2023 13:40:07 -0800 Subject: [PATCH 102/362] Downgrade let_underscore_untyped to restriction --- clippy_lints/src/let_underscore.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/let_underscore.rs b/clippy_lints/src/let_underscore.rs index 7600777fab97..51b5de27de89 100644 --- a/clippy_lints/src/let_underscore.rs +++ b/clippy_lints/src/let_underscore.rs @@ -124,7 +124,7 @@ declare_clippy_lint! { /// ``` #[clippy::version = "1.69.0"] pub LET_UNDERSCORE_UNTYPED, - pedantic, + restriction, "non-binding `let` without a type annotation" } From 29150c23153ecb611d7c578a5d63fef87a97de42 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 3 Mar 2023 21:34:00 +0100 Subject: [PATCH 103/362] Disable pattern type mismatches again --- crates/hir/src/lib.rs | 3 +++ crates/ide-diagnostics/src/handlers/missing_match_arms.rs | 8 -------- crates/ide-diagnostics/src/handlers/type_mismatch.rs | 2 -- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 269c45943e51..20009698f918 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -1466,6 +1466,9 @@ impl DefWithBody { for (pat_or_expr, mismatch) in infer.type_mismatches() { let expr_or_pat = match pat_or_expr { ExprOrPatId::ExprId(expr) => source_map.expr_syntax(expr).map(Either::Left), + // FIXME: Re-enable these once we have less false positives + ExprOrPatId::PatId(_pat) => continue, + #[allow(unreachable_patterns)] ExprOrPatId::PatId(pat) => source_map.pat_syntax(pat).map(Either::Right), }; let expr_or_pat = match expr_or_pat { diff --git a/crates/ide-diagnostics/src/handlers/missing_match_arms.rs b/crates/ide-diagnostics/src/handlers/missing_match_arms.rs index 6594eed26d17..5ded2f22309c 100644 --- a/crates/ide-diagnostics/src/handlers/missing_match_arms.rs +++ b/crates/ide-diagnostics/src/handlers/missing_match_arms.rs @@ -273,20 +273,15 @@ enum Either2 { C, D } fn main() { match Either::A { Either2::C => (), - // ^^^^^^^^^^ error: expected Either, found Either2 Either2::D => (), - // ^^^^^^^^^^ error: expected Either, found Either2 } match (true, false) { (true, false, true) => (), - // ^^^^^^^^^^^^^^^^^^^ error: expected (bool, bool), found (bool, bool, bool) (true) => (), // ^^^^ error: expected (bool, bool), found bool } match (true, false) { (true,) => {} } - // ^^^^^^^ error: expected (bool, bool), found (bool,) match (0) { () => () } - // ^^ error: expected i32, found () match Unresolved::Bar { Unresolved::Baz => () } } "#, @@ -300,9 +295,7 @@ fn main() { r#" fn main() { match false { true | () => {} } - // ^^ error: expected bool, found () match (false,) { (true | (),) => {} } - // ^^ error: expected bool, found () } "#, ); @@ -1050,7 +1043,6 @@ fn main() { fn main() { match (&false,) { (true,) => {} - // ^^^^^^^ error: expected (&bool,), found (bool,) } match (&false,) { (&true,) => {} diff --git a/crates/ide-diagnostics/src/handlers/type_mismatch.rs b/crates/ide-diagnostics/src/handlers/type_mismatch.rs index 2026a2d4b8a0..b57a13e53e6b 100644 --- a/crates/ide-diagnostics/src/handlers/type_mismatch.rs +++ b/crates/ide-diagnostics/src/handlers/type_mismatch.rs @@ -617,10 +617,8 @@ fn f() -> i32 { r#" fn f() { let &() = &mut (); - //^^^ error: expected &mut (), found &() match &() { &9 => () - //^^ error: expected &(), found &i32 //^ error: expected (), found i32 } } From 1ccb1de5992dce38f7b8a941ee390a4f9aee1159 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20K=C3=A5re=20Alsaker?= Date: Fri, 3 Mar 2023 04:54:42 +0100 Subject: [PATCH 104/362] Place size limits on query keys and values --- compiler/rustc_middle/src/ty/query.rs | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/compiler/rustc_middle/src/ty/query.rs b/compiler/rustc_middle/src/ty/query.rs index 2bc51baf8790..9d41e4af959c 100644 --- a/compiler/rustc_middle/src/ty/query.rs +++ b/compiler/rustc_middle/src/ty/query.rs @@ -250,6 +250,36 @@ macro_rules! define_callbacks { )* } + $( + // Ensure that keys grow no larger than 64 bytes + #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] + const _: () = { + if mem::size_of::>() > 64 { + panic!("{}", concat!( + "the query `", + stringify!($name), + "` has a key type `", + stringify!($($K)*), + "` that is too large" + )); + } + }; + + // Ensure that values grow no larger than 64 bytes + #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] + const _: () = { + if mem::size_of::>() > 64 { + panic!("{}", concat!( + "the query `", + stringify!($name), + "` has a value type `", + stringify!($V), + "` that is too large" + )); + } + }; + )* + pub struct QueryArenas<'tcx> { $($(#[$attr])* pub $name: query_if_arena!([$($modifiers)*] (WorkerLocal::Target>>) From 2e465d18f23f71f71f9c826087f78546e5aea529 Mon Sep 17 00:00:00 2001 From: Pascal Kuthe Date: Sat, 4 Mar 2023 02:05:09 +0100 Subject: [PATCH 105/362] generate correct completion edits for missing macro arguments rust-analyzer used the token at the cursor after macro expansion to decide whether to replace the token at the cursor before macro expansion. In most cases these two are the same but in some cases these can mismatch which can lead to incorrect replacements. For example if an ident/expr macro argument is missing rust-analyzer generates a "missing" identifier as a placeholder, there is only a brace at the cursor. Therefore, rust-analyzer will incorrectly replace the macro brace with the completion in that case leading to #14246. Using the expanded token type was intentional. However, this doesn't seem to ever be desirable (this is supported by the fact that there were no tests that relied on this behavior) since the type of edit to perform should always be determined by the token it's actually applied to. --- crates/ide-completion/src/context.rs | 3 +- crates/ide-completion/src/render/macro_.rs | 59 ++++++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index 4dc4d212e60b..7dc29c3d5aca 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -387,8 +387,7 @@ pub(crate) struct CompletionContext<'a> { impl<'a> CompletionContext<'a> { /// The range of the identifier that is being completed. pub(crate) fn source_range(&self) -> TextRange { - // check kind of macro-expanded token, but use range of original token - let kind = self.token.kind(); + let kind = self.original_token.kind(); match kind { CHAR => { // assume we are completing a lifetime but the user has only typed the ' diff --git a/crates/ide-completion/src/render/macro_.rs b/crates/ide-completion/src/render/macro_.rs index ffcad1185aa4..44e886076332 100644 --- a/crates/ide-completion/src/render/macro_.rs +++ b/crates/ide-completion/src/render/macro_.rs @@ -264,6 +264,65 @@ macro_rules! foo { fn main() { foo!($0) } +"#, + ); + } + + #[test] + fn complete_missing_macro_arg() { + // Regression test for https://github.com/rust-lang/rust-analyzer/issues/14246 + check_edit( + "BAR", + r#" +macro_rules! foo { + ($val:ident, $val2: ident) => { + $val $val2 + }; +} + +const BAR: u32 = 9; +fn main() { + foo!(BAR, $0) +} +"#, + r#" +macro_rules! foo { + ($val:ident, $val2: ident) => { + $val $val2 + }; +} + +const BAR: u32 = 9; +fn main() { + foo!(BAR, BAR) +} +"#, + ); + check_edit( + "BAR", + r#" +macro_rules! foo { + ($val:ident, $val2: ident) => { + $val $val2 + }; +} + +const BAR: u32 = 9; +fn main() { + foo!($0) +} +"#, + r#" +macro_rules! foo { + ($val:ident, $val2: ident) => { + $val $val2 + }; +} + +const BAR: u32 = 9; +fn main() { + foo!(BAR) +} "#, ); } From cc6180c2f4bf53a235a139be0ba604dacda618a9 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Fri, 3 Mar 2023 18:50:53 -0800 Subject: [PATCH 106/362] Include former name of renamed lints in lints.json --- .../src/utils/internal_lints/metadata_collector.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index b1b5164ffb3e..3d0d4a52511a 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -26,7 +26,7 @@ use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::symbol::Ident; use rustc_span::{sym, Loc, Span, Symbol}; use serde::{ser::SerializeStruct, Serialize, Serializer}; -use std::collections::BinaryHeap; +use std::collections::{BTreeSet, BinaryHeap}; use std::fmt; use std::fmt::Write as _; use std::fs::{self, OpenOptions}; @@ -264,6 +264,9 @@ struct LintMetadata { /// This field is only used in the output and will only be /// mapped shortly before the actual output. applicability: Option, + /// All the past names of lints which have been renamed. + #[serde(skip_serializing_if = "BTreeSet::is_empty")] + former_ids: BTreeSet, } impl LintMetadata { @@ -283,6 +286,7 @@ impl LintMetadata { version, docs, applicability: None, + former_ids: BTreeSet::new(), } } } @@ -901,6 +905,7 @@ fn collect_renames(lints: &mut Vec) { if name == lint_name; if let Some(past_name) = k.strip_prefix(CLIPPY_LINT_GROUP_PREFIX); then { + lint.former_ids.insert(past_name.to_owned()); writeln!(collected, "* `{past_name}`").unwrap(); names.push(past_name.to_string()); } From 800ab650ac7a0833c4a7ca667a84ea8316b6cf3b Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 4 Mar 2023 09:21:17 +0100 Subject: [PATCH 107/362] split expression and pattern metrics --- crates/rust-analyzer/src/cli/analysis_stats.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs index d8f632791b9d..e8c10927d62c 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -486,8 +486,10 @@ impl flags::AnalysisStats { percentage(num_pats_partially_unknown, num_pats), num_pat_type_mismatches ); - report_metric("unknown type", num_exprs_unknown + num_pats_unknown, "#"); - report_metric("type mismatches", num_expr_type_mismatches + num_pat_type_mismatches, "#"); + report_metric("unknown type", num_exprs_unknown, "#"); + report_metric("type mismatches", num_expr_type_mismatches, "#"); + report_metric("pattern unknown type", num_pats_unknown, "#"); + report_metric("pattern type mismatches", num_pat_type_mismatches, "#"); eprintln!("{:<20} {}", "Inference:", inference_sw.elapsed()); } From 24ba1bed040f7d8f483b250dbd4e49383823f644 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 4 Mar 2023 12:48:57 +0100 Subject: [PATCH 108/362] Set expectation for no-semi expression statements to unit --- crates/hir-def/src/resolver.rs | 4 +- crates/hir-ty/src/infer/expr.rs | 75 ++++++++++++++++---------- crates/hir-ty/src/infer/path.rs | 25 ++++----- crates/hir-ty/src/tests/diagnostics.rs | 21 ++++++++ crates/hir-ty/src/tests/regression.rs | 4 +- 5 files changed, 82 insertions(+), 47 deletions(-) diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs index 0b9c136c7eb5..664db292a7f7 100644 --- a/crates/hir-def/src/resolver.rs +++ b/crates/hir-def/src/resolver.rs @@ -294,8 +294,8 @@ impl Resolver { } } - if let res @ Some(_) = self.module_scope.resolve_path_in_value_ns(db, path) { - return res; + if let Some(res) = self.module_scope.resolve_path_in_value_ns(db, path) { + return Some(res); } // If a path of the shape `u16::from_le_bytes` failed to resolve at all, then we fall back diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 02024e1ea780..81e97a9b0bff 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -130,7 +130,7 @@ impl<'a> InferenceContext<'a> { ); let ty = match label { Some(_) => { - let break_ty = self.table.new_type_var(); + let break_ty = expected.coercion_target_type(&mut self.table); let (breaks, ty) = self.with_breakable_ctx( BreakableKind::Block, Some(break_ty.clone()), @@ -403,37 +403,47 @@ impl<'a> InferenceContext<'a> { Expr::Match { expr, arms } => { let input_ty = self.infer_expr(*expr, &Expectation::none()); - let expected = expected.adjust_for_branches(&mut self.table); - - let result_ty = if arms.is_empty() { + if arms.is_empty() { + self.diverges = Diverges::Always; self.result.standard_types.never.clone() } else { - expected.coercion_target_type(&mut self.table) - }; - let mut coerce = CoerceMany::new(result_ty); - - let matchee_diverges = self.diverges; - let mut all_arms_diverge = Diverges::Always; - - for arm in arms.iter() { - self.diverges = Diverges::Maybe; - let input_ty = self.resolve_ty_shallow(&input_ty); - self.infer_top_pat(arm.pat, &input_ty); - if let Some(guard_expr) = arm.guard { - self.infer_expr( - guard_expr, - &Expectation::HasType(self.result.standard_types.bool_.clone()), - ); + let matchee_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); + let mut all_arms_diverge = Diverges::Always; + for arm in arms.iter() { + let input_ty = self.resolve_ty_shallow(&input_ty); + self.infer_top_pat(arm.pat, &input_ty); } - let arm_ty = self.infer_expr_inner(arm.expr, &expected); - all_arms_diverge &= self.diverges; - coerce.coerce(self, Some(arm.expr), &arm_ty); + let expected = expected.adjust_for_branches(&mut self.table); + let result_ty = match &expected { + // We don't coerce to `()` so that if the match expression is a + // statement it's branches can have any consistent type. + Expectation::HasType(ty) if *ty != self.result.standard_types.unit => { + ty.clone() + } + _ => self.table.new_type_var(), + }; + let mut coerce = CoerceMany::new(result_ty); + + for arm in arms.iter() { + if let Some(guard_expr) = arm.guard { + self.diverges = Diverges::Maybe; + self.infer_expr( + guard_expr, + &Expectation::HasType(self.result.standard_types.bool_.clone()), + ); + } + self.diverges = Diverges::Maybe; + + let arm_ty = self.infer_expr_inner(arm.expr, &expected); + all_arms_diverge &= self.diverges; + coerce.coerce(self, Some(arm.expr), &arm_ty); + } + + self.diverges = matchee_diverges | all_arms_diverge; + + coerce.complete(self) } - - self.diverges = matchee_diverges | all_arms_diverge; - - coerce.complete(self) } Expr::Path(p) => { // FIXME this could be more efficient... @@ -1179,8 +1189,15 @@ impl<'a> InferenceContext<'a> { self.diverges = previous_diverges; } } - Statement::Expr { expr, .. } => { - self.infer_expr(*expr, &Expectation::none()); + &Statement::Expr { expr, has_semi } => { + self.infer_expr( + expr, + &if has_semi { + Expectation::none() + } else { + Expectation::HasType(self.result.standard_types.unit.clone()) + }, + ); } } } diff --git a/crates/hir-ty/src/infer/path.rs b/crates/hir-ty/src/infer/path.rs index 0a8527afbd04..b3867623f37e 100644 --- a/crates/hir-ty/src/infer/path.rs +++ b/crates/hir-ty/src/infer/path.rs @@ -40,20 +40,14 @@ impl<'a> InferenceContext<'a> { id: ExprOrPatId, ) -> Option { let (value, self_subst) = if let Some(type_ref) = path.type_anchor() { - if path.segments().is_empty() { - // This can't actually happen syntax-wise - return None; - } + let Some(last) = path.segments().last() else { return None }; let ty = self.make_ty(type_ref); let remaining_segments_for_ty = path.segments().take(path.segments().len() - 1); let ctx = crate::lower::TyLoweringContext::new(self.db, resolver); let (ty, _) = ctx.lower_ty_relative_path(ty, None, remaining_segments_for_ty); - self.resolve_ty_assoc_item( - ty, - path.segments().last().expect("path had at least one segment").name, - id, - )? + self.resolve_ty_assoc_item(ty, last.name, id)? } else { + // FIXME: report error, unresolved first path segment let value_or_partial = resolver.resolve_path_in_value_ns(self.db.upcast(), path.mod_path())?; @@ -66,10 +60,13 @@ impl<'a> InferenceContext<'a> { }; let typable: ValueTyDefId = match value { - ValueNs::LocalBinding(pat) => { - let ty = self.result.type_of_pat.get(pat)?.clone(); - return Some(ty); - } + ValueNs::LocalBinding(pat) => match self.result.type_of_pat.get(pat) { + Some(ty) => return Some(ty.clone()), + None => { + never!("uninferred pattern?"); + return None; + } + }, ValueNs::FunctionId(it) => it.into(), ValueNs::ConstId(it) => it.into(), ValueNs::StaticId(it) => it.into(), @@ -91,7 +88,7 @@ impl<'a> InferenceContext<'a> { let ty = self.db.value_ty(struct_id.into()).substitute(Interner, &substs); return Some(ty); } else { - // FIXME: diagnostic, invalid Self reference + // FIXME: report error, invalid Self reference return None; } } diff --git a/crates/hir-ty/src/tests/diagnostics.rs b/crates/hir-ty/src/tests/diagnostics.rs index f00fa9729487..1876be303ad4 100644 --- a/crates/hir-ty/src/tests/diagnostics.rs +++ b/crates/hir-ty/src/tests/diagnostics.rs @@ -73,3 +73,24 @@ fn test(x: bool) -> &'static str { "#, ); } + +#[test] +fn non_unit_block_expr_stmt_no_semi() { + check( + r#" +fn test(x: bool) { + if x { + "notok" + //^^^^^^^ expected (), got &str + } else { + "ok" + //^^^^ expected (), got &str + } + match x { true => true, false => 0 } + //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected (), got bool + //^ expected bool, got i32 + () +} +"#, + ); +} diff --git a/crates/hir-ty/src/tests/regression.rs b/crates/hir-ty/src/tests/regression.rs index de6ae7fff8fb..5fc2f46d5601 100644 --- a/crates/hir-ty/src/tests/regression.rs +++ b/crates/hir-ty/src/tests/regression.rs @@ -1015,9 +1015,9 @@ fn cfg_tail() { 20..31 '{ "first" }': () 22..29 '"first"': &str 72..190 '{ ...] 13 }': () - 78..88 '{ "fake" }': &str + 78..88 '{ "fake" }': () 80..86 '"fake"': &str - 93..103 '{ "fake" }': &str + 93..103 '{ "fake" }': () 95..101 '"fake"': &str 108..120 '{ "second" }': () 110..118 '"second"': &str From 1b5bc831188bc55561a9a84fb693de29b41b18bc Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 4 Mar 2023 14:45:57 +0100 Subject: [PATCH 109/362] Remove weird nesting of effect blocks in hir --- crates/hir-def/src/body/lower.rs | 67 +++--- crates/hir-def/src/body/pretty.rs | 72 +++--- crates/hir-def/src/expr.rs | 47 ++-- crates/hir-ty/src/diagnostics/unsafe_check.rs | 6 +- crates/hir-ty/src/infer/expr.rs | 215 +++++++++--------- crates/hir-ty/src/mir/lower.rs | 110 +++++---- crates/hir-ty/src/tests.rs | 5 + crates/hir-ty/src/tests/method_resolution.rs | 1 - crates/hir-ty/src/tests/simple.rs | 9 - 9 files changed, 285 insertions(+), 247 deletions(-) diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs index 04b1c4f01e22..3164a5f4c290 100644 --- a/crates/hir-def/src/body/lower.rs +++ b/crates/hir-def/src/body/lower.rs @@ -37,7 +37,7 @@ use crate::{ item_scope::BuiltinShadowMode, path::{GenericArgs, Path}, type_ref::{Mutability, Rawness, TypeRef}, - AdtId, BlockLoc, ModuleDefId, UnresolvedMacro, + AdtId, BlockId, BlockLoc, ModuleDefId, UnresolvedMacro, }; pub struct LowerCtx<'a> { @@ -238,33 +238,32 @@ impl ExprCollector<'_> { } ast::Expr::BlockExpr(e) => match e.modifier() { Some(ast::BlockModifier::Try(_)) => { - let body = self.collect_block(e); - self.alloc_expr(Expr::TryBlock { body }, syntax_ptr) + self.collect_block_(e, |id, statements, tail| Expr::TryBlock { + id, + statements, + tail, + }) } Some(ast::BlockModifier::Unsafe(_)) => { - let body = self.collect_block(e); - self.alloc_expr(Expr::Unsafe { body }, syntax_ptr) + self.collect_block_(e, |id, statements, tail| Expr::Unsafe { + id, + statements, + tail, + }) } - // FIXME: we need to record these effects somewhere... Some(ast::BlockModifier::Label(label)) => { let label = self.collect_label(label); - let res = self.collect_block(e); - match &mut self.body.exprs[res] { - Expr::Block { label: block_label, .. } => { - *block_label = Some(label); - } - _ => unreachable!(), - } - res - } - Some(ast::BlockModifier::Async(_)) => { - let body = self.collect_block(e); - self.alloc_expr(Expr::Async { body }, syntax_ptr) - } - Some(ast::BlockModifier::Const(_)) => { - let body = self.collect_block(e); - self.alloc_expr(Expr::Const { body }, syntax_ptr) + self.collect_block_(e, |id, statements, tail| Expr::Block { + id, + statements, + tail, + label: Some(label), + }) } + Some(ast::BlockModifier::Async(_)) => self + .collect_block_(e, |id, statements, tail| Expr::Async { id, statements, tail }), + Some(ast::BlockModifier::Const(_)) => self + .collect_block_(e, |id, statements, tail| Expr::Const { id, statements, tail }), None => self.collect_block(e), }, ast::Expr::LoopExpr(e) => { @@ -737,6 +736,19 @@ impl ExprCollector<'_> { } fn collect_block(&mut self, block: ast::BlockExpr) -> ExprId { + self.collect_block_(block, |id, statements, tail| Expr::Block { + id, + statements, + tail, + label: None, + }) + } + + fn collect_block_( + &mut self, + block: ast::BlockExpr, + mk_block: impl FnOnce(BlockId, Box<[Statement]>, Option) -> Expr, + ) -> ExprId { let file_local_id = self.ast_id_map.ast_id(&block); let ast_id = AstId::new(self.expander.current_file_id, file_local_id); let block_loc = @@ -769,15 +781,8 @@ impl ExprCollector<'_> { }); let syntax_node_ptr = AstPtr::new(&block.into()); - let expr_id = self.alloc_expr( - Expr::Block { - id: block_id, - statements: statements.into_boxed_slice(), - tail, - label: None, - }, - syntax_node_ptr, - ); + let expr_id = self + .alloc_expr(mk_block(block_id, statements.into_boxed_slice(), tail), syntax_node_ptr); self.expander.def_map = prev_def_map; self.expander.module = prev_local_module; diff --git a/crates/hir-def/src/body/pretty.rs b/crates/hir-def/src/body/pretty.rs index 4b4664a1cf4a..622756ee8a97 100644 --- a/crates/hir-def/src/body/pretty.rs +++ b/crates/hir-def/src/body/pretty.rs @@ -292,18 +292,6 @@ impl<'a> Printer<'a> { self.print_expr(*expr); w!(self, "?"); } - Expr::TryBlock { body } => { - w!(self, "try "); - self.print_expr(*body); - } - Expr::Async { body } => { - w!(self, "async "); - self.print_expr(*body); - } - Expr::Const { body } => { - w!(self, "const "); - self.print_expr(*body); - } Expr::Cast { expr, type_ref } => { self.print_expr(*expr); w!(self, " as "); @@ -402,10 +390,6 @@ impl<'a> Printer<'a> { } w!(self, ")"); } - Expr::Unsafe { body } => { - w!(self, "unsafe "); - self.print_expr(*body); - } Expr::Array(arr) => { w!(self, "["); if !matches!(arr, Array::ElementList { elements, .. } if elements.is_empty()) { @@ -428,27 +412,49 @@ impl<'a> Printer<'a> { } Expr::Literal(lit) => self.print_literal(lit), Expr::Block { id: _, statements, tail, label } => { - self.whitespace(); - if let Some(lbl) = label { - w!(self, "{}: ", self.body[*lbl].name); - } - w!(self, "{{"); - if !statements.is_empty() || tail.is_some() { - self.indented(|p| { - for stmt in &**statements { - p.print_stmt(stmt); - } - if let Some(tail) = tail { - p.print_expr(*tail); - } - p.newline(); - }); - } - w!(self, "}}"); + let label = label.map(|lbl| format!("{}: ", self.body[lbl].name)); + self.print_block(label.as_deref(), statements, tail); + } + Expr::Unsafe { id: _, statements, tail } => { + self.print_block(Some("unsafe "), statements, tail); + } + Expr::TryBlock { id: _, statements, tail } => { + self.print_block(Some("try "), statements, tail); + } + Expr::Async { id: _, statements, tail } => { + self.print_block(Some("async "), statements, tail); + } + Expr::Const { id: _, statements, tail } => { + self.print_block(Some("const "), statements, tail); } } } + fn print_block( + &mut self, + label: Option<&str>, + statements: &Box<[Statement]>, + tail: &Option>, + ) { + self.whitespace(); + if let Some(lbl) = label { + w!(self, "{}", lbl); + } + w!(self, "{{"); + if !statements.is_empty() || tail.is_some() { + self.indented(|p| { + for stmt in &**statements { + p.print_stmt(stmt); + } + if let Some(tail) = tail { + p.print_expr(*tail); + } + p.newline(); + }); + } + w!(self, "}}"); + } + fn print_pat(&mut self, pat: PatId) { let pat = &self.body[pat]; diff --git a/crates/hir-def/src/expr.rs b/crates/hir-def/src/expr.rs index 8d6f0be2648e..78a2f861233d 100644 --- a/crates/hir-def/src/expr.rs +++ b/crates/hir-def/src/expr.rs @@ -109,6 +109,26 @@ pub enum Expr { tail: Option, label: Option, }, + TryBlock { + id: BlockId, + statements: Box<[Statement]>, + tail: Option, + }, + Async { + id: BlockId, + statements: Box<[Statement]>, + tail: Option, + }, + Const { + id: BlockId, + statements: Box<[Statement]>, + tail: Option, + }, + Unsafe { + id: BlockId, + statements: Box<[Statement]>, + tail: Option, + }, Loop { body: ExprId, label: Option, @@ -172,15 +192,6 @@ pub enum Expr { Try { expr: ExprId, }, - TryBlock { - body: ExprId, - }, - Async { - body: ExprId, - }, - Const { - body: ExprId, - }, Cast { expr: ExprId, type_ref: Interned, @@ -222,9 +233,6 @@ pub enum Expr { exprs: Box<[ExprId]>, is_assignee_expr: bool, }, - Unsafe { - body: ExprId, - }, Array(Array), Literal(Literal), Underscore, @@ -290,13 +298,20 @@ impl Expr { Expr::Let { expr, .. } => { f(*expr); } - Expr::Block { statements, tail, .. } => { + Expr::Block { statements, tail, .. } + | Expr::TryBlock { statements, tail, .. } + | Expr::Unsafe { statements, tail, .. } + | Expr::Async { statements, tail, .. } + | Expr::Const { statements, tail, .. } => { for stmt in statements.iter() { match stmt { - Statement::Let { initializer, .. } => { + Statement::Let { initializer, else_branch, .. } => { if let &Some(expr) = initializer { f(expr); } + if let &Some(expr) = else_branch { + f(expr); + } } Statement::Expr { expr: expression, .. } => f(*expression), } @@ -305,10 +320,6 @@ impl Expr { f(expr); } } - Expr::TryBlock { body } - | Expr::Unsafe { body } - | Expr::Async { body } - | Expr::Const { body } => f(*body), Expr::Loop { body, .. } => f(*body), Expr::While { condition, body, .. } => { f(*condition); diff --git a/crates/hir-ty/src/diagnostics/unsafe_check.rs b/crates/hir-ty/src/diagnostics/unsafe_check.rs index 431ab949b462..9a32a9e92cef 100644 --- a/crates/hir-ty/src/diagnostics/unsafe_check.rs +++ b/crates/hir-ty/src/diagnostics/unsafe_check.rs @@ -94,8 +94,10 @@ fn walk_unsafe( unsafe_expr_cb(UnsafeExpr { expr: current, inside_unsafe_block }); } } - Expr::Unsafe { body: child } => { - return walk_unsafe(db, infer, def, body, *child, true, unsafe_expr_cb); + Expr::Unsafe { .. } => { + expr.walk_child_exprs(|child| { + walk_unsafe(db, infer, def, body, child, true, unsafe_expr_cb); + }); } _ => {} } diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 81e97a9b0bff..023e19d25ed2 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -124,41 +124,18 @@ impl<'a> InferenceContext<'a> { self.result.standard_types.bool_.clone() } Expr::Block { statements, tail, label, id: _ } => { - let old_resolver = mem::replace( - &mut self.resolver, - resolver_for_expr(self.db.upcast(), self.owner, tgt_expr), - ); - let ty = match label { - Some(_) => { - let break_ty = expected.coercion_target_type(&mut self.table); - let (breaks, ty) = self.with_breakable_ctx( - BreakableKind::Block, - Some(break_ty.clone()), - *label, - |this| { - this.infer_block( - tgt_expr, - statements, - *tail, - &Expectation::has_type(break_ty), - ) - }, - ); - breaks.unwrap_or(ty) - } - None => self.infer_block(tgt_expr, statements, *tail, expected), - }; - self.resolver = old_resolver; - ty + self.infer_block(tgt_expr, statements, *tail, *label, expected) } - Expr::Unsafe { body } => self.infer_expr(*body, expected), - Expr::Const { body } => { + Expr::Unsafe { id: _, statements, tail } => { + self.infer_block(tgt_expr, statements, *tail, None, expected) + } + Expr::Const { id: _, statements, tail } => { self.with_breakable_ctx(BreakableKind::Border, None, None, |this| { - this.infer_expr(*body, expected) + this.infer_block(tgt_expr, statements, *tail, None, expected) }) .1 } - Expr::TryBlock { body } => { + Expr::TryBlock { id: _, statements, tail } => { // The type that is returned from the try block let try_ty = self.table.new_type_var(); if let Some(ty) = expected.only_has_type(&mut self.table) { @@ -169,13 +146,16 @@ impl<'a> InferenceContext<'a> { let ok_ty = self.resolve_associated_type(try_ty.clone(), self.resolve_ops_try_output()); - self.with_breakable_ctx(BreakableKind::Block, Some(ok_ty.clone()), None, |this| { - this.infer_expr(*body, &Expectation::has_type(ok_ty)); - }); - + self.infer_block( + tgt_expr, + statements, + *tail, + None, + &Expectation::has_type(ok_ty.clone()), + ); try_ty } - Expr::Async { body } => { + Expr::Async { id: _, statements, tail } => { let ret_ty = self.table.new_type_var(); let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone()); @@ -184,7 +164,13 @@ impl<'a> InferenceContext<'a> { let (_, inner_ty) = self.with_breakable_ctx(BreakableKind::Border, None, None, |this| { - this.infer_expr_coerce(*body, &Expectation::has_type(ret_ty)) + this.infer_block( + tgt_expr, + statements, + *tail, + None, + &Expectation::has_type(ret_ty), + ) }); self.diverges = prev_diverges; @@ -193,7 +179,8 @@ impl<'a> InferenceContext<'a> { // Use the first type parameter as the output type of future. // existential type AsyncBlockImplTrait: Future - let impl_trait_id = crate::ImplTraitId::AsyncBlockTypeImplTrait(self.owner, *body); + let impl_trait_id = + crate::ImplTraitId::AsyncBlockTypeImplTrait(self.owner, tgt_expr); let opaque_ty_id = self.db.intern_impl_trait_id(impl_trait_id).into(); TyKind::OpaqueType(opaque_ty_id, Substitution::from1(Interner, inner_ty)) .intern(Interner) @@ -1153,80 +1140,102 @@ impl<'a> InferenceContext<'a> { expr: ExprId, statements: &[Statement], tail: Option, + label: Option, expected: &Expectation, ) -> Ty { - for stmt in statements { - match stmt { - Statement::Let { pat, type_ref, initializer, else_branch } => { - let decl_ty = type_ref - .as_ref() - .map(|tr| self.make_ty(tr)) - .unwrap_or_else(|| self.table.new_type_var()); + let coerce_ty = expected.coercion_target_type(&mut self.table); + let old_resolver = + mem::replace(&mut self.resolver, resolver_for_expr(self.db.upcast(), self.owner, expr)); - let ty = if let Some(expr) = initializer { - let ty = if contains_explicit_ref_binding(&self.body, *pat) { - self.infer_expr(*expr, &Expectation::has_type(decl_ty.clone())) - } else { - self.infer_expr_coerce(*expr, &Expectation::has_type(decl_ty.clone())) - }; - if type_ref.is_some() { - decl_ty - } else { - ty + let (break_ty, ty) = + self.with_breakable_ctx(BreakableKind::Block, Some(coerce_ty.clone()), label, |this| { + for stmt in statements { + match stmt { + Statement::Let { pat, type_ref, initializer, else_branch } => { + let decl_ty = type_ref + .as_ref() + .map(|tr| this.make_ty(tr)) + .unwrap_or_else(|| this.table.new_type_var()); + + let ty = if let Some(expr) = initializer { + let ty = if contains_explicit_ref_binding(&this.body, *pat) { + this.infer_expr(*expr, &Expectation::has_type(decl_ty.clone())) + } else { + this.infer_expr_coerce( + *expr, + &Expectation::has_type(decl_ty.clone()), + ) + }; + if type_ref.is_some() { + decl_ty + } else { + ty + } + } else { + decl_ty + }; + + this.infer_top_pat(*pat, &ty); + + if let Some(expr) = else_branch { + let previous_diverges = + mem::replace(&mut this.diverges, Diverges::Maybe); + this.infer_expr_coerce( + *expr, + &Expectation::HasType(this.result.standard_types.never.clone()), + ); + this.diverges = previous_diverges; + } + } + &Statement::Expr { expr, has_semi } => { + this.infer_expr( + expr, + &if has_semi { + Expectation::none() + } else { + Expectation::HasType(this.result.standard_types.unit.clone()) + }, + ); } - } else { - decl_ty - }; - - self.infer_top_pat(*pat, &ty); - - if let Some(expr) = else_branch { - let previous_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); - self.infer_expr_coerce( - *expr, - &Expectation::HasType(self.result.standard_types.never.clone()), - ); - self.diverges = previous_diverges; } } - &Statement::Expr { expr, has_semi } => { - self.infer_expr( - expr, - &if has_semi { - Expectation::none() - } else { - Expectation::HasType(self.result.standard_types.unit.clone()) - }, - ); - } - } - } - if let Some(expr) = tail { - self.infer_expr_coerce(expr, expected) - } else { - // Citing rustc: if there is no explicit tail expression, - // that is typically equivalent to a tail expression - // of `()` -- except if the block diverges. In that - // case, there is no value supplied from the tail - // expression (assuming there are no other breaks, - // this implies that the type of the block will be - // `!`). - if self.diverges.is_always() { - // we don't even make an attempt at coercion - self.table.new_maybe_never_var() - } else if let Some(t) = expected.only_has_type(&mut self.table) { - if self.coerce(Some(expr), &TyBuilder::unit(), &t).is_err() { - self.result.type_mismatches.insert( - expr.into(), - TypeMismatch { expected: t.clone(), actual: TyBuilder::unit() }, - ); + // FIXME: This should make use of the breakable CoerceMany + if let Some(expr) = tail { + this.infer_expr_coerce(expr, expected) + } else { + // Citing rustc: if there is no explicit tail expression, + // that is typically equivalent to a tail expression + // of `()` -- except if the block diverges. In that + // case, there is no value supplied from the tail + // expression (assuming there are no other breaks, + // this implies that the type of the block will be + // `!`). + if this.diverges.is_always() { + // we don't even make an attempt at coercion + this.table.new_maybe_never_var() + } else if let Some(t) = expected.only_has_type(&mut this.table) { + if this + .coerce(Some(expr), &this.result.standard_types.unit.clone(), &t) + .is_err() + { + this.result.type_mismatches.insert( + expr.into(), + TypeMismatch { + expected: t.clone(), + actual: this.result.standard_types.unit.clone(), + }, + ); + } + t + } else { + this.result.standard_types.unit.clone() + } } - t - } else { - TyBuilder::unit() - } - } + }); + self.resolver = old_resolver; + + break_ty.unwrap_or(ty) } fn lookup_field( diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs index 1fa21e230c4c..936b56a02170 100644 --- a/crates/hir-ty/src/mir/lower.rs +++ b/crates/hir-ty/src/mir/lower.rs @@ -331,56 +331,11 @@ impl MirLowerCtx<'_> { } Ok(result) } + Expr::Unsafe { id: _, statements, tail } => { + self.lower_block_to_place(None, statements, current, *tail, place) + } Expr::Block { id: _, statements, tail, label } => { - if label.is_some() { - not_supported!("block with label"); - } - for statement in statements.iter() { - match statement { - hir_def::expr::Statement::Let { - pat, - initializer, - else_branch, - type_ref: _, - } => match initializer { - Some(expr_id) => { - let else_block; - let init_place; - (init_place, current) = - self.lower_expr_to_some_place(*expr_id, current)?; - (current, else_block) = self.pattern_match( - current, - None, - init_place, - self.expr_ty(*expr_id), - *pat, - BindingAnnotation::Unannotated, - )?; - match (else_block, else_branch) { - (None, _) => (), - (Some(else_block), None) => { - self.set_terminator(else_block, Terminator::Unreachable); - } - (Some(else_block), Some(else_branch)) => { - let (_, b) = self - .lower_expr_to_some_place(*else_branch, else_block)?; - self.set_terminator(b, Terminator::Unreachable); - } - } - } - None => continue, - }, - hir_def::expr::Statement::Expr { expr, has_semi: _ } => { - let ty = self.expr_ty(*expr); - let temp = self.temp(ty)?; - current = self.lower_expr_to_place(*expr, temp.into(), current)?; - } - } - } - match tail { - Some(tail) => self.lower_expr_to_place(*tail, place, current), - None => Ok(current), - } + self.lower_block_to_place(*label, statements, current, *tail, place) } Expr::Loop { body, label } => self.lower_loop(current, *label, |this, begin, _| { let (_, block) = this.lower_expr_to_some_place(*body, begin)?; @@ -686,7 +641,6 @@ impl MirLowerCtx<'_> { self.push_assignment(current, place, r); Ok(current) } - Expr::Unsafe { body } => self.lower_expr_to_place(*body, place, current), Expr::Array(l) => match l { Array::ElementList { elements, .. } => { let elem_ty = match &self.expr_ty(expr_id).data(Interner).kind { @@ -723,6 +677,62 @@ impl MirLowerCtx<'_> { } } + fn lower_block_to_place( + &mut self, + label: Option, + statements: &[hir_def::expr::Statement], + mut current: BasicBlockId, + tail: Option, + place: Place, + ) -> Result { + if label.is_some() { + not_supported!("block with label"); + } + for statement in statements.iter() { + match statement { + hir_def::expr::Statement::Let { pat, initializer, else_branch, type_ref: _ } => { + match initializer { + Some(expr_id) => { + let else_block; + let init_place; + (init_place, current) = + self.lower_expr_to_some_place(*expr_id, current)?; + (current, else_block) = self.pattern_match( + current, + None, + init_place, + self.expr_ty(*expr_id), + *pat, + BindingAnnotation::Unannotated, + )?; + match (else_block, else_branch) { + (None, _) => (), + (Some(else_block), None) => { + self.set_terminator(else_block, Terminator::Unreachable); + } + (Some(else_block), Some(else_branch)) => { + let (_, b) = + self.lower_expr_to_some_place(*else_branch, else_block)?; + self.set_terminator(b, Terminator::Unreachable); + } + } + } + None => continue, + } + } + hir_def::expr::Statement::Expr { expr, has_semi: _ } => { + let ty = self.expr_ty(*expr); + let temp = self.temp(ty)?; + current = self.lower_expr_to_place(*expr, temp.into(), current)?; + } + } + } + match tail { + Some(tail) => self.lower_expr_to_place(tail, place, current), + None => Ok(current), + } + } + fn lower_literal_to_operand(&mut self, ty: Ty, l: &Literal) -> Result { let size = layout_of_ty(self.db, &ty, self.owner.module(self.db.upcast()).krate())? .size diff --git a/crates/hir-ty/src/tests.rs b/crates/hir-ty/src/tests.rs index ab848a18eb38..759878b10bbf 100644 --- a/crates/hir-ty/src/tests.rs +++ b/crates/hir-ty/src/tests.rs @@ -61,22 +61,27 @@ fn setup_tracing() -> Option { Some(tracing::subscriber::set_default(subscriber)) } +#[track_caller] fn check_types(ra_fixture: &str) { check_impl(ra_fixture, false, true, false) } +#[track_caller] fn check_types_source_code(ra_fixture: &str) { check_impl(ra_fixture, false, true, true) } +#[track_caller] fn check_no_mismatches(ra_fixture: &str) { check_impl(ra_fixture, true, false, false) } +#[track_caller] fn check(ra_fixture: &str) { check_impl(ra_fixture, false, false, false) } +#[track_caller] fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_source: bool) { let _tracing = setup_tracing(); let (db, files) = TestDB::with_many_files(ra_fixture); diff --git a/crates/hir-ty/src/tests/method_resolution.rs b/crates/hir-ty/src/tests/method_resolution.rs index 41c53701df6c..4b671449e154 100644 --- a/crates/hir-ty/src/tests/method_resolution.rs +++ b/crates/hir-ty/src/tests/method_resolution.rs @@ -1167,7 +1167,6 @@ fn test() { 123..167 '{ ...o(); }': () 133..134 's': &S 137..151 'unsafe { f() }': &S - 137..151 'unsafe { f() }': &S 146..147 'f': fn f() -> &S 146..149 'f()': &S 157..158 's': &S diff --git a/crates/hir-ty/src/tests/simple.rs b/crates/hir-ty/src/tests/simple.rs index 2e5787b701ca..1a07a2c51d81 100644 --- a/crates/hir-ty/src/tests/simple.rs +++ b/crates/hir-ty/src/tests/simple.rs @@ -352,7 +352,6 @@ unsafe fn baz(u: MyUnion) { 71..89 'MyUnio...o: 0 }': MyUnion 86..87 '0': u32 95..113 'unsafe...(u); }': () - 95..113 'unsafe...(u); }': () 104..107 'baz': fn baz(MyUnion) 104..110 'baz(u)': () 108..109 'u': MyUnion @@ -360,7 +359,6 @@ unsafe fn baz(u: MyUnion) { 126..146 'MyUnio... 0.0 }': MyUnion 141..144 '0.0': f32 152..170 'unsafe...(u); }': () - 152..170 'unsafe...(u); }': () 161..164 'baz': fn baz(MyUnion) 161..167 'baz(u)': () 165..166 'u': MyUnion @@ -2077,22 +2075,17 @@ async fn main() { 16..193 '{ ...2 }; }': () 26..27 'x': i32 30..43 'unsafe { 92 }': i32 - 30..43 'unsafe { 92 }': i32 39..41 '92': i32 53..54 'y': impl Future - 57..85 'async ...wait }': () 57..85 'async ...wait }': impl Future - 65..77 'async { () }': () 65..77 'async { () }': impl Future 65..83 'async ....await': () 73..75 '()': () 95..96 'z': ControlFlow<(), ()> - 130..140 'try { () }': () 130..140 'try { () }': ControlFlow<(), ()> 136..138 '()': () 150..151 'w': i32 154..166 'const { 92 }': i32 - 154..166 'const { 92 }': i32 162..164 '92': i32 176..177 't': i32 180..190 ''a: { 92 }': i32 @@ -2122,7 +2115,6 @@ fn main() { 83..84 'f': F 89..91 '{}': () 103..231 '{ ... }); }': () - 109..161 'async ... }': Result<(), ()> 109..161 'async ... }': impl Future> 125..139 'return Err(())': ! 132..135 'Err': Err<(), ()>(()) -> Result<(), ()> @@ -2134,7 +2126,6 @@ fn main() { 167..171 'test': fn test<(), (), || -> impl Future>, impl Future>>(|| -> impl Future>) 167..228 'test(|... })': () 172..227 '|| asy... }': || -> impl Future> - 175..227 'async ... }': Result<(), ()> 175..227 'async ... }': impl Future> 191..205 'return Err(())': ! 198..201 'Err': Err<(), ()>(()) -> Result<(), ()> From b8276481e7942734b7e3ac136ef065a077c4b131 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 4 Mar 2023 14:58:58 +0100 Subject: [PATCH 110/362] Fix extract_variable test --- crates/ide-assists/src/handlers/extract_variable.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ide-assists/src/handlers/extract_variable.rs b/crates/ide-assists/src/handlers/extract_variable.rs index a738deffb95b..16356141288d 100644 --- a/crates/ide-assists/src/handlers/extract_variable.rs +++ b/crates/ide-assists/src/handlers/extract_variable.rs @@ -287,7 +287,7 @@ fn foo() { extract_variable, r" fn foo() { - $0{ let x = 0; x }$0 + $0{ let x = 0; x }$0; something_else(); }", r" From 95c4cb991f02841ef15898370640aa47491d9456 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 4 Mar 2023 15:22:39 +0100 Subject: [PATCH 111/362] Handle new hir block kinds in scope calculations --- crates/hir-def/src/body/scope.rs | 10 ++++++++++ crates/hir-ty/src/diagnostics/unsafe_check.rs | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/hir-def/src/body/scope.rs b/crates/hir-def/src/body/scope.rs index 2617d4288a3a..e7078b7953b8 100644 --- a/crates/hir-def/src/body/scope.rs +++ b/crates/hir-def/src/body/scope.rs @@ -194,6 +194,16 @@ fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope scopes.set_scope(expr, scope); compute_block_scopes(statements, *tail, body, scopes, &mut scope); } + Expr::Unsafe { id, statements, tail } + | Expr::Async { id, statements, tail } + | Expr::Const { id, statements, tail } + | Expr::TryBlock { id, statements, tail } => { + let mut scope = scopes.new_block_scope(*scope, *id, None); + // Overwrite the old scope for the block expr, so that every block scope can be found + // via the block itself (important for blocks that only contain items, no expressions). + scopes.set_scope(expr, scope); + compute_block_scopes(statements, *tail, body, scopes, &mut scope); + } Expr::For { iterable, pat, body: body_expr, label } => { compute_expr_scopes(*iterable, body, scopes, scope); let mut scope = scopes.new_labeled_scope(*scope, make_label(label)); diff --git a/crates/hir-ty/src/diagnostics/unsafe_check.rs b/crates/hir-ty/src/diagnostics/unsafe_check.rs index 9a32a9e92cef..d25c0ccf00dc 100644 --- a/crates/hir-ty/src/diagnostics/unsafe_check.rs +++ b/crates/hir-ty/src/diagnostics/unsafe_check.rs @@ -95,7 +95,7 @@ fn walk_unsafe( } } Expr::Unsafe { .. } => { - expr.walk_child_exprs(|child| { + return expr.walk_child_exprs(|child| { walk_unsafe(db, infer, def, body, child, true, unsafe_expr_cb); }); } From de2e16cf715572b19b0c980af2f834c1e9e0e958 Mon Sep 17 00:00:00 2001 From: 823984418 <823984418@qq.com> Date: Sat, 4 Mar 2023 22:51:23 +0800 Subject: [PATCH 112/362] Prevent the `start_bx` basic block in codegen from having two `Builder`s at the same time --- compiler/rustc_codegen_ssa/src/mir/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/rustc_codegen_ssa/src/mir/mod.rs b/compiler/rustc_codegen_ssa/src/mir/mod.rs index 2ec9fdbf44f1..8db81b793eab 100644 --- a/compiler/rustc_codegen_ssa/src/mir/mod.rs +++ b/compiler/rustc_codegen_ssa/src/mir/mod.rs @@ -258,6 +258,8 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( // Apply debuginfo to the newly allocated locals. fx.debug_introduce_locals(&mut start_bx); + drop(start_bx); + // Codegen the body of each block using reverse postorder for (bb, _) in traversal::reverse_postorder(&mir) { fx.codegen_block(bb); From 446ae429a6e30b416853d6ae0dea228b751671b0 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Thu, 2 Mar 2023 13:21:35 +0100 Subject: [PATCH 113/362] lintcheck: fix parallel processing handling Using `rayon::current_num_threads()` causes a bug: ``` thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ThreadPoolBuildError { kind: GlobalPoolAlreadyInitialized }', src/main.rs:632:10 ``` Moreover, using the number of threads and dividing it by 2 wouldn't return the number of physical threads on modern processors which have a varying number of threads per core. It makes little sense to restrict ourselves to physical threads, especially when, in modern architectures, cores with multiple threads are often faster (performance) while cores with a unique threads are often slower (efficient). The Rust runtime will make a better choice. --- lintcheck/src/config.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lintcheck/src/config.rs b/lintcheck/src/config.rs index e0244ddcecb8..f87b902b92d9 100644 --- a/lintcheck/src/config.rs +++ b/lintcheck/src/config.rs @@ -89,14 +89,11 @@ impl LintcheckConfig { 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 + // look at the --threads arg, if 0 is passed, use the threads count let max_jobs = match clap_config.get_one::("threads") { Some(&0) => { // automatic choice - // Rayon seems to return thread count so half that for core count - rayon::current_num_threads() / 2 + std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1) }, Some(&threads) => threads, // no -j passed, use a single thread From b85e2af898546f9c7a7b58b02b43ba0ae0c948c9 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 4 Mar 2023 19:39:00 +0100 Subject: [PATCH 114/362] Correctly handle non-semi statement expressions for never coercions --- crates/hir-ty/src/infer/expr.rs | 45 ++++++++++++++++++++------- crates/hir-ty/src/tests/patterns.rs | 2 +- crates/hir-ty/src/tests/regression.rs | 4 +-- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 023e19d25ed2..8895dc095f92 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -84,6 +84,30 @@ impl<'a> InferenceContext<'a> { } } + pub(super) fn infer_expr_coerce_never(&mut self, expr: ExprId, expected: &Expectation) -> Ty { + let ty = self.infer_expr_inner(expr, expected); + // While we don't allow *arbitrary* coercions here, we *do* allow + // coercions from ! to `expected`. + if ty.is_never() { + if let Some(adjustments) = self.result.expr_adjustments.get(&expr) { + return if let [Adjustment { kind: Adjust::NeverToAny, target }] = &**adjustments { + target.clone() + } else { + self.err_ty() + }; + } + + let adj_ty = self.table.new_type_var(); + self.write_expr_adj( + expr, + vec![Adjustment { kind: Adjust::NeverToAny, target: adj_ty.clone() }], + ); + adj_ty + } else { + ty + } + } + fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { self.db.unwind_if_cancelled(); @@ -91,7 +115,7 @@ impl<'a> InferenceContext<'a> { Expr::Missing => self.err_ty(), &Expr::If { condition, then_branch, else_branch } => { let expected = &expected.adjust_for_branches(&mut self.table); - self.infer_expr( + self.infer_expr_coerce_never( condition, &Expectation::HasType(self.result.standard_types.bool_.clone()), ); @@ -415,7 +439,7 @@ impl<'a> InferenceContext<'a> { for arm in arms.iter() { if let Some(guard_expr) = arm.guard { self.diverges = Diverges::Maybe; - self.infer_expr( + self.infer_expr_coerce_never( guard_expr, &Expectation::HasType(self.result.standard_types.bool_.clone()), ); @@ -1146,7 +1170,6 @@ impl<'a> InferenceContext<'a> { let coerce_ty = expected.coercion_target_type(&mut self.table); let old_resolver = mem::replace(&mut self.resolver, resolver_for_expr(self.db.upcast(), self.owner, expr)); - let (break_ty, ty) = self.with_breakable_ctx(BreakableKind::Block, Some(coerce_ty.clone()), label, |this| { for stmt in statements { @@ -1188,14 +1211,14 @@ impl<'a> InferenceContext<'a> { } } &Statement::Expr { expr, has_semi } => { - this.infer_expr( - expr, - &if has_semi { - Expectation::none() - } else { - Expectation::HasType(this.result.standard_types.unit.clone()) - }, - ); + if has_semi { + this.infer_expr(expr, &Expectation::none()); + } else { + this.infer_expr_coerce( + expr, + &Expectation::HasType(this.result.standard_types.unit.clone()), + ); + } } } } diff --git a/crates/hir-ty/src/tests/patterns.rs b/crates/hir-ty/src/tests/patterns.rs index be67329fee44..74bcab6caa94 100644 --- a/crates/hir-ty/src/tests/patterns.rs +++ b/crates/hir-ty/src/tests/patterns.rs @@ -476,7 +476,7 @@ fn infer_adt_pattern() { 183..184 'x': usize 190..191 'x': usize 201..205 'E::B': E - 209..212 'foo': bool + 209..212 'foo': {unknown} 216..217 '1': usize 227..231 'E::B': E 235..237 '10': usize diff --git a/crates/hir-ty/src/tests/regression.rs b/crates/hir-ty/src/tests/regression.rs index 5fc2f46d5601..2fa6234da1e8 100644 --- a/crates/hir-ty/src/tests/regression.rs +++ b/crates/hir-ty/src/tests/regression.rs @@ -270,7 +270,7 @@ fn infer_std_crash_5() { 61..320 '{ ... }': () 75..79 'name': &{unknown} 82..166 'if doe... }': &{unknown} - 85..98 'doesnt_matter': bool + 85..98 'doesnt_matter': {unknown} 99..128 '{ ... }': &{unknown} 113..118 'first': &{unknown} 134..166 '{ ... }': &{unknown} @@ -279,7 +279,7 @@ fn infer_std_crash_5() { 181..188 'content': &{unknown} 191..313 'if ICE... }': &{unknown} 194..231 'ICE_RE..._VALUE': {unknown} - 194..247 'ICE_RE...&name)': bool + 194..247 'ICE_RE...&name)': {unknown} 241..246 '&name': &&{unknown} 242..246 'name': &{unknown} 248..276 '{ ... }': &{unknown} From 5a91f015b489627d3c9578ec2b01735687c8880a Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 4 Mar 2023 20:33:28 +0100 Subject: [PATCH 115/362] internal: Handle fields called as method calls as the fields they resolve to --- crates/hir/src/semantics.rs | 19 ++++++++++++++++++- crates/hir/src/source_analyzer.rs | 16 ++++++++++++++++ crates/ide-db/src/defs.rs | 9 ++++++--- crates/ide/src/hover/tests.rs | 23 +++++++++++++++++++++++ 4 files changed, 63 insertions(+), 4 deletions(-) diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index be2dffc29ccc..697f4b43bc7d 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -12,7 +12,7 @@ use hir_def::{ macro_id_to_def_id, resolver::{self, HasResolver, Resolver, TypeNs}, type_ref::Mutability, - AsMacroCall, DefWithBodyId, FunctionId, MacroId, TraitId, VariantId, + AsMacroCall, DefWithBodyId, FieldId, FunctionId, MacroId, TraitId, VariantId, }; use hir_expand::{ db::AstDatabase, @@ -366,6 +366,16 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { self.imp.resolve_method_call(call).map(Function::from) } + /// Attempts to resolve this call expression as a method call falling back to resolving it as a field. + pub fn resolve_method_call_field_fallback( + &self, + call: &ast::MethodCallExpr, + ) -> Option> { + self.imp + .resolve_method_call_fallback(call) + .map(|it| it.map_left(Function::from).map_right(Field::from)) + } + pub fn resolve_await_to_poll(&self, await_expr: &ast::AwaitExpr) -> Option { self.imp.resolve_await_to_poll(await_expr).map(Function::from) } @@ -1146,6 +1156,13 @@ impl<'db> SemanticsImpl<'db> { self.analyze(call.syntax())?.resolve_method_call(self.db, call) } + fn resolve_method_call_fallback( + &self, + call: &ast::MethodCallExpr, + ) -> Option> { + self.analyze(call.syntax())?.resolve_method_call_fallback(self.db, call) + } + fn resolve_await_to_poll(&self, await_expr: &ast::AwaitExpr) -> Option { self.analyze(await_expr.syntax())?.resolve_await_to_poll(self.db, await_expr) } diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index ada529fa74c2..118a7f8ea868 100644 --- a/crates/hir/src/source_analyzer.rs +++ b/crates/hir/src/source_analyzer.rs @@ -10,6 +10,7 @@ use std::{ sync::Arc, }; +use either::Either; use hir_def::{ body::{ self, @@ -266,6 +267,21 @@ impl SourceAnalyzer { Some(self.resolve_impl_method_or_trait_def(db, f_in_trait, substs)) } + pub(crate) fn resolve_method_call_fallback( + &self, + db: &dyn HirDatabase, + call: &ast::MethodCallExpr, + ) -> Option> { + let expr_id = self.expr_id(db, &call.clone().into())?; + let inference_result = self.infer.as_ref()?; + match inference_result.method_resolution(expr_id) { + Some((f_in_trait, substs)) => { + Some(Either::Left(self.resolve_impl_method_or_trait_def(db, f_in_trait, substs))) + } + None => inference_result.field_resolution(expr_id).map(Either::Right), + } + } + pub(crate) fn resolve_await_to_poll( &self, db: &dyn HirDatabase, diff --git a/crates/ide-db/src/defs.rs b/crates/ide-db/src/defs.rs index cce33dcfe469..1322f5228e8b 100644 --- a/crates/ide-db/src/defs.rs +++ b/crates/ide-db/src/defs.rs @@ -469,9 +469,12 @@ impl NameRefClass { match_ast! { match parent { ast::MethodCallExpr(method_call) => { - sema.resolve_method_call(&method_call) - .map(Definition::Function) - .map(NameRefClass::Definition) + sema.resolve_method_call_field_fallback(&method_call) + .map(|it| { + it.map_left(Definition::Function) + .map_right(Definition::Field) + .either(NameRefClass::Definition, NameRefClass::Definition) + }) }, ast::FieldExpr(field_expr) => { sema.resolve_field(&field_expr) diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index 42f7c4339c7e..2e67056b9139 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -5797,3 +5797,26 @@ mod m { "#]], ); } + +#[test] +fn field_as_method_call_fallback() { + check( + r#" +struct S { f: u32 } +fn test() { + S { f: 0 }.f$0(); +} +"#, + expect![[r#" + *f* + + ```rust + test::S + ``` + + ```rust + f: u32 // size = 4, align = 4, offset = 0 + ``` + "#]], + ); +} From 79829d8718626b681d26bfbd64182d5d32d2dc57 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sat, 4 Mar 2023 11:28:52 +0100 Subject: [PATCH 116/362] lintcheck: use clap's derive interface This makes the code shorter and clearer. The only incompatible change is that an explicit command-line argument `--crates-toml=` will take precedence over the `LINTCHECK_TOML` environment variable. --- lintcheck/Cargo.toml | 2 +- lintcheck/src/config.rs | 144 ++++++++++++---------------------------- 2 files changed, 44 insertions(+), 102 deletions(-) diff --git a/lintcheck/Cargo.toml b/lintcheck/Cargo.toml index 653121af54dc..dbfeb8dd1d19 100644 --- a/lintcheck/Cargo.toml +++ b/lintcheck/Cargo.toml @@ -11,7 +11,7 @@ publish = false [dependencies] cargo_metadata = "0.15.3" -clap = "4.1.4" +clap = { version = "4.1.8", features = ["derive", "env"] } crossbeam-channel = "0.5.6" flate2 = "1.0" rayon = "1.5.1" diff --git a/lintcheck/src/config.rs b/lintcheck/src/config.rs index f87b902b92d9..e1836c19aa25 100644 --- a/lintcheck/src/config.rs +++ b/lintcheck/src/config.rs @@ -1,128 +1,70 @@ -use clap::{Arg, ArgAction, ArgMatches, Command}; -use std::env; +use clap::Parser; use std::path::PathBuf; -fn get_clap_config() -> ArgMatches { - Command::new("lintcheck") - .about("run clippy on a set of crates and check output") - .args([ - Arg::new("only") - .action(ArgAction::Set) - .value_name("CRATE") - .long("only") - .help("Only process a single crate of the list"), - Arg::new("crates-toml") - .action(ArgAction::Set) - .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::new("threads") - .action(ArgAction::Set) - .value_name("N") - .value_parser(clap::value_parser!(usize)) - .short('j') - .long("jobs") - .help("Number of threads to use, 0 automatic choice"), - Arg::new("fix") - .long("fix") - .help("Runs cargo clippy --fix and checks if all suggestions apply"), - Arg::new("filter") - .long("filter") - .action(ArgAction::Append) - .value_name("clippy_lint_name") - .help("Apply a filter to only collect specified lints, this also overrides `allow` attributes"), - Arg::new("markdown") - .long("markdown") - .help("Change the reports table to use markdown links"), - Arg::new("recursive") - .long("recursive") - .help("Run clippy on the dependencies of crates specified in crates-toml") - .conflicts_with("threads") - .conflicts_with("fix"), - ]) - .get_matches() -} - -#[derive(Debug, Clone)] +#[derive(Clone, Debug, Parser)] pub(crate) struct LintcheckConfig { - /// max number of jobs to spawn (default 1) + /// Number of threads to use, 0 automatic choice + #[clap(long = "jobs", short = 'j', value_name = "N", default_value_t = 1)] pub max_jobs: usize, - /// we read the sources to check from here + /// Set the path for a crates.toml where lintcheck should read the sources from + #[clap( + long = "crates-toml", + value_name = "CRATES-SOURCES-TOML-PATH", + default_value = "lintcheck/lintcheck_crates.toml", + hide_default_value = true, + env = "LINTCHECK_TOML", + hide_env = true + )] pub sources_toml_path: PathBuf, - /// we save the clippy lint results here - pub lintcheck_results_path: PathBuf, - /// Check only a specified package + /// File to save the clippy lint results here + #[clap(skip = "")] + pub lintcheck_results_path: PathBuf, // Overridden in new() + /// Only process a single crate on the list + #[clap(long, value_name = "CRATE")] pub only: Option, - /// whether to just run --fix and not collect all the warnings + /// Runs cargo clippy --fix and checks if all suggestions apply + #[clap(long, conflicts_with("max_jobs"))] pub fix: bool, - /// A list of lints that this lintcheck run should focus on + /// Apply a filter to only collect specified lints, this also overrides `allow` attributes + #[clap(long = "filter", value_name = "clippy_lint_name", use_value_delimiter = true)] pub lint_filter: Vec, - /// Indicate if the output should support markdown syntax + /// Change the reports table to use markdown links + #[clap(long)] pub markdown: bool, - /// Run clippy on the dependencies of crates + /// Run clippy on the dependencies of crates specified in crates-toml + #[clap(long, conflicts_with("max_jobs"))] pub recursive: 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 - // if not, use the default "lintcheck/lintcheck_crates.toml" - let sources_toml = env::var("LINTCHECK_TOML").unwrap_or_else(|_| { - clap_config - .get_one::("crates-toml") - .map_or("lintcheck/lintcheck_crates.toml", |s| &**s) - .into() - }); - - let markdown = clap_config.contains_id("markdown"); - let sources_toml_path = PathBuf::from(sources_toml); + let mut config = LintcheckConfig::parse(); // 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!( + let filename: PathBuf = config.sources_toml_path.file_stem().unwrap().into(); + config.lintcheck_results_path = PathBuf::from(format!( "lintcheck-logs/{}_logs.{}", filename.display(), - if markdown { "md" } else { "txt" } + if config.markdown { "md" } else { "txt" } )); // look at the --threads arg, if 0 is passed, use the threads count - let max_jobs = match clap_config.get_one::("threads") { - Some(&0) => { - // automatic choice - std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1) - }, - Some(&threads) => threads, - // no -j passed, use a single thread - None => 1, + if config.max_jobs == 0 { + // automatic choice + config.max_jobs = std::thread::available_parallelism().map_or(1, |n| n.get()); }; - let lint_filter: Vec = clap_config - .get_many::("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.get_one::("only").map(String::from), - fix: clap_config.contains_id("fix"), - lint_filter, - markdown, - recursive: clap_config.contains_id("recursive"), + for lint_name in &mut config.lint_filter { + *lint_name = format!( + "clippy::{}", + lint_name + .strip_prefix("clippy::") + .unwrap_or(lint_name) + .replace('_', "-") + ); } + + config } } From f95d9deafd0f4d206eea02f2ab56b1c495f2f8d3 Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Sat, 25 Feb 2023 15:54:31 -0500 Subject: [PATCH 117/362] Handle ambiguous projections --- clippy_lints/src/dereference.rs | 8 ++++---- tests/ui/crashes/ice-rust-107877.rs | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 tests/ui/crashes/ice-rust-107877.rs diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index b2ed81219942..4e305d4f6688 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -1357,10 +1357,10 @@ fn replace_types<'tcx>( && let Some(term_ty) = projection_predicate.term.ty() && let ty::Param(term_param_ty) = term_ty.kind() { - let item_def_id = projection_predicate.projection_ty.def_id; - let assoc_item = cx.tcx.associated_item(item_def_id); - let projection = cx.tcx - .mk_projection(assoc_item.def_id, cx.tcx.mk_substs_trait(new_ty, [])); + let projection = cx.tcx.mk_ty(ty::Alias( + ty::Projection, + projection_predicate.projection_ty.with_self_ty(cx.tcx, new_ty), + )); if let Ok(projected_ty) = cx.tcx.try_normalize_erasing_regions(cx.param_env, projection) && substs[term_param_ty.index as usize] != ty::GenericArg::from(projected_ty) diff --git a/tests/ui/crashes/ice-rust-107877.rs b/tests/ui/crashes/ice-rust-107877.rs new file mode 100644 index 000000000000..7f5bae60d55d --- /dev/null +++ b/tests/ui/crashes/ice-rust-107877.rs @@ -0,0 +1,17 @@ +#![allow(dead_code)] + +struct Foo; + +impl<'a> std::convert::TryFrom<&'a String> for Foo { + type Error = std::convert::Infallible; + + fn try_from(_: &'a String) -> Result { + Ok(Foo) + } +} + +fn find(_: impl std::convert::TryInto) {} + +fn main() { + find(&String::new()); +} From ae8ce99d976bc25935bc2c7b09c978aee7d5863c Mon Sep 17 00:00:00 2001 From: hkalbasi Date: Sun, 5 Mar 2023 13:53:49 +0330 Subject: [PATCH 118/362] Bring back the hex in const hover --- crates/hir-ty/src/display.rs | 26 ++++++++++++++++++++++++-- crates/hir/src/lib.rs | 15 +++++++++++++-- crates/ide/src/hover/render.rs | 4 ++-- crates/ide/src/hover/tests.rs | 33 +++++++++++++++++++++++++++------ 4 files changed, 66 insertions(+), 12 deletions(-) diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs index 3123ddb985b2..6dde47736022 100644 --- a/crates/hir-ty/src/display.rs +++ b/crates/hir-ty/src/display.rs @@ -5,7 +5,7 @@ use std::fmt::{self, Debug}; use base_db::CrateId; -use chalk_ir::BoundVar; +use chalk_ir::{BoundVar, TyKind}; use hir_def::{ adt::VariantData, body, @@ -36,7 +36,7 @@ use crate::{ AdtId, AliasEq, AliasTy, Binders, CallableDefId, CallableSig, Const, ConstScalar, ConstValue, DomainGoal, GenericArg, ImplTraitId, Interner, Lifetime, LifetimeData, LifetimeOutlives, MemoryMap, Mutability, OpaqueTy, ProjectionTy, ProjectionTyExt, QuantifiedWhereClause, Scalar, - Substitution, TraitRef, TraitRefExt, Ty, TyExt, TyKind, WhereClause, + Substitution, TraitRef, TraitRefExt, Ty, TyExt, WhereClause, }; pub trait HirWrite: fmt::Write { @@ -383,6 +383,28 @@ impl HirDisplay for Const { } } +pub struct HexifiedConst(pub Const); + +impl HirDisplay for HexifiedConst { + fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { + let data = &self.0.data(Interner); + if let TyKind::Scalar(s) = data.ty.kind(Interner) { + if matches!(s, Scalar::Int(_) | Scalar::Uint(_)) { + if let ConstValue::Concrete(c) = &data.value { + if let ConstScalar::Bytes(b, m) = &c.interned { + let value = u128::from_le_bytes(pad16(b, false)); + if value >= 10 { + render_const_scalar(f, &b, m, &data.ty)?; + return write!(f, " ({:#X})", value); + } + } + } + } + } + self.0.hir_fmt(f) + } +} + fn render_const_scalar( f: &mut HirFormatter<'_>, b: &[u8], diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 20009698f918..df6484db536e 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -60,6 +60,7 @@ use hir_ty::{ all_super_traits, autoderef, consteval::{try_const_usize, unknown_const_as_generic, ConstEvalError, ConstExt}, diagnostics::BodyValidationDiagnostic, + display::HexifiedConst, layout::layout_of_ty, method_resolution::{self, TyFingerprint}, mir::interpret_mir, @@ -1883,8 +1884,18 @@ impl Const { Type::new_with_resolver_inner(db, &resolver, ty) } - pub fn eval(self, db: &dyn HirDatabase) -> Result { - db.const_eval(self.id) + pub fn render_eval(self, db: &dyn HirDatabase) -> Result { + let c = db.const_eval(self.id)?; + let r = format!("{}", HexifiedConst(c).display(db)); + // We want to see things like `` and `` as they are probably bug in our + // implementation, but there is no need to show things like `` or `` to + // the user. + if r.contains("not-supported>") { + return Err(ConstEvalError::MirEvalError(MirEvalError::NotSupported( + "rendering complex constants".to_string(), + ))); + } + return Ok(r); } } diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs index c64b60d2936a..6a29ddf59e18 100644 --- a/crates/ide/src/hover/render.rs +++ b/crates/ide/src/hover/render.rs @@ -432,9 +432,9 @@ pub(super) fn definition( } }), Definition::Const(it) => label_value_and_docs(db, it, |it| { - let body = it.eval(db); + let body = it.render_eval(db); match body { - Ok(x) => Some(format!("{}", x.display(db))), + Ok(x) => Some(x), Err(_) => { let source = it.source(db)?; let mut body = source.value.body()?.syntax().clone(); diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index 2e67056b9139..57bf0f9ad5f3 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -531,7 +531,7 @@ fn hover_const_static() { ``` ```rust - const foo: u32 = 123 + const foo: u32 = 123 (0x7B) ``` "#]], ); @@ -3770,7 +3770,6 @@ const FOO$0: usize = 1 << 3; This is a doc "#]], ); - // FIXME: show hex for >10 check( r#" /// This is a doc @@ -3784,7 +3783,7 @@ const FOO$0: usize = (1 << 3) + (1 << 2); ``` ```rust - const FOO: usize = 12 + const FOO: usize = 12 (0xC) ``` --- @@ -3828,7 +3827,7 @@ const FOO$0: i32 = 2 - 3; ``` ```rust - const FOO: i32 = -1 + const FOO: i32 = -1 (0xFFFFFFFF) ``` --- @@ -3915,7 +3914,7 @@ const FOO$0: u8 = b'a'; ``` ```rust - const FOO: u8 = 97 + const FOO: u8 = 97 (0x61) ``` --- @@ -3937,7 +3936,7 @@ const FOO$0: u8 = b'\x61'; ``` ```rust - const FOO: u8 = 97 + const FOO: u8 = 97 (0x61) ``` --- @@ -3989,6 +3988,28 @@ const FOO$0: f32 = 1f32; This is a doc "#]], ); + // Don't show `` in const hover + check( + r#" +/// This is a doc +const FOO$0: &i32 = &2; +"#, + expect![[r#" + *FOO* + + ```rust + test + ``` + + ```rust + const FOO: &i32 = &2 + ``` + + --- + + This is a doc + "#]], + ); //show f64 typecasted from float check( r#" From a8606e536347d21fa13768354f17048c54d7d89d Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 5 Mar 2023 14:37:44 +0100 Subject: [PATCH 119/362] Re-use the resolver in InferenceContext instead of rebuilding it on every expression change --- crates/hir-def/src/body/scope.rs | 1 + crates/hir-def/src/resolver.rs | 102 +++++++++++++++++++++++++++++-- crates/hir-ty/src/infer.rs | 6 +- crates/hir-ty/src/infer/expr.rs | 14 ++--- crates/hir-ty/src/infer/pat.rs | 5 +- crates/hir-ty/src/infer/path.rs | 22 ++----- crates/hir-ty/src/tests.rs | 4 +- 7 files changed, 116 insertions(+), 38 deletions(-) diff --git a/crates/hir-def/src/body/scope.rs b/crates/hir-def/src/body/scope.rs index e7078b7953b8..cab657b807eb 100644 --- a/crates/hir-def/src/body/scope.rs +++ b/crates/hir-def/src/body/scope.rs @@ -66,6 +66,7 @@ impl ExprScopes { self.scopes[scope].label.clone() } + /// Returns the scopes in ascending order. pub fn scope_chain(&self, scope: Option) -> impl Iterator + '_ { std::iter::successors(scope, move |&scope| self.scopes[scope].parent) } diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs index 664db292a7f7..620e9202aad5 100644 --- a/crates/hir-def/src/resolver.rs +++ b/crates/hir-def/src/resolver.rs @@ -1,5 +1,5 @@ //! Name resolution façade. -use std::{hash::BuildHasherDefault, sync::Arc}; +use std::{fmt, hash::BuildHasherDefault, sync::Arc}; use base_db::CrateId; use hir_expand::name::{name, Name}; @@ -36,19 +36,34 @@ pub struct Resolver { module_scope: ModuleItemMap, } -#[derive(Debug, Clone)] +#[derive(Clone)] struct ModuleItemMap { def_map: Arc, module_id: LocalModuleId, } -#[derive(Debug, Clone)] +impl fmt::Debug for ModuleItemMap { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ModuleItemMap").field("module_id", &self.module_id).finish() + } +} + +#[derive(Clone)] struct ExprScope { owner: DefWithBodyId, expr_scopes: Arc, scope_id: ScopeId, } +impl fmt::Debug for ExprScope { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ExprScope") + .field("owner", &self.owner) + .field("scope_id", &self.scope_id) + .finish() + } +} + #[derive(Debug, Clone)] enum Scope { /// All the items and imported names of a module @@ -478,8 +493,72 @@ impl Resolver { _ => None, }) } + /// `expr_id` is required to be an expression id that comes after the top level expression scope in the given resolver + #[must_use] + pub fn update_to_inner_scope( + &mut self, + db: &dyn DefDatabase, + owner: DefWithBodyId, + expr_id: ExprId, + ) -> UpdateGuard { + #[inline(always)] + fn append_expr_scope( + db: &dyn DefDatabase, + resolver: &mut Resolver, + owner: DefWithBodyId, + expr_scopes: &Arc, + scope_id: ScopeId, + ) { + resolver.scopes.push(Scope::ExprScope(ExprScope { + owner, + expr_scopes: expr_scopes.clone(), + scope_id, + })); + if let Some(block) = expr_scopes.block(scope_id) { + if let Some(def_map) = db.block_def_map(block) { + let root = def_map.root(); + resolver + .scopes + .push(Scope::BlockScope(ModuleItemMap { def_map, module_id: root })); + // FIXME: This adds as many module scopes as there are blocks, but resolving in each + // already traverses all parents, so this is O(n²). I think we could only store the + // innermost module scope instead? + } + } + } + + let start = self.scopes.len(); + let innermost_scope = self.scopes().next(); + match innermost_scope { + Some(&Scope::ExprScope(ExprScope { scope_id, ref expr_scopes, owner })) => { + let expr_scopes = expr_scopes.clone(); + let scope_chain = expr_scopes + .scope_chain(expr_scopes.scope_for(expr_id)) + .take_while(|&it| it != scope_id); + for scope_id in scope_chain { + append_expr_scope(db, self, owner, &expr_scopes, scope_id); + } + } + _ => { + let expr_scopes = db.expr_scopes(owner); + let scope_chain = expr_scopes.scope_chain(expr_scopes.scope_for(expr_id)); + + for scope_id in scope_chain { + append_expr_scope(db, self, owner, &expr_scopes, scope_id); + } + } + } + self.scopes[start..].reverse(); + UpdateGuard(start) + } + + pub fn reset_to_guard(&mut self, UpdateGuard(start): UpdateGuard) { + self.scopes.truncate(start); + } } +pub struct UpdateGuard(usize); + impl Resolver { fn scopes(&self) -> impl Iterator { self.scopes.iter().rev() @@ -576,10 +655,11 @@ impl Scope { } } -// needs arbitrary_self_types to be a method... or maybe move to the def? pub fn resolver_for_expr(db: &dyn DefDatabase, owner: DefWithBodyId, expr_id: ExprId) -> Resolver { + let r = owner.resolver(db); let scopes = db.expr_scopes(owner); - resolver_for_scope(db, owner, scopes.scope_for(expr_id)) + let scope_id = scopes.scope_for(expr_id); + resolver_for_scope_(db, scopes, scope_id, r, owner) } pub fn resolver_for_scope( @@ -587,8 +667,18 @@ pub fn resolver_for_scope( owner: DefWithBodyId, scope_id: Option, ) -> Resolver { - let mut r = owner.resolver(db); + let r = owner.resolver(db); let scopes = db.expr_scopes(owner); + resolver_for_scope_(db, scopes, scope_id, r, owner) +} + +fn resolver_for_scope_( + db: &dyn DefDatabase, + scopes: Arc, + scope_id: Option, + mut r: Resolver, + owner: DefWithBodyId, +) -> Resolver { let scope_chain = scopes.scope_chain(scope_id).collect::>(); r.scopes.reserve(scope_chain.len()); diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 22dcea8fcd45..56ae786193e5 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -706,7 +706,6 @@ impl<'a> InferenceContext<'a> { } fn make_ty(&mut self, type_ref: &TypeRef) -> Ty { - // FIXME use right resolver for block let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver); let ty = ctx.lower_ty(type_ref); let ty = self.insert_type_vars(ty); @@ -822,12 +821,11 @@ impl<'a> InferenceContext<'a> { Some(path) => path, None => return (self.err_ty(), None), }; - let resolver = &self.resolver; let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver); // FIXME: this should resolve assoc items as well, see this example: // https://play.rust-lang.org/?gist=087992e9e22495446c01c0d4e2d69521 let (resolution, unresolved) = if value_ns { - match resolver.resolve_path_in_value_ns(self.db.upcast(), path.mod_path()) { + match self.resolver.resolve_path_in_value_ns(self.db.upcast(), path.mod_path()) { Some(ResolveValueResult::ValueNs(value)) => match value { ValueNs::EnumVariantId(var) => { let substs = ctx.substs_from_path(path, var.into(), true); @@ -848,7 +846,7 @@ impl<'a> InferenceContext<'a> { None => return (self.err_ty(), None), } } else { - match resolver.resolve_path_in_type_ns(self.db.upcast(), path.mod_path()) { + match self.resolver.resolve_path_in_type_ns(self.db.upcast(), path.mod_path()) { Some(it) => it, None => return (self.err_ty(), None), } diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 8895dc095f92..13ca05347228 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -15,7 +15,6 @@ use hir_def::{ generics::TypeOrConstParamData, lang_item::LangItem, path::{GenericArg, GenericArgs}, - resolver::resolver_for_expr, ConstParamId, FieldId, ItemContainerId, Lookup, }; use hir_expand::name::{name, Name}; @@ -457,9 +456,10 @@ impl<'a> InferenceContext<'a> { } } Expr::Path(p) => { - // FIXME this could be more efficient... - let resolver = resolver_for_expr(self.db.upcast(), self.owner, tgt_expr); - self.infer_path(&resolver, p, tgt_expr.into()).unwrap_or_else(|| self.err_ty()) + let g = self.resolver.update_to_inner_scope(self.db.upcast(), self.owner, tgt_expr); + let ty = self.infer_path(p, tgt_expr.into()).unwrap_or_else(|| self.err_ty()); + self.resolver.reset_to_guard(g); + ty } Expr::Continue { label } => { if let None = find_continuable(&mut self.breakables, label.as_ref()) { @@ -1168,8 +1168,8 @@ impl<'a> InferenceContext<'a> { expected: &Expectation, ) -> Ty { let coerce_ty = expected.coercion_target_type(&mut self.table); - let old_resolver = - mem::replace(&mut self.resolver, resolver_for_expr(self.db.upcast(), self.owner, expr)); + let g = self.resolver.update_to_inner_scope(self.db.upcast(), self.owner, expr); + let (break_ty, ty) = self.with_breakable_ctx(BreakableKind::Block, Some(coerce_ty.clone()), label, |this| { for stmt in statements { @@ -1256,7 +1256,7 @@ impl<'a> InferenceContext<'a> { } } }); - self.resolver = old_resolver; + self.resolver.reset_to_guard(g); break_ty.unwrap_or(ty) } diff --git a/crates/hir-ty/src/infer/pat.rs b/crates/hir-ty/src/infer/pat.rs index 3d03c2a527cb..a7bd009e34bd 100644 --- a/crates/hir-ty/src/infer/pat.rs +++ b/crates/hir-ty/src/infer/pat.rs @@ -245,9 +245,8 @@ impl<'a> InferenceContext<'a> { self.infer_record_pat_like(p.as_deref(), &expected, default_bm, pat, subs) } Pat::Path(path) => { - // FIXME use correct resolver for the surrounding expression - let resolver = self.resolver.clone(); - self.infer_path(&resolver, path, pat.into()).unwrap_or_else(|| self.err_ty()) + // FIXME update resolver for the surrounding expression + self.infer_path(path, pat.into()).unwrap_or_else(|| self.err_ty()) } Pat::Bind { mode, name: _, subpat } => { return self.infer_bind_pat(pat, *mode, default_bm, *subpat, &expected); diff --git a/crates/hir-ty/src/infer/path.rs b/crates/hir-ty/src/infer/path.rs index b3867623f37e..93dbd8b3633a 100644 --- a/crates/hir-ty/src/infer/path.rs +++ b/crates/hir-ty/src/infer/path.rs @@ -3,7 +3,7 @@ use chalk_ir::cast::Cast; use hir_def::{ path::{Path, PathSegment}, - resolver::{ResolveValueResult, Resolver, TypeNs, ValueNs}, + resolver::{ResolveValueResult, TypeNs, ValueNs}, AdtId, AssocItemId, EnumVariantId, ItemContainerId, Lookup, }; use hir_expand::name::Name; @@ -21,35 +21,25 @@ use crate::{ use super::{ExprOrPatId, InferenceContext, TraitRef}; impl<'a> InferenceContext<'a> { - pub(super) fn infer_path( - &mut self, - resolver: &Resolver, - path: &Path, - id: ExprOrPatId, - ) -> Option { - let ty = self.resolve_value_path(resolver, path, id)?; + pub(super) fn infer_path(&mut self, path: &Path, id: ExprOrPatId) -> Option { + let ty = self.resolve_value_path(path, id)?; let ty = self.insert_type_vars(ty); let ty = self.normalize_associated_types_in(ty); Some(ty) } - fn resolve_value_path( - &mut self, - resolver: &Resolver, - path: &Path, - id: ExprOrPatId, - ) -> Option { + fn resolve_value_path(&mut self, path: &Path, id: ExprOrPatId) -> Option { let (value, self_subst) = if let Some(type_ref) = path.type_anchor() { let Some(last) = path.segments().last() else { return None }; let ty = self.make_ty(type_ref); let remaining_segments_for_ty = path.segments().take(path.segments().len() - 1); - let ctx = crate::lower::TyLoweringContext::new(self.db, resolver); + let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver); let (ty, _) = ctx.lower_ty_relative_path(ty, None, remaining_segments_for_ty); self.resolve_ty_assoc_item(ty, last.name, id)? } else { // FIXME: report error, unresolved first path segment let value_or_partial = - resolver.resolve_path_in_value_ns(self.db.upcast(), path.mod_path())?; + self.resolver.resolve_path_in_value_ns(self.db.upcast(), path.mod_path())?; match value_or_partial { ResolveValueResult::ValueNs(it) => (it, None), diff --git a/crates/hir-ty/src/tests.rs b/crates/hir-ty/src/tests.rs index 759878b10bbf..bcd63d9472a8 100644 --- a/crates/hir-ty/src/tests.rs +++ b/crates/hir-ty/src/tests.rs @@ -163,7 +163,7 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour } else { ty.display_test(&db).to_string() }; - assert_eq!(actual, expected); + assert_eq!(actual, expected, "type annotation differs at {:#?}", range.range); } } @@ -179,7 +179,7 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour } else { ty.display_test(&db).to_string() }; - assert_eq!(actual, expected); + assert_eq!(actual, expected, "type annotation differs at {:#?}", range.range); } if let Some(expected) = adjustments.remove(&range) { let adjustments = inference_result From a51267c5e0f00f050378892d671317d912ff8257 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 5 Mar 2023 15:04:46 +0100 Subject: [PATCH 120/362] Allocate traits in scope upfront when type checking instead of recollecting them everytime --- crates/hir-def/src/resolver.rs | 9 ++++ crates/hir-ty/src/infer.rs | 14 +++++- crates/hir-ty/src/infer/expr.rs | 10 ++-- crates/hir-ty/src/infer/path.rs | 83 ++++++++++++++++----------------- 4 files changed, 66 insertions(+), 50 deletions(-) diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs index 620e9202aad5..57af92172a78 100644 --- a/crates/hir-def/src/resolver.rs +++ b/crates/hir-def/src/resolver.rs @@ -449,6 +449,15 @@ impl Resolver { traits } + pub fn traits_in_scope_from_block_scopes(&self) -> impl Iterator + '_ { + self.scopes() + .filter_map(|scope| match scope { + Scope::BlockScope(m) => Some(m.def_map[m.module_id].scope.traits()), + _ => None, + }) + .flatten() + } + pub fn module(&self) -> ModuleId { let (def_map, local_id) = self.item_scope(); def_map.module_id(local_id) diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 56ae786193e5..869b39ab37d2 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -33,7 +33,7 @@ use hir_def::{ }; use hir_expand::name::{name, Name}; use la_arena::ArenaMap; -use rustc_hash::FxHashMap; +use rustc_hash::{FxHashMap, FxHashSet}; use stdx::always; use crate::{ @@ -423,6 +423,8 @@ pub(crate) struct InferenceContext<'a> { pub(crate) resolver: Resolver, table: unify::InferenceTable<'a>, trait_env: Arc, + /// The traits in scope, disregarding block modules. This is used for caching purposes. + traits_in_scope: FxHashSet, pub(crate) result: InferenceResult, /// The return type of the function being inferred, the closure or async block if we're /// currently within one. @@ -505,6 +507,7 @@ impl<'a> InferenceContext<'a> { db, owner, body, + traits_in_scope: resolver.traits_in_scope(db.upcast()), resolver, diverges: Diverges::Maybe, breakables: Vec::new(), @@ -1056,6 +1059,15 @@ impl<'a> InferenceContext<'a> { let struct_ = self.resolve_lang_item(LangItem::VaList)?.as_struct()?; Some(struct_.into()) } + + fn get_traits_in_scope(&self) -> Either, &FxHashSet> { + let mut b_traits = self.resolver.traits_in_scope_from_block_scopes().peekable(); + if b_traits.peek().is_some() { + Either::Left(self.traits_in_scope.iter().copied().chain(b_traits).collect()) + } else { + Either::Right(&self.traits_in_scope) + } + } } /// When inferring an expression, we propagate downward whatever type hint we diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 13ca05347228..cca84488c94c 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -1349,14 +1349,14 @@ impl<'a> InferenceContext<'a> { None => { // no field found, let method_with_same_name_exists = { - let canonicalized_receiver = self.canonicalize(receiver_ty.clone()); - let traits_in_scope = self.resolver.traits_in_scope(self.db.upcast()); + self.get_traits_in_scope(); + let canonicalized_receiver = self.canonicalize(receiver_ty.clone()); method_resolution::lookup_method( self.db, &canonicalized_receiver.value, self.trait_env.clone(), - &traits_in_scope, + self.get_traits_in_scope().as_ref().left_or_else(|&it| it), VisibleFromModule::Filter(self.resolver.module()), name, ) @@ -1385,13 +1385,11 @@ impl<'a> InferenceContext<'a> { let receiver_ty = self.infer_expr(receiver, &Expectation::none()); let canonicalized_receiver = self.canonicalize(receiver_ty.clone()); - let traits_in_scope = self.resolver.traits_in_scope(self.db.upcast()); - let resolved = method_resolution::lookup_method( self.db, &canonicalized_receiver.value, self.trait_env.clone(), - &traits_in_scope, + self.get_traits_in_scope().as_ref().left_or_else(|&it| it), VisibleFromModule::Filter(self.resolver.module()), method_name, ); diff --git a/crates/hir-ty/src/infer/path.rs b/crates/hir-ty/src/infer/path.rs index 93dbd8b3633a..d4d0d0819680 100644 --- a/crates/hir-ty/src/infer/path.rs +++ b/crates/hir-ty/src/infer/path.rs @@ -205,6 +205,7 @@ impl<'a> InferenceContext<'a> { Some((def, Some(trait_ref.substitution))) } + // FIXME: Change sig to -> Option<(ValueNs, Substitution)>, subs aren't optional from here anymore fn resolve_ty_assoc_item( &mut self, ty: Ty, @@ -220,70 +221,66 @@ impl<'a> InferenceContext<'a> { } let canonical_ty = self.canonicalize(ty.clone()); - let traits_in_scope = self.resolver.traits_in_scope(self.db.upcast()); let mut not_visible = None; let res = method_resolution::iterate_method_candidates( &canonical_ty.value, self.db, self.table.trait_env.clone(), - &traits_in_scope, + self.get_traits_in_scope().as_ref().left_or_else(|&it| it), VisibleFromModule::Filter(self.resolver.module()), Some(name), method_resolution::LookupMode::Path, |_ty, item, visible| { - let (def, container) = match item { - AssocItemId::FunctionId(f) => { - (ValueNs::FunctionId(f), f.lookup(self.db.upcast()).container) - } - AssocItemId::ConstId(c) => { - (ValueNs::ConstId(c), c.lookup(self.db.upcast()).container) - } - AssocItemId::TypeAliasId(_) => unreachable!(), - }; - let substs = match container { - ItemContainerId::ImplId(impl_id) => { - let impl_substs = TyBuilder::subst_for_def(self.db, impl_id, None) - .fill_with_inference_vars(&mut self.table) - .build(); - let impl_self_ty = - self.db.impl_self_ty(impl_id).substitute(Interner, &impl_substs); - self.unify(&impl_self_ty, &ty); - impl_substs - } - ItemContainerId::TraitId(trait_) => { - // we're picking this method - let trait_ref = TyBuilder::trait_ref(self.db, trait_) - .push(ty.clone()) - .fill_with_inference_vars(&mut self.table) - .build(); - self.push_obligation(trait_ref.clone().cast(Interner)); - trait_ref.substitution - } - ItemContainerId::ModuleId(_) | ItemContainerId::ExternBlockId(_) => { - never!("assoc item contained in module/extern block"); - return None; - } - }; - if visible { - Some((def, item, Some(substs), true)) + Some((item, true)) } else { if not_visible.is_none() { - not_visible = Some((def, item, Some(substs), false)); + not_visible = Some((item, false)); } None } }, ); let res = res.or(not_visible); - if let Some((_, item, Some(ref substs), visible)) = res { - self.write_assoc_resolution(id, item, substs.clone()); - if !visible { - self.push_diagnostic(InferenceDiagnostic::PrivateAssocItem { id, item }) + let (item, visible) = res?; + + let (def, container) = match item { + AssocItemId::FunctionId(f) => { + (ValueNs::FunctionId(f), f.lookup(self.db.upcast()).container) } + AssocItemId::ConstId(c) => (ValueNs::ConstId(c), c.lookup(self.db.upcast()).container), + AssocItemId::TypeAliasId(_) => unreachable!(), + }; + let substs = match container { + ItemContainerId::ImplId(impl_id) => { + let impl_substs = TyBuilder::subst_for_def(self.db, impl_id, None) + .fill_with_inference_vars(&mut self.table) + .build(); + let impl_self_ty = self.db.impl_self_ty(impl_id).substitute(Interner, &impl_substs); + self.unify(&impl_self_ty, &ty); + impl_substs + } + ItemContainerId::TraitId(trait_) => { + // we're picking this method + let trait_ref = TyBuilder::trait_ref(self.db, trait_) + .push(ty.clone()) + .fill_with_inference_vars(&mut self.table) + .build(); + self.push_obligation(trait_ref.clone().cast(Interner)); + trait_ref.substitution + } + ItemContainerId::ModuleId(_) | ItemContainerId::ExternBlockId(_) => { + never!("assoc item contained in module/extern block"); + return None; + } + }; + + self.write_assoc_resolution(id, item, substs.clone()); + if !visible { + self.push_diagnostic(InferenceDiagnostic::PrivateAssocItem { id, item }); } - res.map(|(def, _, substs, _)| (def, substs)) + Some((def, Some(substs))) } fn resolve_enum_variant_on_ty( From 27fad2ad751bc813a98db18d8c31ea3cb178d228 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 5 Mar 2023 15:42:40 +0100 Subject: [PATCH 121/362] Lift segment check out of the loop in resolve_path_in_value_ns --- crates/hir-def/src/resolver.rs | 102 ++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 45 deletions(-) diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs index 57af92172a78..eea837ddd23a 100644 --- a/crates/hir-def/src/resolver.rs +++ b/crates/hir-def/src/resolver.rs @@ -255,55 +255,67 @@ impl Resolver { return self.module_scope.resolve_path_in_value_ns(db, path); } - for scope in self.scopes() { - match scope { - Scope::ExprScope(_) if n_segments > 1 => continue, - Scope::ExprScope(scope) => { - let entry = scope - .expr_scopes - .entries(scope.scope_id) - .iter() - .find(|entry| entry.name() == first_name); + if n_segments <= 1 { + for scope in self.scopes() { + match scope { + Scope::ExprScope(scope) => { + let entry = scope + .expr_scopes + .entries(scope.scope_id) + .iter() + .find(|entry| entry.name() == first_name); - if let Some(e) = entry { - return Some(ResolveValueResult::ValueNs(ValueNs::LocalBinding(e.pat()))); + if let Some(e) = entry { + return Some(ResolveValueResult::ValueNs(ValueNs::LocalBinding( + e.pat(), + ))); + } + } + Scope::GenericParams { params, def } => { + if let Some(id) = params.find_const_by_name(first_name, *def) { + let val = ValueNs::GenericParam(id); + return Some(ResolveValueResult::ValueNs(val)); + } + } + &Scope::ImplDefScope(impl_) => { + if first_name == &name![Self] { + return Some(ResolveValueResult::ValueNs(ValueNs::ImplSelf(impl_))); + } + } + // bare `Self` doesn't work in the value namespace in a struct/enum definition + Scope::AdtScope(_) => continue, + Scope::BlockScope(m) => { + if let Some(def) = m.resolve_path_in_value_ns(db, path) { + return Some(def); + } } } - Scope::GenericParams { params, def } if n_segments > 1 => { - if let Some(id) = params.find_type_by_name(first_name, *def) { - let ty = TypeNs::GenericParam(id); - return Some(ResolveValueResult::Partial(ty, 1)); + } + } else { + for scope in self.scopes() { + match scope { + Scope::ExprScope(_) => continue, + Scope::GenericParams { params, def } => { + if let Some(id) = params.find_type_by_name(first_name, *def) { + let ty = TypeNs::GenericParam(id); + return Some(ResolveValueResult::Partial(ty, 1)); + } } - } - Scope::GenericParams { .. } if n_segments != 1 => continue, - Scope::GenericParams { params, def } => { - if let Some(id) = params.find_const_by_name(first_name, *def) { - let val = ValueNs::GenericParam(id); - return Some(ResolveValueResult::ValueNs(val)); + &Scope::ImplDefScope(impl_) => { + if first_name == &name![Self] { + return Some(ResolveValueResult::Partial(TypeNs::SelfType(impl_), 1)); + } } - } - - &Scope::ImplDefScope(impl_) => { - if first_name == &name![Self] { - return Some(if n_segments > 1 { - ResolveValueResult::Partial(TypeNs::SelfType(impl_), 1) - } else { - ResolveValueResult::ValueNs(ValueNs::ImplSelf(impl_)) - }); + Scope::AdtScope(adt) => { + if first_name == &name![Self] { + let ty = TypeNs::AdtSelfType(*adt); + return Some(ResolveValueResult::Partial(ty, 1)); + } } - } - // bare `Self` doesn't work in the value namespace in a struct/enum definition - Scope::AdtScope(_) if n_segments == 1 => continue, - Scope::AdtScope(adt) => { - if first_name == &name![Self] { - let ty = TypeNs::AdtSelfType(*adt); - return Some(ResolveValueResult::Partial(ty, 1)); - } - } - - Scope::BlockScope(m) => { - if let Some(def) = m.resolve_path_in_value_ns(db, path) { - return Some(def); + Scope::BlockScope(m) => { + if let Some(def) = m.resolve_path_in_value_ns(db, path) { + return Some(def); + } } } } @@ -316,8 +328,8 @@ impl Resolver { // If a path of the shape `u16::from_le_bytes` failed to resolve at all, then we fall back // to resolving to the primitive type, to allow this to still work in the presence of // `use core::u16;`. - if path.kind == PathKind::Plain && path.segments().len() > 1 { - if let Some(builtin) = BuiltinType::by_name(&path.segments()[0]) { + if path.kind == PathKind::Plain && n_segments > 1 { + if let Some(builtin) = BuiltinType::by_name(first_name) { return Some(ResolveValueResult::Partial(TypeNs::BuiltinType(builtin), 1)); } } From d8b1ec6a25d800691da50fa7834f8db2a2f124b6 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 5 Mar 2023 15:43:02 +0100 Subject: [PATCH 122/362] Remove unnecessary option wrapping --- crates/hir-ty/src/infer/path.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/crates/hir-ty/src/infer/path.rs b/crates/hir-ty/src/infer/path.rs index d4d0d0819680..891e1fab2ed4 100644 --- a/crates/hir-ty/src/infer/path.rs +++ b/crates/hir-ty/src/infer/path.rs @@ -35,7 +35,7 @@ impl<'a> InferenceContext<'a> { let remaining_segments_for_ty = path.segments().take(path.segments().len() - 1); let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver); let (ty, _) = ctx.lower_ty_relative_path(ty, None, remaining_segments_for_ty); - self.resolve_ty_assoc_item(ty, last.name, id)? + self.resolve_ty_assoc_item(ty, last.name, id).map(|(it, substs)| (it, Some(substs)))? } else { // FIXME: report error, unresolved first path segment let value_or_partial = @@ -43,9 +43,9 @@ impl<'a> InferenceContext<'a> { match value_or_partial { ResolveValueResult::ValueNs(it) => (it, None), - ResolveValueResult::Partial(def, remaining_index) => { - self.resolve_assoc_item(def, path, remaining_index, id)? - } + ResolveValueResult::Partial(def, remaining_index) => self + .resolve_assoc_item(def, path, remaining_index, id) + .map(|(it, substs)| (it, Some(substs)))?, } }; @@ -113,7 +113,7 @@ impl<'a> InferenceContext<'a> { path: &Path, remaining_index: usize, id: ExprOrPatId, - ) -> Option<(ValueNs, Option)> { + ) -> Option<(ValueNs, Substitution)> { assert!(remaining_index < path.segments().len()); // there may be more intermediate segments between the resolved one and // the end. Only the last segment needs to be resolved to a value; from @@ -166,7 +166,7 @@ impl<'a> InferenceContext<'a> { trait_ref: TraitRef, segment: PathSegment<'_>, id: ExprOrPatId, - ) -> Option<(ValueNs, Option)> { + ) -> Option<(ValueNs, Substitution)> { let trait_ = trait_ref.hir_trait_id(); let item = self.db.trait_data(trait_).items.iter().map(|(_name, id)| (*id)).find_map(|item| { @@ -202,16 +202,15 @@ impl<'a> InferenceContext<'a> { }; self.write_assoc_resolution(id, item, trait_ref.substitution.clone()); - Some((def, Some(trait_ref.substitution))) + Some((def, trait_ref.substitution)) } - // FIXME: Change sig to -> Option<(ValueNs, Substitution)>, subs aren't optional from here anymore fn resolve_ty_assoc_item( &mut self, ty: Ty, name: &Name, id: ExprOrPatId, - ) -> Option<(ValueNs, Option)> { + ) -> Option<(ValueNs, Substitution)> { if let TyKind::Error = ty.kind(Interner) { return None; } @@ -280,7 +279,7 @@ impl<'a> InferenceContext<'a> { if !visible { self.push_diagnostic(InferenceDiagnostic::PrivateAssocItem { id, item }); } - Some((def, Some(substs))) + Some((def, substs)) } fn resolve_enum_variant_on_ty( @@ -288,7 +287,7 @@ impl<'a> InferenceContext<'a> { ty: &Ty, name: &Name, id: ExprOrPatId, - ) -> Option<(ValueNs, Option)> { + ) -> Option<(ValueNs, Substitution)> { let ty = self.resolve_ty_shallow(ty); let (enum_id, subst) = match ty.as_adt() { Some((AdtId::EnumId(e), subst)) => (e, subst), @@ -298,6 +297,6 @@ impl<'a> InferenceContext<'a> { let local_id = enum_data.variant(name)?; let variant = EnumVariantId { parent: enum_id, local_id }; self.write_variant_resolution(id, variant.into()); - Some((ValueNs::EnumVariantId(variant), Some(subst.clone()))) + Some((ValueNs::EnumVariantId(variant), subst.clone())) } } From 7be48ac32c21c949ee1bb190462d3e18fa22e0bd Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 5 Mar 2023 16:52:07 +0100 Subject: [PATCH 123/362] Adjust replace_match_with_if_let applicability range --- .../src/handlers/replace_if_let_with_match.rs | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/crates/ide-assists/src/handlers/replace_if_let_with_match.rs b/crates/ide-assists/src/handlers/replace_if_let_with_match.rs index 457559656a42..5e31d38fbd6a 100644 --- a/crates/ide-assists/src/handlers/replace_if_let_with_match.rs +++ b/crates/ide-assists/src/handlers/replace_if_let_with_match.rs @@ -102,9 +102,11 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext<' return None; } + let let_ = if pat_seen { " let" } else { "" }; + acc.add( AssistId("replace_if_let_with_match", AssistKind::RefactorRewrite), - "Replace if let with match", + format!("Replace if{let_} with match"), available_range, move |edit| { let match_expr = { @@ -210,8 +212,17 @@ fn make_else_arm( // ``` pub(crate) fn replace_match_with_if_let(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let match_expr: ast::MatchExpr = ctx.find_node_at_offset()?; + let match_arm_list = match_expr.match_arm_list()?; + let available_range = TextRange::new( + match_expr.syntax().text_range().start(), + match_arm_list.syntax().text_range().start(), + ); + let cursor_in_range = available_range.contains_range(ctx.selection_trimmed()); + if !cursor_in_range { + return None; + } - let mut arms = match_expr.match_arm_list()?.arms(); + let mut arms = match_arm_list.arms(); let (first_arm, second_arm) = (arms.next()?, arms.next()?); if arms.next().is_some() || first_arm.guard().is_some() || second_arm.guard().is_some() { return None; @@ -226,10 +237,20 @@ pub(crate) fn replace_match_with_if_let(acc: &mut Assists, ctx: &AssistContext<' )?; let scrutinee = match_expr.expr()?; + let let_ = match &if_let_pat { + ast::Pat::LiteralPat(p) + if p.literal() + .map(|it| it.token().kind()) + .map_or(false, |it| it == T![true] || it == T![false]) => + { + "" + } + _ => " let", + }; let target = match_expr.syntax().text_range(); acc.add( AssistId("replace_match_with_if_let", AssistKind::RefactorRewrite), - "Replace match with if let", + format!("Replace match with if{let_}"), target, move |edit| { fn make_block_expr(expr: ast::Expr) -> ast::BlockExpr { From b6f0ebf71c2429d7890993effbfffa76f985c499 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sun, 5 Mar 2023 23:07:41 +0100 Subject: [PATCH 124/362] help: refer to `cargo check --help` --- src/driver.rs | 2 +- src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/driver.rs b/src/driver.rs index dd183362f276..f08393c303ef 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -176,7 +176,7 @@ Common options: --rustc Pass all args to rustc -V, --version Print version info and exit -Other options are the same as `cargo check`. +For the other options see `cargo check --help`. To allow or deny a lint from the command line you can use `cargo clippy --` with: diff --git a/src/main.rs b/src/main.rs index 82147eba33f0..c5e9b96cf3fc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,7 +18,7 @@ Common options: -V, --version Print version info and exit --explain LINT Print the documentation for a given lint -Other options are the same as `cargo check`. +For the other options see `cargo check --help`. To allow or deny a lint from the command line you can use `cargo clippy --` with: From 63396b30cf6e4f9148c7af8a21727b4d2109b632 Mon Sep 17 00:00:00 2001 From: Thom Chiovoloni Date: Sun, 5 Mar 2023 18:16:58 -0800 Subject: [PATCH 125/362] Allow binary files to go through the `FileLoader` --- compiler/rustc_span/src/source_map.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_span/src/source_map.rs b/compiler/rustc_span/src/source_map.rs index 2e339a9d2d2b..a1cb810a4293 100644 --- a/compiler/rustc_span/src/source_map.rs +++ b/compiler/rustc_span/src/source_map.rs @@ -100,6 +100,9 @@ pub trait FileLoader { /// Read the contents of a UTF-8 file into memory. fn read_file(&self, path: &Path) -> io::Result; + + /// Read the contents of a potentially non-UTF-8 file into memory. + fn read_binary_file(&self, path: &Path) -> io::Result>; } /// A FileLoader that uses std::fs to load real files. @@ -113,6 +116,10 @@ impl FileLoader for RealFileLoader { fn read_file(&self, path: &Path) -> io::Result { fs::read_to_string(path) } + + fn read_binary_file(&self, path: &Path) -> io::Result> { + fs::read(path) + } } /// This is a [SourceFile] identifier that is used to correlate source files between @@ -220,9 +227,7 @@ impl SourceMap { /// Unlike `load_file`, guarantees that no normalization like BOM-removal /// takes place. pub fn load_binary_file(&self, path: &Path) -> io::Result> { - // Ideally, this should use `self.file_loader`, but it can't - // deal with binary files yet. - let bytes = fs::read(path)?; + let bytes = self.file_loader.read_binary_file(path)?; // We need to add file to the `SourceMap`, so that it is present // in dep-info. There's also an edge case that file might be both From ef807cb321604ae96ed0f78c78e3e9348d6d11ba Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Fri, 23 Dec 2022 14:59:42 +0000 Subject: [PATCH 126/362] use problem matchers for tidy CI --- src/ci/github-actions/problem_matchers.json | 15 +++++++++++++++ src/ci/scripts/run-build-from-ci.sh | 2 ++ 2 files changed, 17 insertions(+) create mode 100644 src/ci/github-actions/problem_matchers.json diff --git a/src/ci/github-actions/problem_matchers.json b/src/ci/github-actions/problem_matchers.json new file mode 100644 index 000000000000..37561924b7d4 --- /dev/null +++ b/src/ci/github-actions/problem_matchers.json @@ -0,0 +1,15 @@ +{ + "problemMatcher": [ + { + "owner": "tidy-error-file-line", + "pattern": [ + { + "regexp": "^tidy error: /checkout/(.+):(\\d+): (.+)$", + "file": 1, + "line": 2, + "message": 3 + } + ] + } + ] +} diff --git a/src/ci/scripts/run-build-from-ci.sh b/src/ci/scripts/run-build-from-ci.sh index c02117f459de..55e75800d91c 100755 --- a/src/ci/scripts/run-build-from-ci.sh +++ b/src/ci/scripts/run-build-from-ci.sh @@ -10,6 +10,8 @@ source "$(cd "$(dirname "$0")" && pwd)/../shared.sh" export CI="true" export SRC=. +echo "::add-matcher::src/ci/github-actions/problem_matchers.json" + # Remove any preexisting rustup installation since it can interfere # with the cargotest step and its auto-detection of things like Clippy in # the environment From a2040de541057a4d8d25c4d3f075f2e81276eef8 Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Fri, 6 Jan 2023 10:50:09 +0000 Subject: [PATCH 127/362] do not run tidy on x86_64-gnu-llvm-13 --- src/ci/docker/host-x86_64/x86_64-gnu-llvm-14/Dockerfile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-llvm-14/Dockerfile b/src/ci/docker/host-x86_64/x86_64-gnu-llvm-14/Dockerfile index db6032f87521..a007bf183ee1 100644 --- a/src/ci/docker/host-x86_64/x86_64-gnu-llvm-14/Dockerfile +++ b/src/ci/docker/host-x86_64/x86_64-gnu-llvm-14/Dockerfile @@ -62,6 +62,4 @@ ENV SCRIPT ../x.py --stage 2 test --exclude src/tools/tidy && \ # work. # ../x.ps1 --stage 2 test tests/ui --pass=check \ - --host='' --target=i686-unknown-linux-gnu && \ - # Run tidy at the very end, after all the other tests. - python2.7 ../x.py --stage 2 test src/tools/tidy + --host='' --target=i686-unknown-linux-gnu From 85ad8a6fdcd2b62b04219592966152196b0a98d7 Mon Sep 17 00:00:00 2001 From: Michael Schubart Date: Mon, 6 Mar 2023 07:16:36 +0000 Subject: [PATCH 128/362] Avoid false positives from extension traits --- clippy_lints/src/collection_is_never_read.rs | 6 ++++++ tests/ui/collection_is_never_read.rs | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/clippy_lints/src/collection_is_never_read.rs b/clippy_lints/src/collection_is_never_read.rs index c0c231bb2c79..10f2bef268a2 100644 --- a/clippy_lints/src/collection_is_never_read.rs +++ b/clippy_lints/src/collection_is_never_read.rs @@ -98,10 +98,16 @@ fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Bloc // Method call on `id` in a statement ignores any return value, so it's not a read access: // // id.foo(...); // Not reading `id`. + // + // Only assuming this for "official" methods defined on the type. For methods defined in extension + // traits (identified as local, based on the orphan rule), pessimistically assume that they might + // have side effects, so consider them a read. if let Some(Node::Expr(parent)) = get_parent_node(cx.tcx, expr.hir_id) && let ExprKind::MethodCall(_, receiver, _, _) = parent.kind && path_to_local_id(receiver, id) && let Some(Node::Stmt(..)) = get_parent_node(cx.tcx, parent.hir_id) + && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(parent.hir_id) + && !method_def_id.is_local() { return ControlFlow::Continue(()); } diff --git a/tests/ui/collection_is_never_read.rs b/tests/ui/collection_is_never_read.rs index 8f5ceb06b898..49c72e7eefec 100644 --- a/tests/ui/collection_is_never_read.rs +++ b/tests/ui/collection_is_never_read.rs @@ -133,3 +133,23 @@ fn not_read_if_return_value_not_used() { let x = vec![1, 2, 3]; // WARNING x.is_empty(); } + +fn extension_traits() { + trait VecExt { + fn method_with_side_effect(&self); + fn method_without_side_effect(&self); + } + + impl VecExt for Vec { + fn method_with_side_effect(&self) { + println!("my length: {}", self.len()); + } + fn method_without_side_effect(&self) {} + } + + let x = vec![1, 2, 3]; // Ok + x.method_with_side_effect(); + + let y = vec![1, 2, 3]; // Ok (false negative) + y.method_without_side_effect(); +} From b7e2b049f3ef7e082af832fa53ab6af0a45baa62 Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Mon, 6 Mar 2023 10:56:23 +0000 Subject: [PATCH 129/362] Querify registered_tools. --- compiler/rustc_expand/src/base.rs | 8 ++++---- compiler/rustc_interface/src/passes.rs | 4 +++- compiler/rustc_lint/src/levels.rs | 4 ++-- compiler/rustc_lint_defs/src/lib.rs | 4 +++- compiler/rustc_middle/src/arena.rs | 1 + compiler/rustc_middle/src/query/mod.rs | 5 +++++ compiler/rustc_middle/src/ty/mod.rs | 4 +--- compiler/rustc_resolve/src/lib.rs | 9 ++++++--- compiler/rustc_resolve/src/macros.rs | 18 +++++++++++------- 9 files changed, 36 insertions(+), 21 deletions(-) diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index 22bc90f5cac2..713e4fbbdce2 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -12,13 +12,13 @@ use rustc_ast::tokenstream::TokenStream; use rustc_ast::visit::{AssocCtxt, Visitor}; use rustc_ast::{self as ast, AttrVec, Attribute, HasAttrs, Item, NodeId, PatKind}; use rustc_attr::{self as attr, Deprecation, Stability}; -use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; +use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::sync::{self, Lrc}; use rustc_errors::{ Applicability, DiagnosticBuilder, ErrorGuaranteed, IntoDiagnostic, MultiSpan, PResult, }; use rustc_lint_defs::builtin::PROC_MACRO_BACK_COMPAT; -use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiagnostics}; +use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiagnostics, RegisteredTools}; use rustc_parse::{self, parser, MACRO_ARGUMENTS}; use rustc_session::errors::report_lit_error; use rustc_session::{parse::ParseSess, Limit, Session}; @@ -947,14 +947,14 @@ pub trait ResolverExpand { fn declare_proc_macro(&mut self, id: NodeId); /// Tools registered with `#![register_tool]` and used by tool attributes and lints. - fn registered_tools(&self) -> &FxHashSet; + fn registered_tools(&self) -> &RegisteredTools; } pub trait LintStoreExpand { fn pre_expansion_lint( &self, sess: &Session, - registered_tools: &FxHashSet, + registered_tools: &RegisteredTools, node_id: NodeId, attrs: &[Attribute], items: &[P], diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 81c1d665ef07..192ef6504749 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -178,7 +178,7 @@ fn configure_and_expand(mut krate: ast::Crate, resolver: &mut Resolver<'_, '_>) let sess = tcx.sess; let lint_store = unerased_lint_store(tcx); let crate_name = tcx.crate_name(LOCAL_CRATE); - pre_expansion_lint(sess, lint_store, resolver.registered_tools(), &krate, crate_name); + pre_expansion_lint(sess, lint_store, tcx.registered_tools(()), &krate, crate_name); rustc_builtin_macros::register_builtin_macros(resolver); krate = sess.time("crate_injection", || { @@ -557,6 +557,7 @@ fn resolver_for_lowering<'tcx>( (): (), ) -> &'tcx Steal<(ty::ResolverAstLowering, Lrc)> { let arenas = Resolver::arenas(); + let _ = tcx.registered_tools(()); // Uses `crate_for_resolver`. let krate = tcx.crate_for_resolver(()).steal(); let mut resolver = Resolver::new(tcx, &krate, &arenas); let krate = configure_and_expand(krate, &mut resolver); @@ -637,6 +638,7 @@ pub static DEFAULT_QUERY_PROVIDERS: LazyLock = LazyLock::new(|| { rustc_mir_transform::provide(providers); rustc_monomorphize::provide(providers); rustc_privacy::provide(providers); + rustc_resolve::provide(providers); rustc_hir_analysis::provide(providers); rustc_hir_typeck::provide(providers); ty::provide(providers); diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs index bc7488fab4a5..a76229dd3524 100644 --- a/compiler/rustc_lint/src/levels.rs +++ b/compiler/rustc_lint/src/levels.rs @@ -128,7 +128,7 @@ fn lint_expectations(tcx: TyCtxt<'_>, (): ()) -> Vec<(LintExpectationId, LintExp }, warn_about_weird_lints: false, store, - registered_tools: &tcx.resolutions(()).registered_tools, + registered_tools: &tcx.registered_tools(()), }; builder.add_command_line(); @@ -156,7 +156,7 @@ fn shallow_lint_levels_on(tcx: TyCtxt<'_>, owner: hir::OwnerId) -> ShallowLintLe }, warn_about_weird_lints: false, store, - registered_tools: &tcx.resolutions(()).registered_tools, + registered_tools: &tcx.registered_tools(()), }; if owner == hir::CRATE_OWNER_ID { diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index 6efbf5ce9eef..8736b5267df4 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -8,7 +8,7 @@ extern crate rustc_macros; pub use self::Level::*; use rustc_ast::node_id::NodeId; use rustc_ast::{AttrId, Attribute}; -use rustc_data_structures::fx::FxIndexMap; +use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_data_structures::stable_hasher::{HashStable, StableHasher, ToStableHashKey}; use rustc_error_messages::{DiagnosticMessage, MultiSpan}; use rustc_hir::HashStableContext; @@ -594,6 +594,8 @@ impl LintBuffer { } } +pub type RegisteredTools = FxIndexSet; + /// Declares a static item of type `&'static Lint`. /// /// See for diff --git a/compiler/rustc_middle/src/arena.rs b/compiler/rustc_middle/src/arena.rs index d4019b5bf17e..98b976176ffc 100644 --- a/compiler/rustc_middle/src/arena.rs +++ b/compiler/rustc_middle/src/arena.rs @@ -107,6 +107,7 @@ macro_rules! arena_types { // (during lowering) and the `librustc_middle` arena (for decoding MIR) [decode] asm_template: rustc_ast::InlineAsmTemplatePiece, [decode] used_trait_imports: rustc_data_structures::unord::UnordSet, + [decode] registered_tools: rustc_middle::ty::RegisteredTools, [decode] is_late_bound_map: rustc_data_structures::fx::FxIndexSet, [decode] impl_source: rustc_middle::traits::ImplSource<'tcx, ()>, diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index d4435a54b4ab..b69e227cfbed 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -26,6 +26,11 @@ rustc_queries! { desc { "triggering a delay span bug" } } + query registered_tools(_: ()) -> &'tcx ty::RegisteredTools { + arena_cache + desc { "compute registered tools for crate" } + } + query resolutions(_: ()) -> &'tcx ty::ResolverGlobalCtxt { feedable no_hash diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index 17262a0be243..bfa50550cf6d 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -44,6 +44,7 @@ use rustc_index::vec::IndexVec; use rustc_macros::HashStable; use rustc_query_system::ich::StableHashingContext; use rustc_serialize::{Decodable, Encodable}; +pub use rustc_session::lint::RegisteredTools; use rustc_span::hygiene::MacroKind; use rustc_span::symbol::{kw, sym, Ident, Symbol}; use rustc_span::{ExpnId, ExpnKind, Span}; @@ -148,8 +149,6 @@ mod typeck_results; // Data types -pub type RegisteredTools = FxHashSet; - pub struct ResolverOutputs { pub global_ctxt: ResolverGlobalCtxt, pub ast_lowering: ResolverAstLowering, @@ -179,7 +178,6 @@ pub struct ResolverGlobalCtxt { /// Mapping from ident span to path span for paths that don't exist as written, but that /// exist under `std`. For example, wrote `str::from_utf8` instead of `std::str::from_utf8`. pub confused_type_with_std_module: FxHashMap, - pub registered_tools: RegisteredTools, pub doc_link_resolutions: FxHashMap, pub doc_link_traits_in_scope: FxHashMap>, pub all_macro_rules: FxHashMap>, diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 7a1f14f71f21..e9fe4c09a9f3 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -966,7 +966,7 @@ pub struct Resolver<'a, 'tcx> { /// A small map keeping true kinds of built-in macros that appear to be fn-like on /// the surface (`macro` items in libcore), but are actually attributes or derives. builtin_macro_kinds: FxHashMap, - registered_tools: RegisteredTools, + registered_tools: &'tcx RegisteredTools, macro_use_prelude: FxHashMap>, macro_map: FxHashMap, dummy_ext_bang: Lrc, @@ -1241,7 +1241,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { } } - let registered_tools = macros::registered_tools(tcx.sess, &krate.attrs); + let registered_tools = tcx.registered_tools(()); let features = tcx.sess.features_untracked(); @@ -1424,7 +1424,6 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { trait_impls: self.trait_impls, proc_macros, confused_type_with_std_module, - registered_tools: self.registered_tools, doc_link_resolutions: self.doc_link_resolutions, doc_link_traits_in_scope: self.doc_link_traits_in_scope, all_macro_rules: self.all_macro_rules, @@ -2056,3 +2055,7 @@ impl Finalize { Finalize { node_id, path_span, root_span, report_private: true } } } + +pub fn provide(providers: &mut ty::query::Providers) { + providers.registered_tools = macros::registered_tools; +} diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index b38c11e8bb8d..37153854f7e7 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -8,7 +8,6 @@ use crate::{ModuleKind, ModuleOrUniformRoot, NameBinding, PathResult, Segment}; use rustc_ast::{self as ast, Inline, ItemKind, ModKind, NodeId}; use rustc_ast_pretty::pprust; use rustc_attr::StabilityLevel; -use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::intern::Interned; use rustc_data_structures::sync::Lrc; use rustc_errors::{struct_span_err, Applicability}; @@ -20,11 +19,11 @@ use rustc_hir::def::{self, DefKind, NonMacroAttrKind}; use rustc_hir::def_id::{CrateNum, LocalDefId}; use rustc_middle::middle::stability; use rustc_middle::ty::RegisteredTools; +use rustc_middle::ty::TyCtxt; use rustc_session::lint::builtin::{LEGACY_DERIVE_HELPERS, SOFT_UNSTABLE}; use rustc_session::lint::builtin::{UNUSED_MACROS, UNUSED_MACRO_RULES}; use rustc_session::lint::BuiltinLintDiagnostics; use rustc_session::parse::feature_err; -use rustc_session::Session; use rustc_span::edition::Edition; use rustc_span::hygiene::{self, ExpnData, ExpnKind, LocalExpnId}; use rustc_span::hygiene::{AstPass, MacroKind}; @@ -111,15 +110,17 @@ fn fast_print_path(path: &ast::Path) -> Symbol { } } -pub(crate) fn registered_tools(sess: &Session, attrs: &[ast::Attribute]) -> FxHashSet { - let mut registered_tools = FxHashSet::default(); - for attr in sess.filter_by_name(attrs, sym::register_tool) { +pub(crate) fn registered_tools(tcx: TyCtxt<'_>, (): ()) -> RegisteredTools { + let mut registered_tools = RegisteredTools::default(); + let krate = tcx.crate_for_resolver(()).borrow(); + for attr in tcx.sess.filter_by_name(&krate.attrs, sym::register_tool) { for nested_meta in attr.meta_item_list().unwrap_or_default() { match nested_meta.ident() { Some(ident) => { if let Some(old_ident) = registered_tools.replace(ident) { let msg = format!("{} `{}` was already registered", "tool", ident); - sess.struct_span_err(ident.span, &msg) + tcx.sess + .struct_span_err(ident.span, &msg) .span_label(old_ident.span, "already registered here") .emit(); } @@ -127,7 +128,10 @@ pub(crate) fn registered_tools(sess: &Session, attrs: &[ast::Attribute]) -> FxHa None => { let msg = format!("`{}` only accepts identifiers", sym::register_tool); let span = nested_meta.span(); - sess.struct_span_err(span, &msg).span_label(span, "not an identifier").emit(); + tcx.sess + .struct_span_err(span, &msg) + .span_label(span, "not an identifier") + .emit(); } } } From c90fc105cba334c37b2773a054d4f234b501b481 Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Mon, 6 Mar 2023 10:56:53 +0000 Subject: [PATCH 130/362] Querify early_lint_checks. --- compiler/rustc_ast_lowering/src/lib.rs | 1 + compiler/rustc_driver_impl/src/lib.rs | 1 + compiler/rustc_interface/src/passes.rs | 41 +++++++++++++++----------- compiler/rustc_lint_defs/src/lib.rs | 3 +- compiler/rustc_middle/src/query/mod.rs | 4 +++ compiler/rustc_middle/src/ty/mod.rs | 5 ++++ compiler/rustc_resolve/src/lib.rs | 2 ++ 7 files changed, 38 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 5d78d914b6d7..91fcb5d266a9 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -436,6 +436,7 @@ fn compute_hir_hash( pub fn lower_to_hir(tcx: TyCtxt<'_>, (): ()) -> hir::Crate<'_> { let sess = tcx.sess; tcx.ensure().output_filenames(()); + let _ = tcx.early_lint_checks(()); // Borrows `resolver_for_lowering`. let (mut resolver, krate) = tcx.resolver_for_lowering(()).steal(); let ast_index = index_crate(&resolver.node_id_to_def_id, &krate); diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 464ddae476a5..e321a9847ba0 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -331,6 +331,7 @@ fn run_compiler( if let Some(ppm) = &sess.opts.pretty { if ppm.needs_ast_map() { queries.global_ctxt()?.enter(|tcx| { + tcx.ensure().early_lint_checks(()); pretty::print_after_hir_lowering(tcx, *ppm); Ok(()) })?; diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 192ef6504749..4a02981f9543 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -11,7 +11,7 @@ use rustc_data_structures::parallel; use rustc_data_structures::steal::Steal; use rustc_data_structures::sync::{Lrc, OnceCell, WorkerLocal}; use rustc_errors::PResult; -use rustc_expand::base::{ExtCtxt, LintStoreExpand, ResolverExpand}; +use rustc_expand::base::{ExtCtxt, LintStoreExpand}; use rustc_hir::def_id::{StableCrateId, LOCAL_CRATE}; use rustc_lint::{unerased_lint_store, BufferedEarlyLint, EarlyCheckNode, LintStore}; use rustc_metadata::creader::CStore; @@ -302,6 +302,16 @@ fn configure_and_expand(mut krate: ast::Crate, resolver: &mut Resolver<'_, '_>) // Done with macro expansion! + resolver.resolve_crate(&krate); + + krate +} + +fn early_lint_checks(tcx: TyCtxt<'_>, (): ()) { + let sess = tcx.sess; + let (resolver, krate) = &*tcx.resolver_for_lowering(()).borrow(); + let mut lint_buffer = resolver.lint_buffer.steal(); + if sess.opts.unstable_opts.input_stats { eprintln!("Post-expansion node count: {}", count_nodes(&krate)); } @@ -310,8 +320,6 @@ fn configure_and_expand(mut krate: ast::Crate, resolver: &mut Resolver<'_, '_>) hir_stats::print_ast_stats(&krate, "POST EXPANSION AST STATS", "ast-stats-2"); } - resolver.resolve_crate(&krate); - // Needs to go *after* expansion to be able to check the results of macro expansion. sess.time("complete_gated_feature_checking", || { rustc_ast_passes::feature_gate::check_crate(&krate, sess); @@ -321,7 +329,7 @@ fn configure_and_expand(mut krate: ast::Crate, resolver: &mut Resolver<'_, '_>) sess.parse_sess.buffered_lints.with_lock(|buffered_lints| { info!("{} parse sess buffered_lints", buffered_lints.len()); for early_lint in buffered_lints.drain(..) { - resolver.lint_buffer().add_early_lint(early_lint); + lint_buffer.add_early_lint(early_lint); } }); @@ -340,20 +348,16 @@ fn configure_and_expand(mut krate: ast::Crate, resolver: &mut Resolver<'_, '_>) } }); - sess.time("early_lint_checks", || { - let lint_buffer = Some(std::mem::take(resolver.lint_buffer())); - rustc_lint::check_ast_node( - sess, - false, - lint_store, - resolver.registered_tools(), - lint_buffer, - rustc_lint::BuiltinCombinedEarlyLintPass::new(), - &krate, - ) - }); - - krate + let lint_store = unerased_lint_store(tcx); + rustc_lint::check_ast_node( + sess, + false, + lint_store, + tcx.registered_tools(()), + Some(lint_buffer), + rustc_lint::BuiltinCombinedEarlyLintPass::new(), + &**krate, + ) } // Returns all the paths that correspond to generated files. @@ -630,6 +634,7 @@ pub static DEFAULT_QUERY_PROVIDERS: LazyLock = LazyLock::new(|| { providers.hir_crate = rustc_ast_lowering::lower_to_hir; providers.output_filenames = output_filenames; providers.resolver_for_lowering = resolver_for_lowering; + providers.early_lint_checks = early_lint_checks; proc_macro_decls::provide(providers); rustc_const_eval::provide(providers); rustc_middle::hir::provide(providers); diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index 8736b5267df4..5e3225b74ac0 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -526,6 +526,7 @@ pub enum BuiltinLintDiagnostics { /// Lints that are buffered up early on in the `Session` before the /// `LintLevels` is calculated. +#[derive(Debug)] pub struct BufferedEarlyLint { /// The span of code that we are linting on. pub span: MultiSpan, @@ -544,7 +545,7 @@ pub struct BufferedEarlyLint { pub diagnostic: BuiltinLintDiagnostics, } -#[derive(Default)] +#[derive(Default, Debug)] pub struct LintBuffer { pub map: FxIndexMap>, } diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index b69e227cfbed..05f6531a7c4b 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -31,6 +31,10 @@ rustc_queries! { desc { "compute registered tools for crate" } } + query early_lint_checks(_: ()) -> () { + desc { "perform lints prior to macro expansion" } + } + query resolutions(_: ()) -> &'tcx ty::ResolverGlobalCtxt { feedable no_hash diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index bfa50550cf6d..9894baa9e18c 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -34,6 +34,7 @@ use rustc_attr as attr; use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}; use rustc_data_structures::intern::Interned; use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; +use rustc_data_structures::steal::Steal; use rustc_data_structures::tagged_ptr::CopyTaggedPtr; use rustc_errors::ErrorGuaranteed; use rustc_hir as hir; @@ -44,6 +45,7 @@ use rustc_index::vec::IndexVec; use rustc_macros::HashStable; use rustc_query_system::ich::StableHashingContext; use rustc_serialize::{Decodable, Encodable}; +use rustc_session::lint::LintBuffer; pub use rustc_session::lint::RegisteredTools; use rustc_span::hygiene::MacroKind; use rustc_span::symbol::{kw, sym, Ident, Symbol}; @@ -211,6 +213,9 @@ pub struct ResolverAstLowering { pub builtin_macro_kinds: FxHashMap, /// List functions and methods for which lifetime elision was successful. pub lifetime_elision_allowed: FxHashSet, + + /// Lints that were emitted by the resolver and early lints. + pub lint_buffer: Steal, } #[derive(Clone, Copy, Debug)] diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index e9fe4c09a9f3..13d4c01f2599 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -27,6 +27,7 @@ use rustc_ast::{self as ast, NodeId, CRATE_NODE_ID}; use rustc_ast::{AngleBracketedArg, Crate, Expr, ExprKind, GenericArg, GenericArgs, LitKind, Path}; use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}; use rustc_data_structures::intern::Interned; +use rustc_data_structures::steal::Steal; use rustc_data_structures::sync::{Lrc, MappedReadGuard}; use rustc_errors::{ Applicability, DiagnosticBuilder, DiagnosticMessage, ErrorGuaranteed, SubdiagnosticMessage, @@ -1441,6 +1442,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { trait_map: self.trait_map, builtin_macro_kinds: self.builtin_macro_kinds, lifetime_elision_allowed: self.lifetime_elision_allowed, + lint_buffer: Steal::new(self.lint_buffer), }; ResolverOutputs { global_ctxt, ast_lowering } } From c2238527e69faa9f922fb5d3fde9494395b299ff Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 3 Mar 2023 18:27:25 -0300 Subject: [PATCH 131/362] Add tcx::lower_impl_trait_in_trait_to_assoc_ty to avoid accessing through sess.opts.unstable_opts --- compiler/rustc_metadata/src/rmeta/encoder.rs | 2 +- compiler/rustc_middle/src/ty/context.rs | 4 ++++ compiler/rustc_ty_utils/src/assoc.rs | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 3ab01f7809b0..238142a48452 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -1104,7 +1104,7 @@ fn should_encode_const(def_kind: DefKind) -> bool { // We only encode impl trait in trait when using `lower-impl-trait-in-trait-to-assoc-ty` unstable // option. fn should_encode_fn_impl_trait_in_trait<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> bool { - if tcx.sess.opts.unstable_opts.lower_impl_trait_in_trait_to_assoc_ty + if tcx.lower_impl_trait_in_trait_to_assoc_ty() && let Some(assoc_item) = tcx.opt_associated_item(def_id) && assoc_item.container == ty::AssocItemContainer::TraitContainer && assoc_item.kind == ty::AssocKind::Fn diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index 70091477e39f..f87bc45e4f28 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -2439,6 +2439,10 @@ impl<'tcx> TyCtxt<'tcx> { pub fn trait_solver_next(self) -> bool { self.sess.opts.unstable_opts.trait_solver == rustc_session::config::TraitSolver::Next } + + pub fn lower_impl_trait_in_trait_to_assoc_ty(self) -> bool { + self.sess.opts.unstable_opts.lower_impl_trait_in_trait_to_assoc_ty + } } impl<'tcx> TyCtxtAt<'tcx> { diff --git a/compiler/rustc_ty_utils/src/assoc.rs b/compiler/rustc_ty_utils/src/assoc.rs index 2fc55a2527dd..050fd5660493 100644 --- a/compiler/rustc_ty_utils/src/assoc.rs +++ b/compiler/rustc_ty_utils/src/assoc.rs @@ -23,7 +23,7 @@ fn associated_item_def_ids(tcx: TyCtxt<'_>, def_id: DefId) -> &[DefId] { let item = tcx.hir().expect_item(def_id.expect_local()); match item.kind { hir::ItemKind::Trait(.., ref trait_item_refs) => { - if tcx.sess.opts.unstable_opts.lower_impl_trait_in_trait_to_assoc_ty { + if tcx.lower_impl_trait_in_trait_to_assoc_ty() { // We collect RPITITs for each trait method's return type and create a // corresponding associated item using associated_items_for_impl_trait_in_trait // query. @@ -54,7 +54,7 @@ fn associated_item_def_ids(tcx: TyCtxt<'_>, def_id: DefId) -> &[DefId] { } } hir::ItemKind::Impl(ref impl_) => { - if tcx.sess.opts.unstable_opts.lower_impl_trait_in_trait_to_assoc_ty { + if tcx.lower_impl_trait_in_trait_to_assoc_ty() { // We collect RPITITs for each trait method's return type, on the impl side too and // create a corresponding associated item using // associated_items_for_impl_trait_in_trait query. From 61ad6a96ad1019930294e754e72f627978e85787 Mon Sep 17 00:00:00 2001 From: hkalbasi Date: Sun, 19 Feb 2023 00:02:55 +0330 Subject: [PATCH 132/362] Add BindingId --- crates/hir-def/src/body.rs | 25 +++- crates/hir-def/src/body/lower.rs | 117 ++++++++++++------ crates/hir-def/src/body/pretty.rs | 23 ++-- crates/hir-def/src/body/scope.rs | 39 +++--- crates/hir-def/src/expr.rs | 12 +- crates/hir-def/src/resolver.rs | 14 +-- crates/hir-ty/src/consteval/tests.rs | 43 +++++++ crates/hir-ty/src/diagnostics/decl_check.rs | 4 +- crates/hir-ty/src/diagnostics/match_check.rs | 3 +- crates/hir-ty/src/infer.rs | 20 ++- crates/hir-ty/src/infer/pat.rs | 27 ++-- crates/hir-ty/src/infer/path.rs | 2 +- crates/hir-ty/src/layout/tests.rs | 12 +- crates/hir-ty/src/mir/lower.rs | 96 ++++++++++---- crates/hir/src/from_id.rs | 8 +- crates/hir/src/lib.rs | 99 +++++++++------ crates/hir/src/semantics.rs | 4 +- crates/hir/src/semantics/source_to_def.rs | 21 ++-- crates/hir/src/source_analyzer.rs | 8 +- .../src/handlers/convert_match_to_let_else.rs | 2 +- .../src/handlers/extract_function.rs | 25 ++-- .../src/handlers/inline_local_variable.rs | 11 +- crates/ide-db/src/rename.rs | 105 ++++++++-------- crates/ide-db/src/search.rs | 8 +- crates/ide/src/highlight_related.rs | 57 +++++---- crates/ide/src/hover/render.rs | 6 +- crates/ide/src/navigation_target.rs | 20 ++- 27 files changed, 514 insertions(+), 297 deletions(-) diff --git a/crates/hir-def/src/body.rs b/crates/hir-def/src/body.rs index 8fd9255b8b13..545d2bebf5f0 100644 --- a/crates/hir-def/src/body.rs +++ b/crates/hir-def/src/body.rs @@ -24,7 +24,7 @@ use syntax::{ast, AstPtr, SyntaxNode, SyntaxNodePtr}; use crate::{ attr::Attrs, db::DefDatabase, - expr::{dummy_expr_id, Expr, ExprId, Label, LabelId, Pat, PatId}, + expr::{dummy_expr_id, Binding, BindingId, Expr, ExprId, Label, LabelId, Pat, PatId}, item_scope::BuiltinShadowMode, macro_id_to_def_id, nameres::DefMap, @@ -270,6 +270,7 @@ pub struct Mark { pub struct Body { pub exprs: Arena, pub pats: Arena, + pub bindings: Arena, pub or_pats: FxHashMap>, pub labels: Arena