From 91405ab74a4db7993855071511a02bb25a66a628 Mon Sep 17 00:00:00 2001 From: ltdk Date: Wed, 6 Sep 2023 23:24:23 -0400 Subject: [PATCH 01/32] Clean up unchecked_math, separate out unchecked_shifts --- library/core/src/lib.rs | 3 +- library/core/src/num/int_macros.rs | 43 +++++++++++++++---- library/core/src/num/uint_macros.rs | 18 ++++---- .../tests/fail/intrinsics/unchecked_shl.rs | 2 +- .../tests/fail/intrinsics/unchecked_shr.rs | 2 +- tests/codegen/unchecked_shifts.rs | 2 +- tests/mir-opt/inline/unchecked_shifts.rs | 2 +- 7 files changed, 49 insertions(+), 23 deletions(-) diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index 906421327cbc5..881be8914fe9c 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -136,7 +136,6 @@ #![feature(const_hash)] #![feature(const_heap)] #![feature(const_index_range_slice_index)] -#![feature(const_inherent_unchecked_arith)] #![feature(const_int_unchecked_arith)] #![feature(const_intrinsic_forget)] #![feature(const_ipv4)] @@ -190,6 +189,8 @@ #![feature(str_split_inclusive_remainder)] #![feature(str_split_remainder)] #![feature(strict_provenance)] +#![feature(unchecked_math)] +#![feature(unchecked_shifts)] #![feature(utf16_extra)] #![feature(utf16_extra_const)] #![feature(variant_count)] diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index 3cbb55af3bc66..fd01f1b261012 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -471,7 +471,7 @@ macro_rules! int_impl { )] #[must_use = "this returns the result of the operation, \ without modifying the original"] - #[rustc_const_unstable(feature = "const_inherent_unchecked_arith", issue = "85122")] + #[rustc_const_unstable(feature = "unchecked_math", issue = "85122")] #[inline(always)] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub const unsafe fn unchecked_add(self, rhs: Self) -> Self { @@ -539,7 +539,7 @@ macro_rules! int_impl { )] #[must_use = "this returns the result of the operation, \ without modifying the original"] - #[rustc_const_unstable(feature = "const_inherent_unchecked_arith", issue = "85122")] + #[rustc_const_unstable(feature = "unchecked_math", issue = "85122")] #[inline(always)] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub const unsafe fn unchecked_sub(self, rhs: Self) -> Self { @@ -607,7 +607,7 @@ macro_rules! int_impl { )] #[must_use = "this returns the result of the operation, \ without modifying the original"] - #[rustc_const_unstable(feature = "const_inherent_unchecked_arith", issue = "85122")] + #[rustc_const_unstable(feature = "unchecked_math", issue = "85122")] #[inline(always)] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub const unsafe fn unchecked_mul(self, rhs: Self) -> Self { @@ -740,6 +740,31 @@ macro_rules! int_impl { if unlikely!(b) {None} else {Some(a)} } + /// Unchecked negation. Computes `-self`, assuming overflow cannot occur. + /// + /// # Safety + /// + /// This results in undefined behavior when + #[doc = concat!("`self == ", stringify!($SelfT), "::MIN`,")] + /// i.e. when [`checked_neg`] would return `None`. + /// + #[doc = concat!("[`checked_neg`]: ", stringify!($SelfT), "::checked_neg")] + #[unstable( + feature = "unchecked_neg", + reason = "niche optimization path", + issue = "85122", + )] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[rustc_const_unstable(feature = "unchecked_neg", issue = "85122")] + #[inline(always)] + #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces + pub const unsafe fn unchecked_neg(self) -> Self { + // SAFETY: the caller must uphold the safety contract for + // `unchecked_neg`. + unsafe { intrinsics::unchecked_sub(0, self) } + } + /// Checked shift left. Computes `self << rhs`, returning `None` if `rhs` is larger /// than or equal to the number of bits in `self`. /// @@ -772,13 +797,13 @@ macro_rules! int_impl { /// #[doc = concat!("[`checked_shl`]: ", stringify!($SelfT), "::checked_shl")] #[unstable( - feature = "unchecked_math", + feature = "unchecked_shifts", reason = "niche optimization path", issue = "85122", )] #[must_use = "this returns the result of the operation, \ without modifying the original"] - #[rustc_const_unstable(feature = "const_inherent_unchecked_arith", issue = "85122")] + #[rustc_const_unstable(feature = "unchecked_shifts", issue = "85122")] #[inline(always)] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub const unsafe fn unchecked_shl(self, rhs: u32) -> Self { @@ -820,13 +845,13 @@ macro_rules! int_impl { /// #[doc = concat!("[`checked_shr`]: ", stringify!($SelfT), "::checked_shr")] #[unstable( - feature = "unchecked_math", + feature = "unchecked_shifts", reason = "niche optimization path", issue = "85122", )] #[must_use = "this returns the result of the operation, \ without modifying the original"] - #[rustc_const_unstable(feature = "const_inherent_unchecked_arith", issue = "85122")] + #[rustc_const_unstable(feature = "unchecked_shifts", issue = "85122")] #[inline(always)] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub const unsafe fn unchecked_shr(self, rhs: u32) -> Self { @@ -1404,7 +1429,7 @@ macro_rules! int_impl { #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline(always)] - #[rustc_allow_const_fn_unstable(const_inherent_unchecked_arith)] + #[rustc_allow_const_fn_unstable(unchecked_shifts)] pub const fn wrapping_shl(self, rhs: u32) -> Self { // SAFETY: the masking by the bitsize of the type ensures that we do not shift // out of bounds @@ -1434,7 +1459,7 @@ macro_rules! int_impl { #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline(always)] - #[rustc_allow_const_fn_unstable(const_inherent_unchecked_arith)] + #[rustc_allow_const_fn_unstable(unchecked_shifts)] pub const fn wrapping_shr(self, rhs: u32) -> Self { // SAFETY: the masking by the bitsize of the type ensures that we do not shift // out of bounds diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index a9c5312a1c02b..11a53aaf122ec 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -479,7 +479,7 @@ macro_rules! uint_impl { )] #[must_use = "this returns the result of the operation, \ without modifying the original"] - #[rustc_const_unstable(feature = "const_inherent_unchecked_arith", issue = "85122")] + #[rustc_const_unstable(feature = "unchecked_math", issue = "85122")] #[inline(always)] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub const unsafe fn unchecked_add(self, rhs: Self) -> Self { @@ -548,7 +548,7 @@ macro_rules! uint_impl { )] #[must_use = "this returns the result of the operation, \ without modifying the original"] - #[rustc_const_unstable(feature = "const_inherent_unchecked_arith", issue = "85122")] + #[rustc_const_unstable(feature = "unchecked_math", issue = "85122")] #[inline(always)] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub const unsafe fn unchecked_sub(self, rhs: Self) -> Self { @@ -595,7 +595,7 @@ macro_rules! uint_impl { )] #[must_use = "this returns the result of the operation, \ without modifying the original"] - #[rustc_const_unstable(feature = "const_inherent_unchecked_arith", issue = "85122")] + #[rustc_const_unstable(feature = "unchecked_math", issue = "85122")] #[inline(always)] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub const unsafe fn unchecked_mul(self, rhs: Self) -> Self { @@ -926,13 +926,13 @@ macro_rules! uint_impl { /// #[doc = concat!("[`checked_shl`]: ", stringify!($SelfT), "::checked_shl")] #[unstable( - feature = "unchecked_math", + feature = "unchecked_shifts", reason = "niche optimization path", issue = "85122", )] #[must_use = "this returns the result of the operation, \ without modifying the original"] - #[rustc_const_unstable(feature = "const_inherent_unchecked_arith", issue = "85122")] + #[rustc_const_unstable(feature = "unchecked_shifts", issue = "85122")] #[inline(always)] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub const unsafe fn unchecked_shl(self, rhs: u32) -> Self { @@ -974,13 +974,13 @@ macro_rules! uint_impl { /// #[doc = concat!("[`checked_shr`]: ", stringify!($SelfT), "::checked_shr")] #[unstable( - feature = "unchecked_math", + feature = "unchecked_shifts", reason = "niche optimization path", issue = "85122", )] #[must_use = "this returns the result of the operation, \ without modifying the original"] - #[rustc_const_unstable(feature = "const_inherent_unchecked_arith", issue = "85122")] + #[rustc_const_unstable(feature = "unchecked_shifts", issue = "85122")] #[inline(always)] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub const unsafe fn unchecked_shr(self, rhs: u32) -> Self { @@ -1418,7 +1418,7 @@ macro_rules! uint_impl { #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline(always)] - #[rustc_allow_const_fn_unstable(const_inherent_unchecked_arith)] + #[rustc_allow_const_fn_unstable(unchecked_shifts)] pub const fn wrapping_shl(self, rhs: u32) -> Self { // SAFETY: the masking by the bitsize of the type ensures that we do not shift // out of bounds @@ -1451,7 +1451,7 @@ macro_rules! uint_impl { #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline(always)] - #[rustc_allow_const_fn_unstable(const_inherent_unchecked_arith)] + #[rustc_allow_const_fn_unstable(unchecked_shifts)] pub const fn wrapping_shr(self, rhs: u32) -> Self { // SAFETY: the masking by the bitsize of the type ensures that we do not shift // out of bounds diff --git a/src/tools/miri/tests/fail/intrinsics/unchecked_shl.rs b/src/tools/miri/tests/fail/intrinsics/unchecked_shl.rs index 4554d0cb82ba0..abb345938fa0e 100644 --- a/src/tools/miri/tests/fail/intrinsics/unchecked_shl.rs +++ b/src/tools/miri/tests/fail/intrinsics/unchecked_shl.rs @@ -1,4 +1,4 @@ -#![feature(unchecked_math)] +#![feature(unchecked_shifts)] fn main() { unsafe { diff --git a/src/tools/miri/tests/fail/intrinsics/unchecked_shr.rs b/src/tools/miri/tests/fail/intrinsics/unchecked_shr.rs index fe2e85be69868..cdc10185e4707 100644 --- a/src/tools/miri/tests/fail/intrinsics/unchecked_shr.rs +++ b/src/tools/miri/tests/fail/intrinsics/unchecked_shr.rs @@ -1,4 +1,4 @@ -#![feature(unchecked_math)] +#![feature(unchecked_shifts)] fn main() { unsafe { diff --git a/tests/codegen/unchecked_shifts.rs b/tests/codegen/unchecked_shifts.rs index d5f53bedd5434..aca9bec77dfeb 100644 --- a/tests/codegen/unchecked_shifts.rs +++ b/tests/codegen/unchecked_shifts.rs @@ -2,7 +2,7 @@ // ignore-debug (because unchecked is checked in debug) #![crate_type = "lib"] -#![feature(unchecked_math)] +#![feature(unchecked_shifts)] // CHECK-LABEL: @unchecked_shl_unsigned_same #[no_mangle] diff --git a/tests/mir-opt/inline/unchecked_shifts.rs b/tests/mir-opt/inline/unchecked_shifts.rs index 22f84e44a6411..ffb7b079ce247 100644 --- a/tests/mir-opt/inline/unchecked_shifts.rs +++ b/tests/mir-opt/inline/unchecked_shifts.rs @@ -1,6 +1,6 @@ // EMIT_MIR_FOR_EACH_PANIC_STRATEGY #![crate_type = "lib"] -#![feature(unchecked_math)] +#![feature(unchecked_shifts)] // ignore-debug: the debug assertions prevent the inlining we are testing for // compile-flags: -Zmir-opt-level=2 -Zinline-mir From 62fe807e3c6cec9ae31a0a3e7e1f5bc694c2e30c Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 20 Oct 2023 11:10:30 -0700 Subject: [PATCH 02/32] FileCheck asm_unwind --- tests/mir-opt/inline/asm_unwind.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/mir-opt/inline/asm_unwind.rs b/tests/mir-opt/inline/asm_unwind.rs index 0cf21fda72fac..596a4592f25ff 100644 --- a/tests/mir-opt/inline/asm_unwind.rs +++ b/tests/mir-opt/inline/asm_unwind.rs @@ -1,4 +1,3 @@ -// skip-filecheck // Tests inlining of `may_unwind` inline assembly. // // EMIT_MIR_FOR_EACH_PANIC_STRATEGY @@ -20,5 +19,7 @@ fn foo() { // EMIT_MIR asm_unwind.main.Inline.diff pub fn main() { + // CHECK-LABEL: fn main( + // CHECK: (inlined foo) foo(); } From 2d0a34bb78f5546a0a7d7b447ffe4fa1c1ea13d9 Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 20 Oct 2023 11:11:06 -0700 Subject: [PATCH 03/32] FileCheck caller_with_trivial_bound --- tests/mir-opt/inline/caller_with_trivial_bound.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/mir-opt/inline/caller_with_trivial_bound.rs b/tests/mir-opt/inline/caller_with_trivial_bound.rs index 3829cbdd30240..722c7f6724efd 100644 --- a/tests/mir-opt/inline/caller_with_trivial_bound.rs +++ b/tests/mir-opt/inline/caller_with_trivial_bound.rs @@ -1,4 +1,3 @@ -// skip-filecheck // EMIT_MIR_FOR_EACH_PANIC_STRATEGY // needs-unwind @@ -18,6 +17,8 @@ pub fn foo() where IntFactory: Factory, { + // CHECK-LABEL: fn foo( + // CHECK-NOT: (inlined bar::) let mut x: >::Item = bar::(); } From 2f9aa7da9fefdd028d09e4c7450994b3323b2cfa Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 20 Oct 2023 11:11:22 -0700 Subject: [PATCH 04/32] FileCheck cycle --- tests/mir-opt/inline/cycle.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/mir-opt/inline/cycle.rs b/tests/mir-opt/inline/cycle.rs index 3e4f06834358e..8716b44b3dc02 100644 --- a/tests/mir-opt/inline/cycle.rs +++ b/tests/mir-opt/inline/cycle.rs @@ -1,20 +1,27 @@ -// skip-filecheck // EMIT_MIR_FOR_EACH_PANIC_STRATEGY // compile-flags: -Zinline-mir-hint-threshold=1000 // EMIT_MIR cycle.f.Inline.diff #[inline(always)] fn f(g: impl Fn()) { + // CHECK-LABEL: fn f( + // CHECK-NOT: inlined g(); } // EMIT_MIR cycle.g.Inline.diff #[inline(always)] fn g() { + // CHECK-LABEL: fn g( + // CHECK: (inlined f::) + // CHECK-NOT: inlined f(main); } // EMIT_MIR cycle.main.Inline.diff fn main() { + // CHECK-LABEL: fn main( + // CHECK: (inlined f::) + // CHECK-NOT: inlined f(g); } From 9b3f5e15277a2089140db89704277a682374efba Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 20 Oct 2023 11:11:37 -0700 Subject: [PATCH 05/32] FileCheck dont_ice_on_generic_rust_call --- tests/mir-opt/inline/dont_ice_on_generic_rust_call.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/mir-opt/inline/dont_ice_on_generic_rust_call.rs b/tests/mir-opt/inline/dont_ice_on_generic_rust_call.rs index 4147325ec44b9..3307a1408fb56 100644 --- a/tests/mir-opt/inline/dont_ice_on_generic_rust_call.rs +++ b/tests/mir-opt/inline/dont_ice_on_generic_rust_call.rs @@ -1,4 +1,3 @@ -// skip-filecheck // EMIT_MIR_FOR_EACH_PANIC_STRATEGY // compile-flags: -Zmir-enable-passes=+Inline --crate-type=lib @@ -8,5 +7,7 @@ use std::marker::Tuple; // EMIT_MIR dont_ice_on_generic_rust_call.call.Inline.diff pub fn call(mut mock: Box>, input: I) { + // CHECK-LABEL: fn call( + // CHECK-NOT: (inlined > as FnMut>::call_mut) mock.call_mut(input) } From 76faae9cdcf0d37884809ab0d21689fbc7598b98 Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 20 Oct 2023 11:11:50 -0700 Subject: [PATCH 06/32] FileCheck dyn_trait --- tests/mir-opt/inline/dyn_trait.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/mir-opt/inline/dyn_trait.rs b/tests/mir-opt/inline/dyn_trait.rs index 7b41b1e1171e2..005cf155fdb93 100644 --- a/tests/mir-opt/inline/dyn_trait.rs +++ b/tests/mir-opt/inline/dyn_trait.rs @@ -1,4 +1,3 @@ -// skip-filecheck // EMIT_MIR_FOR_EACH_PANIC_STRATEGY #![crate_type = "lib"] @@ -20,18 +19,26 @@ pub trait Query { // EMIT_MIR dyn_trait.mk_cycle.Inline.diff #[inline(always)] pub fn mk_cycle(c: &dyn Cache) { + // CHECK-LABEL: fn mk_cycle( + // CHECK-NOT: (inlined as Cache>::store_nocache) c.store_nocache() } // EMIT_MIR dyn_trait.try_execute_query.Inline.diff #[inline(always)] pub fn try_execute_query(c: &C) { + // CHECK-LABEL: fn try_execute_query( + // CHECK: (inlined mk_cycle::<::V>) mk_cycle(c) } // EMIT_MIR dyn_trait.get_query.Inline.diff #[inline(always)] pub fn get_query(t: &T) { + // CHECK-LABEL: fn get_query( + // CHECK-NOT: (inlined ::cache::) let c = Q::cache(t); + // CHECK: (inlined try_execute_query::<::C>) + // CHECK: (inlined mk_cycle::<::V>) try_execute_query(c) } From f005d2325a5b838a4015b0233ee022c455962801 Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 20 Oct 2023 11:12:05 -0700 Subject: [PATCH 07/32] FileCheck exponential_runtime --- tests/mir-opt/inline/exponential_runtime.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/mir-opt/inline/exponential_runtime.rs b/tests/mir-opt/inline/exponential_runtime.rs index 6d3af8b9c572a..499bec33ffc9b 100644 --- a/tests/mir-opt/inline/exponential_runtime.rs +++ b/tests/mir-opt/inline/exponential_runtime.rs @@ -1,4 +1,3 @@ -// skip-filecheck // EMIT_MIR_FOR_EACH_PANIC_STRATEGY // Checks that code with exponential runtime does not have exponential behavior in inlining. @@ -85,5 +84,13 @@ impl A for () { // EMIT_MIR exponential_runtime.main.Inline.diff fn main() { + // CHECK-LABEL: fn main( + // CHECK: (inlined <() as G>::call) + // CHECK: (inlined <() as F>::call) + // CHECK: (inlined <() as E>::call) + // CHECK: (inlined <() as D>::call) + // CHECK: (inlined <() as C>::call) + // CHECK: (inlined <() as B>::call) + // CHECK-NOT: inlined <() as G>::call(); } From 22679cd36df7ad05eaea153b64712e97ffe69ecf Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 20 Oct 2023 11:12:17 -0700 Subject: [PATCH 08/32] FileCheck inline_any_operand --- tests/mir-opt/inline/inline_any_operand.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/mir-opt/inline/inline_any_operand.rs b/tests/mir-opt/inline/inline_any_operand.rs index e131cd6ef7ee8..659b7c3a0a1f5 100644 --- a/tests/mir-opt/inline/inline_any_operand.rs +++ b/tests/mir-opt/inline/inline_any_operand.rs @@ -1,4 +1,3 @@ -// skip-filecheck // compile-flags: -Z span_free_formats // Tests that MIR inliner works for any operand @@ -9,6 +8,8 @@ fn main() { // EMIT_MIR inline_any_operand.bar.Inline.after.mir fn bar() -> bool { + // CHECK-LABEL: fn bar( + // CHECK: (inlined foo) let f = foo; f(1, -1) } From de56d2d9b2c501985b5f30eb6506475bf312c0c0 Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 20 Oct 2023 11:24:38 -0700 Subject: [PATCH 09/32] FileCheck inline_box_fn --- tests/mir-opt/inline/inline_box_fn.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/mir-opt/inline/inline_box_fn.rs b/tests/mir-opt/inline/inline_box_fn.rs index f6a90b92c9118..7686477c291a3 100644 --- a/tests/mir-opt/inline/inline_box_fn.rs +++ b/tests/mir-opt/inline/inline_box_fn.rs @@ -1,9 +1,10 @@ -// skip-filecheck // EMIT_MIR_FOR_EACH_PANIC_STRATEGY // unit-test: Inline // compile-flags: --crate-type=lib // EMIT_MIR inline_box_fn.call.Inline.diff fn call(x: Box) { + // CHECK-LABEL: fn call( + // CHECK-NOT: (inlined as Fn<(i32,)>>::call) x(1); } From 9d61e6a4e46899a00da5908cf6db34a7d2bebb28 Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 20 Oct 2023 11:28:38 -0700 Subject: [PATCH 10/32] FileCheck inline_closure_borrows_arg --- tests/mir-opt/inline/inline_closure_borrows_arg.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/mir-opt/inline/inline_closure_borrows_arg.rs b/tests/mir-opt/inline/inline_closure_borrows_arg.rs index a5cc7d1036530..1570ab057c728 100644 --- a/tests/mir-opt/inline/inline_closure_borrows_arg.rs +++ b/tests/mir-opt/inline/inline_closure_borrows_arg.rs @@ -1,4 +1,3 @@ -// skip-filecheck // compile-flags: -Z span_free_formats -Zunsound-mir-opts // Tests that MIR inliner can handle closure arguments, @@ -14,5 +13,8 @@ fn foo(_t: T, q: &i32) -> i32 { let variable = &*r; *variable }; + + // CHECK-LABEL: fn foo( + // CHECK: (inlined foo::::{closure#0}) x(q, q) } From 5caee416a5c53c997c35d2358cb61e2499a1e263 Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 20 Oct 2023 11:30:04 -0700 Subject: [PATCH 11/32] FileCheck inline_closure_captures --- tests/mir-opt/inline/inline_closure_captures.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/mir-opt/inline/inline_closure_captures.rs b/tests/mir-opt/inline/inline_closure_captures.rs index 0d95564e5ddee..2b08b10688728 100644 --- a/tests/mir-opt/inline/inline_closure_captures.rs +++ b/tests/mir-opt/inline/inline_closure_captures.rs @@ -1,4 +1,3 @@ -// skip-filecheck // compile-flags: -Z span_free_formats // Tests that MIR inliner can handle closure captures. @@ -10,5 +9,8 @@ fn main() { // EMIT_MIR inline_closure_captures.foo.Inline.after.mir fn foo(t: T, q: i32) -> (i32, T) { let x = |_q| (q, t); + + // CHECK-LABEL: fn foo( + // CHECK: (inlined foo::::{closure#0}) x(q) } From 7ee05d24b5103df74d93c267ab4e1d8060aec444 Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 20 Oct 2023 11:31:05 -0700 Subject: [PATCH 12/32] FileCheck inline_closure --- tests/mir-opt/inline/inline_closure.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/mir-opt/inline/inline_closure.rs b/tests/mir-opt/inline/inline_closure.rs index bd4c84ff0ca29..65f55d49a806e 100644 --- a/tests/mir-opt/inline/inline_closure.rs +++ b/tests/mir-opt/inline/inline_closure.rs @@ -1,4 +1,3 @@ -// skip-filecheck // compile-flags: -Z span_free_formats // Tests that MIR inliner can handle closure arguments. (#45894) @@ -10,5 +9,8 @@ fn main() { // EMIT_MIR inline_closure.foo.Inline.after.mir fn foo(_t: T, q: i32) -> i32 { let x = |_t, _q| _t; + + // CHECK-LABEL: fn foo( + // CHECK: (inlined foo::::{closure#0}) x(q, q) } From d8f33ef93d6dbff4267fd7d779d73a76f1e46e28 Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 20 Oct 2023 18:03:13 -0700 Subject: [PATCH 13/32] FileCheck inline_diverging --- tests/mir-opt/inline/inline_diverging.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/mir-opt/inline/inline_diverging.rs b/tests/mir-opt/inline/inline_diverging.rs index 9e50b1ead2bc0..25a5b9c5c5e72 100644 --- a/tests/mir-opt/inline/inline_diverging.rs +++ b/tests/mir-opt/inline/inline_diverging.rs @@ -1,4 +1,3 @@ -// skip-filecheck // Tests inlining of diverging calls. // // EMIT_MIR_FOR_EACH_PANIC_STRATEGY @@ -7,6 +6,8 @@ // EMIT_MIR inline_diverging.f.Inline.diff pub fn f() { + // CHECK-LABEL: fn f( + // CHECK: (inlined sleep) sleep(); } @@ -15,12 +16,17 @@ pub fn g(i: i32) -> u32 { if i > 0 { i as u32 } else { + // CHECK-LABEL: fn g( + // CHECK: (inlined panic) panic(); } } // EMIT_MIR inline_diverging.h.Inline.diff pub fn h() { + // CHECK-LABEL: fn h( + // CHECK: (inlined call_twice:: ! {sleep}>) + // CHECK-NOT: inlined call_twice(sleep); } From 20e7caa737b1ee4ae34a89201ebc972a4d495982 Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 20 Oct 2023 18:15:32 -0700 Subject: [PATCH 14/32] FileCheck inline_coroutine --- ...ne_coroutine.main.Inline.panic-unwind.diff | 22 +++++++++---------- tests/mir-opt/inline/inline_coroutine.rs | 4 +++- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/tests/mir-opt/inline/inline_coroutine.main.Inline.panic-unwind.diff b/tests/mir-opt/inline/inline_coroutine.main.Inline.panic-unwind.diff index e7d020968389f..fdb42bf3d8a0e 100644 --- a/tests/mir-opt/inline/inline_coroutine.main.Inline.panic-unwind.diff +++ b/tests/mir-opt/inline/inline_coroutine.main.Inline.panic-unwind.diff @@ -4,26 +4,26 @@ fn main() -> () { let mut _0: (); let _1: std::ops::CoroutineState; - let mut _2: std::pin::Pin<&mut {coroutine@$DIR/inline_coroutine.rs:17:5: 17:8}>; - let mut _3: &mut {coroutine@$DIR/inline_coroutine.rs:17:5: 17:8}; - let mut _4: {coroutine@$DIR/inline_coroutine.rs:17:5: 17:8}; + let mut _2: std::pin::Pin<&mut {coroutine@$DIR/inline_coroutine.rs:19:5: 19:8}>; + let mut _3: &mut {coroutine@$DIR/inline_coroutine.rs:19:5: 19:8}; + let mut _4: {coroutine@$DIR/inline_coroutine.rs:19:5: 19:8}; + let mut _5: bool; scope 1 { debug _r => _1; } + scope 2 (inlined g) { + } -+ scope 3 (inlined Pin::<&mut {coroutine@$DIR/inline_coroutine.rs:17:5: 17:8}>::new) { ++ scope 3 (inlined Pin::<&mut {coroutine@$DIR/inline_coroutine.rs:19:5: 19:8}>::new) { + debug pointer => _3; + scope 4 { -+ scope 5 (inlined Pin::<&mut {coroutine@$DIR/inline_coroutine.rs:17:5: 17:8}>::new_unchecked) { ++ scope 5 (inlined Pin::<&mut {coroutine@$DIR/inline_coroutine.rs:19:5: 19:8}>::new_unchecked) { + debug pointer => _3; + } + } + } + scope 6 (inlined g::{closure#0}) { + debug a => _5; -+ let mut _6: &mut {coroutine@$DIR/inline_coroutine.rs:17:5: 17:8}; ++ let mut _6: &mut {coroutine@$DIR/inline_coroutine.rs:19:5: 19:8}; + let mut _7: u32; + let mut _8: i32; + } @@ -37,20 +37,20 @@ - } - - bb1: { -+ _4 = {coroutine@$DIR/inline_coroutine.rs:17:5: 17:8 (#0)}; ++ _4 = {coroutine@$DIR/inline_coroutine.rs:19:5: 19:8 (#0)}; _3 = &mut _4; -- _2 = Pin::<&mut {coroutine@$DIR/inline_coroutine.rs:17:5: 17:8}>::new(move _3) -> [return: bb2, unwind: bb5]; +- _2 = Pin::<&mut {coroutine@$DIR/inline_coroutine.rs:19:5: 19:8}>::new(move _3) -> [return: bb2, unwind: bb5]; - } - - bb2: { -+ _2 = Pin::<&mut {coroutine@$DIR/inline_coroutine.rs:17:5: 17:8}> { pointer: move _3 }; ++ _2 = Pin::<&mut {coroutine@$DIR/inline_coroutine.rs:19:5: 19:8}> { pointer: move _3 }; StorageDead(_3); -- _1 = <{coroutine@$DIR/inline_coroutine.rs:17:5: 17:8} as Coroutine>::resume(move _2, const false) -> [return: bb3, unwind: bb5]; +- _1 = <{coroutine@$DIR/inline_coroutine.rs:19:5: 19:8} as Coroutine>::resume(move _2, const false) -> [return: bb3, unwind: bb5]; + StorageLive(_5); + _5 = const false; + StorageLive(_6); + StorageLive(_7); -+ _6 = (_2.0: &mut {coroutine@$DIR/inline_coroutine.rs:17:5: 17:8}); ++ _6 = (_2.0: &mut {coroutine@$DIR/inline_coroutine.rs:19:5: 19:8}); + _7 = discriminant((*_6)); + switchInt(move _7) -> [0: bb5, 1: bb9, 3: bb10, otherwise: bb11]; } diff --git a/tests/mir-opt/inline/inline_coroutine.rs b/tests/mir-opt/inline/inline_coroutine.rs index d021cdac28efa..a82586bf2bfac 100644 --- a/tests/mir-opt/inline/inline_coroutine.rs +++ b/tests/mir-opt/inline/inline_coroutine.rs @@ -1,4 +1,3 @@ -// skip-filecheck // EMIT_MIR_FOR_EACH_PANIC_STRATEGY // compile-flags: -Zinline-mir-hint-threshold=1000 #![feature(coroutines, coroutine_trait)] @@ -8,6 +7,9 @@ use std::pin::Pin; // EMIT_MIR inline_coroutine.main.Inline.diff fn main() { + // CHECK-LABEL: fn main( + // CHECK: (inlined g) + // CHECK: (inlined g::{closure#0}) let _r = Pin::new(&mut g()).resume(false); } From 19c36a96dfce27de7ad60f084e4bbc9abfdf4768 Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 20 Oct 2023 18:21:41 -0700 Subject: [PATCH 15/32] FileCheck inline_instruction_set --- tests/mir-opt/inline/inline_instruction_set.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/mir-opt/inline/inline_instruction_set.rs b/tests/mir-opt/inline/inline_instruction_set.rs index 4ac4d462f82de..7cb59645587b6 100644 --- a/tests/mir-opt/inline/inline_instruction_set.rs +++ b/tests/mir-opt/inline/inline_instruction_set.rs @@ -1,4 +1,3 @@ -// skip-filecheck // Checks that only functions with the compatible instruction_set attributes are inlined. // // A function is "compatible" when the *callee* has the same attribute or no attribute. @@ -47,16 +46,26 @@ fn inline_always_and_using_inline_asm() { // EMIT_MIR inline_instruction_set.t32.Inline.diff #[instruction_set(arm::t32)] pub fn t32() { + // CHECK-LABEL: fn t32( + // CHECK-NOT: (inlined instruction_set_a32) instruction_set_a32(); + // CHECK: (inlined instruction_set_t32) instruction_set_t32(); + // CHECK: (inlined instruction_set_default) instruction_set_default(); + // CHECK-NOT: (inlined inline_always_and_using_inline_asm) inline_always_and_using_inline_asm(); } // EMIT_MIR inline_instruction_set.default.Inline.diff pub fn default() { + // CHECK-LABEL: fn default( + // CHECK-NOT: (inlined instruction_set_a32) instruction_set_a32(); + // CHECK-NOT: (inlined instruction_set_t32) instruction_set_t32(); + // CHECK: (inlined instruction_set_default) instruction_set_default(); + // CHECK: (inlined inline_always_and_using_inline_asm) inline_always_and_using_inline_asm(); } From de8255194a292d59c14b4d4a4424e576c7820373 Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 20 Oct 2023 20:33:22 -0700 Subject: [PATCH 16/32] FileCheck inline_into_box_place --- tests/mir-opt/inline/inline_into_box_place.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/mir-opt/inline/inline_into_box_place.rs b/tests/mir-opt/inline/inline_into_box_place.rs index b755692afc214..65f8e2916b630 100644 --- a/tests/mir-opt/inline/inline_into_box_place.rs +++ b/tests/mir-opt/inline/inline_into_box_place.rs @@ -1,4 +1,3 @@ -// skip-filecheck // ignore-endian-big // EMIT_MIR_FOR_EACH_PANIC_STRATEGY // ignore-debug MIR alignment checks in std alter the diff, breaking the test @@ -6,5 +5,7 @@ // EMIT_MIR inline_into_box_place.main.Inline.diff fn main() { + // CHECK-LABEL: fn main( + // CHECK: (inlined Box::>::new) let _x: Box> = Box::new(Vec::new()); } From 3202d4e35761cc73b70465fa55f414840728e83a Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 20 Oct 2023 20:34:41 -0700 Subject: [PATCH 17/32] FileCheck inline_options --- tests/mir-opt/inline/inline_options.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/mir-opt/inline/inline_options.rs b/tests/mir-opt/inline/inline_options.rs index 394a8c4945cdd..b940c64f0b886 100644 --- a/tests/mir-opt/inline/inline_options.rs +++ b/tests/mir-opt/inline/inline_options.rs @@ -1,4 +1,3 @@ -// skip-filecheck // EMIT_MIR_FOR_EACH_PANIC_STRATEGY // Checks that inlining threshold can be controlled with // inline-mir-threshold and inline-hint-threshold options. @@ -8,7 +7,10 @@ // EMIT_MIR inline_options.main.Inline.after.mir fn main() { + // CHECK-LABEL: fn main( + // CHECK-NOT: (inlined not_inlined) not_inlined(); + // CHECK: (inlined inlined::) inlined::(); } From 21a4c39cb849d32c1209329a740e2c3013ebe063 Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 20 Oct 2023 20:38:15 -0700 Subject: [PATCH 18/32] FileCheck inline_retag --- tests/mir-opt/inline/inline_retag.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/mir-opt/inline/inline_retag.rs b/tests/mir-opt/inline/inline_retag.rs index f695b9f22e616..c90c0b819b7ba 100644 --- a/tests/mir-opt/inline/inline_retag.rs +++ b/tests/mir-opt/inline/inline_retag.rs @@ -1,4 +1,3 @@ -// skip-filecheck // compile-flags: -Z span_free_formats -Z mir-emit-retag // Tests that MIR inliner fixes up `Retag`'s `fn_entry` flag @@ -9,6 +8,8 @@ fn main() { // EMIT_MIR inline_retag.bar.Inline.after.mir fn bar() -> bool { + // CHECK-LABEL: fn bar( + // CHECK: (inlined foo) let f = foo; f(&1, &-1) } From 25325667f2be173d0d8968f053e20d36ed24545b Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 20 Oct 2023 20:40:20 -0700 Subject: [PATCH 19/32] FileCheck inline_specialization --- tests/mir-opt/inline/inline_specialization.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/mir-opt/inline/inline_specialization.rs b/tests/mir-opt/inline/inline_specialization.rs index eb0cf891dad01..6453abc008129 100644 --- a/tests/mir-opt/inline/inline_specialization.rs +++ b/tests/mir-opt/inline/inline_specialization.rs @@ -1,9 +1,10 @@ -// skip-filecheck // EMIT_MIR_FOR_EACH_PANIC_STRATEGY #![feature(specialization)] // EMIT_MIR inline_specialization.main.Inline.diff fn main() { + // CHECK-LABEL: fn main( + // CHECK: (inlined as Foo>::bar) let x = as Foo>::bar(); } From f7acf17945b6a5739fc498330c698579982fbaf8 Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 20 Oct 2023 20:41:43 -0700 Subject: [PATCH 20/32] FileCheck inline_trait_method_2 --- tests/mir-opt/inline/inline_trait_method_2.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/mir-opt/inline/inline_trait_method_2.rs b/tests/mir-opt/inline/inline_trait_method_2.rs index e87609a8c7e00..b0b6a7b9b01de 100644 --- a/tests/mir-opt/inline/inline_trait_method_2.rs +++ b/tests/mir-opt/inline/inline_trait_method_2.rs @@ -1,9 +1,11 @@ -// skip-filecheck // EMIT_MIR_FOR_EACH_PANIC_STRATEGY // compile-flags: -Z span_free_formats -Z mir-opt-level=4 // EMIT_MIR inline_trait_method_2.test2.Inline.after.mir fn test2(x: &dyn X) -> bool { + // CHECK-LABEL: fn test2( + // CHECK: (inlined test) + // CHECK-NOT: (inlined ::y) test(x) } From 1b9cb5d59bb704c8a7920abcdbdef83f3273afba Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 20 Oct 2023 21:32:56 -0700 Subject: [PATCH 21/32] FileCheck inline_trait_method --- tests/mir-opt/inline/inline_trait_method.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/mir-opt/inline/inline_trait_method.rs b/tests/mir-opt/inline/inline_trait_method.rs index 8a95adf3713e2..ea09966a2aa94 100644 --- a/tests/mir-opt/inline/inline_trait_method.rs +++ b/tests/mir-opt/inline/inline_trait_method.rs @@ -1,4 +1,3 @@ -// skip-filecheck // EMIT_MIR_FOR_EACH_PANIC_STRATEGY // compile-flags: -Z span_free_formats @@ -8,6 +7,8 @@ fn main() { // EMIT_MIR inline_trait_method.test.Inline.after.mir fn test(x: &dyn X) -> u32 { + // CHECK-LABEL: fn test( + // CHECK-NOT: (inlined ::y) x.y() } From 773dc6275666afc56d2ef442f07e2b3908c692d3 Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 20 Oct 2023 21:41:07 -0700 Subject: [PATCH 22/32] FileCheck inline_as_ref_as_mut --- tests/mir-opt/inline/issue_58867_inline_as_ref_as_mut.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/mir-opt/inline/issue_58867_inline_as_ref_as_mut.rs b/tests/mir-opt/inline/issue_58867_inline_as_ref_as_mut.rs index da779fed76fb5..4517c88d71340 100644 --- a/tests/mir-opt/inline/issue_58867_inline_as_ref_as_mut.rs +++ b/tests/mir-opt/inline/issue_58867_inline_as_ref_as_mut.rs @@ -1,21 +1,28 @@ -// skip-filecheck // EMIT_MIR issue_58867_inline_as_ref_as_mut.a.Inline.after.mir pub fn a(x: &mut [T]) -> &mut [T] { + // CHECK-LABEL: fn a( + // CHECK: (inlined <[T] as AsMut<[T]>>::as_mut) x.as_mut() } // EMIT_MIR issue_58867_inline_as_ref_as_mut.b.Inline.after.mir pub fn b(x: &mut Box) -> &mut T { + // CHECK-LABEL: fn b( + // CHECK: (inlined as AsMut>::as_mut) x.as_mut() } // EMIT_MIR issue_58867_inline_as_ref_as_mut.c.Inline.after.mir pub fn c(x: &[T]) -> &[T] { + // CHECK-LABEL: fn c( + // CHECK: (inlined <[T] as AsRef<[T]>>::as_ref) x.as_ref() } // EMIT_MIR issue_58867_inline_as_ref_as_mut.d.Inline.after.mir pub fn d(x: &Box) -> &T { + // CHECK-LABEL: fn d( + // CHECK: (inlined as AsRef>::as_ref) x.as_ref() } From bb695977de920f47eb87ccf7580bb04a6bd36350 Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 20 Oct 2023 21:57:08 -0700 Subject: [PATCH 23/32] FileCheck inline_scopes_parenting --- ...ssue_76997_inline_scopes_parenting.main.Inline.after.mir | 6 +++--- tests/mir-opt/inline/issue_76997_inline_scopes_parenting.rs | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/mir-opt/inline/issue_76997_inline_scopes_parenting.main.Inline.after.mir b/tests/mir-opt/inline/issue_76997_inline_scopes_parenting.main.Inline.after.mir index d28c0441f63c3..ba5eb757cf977 100644 --- a/tests/mir-opt/inline/issue_76997_inline_scopes_parenting.main.Inline.after.mir +++ b/tests/mir-opt/inline/issue_76997_inline_scopes_parenting.main.Inline.after.mir @@ -2,8 +2,8 @@ fn main() -> () { let mut _0: (); - let _1: {closure@$DIR/issue_76997_inline_scopes_parenting.rs:6:13: 6:16}; - let mut _2: &{closure@$DIR/issue_76997_inline_scopes_parenting.rs:6:13: 6:16}; + let _1: {closure@$DIR/issue_76997_inline_scopes_parenting.rs:10:13: 10:16}; + let mut _2: &{closure@$DIR/issue_76997_inline_scopes_parenting.rs:10:13: 10:16}; let mut _3: ((),); let mut _4: (); let mut _5: (); @@ -19,7 +19,7 @@ fn main() -> () { bb0: { StorageLive(_1); - _1 = {closure@$DIR/issue_76997_inline_scopes_parenting.rs:6:13: 6:16}; + _1 = {closure@$DIR/issue_76997_inline_scopes_parenting.rs:10:13: 10:16}; StorageLive(_2); _2 = &_1; StorageLive(_3); diff --git a/tests/mir-opt/inline/issue_76997_inline_scopes_parenting.rs b/tests/mir-opt/inline/issue_76997_inline_scopes_parenting.rs index c7147d42df996..2a7c931a659ee 100644 --- a/tests/mir-opt/inline/issue_76997_inline_scopes_parenting.rs +++ b/tests/mir-opt/inline/issue_76997_inline_scopes_parenting.rs @@ -1,8 +1,12 @@ -// skip-filecheck // Tests that MIR inliner can handle `SourceScopeData` parenting correctly. (#76997) // EMIT_MIR issue_76997_inline_scopes_parenting.main.Inline.after.mir fn main() { + // CHECK-LABEL: fn main( + // CHECK: scope 2 + // CHECK-NEXT: debug x + // CHECK-NEXT: scope 3 + // CHECK-NEXT: debug y let f = |x| { let y = x; y }; f(()) } From 5cf65eb16a57469212a682d469829c082ec25fb2 Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 20 Oct 2023 22:06:38 -0700 Subject: [PATCH 24/32] FileCheck issue_78442 --- tests/mir-opt/inline/issue_78442.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/mir-opt/inline/issue_78442.rs b/tests/mir-opt/inline/issue_78442.rs index f83ed70d0db32..f9a5234283a03 100644 --- a/tests/mir-opt/inline/issue_78442.rs +++ b/tests/mir-opt/inline/issue_78442.rs @@ -1,4 +1,3 @@ -// skip-filecheck // compile-flags: -Z mir-opt-level=3 -Z inline-mir // EMIT_MIR_FOR_EACH_PANIC_STRATEGY #![crate_type = "lib"] @@ -9,6 +8,11 @@ pub fn bar

( // Error won't happen if "bar" is not generic _baz: P, ) { + // CHECK-LABEL: fn bar( + // CHECK: let mut {{.*}}: &fn() {foo}; + // CHECK: let {{.*}}: fn() {foo}; + // CHECK: (inlined hide_foo) + // CHECK-NOT: inlined hide_foo()(); } From 6e047c07a66ed5a69e38e38191c911b7cb579e9e Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 20 Oct 2023 22:18:02 -0700 Subject: [PATCH 25/32] FileCheck unchecked_shifts --- tests/mir-opt/inline/unchecked_shifts.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/mir-opt/inline/unchecked_shifts.rs b/tests/mir-opt/inline/unchecked_shifts.rs index 67666f2f713f2..805195dd8b375 100644 --- a/tests/mir-opt/inline/unchecked_shifts.rs +++ b/tests/mir-opt/inline/unchecked_shifts.rs @@ -1,4 +1,3 @@ -// skip-filecheck // EMIT_MIR_FOR_EACH_PANIC_STRATEGY #![crate_type = "lib"] #![feature(unchecked_math)] @@ -9,23 +8,31 @@ // EMIT_MIR unchecked_shifts.unchecked_shl_unsigned_smaller.Inline.diff // EMIT_MIR unchecked_shifts.unchecked_shl_unsigned_smaller.PreCodegen.after.mir pub unsafe fn unchecked_shl_unsigned_smaller(a: u16, b: u32) -> u16 { + // CHECK-LABEL: fn unchecked_shl_unsigned_smaller( + // CHECK: (inlined core::num::::unchecked_shl) a.unchecked_shl(b) } // EMIT_MIR unchecked_shifts.unchecked_shr_signed_smaller.Inline.diff // EMIT_MIR unchecked_shifts.unchecked_shr_signed_smaller.PreCodegen.after.mir pub unsafe fn unchecked_shr_signed_smaller(a: i16, b: u32) -> i16 { + // CHECK-LABEL: fn unchecked_shr_signed_smaller( + // CHECK: (inlined core::num::::unchecked_shr) a.unchecked_shr(b) } // EMIT_MIR unchecked_shifts.unchecked_shl_unsigned_bigger.Inline.diff // EMIT_MIR unchecked_shifts.unchecked_shl_unsigned_bigger.PreCodegen.after.mir pub unsafe fn unchecked_shl_unsigned_bigger(a: u64, b: u32) -> u64 { + // CHECK-LABEL: fn unchecked_shl_unsigned_bigger( + // CHECK: (inlined core::num::::unchecked_shl) a.unchecked_shl(b) } // EMIT_MIR unchecked_shifts.unchecked_shr_signed_bigger.Inline.diff // EMIT_MIR unchecked_shifts.unchecked_shr_signed_bigger.PreCodegen.after.mir pub unsafe fn unchecked_shr_signed_bigger(a: i64, b: u32) -> i64 { + // CHECK-LABEL: fn unchecked_shr_signed_bigger( + // CHECK: (inlined core::num::::unchecked_shr) a.unchecked_shr(b) } From 3faf05b6e7bfc88f2415cd4c09968ffe454b295f Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 20 Oct 2023 22:21:03 -0700 Subject: [PATCH 26/32] FileCheck unsized_argument --- tests/mir-opt/inline/unsized_argument.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/mir-opt/inline/unsized_argument.rs b/tests/mir-opt/inline/unsized_argument.rs index 22c88b83f9b1b..e8c2bc10be220 100644 --- a/tests/mir-opt/inline/unsized_argument.rs +++ b/tests/mir-opt/inline/unsized_argument.rs @@ -1,4 +1,3 @@ -// skip-filecheck // needs-unwind #![feature(unsized_fn_params)] @@ -7,6 +6,8 @@ fn callee(y: [i32]) {} // EMIT_MIR unsized_argument.caller.Inline.diff fn caller(x: Box<[i32]>) { + // CHECK-LABEL: fn caller( + // CHECK-NOT: (inlined callee) callee(*x); } From 6ab66c3f3724e71d228d498a52f02cb358f9247e Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 20 Oct 2023 22:22:32 -0700 Subject: [PATCH 27/32] FileCheck unwrap_unchecked --- tests/mir-opt/inline/unwrap_unchecked.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/mir-opt/inline/unwrap_unchecked.rs b/tests/mir-opt/inline/unwrap_unchecked.rs index f8964eba227a4..be133706e5c22 100644 --- a/tests/mir-opt/inline/unwrap_unchecked.rs +++ b/tests/mir-opt/inline/unwrap_unchecked.rs @@ -1,4 +1,3 @@ -// skip-filecheck #![crate_type = "lib"] // EMIT_MIR_FOR_EACH_PANIC_STRATEGY @@ -8,5 +7,7 @@ // EMIT_MIR unwrap_unchecked.unwrap_unchecked.Inline.diff // EMIT_MIR unwrap_unchecked.unwrap_unchecked.PreCodegen.after.mir pub unsafe fn unwrap_unchecked(slf: Option) -> T { + // CHECK-LABEL: fn unwrap_unchecked( + // CHECK: (inlined #[track_caller] Option::::unwrap_unchecked) slf.unwrap_unchecked() } From 1ec10ec77f6fa64de7c03150bfbb831dec376357 Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Sun, 22 Oct 2023 09:27:43 -0700 Subject: [PATCH 28/32] address review comments --- tests/mir-opt/inline/asm_unwind.rs | 3 + .../inline/caller_with_trivial_bound.rs | 3 + tests/mir-opt/inline/cycle.rs | 2 + .../inline/dont_ice_on_generic_rust_call.rs | 2 +- tests/mir-opt/inline/dyn_trait.rs | 4 +- tests/mir-opt/inline/exponential_runtime.rs | 1 + tests/mir-opt/inline/inline_box_fn.rs | 2 +- .../inline/inline_retag.bar.Inline.after.mir | 59 ----------------- .../inline/inline_retag.bar.Inline.diff | 65 +++++++++++++++++++ tests/mir-opt/inline/inline_retag.rs | 11 +++- tests/mir-opt/inline/inline_trait_method.rs | 3 +- ...ine_scopes_parenting.main.Inline.after.mir | 6 +- .../issue_76997_inline_scopes_parenting.rs | 9 ++- 13 files changed, 100 insertions(+), 70 deletions(-) delete mode 100644 tests/mir-opt/inline/inline_retag.bar.Inline.after.mir create mode 100644 tests/mir-opt/inline/inline_retag.bar.Inline.diff diff --git a/tests/mir-opt/inline/asm_unwind.rs b/tests/mir-opt/inline/asm_unwind.rs index 596a4592f25ff..0ae20e5221110 100644 --- a/tests/mir-opt/inline/asm_unwind.rs +++ b/tests/mir-opt/inline/asm_unwind.rs @@ -2,6 +2,7 @@ // // EMIT_MIR_FOR_EACH_PANIC_STRATEGY // needs-asm-support +// needs-unwind // compile-flags: -Zinline-mir-hint-threshold=1000 #![feature(asm_unwind)] @@ -21,5 +22,7 @@ fn foo() { pub fn main() { // CHECK-LABEL: fn main( // CHECK: (inlined foo) + // CHECK: asm!("", options(MAY_UNWIND)) -> [return: {{bb.*}}, unwind: [[unwind:bb.*]]]; + // CHECK: [[unwind]] (cleanup) foo(); } diff --git a/tests/mir-opt/inline/caller_with_trivial_bound.rs b/tests/mir-opt/inline/caller_with_trivial_bound.rs index 722c7f6724efd..40f7f4bbab2f7 100644 --- a/tests/mir-opt/inline/caller_with_trivial_bound.rs +++ b/tests/mir-opt/inline/caller_with_trivial_bound.rs @@ -15,6 +15,9 @@ impl Factory for IntFactory { // EMIT_MIR caller_with_trivial_bound.foo.Inline.diff pub fn foo() where + // Because of this trivial bound, the inliner fails to normalize + // `>::Item`. + // Verify that we do not inline anything, which would cause validation ICEs. IntFactory: Factory, { // CHECK-LABEL: fn foo( diff --git a/tests/mir-opt/inline/cycle.rs b/tests/mir-opt/inline/cycle.rs index 8716b44b3dc02..350724235ba04 100644 --- a/tests/mir-opt/inline/cycle.rs +++ b/tests/mir-opt/inline/cycle.rs @@ -13,6 +13,7 @@ fn f(g: impl Fn()) { #[inline(always)] fn g() { // CHECK-LABEL: fn g( + // CHECK-NOT: inlined // CHECK: (inlined f::) // CHECK-NOT: inlined f(main); @@ -21,6 +22,7 @@ fn g() { // EMIT_MIR cycle.main.Inline.diff fn main() { // CHECK-LABEL: fn main( + // CHECK-NOT: inlined // CHECK: (inlined f::) // CHECK-NOT: inlined f(g); diff --git a/tests/mir-opt/inline/dont_ice_on_generic_rust_call.rs b/tests/mir-opt/inline/dont_ice_on_generic_rust_call.rs index 3307a1408fb56..ce5e1855a716b 100644 --- a/tests/mir-opt/inline/dont_ice_on_generic_rust_call.rs +++ b/tests/mir-opt/inline/dont_ice_on_generic_rust_call.rs @@ -8,6 +8,6 @@ use std::marker::Tuple; // EMIT_MIR dont_ice_on_generic_rust_call.call.Inline.diff pub fn call(mut mock: Box>, input: I) { // CHECK-LABEL: fn call( - // CHECK-NOT: (inlined > as FnMut>::call_mut) + // CHECK-NOT: inlined mock.call_mut(input) } diff --git a/tests/mir-opt/inline/dyn_trait.rs b/tests/mir-opt/inline/dyn_trait.rs index 005cf155fdb93..ecf220a85e6a3 100644 --- a/tests/mir-opt/inline/dyn_trait.rs +++ b/tests/mir-opt/inline/dyn_trait.rs @@ -20,7 +20,7 @@ pub trait Query { #[inline(always)] pub fn mk_cycle(c: &dyn Cache) { // CHECK-LABEL: fn mk_cycle( - // CHECK-NOT: (inlined as Cache>::store_nocache) + // CHECK-NOT: inlined c.store_nocache() } @@ -36,7 +36,7 @@ pub fn try_execute_query(c: &C) { #[inline(always)] pub fn get_query(t: &T) { // CHECK-LABEL: fn get_query( - // CHECK-NOT: (inlined ::cache::) + // CHECK-NOT: inlined let c = Q::cache(t); // CHECK: (inlined try_execute_query::<::C>) // CHECK: (inlined mk_cycle::<::V>) diff --git a/tests/mir-opt/inline/exponential_runtime.rs b/tests/mir-opt/inline/exponential_runtime.rs index 499bec33ffc9b..1199ce4e5588e 100644 --- a/tests/mir-opt/inline/exponential_runtime.rs +++ b/tests/mir-opt/inline/exponential_runtime.rs @@ -85,6 +85,7 @@ impl A for () { // EMIT_MIR exponential_runtime.main.Inline.diff fn main() { // CHECK-LABEL: fn main( + // CHECK-NOT: inlined // CHECK: (inlined <() as G>::call) // CHECK: (inlined <() as F>::call) // CHECK: (inlined <() as E>::call) diff --git a/tests/mir-opt/inline/inline_box_fn.rs b/tests/mir-opt/inline/inline_box_fn.rs index 7686477c291a3..d2da239399235 100644 --- a/tests/mir-opt/inline/inline_box_fn.rs +++ b/tests/mir-opt/inline/inline_box_fn.rs @@ -5,6 +5,6 @@ // EMIT_MIR inline_box_fn.call.Inline.diff fn call(x: Box) { // CHECK-LABEL: fn call( - // CHECK-NOT: (inlined as Fn<(i32,)>>::call) + // CHECK-NOT: inlined x(1); } diff --git a/tests/mir-opt/inline/inline_retag.bar.Inline.after.mir b/tests/mir-opt/inline/inline_retag.bar.Inline.after.mir deleted file mode 100644 index 8c3f3a4589e63..0000000000000 --- a/tests/mir-opt/inline/inline_retag.bar.Inline.after.mir +++ /dev/null @@ -1,59 +0,0 @@ -// MIR for `bar` after Inline - -fn bar() -> bool { - let mut _0: bool; - let _1: for<'a, 'b> fn(&'a i32, &'b i32) -> bool {foo}; - let mut _2: for<'a, 'b> fn(&'a i32, &'b i32) -> bool {foo}; - let mut _3: &i32; - let _4: &i32; - let _5: i32; - let mut _6: &i32; - let _7: &i32; - let _8: i32; - scope 1 { - debug f => _1; - let mut _9: &i32; - let mut _10: &i32; - scope 2 (inlined foo) { - debug x => _3; - debug y => _6; - let mut _11: i32; - let mut _12: i32; - } - } - - bb0: { - StorageLive(_1); - _1 = foo; - StorageLive(_2); - _2 = _1; - StorageLive(_3); - StorageLive(_4); - _10 = const _; - Retag(_10); - _4 = &(*_10); - _3 = &(*_4); - StorageLive(_6); - StorageLive(_7); - _9 = const _; - Retag(_9); - _7 = &(*_9); - _6 = &(*_7); - Retag(_3); - Retag(_6); - StorageLive(_11); - _11 = (*_3); - StorageLive(_12); - _12 = (*_6); - _0 = Eq(move _11, move _12); - StorageDead(_12); - StorageDead(_11); - StorageDead(_6); - StorageDead(_3); - StorageDead(_2); - StorageDead(_1); - StorageDead(_7); - StorageDead(_4); - return; - } -} diff --git a/tests/mir-opt/inline/inline_retag.bar.Inline.diff b/tests/mir-opt/inline/inline_retag.bar.Inline.diff new file mode 100644 index 0000000000000..8f53f6342ec6d --- /dev/null +++ b/tests/mir-opt/inline/inline_retag.bar.Inline.diff @@ -0,0 +1,65 @@ +- // MIR for `bar` before Inline ++ // MIR for `bar` after Inline + + fn bar() -> bool { + let mut _0: bool; + let _1: for<'a, 'b> fn(&'a i32, &'b i32) -> bool {foo}; + let mut _2: for<'a, 'b> fn(&'a i32, &'b i32) -> bool {foo}; + let mut _3: &i32; + let _4: &i32; + let _5: i32; + let mut _6: &i32; + let _7: &i32; + let _8: i32; + scope 1 { + debug f => _1; + let mut _9: &i32; + let mut _10: &i32; ++ scope 2 (inlined foo) { ++ debug x => _3; ++ debug y => _6; ++ let mut _11: i32; ++ let mut _12: i32; ++ } + } + + bb0: { + StorageLive(_1); + _1 = foo; + StorageLive(_2); + _2 = _1; + StorageLive(_3); + StorageLive(_4); + _10 = const _; + Retag(_10); + _4 = &(*_10); + _3 = &(*_4); + StorageLive(_6); + StorageLive(_7); + _9 = const _; + Retag(_9); + _7 = &(*_9); + _6 = &(*_7); +- _0 = move _2(move _3, move _6) -> [return: bb1, unwind continue]; +- } +- +- bb1: { ++ Retag(_3); ++ Retag(_6); ++ StorageLive(_11); ++ _11 = (*_3); ++ StorageLive(_12); ++ _12 = (*_6); ++ _0 = Eq(move _11, move _12); ++ StorageDead(_12); ++ StorageDead(_11); + StorageDead(_6); + StorageDead(_3); + StorageDead(_2); + StorageDead(_1); + StorageDead(_7); + StorageDead(_4); + return; + } + } + diff --git a/tests/mir-opt/inline/inline_retag.rs b/tests/mir-opt/inline/inline_retag.rs index c90c0b819b7ba..b9058905892bf 100644 --- a/tests/mir-opt/inline/inline_retag.rs +++ b/tests/mir-opt/inline/inline_retag.rs @@ -6,10 +6,19 @@ fn main() { println!("{}", bar()); } -// EMIT_MIR inline_retag.bar.Inline.after.mir +// EMIT_MIR inline_retag.bar.Inline.diff fn bar() -> bool { // CHECK-LABEL: fn bar( // CHECK: (inlined foo) + // CHECK: debug x => [[x:_.*]]; + // CHECK: debug y => [[y:_.*]]; + // CHECK: bb0: { + // CHECK: Retag + // CHECK: Retag + // CHECK: Retag([[x]]); + // CHECK: Retag([[y]]); + // CHECK: return; + // CHECK-NEXT: } let f = foo; f(&1, &-1) } diff --git a/tests/mir-opt/inline/inline_trait_method.rs b/tests/mir-opt/inline/inline_trait_method.rs index ea09966a2aa94..b39355637a1c5 100644 --- a/tests/mir-opt/inline/inline_trait_method.rs +++ b/tests/mir-opt/inline/inline_trait_method.rs @@ -1,3 +1,4 @@ +// Verify that we do not inline the default impl in a trait object. // EMIT_MIR_FOR_EACH_PANIC_STRATEGY // compile-flags: -Z span_free_formats @@ -8,7 +9,7 @@ fn main() { // EMIT_MIR inline_trait_method.test.Inline.after.mir fn test(x: &dyn X) -> u32 { // CHECK-LABEL: fn test( - // CHECK-NOT: (inlined ::y) + // CHECK-NOT: inlined x.y() } diff --git a/tests/mir-opt/inline/issue_76997_inline_scopes_parenting.main.Inline.after.mir b/tests/mir-opt/inline/issue_76997_inline_scopes_parenting.main.Inline.after.mir index ba5eb757cf977..ba4f91b28d536 100644 --- a/tests/mir-opt/inline/issue_76997_inline_scopes_parenting.main.Inline.after.mir +++ b/tests/mir-opt/inline/issue_76997_inline_scopes_parenting.main.Inline.after.mir @@ -2,8 +2,8 @@ fn main() -> () { let mut _0: (); - let _1: {closure@$DIR/issue_76997_inline_scopes_parenting.rs:10:13: 10:16}; - let mut _2: &{closure@$DIR/issue_76997_inline_scopes_parenting.rs:10:13: 10:16}; + let _1: {closure@$DIR/issue_76997_inline_scopes_parenting.rs:15:13: 15:16}; + let mut _2: &{closure@$DIR/issue_76997_inline_scopes_parenting.rs:15:13: 15:16}; let mut _3: ((),); let mut _4: (); let mut _5: (); @@ -19,7 +19,7 @@ fn main() -> () { bb0: { StorageLive(_1); - _1 = {closure@$DIR/issue_76997_inline_scopes_parenting.rs:10:13: 10:16}; + _1 = {closure@$DIR/issue_76997_inline_scopes_parenting.rs:15:13: 15:16}; StorageLive(_2); _2 = &_1; StorageLive(_3); diff --git a/tests/mir-opt/inline/issue_76997_inline_scopes_parenting.rs b/tests/mir-opt/inline/issue_76997_inline_scopes_parenting.rs index 2a7c931a659ee..2fb363c19044d 100644 --- a/tests/mir-opt/inline/issue_76997_inline_scopes_parenting.rs +++ b/tests/mir-opt/inline/issue_76997_inline_scopes_parenting.rs @@ -3,10 +3,15 @@ // EMIT_MIR issue_76997_inline_scopes_parenting.main.Inline.after.mir fn main() { // CHECK-LABEL: fn main( - // CHECK: scope 2 + // CHECK: scope 1 { + // CHECK-NEXT: debug f + // CHECK-NEXT: scope 2 (inlined main::{closure#0}) { // CHECK-NEXT: debug x - // CHECK-NEXT: scope 3 + // CHECK-NEXT: scope 3 { // CHECK-NEXT: debug y + // CHECK-NEXT: } + // CHECK-NEXT: } + // CHECK-NEXT: } let f = |x| { let y = x; y }; f(()) } From 8076414f89275786ae04035151d54bb5b47bde9f Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Mon, 30 Oct 2023 10:14:43 -0400 Subject: [PATCH 29/32] Don't emit delayed good-path bugs on panic --- compiler/rustc_errors/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index 5e3fdf170bc0c..dd462cc64865f 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -556,7 +556,7 @@ impl Drop for HandlerInner { // instead of "require some error happened". Sadly that isn't ideal, as // lints can be `#[allow]`'d, potentially leading to this triggering. // Also, "good path" should be replaced with a better naming. - if !self.has_any_message() && !self.suppressed_expected_diag { + if !self.has_any_message() && !self.suppressed_expected_diag && !std::thread::panicking() { let bugs = std::mem::replace(&mut self.delayed_good_path_bugs, Vec::new()); self.flush_delayed( bugs, From 147c4a50e0373dc29b8f3bd8d18a21407d9c8fa9 Mon Sep 17 00:00:00 2001 From: yukang Date: Mon, 30 Oct 2023 23:33:48 +0800 Subject: [PATCH 30/32] Refactor: move suggestion functions from demand to suggestions --- compiler/rustc_hir_typeck/src/demand.rs | 1276 +---------------- .../src/fn_ctxt/suggestions.rs | 1269 ++++++++++++++++ 2 files changed, 1272 insertions(+), 1273 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs index abb8322803010..acaa3e02f092f 100644 --- a/compiler/rustc_hir_typeck/src/demand.rs +++ b/compiler/rustc_hir_typeck/src/demand.rs @@ -1,30 +1,21 @@ use crate::FnCtxt; -use rustc_ast::util::parser::PREC_POSTFIX; use rustc_errors::MultiSpan; use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed}; use rustc_hir as hir; -use rustc_hir::def::{CtorKind, Res}; +use rustc_hir::def::Res; use rustc_hir::intravisit::Visitor; -use rustc_hir::lang_items::LangItem; -use rustc_hir::{is_range_literal, Node}; use rustc_infer::infer::{DefineOpaqueTypes, InferOk}; -use rustc_middle::lint::in_external_macro; -use rustc_middle::middle::stability::EvalResult; use rustc_middle::ty::adjustment::AllowTwoPhase; use rustc_middle::ty::error::{ExpectedFound, TypeError}; use rustc_middle::ty::fold::BottomUpFolder; use rustc_middle::ty::print::with_no_trimmed_paths; -use rustc_middle::ty::{self, Article, AssocItem, Ty, TypeAndMut, TypeFoldable, TypeVisitableExt}; +use rustc_middle::ty::{self, AssocItem, Ty, TypeFoldable, TypeVisitableExt}; use rustc_span::symbol::sym; -use rustc_span::{BytePos, Span, DUMMY_SP}; -use rustc_trait_selection::infer::InferCtxtExt as _; +use rustc_span::{Span, DUMMY_SP}; use rustc_trait_selection::traits::ObligationCause; use super::method::probe; -use std::cmp::min; -use std::iter; - impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pub fn emit_type_mismatch_suggestions( &self, @@ -955,301 +946,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); } - pub(crate) fn suggest_coercing_result_via_try_operator( - &self, - err: &mut Diagnostic, - expr: &hir::Expr<'tcx>, - expected: Ty<'tcx>, - found: Ty<'tcx>, - ) -> bool { - let map = self.tcx.hir(); - let returned = matches!( - map.find_parent(expr.hir_id), - Some(hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Ret(_), .. })) - ) || map.get_return_block(expr.hir_id).is_some(); - if returned - && let ty::Adt(e, args_e) = expected.kind() - && let ty::Adt(f, args_f) = found.kind() - && e.did() == f.did() - && Some(e.did()) == self.tcx.get_diagnostic_item(sym::Result) - && let e_ok = args_e.type_at(0) - && let f_ok = args_f.type_at(0) - && self.infcx.can_eq(self.param_env, f_ok, e_ok) - && let e_err = args_e.type_at(1) - && let f_err = args_f.type_at(1) - && self - .infcx - .type_implements_trait( - self.tcx.get_diagnostic_item(sym::Into).unwrap(), - [f_err, e_err], - self.param_env, - ) - .must_apply_modulo_regions() - { - err.multipart_suggestion( - "use `?` to coerce and return an appropriate `Err`, and wrap the resulting value \ - in `Ok` so the expression remains of type `Result`", - vec![ - (expr.span.shrink_to_lo(), "Ok(".to_string()), - (expr.span.shrink_to_hi(), "?)".to_string()), - ], - Applicability::MaybeIncorrect, - ); - return true; - } - false - } - - /// If the expected type is an enum (Issue #55250) with any variants whose - /// sole field is of the found type, suggest such variants. (Issue #42764) - fn suggest_compatible_variants( - &self, - err: &mut Diagnostic, - expr: &hir::Expr<'_>, - expected: Ty<'tcx>, - expr_ty: Ty<'tcx>, - ) -> bool { - if in_external_macro(self.tcx.sess, expr.span) { - return false; - } - if let ty::Adt(expected_adt, args) = expected.kind() { - if let hir::ExprKind::Field(base, ident) = expr.kind { - let base_ty = self.typeck_results.borrow().expr_ty(base); - if self.can_eq(self.param_env, base_ty, expected) - && let Some(base_span) = base.span.find_ancestor_inside(expr.span) - { - err.span_suggestion_verbose( - expr.span.with_lo(base_span.hi()), - format!("consider removing the tuple struct field `{ident}`"), - "", - Applicability::MaybeIncorrect, - ); - return true; - } - } - - // If the expression is of type () and it's the return expression of a block, - // we suggest adding a separate return expression instead. - // (To avoid things like suggesting `Ok(while .. { .. })`.) - if expr_ty.is_unit() { - let mut id = expr.hir_id; - let mut parent; - - // Unroll desugaring, to make sure this works for `for` loops etc. - loop { - parent = self.tcx.hir().parent_id(id); - if let Some(parent_span) = self.tcx.hir().opt_span(parent) { - if parent_span.find_ancestor_inside(expr.span).is_some() { - // The parent node is part of the same span, so is the result of the - // same expansion/desugaring and not the 'real' parent node. - id = parent; - continue; - } - } - break; - } - - if let Some(hir::Node::Block(&hir::Block { - span: block_span, expr: Some(e), .. - })) = self.tcx.hir().find(parent) - { - if e.hir_id == id { - if let Some(span) = expr.span.find_ancestor_inside(block_span) { - let return_suggestions = if self - .tcx - .is_diagnostic_item(sym::Result, expected_adt.did()) - { - vec!["Ok(())"] - } else if self.tcx.is_diagnostic_item(sym::Option, expected_adt.did()) { - vec!["None", "Some(())"] - } else { - return false; - }; - if let Some(indent) = - self.tcx.sess.source_map().indentation_before(span.shrink_to_lo()) - { - // Add a semicolon, except after `}`. - let semicolon = - match self.tcx.sess.source_map().span_to_snippet(span) { - Ok(s) if s.ends_with('}') => "", - _ => ";", - }; - err.span_suggestions( - span.shrink_to_hi(), - "try adding an expression at the end of the block", - return_suggestions - .into_iter() - .map(|r| format!("{semicolon}\n{indent}{r}")), - Applicability::MaybeIncorrect, - ); - } - return true; - } - } - } - } - - let compatible_variants: Vec<(String, _, _, Option)> = expected_adt - .variants() - .iter() - .filter(|variant| { - variant.fields.len() == 1 - }) - .filter_map(|variant| { - let sole_field = &variant.single_field(); - - let field_is_local = sole_field.did.is_local(); - let field_is_accessible = - sole_field.vis.is_accessible_from(expr.hir_id.owner.def_id, self.tcx) - // Skip suggestions for unstable public fields (for example `Pin::pointer`) - && matches!(self.tcx.eval_stability(sole_field.did, None, expr.span, None), EvalResult::Allow | EvalResult::Unmarked); - - if !field_is_local && !field_is_accessible { - return None; - } - - let note_about_variant_field_privacy = (field_is_local && !field_is_accessible) - .then(|| " (its field is private, but it's local to this crate and its privacy can be changed)".to_string()); - - let sole_field_ty = sole_field.ty(self.tcx, args); - if self.can_coerce(expr_ty, sole_field_ty) { - let variant_path = - with_no_trimmed_paths!(self.tcx.def_path_str(variant.def_id)); - // FIXME #56861: DRYer prelude filtering - if let Some(path) = variant_path.strip_prefix("std::prelude::") - && let Some((_, path)) = path.split_once("::") - { - return Some((path.to_string(), variant.ctor_kind(), sole_field.name, note_about_variant_field_privacy)); - } - Some((variant_path, variant.ctor_kind(), sole_field.name, note_about_variant_field_privacy)) - } else { - None - } - }) - .collect(); - - let suggestions_for = |variant: &_, ctor_kind, field_name| { - let prefix = match self.tcx.hir().maybe_get_struct_pattern_shorthand_field(expr) { - Some(ident) => format!("{ident}: "), - None => String::new(), - }; - - let (open, close) = match ctor_kind { - Some(CtorKind::Fn) => ("(".to_owned(), ")"), - None => (format!(" {{ {field_name}: "), " }"), - - // unit variants don't have fields - Some(CtorKind::Const) => unreachable!(), - }; - - // Suggest constructor as deep into the block tree as possible. - // This fixes https://github.com/rust-lang/rust/issues/101065, - // and also just helps make the most minimal suggestions. - let mut expr = expr; - while let hir::ExprKind::Block(block, _) = &expr.kind - && let Some(expr_) = &block.expr - { - expr = expr_ - } - - vec![ - (expr.span.shrink_to_lo(), format!("{prefix}{variant}{open}")), - (expr.span.shrink_to_hi(), close.to_owned()), - ] - }; - - match &compatible_variants[..] { - [] => { /* No variants to format */ } - [(variant, ctor_kind, field_name, note)] => { - // Just a single matching variant. - err.multipart_suggestion_verbose( - format!( - "try wrapping the expression in `{variant}`{note}", - note = note.as_deref().unwrap_or("") - ), - suggestions_for(&**variant, *ctor_kind, *field_name), - Applicability::MaybeIncorrect, - ); - return true; - } - _ => { - // More than one matching variant. - err.multipart_suggestions( - format!( - "try wrapping the expression in a variant of `{}`", - self.tcx.def_path_str(expected_adt.did()) - ), - compatible_variants.into_iter().map( - |(variant, ctor_kind, field_name, _)| { - suggestions_for(&variant, ctor_kind, field_name) - }, - ), - Applicability::MaybeIncorrect, - ); - return true; - } - } - } - - false - } - - fn suggest_non_zero_new_unwrap( - &self, - err: &mut Diagnostic, - expr: &hir::Expr<'_>, - expected: Ty<'tcx>, - expr_ty: Ty<'tcx>, - ) -> bool { - let tcx = self.tcx; - let (adt, unwrap) = match expected.kind() { - // In case Option is wanted, but * is provided, suggest calling new - ty::Adt(adt, args) if tcx.is_diagnostic_item(sym::Option, adt.did()) => { - // Unwrap option - let ty::Adt(adt, _) = args.type_at(0).kind() else { - return false; - }; - - (adt, "") - } - // In case NonZero* is wanted, but * is provided also add `.unwrap()` to satisfy types - ty::Adt(adt, _) => (adt, ".unwrap()"), - _ => return false, - }; - - let map = [ - (sym::NonZeroU8, tcx.types.u8), - (sym::NonZeroU16, tcx.types.u16), - (sym::NonZeroU32, tcx.types.u32), - (sym::NonZeroU64, tcx.types.u64), - (sym::NonZeroU128, tcx.types.u128), - (sym::NonZeroI8, tcx.types.i8), - (sym::NonZeroI16, tcx.types.i16), - (sym::NonZeroI32, tcx.types.i32), - (sym::NonZeroI64, tcx.types.i64), - (sym::NonZeroI128, tcx.types.i128), - ]; - - let Some((s, _)) = map.iter().find(|&&(s, t)| { - self.tcx.is_diagnostic_item(s, adt.did()) && self.can_coerce(expr_ty, t) - }) else { - return false; - }; - - let path = self.tcx.def_path_str(adt.non_enum_variant().def_id); - - err.multipart_suggestion( - format!("consider calling `{s}::new`"), - vec![ - (expr.span.shrink_to_lo(), format!("{path}::new(")), - (expr.span.shrink_to_hi(), format!("){unwrap}")), - ], - Applicability::MaybeIncorrect, - ); - - true - } - pub fn get_conversion_methods( &self, span: Span, @@ -1296,82 +992,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } - /// Identify some cases where `as_ref()` would be appropriate and suggest it. - /// - /// Given the following code: - /// ```compile_fail,E0308 - /// struct Foo; - /// fn takes_ref(_: &Foo) {} - /// let ref opt = Some(Foo); - /// - /// opt.map(|param| takes_ref(param)); - /// ``` - /// Suggest using `opt.as_ref().map(|param| takes_ref(param));` instead. - /// - /// It only checks for `Option` and `Result` and won't work with - /// ```ignore (illustrative) - /// opt.map(|param| { takes_ref(param) }); - /// ``` - fn can_use_as_ref(&self, expr: &hir::Expr<'_>) -> Option<(Vec<(Span, String)>, &'static str)> { - let hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) = expr.kind else { - return None; - }; - - let hir::def::Res::Local(local_id) = path.res else { - return None; - }; - - let local_parent = self.tcx.hir().parent_id(local_id); - let Some(Node::Param(hir::Param { hir_id: param_hir_id, .. })) = - self.tcx.hir().find(local_parent) - else { - return None; - }; - - let param_parent = self.tcx.hir().parent_id(*param_hir_id); - let Some(Node::Expr(hir::Expr { - hir_id: expr_hir_id, - kind: hir::ExprKind::Closure(hir::Closure { fn_decl: closure_fn_decl, .. }), - .. - })) = self.tcx.hir().find(param_parent) - else { - return None; - }; - - let expr_parent = self.tcx.hir().parent_id(*expr_hir_id); - let hir = self.tcx.hir().find(expr_parent); - let closure_params_len = closure_fn_decl.inputs.len(); - let ( - Some(Node::Expr(hir::Expr { - kind: hir::ExprKind::MethodCall(method_path, receiver, ..), - .. - })), - 1, - ) = (hir, closure_params_len) - else { - return None; - }; - - let self_ty = self.typeck_results.borrow().expr_ty(receiver); - let name = method_path.ident.name; - let is_as_ref_able = match self_ty.peel_refs().kind() { - ty::Adt(def, _) => { - (self.tcx.is_diagnostic_item(sym::Option, def.did()) - || self.tcx.is_diagnostic_item(sym::Result, def.did())) - && (name == sym::map || name == sym::and_then) - } - _ => false, - }; - if is_as_ref_able { - Some(( - vec![(method_path.ident.span.shrink_to_lo(), "as_ref().".to_string())], - "consider using `as_ref` instead", - )) - } else { - None - } - } - /// If the given `HirId` corresponds to a block with a trailing expression, return that expression pub(crate) fn maybe_get_block_expr( &self, @@ -1383,21 +1003,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } - /// Returns whether the given expression is an `else if`. - pub(crate) fn is_else_if_block(&self, expr: &hir::Expr<'_>) -> bool { - if let hir::ExprKind::If(..) = expr.kind { - let parent_id = self.tcx.hir().parent_id(expr.hir_id); - if let Some(Node::Expr(hir::Expr { - kind: hir::ExprKind::If(_, _, Some(else_expr)), - .. - })) = self.tcx.hir().find(parent_id) - { - return else_expr.hir_id == expr.hir_id; - } - } - false - } - // Returns whether the given expression is a destruct assignment desugaring. // For example, `(a, b) = (1, &2);` // Here we try to find the pattern binding of the expression, @@ -1422,881 +1027,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return false; } - /// This function is used to determine potential "simple" improvements or users' errors and - /// provide them useful help. For example: - /// - /// ```compile_fail,E0308 - /// fn some_fn(s: &str) {} - /// - /// let x = "hey!".to_owned(); - /// some_fn(x); // error - /// ``` - /// - /// No need to find every potential function which could make a coercion to transform a - /// `String` into a `&str` since a `&` would do the trick! - /// - /// In addition of this check, it also checks between references mutability state. If the - /// expected is mutable but the provided isn't, maybe we could just say "Hey, try with - /// `&mut`!". - pub fn suggest_deref_or_ref( - &self, - expr: &hir::Expr<'tcx>, - checked_ty: Ty<'tcx>, - expected: Ty<'tcx>, - ) -> Option<( - Vec<(Span, String)>, - String, - Applicability, - bool, /* verbose */ - bool, /* suggest `&` or `&mut` type annotation */ - )> { - let sess = self.sess(); - let sp = expr.span; - - // If the span is from an external macro, there's no suggestion we can make. - if in_external_macro(sess, sp) { - return None; - } - - let sm = sess.source_map(); - - let replace_prefix = |s: &str, old: &str, new: &str| { - s.strip_prefix(old).map(|stripped| new.to_string() + stripped) - }; - - // `ExprKind::DropTemps` is semantically irrelevant for these suggestions. - let expr = expr.peel_drop_temps(); - - match (&expr.kind, expected.kind(), checked_ty.kind()) { - (_, &ty::Ref(_, exp, _), &ty::Ref(_, check, _)) => match (exp.kind(), check.kind()) { - (&ty::Str, &ty::Array(arr, _) | &ty::Slice(arr)) if arr == self.tcx.types.u8 => { - if let hir::ExprKind::Lit(_) = expr.kind - && let Ok(src) = sm.span_to_snippet(sp) - && replace_prefix(&src, "b\"", "\"").is_some() - { - let pos = sp.lo() + BytePos(1); - return Some(( - vec![(sp.with_hi(pos), String::new())], - "consider removing the leading `b`".to_string(), - Applicability::MachineApplicable, - true, - false, - )); - } - } - (&ty::Array(arr, _) | &ty::Slice(arr), &ty::Str) if arr == self.tcx.types.u8 => { - if let hir::ExprKind::Lit(_) = expr.kind - && let Ok(src) = sm.span_to_snippet(sp) - && replace_prefix(&src, "\"", "b\"").is_some() - { - return Some(( - vec![(sp.shrink_to_lo(), "b".to_string())], - "consider adding a leading `b`".to_string(), - Applicability::MachineApplicable, - true, - false, - )); - } - } - _ => {} - }, - (_, &ty::Ref(_, _, mutability), _) => { - // Check if it can work when put into a ref. For example: - // - // ``` - // fn bar(x: &mut i32) {} - // - // let x = 0u32; - // bar(&x); // error, expected &mut - // ``` - let ref_ty = match mutability { - hir::Mutability::Mut => { - Ty::new_mut_ref(self.tcx, self.tcx.lifetimes.re_static, checked_ty) - } - hir::Mutability::Not => { - Ty::new_imm_ref(self.tcx, self.tcx.lifetimes.re_static, checked_ty) - } - }; - if self.can_coerce(ref_ty, expected) { - let mut sugg_sp = sp; - if let hir::ExprKind::MethodCall(ref segment, receiver, args, _) = expr.kind { - let clone_trait = - self.tcx.require_lang_item(LangItem::Clone, Some(segment.ident.span)); - if args.is_empty() - && self.typeck_results.borrow().type_dependent_def_id(expr.hir_id).map( - |did| { - let ai = self.tcx.associated_item(did); - ai.trait_container(self.tcx) == Some(clone_trait) - }, - ) == Some(true) - && segment.ident.name == sym::clone - { - // If this expression had a clone call when suggesting borrowing - // we want to suggest removing it because it'd now be unnecessary. - sugg_sp = receiver.span; - } - } - - if let hir::ExprKind::Unary(hir::UnOp::Deref, ref inner) = expr.kind - && let Some(1) = self.deref_steps(expected, checked_ty) - { - // We have `*&T`, check if what was expected was `&T`. - // If so, we may want to suggest removing a `*`. - sugg_sp = sugg_sp.with_hi(inner.span.lo()); - return Some(( - vec![(sugg_sp, String::new())], - "consider removing deref here".to_string(), - Applicability::MachineApplicable, - true, - false, - )); - } - - let needs_parens = match expr.kind { - // parenthesize if needed (Issue #46756) - hir::ExprKind::Cast(_, _) | hir::ExprKind::Binary(_, _, _) => true, - // parenthesize borrows of range literals (Issue #54505) - _ if is_range_literal(expr) => true, - _ => false, - }; - - if let Some((sugg, msg)) = self.can_use_as_ref(expr) { - return Some(( - sugg, - msg.to_string(), - Applicability::MachineApplicable, - true, - false, - )); - } - - let prefix = match self.tcx.hir().maybe_get_struct_pattern_shorthand_field(expr) - { - Some(ident) => format!("{ident}: "), - None => String::new(), - }; - - if let Some(hir::Node::Expr(hir::Expr { - kind: hir::ExprKind::Assign(..), - .. - })) = self.tcx.hir().find_parent(expr.hir_id) - { - if mutability.is_mut() { - // Suppressing this diagnostic, we'll properly print it in `check_expr_assign` - return None; - } - } - - let sugg = mutability.ref_prefix_str(); - let (sugg, verbose) = if needs_parens { - ( - vec![ - (sp.shrink_to_lo(), format!("{prefix}{sugg}(")), - (sp.shrink_to_hi(), ")".to_string()), - ], - false, - ) - } else { - (vec![(sp.shrink_to_lo(), format!("{prefix}{sugg}"))], true) - }; - return Some(( - sugg, - format!("consider {}borrowing here", mutability.mutably_str()), - Applicability::MachineApplicable, - verbose, - false, - )); - } - } - ( - hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, ref expr), - _, - &ty::Ref(_, checked, _), - ) if self.can_sub(self.param_env, checked, expected) => { - let make_sugg = |start: Span, end: BytePos| { - // skip `(` for tuples such as `(c) = (&123)`. - // make sure we won't suggest like `(c) = 123)` which is incorrect. - let sp = sm - .span_extend_while(start.shrink_to_lo(), |c| c == '(' || c.is_whitespace()) - .map_or(start, |s| s.shrink_to_hi()); - Some(( - vec![(sp.with_hi(end), String::new())], - "consider removing the borrow".to_string(), - Applicability::MachineApplicable, - true, - true, - )) - }; - - // We have `&T`, check if what was expected was `T`. If so, - // we may want to suggest removing a `&`. - if sm.is_imported(expr.span) { - // Go through the spans from which this span was expanded, - // and find the one that's pointing inside `sp`. - // - // E.g. for `&format!("")`, where we want the span to the - // `format!()` invocation instead of its expansion. - if let Some(call_span) = - iter::successors(Some(expr.span), |s| s.parent_callsite()) - .find(|&s| sp.contains(s)) - && sm.is_span_accessible(call_span) - { - return make_sugg(sp, call_span.lo()); - } - return None; - } - if sp.contains(expr.span) && sm.is_span_accessible(expr.span) { - return make_sugg(sp, expr.span.lo()); - } - } - ( - _, - &ty::RawPtr(TypeAndMut { ty: ty_b, mutbl: mutbl_b }), - &ty::Ref(_, ty_a, mutbl_a), - ) => { - if let Some(steps) = self.deref_steps(ty_a, ty_b) - // Only suggest valid if dereferencing needed. - && steps > 0 - // The pointer type implements `Copy` trait so the suggestion is always valid. - && let Ok(src) = sm.span_to_snippet(sp) - { - let derefs = "*".repeat(steps); - let old_prefix = mutbl_a.ref_prefix_str(); - let new_prefix = mutbl_b.ref_prefix_str().to_owned() + &derefs; - - let suggestion = replace_prefix(&src, old_prefix, &new_prefix).map(|_| { - // skip `&` or `&mut ` if both mutabilities are mutable - let lo = sp.lo() - + BytePos(min(old_prefix.len(), mutbl_b.ref_prefix_str().len()) as _); - // skip `&` or `&mut ` - let hi = sp.lo() + BytePos(old_prefix.len() as _); - let sp = sp.with_lo(lo).with_hi(hi); - - ( - sp, - format!( - "{}{derefs}", - if mutbl_a != mutbl_b { mutbl_b.prefix_str() } else { "" } - ), - if mutbl_b <= mutbl_a { - Applicability::MachineApplicable - } else { - Applicability::MaybeIncorrect - }, - ) - }); - - if let Some((span, src, applicability)) = suggestion { - return Some(( - vec![(span, src)], - "consider dereferencing".to_string(), - applicability, - true, - false, - )); - } - } - } - _ if sp == expr.span => { - if let Some(mut steps) = self.deref_steps(checked_ty, expected) { - let mut expr = expr.peel_blocks(); - let mut prefix_span = expr.span.shrink_to_lo(); - let mut remove = String::new(); - - // Try peeling off any existing `&` and `&mut` to reach our target type - while steps > 0 { - if let hir::ExprKind::AddrOf(_, mutbl, inner) = expr.kind { - // If the expression has `&`, removing it would fix the error - prefix_span = prefix_span.with_hi(inner.span.lo()); - expr = inner; - remove.push_str(mutbl.ref_prefix_str()); - steps -= 1; - } else { - break; - } - } - // If we've reached our target type with just removing `&`, then just print now. - if steps == 0 && !remove.trim().is_empty() { - return Some(( - vec![(prefix_span, String::new())], - format!("consider removing the `{}`", remove.trim()), - // Do not remove `&&` to get to bool, because it might be something like - // { a } && b, which we have a separate fixup suggestion that is more - // likely correct... - if remove.trim() == "&&" && expected == self.tcx.types.bool { - Applicability::MaybeIncorrect - } else { - Applicability::MachineApplicable - }, - true, - false, - )); - } - - // For this suggestion to make sense, the type would need to be `Copy`, - // or we have to be moving out of a `Box` - if self.type_is_copy_modulo_regions(self.param_env, expected) - // FIXME(compiler-errors): We can actually do this if the checked_ty is - // `steps` layers of boxes, not just one, but this is easier and most likely. - || (checked_ty.is_box() && steps == 1) - // We can always deref a binop that takes its arguments by ref. - || matches!( - self.tcx.hir().get_parent(expr.hir_id), - hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Binary(op, ..), .. }) - if !op.node.is_by_value() - ) - { - let deref_kind = if checked_ty.is_box() { - "unboxing the value" - } else if checked_ty.is_ref() { - "dereferencing the borrow" - } else { - "dereferencing the type" - }; - - // Suggest removing `&` if we have removed any, otherwise suggest just - // dereferencing the remaining number of steps. - let message = if remove.is_empty() { - format!("consider {deref_kind}") - } else { - format!( - "consider removing the `{}` and {} instead", - remove.trim(), - deref_kind - ) - }; - - let prefix = - match self.tcx.hir().maybe_get_struct_pattern_shorthand_field(expr) { - Some(ident) => format!("{ident}: "), - None => String::new(), - }; - - let (span, suggestion) = if self.is_else_if_block(expr) { - // Don't suggest nonsense like `else *if` - return None; - } else if let Some(expr) = self.maybe_get_block_expr(expr) { - // prefix should be empty here.. - (expr.span.shrink_to_lo(), "*".to_string()) - } else { - (prefix_span, format!("{}{}", prefix, "*".repeat(steps))) - }; - if suggestion.trim().is_empty() { - return None; - } - - return Some(( - vec![(span, suggestion)], - message, - Applicability::MachineApplicable, - true, - false, - )); - } - } - } - _ => {} - } - None - } - - pub fn suggest_cast( - &self, - err: &mut Diagnostic, - expr: &hir::Expr<'_>, - checked_ty: Ty<'tcx>, - expected_ty: Ty<'tcx>, - expected_ty_expr: Option<&'tcx hir::Expr<'tcx>>, - ) -> bool { - if self.tcx.sess.source_map().is_imported(expr.span) { - // Ignore if span is from within a macro. - return false; - } - - let Ok(src) = self.tcx.sess.source_map().span_to_snippet(expr.span) else { - return false; - }; - - // If casting this expression to a given numeric type would be appropriate in case of a type - // mismatch. - // - // We want to minimize the amount of casting operations that are suggested, as it can be a - // lossy operation with potentially bad side effects, so we only suggest when encountering - // an expression that indicates that the original type couldn't be directly changed. - // - // For now, don't suggest casting with `as`. - let can_cast = false; - - let mut sugg = vec![]; - - if let Some(hir::Node::ExprField(field)) = self.tcx.hir().find_parent(expr.hir_id) { - // `expr` is a literal field for a struct, only suggest if appropriate - if field.is_shorthand { - // This is a field literal - sugg.push((field.ident.span.shrink_to_lo(), format!("{}: ", field.ident))); - } else { - // Likely a field was meant, but this field wasn't found. Do not suggest anything. - return false; - } - }; - - if let hir::ExprKind::Call(path, args) = &expr.kind - && let (hir::ExprKind::Path(hir::QPath::TypeRelative(base_ty, path_segment)), 1) = - (&path.kind, args.len()) - // `expr` is a conversion like `u32::from(val)`, do not suggest anything (#63697). - && let (hir::TyKind::Path(hir::QPath::Resolved(None, base_ty_path)), sym::from) = - (&base_ty.kind, path_segment.ident.name) - { - if let Some(ident) = &base_ty_path.segments.iter().map(|s| s.ident).next() { - match ident.name { - sym::i128 - | sym::i64 - | sym::i32 - | sym::i16 - | sym::i8 - | sym::u128 - | sym::u64 - | sym::u32 - | sym::u16 - | sym::u8 - | sym::isize - | sym::usize - if base_ty_path.segments.len() == 1 => - { - return false; - } - _ => {} - } - } - } - - let msg = format!( - "you can convert {} `{}` to {} `{}`", - checked_ty.kind().article(), - checked_ty, - expected_ty.kind().article(), - expected_ty, - ); - let cast_msg = format!( - "you can cast {} `{}` to {} `{}`", - checked_ty.kind().article(), - checked_ty, - expected_ty.kind().article(), - expected_ty, - ); - let lit_msg = format!( - "change the type of the numeric literal from `{checked_ty}` to `{expected_ty}`", - ); - - let close_paren = if expr.precedence().order() < PREC_POSTFIX { - sugg.push((expr.span.shrink_to_lo(), "(".to_string())); - ")" - } else { - "" - }; - - let mut cast_suggestion = sugg.clone(); - cast_suggestion.push((expr.span.shrink_to_hi(), format!("{close_paren} as {expected_ty}"))); - let mut into_suggestion = sugg.clone(); - into_suggestion.push((expr.span.shrink_to_hi(), format!("{close_paren}.into()"))); - let mut suffix_suggestion = sugg.clone(); - suffix_suggestion.push(( - if matches!( - (&expected_ty.kind(), &checked_ty.kind()), - (ty::Int(_) | ty::Uint(_), ty::Float(_)) - ) { - // Remove fractional part from literal, for example `42.0f32` into `42` - let src = src.trim_end_matches(&checked_ty.to_string()); - let len = src.split('.').next().unwrap().len(); - expr.span.with_lo(expr.span.lo() + BytePos(len as u32)) - } else { - let len = src.trim_end_matches(&checked_ty.to_string()).len(); - expr.span.with_lo(expr.span.lo() + BytePos(len as u32)) - }, - if expr.precedence().order() < PREC_POSTFIX { - // Readd `)` - format!("{expected_ty})") - } else { - expected_ty.to_string() - }, - )); - let literal_is_ty_suffixed = |expr: &hir::Expr<'_>| { - if let hir::ExprKind::Lit(lit) = &expr.kind { lit.node.is_suffixed() } else { false } - }; - let is_negative_int = - |expr: &hir::Expr<'_>| matches!(expr.kind, hir::ExprKind::Unary(hir::UnOp::Neg, ..)); - let is_uint = |ty: Ty<'_>| matches!(ty.kind(), ty::Uint(..)); - - let in_const_context = self.tcx.hir().is_inside_const_context(expr.hir_id); - - let suggest_fallible_into_or_lhs_from = - |err: &mut Diagnostic, exp_to_found_is_fallible: bool| { - // If we know the expression the expected type is derived from, we might be able - // to suggest a widening conversion rather than a narrowing one (which may - // panic). For example, given x: u8 and y: u32, if we know the span of "x", - // x > y - // can be given the suggestion "u32::from(x) > y" rather than - // "x > y.try_into().unwrap()". - let lhs_expr_and_src = expected_ty_expr.and_then(|expr| { - self.tcx - .sess - .source_map() - .span_to_snippet(expr.span) - .ok() - .map(|src| (expr, src)) - }); - let (msg, suggestion) = if let (Some((lhs_expr, lhs_src)), false) = - (lhs_expr_and_src, exp_to_found_is_fallible) - { - let msg = format!( - "you can convert `{lhs_src}` from `{expected_ty}` to `{checked_ty}`, matching the type of `{src}`", - ); - let suggestion = vec![ - (lhs_expr.span.shrink_to_lo(), format!("{checked_ty}::from(")), - (lhs_expr.span.shrink_to_hi(), ")".to_string()), - ]; - (msg, suggestion) - } else { - let msg = - format!("{} and panic if the converted value doesn't fit", msg.clone()); - let mut suggestion = sugg.clone(); - suggestion.push(( - expr.span.shrink_to_hi(), - format!("{close_paren}.try_into().unwrap()"), - )); - (msg, suggestion) - }; - err.multipart_suggestion_verbose(msg, suggestion, Applicability::MachineApplicable); - }; - - let suggest_to_change_suffix_or_into = - |err: &mut Diagnostic, - found_to_exp_is_fallible: bool, - exp_to_found_is_fallible: bool| { - let exp_is_lhs = expected_ty_expr.is_some_and(|e| self.tcx.hir().is_lhs(e.hir_id)); - - if exp_is_lhs { - return; - } - - let always_fallible = found_to_exp_is_fallible - && (exp_to_found_is_fallible || expected_ty_expr.is_none()); - let msg = if literal_is_ty_suffixed(expr) { - lit_msg.clone() - } else if always_fallible && (is_negative_int(expr) && is_uint(expected_ty)) { - // We now know that converting either the lhs or rhs is fallible. Before we - // suggest a fallible conversion, check if the value can never fit in the - // expected type. - let msg = format!("`{src}` cannot fit into type `{expected_ty}`"); - err.note(msg); - return; - } else if in_const_context { - // Do not recommend `into` or `try_into` in const contexts. - return; - } else if found_to_exp_is_fallible { - return suggest_fallible_into_or_lhs_from(err, exp_to_found_is_fallible); - } else { - msg.clone() - }; - let suggestion = if literal_is_ty_suffixed(expr) { - suffix_suggestion.clone() - } else { - into_suggestion.clone() - }; - err.multipart_suggestion_verbose(msg, suggestion, Applicability::MachineApplicable); - }; - - match (&expected_ty.kind(), &checked_ty.kind()) { - (ty::Int(exp), ty::Int(found)) => { - let (f2e_is_fallible, e2f_is_fallible) = match (exp.bit_width(), found.bit_width()) - { - (Some(exp), Some(found)) if exp < found => (true, false), - (Some(exp), Some(found)) if exp > found => (false, true), - (None, Some(8 | 16)) => (false, true), - (Some(8 | 16), None) => (true, false), - (None, _) | (_, None) => (true, true), - _ => (false, false), - }; - suggest_to_change_suffix_or_into(err, f2e_is_fallible, e2f_is_fallible); - true - } - (ty::Uint(exp), ty::Uint(found)) => { - let (f2e_is_fallible, e2f_is_fallible) = match (exp.bit_width(), found.bit_width()) - { - (Some(exp), Some(found)) if exp < found => (true, false), - (Some(exp), Some(found)) if exp > found => (false, true), - (None, Some(8 | 16)) => (false, true), - (Some(8 | 16), None) => (true, false), - (None, _) | (_, None) => (true, true), - _ => (false, false), - }; - suggest_to_change_suffix_or_into(err, f2e_is_fallible, e2f_is_fallible); - true - } - (&ty::Int(exp), &ty::Uint(found)) => { - let (f2e_is_fallible, e2f_is_fallible) = match (exp.bit_width(), found.bit_width()) - { - (Some(exp), Some(found)) if found < exp => (false, true), - (None, Some(8)) => (false, true), - _ => (true, true), - }; - suggest_to_change_suffix_or_into(err, f2e_is_fallible, e2f_is_fallible); - true - } - (&ty::Uint(exp), &ty::Int(found)) => { - let (f2e_is_fallible, e2f_is_fallible) = match (exp.bit_width(), found.bit_width()) - { - (Some(exp), Some(found)) if found > exp => (true, false), - (Some(8), None) => (true, false), - _ => (true, true), - }; - suggest_to_change_suffix_or_into(err, f2e_is_fallible, e2f_is_fallible); - true - } - (ty::Float(exp), ty::Float(found)) => { - if found.bit_width() < exp.bit_width() { - suggest_to_change_suffix_or_into(err, false, true); - } else if literal_is_ty_suffixed(expr) { - err.multipart_suggestion_verbose( - lit_msg, - suffix_suggestion, - Applicability::MachineApplicable, - ); - } else if can_cast { - // Missing try_into implementation for `f64` to `f32` - err.multipart_suggestion_verbose( - format!("{cast_msg}, producing the closest possible value"), - cast_suggestion, - Applicability::MaybeIncorrect, // lossy conversion - ); - } - true - } - (&ty::Uint(_) | &ty::Int(_), &ty::Float(_)) => { - if literal_is_ty_suffixed(expr) { - err.multipart_suggestion_verbose( - lit_msg, - suffix_suggestion, - Applicability::MachineApplicable, - ); - } else if can_cast { - // Missing try_into implementation for `{float}` to `{integer}` - err.multipart_suggestion_verbose( - format!("{msg}, rounding the float towards zero"), - cast_suggestion, - Applicability::MaybeIncorrect, // lossy conversion - ); - } - true - } - (ty::Float(exp), ty::Uint(found)) => { - // if `found` is `None` (meaning found is `usize`), don't suggest `.into()` - if exp.bit_width() > found.bit_width().unwrap_or(256) { - err.multipart_suggestion_verbose( - format!( - "{msg}, producing the floating point representation of the integer", - ), - into_suggestion, - Applicability::MachineApplicable, - ); - } else if literal_is_ty_suffixed(expr) { - err.multipart_suggestion_verbose( - lit_msg, - suffix_suggestion, - Applicability::MachineApplicable, - ); - } else { - // Missing try_into implementation for `{integer}` to `{float}` - err.multipart_suggestion_verbose( - format!( - "{cast_msg}, producing the floating point representation of the integer, \ - rounded if necessary", - ), - cast_suggestion, - Applicability::MaybeIncorrect, // lossy conversion - ); - } - true - } - (ty::Float(exp), ty::Int(found)) => { - // if `found` is `None` (meaning found is `isize`), don't suggest `.into()` - if exp.bit_width() > found.bit_width().unwrap_or(256) { - err.multipart_suggestion_verbose( - format!( - "{}, producing the floating point representation of the integer", - msg.clone(), - ), - into_suggestion, - Applicability::MachineApplicable, - ); - } else if literal_is_ty_suffixed(expr) { - err.multipart_suggestion_verbose( - lit_msg, - suffix_suggestion, - Applicability::MachineApplicable, - ); - } else { - // Missing try_into implementation for `{integer}` to `{float}` - err.multipart_suggestion_verbose( - format!( - "{}, producing the floating point representation of the integer, \ - rounded if necessary", - &msg, - ), - cast_suggestion, - Applicability::MaybeIncorrect, // lossy conversion - ); - } - true - } - ( - &ty::Uint(ty::UintTy::U32 | ty::UintTy::U64 | ty::UintTy::U128) - | &ty::Int(ty::IntTy::I32 | ty::IntTy::I64 | ty::IntTy::I128), - &ty::Char, - ) => { - err.multipart_suggestion_verbose( - format!("{cast_msg}, since a `char` always occupies 4 bytes"), - cast_suggestion, - Applicability::MachineApplicable, - ); - true - } - _ => false, - } - } - - /// Identify when the user has written `foo..bar()` instead of `foo.bar()`. - pub fn suggest_method_call_on_range_literal( - &self, - err: &mut Diagnostic, - expr: &hir::Expr<'tcx>, - checked_ty: Ty<'tcx>, - expected_ty: Ty<'tcx>, - ) { - if !hir::is_range_literal(expr) { - return; - } - let hir::ExprKind::Struct(hir::QPath::LangItem(LangItem::Range, ..), [start, end], _) = - expr.kind - else { - return; - }; - let parent = self.tcx.hir().parent_id(expr.hir_id); - if let Some(hir::Node::ExprField(_)) = self.tcx.hir().find(parent) { - // Ignore `Foo { field: a..Default::default() }` - return; - } - let mut expr = end.expr; - let mut expectation = Some(expected_ty); - while let hir::ExprKind::MethodCall(_, rcvr, ..) = expr.kind { - // Getting to the root receiver and asserting it is a fn call let's us ignore cases in - // `tests/ui/methods/issues/issue-90315.stderr`. - expr = rcvr; - // If we have more than one layer of calls, then the expected ty - // cannot guide the method probe. - expectation = None; - } - let hir::ExprKind::Call(method_name, _) = expr.kind else { - return; - }; - let ty::Adt(adt, _) = checked_ty.kind() else { - return; - }; - if self.tcx.lang_items().range_struct() != Some(adt.did()) { - return; - } - if let ty::Adt(adt, _) = expected_ty.kind() - && self.tcx.lang_items().range_struct() == Some(adt.did()) - { - return; - } - // Check if start has method named end. - let hir::ExprKind::Path(hir::QPath::Resolved(None, p)) = method_name.kind else { - return; - }; - let [hir::PathSegment { ident, .. }] = p.segments else { - return; - }; - let self_ty = self.typeck_results.borrow().expr_ty(start.expr); - let Ok(_pick) = self.lookup_probe_for_diagnostic( - *ident, - self_ty, - expr, - probe::ProbeScope::AllTraits, - expectation, - ) else { - return; - }; - let mut sugg = "."; - let mut span = start.expr.span.between(end.expr.span); - if span.lo() + BytePos(2) == span.hi() { - // There's no space between the start, the range op and the end, suggest removal which - // will be more noticeable than the replacement of `..` with `.`. - span = span.with_lo(span.lo() + BytePos(1)); - sugg = ""; - } - err.span_suggestion_verbose( - span, - "you likely meant to write a method call instead of a range", - sugg, - Applicability::MachineApplicable, - ); - } - - /// Identify when the type error is because `()` is found in a binding that was assigned a - /// block without a tail expression. - fn suggest_return_binding_for_missing_tail_expr( - &self, - err: &mut Diagnostic, - expr: &hir::Expr<'_>, - checked_ty: Ty<'tcx>, - expected_ty: Ty<'tcx>, - ) { - if !checked_ty.is_unit() { - return; - } - let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind else { - return; - }; - let hir::def::Res::Local(hir_id) = path.res else { - return; - }; - let Some(hir::Node::Pat(pat)) = self.tcx.hir().find(hir_id) else { - return; - }; - let Some(hir::Node::Local(hir::Local { ty: None, init: Some(init), .. })) = - self.tcx.hir().find_parent(pat.hir_id) - else { - return; - }; - let hir::ExprKind::Block(block, None) = init.kind else { - return; - }; - if block.expr.is_some() { - return; - } - let [.., stmt] = block.stmts else { - err.span_label(block.span, "this empty block is missing a tail expression"); - return; - }; - let hir::StmtKind::Semi(tail_expr) = stmt.kind else { - return; - }; - let Some(ty) = self.node_ty_opt(tail_expr.hir_id) else { - return; - }; - if self.can_eq(self.param_env, expected_ty, ty) { - err.span_suggestion_short( - stmt.span.with_lo(tail_expr.span.hi()), - "remove this semicolon", - "", - Applicability::MachineApplicable, - ); - } else { - err.span_label(block.span, "this block is missing a tail expression"); - } - } - fn note_wrong_return_ty_due_to_generic_arg( &self, err: &mut Diagnostic, diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs index ca7dc298de64f..c43d4932fb9f2 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -2,7 +2,14 @@ use super::FnCtxt; use crate::errors; use crate::fluent_generated as fluent; +use crate::fn_ctxt::rustc_span::BytePos; +use crate::hir::is_range_literal; +use crate::method::probe; use crate::method::probe::{IsSuggestion, Mode, ProbeScope}; +use crate::rustc_middle::ty::Article; +use crate::ty::TypeAndMut; +use core::cmp::min; +use core::iter; use rustc_ast::util::parser::{ExprPrecedence, PREC_POSTFIX}; use rustc_errors::{Applicability, Diagnostic, MultiSpan}; use rustc_hir as hir; @@ -16,6 +23,7 @@ use rustc_hir::{ use rustc_hir_analysis::astconv::AstConv; use rustc_infer::traits::{self, StatementAsExpression}; use rustc_middle::lint::in_external_macro; +use rustc_middle::middle::stability::EvalResult; use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::{ self, suggest_constraining_type_params, Binder, IsSuggestable, ToPredicate, Ty, @@ -1816,4 +1824,1265 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); return true; } + + pub(crate) fn suggest_coercing_result_via_try_operator( + &self, + err: &mut Diagnostic, + expr: &hir::Expr<'tcx>, + expected: Ty<'tcx>, + found: Ty<'tcx>, + ) -> bool { + let map = self.tcx.hir(); + let returned = matches!( + map.find_parent(expr.hir_id), + Some(hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Ret(_), .. })) + ) || map.get_return_block(expr.hir_id).is_some(); + if returned + && let ty::Adt(e, args_e) = expected.kind() + && let ty::Adt(f, args_f) = found.kind() + && e.did() == f.did() + && Some(e.did()) == self.tcx.get_diagnostic_item(sym::Result) + && let e_ok = args_e.type_at(0) + && let f_ok = args_f.type_at(0) + && self.infcx.can_eq(self.param_env, f_ok, e_ok) + && let e_err = args_e.type_at(1) + && let f_err = args_f.type_at(1) + && self + .infcx + .type_implements_trait( + self.tcx.get_diagnostic_item(sym::Into).unwrap(), + [f_err, e_err], + self.param_env, + ) + .must_apply_modulo_regions() + { + err.multipart_suggestion( + "use `?` to coerce and return an appropriate `Err`, and wrap the resulting value \ + in `Ok` so the expression remains of type `Result`", + vec![ + (expr.span.shrink_to_lo(), "Ok(".to_string()), + (expr.span.shrink_to_hi(), "?)".to_string()), + ], + Applicability::MaybeIncorrect, + ); + return true; + } + false + } + + /// If the expected type is an enum (Issue #55250) with any variants whose + /// sole field is of the found type, suggest such variants. (Issue #42764) + pub(crate) fn suggest_compatible_variants( + &self, + err: &mut Diagnostic, + expr: &hir::Expr<'_>, + expected: Ty<'tcx>, + expr_ty: Ty<'tcx>, + ) -> bool { + if in_external_macro(self.tcx.sess, expr.span) { + return false; + } + if let ty::Adt(expected_adt, args) = expected.kind() { + if let hir::ExprKind::Field(base, ident) = expr.kind { + let base_ty = self.typeck_results.borrow().expr_ty(base); + if self.can_eq(self.param_env, base_ty, expected) + && let Some(base_span) = base.span.find_ancestor_inside(expr.span) + { + err.span_suggestion_verbose( + expr.span.with_lo(base_span.hi()), + format!("consider removing the tuple struct field `{ident}`"), + "", + Applicability::MaybeIncorrect, + ); + return true; + } + } + + // If the expression is of type () and it's the return expression of a block, + // we suggest adding a separate return expression instead. + // (To avoid things like suggesting `Ok(while .. { .. })`.) + if expr_ty.is_unit() { + let mut id = expr.hir_id; + let mut parent; + + // Unroll desugaring, to make sure this works for `for` loops etc. + loop { + parent = self.tcx.hir().parent_id(id); + if let Some(parent_span) = self.tcx.hir().opt_span(parent) { + if parent_span.find_ancestor_inside(expr.span).is_some() { + // The parent node is part of the same span, so is the result of the + // same expansion/desugaring and not the 'real' parent node. + id = parent; + continue; + } + } + break; + } + + if let Some(hir::Node::Block(&hir::Block { + span: block_span, expr: Some(e), .. + })) = self.tcx.hir().find(parent) + { + if e.hir_id == id { + if let Some(span) = expr.span.find_ancestor_inside(block_span) { + let return_suggestions = if self + .tcx + .is_diagnostic_item(sym::Result, expected_adt.did()) + { + vec!["Ok(())"] + } else if self.tcx.is_diagnostic_item(sym::Option, expected_adt.did()) { + vec!["None", "Some(())"] + } else { + return false; + }; + if let Some(indent) = + self.tcx.sess.source_map().indentation_before(span.shrink_to_lo()) + { + // Add a semicolon, except after `}`. + let semicolon = + match self.tcx.sess.source_map().span_to_snippet(span) { + Ok(s) if s.ends_with('}') => "", + _ => ";", + }; + err.span_suggestions( + span.shrink_to_hi(), + "try adding an expression at the end of the block", + return_suggestions + .into_iter() + .map(|r| format!("{semicolon}\n{indent}{r}")), + Applicability::MaybeIncorrect, + ); + } + return true; + } + } + } + } + + let compatible_variants: Vec<(String, _, _, Option)> = expected_adt + .variants() + .iter() + .filter(|variant| { + variant.fields.len() == 1 + }) + .filter_map(|variant| { + let sole_field = &variant.single_field(); + + let field_is_local = sole_field.did.is_local(); + let field_is_accessible = + sole_field.vis.is_accessible_from(expr.hir_id.owner.def_id, self.tcx) + // Skip suggestions for unstable public fields (for example `Pin::pointer`) + && matches!(self.tcx.eval_stability(sole_field.did, None, expr.span, None), EvalResult::Allow | EvalResult::Unmarked); + + if !field_is_local && !field_is_accessible { + return None; + } + + let note_about_variant_field_privacy = (field_is_local && !field_is_accessible) + .then(|| " (its field is private, but it's local to this crate and its privacy can be changed)".to_string()); + + let sole_field_ty = sole_field.ty(self.tcx, args); + if self.can_coerce(expr_ty, sole_field_ty) { + let variant_path = + with_no_trimmed_paths!(self.tcx.def_path_str(variant.def_id)); + // FIXME #56861: DRYer prelude filtering + if let Some(path) = variant_path.strip_prefix("std::prelude::") + && let Some((_, path)) = path.split_once("::") + { + return Some((path.to_string(), variant.ctor_kind(), sole_field.name, note_about_variant_field_privacy)); + } + Some((variant_path, variant.ctor_kind(), sole_field.name, note_about_variant_field_privacy)) + } else { + None + } + }) + .collect(); + + let suggestions_for = |variant: &_, ctor_kind, field_name| { + let prefix = match self.tcx.hir().maybe_get_struct_pattern_shorthand_field(expr) { + Some(ident) => format!("{ident}: "), + None => String::new(), + }; + + let (open, close) = match ctor_kind { + Some(CtorKind::Fn) => ("(".to_owned(), ")"), + None => (format!(" {{ {field_name}: "), " }"), + + // unit variants don't have fields + Some(CtorKind::Const) => unreachable!(), + }; + + // Suggest constructor as deep into the block tree as possible. + // This fixes https://github.com/rust-lang/rust/issues/101065, + // and also just helps make the most minimal suggestions. + let mut expr = expr; + while let hir::ExprKind::Block(block, _) = &expr.kind + && let Some(expr_) = &block.expr + { + expr = expr_ + } + + vec![ + (expr.span.shrink_to_lo(), format!("{prefix}{variant}{open}")), + (expr.span.shrink_to_hi(), close.to_owned()), + ] + }; + + match &compatible_variants[..] { + [] => { /* No variants to format */ } + [(variant, ctor_kind, field_name, note)] => { + // Just a single matching variant. + err.multipart_suggestion_verbose( + format!( + "try wrapping the expression in `{variant}`{note}", + note = note.as_deref().unwrap_or("") + ), + suggestions_for(&**variant, *ctor_kind, *field_name), + Applicability::MaybeIncorrect, + ); + return true; + } + _ => { + // More than one matching variant. + err.multipart_suggestions( + format!( + "try wrapping the expression in a variant of `{}`", + self.tcx.def_path_str(expected_adt.did()) + ), + compatible_variants.into_iter().map( + |(variant, ctor_kind, field_name, _)| { + suggestions_for(&variant, ctor_kind, field_name) + }, + ), + Applicability::MaybeIncorrect, + ); + return true; + } + } + } + + false + } + + pub(crate) fn suggest_non_zero_new_unwrap( + &self, + err: &mut Diagnostic, + expr: &hir::Expr<'_>, + expected: Ty<'tcx>, + expr_ty: Ty<'tcx>, + ) -> bool { + let tcx = self.tcx; + let (adt, unwrap) = match expected.kind() { + // In case Option is wanted, but * is provided, suggest calling new + ty::Adt(adt, args) if tcx.is_diagnostic_item(sym::Option, adt.did()) => { + // Unwrap option + let ty::Adt(adt, _) = args.type_at(0).kind() else { + return false; + }; + + (adt, "") + } + // In case NonZero* is wanted, but * is provided also add `.unwrap()` to satisfy types + ty::Adt(adt, _) => (adt, ".unwrap()"), + _ => return false, + }; + + let map = [ + (sym::NonZeroU8, tcx.types.u8), + (sym::NonZeroU16, tcx.types.u16), + (sym::NonZeroU32, tcx.types.u32), + (sym::NonZeroU64, tcx.types.u64), + (sym::NonZeroU128, tcx.types.u128), + (sym::NonZeroI8, tcx.types.i8), + (sym::NonZeroI16, tcx.types.i16), + (sym::NonZeroI32, tcx.types.i32), + (sym::NonZeroI64, tcx.types.i64), + (sym::NonZeroI128, tcx.types.i128), + ]; + + let Some((s, _)) = map.iter().find(|&&(s, t)| { + self.tcx.is_diagnostic_item(s, adt.did()) && self.can_coerce(expr_ty, t) + }) else { + return false; + }; + + let path = self.tcx.def_path_str(adt.non_enum_variant().def_id); + + err.multipart_suggestion( + format!("consider calling `{s}::new`"), + vec![ + (expr.span.shrink_to_lo(), format!("{path}::new(")), + (expr.span.shrink_to_hi(), format!("){unwrap}")), + ], + Applicability::MaybeIncorrect, + ); + + true + } + + /// Identify some cases where `as_ref()` would be appropriate and suggest it. + /// + /// Given the following code: + /// ```compile_fail,E0308 + /// struct Foo; + /// fn takes_ref(_: &Foo) {} + /// let ref opt = Some(Foo); + /// + /// opt.map(|param| takes_ref(param)); + /// ``` + /// Suggest using `opt.as_ref().map(|param| takes_ref(param));` instead. + /// + /// It only checks for `Option` and `Result` and won't work with + /// ```ignore (illustrative) + /// opt.map(|param| { takes_ref(param) }); + /// ``` + fn can_use_as_ref(&self, expr: &hir::Expr<'_>) -> Option<(Vec<(Span, String)>, &'static str)> { + let hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) = expr.kind else { + return None; + }; + + let hir::def::Res::Local(local_id) = path.res else { + return None; + }; + + let local_parent = self.tcx.hir().parent_id(local_id); + let Some(Node::Param(hir::Param { hir_id: param_hir_id, .. })) = + self.tcx.hir().find(local_parent) + else { + return None; + }; + + let param_parent = self.tcx.hir().parent_id(*param_hir_id); + let Some(Node::Expr(hir::Expr { + hir_id: expr_hir_id, + kind: hir::ExprKind::Closure(hir::Closure { fn_decl: closure_fn_decl, .. }), + .. + })) = self.tcx.hir().find(param_parent) + else { + return None; + }; + + let expr_parent = self.tcx.hir().parent_id(*expr_hir_id); + let hir = self.tcx.hir().find(expr_parent); + let closure_params_len = closure_fn_decl.inputs.len(); + let ( + Some(Node::Expr(hir::Expr { + kind: hir::ExprKind::MethodCall(method_path, receiver, ..), + .. + })), + 1, + ) = (hir, closure_params_len) + else { + return None; + }; + + let self_ty = self.typeck_results.borrow().expr_ty(receiver); + let name = method_path.ident.name; + let is_as_ref_able = match self_ty.peel_refs().kind() { + ty::Adt(def, _) => { + (self.tcx.is_diagnostic_item(sym::Option, def.did()) + || self.tcx.is_diagnostic_item(sym::Result, def.did())) + && (name == sym::map || name == sym::and_then) + } + _ => false, + }; + if is_as_ref_able { + Some(( + vec![(method_path.ident.span.shrink_to_lo(), "as_ref().".to_string())], + "consider using `as_ref` instead", + )) + } else { + None + } + } + + /// This function is used to determine potential "simple" improvements or users' errors and + /// provide them useful help. For example: + /// + /// ```compile_fail,E0308 + /// fn some_fn(s: &str) {} + /// + /// let x = "hey!".to_owned(); + /// some_fn(x); // error + /// ``` + /// + /// No need to find every potential function which could make a coercion to transform a + /// `String` into a `&str` since a `&` would do the trick! + /// + /// In addition of this check, it also checks between references mutability state. If the + /// expected is mutable but the provided isn't, maybe we could just say "Hey, try with + /// `&mut`!". + pub(crate) fn suggest_deref_or_ref( + &self, + expr: &hir::Expr<'tcx>, + checked_ty: Ty<'tcx>, + expected: Ty<'tcx>, + ) -> Option<( + Vec<(Span, String)>, + String, + Applicability, + bool, /* verbose */ + bool, /* suggest `&` or `&mut` type annotation */ + )> { + let sess = self.sess(); + let sp = expr.span; + + // If the span is from an external macro, there's no suggestion we can make. + if in_external_macro(sess, sp) { + return None; + } + + let sm = sess.source_map(); + + let replace_prefix = |s: &str, old: &str, new: &str| { + s.strip_prefix(old).map(|stripped| new.to_string() + stripped) + }; + + // `ExprKind::DropTemps` is semantically irrelevant for these suggestions. + let expr = expr.peel_drop_temps(); + + match (&expr.kind, expected.kind(), checked_ty.kind()) { + (_, &ty::Ref(_, exp, _), &ty::Ref(_, check, _)) => match (exp.kind(), check.kind()) { + (&ty::Str, &ty::Array(arr, _) | &ty::Slice(arr)) if arr == self.tcx.types.u8 => { + if let hir::ExprKind::Lit(_) = expr.kind + && let Ok(src) = sm.span_to_snippet(sp) + && replace_prefix(&src, "b\"", "\"").is_some() + { + let pos = sp.lo() + BytePos(1); + return Some(( + vec![(sp.with_hi(pos), String::new())], + "consider removing the leading `b`".to_string(), + Applicability::MachineApplicable, + true, + false, + )); + } + } + (&ty::Array(arr, _) | &ty::Slice(arr), &ty::Str) if arr == self.tcx.types.u8 => { + if let hir::ExprKind::Lit(_) = expr.kind + && let Ok(src) = sm.span_to_snippet(sp) + && replace_prefix(&src, "\"", "b\"").is_some() + { + return Some(( + vec![(sp.shrink_to_lo(), "b".to_string())], + "consider adding a leading `b`".to_string(), + Applicability::MachineApplicable, + true, + false, + )); + } + } + _ => {} + }, + (_, &ty::Ref(_, _, mutability), _) => { + // Check if it can work when put into a ref. For example: + // + // ``` + // fn bar(x: &mut i32) {} + // + // let x = 0u32; + // bar(&x); // error, expected &mut + // ``` + let ref_ty = match mutability { + hir::Mutability::Mut => { + Ty::new_mut_ref(self.tcx, self.tcx.lifetimes.re_static, checked_ty) + } + hir::Mutability::Not => { + Ty::new_imm_ref(self.tcx, self.tcx.lifetimes.re_static, checked_ty) + } + }; + if self.can_coerce(ref_ty, expected) { + let mut sugg_sp = sp; + if let hir::ExprKind::MethodCall(ref segment, receiver, args, _) = expr.kind { + let clone_trait = + self.tcx.require_lang_item(LangItem::Clone, Some(segment.ident.span)); + if args.is_empty() + && self.typeck_results.borrow().type_dependent_def_id(expr.hir_id).map( + |did| { + let ai = self.tcx.associated_item(did); + ai.trait_container(self.tcx) == Some(clone_trait) + }, + ) == Some(true) + && segment.ident.name == sym::clone + { + // If this expression had a clone call when suggesting borrowing + // we want to suggest removing it because it'd now be unnecessary. + sugg_sp = receiver.span; + } + } + + if let hir::ExprKind::Unary(hir::UnOp::Deref, ref inner) = expr.kind + && let Some(1) = self.deref_steps(expected, checked_ty) + { + // We have `*&T`, check if what was expected was `&T`. + // If so, we may want to suggest removing a `*`. + sugg_sp = sugg_sp.with_hi(inner.span.lo()); + return Some(( + vec![(sugg_sp, String::new())], + "consider removing deref here".to_string(), + Applicability::MachineApplicable, + true, + false, + )); + } + + let needs_parens = match expr.kind { + // parenthesize if needed (Issue #46756) + hir::ExprKind::Cast(_, _) | hir::ExprKind::Binary(_, _, _) => true, + // parenthesize borrows of range literals (Issue #54505) + _ if is_range_literal(expr) => true, + _ => false, + }; + + if let Some((sugg, msg)) = self.can_use_as_ref(expr) { + return Some(( + sugg, + msg.to_string(), + Applicability::MachineApplicable, + true, + false, + )); + } + + let prefix = match self.tcx.hir().maybe_get_struct_pattern_shorthand_field(expr) + { + Some(ident) => format!("{ident}: "), + None => String::new(), + }; + + if let Some(hir::Node::Expr(hir::Expr { + kind: hir::ExprKind::Assign(..), + .. + })) = self.tcx.hir().find_parent(expr.hir_id) + { + if mutability.is_mut() { + // Suppressing this diagnostic, we'll properly print it in `check_expr_assign` + return None; + } + } + + let sugg = mutability.ref_prefix_str(); + let (sugg, verbose) = if needs_parens { + ( + vec![ + (sp.shrink_to_lo(), format!("{prefix}{sugg}(")), + (sp.shrink_to_hi(), ")".to_string()), + ], + false, + ) + } else { + (vec![(sp.shrink_to_lo(), format!("{prefix}{sugg}"))], true) + }; + return Some(( + sugg, + format!("consider {}borrowing here", mutability.mutably_str()), + Applicability::MachineApplicable, + verbose, + false, + )); + } + } + ( + hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, ref expr), + _, + &ty::Ref(_, checked, _), + ) if self.can_sub(self.param_env, checked, expected) => { + let make_sugg = |start: Span, end: BytePos| { + // skip `(` for tuples such as `(c) = (&123)`. + // make sure we won't suggest like `(c) = 123)` which is incorrect. + let sp = sm + .span_extend_while(start.shrink_to_lo(), |c| c == '(' || c.is_whitespace()) + .map_or(start, |s| s.shrink_to_hi()); + Some(( + vec![(sp.with_hi(end), String::new())], + "consider removing the borrow".to_string(), + Applicability::MachineApplicable, + true, + true, + )) + }; + + // We have `&T`, check if what was expected was `T`. If so, + // we may want to suggest removing a `&`. + if sm.is_imported(expr.span) { + // Go through the spans from which this span was expanded, + // and find the one that's pointing inside `sp`. + // + // E.g. for `&format!("")`, where we want the span to the + // `format!()` invocation instead of its expansion. + if let Some(call_span) = + iter::successors(Some(expr.span), |s| s.parent_callsite()) + .find(|&s| sp.contains(s)) + && sm.is_span_accessible(call_span) + { + return make_sugg(sp, call_span.lo()); + } + return None; + } + if sp.contains(expr.span) && sm.is_span_accessible(expr.span) { + return make_sugg(sp, expr.span.lo()); + } + } + ( + _, + &ty::RawPtr(TypeAndMut { ty: ty_b, mutbl: mutbl_b }), + &ty::Ref(_, ty_a, mutbl_a), + ) => { + if let Some(steps) = self.deref_steps(ty_a, ty_b) + // Only suggest valid if dereferencing needed. + && steps > 0 + // The pointer type implements `Copy` trait so the suggestion is always valid. + && let Ok(src) = sm.span_to_snippet(sp) + { + let derefs = "*".repeat(steps); + let old_prefix = mutbl_a.ref_prefix_str(); + let new_prefix = mutbl_b.ref_prefix_str().to_owned() + &derefs; + + let suggestion = replace_prefix(&src, old_prefix, &new_prefix).map(|_| { + // skip `&` or `&mut ` if both mutabilities are mutable + let lo = sp.lo() + + BytePos(min(old_prefix.len(), mutbl_b.ref_prefix_str().len()) as _); + // skip `&` or `&mut ` + let hi = sp.lo() + BytePos(old_prefix.len() as _); + let sp = sp.with_lo(lo).with_hi(hi); + + ( + sp, + format!( + "{}{derefs}", + if mutbl_a != mutbl_b { mutbl_b.prefix_str() } else { "" } + ), + if mutbl_b <= mutbl_a { + Applicability::MachineApplicable + } else { + Applicability::MaybeIncorrect + }, + ) + }); + + if let Some((span, src, applicability)) = suggestion { + return Some(( + vec![(span, src)], + "consider dereferencing".to_string(), + applicability, + true, + false, + )); + } + } + } + _ if sp == expr.span => { + if let Some(mut steps) = self.deref_steps(checked_ty, expected) { + let mut expr = expr.peel_blocks(); + let mut prefix_span = expr.span.shrink_to_lo(); + let mut remove = String::new(); + + // Try peeling off any existing `&` and `&mut` to reach our target type + while steps > 0 { + if let hir::ExprKind::AddrOf(_, mutbl, inner) = expr.kind { + // If the expression has `&`, removing it would fix the error + prefix_span = prefix_span.with_hi(inner.span.lo()); + expr = inner; + remove.push_str(mutbl.ref_prefix_str()); + steps -= 1; + } else { + break; + } + } + // If we've reached our target type with just removing `&`, then just print now. + if steps == 0 && !remove.trim().is_empty() { + return Some(( + vec![(prefix_span, String::new())], + format!("consider removing the `{}`", remove.trim()), + // Do not remove `&&` to get to bool, because it might be something like + // { a } && b, which we have a separate fixup suggestion that is more + // likely correct... + if remove.trim() == "&&" && expected == self.tcx.types.bool { + Applicability::MaybeIncorrect + } else { + Applicability::MachineApplicable + }, + true, + false, + )); + } + + // For this suggestion to make sense, the type would need to be `Copy`, + // or we have to be moving out of a `Box` + if self.type_is_copy_modulo_regions(self.param_env, expected) + // FIXME(compiler-errors): We can actually do this if the checked_ty is + // `steps` layers of boxes, not just one, but this is easier and most likely. + || (checked_ty.is_box() && steps == 1) + // We can always deref a binop that takes its arguments by ref. + || matches!( + self.tcx.hir().get_parent(expr.hir_id), + hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Binary(op, ..), .. }) + if !op.node.is_by_value() + ) + { + let deref_kind = if checked_ty.is_box() { + "unboxing the value" + } else if checked_ty.is_ref() { + "dereferencing the borrow" + } else { + "dereferencing the type" + }; + + // Suggest removing `&` if we have removed any, otherwise suggest just + // dereferencing the remaining number of steps. + let message = if remove.is_empty() { + format!("consider {deref_kind}") + } else { + format!( + "consider removing the `{}` and {} instead", + remove.trim(), + deref_kind + ) + }; + + let prefix = + match self.tcx.hir().maybe_get_struct_pattern_shorthand_field(expr) { + Some(ident) => format!("{ident}: "), + None => String::new(), + }; + + let (span, suggestion) = if self.is_else_if_block(expr) { + // Don't suggest nonsense like `else *if` + return None; + } else if let Some(expr) = self.maybe_get_block_expr(expr) { + // prefix should be empty here.. + (expr.span.shrink_to_lo(), "*".to_string()) + } else { + (prefix_span, format!("{}{}", prefix, "*".repeat(steps))) + }; + if suggestion.trim().is_empty() { + return None; + } + + return Some(( + vec![(span, suggestion)], + message, + Applicability::MachineApplicable, + true, + false, + )); + } + } + } + _ => {} + } + None + } + + /// Returns whether the given expression is an `else if`. + fn is_else_if_block(&self, expr: &hir::Expr<'_>) -> bool { + if let hir::ExprKind::If(..) = expr.kind { + let parent_id = self.tcx.hir().parent_id(expr.hir_id); + if let Some(Node::Expr(hir::Expr { + kind: hir::ExprKind::If(_, _, Some(else_expr)), + .. + })) = self.tcx.hir().find(parent_id) + { + return else_expr.hir_id == expr.hir_id; + } + } + false + } + + pub(crate) fn suggest_cast( + &self, + err: &mut Diagnostic, + expr: &hir::Expr<'_>, + checked_ty: Ty<'tcx>, + expected_ty: Ty<'tcx>, + expected_ty_expr: Option<&'tcx hir::Expr<'tcx>>, + ) -> bool { + if self.tcx.sess.source_map().is_imported(expr.span) { + // Ignore if span is from within a macro. + return false; + } + + let Ok(src) = self.tcx.sess.source_map().span_to_snippet(expr.span) else { + return false; + }; + + // If casting this expression to a given numeric type would be appropriate in case of a type + // mismatch. + // + // We want to minimize the amount of casting operations that are suggested, as it can be a + // lossy operation with potentially bad side effects, so we only suggest when encountering + // an expression that indicates that the original type couldn't be directly changed. + // + // For now, don't suggest casting with `as`. + let can_cast = false; + + let mut sugg = vec![]; + + if let Some(hir::Node::ExprField(field)) = self.tcx.hir().find_parent(expr.hir_id) { + // `expr` is a literal field for a struct, only suggest if appropriate + if field.is_shorthand { + // This is a field literal + sugg.push((field.ident.span.shrink_to_lo(), format!("{}: ", field.ident))); + } else { + // Likely a field was meant, but this field wasn't found. Do not suggest anything. + return false; + } + }; + + if let hir::ExprKind::Call(path, args) = &expr.kind + && let (hir::ExprKind::Path(hir::QPath::TypeRelative(base_ty, path_segment)), 1) = + (&path.kind, args.len()) + // `expr` is a conversion like `u32::from(val)`, do not suggest anything (#63697). + && let (hir::TyKind::Path(hir::QPath::Resolved(None, base_ty_path)), sym::from) = + (&base_ty.kind, path_segment.ident.name) + { + if let Some(ident) = &base_ty_path.segments.iter().map(|s| s.ident).next() { + match ident.name { + sym::i128 + | sym::i64 + | sym::i32 + | sym::i16 + | sym::i8 + | sym::u128 + | sym::u64 + | sym::u32 + | sym::u16 + | sym::u8 + | sym::isize + | sym::usize + if base_ty_path.segments.len() == 1 => + { + return false; + } + _ => {} + } + } + } + + let msg = format!( + "you can convert {} `{}` to {} `{}`", + checked_ty.kind().article(), + checked_ty, + expected_ty.kind().article(), + expected_ty, + ); + let cast_msg = format!( + "you can cast {} `{}` to {} `{}`", + checked_ty.kind().article(), + checked_ty, + expected_ty.kind().article(), + expected_ty, + ); + let lit_msg = format!( + "change the type of the numeric literal from `{checked_ty}` to `{expected_ty}`", + ); + + let close_paren = if expr.precedence().order() < PREC_POSTFIX { + sugg.push((expr.span.shrink_to_lo(), "(".to_string())); + ")" + } else { + "" + }; + + let mut cast_suggestion = sugg.clone(); + cast_suggestion.push((expr.span.shrink_to_hi(), format!("{close_paren} as {expected_ty}"))); + let mut into_suggestion = sugg.clone(); + into_suggestion.push((expr.span.shrink_to_hi(), format!("{close_paren}.into()"))); + let mut suffix_suggestion = sugg.clone(); + suffix_suggestion.push(( + if matches!( + (&expected_ty.kind(), &checked_ty.kind()), + (ty::Int(_) | ty::Uint(_), ty::Float(_)) + ) { + // Remove fractional part from literal, for example `42.0f32` into `42` + let src = src.trim_end_matches(&checked_ty.to_string()); + let len = src.split('.').next().unwrap().len(); + expr.span.with_lo(expr.span.lo() + BytePos(len as u32)) + } else { + let len = src.trim_end_matches(&checked_ty.to_string()).len(); + expr.span.with_lo(expr.span.lo() + BytePos(len as u32)) + }, + if expr.precedence().order() < PREC_POSTFIX { + // Readd `)` + format!("{expected_ty})") + } else { + expected_ty.to_string() + }, + )); + let literal_is_ty_suffixed = |expr: &hir::Expr<'_>| { + if let hir::ExprKind::Lit(lit) = &expr.kind { lit.node.is_suffixed() } else { false } + }; + let is_negative_int = + |expr: &hir::Expr<'_>| matches!(expr.kind, hir::ExprKind::Unary(hir::UnOp::Neg, ..)); + let is_uint = |ty: Ty<'_>| matches!(ty.kind(), ty::Uint(..)); + + let in_const_context = self.tcx.hir().is_inside_const_context(expr.hir_id); + + let suggest_fallible_into_or_lhs_from = + |err: &mut Diagnostic, exp_to_found_is_fallible: bool| { + // If we know the expression the expected type is derived from, we might be able + // to suggest a widening conversion rather than a narrowing one (which may + // panic). For example, given x: u8 and y: u32, if we know the span of "x", + // x > y + // can be given the suggestion "u32::from(x) > y" rather than + // "x > y.try_into().unwrap()". + let lhs_expr_and_src = expected_ty_expr.and_then(|expr| { + self.tcx + .sess + .source_map() + .span_to_snippet(expr.span) + .ok() + .map(|src| (expr, src)) + }); + let (msg, suggestion) = if let (Some((lhs_expr, lhs_src)), false) = + (lhs_expr_and_src, exp_to_found_is_fallible) + { + let msg = format!( + "you can convert `{lhs_src}` from `{expected_ty}` to `{checked_ty}`, matching the type of `{src}`", + ); + let suggestion = vec![ + (lhs_expr.span.shrink_to_lo(), format!("{checked_ty}::from(")), + (lhs_expr.span.shrink_to_hi(), ")".to_string()), + ]; + (msg, suggestion) + } else { + let msg = + format!("{} and panic if the converted value doesn't fit", msg.clone()); + let mut suggestion = sugg.clone(); + suggestion.push(( + expr.span.shrink_to_hi(), + format!("{close_paren}.try_into().unwrap()"), + )); + (msg, suggestion) + }; + err.multipart_suggestion_verbose(msg, suggestion, Applicability::MachineApplicable); + }; + + let suggest_to_change_suffix_or_into = + |err: &mut Diagnostic, + found_to_exp_is_fallible: bool, + exp_to_found_is_fallible: bool| { + let exp_is_lhs = expected_ty_expr.is_some_and(|e| self.tcx.hir().is_lhs(e.hir_id)); + + if exp_is_lhs { + return; + } + + let always_fallible = found_to_exp_is_fallible + && (exp_to_found_is_fallible || expected_ty_expr.is_none()); + let msg = if literal_is_ty_suffixed(expr) { + lit_msg.clone() + } else if always_fallible && (is_negative_int(expr) && is_uint(expected_ty)) { + // We now know that converting either the lhs or rhs is fallible. Before we + // suggest a fallible conversion, check if the value can never fit in the + // expected type. + let msg = format!("`{src}` cannot fit into type `{expected_ty}`"); + err.note(msg); + return; + } else if in_const_context { + // Do not recommend `into` or `try_into` in const contexts. + return; + } else if found_to_exp_is_fallible { + return suggest_fallible_into_or_lhs_from(err, exp_to_found_is_fallible); + } else { + msg.clone() + }; + let suggestion = if literal_is_ty_suffixed(expr) { + suffix_suggestion.clone() + } else { + into_suggestion.clone() + }; + err.multipart_suggestion_verbose(msg, suggestion, Applicability::MachineApplicable); + }; + + match (&expected_ty.kind(), &checked_ty.kind()) { + (ty::Int(exp), ty::Int(found)) => { + let (f2e_is_fallible, e2f_is_fallible) = match (exp.bit_width(), found.bit_width()) + { + (Some(exp), Some(found)) if exp < found => (true, false), + (Some(exp), Some(found)) if exp > found => (false, true), + (None, Some(8 | 16)) => (false, true), + (Some(8 | 16), None) => (true, false), + (None, _) | (_, None) => (true, true), + _ => (false, false), + }; + suggest_to_change_suffix_or_into(err, f2e_is_fallible, e2f_is_fallible); + true + } + (ty::Uint(exp), ty::Uint(found)) => { + let (f2e_is_fallible, e2f_is_fallible) = match (exp.bit_width(), found.bit_width()) + { + (Some(exp), Some(found)) if exp < found => (true, false), + (Some(exp), Some(found)) if exp > found => (false, true), + (None, Some(8 | 16)) => (false, true), + (Some(8 | 16), None) => (true, false), + (None, _) | (_, None) => (true, true), + _ => (false, false), + }; + suggest_to_change_suffix_or_into(err, f2e_is_fallible, e2f_is_fallible); + true + } + (&ty::Int(exp), &ty::Uint(found)) => { + let (f2e_is_fallible, e2f_is_fallible) = match (exp.bit_width(), found.bit_width()) + { + (Some(exp), Some(found)) if found < exp => (false, true), + (None, Some(8)) => (false, true), + _ => (true, true), + }; + suggest_to_change_suffix_or_into(err, f2e_is_fallible, e2f_is_fallible); + true + } + (&ty::Uint(exp), &ty::Int(found)) => { + let (f2e_is_fallible, e2f_is_fallible) = match (exp.bit_width(), found.bit_width()) + { + (Some(exp), Some(found)) if found > exp => (true, false), + (Some(8), None) => (true, false), + _ => (true, true), + }; + suggest_to_change_suffix_or_into(err, f2e_is_fallible, e2f_is_fallible); + true + } + (ty::Float(exp), ty::Float(found)) => { + if found.bit_width() < exp.bit_width() { + suggest_to_change_suffix_or_into(err, false, true); + } else if literal_is_ty_suffixed(expr) { + err.multipart_suggestion_verbose( + lit_msg, + suffix_suggestion, + Applicability::MachineApplicable, + ); + } else if can_cast { + // Missing try_into implementation for `f64` to `f32` + err.multipart_suggestion_verbose( + format!("{cast_msg}, producing the closest possible value"), + cast_suggestion, + Applicability::MaybeIncorrect, // lossy conversion + ); + } + true + } + (&ty::Uint(_) | &ty::Int(_), &ty::Float(_)) => { + if literal_is_ty_suffixed(expr) { + err.multipart_suggestion_verbose( + lit_msg, + suffix_suggestion, + Applicability::MachineApplicable, + ); + } else if can_cast { + // Missing try_into implementation for `{float}` to `{integer}` + err.multipart_suggestion_verbose( + format!("{msg}, rounding the float towards zero"), + cast_suggestion, + Applicability::MaybeIncorrect, // lossy conversion + ); + } + true + } + (ty::Float(exp), ty::Uint(found)) => { + // if `found` is `None` (meaning found is `usize`), don't suggest `.into()` + if exp.bit_width() > found.bit_width().unwrap_or(256) { + err.multipart_suggestion_verbose( + format!( + "{msg}, producing the floating point representation of the integer", + ), + into_suggestion, + Applicability::MachineApplicable, + ); + } else if literal_is_ty_suffixed(expr) { + err.multipart_suggestion_verbose( + lit_msg, + suffix_suggestion, + Applicability::MachineApplicable, + ); + } else { + // Missing try_into implementation for `{integer}` to `{float}` + err.multipart_suggestion_verbose( + format!( + "{cast_msg}, producing the floating point representation of the integer, \ + rounded if necessary", + ), + cast_suggestion, + Applicability::MaybeIncorrect, // lossy conversion + ); + } + true + } + (ty::Float(exp), ty::Int(found)) => { + // if `found` is `None` (meaning found is `isize`), don't suggest `.into()` + if exp.bit_width() > found.bit_width().unwrap_or(256) { + err.multipart_suggestion_verbose( + format!( + "{}, producing the floating point representation of the integer", + msg.clone(), + ), + into_suggestion, + Applicability::MachineApplicable, + ); + } else if literal_is_ty_suffixed(expr) { + err.multipart_suggestion_verbose( + lit_msg, + suffix_suggestion, + Applicability::MachineApplicable, + ); + } else { + // Missing try_into implementation for `{integer}` to `{float}` + err.multipart_suggestion_verbose( + format!( + "{}, producing the floating point representation of the integer, \ + rounded if necessary", + &msg, + ), + cast_suggestion, + Applicability::MaybeIncorrect, // lossy conversion + ); + } + true + } + ( + &ty::Uint(ty::UintTy::U32 | ty::UintTy::U64 | ty::UintTy::U128) + | &ty::Int(ty::IntTy::I32 | ty::IntTy::I64 | ty::IntTy::I128), + &ty::Char, + ) => { + err.multipart_suggestion_verbose( + format!("{cast_msg}, since a `char` always occupies 4 bytes"), + cast_suggestion, + Applicability::MachineApplicable, + ); + true + } + _ => false, + } + } + + /// Identify when the user has written `foo..bar()` instead of `foo.bar()`. + pub(crate) fn suggest_method_call_on_range_literal( + &self, + err: &mut Diagnostic, + expr: &hir::Expr<'tcx>, + checked_ty: Ty<'tcx>, + expected_ty: Ty<'tcx>, + ) { + if !hir::is_range_literal(expr) { + return; + } + let hir::ExprKind::Struct(hir::QPath::LangItem(LangItem::Range, ..), [start, end], _) = + expr.kind + else { + return; + }; + let parent = self.tcx.hir().parent_id(expr.hir_id); + if let Some(hir::Node::ExprField(_)) = self.tcx.hir().find(parent) { + // Ignore `Foo { field: a..Default::default() }` + return; + } + let mut expr = end.expr; + let mut expectation = Some(expected_ty); + while let hir::ExprKind::MethodCall(_, rcvr, ..) = expr.kind { + // Getting to the root receiver and asserting it is a fn call let's us ignore cases in + // `tests/ui/methods/issues/issue-90315.stderr`. + expr = rcvr; + // If we have more than one layer of calls, then the expected ty + // cannot guide the method probe. + expectation = None; + } + let hir::ExprKind::Call(method_name, _) = expr.kind else { + return; + }; + let ty::Adt(adt, _) = checked_ty.kind() else { + return; + }; + if self.tcx.lang_items().range_struct() != Some(adt.did()) { + return; + } + if let ty::Adt(adt, _) = expected_ty.kind() + && self.tcx.lang_items().range_struct() == Some(adt.did()) + { + return; + } + // Check if start has method named end. + let hir::ExprKind::Path(hir::QPath::Resolved(None, p)) = method_name.kind else { + return; + }; + let [hir::PathSegment { ident, .. }] = p.segments else { + return; + }; + let self_ty = self.typeck_results.borrow().expr_ty(start.expr); + let Ok(_pick) = self.lookup_probe_for_diagnostic( + *ident, + self_ty, + expr, + probe::ProbeScope::AllTraits, + expectation, + ) else { + return; + }; + let mut sugg = "."; + let mut span = start.expr.span.between(end.expr.span); + if span.lo() + BytePos(2) == span.hi() { + // There's no space between the start, the range op and the end, suggest removal which + // will be more noticeable than the replacement of `..` with `.`. + span = span.with_lo(span.lo() + BytePos(1)); + sugg = ""; + } + err.span_suggestion_verbose( + span, + "you likely meant to write a method call instead of a range", + sugg, + Applicability::MachineApplicable, + ); + } + + /// Identify when the type error is because `()` is found in a binding that was assigned a + /// block without a tail expression. + pub(crate) fn suggest_return_binding_for_missing_tail_expr( + &self, + err: &mut Diagnostic, + expr: &hir::Expr<'_>, + checked_ty: Ty<'tcx>, + expected_ty: Ty<'tcx>, + ) { + if !checked_ty.is_unit() { + return; + } + let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind else { + return; + }; + let hir::def::Res::Local(hir_id) = path.res else { + return; + }; + let Some(hir::Node::Pat(pat)) = self.tcx.hir().find(hir_id) else { + return; + }; + let Some(hir::Node::Local(hir::Local { ty: None, init: Some(init), .. })) = + self.tcx.hir().find_parent(pat.hir_id) + else { + return; + }; + let hir::ExprKind::Block(block, None) = init.kind else { + return; + }; + if block.expr.is_some() { + return; + } + let [.., stmt] = block.stmts else { + err.span_label(block.span, "this empty block is missing a tail expression"); + return; + }; + let hir::StmtKind::Semi(tail_expr) = stmt.kind else { + return; + }; + let Some(ty) = self.node_ty_opt(tail_expr.hir_id) else { + return; + }; + if self.can_eq(self.param_env, expected_ty, ty) { + err.span_suggestion_short( + stmt.span.with_lo(tail_expr.span.hi()), + "remove this semicolon", + "", + Applicability::MachineApplicable, + ); + } else { + err.span_label(block.span, "this block is missing a tail expression"); + } + } } From 2fcb4d92b0cfe13e9f30444739545a53a569b579 Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Tue, 31 Oct 2023 11:58:20 -0700 Subject: [PATCH 31/32] change inline_retag to after.mir --- .../inline/inline_retag.bar.Inline.after.mir | 59 +++++++++++++++++ .../inline/inline_retag.bar.Inline.diff | 65 ------------------- tests/mir-opt/inline/inline_retag.rs | 2 +- 3 files changed, 60 insertions(+), 66 deletions(-) create mode 100644 tests/mir-opt/inline/inline_retag.bar.Inline.after.mir delete mode 100644 tests/mir-opt/inline/inline_retag.bar.Inline.diff diff --git a/tests/mir-opt/inline/inline_retag.bar.Inline.after.mir b/tests/mir-opt/inline/inline_retag.bar.Inline.after.mir new file mode 100644 index 0000000000000..8c3f3a4589e63 --- /dev/null +++ b/tests/mir-opt/inline/inline_retag.bar.Inline.after.mir @@ -0,0 +1,59 @@ +// MIR for `bar` after Inline + +fn bar() -> bool { + let mut _0: bool; + let _1: for<'a, 'b> fn(&'a i32, &'b i32) -> bool {foo}; + let mut _2: for<'a, 'b> fn(&'a i32, &'b i32) -> bool {foo}; + let mut _3: &i32; + let _4: &i32; + let _5: i32; + let mut _6: &i32; + let _7: &i32; + let _8: i32; + scope 1 { + debug f => _1; + let mut _9: &i32; + let mut _10: &i32; + scope 2 (inlined foo) { + debug x => _3; + debug y => _6; + let mut _11: i32; + let mut _12: i32; + } + } + + bb0: { + StorageLive(_1); + _1 = foo; + StorageLive(_2); + _2 = _1; + StorageLive(_3); + StorageLive(_4); + _10 = const _; + Retag(_10); + _4 = &(*_10); + _3 = &(*_4); + StorageLive(_6); + StorageLive(_7); + _9 = const _; + Retag(_9); + _7 = &(*_9); + _6 = &(*_7); + Retag(_3); + Retag(_6); + StorageLive(_11); + _11 = (*_3); + StorageLive(_12); + _12 = (*_6); + _0 = Eq(move _11, move _12); + StorageDead(_12); + StorageDead(_11); + StorageDead(_6); + StorageDead(_3); + StorageDead(_2); + StorageDead(_1); + StorageDead(_7); + StorageDead(_4); + return; + } +} diff --git a/tests/mir-opt/inline/inline_retag.bar.Inline.diff b/tests/mir-opt/inline/inline_retag.bar.Inline.diff deleted file mode 100644 index 8f53f6342ec6d..0000000000000 --- a/tests/mir-opt/inline/inline_retag.bar.Inline.diff +++ /dev/null @@ -1,65 +0,0 @@ -- // MIR for `bar` before Inline -+ // MIR for `bar` after Inline - - fn bar() -> bool { - let mut _0: bool; - let _1: for<'a, 'b> fn(&'a i32, &'b i32) -> bool {foo}; - let mut _2: for<'a, 'b> fn(&'a i32, &'b i32) -> bool {foo}; - let mut _3: &i32; - let _4: &i32; - let _5: i32; - let mut _6: &i32; - let _7: &i32; - let _8: i32; - scope 1 { - debug f => _1; - let mut _9: &i32; - let mut _10: &i32; -+ scope 2 (inlined foo) { -+ debug x => _3; -+ debug y => _6; -+ let mut _11: i32; -+ let mut _12: i32; -+ } - } - - bb0: { - StorageLive(_1); - _1 = foo; - StorageLive(_2); - _2 = _1; - StorageLive(_3); - StorageLive(_4); - _10 = const _; - Retag(_10); - _4 = &(*_10); - _3 = &(*_4); - StorageLive(_6); - StorageLive(_7); - _9 = const _; - Retag(_9); - _7 = &(*_9); - _6 = &(*_7); -- _0 = move _2(move _3, move _6) -> [return: bb1, unwind continue]; -- } -- -- bb1: { -+ Retag(_3); -+ Retag(_6); -+ StorageLive(_11); -+ _11 = (*_3); -+ StorageLive(_12); -+ _12 = (*_6); -+ _0 = Eq(move _11, move _12); -+ StorageDead(_12); -+ StorageDead(_11); - StorageDead(_6); - StorageDead(_3); - StorageDead(_2); - StorageDead(_1); - StorageDead(_7); - StorageDead(_4); - return; - } - } - diff --git a/tests/mir-opt/inline/inline_retag.rs b/tests/mir-opt/inline/inline_retag.rs index b9058905892bf..9fb6f709223c9 100644 --- a/tests/mir-opt/inline/inline_retag.rs +++ b/tests/mir-opt/inline/inline_retag.rs @@ -6,7 +6,7 @@ fn main() { println!("{}", bar()); } -// EMIT_MIR inline_retag.bar.Inline.diff +// EMIT_MIR inline_retag.bar.Inline.after.mir fn bar() -> bool { // CHECK-LABEL: fn bar( // CHECK: (inlined foo) From 587af910459fe408f03e004d264fdf218203849d Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 31 Oct 2023 09:32:10 +1100 Subject: [PATCH 32/32] Inline and remove `create_session`. Currently the parts of session initialization that happen within `rustc_interface` are split between `run_compiler` and `create_session`. This split isn't necessary and obscures what's happening. This commit merges the two functions. I think a single longer function is much clearer than splitting this code across two functions in different modules, especially when `create_session` has 13 parameters, and is misnamed (it also creates the codegen backend). The net result is 43 fewer lines of code. --- compiler/rustc_interface/src/interface.rs | 64 +++++++++++++---- compiler/rustc_interface/src/util.rs | 87 +---------------------- 2 files changed, 54 insertions(+), 97 deletions(-) diff --git a/compiler/rustc_interface/src/interface.rs b/compiler/rustc_interface/src/interface.rs index 26034dbaf9977..ffdc05526f231 100644 --- a/compiler/rustc_interface/src/interface.rs +++ b/compiler/rustc_interface/src/interface.rs @@ -18,10 +18,9 @@ use rustc_query_system::query::print_query_stack; use rustc_session::config::{ self, Cfg, CheckCfg, ExpectedValues, Input, OutFileName, OutputFilenames, }; +use rustc_session::filesearch::sysroot_candidates; use rustc_session::parse::ParseSess; -use rustc_session::CompilerIO; -use rustc_session::Session; -use rustc_session::{lint, EarlyErrorHandler}; +use rustc_session::{lint, CompilerIO, EarlyErrorHandler, Session}; use rustc_span::source_map::{FileLoader, FileName}; use rustc_span::symbol::sym; use std::path::PathBuf; @@ -398,32 +397,71 @@ pub fn run_compiler(config: Config, f: impl FnOnce(&Compiler) -> R + Se || { crate::callbacks::setup_callbacks(); - let registry = &config.registry; - let handler = EarlyErrorHandler::new(config.opts.error_format); + let codegen_backend = if let Some(make_codegen_backend) = config.make_codegen_backend { + make_codegen_backend(&config.opts) + } else { + util::get_codegen_backend( + &handler, + &config.opts.maybe_sysroot, + config.opts.unstable_opts.codegen_backend.as_deref(), + ) + }; + let temps_dir = config.opts.unstable_opts.temps_dir.as_deref().map(PathBuf::from); - let (mut sess, codegen_backend) = util::create_session( + + let bundle = match rustc_errors::fluent_bundle( + config.opts.maybe_sysroot.clone(), + sysroot_candidates().to_vec(), + config.opts.unstable_opts.translate_lang.clone(), + config.opts.unstable_opts.translate_additional_ftl.as_deref(), + config.opts.unstable_opts.translate_directionality_markers, + ) { + Ok(bundle) => bundle, + Err(e) => { + handler.early_error(format!("failed to load fluent bundle: {e}")); + } + }; + + let mut locale_resources = Vec::from(config.locale_resources); + locale_resources.push(codegen_backend.locale_resource()); + + // target_override is documented to be called before init(), so this is okay + let target_override = codegen_backend.target_override(&config.opts); + + let mut sess = rustc_session::build_session( &handler, config.opts, - parse_cfg(&handler, config.crate_cfg), - parse_check_cfg(&handler, config.crate_check_cfg), - config.locale_resources, - config.file_loader, CompilerIO { input: config.input, output_dir: config.output_dir, output_file: config.output_file, temps_dir, }, + bundle, + config.registry.clone(), + locale_resources, config.lint_caps, - config.make_codegen_backend, - registry.clone(), + config.file_loader, + target_override, + util::rustc_version_str().unwrap_or("unknown"), config.ice_file, config.using_internal_features, config.expanded_args, ); + codegen_backend.init(&sess); + + let cfg = parse_cfg(&handler, config.crate_cfg); + let mut cfg = config::build_configuration(&sess, cfg); + util::add_configuration(&mut cfg, &mut sess, &*codegen_backend); + sess.parse_sess.config = cfg; + + let mut check_cfg = parse_check_cfg(&handler, config.crate_check_cfg); + check_cfg.fill_well_known(&sess.target); + sess.parse_sess.check_config = check_cfg; + if let Some(parse_sess_created) = config.parse_sess_created { parse_sess_created(&mut sess.parse_sess); } @@ -444,7 +482,7 @@ pub fn run_compiler(config: Config, f: impl FnOnce(&Compiler) -> R + Se rustc_span::set_source_map(compiler.sess.parse_sess.clone_source_map(), move || { let r = { let _sess_abort_error = defer(|| { - compiler.sess.finish_diagnostics(registry); + compiler.sess.finish_diagnostics(&config.registry); }); f(&compiler) diff --git a/compiler/rustc_interface/src/util.rs b/compiler/rustc_interface/src/util.rs index 5fde98c7c062e..22d12793464a8 100644 --- a/compiler/rustc_interface/src/util.rs +++ b/compiler/rustc_interface/src/util.rs @@ -3,29 +3,24 @@ use info; use libloading::Library; use rustc_ast as ast; use rustc_codegen_ssa::traits::CodegenBackend; -use rustc_data_structures::fx::FxHashMap; #[cfg(parallel_compiler)] use rustc_data_structures::sync; -use rustc_errors::registry::Registry; use rustc_parse::validate_attr; use rustc_session as session; -use rustc_session::config::{ - self, Cfg, CheckCfg, CrateType, OutFileName, OutputFilenames, OutputTypes, -}; +use rustc_session::config::{self, Cfg, CrateType, OutFileName, OutputFilenames, OutputTypes}; use rustc_session::filesearch::sysroot_candidates; use rustc_session::lint::{self, BuiltinLintDiagnostics, LintBuffer}; use rustc_session::{filesearch, output, Session}; use rustc_span::edit_distance::find_best_match_for_name; use rustc_span::edition::Edition; -use rustc_span::source_map::FileLoader; use rustc_span::symbol::{sym, Symbol}; -use session::{CompilerIO, EarlyErrorHandler}; +use session::EarlyErrorHandler; use std::env; use std::env::consts::{DLL_PREFIX, DLL_SUFFIX}; use std::mem; use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, OnceLock}; +use std::sync::OnceLock; use std::thread; /// Function pointer type that constructs a new CodegenBackend. @@ -52,82 +47,6 @@ pub fn add_configuration(cfg: &mut Cfg, sess: &mut Session, codegen_backend: &dy } } -pub fn create_session( - handler: &EarlyErrorHandler, - sopts: config::Options, - cfg: Cfg, - mut check_cfg: CheckCfg, - locale_resources: &'static [&'static str], - file_loader: Option>, - io: CompilerIO, - lint_caps: FxHashMap, - make_codegen_backend: Option< - Box Box + Send>, - >, - descriptions: Registry, - ice_file: Option, - using_internal_features: Arc, - expanded_args: Vec, -) -> (Session, Box) { - let codegen_backend = if let Some(make_codegen_backend) = make_codegen_backend { - make_codegen_backend(&sopts) - } else { - get_codegen_backend( - handler, - &sopts.maybe_sysroot, - sopts.unstable_opts.codegen_backend.as_deref(), - ) - }; - - // target_override is documented to be called before init(), so this is okay - let target_override = codegen_backend.target_override(&sopts); - - let bundle = match rustc_errors::fluent_bundle( - sopts.maybe_sysroot.clone(), - sysroot_candidates().to_vec(), - sopts.unstable_opts.translate_lang.clone(), - sopts.unstable_opts.translate_additional_ftl.as_deref(), - sopts.unstable_opts.translate_directionality_markers, - ) { - Ok(bundle) => bundle, - Err(e) => { - handler.early_error(format!("failed to load fluent bundle: {e}")); - } - }; - - let mut locale_resources = Vec::from(locale_resources); - locale_resources.push(codegen_backend.locale_resource()); - - let mut sess = session::build_session( - handler, - sopts, - io, - bundle, - descriptions, - locale_resources, - lint_caps, - file_loader, - target_override, - rustc_version_str().unwrap_or("unknown"), - ice_file, - using_internal_features, - expanded_args, - ); - - codegen_backend.init(&sess); - - let mut cfg = config::build_configuration(&sess, cfg); - add_configuration(&mut cfg, &mut sess, &*codegen_backend); - - check_cfg.fill_well_known(&sess.target); - - // These configs use symbols, rather than strings. - sess.parse_sess.config = cfg; - sess.parse_sess.check_config = check_cfg; - - (sess, codegen_backend) -} - const STACK_SIZE: usize = 8 * 1024 * 1024; fn get_stack_size() -> Option {