From 7d91039ac06dc34795cb141b53b610e10cbc1340 Mon Sep 17 00:00:00 2001 From: William Venner Date: Tue, 5 Jul 2022 17:19:03 +0100 Subject: [PATCH 0001/1762] Add `BufRead::skip_until` --- library/std/src/io/mod.rs | 84 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index f4f2e3f243457..a5dab49c75dc4 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -1923,6 +1923,28 @@ fn read_until(r: &mut R, delim: u8, buf: &mut Vec) -> R } } +fn skip_until(r: &mut R, delim: u8) -> Result { + let mut read = 0; + loop { + let (done, used) = { + let available = match r.fill_buf() { + Ok(n) => n, + Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, + Err(e) => return Err(e), + }; + match memchr::memchr(delim, available) { + Some(i) => (true, i + 1), + None => (false, available.len()), + } + }; + r.consume(used); + read += used; + if done || used == 0 { + return Ok(read); + } + } +} + /// A `BufRead` is a type of `Read`er which has an internal buffer, allowing it /// to perform extra ways of reading. /// @@ -2126,6 +2148,68 @@ pub trait BufRead: Read { read_until(self, byte, buf) } + /// Skip all bytes until the delimiter `byte` or EOF is reached. + /// + /// This function will read (and discard) bytes from the underlying stream until the + /// delimiter or EOF is found. + /// + /// If successful, this function will return the total number of bytes read, + /// including the delimiter byte. + /// + /// This is useful for efficiently skipping data such as NUL-terminated strings + /// in binary file formats without buffering. + /// + /// This function is blocking and should be used carefully: it is possible for + /// an attacker to continuously send bytes without ever sending the delimiter + /// or EOF. + /// + /// # Errors + /// + /// This function will ignore all instances of [`ErrorKind::Interrupted`] and + /// will otherwise return any errors returned by [`fill_buf`]. + /// + /// If an I/O error is encountered then all bytes read so far will be + /// present in `buf` and its length will have been adjusted appropriately. + /// + /// [`fill_buf`]: BufRead::fill_buf + /// + /// # Examples + /// + /// [`std::io::Cursor`][`Cursor`] is a type that implements `BufRead`. In + /// this example, we use [`Cursor`] to read some NUL-terminated information + /// about Ferris from a binary string, skipping the fun fact: + /// + /// ``` + /// #![feature(bufread_skip_until)] + /// + /// use std::io::{self, BufRead}; + /// + /// let mut cursor = io::Cursor::new(b"Ferris\0Likes long walks on the beach\0Crustacean\0"); + /// + /// // read name + /// let mut name = Vec::new(); + /// let num_bytes = cursor.read_until(b'\0', &mut name) + /// .expect("reading from cursor won't fail"); + /// assert_eq!(num_bytes, 7); + /// assert_eq!(name, b"Ferris\0"); + /// + /// // skip fun fact + /// let num_bytes = cursor.skip_until(b'\0') + /// .expect("reading from cursor won't fail"); + /// assert_eq!(num_bytes, 30); + /// + /// // read animal type + /// let mut animal = Vec::new(); + /// let num_bytes = cursor.read_until(b'\0', &mut animal) + /// .expect("reading from cursor won't fail"); + /// assert_eq!(num_bytes, 11); + /// assert_eq!(animal, b"Crustacean\0"); + /// ``` + #[unstable(feature = "bufread_skip_until", issue = "none")] + fn skip_until(&mut self, byte: u8) -> Result { + skip_until(self, byte) + } + /// Read all bytes until a newline (the `0xA` byte) is reached, and append /// them to the provided buffer. You do not need to clear the buffer before /// appending. From 4eea9763847250c89f535f6a8c3b0fd49d860e78 Mon Sep 17 00:00:00 2001 From: William Venner Date: Thu, 18 May 2023 18:57:15 +0100 Subject: [PATCH 0002/1762] Add `BufRead::skip_until` test --- library/std/src/io/buffered/tests.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/library/std/src/io/buffered/tests.rs b/library/std/src/io/buffered/tests.rs index 9d429e7090e83..ca5d8fb4b7f9a 100644 --- a/library/std/src/io/buffered/tests.rs +++ b/library/std/src/io/buffered/tests.rs @@ -400,6 +400,34 @@ fn test_read_until() { assert_eq!(v, []); } +#[test] +fn test_skip_until() { + let bytes: &[u8] = b"read\0ignore\0read\0ignore\0read\0ignore"; + let mut reader = BufReader::new(bytes); + + // read from the bytes, alternating between + // consuming `read\0`s and skipping `ignore\0`s + loop { + // consume `read\0` + let mut out = Vec::new(); + let read = reader.read_until(0, &mut out).unwrap(); + if read == 0 { + // eof + break; + } else { + assert_eq!(out, b"read\0"); + } + + // skip past `ignore\0` + reader.skip_until(0).unwrap(); + } + + // ensure we are at the end of the byte slice and that we can skip no further + // also ensure skip_until matches the behavior of read_until at EOF + let skipped = reader.skip_until(0).unwrap(); + assert_eq!(skipped, 0); +} + #[test] fn test_line_buffer() { let mut writer = LineWriter::new(Vec::new()); From 7c9ad34362ab545dd5be75637afd84b6287507a0 Mon Sep 17 00:00:00 2001 From: William Venner Date: Thu, 18 May 2023 18:59:36 +0100 Subject: [PATCH 0003/1762] Move `BufRead::skip_until` test to a more appropriate location --- library/std/src/io/buffered/tests.rs | 28 ---------------------------- library/std/src/io/tests.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/library/std/src/io/buffered/tests.rs b/library/std/src/io/buffered/tests.rs index ca5d8fb4b7f9a..9d429e7090e83 100644 --- a/library/std/src/io/buffered/tests.rs +++ b/library/std/src/io/buffered/tests.rs @@ -400,34 +400,6 @@ fn test_read_until() { assert_eq!(v, []); } -#[test] -fn test_skip_until() { - let bytes: &[u8] = b"read\0ignore\0read\0ignore\0read\0ignore"; - let mut reader = BufReader::new(bytes); - - // read from the bytes, alternating between - // consuming `read\0`s and skipping `ignore\0`s - loop { - // consume `read\0` - let mut out = Vec::new(); - let read = reader.read_until(0, &mut out).unwrap(); - if read == 0 { - // eof - break; - } else { - assert_eq!(out, b"read\0"); - } - - // skip past `ignore\0` - reader.skip_until(0).unwrap(); - } - - // ensure we are at the end of the byte slice and that we can skip no further - // also ensure skip_until matches the behavior of read_until at EOF - let skipped = reader.skip_until(0).unwrap(); - assert_eq!(skipped, 0); -} - #[test] fn test_line_buffer() { let mut writer = LineWriter::new(Vec::new()); diff --git a/library/std/src/io/tests.rs b/library/std/src/io/tests.rs index d5a8c93b0ce9f..9a9a790d77d9c 100644 --- a/library/std/src/io/tests.rs +++ b/library/std/src/io/tests.rs @@ -25,6 +25,34 @@ fn read_until() { assert_eq!(v, []); } +#[test] +fn skip_until() { + let bytes: &[u8] = b"read\0ignore\0read\0ignore\0read\0ignore"; + let mut reader = BufReader::new(bytes); + + // read from the bytes, alternating between + // consuming `read\0`s and skipping `ignore\0`s + loop { + // consume `read\0` + let mut out = Vec::new(); + let read = reader.read_until(0, &mut out).unwrap(); + if read == 0 { + // eof + break; + } else { + assert_eq!(out, b"read\0"); + } + + // skip past `ignore\0` + reader.skip_until(0).unwrap(); + } + + // ensure we are at the end of the byte slice and that we can skip no further + // also ensure skip_until matches the behavior of read_until at EOF + let skipped = reader.skip_until(0).unwrap(); + assert_eq!(skipped, 0); +} + #[test] fn split() { let buf = Cursor::new(&b"12"[..]); From 1d7d7654e686f0452f10e016ef78c3d4b191526e Mon Sep 17 00:00:00 2001 From: William Venner Date: Thu, 18 May 2023 19:08:41 +0100 Subject: [PATCH 0004/1762] Add tracking issue ID for `bufread_skip_until` feature gate --- library/std/src/io/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index a5dab49c75dc4..2cbdd027036d8 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -2205,7 +2205,7 @@ pub trait BufRead: Read { /// assert_eq!(num_bytes, 11); /// assert_eq!(animal, b"Crustacean\0"); /// ``` - #[unstable(feature = "bufread_skip_until", issue = "none")] + #[unstable(feature = "bufread_skip_until", issue = "111735")] fn skip_until(&mut self, byte: u8) -> Result { skip_until(self, byte) } From c6ed532bef02e752ea881ab104cacc28e5d06738 Mon Sep 17 00:00:00 2001 From: Jan <66554238+vaporoxx@users.noreply.github.com> Date: Wed, 24 May 2023 22:27:10 +0200 Subject: [PATCH 0005/1762] feat: implement `DoubleEndedSearcher` for `CharArray[Ref]Searcher` --- library/core/src/str/pattern.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/core/src/str/pattern.rs b/library/core/src/str/pattern.rs index 91ee2903aab43..a31bd1219b978 100644 --- a/library/core/src/str/pattern.rs +++ b/library/core/src/str/pattern.rs @@ -806,6 +806,8 @@ unsafe impl<'a, const N: usize> ReverseSearcher<'a> for CharArraySearcher<'a, N> searcher_methods!(reverse); } +impl<'a, const N: usize> DoubleEndedSearcher<'a> for CharArraySearcher<'a, N> {} + /// Searches for chars that are equal to any of the [`char`]s in the array. /// /// # Examples @@ -826,6 +828,8 @@ unsafe impl<'a, 'b, const N: usize> ReverseSearcher<'a> for CharArrayRefSearcher searcher_methods!(reverse); } +impl<'a, 'b, const N: usize> DoubleEndedSearcher<'a> for CharArrayRefSearcher<'a, 'b, N> {} + ///////////////////////////////////////////////////////////////////////////// // Impl for &[char] ///////////////////////////////////////////////////////////////////////////// From 11c43c0c160539b6d040539b668e0142769537a5 Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Fri, 21 Jul 2023 15:33:09 -0400 Subject: [PATCH 0006/1762] Fix is_subnormal on architectures that flush subnormals to zero --- crates/core_simd/src/elements/float.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/core_simd/src/elements/float.rs b/crates/core_simd/src/elements/float.rs index 501c1c5ddd3f2..d700011ff9c5d 100644 --- a/crates/core_simd/src/elements/float.rs +++ b/crates/core_simd/src/elements/float.rs @@ -336,7 +336,10 @@ macro_rules! impl_trait { #[inline] fn is_subnormal(self) -> Self::Mask { - self.abs().simd_ne(Self::splat(0.0)) & (self.to_bits() & Self::splat(Self::Scalar::INFINITY).to_bits()).simd_eq(Simd::splat(0)) + // On some architectures (e.g. armv7 and some ppc) subnormals are flushed to zero, + // so this comparison must be done with integers. + let not_zero = self.abs().to_bits().simd_ne(Self::splat(0.0).to_bits()); + not_zero & (self.to_bits() & Self::splat(Self::Scalar::INFINITY).to_bits()).simd_eq(Simd::splat(0)) } #[inline] From dc0ba7836528b7d8720b868e331a378ceaf8fa95 Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Fri, 21 Jul 2023 15:44:15 -0400 Subject: [PATCH 0007/1762] Don't require strict equality when subnormals are flushed --- crates/test_helpers/Cargo.toml | 7 +++---- crates/test_helpers/src/biteq.rs | 2 ++ crates/test_helpers/src/lib.rs | 13 +++++++++++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/crates/test_helpers/Cargo.toml b/crates/test_helpers/Cargo.toml index 1d2bc8b519aa6..bd481db6bac74 100644 --- a/crates/test_helpers/Cargo.toml +++ b/crates/test_helpers/Cargo.toml @@ -4,10 +4,9 @@ version = "0.1.0" edition = "2021" publish = false -[dependencies.proptest] -version = "0.10" -default-features = false -features = ["alloc"] +[dependencies] +float_eq = "1.0" +proptest = { version = "0.10", default-features = false, features = ["alloc"] } [features] all_lane_counts = [] diff --git a/crates/test_helpers/src/biteq.rs b/crates/test_helpers/src/biteq.rs index 7d91260d838a6..515eaf1c048d7 100644 --- a/crates/test_helpers/src/biteq.rs +++ b/crates/test_helpers/src/biteq.rs @@ -40,6 +40,8 @@ macro_rules! impl_float_biteq { fn biteq(&self, other: &Self) -> bool { if self.is_nan() && other.is_nan() { true // exact nan bits don't matter + } else if crate::flush_subnormals::() { + self.to_bits() == other.to_bits() || float_eq::float_eq!(self, other, abs <= 2. * <$type>::EPSILON) } else { self.to_bits() == other.to_bits() } diff --git a/crates/test_helpers/src/lib.rs b/crates/test_helpers/src/lib.rs index b26cdc311a215..1b98bccf706f1 100644 --- a/crates/test_helpers/src/lib.rs +++ b/crates/test_helpers/src/lib.rs @@ -6,6 +6,19 @@ pub mod wasm; #[macro_use] pub mod biteq; +/// Indicates if subnormal floats are flushed to zero. +pub fn flush_subnormals() -> bool { + let is_f32 = core::mem::size_of::() == 4; + let ppc_flush = is_f32 + && cfg!(all( + target_arch = "powerpc64", + target_endian = "big", + not(target_feature = "vsx") + )); + let arm_flush = is_f32 && cfg!(all(target_arch = "arm", target_feature = "neon")); + ppc_flush || arm_flush +} + /// Specifies the default strategy for testing a type. /// /// This strategy should be what "makes sense" to test. From 38c7ba09dd6c81ff76477a7e54b561c07f1d1db0 Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Sat, 22 Jul 2023 00:41:21 -0400 Subject: [PATCH 0008/1762] Handle subnormal numbers exactly --- crates/core_simd/tests/ops_macros.rs | 23 ++++-- crates/test_helpers/Cargo.toml | 1 - crates/test_helpers/src/biteq.rs | 34 +++++++- crates/test_helpers/src/lib.rs | 108 ++++++++++++++++++++++---- crates/test_helpers/src/subnormals.rs | 39 ++++++++++ 5 files changed, 181 insertions(+), 24 deletions(-) create mode 100644 crates/test_helpers/src/subnormals.rs diff --git a/crates/core_simd/tests/ops_macros.rs b/crates/core_simd/tests/ops_macros.rs index 3a02f3f01e1cf..1f5d9488ad0eb 100644 --- a/crates/core_simd/tests/ops_macros.rs +++ b/crates/core_simd/tests/ops_macros.rs @@ -6,7 +6,7 @@ macro_rules! impl_unary_op_test { { $scalar:ty, $trait:ident :: $fn:ident, $scalar_fn:expr } => { test_helpers::test_lanes! { fn $fn() { - test_helpers::test_unary_elementwise( + test_helpers::test_unary_elementwise_flush_subnormals( & as core::ops::$trait>::$fn, &$scalar_fn, &|_| true, @@ -31,7 +31,7 @@ macro_rules! impl_binary_op_test { test_helpers::test_lanes! { fn normal() { - test_helpers::test_binary_elementwise( + test_helpers::test_binary_elementwise_flush_subnormals( & as core::ops::$trait>::$fn, &$scalar_fn, &|_, _| true, @@ -39,7 +39,7 @@ macro_rules! impl_binary_op_test { } fn assign() { - test_helpers::test_binary_elementwise( + test_helpers::test_binary_elementwise_flush_subnormals( &|mut a, b| { as core::ops::$trait_assign>::$fn_assign(&mut a, b); a }, &$scalar_fn, &|_, _| true, @@ -433,7 +433,7 @@ macro_rules! impl_float_tests { } fn to_degrees() { - test_helpers::test_unary_elementwise( + test_helpers::test_unary_elementwise_flush_subnormals( &Vector::::to_degrees, &Scalar::to_degrees, &|_| true, @@ -441,7 +441,7 @@ macro_rules! impl_float_tests { } fn to_radians() { - test_helpers::test_unary_elementwise( + test_helpers::test_unary_elementwise_flush_subnormals( &Vector::::to_radians, &Scalar::to_radians, &|_| true, @@ -512,6 +512,7 @@ macro_rules! impl_float_tests { fn simd_clamp() { test_helpers::test_3(&|value: [Scalar; LANES], mut min: [Scalar; LANES], mut max: [Scalar; LANES]| { + use test_helpers::subnormals::FlushSubnormals; for (min, max) in min.iter_mut().zip(max.iter_mut()) { if max < min { core::mem::swap(min, max); @@ -528,8 +529,18 @@ macro_rules! impl_float_tests { for i in 0..LANES { result_scalar[i] = value[i].clamp(min[i], max[i]); } + let mut result_scalar_flush = [Scalar::default(); LANES]; + for i in 0..LANES { + result_scalar_flush[i] = value[i]; + if FlushSubnormals::flush(value[i]) < FlushSubnormals::flush(min[i]) { + result_scalar_flush[i] = min[i]; + } + if FlushSubnormals::flush(value[i]) > FlushSubnormals::flush(max[i]) { + result_scalar_flush[i] = max[i]; + } + } let result_vector = Vector::from_array(value).simd_clamp(min.into(), max.into()).to_array(); - test_helpers::prop_assert_biteq!(result_scalar, result_vector); + test_helpers::prop_assert_biteq!(result_vector, result_scalar, result_scalar_flush); Ok(()) }) } diff --git a/crates/test_helpers/Cargo.toml b/crates/test_helpers/Cargo.toml index bd481db6bac74..23dae7c93381e 100644 --- a/crates/test_helpers/Cargo.toml +++ b/crates/test_helpers/Cargo.toml @@ -5,7 +5,6 @@ edition = "2021" publish = false [dependencies] -float_eq = "1.0" proptest = { version = "0.10", default-features = false, features = ["alloc"] } [features] diff --git a/crates/test_helpers/src/biteq.rs b/crates/test_helpers/src/biteq.rs index 515eaf1c048d7..cbc20cda0d626 100644 --- a/crates/test_helpers/src/biteq.rs +++ b/crates/test_helpers/src/biteq.rs @@ -40,8 +40,6 @@ macro_rules! impl_float_biteq { fn biteq(&self, other: &Self) -> bool { if self.is_nan() && other.is_nan() { true // exact nan bits don't matter - } else if crate::flush_subnormals::() { - self.to_bits() == other.to_bits() || float_eq::float_eq!(self, other, abs <= 2. * <$type>::EPSILON) } else { self.to_bits() == other.to_bits() } @@ -115,6 +113,27 @@ impl core::fmt::Debug for BitEqWrapper<'_, T> { } } +#[doc(hidden)] +pub struct BitEqEitherWrapper<'a, T>(pub &'a T, pub &'a T); + +impl PartialEq> for BitEqWrapper<'_, T> { + fn eq(&self, other: &BitEqEitherWrapper<'_, T>) -> bool { + self.0.biteq(other.0) || self.0.biteq(other.1) + } +} + +impl core::fmt::Debug for BitEqEitherWrapper<'_, T> { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + if self.0.biteq(self.1) { + self.0.fmt(f) + } else { + self.0.fmt(f)?; + write!(f, " or ")?; + self.1.fmt(f) + } + } +} + #[macro_export] macro_rules! prop_assert_biteq { { $a:expr, $b:expr $(,)? } => { @@ -124,5 +143,14 @@ macro_rules! prop_assert_biteq { let b = $b; proptest::prop_assert_eq!(BitEqWrapper(&a), BitEqWrapper(&b)); } - } + }; + { $a:expr, $b:expr, $c:expr $(,)? } => { + { + use $crate::biteq::{BitEqWrapper, BitEqEitherWrapper}; + let a = $a; + let b = $b; + let c = $c; + proptest::prop_assert_eq!(BitEqWrapper(&a), BitEqEitherWrapper(&b, &c)); + } + }; } diff --git a/crates/test_helpers/src/lib.rs b/crates/test_helpers/src/lib.rs index 1b98bccf706f1..63308a2ca33dc 100644 --- a/crates/test_helpers/src/lib.rs +++ b/crates/test_helpers/src/lib.rs @@ -6,18 +6,8 @@ pub mod wasm; #[macro_use] pub mod biteq; -/// Indicates if subnormal floats are flushed to zero. -pub fn flush_subnormals() -> bool { - let is_f32 = core::mem::size_of::() == 4; - let ppc_flush = is_f32 - && cfg!(all( - target_arch = "powerpc64", - target_endian = "big", - not(target_feature = "vsx") - )); - let arm_flush = is_f32 && cfg!(all(target_arch = "arm", target_feature = "neon")); - ppc_flush || arm_flush -} +pub mod subnormals; +use subnormals::FlushSubnormals; /// Specifies the default strategy for testing a type. /// @@ -164,7 +154,6 @@ pub fn test_3< } /// Test a unary vector function against a unary scalar function, applied elementwise. -#[inline(never)] pub fn test_unary_elementwise( fv: &dyn Fn(Vector) -> VectorResult, fs: &dyn Fn(Scalar) -> ScalarResult, @@ -190,6 +179,48 @@ pub fn test_unary_elementwise( + fv: &dyn Fn(Vector) -> VectorResult, + fs: &dyn Fn(Scalar) -> ScalarResult, + check: &dyn Fn([Scalar; LANES]) -> bool, +) where + Scalar: Copy + core::fmt::Debug + DefaultStrategy + FlushSubnormals, + ScalarResult: Copy + biteq::BitEq + core::fmt::Debug + DefaultStrategy + FlushSubnormals, + Vector: Into<[Scalar; LANES]> + From<[Scalar; LANES]> + Copy, + VectorResult: Into<[ScalarResult; LANES]> + From<[ScalarResult; LANES]> + Copy, +{ + let flush = |x: Scalar| FlushSubnormals::flush(fs(FlushSubnormals::flush(x))); + test_1(&|x: [Scalar; LANES]| { + proptest::prop_assume!(check(x)); + let result_v: [ScalarResult; LANES] = fv(x.into()).into(); + let result_s: [ScalarResult; LANES] = x + .iter() + .copied() + .map(fs) + .collect::>() + .try_into() + .unwrap(); + let result_sf: [ScalarResult; LANES] = x + .iter() + .copied() + .map(flush) + .collect::>() + .try_into() + .unwrap(); + crate::prop_assert_biteq!(result_v, result_s, result_sf); + Ok(()) + }); +} + /// Test a unary vector function against a unary scalar function, applied elementwise. #[inline(never)] pub fn test_unary_mask_elementwise( @@ -217,7 +248,6 @@ pub fn test_unary_mask_elementwise( } /// Test a binary vector function against a binary scalar function, applied elementwise. -#[inline(never)] pub fn test_binary_elementwise< Scalar1, Scalar2, @@ -254,6 +284,56 @@ pub fn test_binary_elementwise< }); } +/// Test a binary vector function against a binary scalar function, applied elementwise. +/// +/// Where subnormals are flushed, use approximate equality. +pub fn test_binary_elementwise_flush_subnormals< + Scalar1, + Scalar2, + ScalarResult, + Vector1, + Vector2, + VectorResult, + const LANES: usize, +>( + fv: &dyn Fn(Vector1, Vector2) -> VectorResult, + fs: &dyn Fn(Scalar1, Scalar2) -> ScalarResult, + check: &dyn Fn([Scalar1; LANES], [Scalar2; LANES]) -> bool, +) where + Scalar1: Copy + core::fmt::Debug + DefaultStrategy + FlushSubnormals, + Scalar2: Copy + core::fmt::Debug + DefaultStrategy + FlushSubnormals, + ScalarResult: Copy + biteq::BitEq + core::fmt::Debug + DefaultStrategy + FlushSubnormals, + Vector1: Into<[Scalar1; LANES]> + From<[Scalar1; LANES]> + Copy, + Vector2: Into<[Scalar2; LANES]> + From<[Scalar2; LANES]> + Copy, + VectorResult: Into<[ScalarResult; LANES]> + From<[ScalarResult; LANES]> + Copy, +{ + let flush = |x: Scalar1, y: Scalar2| { + FlushSubnormals::flush(fs(FlushSubnormals::flush(x), FlushSubnormals::flush(y))) + }; + test_2(&|x: [Scalar1; LANES], y: [Scalar2; LANES]| { + proptest::prop_assume!(check(x, y)); + let result_v: [ScalarResult; LANES] = fv(x.into(), y.into()).into(); + let result_s: [ScalarResult; LANES] = x + .iter() + .copied() + .zip(y.iter().copied()) + .map(|(x, y)| fs(x, y)) + .collect::>() + .try_into() + .unwrap(); + let result_sf: [ScalarResult; LANES] = x + .iter() + .copied() + .zip(y.iter().copied()) + .map(|(x, y)| flush(x, y)) + .collect::>() + .try_into() + .unwrap(); + crate::prop_assert_biteq!(result_v, result_s, result_sf); + Ok(()) + }); +} + /// Test a binary vector-scalar function against a binary scalar function, applied elementwise. #[inline(never)] pub fn test_binary_scalar_rhs_elementwise< diff --git a/crates/test_helpers/src/subnormals.rs b/crates/test_helpers/src/subnormals.rs new file mode 100644 index 0000000000000..122304f96db41 --- /dev/null +++ b/crates/test_helpers/src/subnormals.rs @@ -0,0 +1,39 @@ +pub trait FlushSubnormals: Sized { + fn flush(self) -> Self { + self + } +} + +impl FlushSubnormals for *const T {} +impl FlushSubnormals for *mut T {} + +macro_rules! impl_float { + { $($ty:ty),* } => { + $( + impl FlushSubnormals for $ty { + fn flush(self) -> Self { + let is_f32 = core::mem::size_of::() == 4; + let ppc_flush = is_f32 && cfg!(all(target_arch = "powerpc64", target_endian = "big", not(target_feature = "vsx"))); + let arm_flush = is_f32 && cfg!(all(target_arch = "arm", target_feature = "neon")); + let flush = ppc_flush || arm_flush; + if flush && self.is_subnormal() { + <$ty>::copysign(0., self) + } else { + self + } + } + } + )* + } +} + +macro_rules! impl_else { + { $($ty:ty),* } => { + $( + impl FlushSubnormals for $ty {} + )* + } +} + +impl_float! { f32, f64 } +impl_else! { i8, i16, i32, i64, isize, u8, u16, u32, u64, usize } From 8c89a7240c668b92cbd66d8afc1d939ae45141c4 Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Sat, 22 Jul 2023 00:44:48 -0400 Subject: [PATCH 0009/1762] Enable more cross tests --- .github/workflows/ci.yml | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ff377fce3467..5ae654bef3ba0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -171,36 +171,19 @@ jobs: runs-on: ubuntu-latest strategy: fail-fast: false - # TODO: Sadly, we cant configure target-feature in a meaningful way - # because `cross` doesn't tell qemu to enable any non-default cpu - # features, nor does it give us a way to do so. - # - # Ultimately, we'd like to do something like [rust-lang/stdarch][stdarch]. - # This is a lot more complex... but in practice it's likely that we can just - # snarf the docker config from around [here][1000-dockerfiles]. - # - # [stdarch]: https://github.com/rust-lang/stdarch/blob/a5db4eaf/.github/workflows/main.yml#L67 - # [1000-dockerfiles]: https://github.com/rust-lang/stdarch/tree/a5db4eaf/ci/docker matrix: target: - - i586-unknown-linux-gnu - # 32-bit arm has a few idiosyncracies like having subnormal flushing - # to zero on by default. Ideally we'd set - armv7-unknown-linux-gnueabihf - aarch64-unknown-linux-gnu - # Note: The issue above means neither of these mips targets will use - # MSA (mips simd) but MIPS uses a nonstandard binary representation - # for NaNs which makes it worth testing on despite that. + - powerpc-unknown-linux-gnu + - powerpc64-unknown-linux-gnu + - powerpc64le-unknown-linux-gnu + - riscv64gc-unknown-linux-gnu + # MIPS uses a nonstandard binary representation for NaNs which makes it worth testing # - mips-unknown-linux-gnu # - mips64-unknown-linux-gnuabi64 - - riscv64gc-unknown-linux-gnu - # TODO this test works, but it appears to time out - # - powerpc-unknown-linux-gnu - # TODO this test is broken, but it appears to be a problem with QEMU, not us. - # - powerpc64le-unknown-linux-gnu - # TODO enable this once a new version of cross is released - # - powerpc64-unknown-linux-gnu + target_feature: ["", "+native"] steps: - uses: actions/checkout@v2 @@ -217,11 +200,14 @@ jobs: # being part of the tarball means we can't just use the download/latest # URL :( run: | - CROSS_URL=https://github.com/rust-embedded/cross/releases/download/v0.2.1/cross-v0.2.1-x86_64-unknown-linux-gnu.tar.gz + CROSS_URL=https://github.com/cross-rs/cross/releases/download/v0.2.5/cross-x86_64-unknown-linux-gnu.tar.gz mkdir -p "$HOME/.bin" curl -sfSL --retry-delay 10 --retry 5 "${CROSS_URL}" | tar zxf - -C "$HOME/.bin" echo "$HOME/.bin" >> $GITHUB_PATH + - name: Configure RUSTFLAGS + run: echo "-Ctarget-feature=${{ matrix.target_feature }}" >> $GITHUB_ENV + - name: Test (debug) run: cross test --verbose --target=${{ matrix.target }} From 36c8bf363fa8f5afe6e56641ae7fd4ae65bb1deb Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Sat, 22 Jul 2023 08:55:55 -0400 Subject: [PATCH 0010/1762] Improve cross tests --- .github/workflows/ci.yml | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ae654bef3ba0..7758409a92d7d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -167,7 +167,7 @@ jobs: RUSTFLAGS: ${{ matrix.rustflags }} cross-tests: - name: "${{ matrix.target }} (via cross)" + name: "${{ matrix.target_feature }} on ${{ matrix.target }} (via cross)" runs-on: ubuntu-latest strategy: fail-fast: false @@ -175,6 +175,7 @@ jobs: matrix: target: - armv7-unknown-linux-gnueabihf + - thumbv7neon-unknown-linux-gnueabihf - aarch64-unknown-linux-gnu - powerpc-unknown-linux-gnu - powerpc64-unknown-linux-gnu @@ -183,7 +184,11 @@ jobs: # MIPS uses a nonstandard binary representation for NaNs which makes it worth testing # - mips-unknown-linux-gnu # - mips64-unknown-linux-gnuabi64 - target_feature: ["", "+native"] + target_feature: "default" + include: + - { target: powerpc64-unknown-linux-gnu, target_feature: "native" } + - { target: powerpc64le-unknown-linux-gnu, target_feature: "native" } + - { target: riscv64gc-unknown-linux-gnu, target_feature: "native" } steps: - uses: actions/checkout@v2 @@ -206,7 +211,18 @@ jobs: echo "$HOME/.bin" >> $GITHUB_PATH - name: Configure RUSTFLAGS - run: echo "-Ctarget-feature=${{ matrix.target_feature }}" >> $GITHUB_ENV + shell: bash + run: | + case "${{ matrix.target_feature }}" in + default) + echo "RUSTFLAGS=" >> $GITHUB_ENV;; + native) + echo "RUSTFLAGS=-Ctarget-cpu=native" >> $GITHUB_ENV + ;; + *) + echo "RUSTFLAGS=-Ctarget-feature=${{ matrix.target_feature }}" >> $GITHUB_ENV + ;; + esac - name: Test (debug) run: cross test --verbose --target=${{ matrix.target }} From 40f04353d8fa40484a1df8ca15091b07413b5655 Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Sat, 22 Jul 2023 08:58:37 -0400 Subject: [PATCH 0011/1762] Fix workflow --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7758409a92d7d..73d38b41a3864 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -184,7 +184,7 @@ jobs: # MIPS uses a nonstandard binary representation for NaNs which makes it worth testing # - mips-unknown-linux-gnu # - mips64-unknown-linux-gnuabi64 - target_feature: "default" + target_feature: [default] include: - { target: powerpc64-unknown-linux-gnu, target_feature: "native" } - { target: powerpc64le-unknown-linux-gnu, target_feature: "native" } From 415b50f6715c7967fc83c5a05dd6917a344f0b5f Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Sat, 22 Jul 2023 09:16:42 -0400 Subject: [PATCH 0012/1762] Flush subnormals in f32::fract test --- crates/core_simd/tests/round.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/core_simd/tests/round.rs b/crates/core_simd/tests/round.rs index aacf7bd3bcc2c..191c39e2370c3 100644 --- a/crates/core_simd/tests/round.rs +++ b/crates/core_simd/tests/round.rs @@ -43,7 +43,7 @@ macro_rules! float_rounding_test { } fn fract() { - test_helpers::test_unary_elementwise( + test_helpers::test_unary_elementwise_flush_subnormals( &Vector::::fract, &Scalar::fract, &|_| true, From 1948b02e40293c56e9498a5e553d29d48fcc3836 Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Sat, 22 Jul 2023 09:35:18 -0400 Subject: [PATCH 0013/1762] Don't use native cpu in cross --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 73d38b41a3864..d36cad638afc7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -186,9 +186,9 @@ jobs: # - mips64-unknown-linux-gnuabi64 target_feature: [default] include: - - { target: powerpc64-unknown-linux-gnu, target_feature: "native" } - - { target: powerpc64le-unknown-linux-gnu, target_feature: "native" } - - { target: riscv64gc-unknown-linux-gnu, target_feature: "native" } + - { target: powerpc64-unknown-linux-gnu, target_feature: "+vsx" } + - { target: powerpc64le-unknown-linux-gnu, target_feature: "+vsx" } + - { target: riscv64gc-unknown-linux-gnu, target_feature: "+zvl128b" } steps: - uses: actions/checkout@v2 From 94f20143a9d6f69f5472565c6694d05277112ad3 Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Sat, 22 Jul 2023 09:48:26 -0400 Subject: [PATCH 0014/1762] Enable v extension on riscv --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d36cad638afc7..89c355e11906a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -188,7 +188,7 @@ jobs: include: - { target: powerpc64-unknown-linux-gnu, target_feature: "+vsx" } - { target: powerpc64le-unknown-linux-gnu, target_feature: "+vsx" } - - { target: riscv64gc-unknown-linux-gnu, target_feature: "+zvl128b" } + - { target: riscv64gc-unknown-linux-gnu, target_feature: "+v,+zvl128b" } steps: - uses: actions/checkout@v2 From 5c6405ba8956d5a0252789b7152fb0c5b684b67f Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Sat, 22 Jul 2023 10:24:32 -0400 Subject: [PATCH 0015/1762] Disable riscv v extension --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 89c355e11906a..c0429a1332c4a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -188,7 +188,8 @@ jobs: include: - { target: powerpc64-unknown-linux-gnu, target_feature: "+vsx" } - { target: powerpc64le-unknown-linux-gnu, target_feature: "+vsx" } - - { target: riscv64gc-unknown-linux-gnu, target_feature: "+v,+zvl128b" } + # We should test this, but cross currently can't run it + # - { target: riscv64gc-unknown-linux-gnu, target_feature: "+v,+zvl128b" } steps: - uses: actions/checkout@v2 From 49e92a2918f9f3d95ad8a0060a2d63d6f7b52950 Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Sat, 22 Jul 2023 14:18:16 -0400 Subject: [PATCH 0016/1762] Improve powerpc subnormal flushing check --- .github/workflows/ci.yml | 4 +--- crates/test_helpers/src/subnormals.rs | 6 +++++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c0429a1332c4a..42172968341e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -186,6 +186,7 @@ jobs: # - mips64-unknown-linux-gnuabi64 target_feature: [default] include: + - { target: powerpc-unknown-linux-gnu, target_feature: "+altivec" } - { target: powerpc64-unknown-linux-gnu, target_feature: "+vsx" } - { target: powerpc64le-unknown-linux-gnu, target_feature: "+vsx" } # We should test this, but cross currently can't run it @@ -217,9 +218,6 @@ jobs: case "${{ matrix.target_feature }}" in default) echo "RUSTFLAGS=" >> $GITHUB_ENV;; - native) - echo "RUSTFLAGS=-Ctarget-cpu=native" >> $GITHUB_ENV - ;; *) echo "RUSTFLAGS=-Ctarget-feature=${{ matrix.target_feature }}" >> $GITHUB_ENV ;; diff --git a/crates/test_helpers/src/subnormals.rs b/crates/test_helpers/src/subnormals.rs index 122304f96db41..d46e8524116ce 100644 --- a/crates/test_helpers/src/subnormals.rs +++ b/crates/test_helpers/src/subnormals.rs @@ -13,7 +13,11 @@ macro_rules! impl_float { impl FlushSubnormals for $ty { fn flush(self) -> Self { let is_f32 = core::mem::size_of::() == 4; - let ppc_flush = is_f32 && cfg!(all(target_arch = "powerpc64", target_endian = "big", not(target_feature = "vsx"))); + let ppc_flush = is_f32 && cfg!(all( + any(target_arch = "powerpc", all(target_arch = "powerpc64", target_endian = "big")), + target_feature = "altivec", + not(target_feature = "vsx"), + )); let arm_flush = is_f32 && cfg!(all(target_arch = "arm", target_feature = "neon")); let flush = ppc_flush || arm_flush; if flush && self.is_subnormal() { From e73d02929abe6c24b1223a007333d7799e50bb57 Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Sat, 22 Jul 2023 16:02:00 -0400 Subject: [PATCH 0017/1762] Specify emulated CPUs --- .github/workflows/ci.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 42172968341e4..42e2ba55c28af 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -187,8 +187,8 @@ jobs: target_feature: [default] include: - { target: powerpc-unknown-linux-gnu, target_feature: "+altivec" } - - { target: powerpc64-unknown-linux-gnu, target_feature: "+vsx" } - - { target: powerpc64le-unknown-linux-gnu, target_feature: "+vsx" } + - { target: powerpc64-unknown-linux-gnu, target_feature: "+power10-vector" } + - { target: powerpc64le-unknown-linux-gnu, target_feature: "+power10-vector" } # We should test this, but cross currently can't run it # - { target: riscv64gc-unknown-linux-gnu, target_feature: "+v,+zvl128b" } @@ -212,6 +212,12 @@ jobs: curl -sfSL --retry-delay 10 --retry 5 "${CROSS_URL}" | tar zxf - -C "$HOME/.bin" echo "$HOME/.bin" >> $GITHUB_PATH + - name: Configure Emulated CPUs + run: | + echo "CARGO_TARGET_POWERPC_UNKNOWN_LINUX_GNU_RUNNER=qemu-ppc -cpu e600" >> $GITHUB_ENV + echo "CARGO_TARGET_POWERPC64_UNKNOWN_LINUX_GNU_RUNNER=qemu-ppc64 -cpu power10" >> $GITHUB_ENV + echo "CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_RUNNER=qemu-riscv64 -cpu rv64,zba=true,zbb=true,v=true,vlen=256,vext_spec=v1.0" >> $GITHUB_ENV + - name: Configure RUSTFLAGS shell: bash run: | From d07ce3cef9223c918ae2381fd46ff53ce0cf38b4 Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Sat, 22 Jul 2023 17:56:21 -0400 Subject: [PATCH 0018/1762] Account for possible qemu bug --- crates/test_helpers/src/lib.rs | 6 +++-- crates/test_helpers/src/subnormals.rs | 35 +++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/crates/test_helpers/src/lib.rs b/crates/test_helpers/src/lib.rs index 63308a2ca33dc..d032ded576d40 100644 --- a/crates/test_helpers/src/lib.rs +++ b/crates/test_helpers/src/lib.rs @@ -1,3 +1,5 @@ +#![feature(stdsimd, powerpc_target_feature)] + pub mod array; #[cfg(target_arch = "wasm32")] @@ -198,7 +200,7 @@ pub fn test_unary_elementwise_flush_subnormals< Vector: Into<[Scalar; LANES]> + From<[Scalar; LANES]> + Copy, VectorResult: Into<[ScalarResult; LANES]> + From<[ScalarResult; LANES]> + Copy, { - let flush = |x: Scalar| FlushSubnormals::flush(fs(FlushSubnormals::flush(x))); + let flush = |x: Scalar| subnormals::flush(fs(subnormals::flush_in(x))); test_1(&|x: [Scalar; LANES]| { proptest::prop_assume!(check(x)); let result_v: [ScalarResult; LANES] = fv(x.into()).into(); @@ -308,7 +310,7 @@ pub fn test_binary_elementwise_flush_subnormals< VectorResult: Into<[ScalarResult; LANES]> + From<[ScalarResult; LANES]> + Copy, { let flush = |x: Scalar1, y: Scalar2| { - FlushSubnormals::flush(fs(FlushSubnormals::flush(x), FlushSubnormals::flush(y))) + subnormals::flush(fs(subnormals::flush_in(x), subnormals::flush_in(y))) }; test_2(&|x: [Scalar1; LANES], y: [Scalar2; LANES]| { proptest::prop_assume!(check(x, y)); diff --git a/crates/test_helpers/src/subnormals.rs b/crates/test_helpers/src/subnormals.rs index d46e8524116ce..585b80bb6c739 100644 --- a/crates/test_helpers/src/subnormals.rs +++ b/crates/test_helpers/src/subnormals.rs @@ -41,3 +41,38 @@ macro_rules! impl_else { impl_float! { f32, f64 } impl_else! { i8, i16, i32, i64, isize, u8, u16, u32, u64, usize } + +/// AltiVec should flush subnormal inputs to zero, but QEMU seems to only flush outputs. +/// https://gitlab.com/qemu-project/qemu/-/issues/1779 +#[cfg(all(target_arch = "powerpc", target_feature = "altivec"))] +fn in_buggy_qemu() -> bool { + use std::sync::OnceLock; + static BUGGY: OnceLock = OnceLock::new(); + + fn add(x: f32, y: f32) -> f32 { + use core::arch::powerpc::*; + let array: [f32; 4] = + unsafe { core::mem::transmute(vec_add(vec_splats(x), vec_splats(y))) }; + array[0] + } + + *BUGGY.get_or_init(|| add(-1.0857398e-38, 0.).is_sign_negative()) +} + +#[cfg(all(target_arch = "powerpc", target_feature = "altivec"))] +pub fn flush_in(x: T) -> T { + if in_buggy_qemu() { + x + } else { + x.flush() + } +} + +#[cfg(not(all(target_arch = "powerpc", target_feature = "altivec")))] +pub fn flush_in(x: T) -> T { + x.flush() +} + +pub fn flush(x: T) -> T { + x.flush() +} From ad747af0bb3f2249ec2a87b9f25f22b238dc700a Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Sat, 22 Jul 2023 18:33:33 -0400 Subject: [PATCH 0019/1762] Apply workaround to ppc64 --- crates/test_helpers/src/subnormals.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/crates/test_helpers/src/subnormals.rs b/crates/test_helpers/src/subnormals.rs index 585b80bb6c739..ec0f1fb24b936 100644 --- a/crates/test_helpers/src/subnormals.rs +++ b/crates/test_helpers/src/subnormals.rs @@ -44,13 +44,20 @@ impl_else! { i8, i16, i32, i64, isize, u8, u16, u32, u64, usize } /// AltiVec should flush subnormal inputs to zero, but QEMU seems to only flush outputs. /// https://gitlab.com/qemu-project/qemu/-/issues/1779 -#[cfg(all(target_arch = "powerpc", target_feature = "altivec"))] +#[cfg(all( + any(target_arch = "powerpc", target_arch = "powerpc64"), + target_feature = "altivec" +))] fn in_buggy_qemu() -> bool { use std::sync::OnceLock; static BUGGY: OnceLock = OnceLock::new(); fn add(x: f32, y: f32) -> f32 { + #[cfg(target_arch = "powerpc")] use core::arch::powerpc::*; + #[cfg(target_arch = "powerpc64")] + use core::arch::powerpc64::*; + let array: [f32; 4] = unsafe { core::mem::transmute(vec_add(vec_splats(x), vec_splats(y))) }; array[0] @@ -59,7 +66,10 @@ fn in_buggy_qemu() -> bool { *BUGGY.get_or_init(|| add(-1.0857398e-38, 0.).is_sign_negative()) } -#[cfg(all(target_arch = "powerpc", target_feature = "altivec"))] +#[cfg(all( + any(target_arch = "powerpc", target_arch = "powerpc64"), + target_feature = "altivec" +))] pub fn flush_in(x: T) -> T { if in_buggy_qemu() { x @@ -68,7 +78,10 @@ pub fn flush_in(x: T) -> T { } } -#[cfg(not(all(target_arch = "powerpc", target_feature = "altivec")))] +#[cfg(not(all( + any(target_arch = "powerpc", target_arch = "powerpc64"), + target_feature = "altivec" +)))] pub fn flush_in(x: T) -> T { x.flush() } From ca12492584e77fbb2d982942c9cf2843b8f99487 Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Sat, 22 Jul 2023 18:35:04 -0400 Subject: [PATCH 0020/1762] Revert some CI changes --- .github/workflows/ci.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 42e2ba55c28af..4dd334a134435 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -187,8 +187,8 @@ jobs: target_feature: [default] include: - { target: powerpc-unknown-linux-gnu, target_feature: "+altivec" } - - { target: powerpc64-unknown-linux-gnu, target_feature: "+power10-vector" } - - { target: powerpc64le-unknown-linux-gnu, target_feature: "+power10-vector" } + - { target: powerpc64-unknown-linux-gnu, target_feature: "+vsx" } + - { target: powerpc64le-unknown-linux-gnu, target_feature: "+vsx" } # We should test this, but cross currently can't run it # - { target: riscv64gc-unknown-linux-gnu, target_feature: "+v,+zvl128b" } @@ -215,8 +215,7 @@ jobs: - name: Configure Emulated CPUs run: | echo "CARGO_TARGET_POWERPC_UNKNOWN_LINUX_GNU_RUNNER=qemu-ppc -cpu e600" >> $GITHUB_ENV - echo "CARGO_TARGET_POWERPC64_UNKNOWN_LINUX_GNU_RUNNER=qemu-ppc64 -cpu power10" >> $GITHUB_ENV - echo "CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_RUNNER=qemu-riscv64 -cpu rv64,zba=true,zbb=true,v=true,vlen=256,vext_spec=v1.0" >> $GITHUB_ENV + # echo "CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_RUNNER=qemu-riscv64 -cpu rv64,zba=true,zbb=true,v=true,vlen=256,vext_spec=v1.0" >> $GITHUB_ENV - name: Configure RUSTFLAGS shell: bash From 52d6397da7d75deb32efa6636839ed4bcc6b2fdc Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Sat, 22 Jul 2023 19:22:30 -0400 Subject: [PATCH 0021/1762] Flush subnormals in reduce tests --- crates/core_simd/tests/ops_macros.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/core_simd/tests/ops_macros.rs b/crates/core_simd/tests/ops_macros.rs index 1f5d9488ad0eb..22265b8cf8655 100644 --- a/crates/core_simd/tests/ops_macros.rs +++ b/crates/core_simd/tests/ops_macros.rs @@ -96,9 +96,11 @@ macro_rules! impl_common_integer_tests { test_helpers::test_lanes! { fn reduce_sum() { test_helpers::test_1(&|x| { + use test_helpers::subnormals::{flush, flush_in}; test_helpers::prop_assert_biteq! ( $vector::::from_array(x).reduce_sum(), x.iter().copied().fold(0 as $scalar, $scalar::wrapping_add), + flush(x.iter().copied().map(flush_in).fold(0 as $scalar, $scalar::wrapping_add)), ); Ok(()) }); @@ -106,9 +108,11 @@ macro_rules! impl_common_integer_tests { fn reduce_product() { test_helpers::test_1(&|x| { + use test_helpers::subnormals::{flush, flush_in}; test_helpers::prop_assert_biteq! ( $vector::::from_array(x).reduce_product(), x.iter().copied().fold(1 as $scalar, $scalar::wrapping_mul), + flush(x.iter().copied().map(flush_in).fold(1 as $scalar, $scalar::wrapping_mul)), ); Ok(()) }); From bd4e6616f36a47f74059803fbfd0b8ddeff1b46f Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Sat, 22 Jul 2023 20:50:49 -0400 Subject: [PATCH 0022/1762] Adjust clamp test --- crates/core_simd/tests/ops_macros.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/core_simd/tests/ops_macros.rs b/crates/core_simd/tests/ops_macros.rs index 22265b8cf8655..7e705892b5385 100644 --- a/crates/core_simd/tests/ops_macros.rs +++ b/crates/core_simd/tests/ops_macros.rs @@ -516,7 +516,7 @@ macro_rules! impl_float_tests { fn simd_clamp() { test_helpers::test_3(&|value: [Scalar; LANES], mut min: [Scalar; LANES], mut max: [Scalar; LANES]| { - use test_helpers::subnormals::FlushSubnormals; + use test_helpers::subnormals::flush_in; for (min, max) in min.iter_mut().zip(max.iter_mut()) { if max < min { core::mem::swap(min, max); @@ -535,13 +535,14 @@ macro_rules! impl_float_tests { } let mut result_scalar_flush = [Scalar::default(); LANES]; for i in 0..LANES { - result_scalar_flush[i] = value[i]; - if FlushSubnormals::flush(value[i]) < FlushSubnormals::flush(min[i]) { - result_scalar_flush[i] = min[i]; + let mut value = flush_in(value[i]); + if value < flush_in(min[i]) { + value = min[i]; } - if FlushSubnormals::flush(value[i]) > FlushSubnormals::flush(max[i]) { - result_scalar_flush[i] = max[i]; + if value > flush_in(max[i]) { + value = max[i]; } + result_scalar_flush[i] = value } let result_vector = Vector::from_array(value).simd_clamp(min.into(), max.into()).to_array(); test_helpers::prop_assert_biteq!(result_vector, result_scalar, result_scalar_flush); From 616cb6e9e6de58dd9409d815078451ee550fb946 Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Sat, 22 Jul 2023 23:00:51 -0400 Subject: [PATCH 0023/1762] Disable simd_clamp test on ppc64 --- crates/core_simd/tests/ops_macros.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/core_simd/tests/ops_macros.rs b/crates/core_simd/tests/ops_macros.rs index 7e705892b5385..a6d6798672824 100644 --- a/crates/core_simd/tests/ops_macros.rs +++ b/crates/core_simd/tests/ops_macros.rs @@ -514,6 +514,8 @@ macro_rules! impl_float_tests { assert!(n_zero.simd_max(p_zero).to_array().iter().all(|x| *x == 0.)); } + #[cfg(not(all(target_arch = "powerpc64", target_feature = "vsx")))] + // https://gitlab.com/qemu-project/qemu/-/issues/1780 fn simd_clamp() { test_helpers::test_3(&|value: [Scalar; LANES], mut min: [Scalar; LANES], mut max: [Scalar; LANES]| { use test_helpers::subnormals::flush_in; From fd712fe3f39470160baf95ff8eb6fb00ebf31453 Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Sat, 22 Jul 2023 23:38:20 -0400 Subject: [PATCH 0024/1762] Fix test skip --- crates/core_simd/tests/ops_macros.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/core_simd/tests/ops_macros.rs b/crates/core_simd/tests/ops_macros.rs index a6d6798672824..7b309df22cb73 100644 --- a/crates/core_simd/tests/ops_macros.rs +++ b/crates/core_simd/tests/ops_macros.rs @@ -514,9 +514,11 @@ macro_rules! impl_float_tests { assert!(n_zero.simd_max(p_zero).to_array().iter().all(|x| *x == 0.)); } - #[cfg(not(all(target_arch = "powerpc64", target_feature = "vsx")))] - // https://gitlab.com/qemu-project/qemu/-/issues/1780 fn simd_clamp() { + if cfg!(all(target_arch = "powerpc64", target_feature = "vsx")) { + // https://gitlab.com/qemu-project/qemu/-/issues/1780 + return; + } test_helpers::test_3(&|value: [Scalar; LANES], mut min: [Scalar; LANES], mut max: [Scalar; LANES]| { use test_helpers::subnormals::flush_in; for (min, max) in min.iter_mut().zip(max.iter_mut()) { From 86b112204ade0203d1c2272a523e26b4593fb60a Mon Sep 17 00:00:00 2001 From: clubby789 Date: Fri, 21 Jul 2023 15:59:28 +0000 Subject: [PATCH 0025/1762] Improve diagnostic for const ctors in array repeat expressions --- compiler/rustc_hir_typeck/src/expr.rs | 35 ++++++++++++--- compiler/rustc_middle/src/traits/mod.rs | 26 +++++++++-- .../src/traits/error_reporting/suggestions.rs | 29 ++++++++---- .../const-blocks/fn-call-in-non-const.stderr | 6 ++- .../ui/consts/const-blocks/trait-error.stderr | 6 ++- tests/ui/consts/const-fn-in-vec.rs | 8 +++- tests/ui/consts/const-fn-in-vec.stderr | 44 ++++++++++++++++--- 7 files changed, 126 insertions(+), 28 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 29488c9011a19..b04d83a884f56 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -1507,21 +1507,42 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } _ => {} } - // If someone calls a const fn, they can extract that call out into a separate constant (or a const - // block in the future), so we check that to tell them that in the diagnostic. Does not affect typeck. - let is_const_fn = match element.kind { + // If someone calls a const fn or constructs a const value, they can extract that + // out into a separate constant (or a const block in the future), so we check that + // to tell them that in the diagnostic. Does not affect typeck. + let is_constable = match element.kind { hir::ExprKind::Call(func, _args) => match *self.node_ty(func.hir_id).kind() { - ty::FnDef(def_id, _) => tcx.is_const_fn(def_id), - _ => false, + ty::FnDef(def_id, _) if tcx.is_const_fn(def_id) => traits::IsConstable::Fn, + _ => traits::IsConstable::No, }, - _ => false, + hir::ExprKind::Path(qpath) => { + match self.typeck_results.borrow().qpath_res(&qpath, element.hir_id) { + Res::Def(DefKind::Ctor(_, CtorKind::Const), _) => traits::IsConstable::Ctor, + _ => traits::IsConstable::No, + } + } + _ => traits::IsConstable::No, }; // If the length is 0, we don't create any elements, so we don't copy any. If the length is 1, we // don't copy that one element, we move it. Only check for Copy if the length is larger. if count.try_eval_target_usize(tcx, self.param_env).map_or(true, |len| len > 1) { let lang_item = self.tcx.require_lang_item(LangItem::Copy, None); - let code = traits::ObligationCauseCode::RepeatElementCopy { is_const_fn }; + let code = traits::ObligationCauseCode::RepeatElementCopy { + is_constable, + elt_type: element_ty, + elt_span: element.span, + elt_stmt_span: self + .tcx + .hir() + .parent_iter(element.hir_id) + .find_map(|(_, node)| match node { + hir::Node::Item(it) => Some(it.span), + hir::Node::Stmt(stmt) => Some(stmt.span), + _ => None, + }) + .expect("array repeat expressions must be inside an item or statement"), + }; self.require_type_meets(element_ty, element.span, code, lang_item); } } diff --git a/compiler/rustc_middle/src/traits/mod.rs b/compiler/rustc_middle/src/traits/mod.rs index b7ffed57a0be0..2e1c3fe1fed24 100644 --- a/compiler/rustc_middle/src/traits/mod.rs +++ b/compiler/rustc_middle/src/traits/mod.rs @@ -301,9 +301,14 @@ pub enum ObligationCauseCode<'tcx> { InlineAsmSized, /// `[expr; N]` requires `type_of(expr): Copy`. RepeatElementCopy { - /// If element is a `const fn` we display a help message suggesting to move the - /// function call to a new `const` item while saying that `T` doesn't implement `Copy`. - is_const_fn: bool, + /// If element is a `const fn` or const ctor we display a help message suggesting + /// to move it to a new `const` item while saying that `T` doesn't implement `Copy`. + is_constable: IsConstable, + elt_type: Ty<'tcx>, + elt_span: Span, + /// Span of the statement/item in which the repeat expression occurs. We can use this to + /// place a `const` declaration before it + elt_stmt_span: Span, }, /// Types of fields (other than the last, except for packed structs) in a struct must be sized. @@ -448,6 +453,21 @@ pub enum ObligationCauseCode<'tcx> { TypeAlias(InternedObligationCauseCode<'tcx>, Span, DefId), } +/// Whether a value can be extracted into a const. +/// Used for diagnostics around array repeat expressions. +#[derive(Copy, Clone, Debug, PartialEq, Eq, HashStable, TyEncodable, TyDecodable)] +pub enum IsConstable { + No, + /// Call to a const fn + Fn, + /// Use of a const ctor + Ctor, +} + +crate::TrivialTypeTraversalAndLiftImpls! { + IsConstable, +} + /// The 'location' at which we try to perform HIR-based wf checking. /// This information is used to obtain an `hir::Ty`, which /// we can walk in order to obtain precise spans for any diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs index 073a2a2b1a0c9..edae9e10818b4 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs @@ -27,6 +27,7 @@ use rustc_infer::infer::error_reporting::TypeErrCtxt; use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; use rustc_infer::infer::{DefineOpaqueTypes, InferOk, LateBoundRegionConversionTime}; use rustc_middle::hir::map; +use rustc_middle::traits::IsConstable; use rustc_middle::ty::error::TypeError::{self, Sorts}; use rustc_middle::ty::{ self, suggest_arbitrary_trait_bound, suggest_constraining_type_param, AdtKind, @@ -2832,20 +2833,30 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { )); } } - ObligationCauseCode::RepeatElementCopy { is_const_fn } => { + ObligationCauseCode::RepeatElementCopy { is_constable, elt_type, elt_span, elt_stmt_span } => { err.note( "the `Copy` trait is required because this value will be copied for each element of the array", ); - - if is_const_fn { - err.help( - "consider creating a new `const` item and initializing it with the result \ - of the function call to be used in the repeat position, like \ - `const VAL: Type = const_fn();` and `let x = [VAL; 42];`", - ); + let value_kind = match is_constable { + IsConstable::Fn => Some("the result of the function call"), + IsConstable::Ctor => Some("the result of the constructor"), + _ => None + }; + let sm = tcx.sess.source_map(); + if let Some(value_kind) = value_kind && + let Ok(snip) = sm.span_to_snippet(elt_span) + { + let help_msg = format!( + "consider creating a new `const` item and initializing it with {value_kind} \ + to be used in the repeat position"); + let indentation = sm.indentation_before(elt_stmt_span).unwrap_or_default(); + err.multipart_suggestion(help_msg, vec![ + (elt_stmt_span.shrink_to_lo(), format!("const ARRAY_REPEAT_VALUE: {elt_type} = {snip};\n{indentation}")), + (elt_span, "ARRAY_REPEAT_VALUE".to_string()) + ], Applicability::MachineApplicable); } - if self.tcx.sess.is_nightly_build() && is_const_fn { + if self.tcx.sess.is_nightly_build() && matches!(is_constable, IsConstable::Fn|IsConstable::Ctor) { err.help( "create an inline `const` block, see RFC #2920 \ for more information", diff --git a/tests/ui/consts/const-blocks/fn-call-in-non-const.stderr b/tests/ui/consts/const-blocks/fn-call-in-non-const.stderr index 174103eeba4c4..eb8b8ac753430 100644 --- a/tests/ui/consts/const-blocks/fn-call-in-non-const.stderr +++ b/tests/ui/consts/const-blocks/fn-call-in-non-const.stderr @@ -6,13 +6,17 @@ LL | let _: [Option; 2] = [no_copy(); 2]; | = note: required for `Option` to implement `Copy` = note: the `Copy` trait is required because this value will be copied for each element of the array - = help: consider creating a new `const` item and initializing it with the result of the function call to be used in the repeat position, like `const VAL: Type = const_fn();` and `let x = [VAL; 42];` = help: create an inline `const` block, see RFC #2920 for more information help: consider annotating `Bar` with `#[derive(Copy)]` | LL + #[derive(Copy)] LL | struct Bar; | +help: consider creating a new `const` item and initializing it with the result of the function call to be used in the repeat position + | +LL ~ const ARRAY_REPEAT_VALUE: Option = no_copy(); +LL ~ let _: [Option; 2] = [ARRAY_REPEAT_VALUE; 2]; + | error: aborting due to previous error diff --git a/tests/ui/consts/const-blocks/trait-error.stderr b/tests/ui/consts/const-blocks/trait-error.stderr index 06fa4b0b1f30c..858ffa820e215 100644 --- a/tests/ui/consts/const-blocks/trait-error.stderr +++ b/tests/ui/consts/const-blocks/trait-error.stderr @@ -10,9 +10,13 @@ note: required for `Foo` to implement `Copy` LL | #[derive(Copy, Clone)] | ^^^^ unsatisfied trait bound introduced in this `derive` macro = note: the `Copy` trait is required because this value will be copied for each element of the array - = help: consider creating a new `const` item and initializing it with the result of the function call to be used in the repeat position, like `const VAL: Type = const_fn();` and `let x = [VAL; 42];` = help: create an inline `const` block, see RFC #2920 for more information = note: this error originates in the derive macro `Copy` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider creating a new `const` item and initializing it with the result of the function call to be used in the repeat position + | +LL ~ const ARRAY_REPEAT_VALUE: Foo = Foo(String::new()); +LL ~ [ARRAY_REPEAT_VALUE; 4]; + | error: aborting due to previous error diff --git a/tests/ui/consts/const-fn-in-vec.rs b/tests/ui/consts/const-fn-in-vec.rs index a40290eca0972..0483800efefd1 100644 --- a/tests/ui/consts/const-fn-in-vec.rs +++ b/tests/ui/consts/const-fn-in-vec.rs @@ -1,7 +1,11 @@ +static _MAYBE_STRINGS: [Option; 5] = [None; 5]; +//~^ ERROR the trait bound `String: Copy` is not satisfied + fn main() { // should hint to create an inline `const` block // or to create a new `const` item - let strings: [String; 5] = [String::new(); 5]; + let _strings: [String; 5] = [String::new(); 5]; + //~^ ERROR the trait bound `String: Copy` is not satisfied + let _maybe_strings: [Option; 5] = [None; 5]; //~^ ERROR the trait bound `String: Copy` is not satisfied - println!("{:?}", strings); } diff --git a/tests/ui/consts/const-fn-in-vec.stderr b/tests/ui/consts/const-fn-in-vec.stderr index 9eb7524b5044b..4593034bfaea3 100644 --- a/tests/ui/consts/const-fn-in-vec.stderr +++ b/tests/ui/consts/const-fn-in-vec.stderr @@ -1,13 +1,47 @@ error[E0277]: the trait bound `String: Copy` is not satisfied - --> $DIR/const-fn-in-vec.rs:4:33 + --> $DIR/const-fn-in-vec.rs:1:47 | -LL | let strings: [String; 5] = [String::new(); 5]; - | ^^^^^^^^^^^^^ the trait `Copy` is not implemented for `String` +LL | static _MAYBE_STRINGS: [Option; 5] = [None; 5]; + | ^^^^ the trait `Copy` is not implemented for `String` | + = note: required for `Option` to implement `Copy` = note: the `Copy` trait is required because this value will be copied for each element of the array - = help: consider creating a new `const` item and initializing it with the result of the function call to be used in the repeat position, like `const VAL: Type = const_fn();` and `let x = [VAL; 42];` = help: create an inline `const` block, see RFC #2920 for more information +help: consider creating a new `const` item and initializing it with the result of the constructor to be used in the repeat position + | +LL + const ARRAY_REPEAT_VALUE: Option = None; +LL ~ static _MAYBE_STRINGS: [Option; 5] = [ARRAY_REPEAT_VALUE; 5]; + | + +error[E0277]: the trait bound `String: Copy` is not satisfied + --> $DIR/const-fn-in-vec.rs:7:34 + | +LL | let _strings: [String; 5] = [String::new(); 5]; + | ^^^^^^^^^^^^^ the trait `Copy` is not implemented for `String` + | + = note: the `Copy` trait is required because this value will be copied for each element of the array + = help: create an inline `const` block, see RFC #2920 for more information +help: consider creating a new `const` item and initializing it with the result of the function call to be used in the repeat position + | +LL ~ const ARRAY_REPEAT_VALUE: String = String::new(); +LL ~ let _strings: [String; 5] = [ARRAY_REPEAT_VALUE; 5]; + | + +error[E0277]: the trait bound `String: Copy` is not satisfied + --> $DIR/const-fn-in-vec.rs:9:48 + | +LL | let _maybe_strings: [Option; 5] = [None; 5]; + | ^^^^ the trait `Copy` is not implemented for `String` + | + = note: required for `Option` to implement `Copy` + = note: the `Copy` trait is required because this value will be copied for each element of the array + = help: create an inline `const` block, see RFC #2920 for more information +help: consider creating a new `const` item and initializing it with the result of the constructor to be used in the repeat position + | +LL ~ const ARRAY_REPEAT_VALUE: Option = None; +LL ~ let _maybe_strings: [Option; 5] = [ARRAY_REPEAT_VALUE; 5]; + | -error: aborting due to previous error +error: aborting due to 3 previous errors For more information about this error, try `rustc --explain E0277`. From dbcbc3e4c50e4127e7034aef962f4241143e6c79 Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Thu, 27 Jul 2023 00:07:28 -0400 Subject: [PATCH 0026/1762] Disable misbehaving targets --- .github/workflows/ci.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4dd334a134435..ca1ab996a7b30 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -175,20 +175,24 @@ jobs: matrix: target: - armv7-unknown-linux-gnueabihf - - thumbv7neon-unknown-linux-gnueabihf - - aarch64-unknown-linux-gnu + - thumbv7neon-unknown-linux-gnueabihf # includes neon by default + - aarch64-unknown-linux-gnu # includes neon by default - powerpc-unknown-linux-gnu - - powerpc64-unknown-linux-gnu - - powerpc64le-unknown-linux-gnu + - powerpc64le-unknown-linux-gnu # includes altivec by default - riscv64gc-unknown-linux-gnu # MIPS uses a nonstandard binary representation for NaNs which makes it worth testing + # non-nightly since https://github.com/rust-lang/rust/pull/113274 # - mips-unknown-linux-gnu # - mips64-unknown-linux-gnuabi64 + # Lots of errors in QEMU and no real hardware to test on. Not clear if it's QEMU or bad codegen. + # - powerpc64-unknown-linux-gnu target_feature: [default] include: - - { target: powerpc-unknown-linux-gnu, target_feature: "+altivec" } - { target: powerpc64-unknown-linux-gnu, target_feature: "+vsx" } - { target: powerpc64le-unknown-linux-gnu, target_feature: "+vsx" } + # Fails due to QEMU floating point errors, probably handling subnormals incorrectly. + # This target is somewhat redundant, since ppc64le has altivec as well. + # - { target: powerpc-unknown-linux-gnu, target_feature: "+altivec" } # We should test this, but cross currently can't run it # - { target: riscv64gc-unknown-linux-gnu, target_feature: "+v,+zvl128b" } From cb461aceb3da91c116f13cdc5a3574bdf028923a Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Thu, 27 Jul 2023 01:02:26 -0400 Subject: [PATCH 0027/1762] Only flush comparison in test --- crates/core_simd/tests/ops_macros.rs | 6 +++--- crates/test_helpers/src/lib.rs | 29 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/crates/core_simd/tests/ops_macros.rs b/crates/core_simd/tests/ops_macros.rs index 7b309df22cb73..8386850cb90b5 100644 --- a/crates/core_simd/tests/ops_macros.rs +++ b/crates/core_simd/tests/ops_macros.rs @@ -539,11 +539,11 @@ macro_rules! impl_float_tests { } let mut result_scalar_flush = [Scalar::default(); LANES]; for i in 0..LANES { - let mut value = flush_in(value[i]); - if value < flush_in(min[i]) { + let mut value = value[i]; + if flush_in(value) < flush_in(min[i]) { value = min[i]; } - if value > flush_in(max[i]) { + if flush_in(value) > flush_in(max[i]) { value = max[i]; } result_scalar_flush[i] = value diff --git a/crates/test_helpers/src/lib.rs b/crates/test_helpers/src/lib.rs index d032ded576d40..b80c745aaf2fb 100644 --- a/crates/test_helpers/src/lib.rs +++ b/crates/test_helpers/src/lib.rs @@ -336,6 +336,35 @@ pub fn test_binary_elementwise_flush_subnormals< }); } +/// Test a unary vector function against a unary scalar function, applied elementwise. +#[inline(never)] +pub fn test_binary_mask_elementwise( + fv: &dyn Fn(Vector1, Vector2) -> Mask, + fs: &dyn Fn(Scalar1, Scalar2) -> bool, + check: &dyn Fn([Scalar1; LANES], [Scalar2; LANES]) -> bool, +) where + Scalar1: Copy + core::fmt::Debug + DefaultStrategy, + Scalar2: Copy + core::fmt::Debug + DefaultStrategy, + Vector1: Into<[Scalar1; LANES]> + From<[Scalar1; LANES]> + Copy, + Vector2: Into<[Scalar2; LANES]> + From<[Scalar2; LANES]> + Copy, + Mask: Into<[bool; LANES]> + From<[bool; LANES]> + Copy, +{ + test_2(&|x: [Scalar1; LANES], y: [Scalar2; LANES]| { + proptest::prop_assume!(check(x, y)); + let result_v: [bool; LANES] = fv(x.into(), y.into()).into(); + let result_s: [bool; LANES] = x + .iter() + .copied() + .zip(y.iter().copied()) + .map(|(x, y)| fs(x, y)) + .collect::>() + .try_into() + .unwrap(); + crate::prop_assert_biteq!(result_v, result_s); + Ok(()) + }); +} + /// Test a binary vector-scalar function against a binary scalar function, applied elementwise. #[inline(never)] pub fn test_binary_scalar_rhs_elementwise< From baa5791a453dccf5d09404e601cc6403c6a9cd3b Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Thu, 27 Jul 2023 01:03:43 -0400 Subject: [PATCH 0028/1762] Document odd test behavior --- crates/core_simd/tests/ops_macros.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/core_simd/tests/ops_macros.rs b/crates/core_simd/tests/ops_macros.rs index 8386850cb90b5..f6ded66e9fc32 100644 --- a/crates/core_simd/tests/ops_macros.rs +++ b/crates/core_simd/tests/ops_macros.rs @@ -539,6 +539,7 @@ macro_rules! impl_float_tests { } let mut result_scalar_flush = [Scalar::default(); LANES]; for i in 0..LANES { + // Comparisons flush-to-zero, but return value selection is _not_ flushed. let mut value = value[i]; if flush_in(value) < flush_in(min[i]) { value = min[i]; From 5e5745318a7efdfd3f927102550ba4697c4f5863 Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Thu, 27 Jul 2023 10:15:05 -0400 Subject: [PATCH 0029/1762] Disable big endian ppc64 --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca1ab996a7b30..ed1589be4f16d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -188,7 +188,6 @@ jobs: # - powerpc64-unknown-linux-gnu target_feature: [default] include: - - { target: powerpc64-unknown-linux-gnu, target_feature: "+vsx" } - { target: powerpc64le-unknown-linux-gnu, target_feature: "+vsx" } # Fails due to QEMU floating point errors, probably handling subnormals incorrectly. # This target is somewhat redundant, since ppc64le has altivec as well. From 6cdb7df44313d395ce582331c9191e278f227422 Mon Sep 17 00:00:00 2001 From: SabrinaJewson Date: Thu, 27 Jul 2023 16:29:13 +0100 Subject: [PATCH 0030/1762] Override `Waker::clone_from` to avoid cloning `Waker`s unnecessarily --- library/core/src/task/wake.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/library/core/src/task/wake.rs b/library/core/src/task/wake.rs index b63fd5c9095fc..817e39942c053 100644 --- a/library/core/src/task/wake.rs +++ b/library/core/src/task/wake.rs @@ -231,6 +231,10 @@ impl fmt::Debug for Context<'_> { /// this might be done to wake a future when a blocking function call completes on another /// thread. /// +/// Note that it is preferable to use `waker.clone_from(&new_waker)` instead +/// of `*waker = new_waker.clone()`, as the former will avoid cloning the waker +/// unnecessarily if the two wakers [wake the same task](Self::will_wake). +/// /// [`Future::poll()`]: core::future::Future::poll /// [`Poll::Pending`]: core::task::Poll::Pending #[cfg_attr(not(doc), repr(transparent))] // work around https://github.com/rust-lang/rust/issues/66401 @@ -302,7 +306,9 @@ impl Waker { /// when the `Waker`s would awaken the same task. However, if this function /// returns `true`, it is guaranteed that the `Waker`s will awaken the same task. /// - /// This function is primarily used for optimization purposes. + /// This function is primarily used for optimization purposes — for example, + /// this type's [`clone_from`](Self::clone_from) implementation uses it to + /// avoid cloning the waker when they would wake the same task anyway. #[inline] #[must_use] #[stable(feature = "futures_api", since = "1.36.0")] @@ -382,6 +388,13 @@ impl Clone for Waker { waker: unsafe { (self.waker.vtable.clone)(self.waker.data) }, } } + + #[inline] + fn clone_from(&mut self, source: &Self) { + if !self.will_wake(source) { + *self = source.clone(); + } + } } #[stable(feature = "futures_api", since = "1.36.0")] From 6e8d21ee760d4672ed6c374c9be1687c531499fb Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Thu, 27 Jul 2023 13:21:56 -0400 Subject: [PATCH 0031/1762] Define portability --- crates/core_simd/src/core_simd_docs.md | 33 ++++++++++++++++++++++++++ crates/core_simd/src/mod.rs | 3 ++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/crates/core_simd/src/core_simd_docs.md b/crates/core_simd/src/core_simd_docs.md index 15e8ed0253e14..3d2e737e2afe5 100644 --- a/crates/core_simd/src/core_simd_docs.md +++ b/crates/core_simd/src/core_simd_docs.md @@ -2,3 +2,36 @@ Portable SIMD module. This module offers a portable abstraction for SIMD operations that is not bound to any particular hardware architecture. + +# What is "portable"? + +This module provides a SIMD implementation that is fast and predictable on any target. + +### Portable SIMD works on every target + +Unlike target-specific SIMD in `std::arch`, portable SIMD compiles for every target. +In this regard, it is just like "regular" Rust. + +### Portable SIMD is consistent between targets + +A program using portable SIMD can expect identical behavior on any target. +In most regards, [`Simd`] can be thought of as a parallelized `[T; N]` and operates like a sequence of `T`. + +This has one notable exception: a handful of older architectures (e.g. `armv7` and `powerpc`) flush [subnormal](`f32::is_subnormal`) `f32` values to zero. +On these architectures, subnormal `f32` input values are replaced with zeros, and any operation producing subnormal `f32` values produces zeros instead. +This doesn't affect most architectures or programs. + +### Operations use the best instructions available + +Operations provided by this module compile to the best available SIMD instructions. + +Portable SIMD is not a low-level vendor library, and operations in portable SIMD _do not_ necessarily map to a single instruction. +Instead, they map to a reasonable implementation of the operation for the target. + +Consistency between targets is not compromised to use faster or fewer instructions. +In some cases, `std::arch` will provide a faster function that has slightly different behavior than the `std::simd` equivalent. +For example, [`_mm_min_ps`](`core::arch::x86_64::_mm_min_ps`) can be slightly faster than [`SimdFloat::simd_min`], but does not conform to the IEEE standard also used by [`f32::min`]. +When necessary, [`Simd`] can be converted to the types provided by `std::arch` to make use of target-specific functions. + +Many targets simply don't have SIMD, or don't support SIMD for a particular element type. +In those cases, regular scalar operations are generated instead. diff --git a/crates/core_simd/src/mod.rs b/crates/core_simd/src/mod.rs index f9891a3b7c1d7..dd954b7cc48b6 100644 --- a/crates/core_simd/src/mod.rs +++ b/crates/core_simd/src/mod.rs @@ -21,8 +21,9 @@ mod swizzle_dyn; mod vector; mod vendor; -#[doc = include_str!("core_simd_docs.md")] pub mod simd { + #![doc = include_str!("core_simd_docs.md")] + pub mod prelude; pub(crate) use crate::core_simd::intrinsics; From 927139d1e711a9fd276632616feea393693258f4 Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Thu, 27 Jul 2023 14:15:20 -0400 Subject: [PATCH 0032/1762] Add scalar shifts --- crates/core_simd/src/ops.rs | 1 + crates/core_simd/src/ops/shift_scalar.rs | 58 ++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 crates/core_simd/src/ops/shift_scalar.rs diff --git a/crates/core_simd/src/ops.rs b/crates/core_simd/src/ops.rs index b007456cf2cc5..63a96106283ac 100644 --- a/crates/core_simd/src/ops.rs +++ b/crates/core_simd/src/ops.rs @@ -6,6 +6,7 @@ use core::ops::{Shl, Shr}; mod assign; mod deref; +mod shift_scalar; mod unary; impl core::ops::Index for Simd diff --git a/crates/core_simd/src/ops/shift_scalar.rs b/crates/core_simd/src/ops/shift_scalar.rs new file mode 100644 index 0000000000000..77aac656395fb --- /dev/null +++ b/crates/core_simd/src/ops/shift_scalar.rs @@ -0,0 +1,58 @@ +// Shift operations uniquely typically only have a scalar on the right-hand side. +// Here, we implement shifts for scalar RHS arguments. + +use crate::simd::{LaneCount, Simd, SupportedLaneCount}; + +macro_rules! impl_splatted_shifts { + { impl $trait:ident :: $trait_fn:ident for $ty:ty } => { + impl core::ops::$trait<$ty> for Simd<$ty, N> + where + LaneCount: SupportedLaneCount, + { + type Output = Self; + fn $trait_fn(self, rhs: $ty) -> Self::Output { + self.$trait_fn(Simd::splat(rhs)) + } + } + + impl core::ops::$trait<&$ty> for Simd<$ty, N> + where + LaneCount: SupportedLaneCount, + { + type Output = Self; + fn $trait_fn(self, rhs: &$ty) -> Self::Output { + self.$trait_fn(Simd::splat(*rhs)) + } + } + + impl<'lhs, const N: usize> core::ops::$trait<$ty> for &'lhs Simd<$ty, N> + where + LaneCount: SupportedLaneCount, + { + type Output = Simd<$ty, N>; + fn $trait_fn(self, rhs: $ty) -> Self::Output { + self.$trait_fn(Simd::splat(rhs)) + } + } + + impl<'lhs, const N: usize> core::ops::$trait<&$ty> for &'lhs Simd<$ty, N> + where + LaneCount: SupportedLaneCount, + { + type Output = Simd<$ty, N>; + fn $trait_fn(self, rhs: &$ty) -> Self::Output { + self.$trait_fn(Simd::splat(*rhs)) + } + } + }; + { $($ty:ty),* } => { + $( + impl_splatted_shifts! { impl Shl::shl for $ty } + impl_splatted_shifts! { impl Shr::shr for $ty } + )* + } +} + +// In the past there were inference issues when generically splatting arguments. +// Enumerate them instead. +impl_splatted_shifts! { i8, i16, i32, i64, isize, u8, u16, u32, u64, usize } From 40a83be6eb91efffa1965f4480b8c035524d44be Mon Sep 17 00:00:00 2001 From: Ramon de C Valle Date: Thu, 27 Jul 2023 13:14:43 -0700 Subject: [PATCH 0033/1762] Format exploit mitigations documentation Formats lines in the exploit mitigations documentation to be at maximum 80 characters long. --- src/doc/rustc/src/exploit-mitigations.md | 338 +++++++++++------------ 1 file changed, 161 insertions(+), 177 deletions(-) diff --git a/src/doc/rustc/src/exploit-mitigations.md b/src/doc/rustc/src/exploit-mitigations.md index 172048704f48d..f02a4dcf963a2 100644 --- a/src/doc/rustc/src/exploit-mitigations.md +++ b/src/doc/rustc/src/exploit-mitigations.md @@ -1,12 +1,12 @@ # Exploit Mitigations -This chapter documents the exploit mitigations supported by the Rust -compiler, and is by no means an extensive survey of the Rust programming -language’s security features. +This chapter documents the exploit mitigations supported by the Rust compiler, +and is by no means an extensive survey of the Rust programming language’s +security features. This chapter is for software engineers working with the Rust programming -language, and assumes prior knowledge of the Rust programming language and -its toolchain. +language, and assumes prior knowledge of the Rust programming language and its +toolchain. ## Introduction @@ -14,8 +14,8 @@ its toolchain. The Rust programming language provides memory[1] and thread[2] safety guarantees via its ownership[3], references and borrowing[4], and slice types[5] features. However, Unsafe Rust[6] introduces unsafe blocks, unsafe -functions and methods, unsafe traits, and new types that are not subject to -the borrowing rules. +functions and methods, unsafe traits, and new types that are not subject to the +borrowing rules. Parts of the Rust standard library are implemented as safe abstractions over unsafe code (and historically have been vulnerable to memory corruption[7]). @@ -23,33 +23,31 @@ Furthermore, the Rust code and documentation encourage creating safe abstractions over unsafe code. This can cause a false sense of security if unsafe code is not properly reviewed and tested. -Unsafe Rust introduces features that do not provide the same memory and -thread safety guarantees. This causes programs or libraries to be -susceptible to memory corruption (CWE-119)[8] and concurrency issues -(CWE-557)[9]. Modern C and C++ compilers provide exploit mitigations to -increase the difficulty to exploit vulnerabilities resulting from these -issues. Therefore, the Rust compiler must also support these exploit -mitigations in order to mitigate vulnerabilities resulting from the use of -Unsafe Rust. This chapter documents these exploit mitigations and how they -apply to Rust. +Unsafe Rust introduces features that do not provide the same memory and thread +safety guarantees. This causes programs or libraries to be susceptible to +memory corruption (CWE-119)[8] and concurrency issues (CWE-557)[9]. Modern C +and C++ compilers provide exploit mitigations to increase the difficulty to +exploit vulnerabilities resulting from these issues. Therefore, the Rust +compiler must also support these exploit mitigations in order to mitigate +vulnerabilities resulting from the use of Unsafe Rust. This chapter documents +these exploit mitigations and how they apply to Rust. -This chapter does not discuss the effectiveness of these exploit mitigations -as they vary greatly depending on several factors besides their design and -implementation, but rather describe what they do, so their effectiveness can -be understood within a given context. +This chapter does not discuss the effectiveness of these exploit mitigations as +they vary greatly depending on several factors besides their design and +implementation, but rather describe what they do, so their effectiveness can be +understood within a given context. ## Exploit mitigations -This section documents the exploit mitigations applicable to the Rust -compiler when building programs for the Linux operating system on the AMD64 -architecture and equivalent.1 +This section documents the exploit mitigations applicable to the Rust compiler +when building programs for the Linux operating system on the AMD64 architecture +and equivalent.1 -The Rust Programming Language currently has no specification. The Rust -compiler (i.e., rustc) is the language reference implementation. All -references to “the Rust compiler” in this chapter refer to the language -reference implementation. +The Rust Programming Language currently has no specification. The Rust compiler +(i.e., rustc) is the language reference implementation. All references to “the +Rust compiler” in this chapter refer to the language reference implementation. Table I \ Summary of exploit mitigations supported by the Rust compiler when building @@ -83,8 +81,8 @@ instructing the dynamic linker to load it similarly to a shared object at a random load address, thus also benefiting from address-space layout randomization (ASLR). This is also referred to as “full ASLR”. -The Rust compiler supports position-independent executable, and enables it -by default since version 0.12.0 (2014-10-09)[10]–[13]. +The Rust compiler supports position-independent executable, and enables it by +default since version 0.12.0 (2014-10-09)[10]–[13]. ```text $ readelf -h target/release/hello-rust | grep Type: @@ -93,8 +91,7 @@ $ readelf -h target/release/hello-rust | grep Type: Fig. 1. Checking if an executable is a position-independent executable. An executable with an object type of `ET_DYN` (i.e., shared object) and not -`ET_EXEC` (i.e., executable) is a position-independent executable (see Fig. -1). +`ET_EXEC` (i.e., executable) is a position-independent executable (see Fig. 1). ### Integer overflow checks @@ -104,8 +101,8 @@ behavior (which may cause vulnerabilities) by checking for results of signed and unsigned integer computations that cannot be represented in their type, resulting in an overflow or wraparound. -The Rust compiler supports integer overflow checks, and enables it when -debug assertions are enabled since version 1.1.0 (2015-06-25)[14]–[20]. +The Rust compiler supports integer overflow checks, and enables it when debug +assertions are enabled since version 1.1.0 (2015-06-25)[14]–[20]. ```compile_fail fn main() { @@ -123,7 +120,7 @@ $ cargo run thread 'main' panicked at 'attempt to add with overflow', src/main.rs:3:23 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace. ``` -Fig. 3. Build and execution of hello-rust-integer with debug assertions +Fig. 3. Build and execution of hello-rust-integer with debug assertions enabled. ```text @@ -133,24 +130,24 @@ $ cargo run --release Running `target/release/hello-rust-integer` u: 0 ``` -Fig. 4. Build and execution of hello-rust-integer with debug assertions +Fig. 4. Build and execution of hello-rust-integer with debug assertions disabled. -Integer overflow checks are enabled when debug assertions are enabled (see -Fig. 3), and disabled when debug assertions are disabled (see Fig. 4). To -enable integer overflow checks independently, use the option to control -integer overflow checks, scoped attributes, or explicit checking methods -such as `checked_add`2. -It is recommended that explicit wrapping methods such as `wrapping_add` be -used when wrapping semantics are intended, and that explicit checking and -wrapping methods always be used when using Unsafe Rust. +It is recommended that explicit wrapping methods such as `wrapping_add` be used +when wrapping semantics are intended, and that explicit checking and wrapping +methods always be used when using Unsafe Rust. -2\. See [the `u32` docs](../std/primitive.u32.html) -for more information on the checked, overflowing, saturating, and wrapping -methods (using u32 as an example). +2\. See [the `u32` docs](../std/primitive.u32.html) for more +information on the checked, overflowing, saturating, and wrapping methods +(using u32 as an example). ### Non-executable memory regions @@ -158,29 +155,28 @@ class="reversefootnote" role="doc-backlink">↩ Non-executable memory regions increase the difficulty of exploitation by limiting the memory regions that can be used to execute arbitrary code. Most modern processors provide support for the operating system to mark memory -regions as non executable, but it was previously emulated by software, such -as in grsecurity/PaX's -[PAGEEXEC](https://pax.grsecurity.net/docs/pageexec.txt) and -[SEGMEXEC](https://pax.grsecurity.net/docs/segmexec.txt), on processors that -did not provide support for it. This is also known as “No Execute (NX) Bit”, -“Execute Disable (XD) Bit”, “Execute Never (XN) Bit”, and others. +regions as non executable, but it was previously emulated by software, such as +in grsecurity/PaX's [PAGEEXEC](https://pax.grsecurity.net/docs/pageexec.txt) +and [SEGMEXEC](https://pax.grsecurity.net/docs/segmexec.txt), on processors +that did not provide support for it. This is also known as “No Execute (NX) +Bit”, “Execute Disable (XD) Bit”, “Execute Never (XN) Bit”, and others. The Rust compiler supports non-executable memory regions, and enables it by -default since its initial release, version 0.1 (2012-01-20)[21], [22], but -has regressed since then[23]–[25], and enforced by default since version -1.8.0 (2016-04-14)[25]. +default since its initial release, version 0.1 (2012-01-20)[21], [22], but has +regressed since then[23]–[25], and enforced by default since version 1.8.0 +(2016-04-14)[25]. ```text $ readelf -l target/release/hello-rust | grep -A 1 GNU_STACK GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 0x10 ``` -Fig. 5. Checking if non-executable memory regions are enabled for a given +Fig. 5. Checking if non-executable memory regions are enabled for a given binary. -The presence of an element of type `PT_GNU_STACK` in the program header -table with the `PF_X` (i.e., executable) flag unset indicates non-executable -memory regions3 are enabled for a given binary (see Fig. 5). Conversely, the presence of an element of type `PT_GNU_STACK` in the program header table with the `PF_X` flag set or the absence of an element of type @@ -196,16 +192,15 @@ class="reversefootnote" role="doc-backlink">↩ Stack clashing protection protects the stack from overlapping with another memory region—allowing arbitrary data in both to be overwritten using each -other—by reading from the stack pages as the stack grows to cause a page -fault when attempting to read from the guard page/region. This is also -referred to as “stack probes” or “stack probing”. +other—by reading from the stack pages as the stack grows to cause a page fault +when attempting to read from the guard page/region. This is also referred to as +“stack probes” or “stack probing”. The Rust compiler supports stack clashing protection via stack probing, and enables it by default since version 1.20.0 (2017-08-31)[26]–[29]. ![Screenshot of IDA Pro listing cross references to __rust_probestack in hello-rust.](images/image1.png "Cross references to __rust_probestack in hello-rust.") -Fig. 6. IDA Pro listing cross references to `__rust_probestack` in -hello-rust. +Fig. 6. IDA Pro listing cross references to `__rust_probestack` in hello-rust. ```rust fn hello() { @@ -223,11 +218,11 @@ Fig 7. Modified hello-rust. Fig. 8. IDA Pro listing cross references to `__rust_probestack` in modified hello-rust. -To check if stack clashing protection is enabled for a given binary, search -for cross references to `__rust_probestack`. The `__rust_probestack` is -called in the prologue of functions whose stack size is larger than a page -size (see Fig. 6), and can be forced for illustration purposes by modifying -the hello-rust example as seen in Fig. 7 and Fig. 8. +To check if stack clashing protection is enabled for a given binary, search for +cross references to `__rust_probestack`. The `__rust_probestack` is called in +the prologue of functions whose stack size is larger than a page size (see Fig. +6), and can be forced for illustration purposes by modifying the hello-rust +example as seen in Fig. 7 and Fig. 8. ### Read-only relocations and immediate binding @@ -246,21 +241,20 @@ $ readelf -l target/release/hello-rust | grep GNU_RELRO ``` Fig. 9. Checking if read-only relocations is enabled for a given binary. -The presence of an element of type `PT_GNU_RELRO` in the program header -table indicates read-only relocations are enabled for a given binary (see -Fig. 9). Conversely, the absence of an element of type `PT_GNU_RELRO` in the -program header table indicates read-only relocations are not enabled for a -given binary. +The presence of an element of type `PT_GNU_RELRO` in the program header table +indicates read-only relocations are enabled for a given binary (see Fig. 9). +Conversely, the absence of an element of type `PT_GNU_RELRO` in the program +header table indicates read-only relocations are not enabled for a given +binary. **Immediate binding** protects additional segments containing relocations -(i.e., `.got.plt`) from being overwritten by instructing the dynamic linker -to perform all relocations before transferring control to the program during -startup, so all segments containing relocations can be marked read only -(when combined with read-only relocations). This is also referred to as -“full RELRO”. +(i.e., `.got.plt`) from being overwritten by instructing the dynamic linker to +perform all relocations before transferring control to the program during +startup, so all segments containing relocations can be marked read only (when +combined with read-only relocations). This is also referred to as “full RELRO”. -The Rust compiler supports immediate binding, and enables it by default -since version 1.21.0 (2017-10-12)[30], [31]. +The Rust compiler supports immediate binding, and enables it by default since +version 1.21.0 (2017-10-12)[30], [31]. ```text $ readelf -d target/release/hello-rust | grep BIND_NOW @@ -270,16 +264,15 @@ Fig. 10. Checking if immediate binding is enabled for a given binary. The presence of an element with the `DT_BIND_NOW` tag and the `DF_BIND_NOW` flag4 in the dynamic section indicates immediate -binding is enabled for a given binary (see Fig. 10). Conversely, the absence -of an element with the `DT_BIND_NOW` tag and the `DF_BIND_NOW` flag in the -dynamic section indicates immediate binding is not enabled for a given -binary. +class="footnote">4 in the dynamic section indicates immediate binding +is enabled for a given binary (see Fig. 10). Conversely, the absence of an +element with the `DT_BIND_NOW` tag and the `DF_BIND_NOW` flag in the dynamic +section indicates immediate binding is not enabled for a given binary. The presence of both an element of type `PT_GNU_RELRO` in the program header -table and of an element with the `DT_BIND_NOW` tag and the `DF_BIND_NOW` -flag in the dynamic section indicates full RELRO is enabled for a given -binary (see Fig. 9 and Fig. 10). +table and of an element with the `DT_BIND_NOW` tag and the `DF_BIND_NOW` flag +in the dynamic section indicates full RELRO is enabled for a given binary (see +Fig. 9 and Fig. 10). 4\. And the `DF_1_NOW` flag for some link editors. @@ -287,26 +280,24 @@ href="#fnref:4" class="reversefootnote" role="doc-backlink">↩ ### Heap corruption protection -Heap corruption protection protects memory allocated dynamically by -performing several checks, such as checks for corrupted links between list -elements, invalid pointers, invalid sizes, double/multiple “frees” of the -same memory allocated, and many corner cases of these. These checks are -implementation specific, and vary per allocator. +Heap corruption protection protects memory allocated dynamically by performing +several checks, such as checks for corrupted links between list elements, +invalid pointers, invalid sizes, double/multiple “frees” of the same memory +allocated, and many corner cases of these. These checks are implementation +specific, and vary per allocator. [ARM Memory Tagging Extension (MTE)](https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/enhancing-memory-safety), -when available, will provide hardware assistance for a probabilistic -mitigation to detect memory safety violations by tagging memory allocations, -and automatically checking that the correct tag is used on every memory -access. +when available, will provide hardware assistance for a probabilistic mitigation +to detect memory safety violations by tagging memory allocations, and +automatically checking that the correct tag is used on every memory access. Rust’s default allocator has historically been -[jemalloc](http://jemalloc.net/), and it has long been the cause of issues -and the subject of much discussion[32]–[38]. Consequently, it has been -removed as the default allocator in favor of the operating system’s standard -C library default allocator5 since version 1.32.0 -(2019-01-17)[39]. +[jemalloc](http://jemalloc.net/), and it has long been the cause of issues and +the subject of much discussion[32]–[38]. Consequently, it has been removed as +the default allocator in favor of the operating system’s standard C library +default allocator5 since version 1.32.0 (2019-01-17)[39]. ```rust,no_run fn main() { @@ -330,8 +321,7 @@ $ cargo run free(): invalid next size (normal) Aborted ``` -Fig. 12. Build and execution of hello-rust-heap with debug assertions -enabled. +Fig. 12. Build and execution of hello-rust-heap with debug assertions enabled. ```text $ cargo run --release @@ -341,47 +331,43 @@ $ cargo run --release free(): invalid next size (normal) Aborted ``` -Fig. 13. Build and execution of hello-rust-heap with debug assertions -disabled. +Fig. 13. Build and execution of hello-rust-heap with debug assertions disabled. Heap corruption checks are being performed when using the default allocator (i.e., the GNU Allocator) as seen in Fig. 12 and Fig. 13. 5\. Linux's standard C library default allocator is the GNU -Allocator, which is derived from ptmalloc (pthreads malloc) by Wolfram -Gloger, which in turn is derived from dlmalloc (Doug Lea malloc) by Doug -Lea. +Allocator, which is derived from ptmalloc (pthreads malloc) by Wolfram Gloger, +which in turn is derived from dlmalloc (Doug Lea malloc) by Doug Lea. ### Stack smashing protection -Stack smashing protection protects programs from stack-based buffer -overflows by inserting a random guard value between local variables and the -saved return instruction pointer, and checking if this value has changed -when returning from a function. This is also known as “Stack Protector” or -“Stack Smashing Protector (SSP)”. +Stack smashing protection protects programs from stack-based buffer overflows +by inserting a random guard value between local variables and the saved return +instruction pointer, and checking if this value has changed when returning from +a function. This is also known as “Stack Protector” or “Stack Smashing +Protector (SSP)”. The Rust compiler supports stack smashing protection on nightly builds[42]. ![Screenshot of IDA Pro listing cross references to __stack_chk_fail in hello-rust.](images/image3.png "Cross references to __stack_chk_fail in hello-rust.") -Fig. 14. IDA Pro listing cross references to `__stack_chk_fail` in -hello-rust. +Fig. 14. IDA Pro listing cross references to `__stack_chk_fail` in hello-rust. -To check if stack smashing protection is enabled for a given binary, search -for cross references to `__stack_chk_fail`. The presence of these -cross-references in Rust-compiled code (e.g., `hello_rust::main`) indicates -that the stack smashing protection is enabled (see Fig. 14). +To check if stack smashing protection is enabled for a given binary, search for +cross references to `__stack_chk_fail`. The presence of these cross-references +in Rust-compiled code (e.g., `hello_rust::main`) indicates that the stack +smashing protection is enabled (see Fig. 14). ### Forward-edge control flow protection -Forward-edge control flow protection protects programs from having its -control flow changed/hijacked by performing checks to ensure that -destinations of indirect branches are one of their valid destinations in the -control flow graph. The comprehensiveness of these checks vary per -implementation. This is also known as “forward-edge control flow integrity -(CFI)”. +Forward-edge control flow protection protects programs from having its control +flow changed/hijacked by performing checks to ensure that destinations of +indirect branches are one of their valid destinations in the control flow +graph. The comprehensiveness of these checks vary per implementation. This is +also known as “forward-edge control flow integrity (CFI)”. Newer processors provide hardware assistance for forward-edge control flow protection, such as ARM Branch Target Identification (BTI), ARM Pointer @@ -407,9 +393,9 @@ $ readelf -s -W target/debug/rust-cfi | grep "\.cfi" Fig. 15. Checking if LLVM CFI is enabled for a given binary[41]. The presence of symbols suffixed with ".cfi" or the `__cfi_init` symbol (and -references to `__cfi_check`) indicates that LLVM CFI (i.e., forward-edge control -flow protection) is enabled for a given binary. Conversely, the absence of -symbols suffixed with ".cfi" or the `__cfi_init` symbol (and references to +references to `__cfi_check`) indicates that LLVM CFI (i.e., forward-edge +control flow protection) is enabled for a given binary. Conversely, the absence +of symbols suffixed with ".cfi" or the `__cfi_init` symbol (and references to `__cfi_check`) indicates that LLVM CFI is not enabled for a given binary (see Fig. 15). @@ -421,32 +407,32 @@ class="reversefootnote" role="doc-backlink">↩ ### Backward-edge control flow protection **Shadow stack** protects saved return instruction pointers from being -overwritten by storing a copy of them on a separate (shadow) stack, and -using these copies as authoritative values when returning from functions. -This is also known as “ShadowCallStack” and “Return Flow Guard”, and is -considered an implementation of backward-edge control flow protection (or -“backward-edge CFI”). +overwritten by storing a copy of them on a separate (shadow) stack, and using +these copies as authoritative values when returning from functions. This is +also known as “ShadowCallStack” and “Return Flow Guard”, and is considered an +implementation of backward-edge control flow protection (or “backward-edge +CFI”). **Safe stack** protects not only the saved return instruction pointers, but -also register spills and some local variables from being overwritten by -storing unsafe variables, such as large arrays, on a separate (unsafe) -stack, and using these unsafe variables on the separate stack instead. This -is also known as “SafeStack”, and is also considered an implementation of -backward-edge control flow protection. +also register spills and some local variables from being overwritten by storing +unsafe variables, such as large arrays, on a separate (unsafe) stack, and using +these unsafe variables on the separate stack instead. This is also known as +“SafeStack”, and is also considered an implementation of backward-edge control +flow protection. -Both shadow and safe stack are intended to be a more comprehensive -alternatives to stack smashing protection as they protect the saved return -instruction pointers (and other data in the case of safe stack) from -arbitrary writes and non-linear out-of-bounds writes. +Both shadow and safe stack are intended to be a more comprehensive alternatives +to stack smashing protection as they protect the saved return instruction +pointers (and other data in the case of safe stack) from arbitrary writes and +non-linear out-of-bounds writes. Newer processors provide hardware assistance for backward-edge control flow -protection, such as ARM Pointer Authentication, and Intel Shadow Stack as -part of Intel CET. +protection, such as ARM Pointer Authentication, and Intel Shadow Stack as part +of Intel CET. -The Rust compiler supports shadow stack for aarch64 only -7 -on nightly Rust compilers [43]-[44]. Safe stack is available on nightly -Rust compilers [45]-[46]. +The Rust compiler supports shadow stack for aarch64 only 7 on nightly Rust +compilers [43]-[44]. Safe stack is available on nightly Rust compilers +[45]-[46]. ```text $ readelf -s target/release/hello-rust | grep __safestack_init @@ -454,15 +440,14 @@ $ readelf -s target/release/hello-rust | grep __safestack_init ``` Fig. 16. Checking if LLVM SafeStack is enabled for a given binary. -The presence of the `__safestack_init` symbol indicates that LLVM SafeStack -is enabled for a given binary (see Fig. 16). Conversely, the absence of the +The presence of the `__safestack_init` symbol indicates that LLVM SafeStack is +enabled for a given binary (see Fig. 16). Conversely, the absence of the `__safestack_init` symbol indicates that LLVM SafeStack is not enabled for a given binary. -7\. The shadow stack implementation for the AMD64 -architecture and equivalent in LLVM was removed due to performance and -security issues. +7\. The shadow stack implementation for the AMD64 architecture +and equivalent in LLVM was removed due to performance and security issues. ## Appendix @@ -470,29 +455,28 @@ role="doc-backlink">↩ As of the latest version of the [Linux Standard Base (LSB) Core Specification](https://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/progheader.html), the `PT_GNU_STACK` program header indicates whether the stack should be -executable, and the absence of this header indicates that the stack should -be executable. However, the Linux kernel currently sets the -`READ_IMPLIES_EXEC` personality upon loading any executable with the -`PT_GNU_STACK` program header and the `PF_X `flag set or with the absence of -this header, resulting in not only the stack, but also all readable virtual -memory mappings being executable. +executable, and the absence of this header indicates that the stack should be +executable. However, the Linux kernel currently sets the `READ_IMPLIES_EXEC` +personality upon loading any executable with the `PT_GNU_STACK` program header +and the `PF_X `flag set or with the absence of this header, resulting in not +only the stack, but also all readable virtual memory mappings being executable. An attempt to fix this [was made in 2012](https://lore.kernel.org/lkml/f298f914-2239-44e4-8aa1-a51282e7fac0@zmail15.collab.prod.int.phx2.redhat.com/), and another [was made in 2020](https://lore.kernel.org/kernel-hardening/20200327064820.12602-1-keescook@chromium.org/). The former never landed, and the latter partially fixed it, but introduced -other issues—the absence of the `PT_GNU_STACK` program header still causes -not only the stack, but also all readable virtual memory mappings to be -executable in some architectures, such as IA-32 and equivalent (or causes -the stack to be non-executable in some architectures, such as AMD64 and -equivalent, contradicting the LSB). - -The `READ_IMPLIES_EXEC` personality needs to be completely separated from -the `PT_GNU_STACK` program header by having a separate option for it (or -setarch -X could just be used whenever `READ_IMPLIES_EXEC` is needed), and -the absence of the `PT_GNU_STACK` program header needs to have more secure -defaults (unrelated to `READ_IMPLIES_EXEC`). +other issues—the absence of the `PT_GNU_STACK` program header still causes not +only the stack, but also all readable virtual memory mappings to be executable +in some architectures, such as IA-32 and equivalent (or causes the stack to be +non-executable in some architectures, such as AMD64 and equivalent, +contradicting the LSB). + +The `READ_IMPLIES_EXEC` personality needs to be completely separated from the +`PT_GNU_STACK` program header by having a separate option for it (or setarch -X +could just be used whenever `READ_IMPLIES_EXEC` is needed), and the absence of +the `PT_GNU_STACK` program header needs to have more secure defaults (unrelated +to `READ_IMPLIES_EXEC`). ## References From 5c97c0db2457872ef83a2b30c9d30f24963a1752 Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Thu, 27 Jul 2023 16:26:22 -0400 Subject: [PATCH 0034/1762] Add wrapping negation --- crates/core_simd/src/elements/uint.rs | 16 ++++++++++++++-- crates/core_simd/tests/ops_macros.rs | 10 ++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/crates/core_simd/src/elements/uint.rs b/crates/core_simd/src/elements/uint.rs index 3926c395ec9ac..c8bf24998ddd2 100644 --- a/crates/core_simd/src/elements/uint.rs +++ b/crates/core_simd/src/elements/uint.rs @@ -16,6 +16,12 @@ pub trait SimdUint: Copy + Sealed { #[must_use] fn cast(self) -> Self::Cast; + /// Wrapping negation. + /// + /// Like [`u32::wrapping_neg`], all applications of this function will wrap, with the exception + /// of `-0`. + fn wrapping_neg(self) -> Self; + /// Lanewise saturating add. /// /// # Examples @@ -74,7 +80,7 @@ pub trait SimdUint: Copy + Sealed { } macro_rules! impl_trait { - { $($ty:ty),* } => { + { $($ty:ident ($signed:ident)),* } => { $( impl Sealed for Simd<$ty, LANES> where @@ -95,6 +101,12 @@ macro_rules! impl_trait { unsafe { intrinsics::simd_as(self) } } + #[inline] + fn wrapping_neg(self) -> Self { + use crate::simd::SimdInt; + (-self.cast::<$signed>()).cast() + } + #[inline] fn saturating_add(self, second: Self) -> Self { // Safety: `self` is a vector @@ -153,4 +165,4 @@ macro_rules! impl_trait { } } -impl_trait! { u8, u16, u32, u64, usize } +impl_trait! { u8 (i8), u16 (i16), u32 (i32), u64 (i64), usize (isize) } diff --git a/crates/core_simd/tests/ops_macros.rs b/crates/core_simd/tests/ops_macros.rs index 3a02f3f01e1cf..ee0d3ce2f5ab4 100644 --- a/crates/core_simd/tests/ops_macros.rs +++ b/crates/core_simd/tests/ops_macros.rs @@ -327,6 +327,16 @@ macro_rules! impl_unsigned_tests { } } + test_helpers::test_lanes! { + fn wrapping_neg() { + test_helpers::test_unary_elementwise( + &Vector::::wrapping_neg, + &Scalar::wrapping_neg, + &|_| true, + ); + } + } + impl_binary_op_test!(Scalar, Add::add, AddAssign::add_assign, Scalar::wrapping_add); impl_binary_op_test!(Scalar, Sub::sub, SubAssign::sub_assign, Scalar::wrapping_sub); impl_binary_op_test!(Scalar, Mul::mul, MulAssign::mul_assign, Scalar::wrapping_mul); From e51ee248c33d8c1662c61fcfcb8cf1843979007f Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Fri, 28 Jul 2023 21:26:14 -0400 Subject: [PATCH 0035/1762] Add tests --- crates/core_simd/tests/ops_macros.rs | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/crates/core_simd/tests/ops_macros.rs b/crates/core_simd/tests/ops_macros.rs index 3a02f3f01e1cf..dfc0e1a370879 100644 --- a/crates/core_simd/tests/ops_macros.rs +++ b/crates/core_simd/tests/ops_macros.rs @@ -94,6 +94,36 @@ macro_rules! impl_binary_checked_op_test { macro_rules! impl_common_integer_tests { { $vector:ident, $scalar:ident } => { test_helpers::test_lanes! { + fn shr() { + use core::ops::Shr; + let shr = |x: $scalar, y: $scalar| x.wrapping_shr(y as _); + test_helpers::test_binary_elementwise( + &<$vector:: as Shr<$vector::>>::shr, + &shr, + &|_, _| true, + ); + test_helpers::test_binary_scalar_rhs_elementwise( + &<$vector:: as Shr<$scalar>>::shr, + &shr, + &|_, _| true, + ); + } + + fn shl() { + use core::ops::Shl; + let shl = |x: $scalar, y: $scalar| x.wrapping_shl(y as _); + test_helpers::test_binary_elementwise( + &<$vector:: as Shl<$vector::>>::shl, + &shl, + &|_, _| true, + ); + test_helpers::test_binary_scalar_rhs_elementwise( + &<$vector:: as Shl<$scalar>>::shl, + &shl, + &|_, _| true, + ); + } + fn reduce_sum() { test_helpers::test_1(&|x| { test_helpers::prop_assert_biteq! ( From 3da60554e59f0e9ffe85c717253dd0c3c681296b Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Fri, 28 Jul 2023 21:38:58 -0400 Subject: [PATCH 0036/1762] Add footnote --- crates/core_simd/src/core_simd_docs.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/core_simd/src/core_simd_docs.md b/crates/core_simd/src/core_simd_docs.md index 3d2e737e2afe5..8acdeb044279a 100644 --- a/crates/core_simd/src/core_simd_docs.md +++ b/crates/core_simd/src/core_simd_docs.md @@ -30,8 +30,10 @@ Instead, they map to a reasonable implementation of the operation for the target Consistency between targets is not compromised to use faster or fewer instructions. In some cases, `std::arch` will provide a faster function that has slightly different behavior than the `std::simd` equivalent. -For example, [`_mm_min_ps`](`core::arch::x86_64::_mm_min_ps`) can be slightly faster than [`SimdFloat::simd_min`], but does not conform to the IEEE standard also used by [`f32::min`]. +For example, [`_mm_min_ps`](`core::arch::x86_64::_mm_min_ps`)[^1] can be slightly faster than [`SimdFloat::simd_min`], but does not conform to the IEEE standard also used by [`f32::min`]. When necessary, [`Simd`] can be converted to the types provided by `std::arch` to make use of target-specific functions. Many targets simply don't have SIMD, or don't support SIMD for a particular element type. In those cases, regular scalar operations are generated instead. + +[^1]: `_mm_min_ps(x, y)` is equivalent to `x.simd_lt(y).select(x, y)` From 103b2df63b689a19169a7134a0b77d1c3c153943 Mon Sep 17 00:00:00 2001 From: Alex Zepeda Date: Fri, 28 Jul 2023 02:14:45 -0700 Subject: [PATCH 0037/1762] rustc_llvm: Link to libkstat on Solaris/SPARC getHostCPUName calls into libkstat but as of LLVM 16.0.6 libLLVMTargetParser is not explicitly linked against libkstat causing builds to fail due to undefined symbols. See also: llvm/llvm-project#64186 --- compiler/rustc_llvm/build.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/compiler/rustc_llvm/build.rs b/compiler/rustc_llvm/build.rs index aa1121d6bb3f6..107f2b65171b8 100644 --- a/compiler/rustc_llvm/build.rs +++ b/compiler/rustc_llvm/build.rs @@ -241,6 +241,11 @@ fn main() { cmd.arg("--system-libs"); } + // We need libkstat for getHostCPUName on SPARC builds. + if target.starts_with("sparcv9") && target.contains("solaris") { + println!("cargo:rustc-link-lib=kstat"); + } + if (target.starts_with("arm") && !target.contains("freebsd")) || target.starts_with("mips-") || target.starts_with("mipsel-") From 8101074e2e1b8fb3a7469446746625b7febb7f33 Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Sun, 30 Jul 2023 15:53:32 -0400 Subject: [PATCH 0038/1762] Add various integer ops --- crates/core_simd/src/elements/int.rs | 59 ++++++++++++++++++++++++++- crates/core_simd/src/elements/uint.rs | 53 ++++++++++++++++++++++++ crates/core_simd/src/intrinsics.rs | 6 +++ crates/core_simd/src/to_bytes.rs | 44 ++++++++++++++++++++ crates/core_simd/tests/ops_macros.rs | 48 ++++++++++++++++++++++ crates/core_simd/tests/to_bytes.rs | 16 ++++++-- 6 files changed, 220 insertions(+), 6 deletions(-) diff --git a/crates/core_simd/src/elements/int.rs b/crates/core_simd/src/elements/int.rs index 6db89ff9a659e..6992b679515d5 100644 --- a/crates/core_simd/src/elements/int.rs +++ b/crates/core_simd/src/elements/int.rs @@ -191,10 +191,29 @@ pub trait SimdInt: Copy + Sealed { /// Returns the cumulative bitwise "xor" across the lanes of the vector. fn reduce_xor(self) -> Self::Scalar; + + /// Reverses the byte order of each element. + fn swap_bytes(self) -> Self; + + /// Reverses the order of bits in each elemnent. + /// The least significant bit becomes the most significant bit, second least-significant bit becomes second most-significant bit, etc. + fn reverse_bits(self) -> Self; + + /// Returns the number of leading zeros in the binary representation of each element. + fn leading_zeros(self) -> Self; + + /// Returns the number of trailing zeros in the binary representation of each element. + fn trailing_zeros(self) -> Self; + + /// Returns the number of leading ones in the binary representation of each element. + fn leading_ones(self) -> Self; + + /// Returns the number of trailing ones in the binary representation of each element. + fn trailing_ones(self) -> Self; } macro_rules! impl_trait { - { $($ty:ty),* } => { + { $($ty:ident ($unsigned:ident)),* } => { $( impl Sealed for Simd<$ty, LANES> where @@ -307,9 +326,45 @@ macro_rules! impl_trait { // Safety: `self` is an integer vector unsafe { intrinsics::simd_reduce_xor(self) } } + + #[inline] + fn swap_bytes(self) -> Self { + // Safety: `self` is an integer vector + unsafe { intrinsics::simd_bswap(self) } + } + + #[inline] + fn reverse_bits(self) -> Self { + // Safety: `self` is an integer vector + unsafe { intrinsics::simd_bitreverse(self) } + } + + #[inline] + fn leading_zeros(self) -> Self { + // Safety: `self` is an integer vector + unsafe { intrinsics::simd_ctlz(self) } + } + + #[inline] + fn trailing_zeros(self) -> Self { + // Safety: `self` is an integer vector + unsafe { intrinsics::simd_cttz(self) } + } + + #[inline] + fn leading_ones(self) -> Self { + use crate::simd::SimdUint; + self.cast::<$unsigned>().leading_ones().cast() + } + + #[inline] + fn trailing_ones(self) -> Self { + use crate::simd::SimdUint; + self.cast::<$unsigned>().trailing_ones().cast() + } } )* } } -impl_trait! { i8, i16, i32, i64, isize } +impl_trait! { i8 (u8), i16 (u16), i32 (u32), i64 (u64), isize (usize) } diff --git a/crates/core_simd/src/elements/uint.rs b/crates/core_simd/src/elements/uint.rs index 3926c395ec9ac..7490340e4f4a2 100644 --- a/crates/core_simd/src/elements/uint.rs +++ b/crates/core_simd/src/elements/uint.rs @@ -71,6 +71,25 @@ pub trait SimdUint: Copy + Sealed { /// Returns the cumulative bitwise "xor" across the lanes of the vector. fn reduce_xor(self) -> Self::Scalar; + + /// Reverses the byte order of each element. + fn swap_bytes(self) -> Self; + + /// Reverses the order of bits in each elemnent. + /// The least significant bit becomes the most significant bit, second least-significant bit becomes second most-significant bit, etc. + fn reverse_bits(self) -> Self; + + /// Returns the number of leading zeros in the binary representation of each element. + fn leading_zeros(self) -> Self; + + /// Returns the number of trailing zeros in the binary representation of each element. + fn trailing_zeros(self) -> Self; + + /// Returns the number of leading ones in the binary representation of each element. + fn leading_ones(self) -> Self; + + /// Returns the number of trailing ones in the binary representation of each element. + fn trailing_ones(self) -> Self; } macro_rules! impl_trait { @@ -148,6 +167,40 @@ macro_rules! impl_trait { // Safety: `self` is an integer vector unsafe { intrinsics::simd_reduce_xor(self) } } + + #[inline] + fn swap_bytes(self) -> Self { + // Safety: `self` is an integer vector + unsafe { intrinsics::simd_bswap(self) } + } + + #[inline] + fn reverse_bits(self) -> Self { + // Safety: `self` is an integer vector + unsafe { intrinsics::simd_bitreverse(self) } + } + + #[inline] + fn leading_zeros(self) -> Self { + // Safety: `self` is an integer vector + unsafe { intrinsics::simd_ctlz(self) } + } + + #[inline] + fn trailing_zeros(self) -> Self { + // Safety: `self` is an integer vector + unsafe { intrinsics::simd_cttz(self) } + } + + #[inline] + fn leading_ones(self) -> Self { + (!self).leading_zeros() + } + + #[inline] + fn trailing_ones(self) -> Self { + (!self).trailing_zeros() + } } )* } diff --git a/crates/core_simd/src/intrinsics.rs b/crates/core_simd/src/intrinsics.rs index dd6698e2ba56c..b27893bc7294d 100644 --- a/crates/core_simd/src/intrinsics.rs +++ b/crates/core_simd/src/intrinsics.rs @@ -160,4 +160,10 @@ extern "platform-intrinsic" { /// convert an exposed address back to a pointer pub(crate) fn simd_from_exposed_addr(addr: T) -> U; + + // Integer operations + pub(crate) fn simd_bswap(x: T) -> T; + pub(crate) fn simd_bitreverse(x: T) -> T; + pub(crate) fn simd_ctlz(x: T) -> T; + pub(crate) fn simd_cttz(x: T) -> T; } diff --git a/crates/core_simd/src/to_bytes.rs b/crates/core_simd/src/to_bytes.rs index b36b1a347b226..563b0c95a8a7f 100644 --- a/crates/core_simd/src/to_bytes.rs +++ b/crates/core_simd/src/to_bytes.rs @@ -1,3 +1,5 @@ +use crate::simd::SimdUint; + macro_rules! impl_to_bytes { { $ty:ty, $size:literal } => { impl crate::simd::Simd<$ty, LANES> @@ -12,12 +14,54 @@ macro_rules! impl_to_bytes { unsafe { core::mem::transmute_copy(&self) } } + /// Return the memory representation of this integer as a byte array in big-endian + /// (network) byte order. + pub fn to_be_bytes(self) -> crate::simd::Simd { + let bytes = self.to_ne_bytes(); + if cfg!(target_endian = "big") { + bytes + } else { + bytes.swap_bytes() + } + } + + /// Return the memory representation of this integer as a byte array in little-endian + /// byte order. + pub fn to_le_bytes(self) -> crate::simd::Simd { + let bytes = self.to_ne_bytes(); + if cfg!(target_endian = "little") { + bytes + } else { + bytes.swap_bytes() + } + } + /// Create a native endian integer value from its memory representation as a byte array /// in native endianness. pub fn from_ne_bytes(bytes: crate::simd::Simd) -> Self { // Safety: transmuting between vectors is safe unsafe { core::mem::transmute_copy(&bytes) } } + + /// Create an integer value from its representation as a byte array in big endian. + pub fn from_be_bytes(bytes: crate::simd::Simd) -> Self { + let bytes = if cfg!(target_endian = "big") { + bytes + } else { + bytes.swap_bytes() + }; + Self::from_ne_bytes(bytes) + } + + /// Create an integer value from its representation as a byte array in little endian. + pub fn from_le_bytes(bytes: crate::simd::Simd) -> Self { + let bytes = if cfg!(target_endian = "little") { + bytes + } else { + bytes.swap_bytes() + }; + Self::from_ne_bytes(bytes) + } } } } diff --git a/crates/core_simd/tests/ops_macros.rs b/crates/core_simd/tests/ops_macros.rs index dfc0e1a370879..bd1856e1bcc5b 100644 --- a/crates/core_simd/tests/ops_macros.rs +++ b/crates/core_simd/tests/ops_macros.rs @@ -193,6 +193,54 @@ macro_rules! impl_common_integer_tests { Ok(()) }); } + + fn swap_bytes() { + test_helpers::test_unary_elementwise( + &$vector::::swap_bytes, + &$scalar::swap_bytes, + &|_| true, + ) + } + + fn reverse_bits() { + test_helpers::test_unary_elementwise( + &$vector::::reverse_bits, + &$scalar::reverse_bits, + &|_| true, + ) + } + + fn leading_zeros() { + test_helpers::test_unary_elementwise( + &$vector::::leading_zeros, + &|x| x.leading_zeros() as $scalar, + &|_| true, + ) + } + + fn trailing_zeros() { + test_helpers::test_unary_elementwise( + &$vector::::leading_zeros, + &|x| x.trailing_zeros() as $scalar, + &|_| true, + ) + } + + fn leading_ones() { + test_helpers::test_unary_elementwise( + &$vector::::leading_ones, + &|x| x.leading_ones() as $scalar, + &|_| true, + ) + } + + fn trailing_ones() { + test_helpers::test_unary_elementwise( + &$vector::::leading_ones, + &|x| x.trailing_ones() as $scalar, + &|_| true, + ) + } } } } diff --git a/crates/core_simd/tests/to_bytes.rs b/crates/core_simd/tests/to_bytes.rs index be0ee4349c579..7dd740d65dd54 100644 --- a/crates/core_simd/tests/to_bytes.rs +++ b/crates/core_simd/tests/to_bytes.rs @@ -7,8 +7,16 @@ use core_simd::simd::Simd; #[test] fn byte_convert() { let int = Simd::::from_array([0xdeadbeef, 0x8badf00d]); - let bytes = int.to_ne_bytes(); - assert_eq!(int[0].to_ne_bytes(), bytes[..4]); - assert_eq!(int[1].to_ne_bytes(), bytes[4..]); - assert_eq!(Simd::::from_ne_bytes(bytes), int); + let ne_bytes = int.to_ne_bytes(); + let be_bytes = int.to_be_bytes(); + let le_bytes = int.to_le_bytes(); + assert_eq!(int[0].to_ne_bytes(), ne_bytes[..4]); + assert_eq!(int[1].to_ne_bytes(), ne_bytes[4..]); + assert_eq!(int[0].to_be_bytes(), be_bytes[..4]); + assert_eq!(int[1].to_be_bytes(), be_bytes[4..]); + assert_eq!(int[0].to_le_bytes(), le_bytes[..4]); + assert_eq!(int[1].to_le_bytes(), le_bytes[4..]); + assert_eq!(Simd::::from_ne_bytes(ne_bytes), int); + assert_eq!(Simd::::from_be_bytes(be_bytes), int); + assert_eq!(Simd::::from_le_bytes(le_bytes), int); } From b1245ffb1277ad4274f600607f9058281baf3bc6 Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Sun, 30 Jul 2023 16:20:20 -0400 Subject: [PATCH 0039/1762] Fix bad copy-paste --- crates/core_simd/tests/ops_macros.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/core_simd/tests/ops_macros.rs b/crates/core_simd/tests/ops_macros.rs index bd1856e1bcc5b..23e914e64b507 100644 --- a/crates/core_simd/tests/ops_macros.rs +++ b/crates/core_simd/tests/ops_macros.rs @@ -220,7 +220,7 @@ macro_rules! impl_common_integer_tests { fn trailing_zeros() { test_helpers::test_unary_elementwise( - &$vector::::leading_zeros, + &$vector::::trailing_zeros, &|x| x.trailing_zeros() as $scalar, &|_| true, ) @@ -236,7 +236,7 @@ macro_rules! impl_common_integer_tests { fn trailing_ones() { test_helpers::test_unary_elementwise( - &$vector::::leading_ones, + &$vector::::trailing_ones, &|x| x.trailing_ones() as $scalar, &|_| true, ) From c948b703ff57f25a1a41be5e03553065454080b9 Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Tue, 1 Aug 2023 07:58:44 -0400 Subject: [PATCH 0040/1762] Simplify signed leading_ones/trailing_ones --- crates/core_simd/src/elements/int.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/core_simd/src/elements/int.rs b/crates/core_simd/src/elements/int.rs index 6992b679515d5..e81ed7bf601a1 100644 --- a/crates/core_simd/src/elements/int.rs +++ b/crates/core_simd/src/elements/int.rs @@ -213,7 +213,7 @@ pub trait SimdInt: Copy + Sealed { } macro_rules! impl_trait { - { $($ty:ident ($unsigned:ident)),* } => { + { $($ty:ty),* } => { $( impl Sealed for Simd<$ty, LANES> where @@ -353,18 +353,16 @@ macro_rules! impl_trait { #[inline] fn leading_ones(self) -> Self { - use crate::simd::SimdUint; - self.cast::<$unsigned>().leading_ones().cast() + (!self).leading_zeros() } #[inline] fn trailing_ones(self) -> Self { - use crate::simd::SimdUint; - self.cast::<$unsigned>().trailing_ones().cast() + (!self).trailing_zeros() } } )* } } -impl_trait! { i8 (u8), i16 (u16), i32 (u32), i64 (u64), isize (usize) } +impl_trait! { i8, i16, i32, i64, isize } From 7c1ab71f71087d88aade79925ae68a447397422f Mon Sep 17 00:00:00 2001 From: William Venner Date: Thu, 3 Aug 2023 09:52:57 +0100 Subject: [PATCH 0041/1762] Add assertion to test `skip_until` return value The extra `\0` in this commit is needed because the assertion on line 49 will fail otherwise (as `skip_until` stops reading on EOF and therefore does not read a trailing `\0`, returning 6 read bytes rather than the expected 7) --- library/std/src/io/tests.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/library/std/src/io/tests.rs b/library/std/src/io/tests.rs index 9a9a790d77d9c..4c5f86fe43163 100644 --- a/library/std/src/io/tests.rs +++ b/library/std/src/io/tests.rs @@ -27,7 +27,7 @@ fn read_until() { #[test] fn skip_until() { - let bytes: &[u8] = b"read\0ignore\0read\0ignore\0read\0ignore"; + let bytes: &[u8] = b"read\0ignore\0read\0ignore\0read\0ignore\0"; let mut reader = BufReader::new(bytes); // read from the bytes, alternating between @@ -41,10 +41,12 @@ fn skip_until() { break; } else { assert_eq!(out, b"read\0"); + assert_eq!(read, b"read\0".len()); } // skip past `ignore\0` - reader.skip_until(0).unwrap(); + let skipped = reader.skip_until(0).unwrap(); + assert_eq!(skipped, b"ignore\0".len()); } // ensure we are at the end of the byte slice and that we can skip no further From b1ad22941b6fc90a177ba896d60b367a8c216a55 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 10 Aug 2023 16:27:49 -0400 Subject: [PATCH 0042/1762] Allow setting `rla-*` labels via `rustbot` https://github.com/rust-lang/rust-log-analyzer/pull/75 adds a `rla-silenced` label flag that will turn off RLA updates for non-bors tests. Allow setting that labels and others via `rustbot`. --- triagebot.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/triagebot.toml b/triagebot.toml index 6b36e59dfc84d..0b71a67ebd56a 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -18,6 +18,7 @@ allow-unauthenticated = [ "relnotes", "requires-*", "regression-*", + "rla-*", "perf-*", "AsyncAwait-OnDeck", "needs-triage", From 940d87b2d05dcf074bfe5461999f244ca82f6403 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 21 Aug 2023 10:41:49 +0200 Subject: [PATCH 0043/1762] extend check.overrideCommand and buildScripts.overrideCommand docs regarding invocation strategy and location --- crates/rust-analyzer/src/config.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 40c50f6d1768f..dc678dae6ebc7 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -90,6 +90,12 @@ config_data! { /// and should therefore include `--message-format=json` or a similar /// option. /// + /// If there are multiple linked projects, this command is invoked for + /// each of them, with the working directory being the project root + /// (i.e., the folder containing the `Cargo.toml`). This can be overwritten + /// by changing `#rust-analyzer.cargo.buildScripts.invocationStrategy#` and + /// `#rust-analyzer.cargo.buildScripts.invocationLocation#`. + /// /// By default, a cargo invocation will be constructed for the configured /// targets and features, with the following base command line: /// @@ -183,7 +189,9 @@ config_data! { /// /// If there are multiple linked projects, this command is invoked for /// each of them, with the working directory being the project root - /// (i.e., the folder containing the `Cargo.toml`). + /// (i.e., the folder containing the `Cargo.toml`). This can be overwritten + /// by changing `#rust-analyzer.cargo.check.invocationStrategy#` and + /// `#rust-analyzer.cargo.check.invocationLocation#`. /// /// An example command would be: /// From 2de62be09b33a86f5f9f23bdc88c589dde2f767e Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 21 Aug 2023 10:51:58 +0200 Subject: [PATCH 0044/1762] projects/workspaces --- crates/rust-analyzer/src/config.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index dc678dae6ebc7..a6803b1b48039 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -90,8 +90,8 @@ config_data! { /// and should therefore include `--message-format=json` or a similar /// option. /// - /// If there are multiple linked projects, this command is invoked for - /// each of them, with the working directory being the project root + /// If there are multiple linked projects/workspaces, this command is invoked for + /// each of them, with the working directory being the workspace root /// (i.e., the folder containing the `Cargo.toml`). This can be overwritten /// by changing `#rust-analyzer.cargo.buildScripts.invocationStrategy#` and /// `#rust-analyzer.cargo.buildScripts.invocationLocation#`. @@ -187,8 +187,8 @@ config_data! { /// Cargo, you might also want to change /// `#rust-analyzer.cargo.buildScripts.overrideCommand#`. /// - /// If there are multiple linked projects, this command is invoked for - /// each of them, with the working directory being the project root + /// If there are multiple linked projects/workspaces, this command is invoked for + /// each of them, with the working directory being the workspace root /// (i.e., the folder containing the `Cargo.toml`). This can be overwritten /// by changing `#rust-analyzer.cargo.check.invocationStrategy#` and /// `#rust-analyzer.cargo.check.invocationLocation#`. From 29392c091b8c22a42ff5e8074d164c28e1122113 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sat, 26 Aug 2023 12:00:05 +0000 Subject: [PATCH 0045/1762] Commit Cargo.lock As of rust-lang/cargo#8728 it is now recommended to always check in Cargo.lock. This will help with reproducability and will avoid the need for cg_clif's build system to keep it's own copy of Cargo.lock for vendoring. It will also allow tidy to run on the portable-simd workspace. --- .gitignore | 1 - Cargo.lock | 304 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 304 insertions(+), 1 deletion(-) create mode 100644 Cargo.lock diff --git a/.gitignore b/.gitignore index 96ef6c0b944e2..ea8c4bf7f35f6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ /target -Cargo.lock diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000000000..46312c09657d5 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,304 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "core_simd" +version = "0.1.0" +dependencies = [ + "proptest", + "std_float", + "test_helpers", + "wasm-bindgen", + "wasm-bindgen-test", +] + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12e6c80c1139113c28ee4670dc50cc42915228b51f56a9e407f0ec60f966646f" +dependencies = [ + "bitflags", + "byteorder", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_xorshift" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77d416b86801d23dde1aa643023b775c3a462efc0ed96443add11546cdf1dca8" +dependencies = [ + "rand_core", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "std_float" +version = "0.1.0" +dependencies = [ + "core_simd", +] + +[[package]] +name = "syn" +version = "2.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "test_helpers" +version = "0.1.0" +dependencies = [ + "proptest", +] + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e6e302a7ea94f83a6d09e78e7dc7d9ca7b186bc2829c24a22d0753efd680671" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecb993dd8c836930ed130e020e77d9b2e65dd0fbab1b67c790b0f5d80b11a575" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] From d08b2a50a3a06edd6c2f412a4ffbeaec53507d2f Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sat, 26 Aug 2023 12:32:10 +0000 Subject: [PATCH 0046/1762] Allow internal_features lint --- crates/core_simd/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/core_simd/src/lib.rs b/crates/core_simd/src/lib.rs index fde406bda7060..2d68e4cce8542 100644 --- a/crates/core_simd/src/lib.rs +++ b/crates/core_simd/src/lib.rs @@ -18,6 +18,7 @@ #![cfg_attr(feature = "generic_const_exprs", allow(incomplete_features))] #![warn(missing_docs, clippy::missing_inline_in_public_items)] // basically all items, really #![deny(unsafe_op_in_unsafe_fn, clippy::undocumented_unsafe_blocks)] +#![allow(internal_features)] #![unstable(feature = "portable_simd", issue = "86656")] //! Portable SIMD module. From 11a64a18342865b166d2da703a06deb447f834c1 Mon Sep 17 00:00:00 2001 From: Zachary S Date: Fri, 1 Sep 2023 20:20:29 -0500 Subject: [PATCH 0047/1762] don't panic in BorrowedCursor::write --- library/std/src/io/readbuf.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/library/std/src/io/readbuf.rs b/library/std/src/io/readbuf.rs index 034ddd8df9aed..95044de2a09e1 100644 --- a/library/std/src/io/readbuf.rs +++ b/library/std/src/io/readbuf.rs @@ -306,8 +306,9 @@ impl<'a> BorrowedCursor<'a> { impl<'a> Write for BorrowedCursor<'a> { fn write(&mut self, buf: &[u8]) -> Result { - self.append(buf); - Ok(buf.len()) + let amt = cmp::min(buf.len(), self.capacity()); + self.append(&buf[..amt]); + Ok(amt) } #[inline] From 3ca6bb0b44c3e65dab07e12aec9efb277dc206f9 Mon Sep 17 00:00:00 2001 From: The 8472 Date: Sat, 15 Apr 2023 12:59:16 +0200 Subject: [PATCH 0048/1762] Expand in-place iteration specialization to Flatten, FlatMap and ArrayChunks --- .../alloc/src/collections/binary_heap/mod.rs | 11 ++- library/alloc/src/lib.rs | 1 + library/alloc/src/vec/in_place_collect.rs | 83 +++++++++++++++---- library/alloc/src/vec/into_iter.rs | 12 ++- library/alloc/tests/lib.rs | 1 + library/alloc/tests/vec.rs | 34 +++++++- .../core/src/iter/adapters/array_chunks.rs | 34 +++++++- library/core/src/iter/adapters/enumerate.rs | 10 ++- library/core/src/iter/adapters/filter.rs | 11 ++- library/core/src/iter/adapters/filter_map.rs | 12 ++- library/core/src/iter/adapters/flatten.rs | 81 +++++++++++++++++- library/core/src/iter/adapters/fuse.rs | 25 +++++- library/core/src/iter/adapters/inspect.rs | 11 ++- library/core/src/iter/adapters/map.rs | 11 ++- library/core/src/iter/adapters/map_while.rs | 7 +- library/core/src/iter/adapters/mod.rs | 11 ++- library/core/src/iter/adapters/scan.rs | 7 +- library/core/src/iter/adapters/skip.rs | 9 +- library/core/src/iter/adapters/skip_while.rs | 12 ++- library/core/src/iter/adapters/take.rs | 11 ++- library/core/src/iter/adapters/take_while.rs | 12 ++- library/core/src/iter/adapters/zip.rs | 16 +++- library/core/src/iter/mod.rs | 2 + library/core/src/iter/traits/marker.rs | 28 ++++++- library/core/src/iter/traits/mod.rs | 2 + 25 files changed, 391 insertions(+), 63 deletions(-) diff --git a/library/alloc/src/collections/binary_heap/mod.rs b/library/alloc/src/collections/binary_heap/mod.rs index 66573b90db963..ad2c4e483a3bf 100644 --- a/library/alloc/src/collections/binary_heap/mod.rs +++ b/library/alloc/src/collections/binary_heap/mod.rs @@ -145,7 +145,7 @@ use core::alloc::Allocator; use core::fmt; -use core::iter::{FusedIterator, InPlaceIterable, SourceIter, TrustedLen}; +use core::iter::{FusedIterator, InPlaceIterable, SourceIter, TrustedFused, TrustedLen}; use core::mem::{self, swap, ManuallyDrop}; use core::num::NonZeroUsize; use core::ops::{Deref, DerefMut}; @@ -1540,6 +1540,10 @@ impl ExactSizeIterator for IntoIter { #[stable(feature = "fused", since = "1.26.0")] impl FusedIterator for IntoIter {} +#[doc(hidden)] +#[unstable(issue = "none", feature = "trusted_fused")] +unsafe impl TrustedFused for IntoIter {} + #[stable(feature = "default_iters", since = "1.70.0")] impl Default for IntoIter { /// Creates an empty `binary_heap::IntoIter`. @@ -1569,7 +1573,10 @@ unsafe impl SourceIter for IntoIter { #[unstable(issue = "none", feature = "inplace_iteration")] #[doc(hidden)] -unsafe impl InPlaceIterable for IntoIter {} +unsafe impl InPlaceIterable for IntoIter { + const EXPAND_BY: Option = NonZeroUsize::new(1); + const MERGE_BY: Option = NonZeroUsize::new(1); +} unsafe impl AsVecIntoIter for IntoIter { type Item = I; diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index e43b6ac40391d..c9fee435f1e5d 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -155,6 +155,7 @@ #![feature(std_internals)] #![feature(str_internals)] #![feature(strict_provenance)] +#![feature(trusted_fused)] #![feature(trusted_len)] #![feature(trusted_random_access)] #![feature(try_trait_v2)] diff --git a/library/alloc/src/vec/in_place_collect.rs b/library/alloc/src/vec/in_place_collect.rs index 5ecd0479971ea..d174074902ab7 100644 --- a/library/alloc/src/vec/in_place_collect.rs +++ b/library/alloc/src/vec/in_place_collect.rs @@ -137,44 +137,73 @@ //! } //! vec.truncate(write_idx); //! ``` +use crate::alloc::{handle_alloc_error, Global}; +use core::alloc::Allocator; +use core::alloc::Layout; use core::iter::{InPlaceIterable, SourceIter, TrustedRandomAccessNoCoerce}; use core::mem::{self, ManuallyDrop, SizedTypeProperties}; -use core::ptr::{self}; +use core::num::NonZeroUsize; +use core::ptr::{self, NonNull}; use super::{InPlaceDrop, InPlaceDstBufDrop, SpecFromIter, SpecFromIterNested, Vec}; -/// Specialization marker for collecting an iterator pipeline into a Vec while reusing the -/// source allocation, i.e. executing the pipeline in place. -#[rustc_unsafe_specialization_marker] -pub(super) trait InPlaceIterableMarker {} +const fn in_place_collectible( + step_merge: Option, + step_expand: Option, +) -> bool { + if DEST::IS_ZST || mem::align_of::() != mem::align_of::() { + return false; + } + + match (step_merge, step_expand) { + (Some(step_merge), Some(step_expand)) => { + // At least N merged source items -> at most M expanded destination items + // e.g. + // - 1 x [u8; 4] -> 4x u8, via flatten + // - 4 x u8 -> 1x [u8; 4], via array_chunks + mem::size_of::() * step_merge.get() == mem::size_of::() * step_expand.get() + } + // Fall back to other from_iter impls if an overflow occured in the step merge/expansion + // tracking. + _ => false, + } +} -impl InPlaceIterableMarker for T where T: InPlaceIterable {} +/// This provides a shorthand for the source type since local type aliases aren't a thing. +#[rustc_specialization_trait] +trait InPlaceCollect: SourceIter + InPlaceIterable { + type Src; +} + +impl InPlaceCollect for T +where + T: SourceIter + InPlaceIterable, +{ + type Src = <::Source as AsVecIntoIter>::Item; +} impl SpecFromIter for Vec where - I: Iterator + SourceIter + InPlaceIterableMarker, + I: Iterator + InPlaceCollect, + ::Source: AsVecIntoIter, { default fn from_iter(mut iterator: I) -> Self { // See "Layout constraints" section in the module documentation. We rely on const // optimization here since these conditions currently cannot be expressed as trait bounds - if T::IS_ZST - || mem::size_of::() - != mem::size_of::<<::Source as AsVecIntoIter>::Item>() - || mem::align_of::() - != mem::align_of::<<::Source as AsVecIntoIter>::Item>() - { + if const { !in_place_collectible::(I::MERGE_BY, I::EXPAND_BY) } { // fallback to more generic implementations return SpecFromIterNested::from_iter(iterator); } - let (src_buf, src_ptr, dst_buf, dst_end, cap) = unsafe { + let (src_buf, src_ptr, src_cap, mut dst_buf, dst_end, dst_cap) = unsafe { let inner = iterator.as_inner().as_into_iter(); ( inner.buf.as_ptr(), inner.ptr, + inner.cap, inner.buf.as_ptr() as *mut T, inner.end as *const T, - inner.cap, + inner.cap * mem::size_of::() / mem::size_of::(), ) }; @@ -203,11 +232,31 @@ where // Note: This access to the source wouldn't be allowed by the TrustedRandomIteratorNoCoerce // contract (used by SpecInPlaceCollect below). But see the "O(1) collect" section in the // module documentation why this is ok anyway. - let dst_guard = InPlaceDstBufDrop { ptr: dst_buf, len, cap }; + let dst_guard = InPlaceDstBufDrop { ptr: dst_buf, len, cap: dst_cap }; src.forget_allocation_drop_remaining(); mem::forget(dst_guard); - let vec = unsafe { Vec::from_raw_parts(dst_buf, len, cap) }; + // Adjust the allocation size if the source had a capacity in bytes that wasn't a multiple + // of the destination type size. + // Since the discrepancy should generally be small this should only result in some + // bookkeeping updates and no memmove. + if const { mem::size_of::() > mem::size_of::() } + && src_cap * mem::size_of::() != dst_cap * mem::size_of::() + { + let alloc = Global; + unsafe { + let new_layout = Layout::array::(dst_cap).unwrap(); + let result = alloc.shrink( + NonNull::new_unchecked(dst_buf as *mut u8), + Layout::array::(src_cap).unwrap(), + new_layout, + ); + let Ok(reallocated) = result else { handle_alloc_error(new_layout) }; + dst_buf = reallocated.as_ptr() as *mut T; + } + } + + let vec = unsafe { Vec::from_raw_parts(dst_buf, len, dst_cap) }; vec } diff --git a/library/alloc/src/vec/into_iter.rs b/library/alloc/src/vec/into_iter.rs index b2db2fdfd18f1..40b93b34e18bb 100644 --- a/library/alloc/src/vec/into_iter.rs +++ b/library/alloc/src/vec/into_iter.rs @@ -7,7 +7,8 @@ use crate::raw_vec::RawVec; use core::array; use core::fmt; use core::iter::{ - FusedIterator, InPlaceIterable, SourceIter, TrustedLen, TrustedRandomAccessNoCoerce, + FusedIterator, InPlaceIterable, SourceIter, TrustedFused, TrustedLen, + TrustedRandomAccessNoCoerce, }; use core::marker::PhantomData; use core::mem::{self, ManuallyDrop, MaybeUninit, SizedTypeProperties}; @@ -339,6 +340,10 @@ impl ExactSizeIterator for IntoIter { #[stable(feature = "fused", since = "1.26.0")] impl FusedIterator for IntoIter {} +#[doc(hidden)] +#[unstable(issue = "none", feature = "trusted_fused")] +unsafe impl TrustedFused for IntoIter {} + #[unstable(feature = "trusted_len", issue = "37572")] unsafe impl TrustedLen for IntoIter {} @@ -423,7 +428,10 @@ unsafe impl<#[may_dangle] T, A: Allocator> Drop for IntoIter { // also refer to the vec::in_place_collect module documentation to get an overview #[unstable(issue = "none", feature = "inplace_iteration")] #[doc(hidden)] -unsafe impl InPlaceIterable for IntoIter {} +unsafe impl InPlaceIterable for IntoIter { + const EXPAND_BY: Option = NonZeroUsize::new(1); + const MERGE_BY: Option = NonZeroUsize::new(1); +} #[unstable(issue = "none", feature = "inplace_iteration")] #[doc(hidden)] diff --git a/library/alloc/tests/lib.rs b/library/alloc/tests/lib.rs index aa7a331b368be..5bb531f0e6272 100644 --- a/library/alloc/tests/lib.rs +++ b/library/alloc/tests/lib.rs @@ -1,5 +1,6 @@ #![feature(allocator_api)] #![feature(alloc_layout_extra)] +#![feature(iter_array_chunks)] #![feature(assert_matches)] #![feature(btree_extract_if)] #![feature(cow_is_borrowed)] diff --git a/library/alloc/tests/vec.rs b/library/alloc/tests/vec.rs index 9cb27899f1091..33dd2658e1e54 100644 --- a/library/alloc/tests/vec.rs +++ b/library/alloc/tests/vec.rs @@ -1,6 +1,7 @@ +use alloc::vec::Vec; use core::alloc::{Allocator, Layout}; -use core::assert_eq; -use core::iter::IntoIterator; +use core::{assert_eq, assert_ne}; +use core::iter::{IntoIterator, Iterator}; use core::num::NonZeroUsize; use core::ptr::NonNull; use std::alloc::System; @@ -1184,6 +1185,35 @@ fn test_from_iter_specialization_with_iterator_adapters() { assert_eq!(srcptr, sinkptr as *const usize); } +#[test] +fn test_in_place_specialization_step_up_down() { + fn assert_in_place_trait(_: &T) {} + let src = vec![[0u8; 4]; 256]; + let srcptr = src.as_ptr(); + let src_cap = src.capacity(); + let iter = src.into_iter().flatten(); + assert_in_place_trait(&iter); + let sink = iter.collect::>(); + let sinkptr = sink.as_ptr(); + assert_eq!(srcptr as *const u8, sinkptr); + assert_eq!(src_cap * 4, sink.capacity()); + + let iter = sink.into_iter().array_chunks::<4>(); + assert_in_place_trait(&iter); + let sink = iter.collect::>(); + let sinkptr = sink.as_ptr(); + assert_eq!(srcptr, sinkptr); + assert_eq!(src_cap, sink.capacity()); + + let mut src: Vec = Vec::with_capacity(17); + let src_bytes = src.capacity(); + src.resize(8, 0u8); + let sink: Vec<[u8; 4]> = src.into_iter().array_chunks::<4>().collect(); + let sink_bytes = sink.capacity() * 4; + assert_ne!(src_bytes, sink_bytes); + assert_eq!(sink.len(), 2); +} + #[test] fn test_from_iter_specialization_head_tail_drop() { let drop_count: Vec<_> = (0..=2).map(|_| Rc::new(())).collect(); diff --git a/library/core/src/iter/adapters/array_chunks.rs b/library/core/src/iter/adapters/array_chunks.rs index 13719c727e93f..319af4408d76d 100644 --- a/library/core/src/iter/adapters/array_chunks.rs +++ b/library/core/src/iter/adapters/array_chunks.rs @@ -1,5 +1,9 @@ use crate::array; -use crate::iter::{ByRefSized, FusedIterator, Iterator, TrustedRandomAccessNoCoerce}; +use crate::iter::adapters::SourceIter; +use crate::iter::{ + ByRefSized, FusedIterator, InPlaceIterable, Iterator, TrustedFused, TrustedRandomAccessNoCoerce, +}; +use crate::num::NonZeroUsize; use crate::ops::{ControlFlow, NeverShortCircuit, Try}; /// An iterator over `N` elements of the iterator at a time. @@ -159,6 +163,9 @@ where #[unstable(feature = "iter_array_chunks", reason = "recently added", issue = "100450")] impl FusedIterator for ArrayChunks where I: FusedIterator {} +#[unstable(issue = "none", feature = "trusted_fused")] +unsafe impl TrustedFused for ArrayChunks where I: TrustedFused + Iterator {} + #[unstable(feature = "iter_array_chunks", reason = "recently added", issue = "100450")] impl ExactSizeIterator for ArrayChunks where @@ -229,3 +236,28 @@ where accum } } + +#[unstable(issue = "none", feature = "inplace_iteration")] +unsafe impl SourceIter for ArrayChunks +where + I: SourceIter + Iterator, +{ + type Source = I::Source; + + #[inline] + unsafe fn as_inner(&mut self) -> &mut I::Source { + // SAFETY: unsafe function forwarding to unsafe function with the same requirements + unsafe { SourceIter::as_inner(&mut self.iter) } + } +} + +#[unstable(issue = "none", feature = "inplace_iteration")] +unsafe impl InPlaceIterable for ArrayChunks { + const EXPAND_BY: Option = I::EXPAND_BY; + const MERGE_BY: Option = const { + match (I::MERGE_BY, NonZeroUsize::new(N)) { + (Some(m), Some(n)) => m.checked_mul(n), + _ => None, + } + }; +} diff --git a/library/core/src/iter/adapters/enumerate.rs b/library/core/src/iter/adapters/enumerate.rs index 00c1c377bf9c2..92f465ccdb4e8 100644 --- a/library/core/src/iter/adapters/enumerate.rs +++ b/library/core/src/iter/adapters/enumerate.rs @@ -1,7 +1,7 @@ use crate::iter::adapters::{ zip::try_get_unchecked, SourceIter, TrustedRandomAccess, TrustedRandomAccessNoCoerce, }; -use crate::iter::{FusedIterator, InPlaceIterable, TrustedLen}; +use crate::iter::{FusedIterator, InPlaceIterable, TrustedFused, TrustedLen}; use crate::num::NonZeroUsize; use crate::ops::Try; @@ -243,6 +243,9 @@ where #[stable(feature = "fused", since = "1.26.0")] impl FusedIterator for Enumerate where I: FusedIterator {} +#[unstable(issue = "none", feature = "trusted_fused")] +unsafe impl TrustedFused for Enumerate {} + #[unstable(feature = "trusted_len", issue = "37572")] unsafe impl TrustedLen for Enumerate where I: TrustedLen {} @@ -261,7 +264,10 @@ where } #[unstable(issue = "none", feature = "inplace_iteration")] -unsafe impl InPlaceIterable for Enumerate {} +unsafe impl InPlaceIterable for Enumerate { + const EXPAND_BY: Option = I::EXPAND_BY; + const MERGE_BY: Option = I::MERGE_BY; +} #[stable(feature = "default_iters", since = "1.70.0")] impl Default for Enumerate { diff --git a/library/core/src/iter/adapters/filter.rs b/library/core/src/iter/adapters/filter.rs index 723657b9e43e4..882f3e3bc60f5 100644 --- a/library/core/src/iter/adapters/filter.rs +++ b/library/core/src/iter/adapters/filter.rs @@ -1,5 +1,6 @@ use crate::fmt; -use crate::iter::{adapters::SourceIter, FusedIterator, InPlaceIterable}; +use crate::iter::{adapters::SourceIter, FusedIterator, InPlaceIterable, TrustedFused}; +use crate::num::NonZeroUsize; use crate::ops::Try; use core::array; use core::mem::{ManuallyDrop, MaybeUninit}; @@ -189,6 +190,9 @@ where #[stable(feature = "fused", since = "1.26.0")] impl FusedIterator for Filter where P: FnMut(&I::Item) -> bool {} +#[unstable(issue = "none", feature = "trusted_fused")] +unsafe impl TrustedFused for Filter {} + #[unstable(issue = "none", feature = "inplace_iteration")] unsafe impl SourceIter for Filter where @@ -204,4 +208,7 @@ where } #[unstable(issue = "none", feature = "inplace_iteration")] -unsafe impl InPlaceIterable for Filter where P: FnMut(&I::Item) -> bool {} +unsafe impl InPlaceIterable for Filter { + const EXPAND_BY: Option = I::EXPAND_BY; + const MERGE_BY: Option = I::MERGE_BY; +} diff --git a/library/core/src/iter/adapters/filter_map.rs b/library/core/src/iter/adapters/filter_map.rs index 693479977db51..c0bdc065b05dd 100644 --- a/library/core/src/iter/adapters/filter_map.rs +++ b/library/core/src/iter/adapters/filter_map.rs @@ -1,5 +1,6 @@ -use crate::iter::{adapters::SourceIter, FusedIterator, InPlaceIterable}; +use crate::iter::{adapters::SourceIter, FusedIterator, InPlaceIterable, TrustedFused}; use crate::mem::{ManuallyDrop, MaybeUninit}; +use crate::num::NonZeroUsize; use crate::ops::{ControlFlow, Try}; use crate::{array, fmt}; @@ -188,6 +189,9 @@ where #[stable(feature = "fused", since = "1.26.0")] impl FusedIterator for FilterMap where F: FnMut(I::Item) -> Option {} +#[unstable(issue = "none", feature = "trusted_fused")] +unsafe impl TrustedFused for FilterMap {} + #[unstable(issue = "none", feature = "inplace_iteration")] unsafe impl SourceIter for FilterMap where @@ -203,7 +207,7 @@ where } #[unstable(issue = "none", feature = "inplace_iteration")] -unsafe impl InPlaceIterable for FilterMap where - F: FnMut(I::Item) -> Option -{ +unsafe impl InPlaceIterable for FilterMap { + const EXPAND_BY: Option = I::EXPAND_BY; + const MERGE_BY: Option = I::MERGE_BY; } diff --git a/library/core/src/iter/adapters/flatten.rs b/library/core/src/iter/adapters/flatten.rs index eee6e5bccecc7..60d5418716b91 100644 --- a/library/core/src/iter/adapters/flatten.rs +++ b/library/core/src/iter/adapters/flatten.rs @@ -1,7 +1,12 @@ -use crate::fmt; -use crate::iter::{DoubleEndedIterator, Fuse, FusedIterator, Iterator, Map, TrustedLen}; +use crate::iter::adapters::SourceIter; +use crate::iter::{ + DoubleEndedIterator, Fuse, FusedIterator, InPlaceIterable, Iterator, Map, TrustedFused, + TrustedLen, +}; use crate::num::NonZeroUsize; use crate::ops::{ControlFlow, Try}; +use crate::{fmt, option}; +use core::iter::Once; /// An iterator that maps each element to an iterator, and yields the elements /// of the produced iterators. @@ -145,6 +150,48 @@ where { } +#[unstable(issue = "none", feature = "inplace_iteration")] +unsafe impl InPlaceIterable for FlatMap +where + I: InPlaceIterable, + U: KnownExpansionFactor + IntoIterator, +{ + const EXPAND_BY: Option = const { + match (I::EXPAND_BY, U::FACTOR) { + (Some(m), Some(n)) => m.checked_mul(n), + _ => None, + } + }; + const MERGE_BY: Option = I::MERGE_BY; +} + +#[unstable(issue = "none", feature = "inplace_iteration")] +unsafe impl SourceIter for FlatMap +where + I: SourceIter + TrustedFused, + U: IntoIterator, +{ + type Source = I::Source; + + #[inline] + unsafe fn as_inner(&mut self) -> &mut I::Source { + // SAFETY: unsafe function forwarding to unsafe function with the same requirements + unsafe { SourceIter::as_inner(&mut self.inner.iter) } + } +} + +#[rustc_specialization_trait] +trait KnownExpansionFactor { + const FACTOR: Option = NonZeroUsize::new(1); +} + +impl KnownExpansionFactor for Option {} +impl KnownExpansionFactor for option::IntoIter {} +impl KnownExpansionFactor for Once {} +impl KnownExpansionFactor for [T; N] { + const FACTOR: Option = NonZeroUsize::new(N); +} + /// An iterator that flattens one level of nesting in an iterator of things /// that can be turned into iterators. /// @@ -289,6 +336,36 @@ where { } +#[unstable(issue = "none", feature = "inplace_iteration")] +unsafe impl InPlaceIterable for Flatten +where + I: InPlaceIterable + Iterator, + ::Item: IntoIterator + KnownExpansionFactor, +{ + const EXPAND_BY: Option = const { + match (I::EXPAND_BY, I::Item::FACTOR) { + (Some(m), Some(n)) => m.checked_mul(n), + _ => None, + } + }; + const MERGE_BY: Option = I::MERGE_BY; +} + +#[unstable(issue = "none", feature = "inplace_iteration")] +unsafe impl SourceIter for Flatten +where + I: SourceIter + TrustedFused + Iterator, + ::Item: IntoIterator, +{ + type Source = I::Source; + + #[inline] + unsafe fn as_inner(&mut self) -> &mut I::Source { + // SAFETY: unsafe function forwarding to unsafe function with the same requirements + unsafe { SourceIter::as_inner(&mut self.inner.iter) } + } +} + #[stable(feature = "default_iters", since = "1.70.0")] impl Default for Flatten where diff --git a/library/core/src/iter/adapters/fuse.rs b/library/core/src/iter/adapters/fuse.rs index b1fa4f92117b1..3234cade50dc5 100644 --- a/library/core/src/iter/adapters/fuse.rs +++ b/library/core/src/iter/adapters/fuse.rs @@ -1,8 +1,9 @@ use crate::intrinsics; use crate::iter::adapters::zip::try_get_unchecked; +use crate::iter::adapters::SourceIter; use crate::iter::{ - DoubleEndedIterator, ExactSizeIterator, FusedIterator, TrustedLen, TrustedRandomAccess, - TrustedRandomAccessNoCoerce, + DoubleEndedIterator, ExactSizeIterator, FusedIterator, TrustedFused, TrustedLen, + TrustedRandomAccess, TrustedRandomAccessNoCoerce, }; use crate::ops::Try; @@ -29,6 +30,9 @@ impl Fuse { #[stable(feature = "fused", since = "1.26.0")] impl FusedIterator for Fuse where I: Iterator {} +#[unstable(issue = "none", feature = "trusted_fused")] +unsafe impl TrustedFused for Fuse where I: TrustedFused {} + // Any specialized implementation here is made internal // to avoid exposing default fns outside this trait. #[stable(feature = "rust1", since = "1.0.0")] @@ -418,6 +422,23 @@ where } } +// This is used by Flatten's SourceIter impl +#[unstable(issue = "none", feature = "inplace_iteration")] +unsafe impl SourceIter for Fuse +where + I: SourceIter + TrustedFused, +{ + type Source = I::Source; + + #[inline] + unsafe fn as_inner(&mut self) -> &mut I::Source { + // SAFETY: unsafe function forwarding to unsafe function with the same requirements. + // TrustedFused guarantees that we'll never encounter a case where `self.iter` would + // be set to None. + unsafe { SourceIter::as_inner(self.iter.as_mut().unwrap_unchecked()) } + } +} + #[inline] fn and_then_or_clear(opt: &mut Option, f: impl FnOnce(&mut T) -> Option) -> Option { let x = f(opt.as_mut()?); diff --git a/library/core/src/iter/adapters/inspect.rs b/library/core/src/iter/adapters/inspect.rs index 19839fdfe5bc3..fd2d830b693d1 100644 --- a/library/core/src/iter/adapters/inspect.rs +++ b/library/core/src/iter/adapters/inspect.rs @@ -1,5 +1,6 @@ use crate::fmt; -use crate::iter::{adapters::SourceIter, FusedIterator, InPlaceIterable}; +use crate::iter::{adapters::SourceIter, FusedIterator, InPlaceIterable, TrustedFused}; +use crate::num::NonZeroUsize; use crate::ops::Try; /// An iterator that calls a function with a reference to each element before @@ -148,6 +149,9 @@ where #[stable(feature = "fused", since = "1.26.0")] impl FusedIterator for Inspect where F: FnMut(&I::Item) {} +#[unstable(issue = "none", feature = "trusted_fused")] +unsafe impl TrustedFused for Inspect {} + #[unstable(issue = "none", feature = "inplace_iteration")] unsafe impl SourceIter for Inspect where @@ -163,4 +167,7 @@ where } #[unstable(issue = "none", feature = "inplace_iteration")] -unsafe impl InPlaceIterable for Inspect where F: FnMut(&I::Item) {} +unsafe impl InPlaceIterable for Inspect { + const EXPAND_BY: Option = I::EXPAND_BY; + const MERGE_BY: Option = I::MERGE_BY; +} diff --git a/library/core/src/iter/adapters/map.rs b/library/core/src/iter/adapters/map.rs index 31d02a4da6ea5..e27fc7257f6cc 100644 --- a/library/core/src/iter/adapters/map.rs +++ b/library/core/src/iter/adapters/map.rs @@ -2,7 +2,8 @@ use crate::fmt; use crate::iter::adapters::{ zip::try_get_unchecked, SourceIter, TrustedRandomAccess, TrustedRandomAccessNoCoerce, }; -use crate::iter::{FusedIterator, InPlaceIterable, TrustedLen, UncheckedIterator}; +use crate::iter::{FusedIterator, InPlaceIterable, TrustedFused, TrustedLen, UncheckedIterator}; +use crate::num::NonZeroUsize; use crate::ops::Try; /// An iterator that maps the values of `iter` with `f`. @@ -179,6 +180,9 @@ where #[stable(feature = "fused", since = "1.26.0")] impl FusedIterator for Map where F: FnMut(I::Item) -> B {} +#[unstable(issue = "none", feature = "trusted_fused")] +unsafe impl TrustedFused for Map {} + #[unstable(feature = "trusted_len", issue = "37572")] unsafe impl TrustedLen for Map where @@ -228,4 +232,7 @@ where } #[unstable(issue = "none", feature = "inplace_iteration")] -unsafe impl InPlaceIterable for Map where F: FnMut(I::Item) -> B {} +unsafe impl InPlaceIterable for Map { + const EXPAND_BY: Option = I::EXPAND_BY; + const MERGE_BY: Option = I::MERGE_BY; +} diff --git a/library/core/src/iter/adapters/map_while.rs b/library/core/src/iter/adapters/map_while.rs index fbdeca4d4ee4f..bcae73cbe09cf 100644 --- a/library/core/src/iter/adapters/map_while.rs +++ b/library/core/src/iter/adapters/map_while.rs @@ -1,5 +1,6 @@ use crate::fmt; use crate::iter::{adapters::SourceIter, InPlaceIterable}; +use crate::num::NonZeroUsize; use crate::ops::{ControlFlow, Try}; /// An iterator that only accepts elements while `predicate` returns `Some(_)`. @@ -82,7 +83,7 @@ where } #[unstable(issue = "none", feature = "inplace_iteration")] -unsafe impl InPlaceIterable for MapWhile where - P: FnMut(I::Item) -> Option -{ +unsafe impl InPlaceIterable for MapWhile { + const EXPAND_BY: Option = I::EXPAND_BY; + const MERGE_BY: Option = I::MERGE_BY; } diff --git a/library/core/src/iter/adapters/mod.rs b/library/core/src/iter/adapters/mod.rs index 6f4fa7010f446..a691039c789c6 100644 --- a/library/core/src/iter/adapters/mod.rs +++ b/library/core/src/iter/adapters/mod.rs @@ -1,4 +1,5 @@ use crate::iter::{InPlaceIterable, Iterator}; +use crate::num::NonZeroUsize; use crate::ops::{ChangeOutputType, ControlFlow, FromResidual, Residual, Try}; mod array_chunks; @@ -119,8 +120,9 @@ pub unsafe trait SourceIter { /// /// # Safety /// - /// Implementations of must return the same mutable reference for their lifetime, unless + /// Implementations must return the same mutable reference for their lifetime, unless /// replaced by a caller. + /// /// Callers may only replace the reference when they stopped iteration and drop the /// iterator pipeline after extracting the source. /// @@ -228,7 +230,10 @@ where // in order to return `Some(_)`. Since `iter` has type `I: InPlaceIterable` it's // guaranteed that at least one item will be moved out from the underlying source. #[unstable(issue = "none", feature = "inplace_iteration")] -unsafe impl InPlaceIterable for GenericShunt<'_, I, R> where - I: Iterator> + InPlaceIterable +unsafe impl InPlaceIterable for GenericShunt<'_, I, R> +where + I: InPlaceIterable, { + const EXPAND_BY: Option = I::EXPAND_BY; + const MERGE_BY: Option = I::MERGE_BY; } diff --git a/library/core/src/iter/adapters/scan.rs b/library/core/src/iter/adapters/scan.rs index 62470512cc747..635bad199ff95 100644 --- a/library/core/src/iter/adapters/scan.rs +++ b/library/core/src/iter/adapters/scan.rs @@ -1,5 +1,6 @@ use crate::fmt; use crate::iter::{adapters::SourceIter, InPlaceIterable}; +use crate::num::NonZeroUsize; use crate::ops::{ControlFlow, Try}; /// An iterator to maintain state while iterating another iterator. @@ -92,7 +93,7 @@ where } #[unstable(issue = "none", feature = "inplace_iteration")] -unsafe impl InPlaceIterable for Scan where - F: FnMut(&mut St, I::Item) -> Option -{ +unsafe impl InPlaceIterable for Scan { + const EXPAND_BY: Option = I::EXPAND_BY; + const MERGE_BY: Option = I::MERGE_BY; } diff --git a/library/core/src/iter/adapters/skip.rs b/library/core/src/iter/adapters/skip.rs index 306338bc7cca0..e6c946e7f880d 100644 --- a/library/core/src/iter/adapters/skip.rs +++ b/library/core/src/iter/adapters/skip.rs @@ -1,4 +1,5 @@ use crate::intrinsics::unlikely; +use crate::iter::TrustedFused; use crate::iter::{adapters::SourceIter, FusedIterator, InPlaceIterable}; use crate::num::NonZeroUsize; use crate::ops::{ControlFlow, Try}; @@ -214,6 +215,9 @@ where #[stable(feature = "fused", since = "1.26.0")] impl FusedIterator for Skip where I: FusedIterator {} +#[unstable(issue = "none", feature = "trusted_fused")] +unsafe impl TrustedFused for Skip {} + #[unstable(issue = "none", feature = "inplace_iteration")] unsafe impl SourceIter for Skip where @@ -229,4 +233,7 @@ where } #[unstable(issue = "none", feature = "inplace_iteration")] -unsafe impl InPlaceIterable for Skip {} +unsafe impl InPlaceIterable for Skip { + const EXPAND_BY: Option = I::EXPAND_BY; + const MERGE_BY: Option = I::MERGE_BY; +} diff --git a/library/core/src/iter/adapters/skip_while.rs b/library/core/src/iter/adapters/skip_while.rs index f29661779c056..3a661973e5fe0 100644 --- a/library/core/src/iter/adapters/skip_while.rs +++ b/library/core/src/iter/adapters/skip_while.rs @@ -1,5 +1,6 @@ use crate::fmt; -use crate::iter::{adapters::SourceIter, FusedIterator, InPlaceIterable}; +use crate::iter::{adapters::SourceIter, FusedIterator, InPlaceIterable, TrustedFused}; +use crate::num::NonZeroUsize; use crate::ops::Try; /// An iterator that rejects elements while `predicate` returns `true`. @@ -104,6 +105,9 @@ where { } +#[unstable(issue = "none", feature = "trusted_fused")] +unsafe impl TrustedFused for SkipWhile {} + #[unstable(issue = "none", feature = "inplace_iteration")] unsafe impl SourceIter for SkipWhile where @@ -119,7 +123,7 @@ where } #[unstable(issue = "none", feature = "inplace_iteration")] -unsafe impl InPlaceIterable for SkipWhile where - F: FnMut(&I::Item) -> bool -{ +unsafe impl InPlaceIterable for SkipWhile { + const EXPAND_BY: Option = I::EXPAND_BY; + const MERGE_BY: Option = I::MERGE_BY; } diff --git a/library/core/src/iter/adapters/take.rs b/library/core/src/iter/adapters/take.rs index c1d8cc4ff57bd..80e06066d28f9 100644 --- a/library/core/src/iter/adapters/take.rs +++ b/library/core/src/iter/adapters/take.rs @@ -1,6 +1,7 @@ use crate::cmp; use crate::iter::{ - adapters::SourceIter, FusedIterator, InPlaceIterable, TrustedLen, TrustedRandomAccess, + adapters::SourceIter, FusedIterator, InPlaceIterable, TrustedFused, TrustedLen, + TrustedRandomAccess, }; use crate::num::NonZeroUsize; use crate::ops::{ControlFlow, Try}; @@ -143,7 +144,10 @@ where } #[unstable(issue = "none", feature = "inplace_iteration")] -unsafe impl InPlaceIterable for Take {} +unsafe impl InPlaceIterable for Take { + const EXPAND_BY: Option = I::EXPAND_BY; + const MERGE_BY: Option = I::MERGE_BY; +} #[stable(feature = "double_ended_take_iterator", since = "1.38.0")] impl DoubleEndedIterator for Take @@ -241,6 +245,9 @@ impl ExactSizeIterator for Take where I: ExactSizeIterator {} #[stable(feature = "fused", since = "1.26.0")] impl FusedIterator for Take where I: FusedIterator {} +#[unstable(issue = "none", feature = "trusted_fused")] +unsafe impl TrustedFused for Take {} + #[unstable(feature = "trusted_len", issue = "37572")] unsafe impl TrustedLen for Take {} diff --git a/library/core/src/iter/adapters/take_while.rs b/library/core/src/iter/adapters/take_while.rs index ec66dc3aec360..e55d55a6d2377 100644 --- a/library/core/src/iter/adapters/take_while.rs +++ b/library/core/src/iter/adapters/take_while.rs @@ -1,5 +1,6 @@ use crate::fmt; -use crate::iter::{adapters::SourceIter, FusedIterator, InPlaceIterable}; +use crate::iter::{adapters::SourceIter, FusedIterator, InPlaceIterable, TrustedFused}; +use crate::num::NonZeroUsize; use crate::ops::{ControlFlow, Try}; /// An iterator that only accepts elements while `predicate` returns `true`. @@ -105,6 +106,9 @@ where { } +#[unstable(issue = "none", feature = "trusted_fused")] +unsafe impl TrustedFused for TakeWhile {} + #[unstable(issue = "none", feature = "inplace_iteration")] unsafe impl SourceIter for TakeWhile where @@ -120,7 +124,7 @@ where } #[unstable(issue = "none", feature = "inplace_iteration")] -unsafe impl InPlaceIterable for TakeWhile where - F: FnMut(&I::Item) -> bool -{ +unsafe impl InPlaceIterable for TakeWhile { + const EXPAND_BY: Option = I::EXPAND_BY; + const MERGE_BY: Option = I::MERGE_BY; } diff --git a/library/core/src/iter/adapters/zip.rs b/library/core/src/iter/adapters/zip.rs index b6b0c90cb7d14..5993c1e304589 100644 --- a/library/core/src/iter/adapters/zip.rs +++ b/library/core/src/iter/adapters/zip.rs @@ -1,7 +1,8 @@ use crate::cmp; use crate::fmt::{self, Debug}; -use crate::iter::{DoubleEndedIterator, ExactSizeIterator, FusedIterator, Iterator}; +use crate::iter::{DoubleEndedIterator, ExactSizeIterator, FusedIterator, Iterator, TrustedFused}; use crate::iter::{InPlaceIterable, SourceIter, TrustedLen, UncheckedIterator}; +use crate::num::NonZeroUsize; /// An iterator that iterates two other iterators simultaneously. /// @@ -409,6 +410,14 @@ where { } +#[unstable(issue = "none", feature = "trusted_fused")] +unsafe impl TrustedFused for Zip +where + A: TrustedFused, + B: TrustedFused, +{ +} + #[unstable(feature = "trusted_len", issue = "37572")] unsafe impl TrustedLen for Zip where @@ -442,7 +451,10 @@ where // Since SourceIter forwards the left hand side we do the same here #[unstable(issue = "none", feature = "inplace_iteration")] -unsafe impl InPlaceIterable for Zip {} +unsafe impl InPlaceIterable for Zip { + const EXPAND_BY: Option = A::EXPAND_BY; + const MERGE_BY: Option = A::MERGE_BY; +} #[stable(feature = "rust1", since = "1.0.0")] impl Debug for Zip { diff --git a/library/core/src/iter/mod.rs b/library/core/src/iter/mod.rs index ca977d1ef8240..a727a97ed502d 100644 --- a/library/core/src/iter/mod.rs +++ b/library/core/src/iter/mod.rs @@ -417,6 +417,8 @@ pub use self::sources::{successors, Successors}; pub use self::traits::FusedIterator; #[unstable(issue = "none", feature = "inplace_iteration")] pub use self::traits::InPlaceIterable; +#[unstable(issue = "none", feature = "trusted_fused")] +pub use self::traits::TrustedFused; #[unstable(feature = "trusted_len", issue = "37572")] pub use self::traits::TrustedLen; #[unstable(feature = "trusted_step", issue = "85731")] diff --git a/library/core/src/iter/traits/marker.rs b/library/core/src/iter/traits/marker.rs index c21a2aac1c9a6..e7c1f195aacc6 100644 --- a/library/core/src/iter/traits/marker.rs +++ b/library/core/src/iter/traits/marker.rs @@ -1,4 +1,16 @@ use crate::iter::Step; +use crate::num::NonZeroUsize; + +/// Same as FusedIterator +/// +/// # Safety +/// +/// This is used for specialization. Therefore implementations must not +/// be lifetime-dependent. +#[unstable(issue = "none", feature = "trusted_fused")] +#[doc(hidden)] +#[rustc_specialization_trait] +pub unsafe trait TrustedFused {} /// An iterator that always continues to yield `None` when exhausted. /// @@ -14,6 +26,8 @@ use crate::iter::Step; /// [`Fuse`]: crate::iter::Fuse #[stable(feature = "fused", since = "1.26.0")] #[rustc_unsafe_specialization_marker] +// FIXME: this should be a #[marker] and have another blanket impl for T: TrustedFused +// but that ICEs iter::Fuse specializations. pub trait FusedIterator: Iterator {} #[stable(feature = "fused", since = "1.26.0")] @@ -71,7 +85,19 @@ unsafe impl TrustedLen for &mut I {} /// [`try_fold()`]: Iterator::try_fold #[unstable(issue = "none", feature = "inplace_iteration")] #[doc(hidden)] -pub unsafe trait InPlaceIterable: Iterator {} +#[rustc_specialization_trait] +pub unsafe trait InPlaceIterable { + /// The product of one-to-many item expansions that happen throughout the iterator pipeline. + /// E.g. [[u8; 4]; 4].iter().flatten().flatten() would have a `EXPAND_BY` of 16. + /// This is an upper bound, i.e. the transformations will produce at most this many items per + /// input. It's meant for layout calculations. + const EXPAND_BY: Option; + /// The product of many-to-one item reductions that happen throughout the iterator pipeline. + /// E.g. [u8].iter().array_chunks::<4>().array_chunks::<4>() would have a `MERGE_BY` of 16. + /// This is a lower bound, i.e. the transformations will consume at least this many items per + /// output. + const MERGE_BY: Option; +} /// A type that upholds all invariants of [`Step`]. /// diff --git a/library/core/src/iter/traits/mod.rs b/library/core/src/iter/traits/mod.rs index 41ea29e6a84d9..d4c9cc4b16037 100644 --- a/library/core/src/iter/traits/mod.rs +++ b/library/core/src/iter/traits/mod.rs @@ -18,6 +18,8 @@ pub use self::{ #[unstable(issue = "none", feature = "inplace_iteration")] pub use self::marker::InPlaceIterable; +#[unstable(issue = "none", feature = "trusted_fused")] +pub use self::marker::TrustedFused; #[unstable(feature = "trusted_step", issue = "85731")] pub use self::marker::TrustedStep; From 6e4f98c987f196ed27a3eae9ebb019ec89759f6c Mon Sep 17 00:00:00 2001 From: The 8472 Date: Sat, 29 Apr 2023 12:34:28 +0200 Subject: [PATCH 0049/1762] don't leak items if alloc::shrink panics --- library/alloc/src/vec/in_place_collect.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/library/alloc/src/vec/in_place_collect.rs b/library/alloc/src/vec/in_place_collect.rs index d174074902ab7..20ecc50c4b67b 100644 --- a/library/alloc/src/vec/in_place_collect.rs +++ b/library/alloc/src/vec/in_place_collect.rs @@ -225,16 +225,18 @@ where } // The ownership of the allocation and the new `T` values is temporarily moved into `dst_guard`. - // This is safe because `forget_allocation_drop_remaining` immediately forgets the allocation + // This is safe because + // * `forget_allocation_drop_remaining` immediately forgets the allocation // before any panic can occur in order to avoid any double free, and then proceeds to drop // any remaining values at the tail of the source. + // * the shrink either panics without invalidating the allocation, aborts or + // succeeds. In the last case we disarm the guard. // // Note: This access to the source wouldn't be allowed by the TrustedRandomIteratorNoCoerce // contract (used by SpecInPlaceCollect below). But see the "O(1) collect" section in the // module documentation why this is ok anyway. let dst_guard = InPlaceDstBufDrop { ptr: dst_buf, len, cap: dst_cap }; src.forget_allocation_drop_remaining(); - mem::forget(dst_guard); // Adjust the allocation size if the source had a capacity in bytes that wasn't a multiple // of the destination type size. @@ -256,6 +258,8 @@ where } } + mem::forget(dst_guard); + let vec = unsafe { Vec::from_raw_parts(dst_buf, len, dst_cap) }; vec From 7047fb41b6567a43bb2239035c114084807845f6 Mon Sep 17 00:00:00 2001 From: The 8472 Date: Sat, 29 Apr 2023 12:34:52 +0200 Subject: [PATCH 0050/1762] update in-place-iteration module docs --- library/alloc/src/vec/in_place_collect.rs | 33 +++++++++++++++++------ 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/library/alloc/src/vec/in_place_collect.rs b/library/alloc/src/vec/in_place_collect.rs index 20ecc50c4b67b..90ba1b3a96fb9 100644 --- a/library/alloc/src/vec/in_place_collect.rs +++ b/library/alloc/src/vec/in_place_collect.rs @@ -6,11 +6,11 @@ //! The specialization in this module applies to iterators in the shape of //! `source.adapter().adapter().adapter().collect::>()` //! where `source` is an owning iterator obtained from [`Vec`], [`Box<[T]>`][box] (by conversion to `Vec`) -//! or [`BinaryHeap`], the adapters each consume one or more items per step -//! (represented by [`InPlaceIterable`]), provide transitive access to `source` (via [`SourceIter`]) -//! and thus the underlying allocation. And finally the layouts of `T` and `U` must -//! have the same size and alignment, this is currently ensured via const eval instead of trait bounds -//! in the specialized [`SpecFromIter`] implementation. +//! or [`BinaryHeap`], the adapters guarantee to consume enough items per step to make room +//! for the results (represented by [`InPlaceIterable`]), provide transitive access to `source` +//! (via [`SourceIter`]) and thus the underlying allocation. +//! And finally there are alignment and size constriants to consider, this is currently ensured via +//! const eval instead of trait bounds in the specialized [`SpecFromIter`] implementation. //! //! [`BinaryHeap`]: crate::collections::BinaryHeap //! [box]: crate::boxed::Box @@ -35,11 +35,28 @@ //! the step of reading a value and getting a reference to write to. Instead raw pointers must be //! used on the reader and writer side. //! -//! That writes never clobber a yet-to-be-read item is ensured by the [`InPlaceIterable`] requirements. +//! That writes never clobber a yet-to-be-read items is ensured by the [`InPlaceIterable`] requirements. //! //! # Layout constraints //! -//! [`Allocator`] requires that `allocate()` and `deallocate()` have matching alignment and size. +//! When recycling an allocation between different types we must uphold the [`Allocator`] contract +//! which means that the input and output Layouts have to "fit". +//! +//! To complicate things further `InPlaceIterable` supports splitting or merging items into smaller/ +//! larger ones to enable (de)aggregation of arrays. +//! +//! Ultimately each step of the iterator must free up enough *bytes* in the source to make room +//! for the next output item. +//! If `T` and `U` have the same size no fixup is needed. +//! If `T`'s size is a multiple of `U`'s we can compensate by multiplying the capacity accordingly. +//! Otherwise the input capacity (and thus layout) in bytes may not be representable by the output +//! `Vec`. In that case `alloc.shrink()` is used to update the allocation's layout. +//! +//! Currently alignments of `T` and `U` must be the same. In principle smaller output alignments +//! could be supported but that would require always calling `alloc.shrink` for those transformations. +//! +//! See `in_place_collectible()` for the current conditions. +//! //! Additionally this specialization doesn't make sense for ZSTs as there is no reallocation to //! avoid and it would make pointer arithmetic more difficult. //! @@ -163,7 +180,7 @@ const fn in_place_collectible( // - 4 x u8 -> 1x [u8; 4], via array_chunks mem::size_of::() * step_merge.get() == mem::size_of::() * step_expand.get() } - // Fall back to other from_iter impls if an overflow occured in the step merge/expansion + // Fall back to other from_iter impls if an overflow occurred in the step merge/expansion // tracking. _ => false, } From 439f63019fccbbdcc00074856f67ce6af2abcac2 Mon Sep 17 00:00:00 2001 From: The 8472 Date: Sat, 29 Apr 2023 15:53:26 +0200 Subject: [PATCH 0051/1762] support in-place collecting additional FlatMap shapes --- library/alloc/tests/vec.rs | 11 ++++ library/core/src/iter/adapters/flatten.rs | 74 ++++++++++++++++++----- 2 files changed, 70 insertions(+), 15 deletions(-) diff --git a/library/alloc/tests/vec.rs b/library/alloc/tests/vec.rs index 33dd2658e1e54..c545f47a5d15f 100644 --- a/library/alloc/tests/vec.rs +++ b/library/alloc/tests/vec.rs @@ -1212,6 +1212,17 @@ fn test_in_place_specialization_step_up_down() { let sink_bytes = sink.capacity() * 4; assert_ne!(src_bytes, sink_bytes); assert_eq!(sink.len(), 2); + + let src = vec![[0u8; 4]; 256]; + let srcptr = src.as_ptr(); + let iter = src + .into_iter() + .flat_map(|a| { + a.into_iter().map(|b| b.wrapping_add(1)) + }); + assert_in_place_trait(&iter); + let sink = iter.collect::>(); + assert_eq!(srcptr as *const u8, sink.as_ptr()); } #[test] diff --git a/library/core/src/iter/adapters/flatten.rs b/library/core/src/iter/adapters/flatten.rs index 60d5418716b91..09428350fd92a 100644 --- a/library/core/src/iter/adapters/flatten.rs +++ b/library/core/src/iter/adapters/flatten.rs @@ -1,12 +1,13 @@ use crate::iter::adapters::SourceIter; use crate::iter::{ - DoubleEndedIterator, Fuse, FusedIterator, InPlaceIterable, Iterator, Map, TrustedFused, - TrustedLen, + Cloned, Copied, DoubleEndedIterator, Filter, FilterMap, Fuse, FusedIterator, InPlaceIterable, + Iterator, Map, TrustedFused, TrustedLen, }; +use crate::iter::{Once, OnceWith}; use crate::num::NonZeroUsize; use crate::ops::{ControlFlow, Try}; -use crate::{fmt, option}; -use core::iter::Once; +use crate::result; +use crate::{array, fmt, option}; /// An iterator that maps each element to an iterator, and yields the elements /// of the produced iterators. @@ -154,10 +155,10 @@ where unsafe impl InPlaceIterable for FlatMap where I: InPlaceIterable, - U: KnownExpansionFactor + IntoIterator, + U: BoundedSize + IntoIterator, { const EXPAND_BY: Option = const { - match (I::EXPAND_BY, U::FACTOR) { + match (I::EXPAND_BY, U::UPPER_BOUND) { (Some(m), Some(n)) => m.checked_mul(n), _ => None, } @@ -180,16 +181,59 @@ where } } +/// Marker trait for iterators/iterables which have a statically known upper +/// bound of the number of items they can produce. +/// +/// # Safety +/// +/// Implementations must not yield more elements than indicated by UPPER_BOUND if it is `Some`. +/// Used in specializations. Implementations must not be conditional on lifetimes or +/// user-implementable traits. #[rustc_specialization_trait] -trait KnownExpansionFactor { - const FACTOR: Option = NonZeroUsize::new(1); +#[unstable(issue = "none", feature = "inplace_iteration")] +unsafe trait BoundedSize { + const UPPER_BOUND: Option = NonZeroUsize::new(1); } -impl KnownExpansionFactor for Option {} -impl KnownExpansionFactor for option::IntoIter {} -impl KnownExpansionFactor for Once {} -impl KnownExpansionFactor for [T; N] { - const FACTOR: Option = NonZeroUsize::new(N); +#[unstable(issue = "none", feature = "inplace_iteration")] +unsafe impl BoundedSize for Option {} +#[unstable(issue = "none", feature = "inplace_iteration")] +unsafe impl BoundedSize for option::IntoIter {} +#[unstable(issue = "none", feature = "inplace_iteration")] +unsafe impl BoundedSize for Result {} +#[unstable(issue = "none", feature = "inplace_iteration")] +unsafe impl BoundedSize for result::IntoIter {} +#[unstable(issue = "none", feature = "inplace_iteration")] +unsafe impl BoundedSize for Once {} +#[unstable(issue = "none", feature = "inplace_iteration")] +unsafe impl BoundedSize for OnceWith {} +#[unstable(issue = "none", feature = "inplace_iteration")] +unsafe impl BoundedSize for [T; N] { + const UPPER_BOUND: Option = NonZeroUsize::new(N); +} +#[unstable(issue = "none", feature = "inplace_iteration")] +unsafe impl BoundedSize for array::IntoIter { + const UPPER_BOUND: Option = NonZeroUsize::new(N); +} +#[unstable(issue = "none", feature = "inplace_iteration")] +unsafe impl BoundedSize for Filter { + const UPPER_BOUND: Option = I::UPPER_BOUND; +} +#[unstable(issue = "none", feature = "inplace_iteration")] +unsafe impl BoundedSize for FilterMap { + const UPPER_BOUND: Option = I::UPPER_BOUND; +} +#[unstable(issue = "none", feature = "inplace_iteration")] +unsafe impl BoundedSize for Map { + const UPPER_BOUND: Option = I::UPPER_BOUND; +} +#[unstable(issue = "none", feature = "inplace_iteration")] +unsafe impl BoundedSize for Copied { + const UPPER_BOUND: Option = I::UPPER_BOUND; +} +#[unstable(issue = "none", feature = "inplace_iteration")] +unsafe impl BoundedSize for Cloned { + const UPPER_BOUND: Option = I::UPPER_BOUND; } /// An iterator that flattens one level of nesting in an iterator of things @@ -340,10 +384,10 @@ where unsafe impl InPlaceIterable for Flatten where I: InPlaceIterable + Iterator, - ::Item: IntoIterator + KnownExpansionFactor, + ::Item: IntoIterator + BoundedSize, { const EXPAND_BY: Option = const { - match (I::EXPAND_BY, I::Item::FACTOR) { + match (I::EXPAND_BY, I::Item::UPPER_BOUND) { (Some(m), Some(n)) => m.checked_mul(n), _ => None, } From c98070d522d8d12e61219f268ac377aceda30e92 Mon Sep 17 00:00:00 2001 From: The 8472 Date: Sat, 29 Apr 2023 18:31:06 +0200 Subject: [PATCH 0052/1762] relax size and alignment requirements for in-place iteration --- library/alloc/src/vec/in_place_collect.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/library/alloc/src/vec/in_place_collect.rs b/library/alloc/src/vec/in_place_collect.rs index 90ba1b3a96fb9..1f03f0d19ec17 100644 --- a/library/alloc/src/vec/in_place_collect.rs +++ b/library/alloc/src/vec/in_place_collect.rs @@ -52,8 +52,8 @@ //! Otherwise the input capacity (and thus layout) in bytes may not be representable by the output //! `Vec`. In that case `alloc.shrink()` is used to update the allocation's layout. //! -//! Currently alignments of `T` and `U` must be the same. In principle smaller output alignments -//! could be supported but that would require always calling `alloc.shrink` for those transformations. +//! Alignments of `T` must be the same or larger than `U`. Since alignments are always a power +//! of two _larger_ implies _is a multiple of_. //! //! See `in_place_collectible()` for the current conditions. //! @@ -168,7 +168,7 @@ const fn in_place_collectible( step_merge: Option, step_expand: Option, ) -> bool { - if DEST::IS_ZST || mem::align_of::() != mem::align_of::() { + if DEST::IS_ZST || mem::align_of::() < mem::align_of::() { return false; } @@ -178,7 +178,7 @@ const fn in_place_collectible( // e.g. // - 1 x [u8; 4] -> 4x u8, via flatten // - 4 x u8 -> 1x [u8; 4], via array_chunks - mem::size_of::() * step_merge.get() == mem::size_of::() * step_expand.get() + mem::size_of::() * step_merge.get() >= mem::size_of::() * step_expand.get() } // Fall back to other from_iter impls if an overflow occurred in the step merge/expansion // tracking. @@ -255,12 +255,15 @@ where let dst_guard = InPlaceDstBufDrop { ptr: dst_buf, len, cap: dst_cap }; src.forget_allocation_drop_remaining(); - // Adjust the allocation size if the source had a capacity in bytes that wasn't a multiple - // of the destination type size. + // Adjust the allocation if the alignment didn't match or the source had a capacity in bytes + // that wasn't a multiple of the destination type size. // Since the discrepancy should generally be small this should only result in some // bookkeeping updates and no memmove. - if const { mem::size_of::() > mem::size_of::() } - && src_cap * mem::size_of::() != dst_cap * mem::size_of::() + if (const { + let src_sz = mem::size_of::(); + src_sz > 0 && mem::size_of::() % src_sz != 0 + } && src_cap * mem::size_of::() != dst_cap * mem::size_of::()) + || const { mem::align_of::() != mem::align_of::() } { let alloc = Global; unsafe { From 072b51cbb5b4ee39ae433b5f92a5952054e6caf3 Mon Sep 17 00:00:00 2001 From: The 8472 Date: Mon, 29 May 2023 13:04:26 +0200 Subject: [PATCH 0053/1762] unchecked layout calculations when shrinking during in-place collect Reduces the amount of emitted IR. RawVec has similar optimizations --- library/alloc/src/vec/in_place_collect.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/library/alloc/src/vec/in_place_collect.rs b/library/alloc/src/vec/in_place_collect.rs index 1f03f0d19ec17..e4f96fd764040 100644 --- a/library/alloc/src/vec/in_place_collect.rs +++ b/library/alloc/src/vec/in_place_collect.rs @@ -267,10 +267,20 @@ where { let alloc = Global; unsafe { - let new_layout = Layout::array::(dst_cap).unwrap(); + // The old allocation exists, therefore it must have a valid layout. + let src_align = mem::align_of::(); + let src_size = mem::size_of::().unchecked_mul(src_cap); + let old_layout = Layout::from_size_align_unchecked(src_size, src_align); + + // The must be equal or smaller for in-place iteration to be possible + // therefore the new layout must be ≤ the old one and therefore valid. + let dst_align = mem::align_of::(); + let dst_size = mem::size_of::().unchecked_mul(dst_cap); + let new_layout = Layout::from_size_align_unchecked(dst_size, dst_align); + let result = alloc.shrink( NonNull::new_unchecked(dst_buf as *mut u8), - Layout::array::(src_cap).unwrap(), + old_layout, new_layout, ); let Ok(reallocated) = result else { handle_alloc_error(new_layout) }; From 23ffda1a97726ccdb70e43910ae4363f3e5b8448 Mon Sep 17 00:00:00 2001 From: vxpm Date: Sun, 3 Sep 2023 22:42:56 -0300 Subject: [PATCH 0054/1762] full function signatures option --- crates/ide-completion/src/config.rs | 1 + crates/ide-completion/src/render/function.rs | 8 ++++++-- crates/rust-analyzer/src/config.rs | 3 +++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/ide-completion/src/config.rs b/crates/ide-completion/src/config.rs index 8f6a97e1e09d8..3d025f284bbb7 100644 --- a/crates/ide-completion/src/config.rs +++ b/crates/ide-completion/src/config.rs @@ -14,6 +14,7 @@ pub struct CompletionConfig { pub enable_imports_on_the_fly: bool, pub enable_self_on_the_fly: bool, pub enable_private_editable: bool, + pub full_function_signatures: bool, pub callable: Option, pub snippet_cap: Option, pub insert_use: InsertUseConfig, diff --git a/crates/ide-completion/src/render/function.rs b/crates/ide-completion/src/render/function.rs index 8afce8db5ea86..1672826f3306b 100644 --- a/crates/ide-completion/src/render/function.rs +++ b/crates/ide-completion/src/render/function.rs @@ -100,7 +100,7 @@ fn render( item.set_documentation(ctx.docs(func)) .set_deprecated(ctx.is_deprecated(func) || ctx.is_deprecated_assoc_item(func)) - .detail(detail(db, func)) + .detail(detail(db, func, ctx.completion.config.full_function_signatures)) .lookup_by(name.unescaped().to_smol_str()); match ctx.completion.config.snippet_cap { @@ -239,7 +239,11 @@ fn ref_of_param(ctx: &CompletionContext<'_>, arg: &str, ty: &hir::Type) -> &'sta "" } -fn detail(db: &dyn HirDatabase, func: hir::Function) -> String { +fn detail(db: &dyn HirDatabase, func: hir::Function, full_function_signature: bool) -> String { + if full_function_signature { + return format!("{}", func.display(db)).replace("\n", " "); + } + let mut ret_ty = func.ret_type(db); let mut detail = String::new(); diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index ea3a21241cb6e..07c6457b9c759 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -215,6 +215,8 @@ config_data! { completion_postfix_enable: bool = "true", /// Enables completions of private items and fields that are defined in the current workspace even if they are not visible at the current position. completion_privateEditable_enable: bool = "false", + /// Whether to show full function/method signatures in completion docs. + completion_fullFunctionSignatures_enable: bool = "false", /// Custom completion snippets. // NOTE: Keep this list in sync with the feature docs of user snippets. completion_snippets_custom: FxHashMap = r#"{ @@ -1444,6 +1446,7 @@ impl Config { && completion_item_edit_resolve(&self.caps), enable_self_on_the_fly: self.data.completion_autoself_enable, enable_private_editable: self.data.completion_privateEditable_enable, + full_function_signatures: self.data.completion_fullFunctionSignatures_enable, callable: match self.data.completion_callable_snippets { CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments), CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses), From 6afa5b0ba28c46905c0d0ac3022d4e1af0f1de36 Mon Sep 17 00:00:00 2001 From: vxpm Date: Sun, 3 Sep 2023 23:41:13 -0300 Subject: [PATCH 0055/1762] better handling of spaces & newlines --- crates/ide-completion/src/render/function.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/crates/ide-completion/src/render/function.rs b/crates/ide-completion/src/render/function.rs index 1672826f3306b..dd7de72190d1e 100644 --- a/crates/ide-completion/src/render/function.rs +++ b/crates/ide-completion/src/render/function.rs @@ -241,7 +241,18 @@ fn ref_of_param(ctx: &CompletionContext<'_>, arg: &str, ty: &hir::Type) -> &'sta fn detail(db: &dyn HirDatabase, func: hir::Function, full_function_signature: bool) -> String { if full_function_signature { - return format!("{}", func.display(db)).replace("\n", " "); + let signature = format!("{}", func.display(db)); + let mut singleline = String::with_capacity(signature.len()); + + for segment in signature.split_whitespace() { + if !singleline.is_empty() { + singleline.push(' '); + } + + singleline.push_str(segment); + } + + return singleline; } let mut ret_ty = func.ret_type(db); From 6b487ed4be81e723e0ed2035f79b27d9efb93b8a Mon Sep 17 00:00:00 2001 From: vxpm Date: Mon, 4 Sep 2023 00:02:08 -0300 Subject: [PATCH 0056/1762] fix & run tests --- crates/ide-completion/src/tests.rs | 1 + crates/rust-analyzer/src/config.rs | 4 ++-- crates/rust-analyzer/src/integrated_benchmarks.rs | 2 ++ docs/user/generated_config.adoc | 5 +++++ editors/code/package.json | 5 +++++ 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/crates/ide-completion/src/tests.rs b/crates/ide-completion/src/tests.rs index 2464e8d5f8175..284bdd8af21f8 100644 --- a/crates/ide-completion/src/tests.rs +++ b/crates/ide-completion/src/tests.rs @@ -64,6 +64,7 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig { enable_imports_on_the_fly: true, enable_self_on_the_fly: true, enable_private_editable: false, + full_function_signatures: false, callable: Some(CallableSnippets::FillArguments), snippet_cap: SnippetCap::new(true), prefer_no_std: false, diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 07c6457b9c759..9382f4ea19efe 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -209,14 +209,14 @@ config_data! { completion_autoself_enable: bool = "true", /// Whether to add parenthesis and argument snippets when completing function. completion_callable_snippets: CallableCompletionDef = "\"fill_arguments\"", + /// Whether to show full function/method signatures in completion docs. + completion_fullFunctionSignatures_enable: bool = "false", /// Maximum number of completions to return. If `None`, the limit is infinite. completion_limit: Option = "null", /// Whether to show postfix snippets like `dbg`, `if`, `not`, etc. completion_postfix_enable: bool = "true", /// Enables completions of private items and fields that are defined in the current workspace even if they are not visible at the current position. completion_privateEditable_enable: bool = "false", - /// Whether to show full function/method signatures in completion docs. - completion_fullFunctionSignatures_enable: bool = "false", /// Custom completion snippets. // NOTE: Keep this list in sync with the feature docs of user snippets. completion_snippets_custom: FxHashMap = r#"{ diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs index 5a11012b93cf1..2c402552919f0 100644 --- a/crates/rust-analyzer/src/integrated_benchmarks.rs +++ b/crates/rust-analyzer/src/integrated_benchmarks.rs @@ -134,6 +134,7 @@ fn integrated_completion_benchmark() { enable_imports_on_the_fly: true, enable_self_on_the_fly: true, enable_private_editable: true, + full_function_signatures: false, callable: Some(CallableSnippets::FillArguments), snippet_cap: SnippetCap::new(true), insert_use: InsertUseConfig { @@ -173,6 +174,7 @@ fn integrated_completion_benchmark() { enable_imports_on_the_fly: true, enable_self_on_the_fly: true, enable_private_editable: true, + full_function_signatures: false, callable: Some(CallableSnippets::FillArguments), snippet_cap: SnippetCap::new(true), insert_use: InsertUseConfig { diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index 71feed0f72ca0..cf549565fe1db 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -244,6 +244,11 @@ with `self` prefixed to them when inside a method. -- Whether to add parenthesis and argument snippets when completing function. -- +[[rust-analyzer.completion.fullFunctionSignatures.enable]]rust-analyzer.completion.fullFunctionSignatures.enable (default: `false`):: ++ +-- +Whether to show full function/method signatures in completion docs. +-- [[rust-analyzer.completion.limit]]rust-analyzer.completion.limit (default: `null`):: + -- diff --git a/editors/code/package.json b/editors/code/package.json index 233e7bf44b166..e075e227625ab 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -799,6 +799,11 @@ "Do no snippet completions for callables." ] }, + "rust-analyzer.completion.fullFunctionSignatures.enable": { + "markdownDescription": "Whether to show full function/method signatures in completion docs.", + "default": false, + "type": "boolean" + }, "rust-analyzer.completion.limit": { "markdownDescription": "Maximum number of completions to return. If `None`, the limit is infinite.", "default": null, From d7a8e800c9270023d5a19f793f28054b936f7c5d Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Sat, 19 Aug 2023 16:49:26 -0600 Subject: [PATCH 0057/1762] feat: initial version of bool_to_enum assist --- .../ide-assists/src/handlers/bool_to_enum.rs | 724 ++++++++++++++++++ crates/ide-assists/src/lib.rs | 2 + crates/syntax/src/ast/make.rs | 28 + 3 files changed, 754 insertions(+) create mode 100644 crates/ide-assists/src/handlers/bool_to_enum.rs diff --git a/crates/ide-assists/src/handlers/bool_to_enum.rs b/crates/ide-assists/src/handlers/bool_to_enum.rs new file mode 100644 index 0000000000000..dd11824b992d7 --- /dev/null +++ b/crates/ide-assists/src/handlers/bool_to_enum.rs @@ -0,0 +1,724 @@ +use ide_db::{ + assists::{AssistId, AssistKind}, + defs::Definition, + search::{FileReference, SearchScope, UsageSearchResult}, + source_change::SourceChangeBuilder, +}; +use syntax::{ + ast::{ + self, + edit::IndentLevel, + edit_in_place::{AttrsOwnerEdit, Indent}, + make, HasName, + }, + ted, AstNode, NodeOrToken, SyntaxNode, T, +}; + +use crate::assist_context::{AssistContext, Assists}; + +// Assist: bool_to_enum +// +// This converts boolean local variables, fields, constants, and statics into a new +// enum with two variants `Bool::True` and `Bool::False`, as well as replacing +// all assignments with the variants and replacing all usages with `== Bool::True` or +// `== Bool::False`. +// +// ``` +// fn main() { +// let $0bool = true; +// +// if bool { +// println!("foo"); +// } +// } +// ``` +// -> +// ``` +// fn main() { +// #[derive(PartialEq, Eq)] +// enum Bool { True, False } +// +// let bool = Bool::True; +// +// if bool == Bool::True { +// println!("foo"); +// } +// } +// ``` +pub(crate) fn bool_to_enum(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let BoolNodeData { target_node, name, ty_annotation, initializer, definition } = + find_bool_node(ctx)?; + + let target = name.syntax().text_range(); + acc.add( + AssistId("bool_to_enum", AssistKind::RefactorRewrite), + "Convert boolean to enum", + target, + |edit| { + if let Some(ty) = &ty_annotation { + cov_mark::hit!(replaces_ty_annotation); + edit.replace(ty.syntax().text_range(), "Bool"); + } + + if let Some(initializer) = initializer { + replace_bool_expr(edit, initializer); + } + + let usages = definition + .usages(&ctx.sema) + .in_scope(&SearchScope::single_file(ctx.file_id())) + .all(); + replace_usages(edit, &usages); + + add_enum_def(edit, ctx, &usages, target_node); + }, + ) +} + +struct BoolNodeData { + target_node: SyntaxNode, + name: ast::Name, + ty_annotation: Option, + initializer: Option, + definition: Definition, +} + +/// Attempts to find an appropriate node to apply the action to. +fn find_bool_node(ctx: &AssistContext<'_>) -> Option { + if let Some(let_stmt) = ctx.find_node_at_offset::() { + let bind_pat = match let_stmt.pat()? { + ast::Pat::IdentPat(pat) => pat, + _ => { + cov_mark::hit!(not_applicable_in_non_ident_pat); + return None; + } + }; + let def = ctx.sema.to_def(&bind_pat)?; + if !def.ty(ctx.db()).is_bool() { + cov_mark::hit!(not_applicable_non_bool_local); + return None; + } + + Some(BoolNodeData { + target_node: let_stmt.syntax().clone(), + name: bind_pat.name()?, + ty_annotation: let_stmt.ty(), + initializer: let_stmt.initializer(), + definition: Definition::Local(def), + }) + } else if let Some(const_) = ctx.find_node_at_offset::() { + let def = ctx.sema.to_def(&const_)?; + if !def.ty(ctx.db()).is_bool() { + cov_mark::hit!(not_applicable_non_bool_const); + return None; + } + + Some(BoolNodeData { + target_node: const_.syntax().clone(), + name: const_.name()?, + ty_annotation: const_.ty(), + initializer: const_.body(), + definition: Definition::Const(def), + }) + } else if let Some(static_) = ctx.find_node_at_offset::() { + let def = ctx.sema.to_def(&static_)?; + if !def.ty(ctx.db()).is_bool() { + cov_mark::hit!(not_applicable_non_bool_static); + return None; + } + + Some(BoolNodeData { + target_node: static_.syntax().clone(), + name: static_.name()?, + ty_annotation: static_.ty(), + initializer: static_.body(), + definition: Definition::Static(def), + }) + } else if let Some(field_name) = ctx.find_node_at_offset::() { + let field = field_name.syntax().ancestors().find_map(ast::RecordField::cast)?; + if field.name()? != field_name { + return None; + } + + let strukt = field.syntax().ancestors().find_map(ast::Struct::cast)?; + let def = ctx.sema.to_def(&field)?; + if !def.ty(ctx.db()).is_bool() { + cov_mark::hit!(not_applicable_non_bool_field); + return None; + } + Some(BoolNodeData { + target_node: strukt.syntax().clone(), + name: field_name, + ty_annotation: field.ty(), + initializer: None, + definition: Definition::Field(def), + }) + } else { + None + } +} + +fn replace_bool_expr(edit: &mut SourceChangeBuilder, expr: ast::Expr) { + let expr_range = expr.syntax().text_range(); + let enum_expr = bool_expr_to_enum_expr(expr); + edit.replace(expr_range, enum_expr.syntax().text()) +} + +/// Converts an expression of type `bool` to one of the new enum type. +fn bool_expr_to_enum_expr(expr: ast::Expr) -> ast::Expr { + let true_expr = make::expr_path(make::path_from_text("Bool::True")).clone_for_update(); + let false_expr = make::expr_path(make::path_from_text("Bool::False")).clone_for_update(); + + if let ast::Expr::Literal(literal) = &expr { + match literal.kind() { + ast::LiteralKind::Bool(true) => true_expr, + ast::LiteralKind::Bool(false) => false_expr, + _ => expr, + } + } else { + make::expr_if( + expr, + make::tail_only_block_expr(true_expr), + Some(ast::ElseBranch::Block(make::tail_only_block_expr(false_expr))), + ) + .clone_for_update() + } +} + +/// Replaces all usages of the target identifier, both when read and written to. +fn replace_usages(edit: &mut SourceChangeBuilder, usages: &UsageSearchResult) { + for (_, references) in usages.iter() { + references + .into_iter() + .filter_map(|FileReference { range, name, .. }| match name { + ast::NameLike::NameRef(name) => Some((*range, name)), + _ => None, + }) + .for_each(|(range, name_ref)| { + if let Some(initializer) = find_assignment_usage(name_ref) { + cov_mark::hit!(replaces_assignment); + + replace_bool_expr(edit, initializer); + } else if let Some((prefix_expr, expr)) = find_negated_usage(name_ref) { + cov_mark::hit!(replaces_negation); + + edit.replace( + prefix_expr.syntax().text_range(), + format!("{} == Bool::False", expr), + ); + } else if let Some((record_field, initializer)) = find_record_expr_usage(name_ref) { + cov_mark::hit!(replaces_record_expr); + + let record_field = edit.make_mut(record_field); + let enum_expr = bool_expr_to_enum_expr(initializer); + record_field.replace_expr(enum_expr); + } else if name_ref.syntax().ancestors().find_map(ast::Expr::cast).is_some() { + // for any other usage in an expression, replace it with a check that it is the true variant + edit.replace(range, format!("{} == Bool::True", name_ref.text())); + } + }) + } +} + +fn find_assignment_usage(name_ref: &ast::NameRef) -> Option { + let bin_expr = name_ref.syntax().ancestors().find_map(ast::BinExpr::cast)?; + + if let Some(ast::BinaryOp::Assignment { op: None }) = bin_expr.op_kind() { + bin_expr.rhs() + } else { + None + } +} + +fn find_negated_usage(name_ref: &ast::NameRef) -> Option<(ast::PrefixExpr, ast::Expr)> { + let prefix_expr = name_ref.syntax().ancestors().find_map(ast::PrefixExpr::cast)?; + + if let Some(ast::UnaryOp::Not) = prefix_expr.op_kind() { + let initializer = prefix_expr.expr()?; + Some((prefix_expr, initializer)) + } else { + None + } +} + +fn find_record_expr_usage(name_ref: &ast::NameRef) -> Option<(ast::RecordExprField, ast::Expr)> { + let record_field = name_ref.syntax().ancestors().find_map(ast::RecordExprField::cast)?; + let initializer = record_field.expr()?; + + Some((record_field, initializer)) +} + +/// Adds the definition of the new enum before the target node. +fn add_enum_def( + edit: &mut SourceChangeBuilder, + ctx: &AssistContext<'_>, + usages: &UsageSearchResult, + target_node: SyntaxNode, +) { + let make_enum_pub = usages.iter().any(|(file_id, _)| file_id != &ctx.file_id()); + let enum_def = make_bool_enum(make_enum_pub); + + let indent = IndentLevel::from_node(&target_node); + enum_def.reindent_to(indent); + + ted::insert_all( + ted::Position::before(&edit.make_syntax_mut(target_node)), + vec![ + enum_def.syntax().clone().into(), + make::tokens::whitespace(&format!("\n\n{indent}")).into(), + ], + ); +} + +fn make_bool_enum(make_pub: bool) -> ast::Enum { + let enum_def = make::enum_( + if make_pub { Some(make::visibility_pub()) } else { None }, + make::name("Bool"), + make::variant_list(vec![ + make::variant(make::name("True"), None), + make::variant(make::name("False"), None), + ]), + ) + .clone_for_update(); + + let derive_eq = make::attr_outer(make::meta_token_tree( + make::ext::ident_path("derive"), + make::token_tree( + T!['('], + vec![ + NodeOrToken::Token(make::tokens::ident("PartialEq")), + NodeOrToken::Token(make::token(T![,])), + NodeOrToken::Token(make::tokens::single_space()), + NodeOrToken::Token(make::tokens::ident("Eq")), + ], + ), + )) + .clone_for_update(); + enum_def.add_attr(derive_eq); + + enum_def +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::tests::{check_assist, check_assist_not_applicable}; + + #[test] + fn local_variable_with_usage() { + check_assist( + bool_to_enum, + r#" +fn main() { + let $0foo = true; + + if foo { + println!("foo"); + } +} +"#, + r#" +fn main() { + #[derive(PartialEq, Eq)] + enum Bool { True, False } + + let foo = Bool::True; + + if foo == Bool::True { + println!("foo"); + } +} +"#, + ) + } + + #[test] + fn local_variable_with_usage_negated() { + cov_mark::check!(replaces_negation); + check_assist( + bool_to_enum, + r#" +fn main() { + let $0foo = true; + + if !foo { + println!("foo"); + } +} +"#, + r#" +fn main() { + #[derive(PartialEq, Eq)] + enum Bool { True, False } + + let foo = Bool::True; + + if foo == Bool::False { + println!("foo"); + } +} +"#, + ) + } + + #[test] + fn local_variable_with_type_annotation() { + cov_mark::check!(replaces_ty_annotation); + check_assist( + bool_to_enum, + r#" +fn main() { + let $0foo: bool = false; +} +"#, + r#" +fn main() { + #[derive(PartialEq, Eq)] + enum Bool { True, False } + + let foo: Bool = Bool::False; +} +"#, + ) + } + + #[test] + fn local_variable_with_non_literal_initializer() { + check_assist( + bool_to_enum, + r#" +fn main() { + let $0foo = 1 == 2; +} +"#, + r#" +fn main() { + #[derive(PartialEq, Eq)] + enum Bool { True, False } + + let foo = if 1 == 2 { Bool::True } else { Bool::False }; +} +"#, + ) + } + + #[test] + fn local_variable_binexpr_usage() { + check_assist( + bool_to_enum, + r#" +fn main() { + let $0foo = false; + let bar = true; + + if !foo && bar { + println!("foobar"); + } +} +"#, + r#" +fn main() { + #[derive(PartialEq, Eq)] + enum Bool { True, False } + + let foo = Bool::False; + let bar = true; + + if foo == Bool::False && bar { + println!("foobar"); + } +} +"#, + ) + } + + #[test] + fn local_variable_unop_usage() { + check_assist( + bool_to_enum, + r#" +fn main() { + let $0foo = true; + + if *&foo { + println!("foobar"); + } +} +"#, + r#" +fn main() { + #[derive(PartialEq, Eq)] + enum Bool { True, False } + + let foo = Bool::True; + + if *&foo == Bool::True { + println!("foobar"); + } +} +"#, + ) + } + + #[test] + fn local_variable_assigned_later() { + cov_mark::check!(replaces_assignment); + check_assist( + bool_to_enum, + r#" +fn main() { + let $0foo: bool; + foo = true; +} +"#, + r#" +fn main() { + #[derive(PartialEq, Eq)] + enum Bool { True, False } + + let foo: Bool; + foo = Bool::True; +} +"#, + ) + } + + #[test] + fn local_variable_does_not_apply_recursively() { + check_assist( + bool_to_enum, + r#" +fn main() { + let $0foo = true; + let bar = !foo; + + if bar { + println!("bar"); + } +} +"#, + r#" +fn main() { + #[derive(PartialEq, Eq)] + enum Bool { True, False } + + let foo = Bool::True; + let bar = foo == Bool::False; + + if bar { + println!("bar"); + } +} +"#, + ) + } + + #[test] + fn local_variable_non_bool() { + cov_mark::check!(not_applicable_non_bool_local); + check_assist_not_applicable( + bool_to_enum, + r#" +fn main() { + let $0foo = 1; +} +"#, + ) + } + + #[test] + fn local_variable_non_ident_pat() { + cov_mark::check!(not_applicable_in_non_ident_pat); + check_assist_not_applicable( + bool_to_enum, + r#" +fn main() { + let ($0foo, bar) = (true, false); +} +"#, + ) + } + + #[test] + fn field_basic() { + cov_mark::check!(replaces_record_expr); + check_assist( + bool_to_enum, + r#" +struct Foo { + $0bar: bool, + baz: bool, +} + +fn main() { + let foo = Foo { bar: true, baz: false }; + + if foo.bar { + println!("foo"); + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +struct Foo { + bar: Bool, + baz: bool, +} + +fn main() { + let foo = Foo { bar: Bool::True, baz: false }; + + if foo.bar == Bool::True { + println!("foo"); + } +} +"#, + ) + } + + #[test] + fn field_in_mod_properly_indented() { + check_assist( + bool_to_enum, + r#" +mod foo { + struct Bar { + $0baz: bool, + } + + impl Bar { + fn new(baz: bool) -> Self { + Self { baz } + } + } +} +"#, + r#" +mod foo { + #[derive(PartialEq, Eq)] + enum Bool { True, False } + + struct Bar { + baz: Bool, + } + + impl Bar { + fn new(baz: bool) -> Self { + Self { baz: if baz { Bool::True } else { Bool::False } } + } + } +} +"#, + ) + } + + #[test] + fn field_non_bool() { + cov_mark::check!(not_applicable_non_bool_field); + check_assist_not_applicable( + bool_to_enum, + r#" +struct Foo { + $0bar: usize, +} + +fn main() { + let foo = Foo { bar: 1 }; +} +"#, + ) + } + + #[test] + fn const_basic() { + check_assist( + bool_to_enum, + r#" +const $0FOO: bool = false; + +fn main() { + if FOO { + println!("foo"); + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +const FOO: Bool = Bool::False; + +fn main() { + if FOO == Bool::True { + println!("foo"); + } +} +"#, + ) + } + + #[test] + fn const_non_bool() { + cov_mark::check!(not_applicable_non_bool_const); + check_assist_not_applicable( + bool_to_enum, + r#" +const $0FOO: &str = "foo"; + +fn main() { + println!("{FOO}"); +} +"#, + ) + } + + #[test] + fn static_basic() { + check_assist( + bool_to_enum, + r#" +static mut $0BOOL: bool = true; + +fn main() { + unsafe { BOOL = false }; + if unsafe { BOOL } { + println!("foo"); + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +static mut BOOL: Bool = Bool::True; + +fn main() { + unsafe { BOOL = Bool::False }; + if unsafe { BOOL == Bool::True } { + println!("foo"); + } +} +"#, + ) + } + + #[test] + fn static_non_bool() { + cov_mark::check!(not_applicable_non_bool_static); + check_assist_not_applicable( + bool_to_enum, + r#" +static mut $0FOO: usize = 0; + +fn main() { + if unsafe { FOO } == 0 { + println!("foo"); + } +} +"#, + ) + } +} diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index 6f973ab53eec1..a17ce93e928c8 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -115,6 +115,7 @@ mod handlers { mod apply_demorgan; mod auto_import; mod bind_unused_param; + mod bool_to_enum; mod change_visibility; mod convert_bool_then; mod convert_comment_block; @@ -227,6 +228,7 @@ mod handlers { apply_demorgan::apply_demorgan, auto_import::auto_import, bind_unused_param::bind_unused_param, + bool_to_enum::bool_to_enum, change_visibility::change_visibility, convert_bool_then::convert_bool_then_to_if, convert_bool_then::convert_if_to_bool_then, diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs index 17e311c0c502f..e0055be6e69e2 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs @@ -973,6 +973,11 @@ pub fn tuple_field(visibility: Option, ty: ast::Type) -> ast::T ast_from_text(&format!("struct f({visibility}{ty});")) } +pub fn variant_list(variants: impl IntoIterator) -> ast::VariantList { + let variants = variants.into_iter().join(", "); + ast_from_text(&format!("enum f {{ {variants} }}")) +} + pub fn variant(name: ast::Name, field_list: Option) -> ast::Variant { let field_list = match field_list { None => String::new(), @@ -1037,6 +1042,19 @@ pub fn struct_( ast_from_text(&format!("{visibility}struct {strukt_name}{type_params}{field_list}{semicolon}",)) } +pub fn enum_( + visibility: Option, + enum_name: ast::Name, + variant_list: ast::VariantList, +) -> ast::Enum { + let visibility = match visibility { + None => String::new(), + Some(it) => format!("{it} "), + }; + + ast_from_text(&format!("{visibility}enum {enum_name} {variant_list}")) +} + pub fn attr_outer(meta: ast::Meta) -> ast::Attr { ast_from_text(&format!("#[{meta}]")) } @@ -1149,6 +1167,16 @@ pub mod tokens { lit.syntax().first_child_or_token().unwrap().into_token().unwrap() } + pub fn ident(text: &str) -> SyntaxToken { + assert_eq!(text.trim(), text); + let path: ast::Path = super::ext::ident_path(text); + path.syntax() + .descendants_with_tokens() + .filter_map(|it| it.into_token()) + .find(|it| it.kind() == IDENT) + .unwrap() + } + pub fn single_newline() -> SyntaxToken { let res = SOURCE_FILE .tree() From 59738d5fd5f868cec69e0ff30e27a6b80fc81ee4 Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Sat, 19 Aug 2023 17:41:44 -0600 Subject: [PATCH 0058/1762] fix: add generated doctest --- crates/ide-assists/src/tests/generated.rs | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index dfaa53449f42d..63a08a0e5697b 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -280,6 +280,34 @@ fn some_function(x: i32) { ) } +#[test] +fn doctest_bool_to_enum() { + check_doc_test( + "bool_to_enum", + r#####" +fn main() { + let $0bool = true; + + if bool { + println!("foo"); + } +} +"#####, + r#####" +fn main() { + #[derive(PartialEq, Eq)] + enum Bool { True, False } + + let bool = Bool::True; + + if bool == Bool::True { + println!("foo"); + } +} +"#####, + ) +} + #[test] fn doctest_change_visibility() { check_doc_test( From 83196fd4d9ed8544410fc82fee5d54830163f248 Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Sat, 19 Aug 2023 17:45:16 -0600 Subject: [PATCH 0059/1762] fix: remove trailing whitespace --- crates/ide-assists/src/handlers/bool_to_enum.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/ide-assists/src/handlers/bool_to_enum.rs b/crates/ide-assists/src/handlers/bool_to_enum.rs index dd11824b992d7..279e5583624fc 100644 --- a/crates/ide-assists/src/handlers/bool_to_enum.rs +++ b/crates/ide-assists/src/handlers/bool_to_enum.rs @@ -369,7 +369,7 @@ fn main() { bool_to_enum, r#" fn main() { - let $0foo: bool = false; + let $0foo: bool = false; } "#, r#" @@ -377,7 +377,7 @@ fn main() { #[derive(PartialEq, Eq)] enum Bool { True, False } - let foo: Bool = Bool::False; + let foo: Bool = Bool::False; } "#, ) @@ -389,7 +389,7 @@ fn main() { bool_to_enum, r#" fn main() { - let $0foo = 1 == 2; + let $0foo = 1 == 2; } "#, r#" @@ -397,7 +397,7 @@ fn main() { #[derive(PartialEq, Eq)] enum Bool { True, False } - let foo = if 1 == 2 { Bool::True } else { Bool::False }; + let foo = if 1 == 2 { Bool::True } else { Bool::False }; } "#, ) @@ -468,7 +468,7 @@ fn main() { bool_to_enum, r#" fn main() { - let $0foo: bool; + let $0foo: bool; foo = true; } "#, @@ -477,7 +477,7 @@ fn main() { #[derive(PartialEq, Eq)] enum Bool { True, False } - let foo: Bool; + let foo: Bool; foo = Bool::True; } "#, From 91ac1d619475e1b61bf4ae8d318c4740a0adce66 Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 8 Sep 2023 07:45:23 -0700 Subject: [PATCH 0060/1762] fix: initializing struct multiple times --- .../ide-assists/src/handlers/bool_to_enum.rs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/crates/ide-assists/src/handlers/bool_to_enum.rs b/crates/ide-assists/src/handlers/bool_to_enum.rs index 279e5583624fc..4158b75dc00c9 100644 --- a/crates/ide-assists/src/handlers/bool_to_enum.rs +++ b/crates/ide-assists/src/handlers/bool_to_enum.rs @@ -194,6 +194,7 @@ fn replace_usages(edit: &mut SourceChangeBuilder, usages: &UsageSearchResult) { ast::NameLike::NameRef(name) => Some((*range, name)), _ => None, }) + .rev() .for_each(|(range, name_ref)| { if let Some(initializer) = find_assignment_usage(name_ref) { cov_mark::hit!(replaces_assignment); @@ -615,6 +616,46 @@ mod foo { ) } + #[test] + fn field_multiple_initializations() { + check_assist( + bool_to_enum, + r#" +struct Foo { + $0bar: bool, + baz: bool, +} + +fn main() { + let foo1 = Foo { bar: true, baz: false }; + let foo2 = Foo { bar: false, baz: false }; + + if foo1.bar && foo2.bar { + println!("foo"); + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum $0Bool { True, False } + +struct Foo { + bar: Bool, + baz: bool, +} + +fn main() { + let foo1 = Foo { bar: Bool::True, baz: false }; + let foo2 = Foo { bar: Bool::False, baz: false }; + + if foo1.bar == Bool::True && foo2.bar == Bool::True { + println!("foo"); + } +} +"#, + ) + } + #[test] fn field_non_bool() { cov_mark::check!(not_applicable_non_bool_field); From 455dacfd3b5387bcf2854f2a88edb9b69361e69f Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 8 Sep 2023 10:06:17 -0700 Subject: [PATCH 0061/1762] fix: only trigger assist on Name --- .../ide-assists/src/handlers/bool_to_enum.rs | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/crates/ide-assists/src/handlers/bool_to_enum.rs b/crates/ide-assists/src/handlers/bool_to_enum.rs index 4158b75dc00c9..56749edf4636b 100644 --- a/crates/ide-assists/src/handlers/bool_to_enum.rs +++ b/crates/ide-assists/src/handlers/bool_to_enum.rs @@ -85,7 +85,9 @@ struct BoolNodeData { /// Attempts to find an appropriate node to apply the action to. fn find_bool_node(ctx: &AssistContext<'_>) -> Option { - if let Some(let_stmt) = ctx.find_node_at_offset::() { + let name: ast::Name = ctx.find_node_at_offset()?; + + if let Some(let_stmt) = name.syntax().ancestors().find_map(ast::LetStmt::cast) { let bind_pat = match let_stmt.pat()? { ast::Pat::IdentPat(pat) => pat, _ => { @@ -101,12 +103,12 @@ fn find_bool_node(ctx: &AssistContext<'_>) -> Option { Some(BoolNodeData { target_node: let_stmt.syntax().clone(), - name: bind_pat.name()?, + name, ty_annotation: let_stmt.ty(), initializer: let_stmt.initializer(), definition: Definition::Local(def), }) - } else if let Some(const_) = ctx.find_node_at_offset::() { + } else if let Some(const_) = name.syntax().ancestors().find_map(ast::Const::cast) { let def = ctx.sema.to_def(&const_)?; if !def.ty(ctx.db()).is_bool() { cov_mark::hit!(not_applicable_non_bool_const); @@ -115,12 +117,12 @@ fn find_bool_node(ctx: &AssistContext<'_>) -> Option { Some(BoolNodeData { target_node: const_.syntax().clone(), - name: const_.name()?, + name, ty_annotation: const_.ty(), initializer: const_.body(), definition: Definition::Const(def), }) - } else if let Some(static_) = ctx.find_node_at_offset::() { + } else if let Some(static_) = name.syntax().ancestors().find_map(ast::Static::cast) { let def = ctx.sema.to_def(&static_)?; if !def.ty(ctx.db()).is_bool() { cov_mark::hit!(not_applicable_non_bool_static); @@ -129,14 +131,14 @@ fn find_bool_node(ctx: &AssistContext<'_>) -> Option { Some(BoolNodeData { target_node: static_.syntax().clone(), - name: static_.name()?, + name, ty_annotation: static_.ty(), initializer: static_.body(), definition: Definition::Static(def), }) - } else if let Some(field_name) = ctx.find_node_at_offset::() { - let field = field_name.syntax().ancestors().find_map(ast::RecordField::cast)?; - if field.name()? != field_name { + } else { + let field = name.syntax().ancestors().find_map(ast::RecordField::cast)?; + if field.name()? != name { return None; } @@ -148,13 +150,11 @@ fn find_bool_node(ctx: &AssistContext<'_>) -> Option { } Some(BoolNodeData { target_node: strukt.syntax().clone(), - name: field_name, + name, ty_annotation: field.ty(), initializer: None, definition: Definition::Field(def), }) - } else { - None } } @@ -528,6 +528,18 @@ fn main() { ) } + #[test] + fn local_variable_cursor_not_on_ident() { + check_assist_not_applicable( + bool_to_enum, + r#" +fn main() { + let foo = $0true; +} +"#, + ) + } + #[test] fn local_variable_non_ident_pat() { cov_mark::check!(not_applicable_in_non_ident_pat); @@ -762,4 +774,9 @@ fn main() { "#, ) } + + #[test] + fn not_applicable_to_other_names() { + check_assist_not_applicable(bool_to_enum, "fn $0main() {}") + } } From 136a9dbe36606cb00b546c3562088c462d8a0926 Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Fri, 8 Sep 2023 10:54:30 -0700 Subject: [PATCH 0062/1762] style: rename some locals --- crates/ide-assists/src/handlers/bool_to_enum.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/ide-assists/src/handlers/bool_to_enum.rs b/crates/ide-assists/src/handlers/bool_to_enum.rs index 56749edf4636b..975226484403c 100644 --- a/crates/ide-assists/src/handlers/bool_to_enum.rs +++ b/crates/ide-assists/src/handlers/bool_to_enum.rs @@ -200,12 +200,12 @@ fn replace_usages(edit: &mut SourceChangeBuilder, usages: &UsageSearchResult) { cov_mark::hit!(replaces_assignment); replace_bool_expr(edit, initializer); - } else if let Some((prefix_expr, expr)) = find_negated_usage(name_ref) { + } else if let Some((prefix_expr, inner_expr)) = find_negated_usage(name_ref) { cov_mark::hit!(replaces_negation); edit.replace( prefix_expr.syntax().text_range(), - format!("{} == Bool::False", expr), + format!("{} == Bool::False", inner_expr), ); } else if let Some((record_field, initializer)) = find_record_expr_usage(name_ref) { cov_mark::hit!(replaces_record_expr); @@ -235,8 +235,8 @@ fn find_negated_usage(name_ref: &ast::NameRef) -> Option<(ast::PrefixExpr, ast:: let prefix_expr = name_ref.syntax().ancestors().find_map(ast::PrefixExpr::cast)?; if let Some(ast::UnaryOp::Not) = prefix_expr.op_kind() { - let initializer = prefix_expr.expr()?; - Some((prefix_expr, initializer)) + let inner_expr = prefix_expr.expr()?; + Some((prefix_expr, inner_expr)) } else { None } From 2e13aed3bc235d47d92f9ce3b8fd4fa3c5f87939 Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Sat, 9 Sep 2023 11:40:29 -0700 Subject: [PATCH 0063/1762] feat: support cross module imports --- .../ide-assists/src/handlers/bool_to_enum.rs | 226 +++++++++++++++++- 1 file changed, 214 insertions(+), 12 deletions(-) diff --git a/crates/ide-assists/src/handlers/bool_to_enum.rs b/crates/ide-assists/src/handlers/bool_to_enum.rs index 975226484403c..f59b0528131e4 100644 --- a/crates/ide-assists/src/handlers/bool_to_enum.rs +++ b/crates/ide-assists/src/handlers/bool_to_enum.rs @@ -1,9 +1,13 @@ +use hir::ModuleDef; use ide_db::{ assists::{AssistId, AssistKind}, defs::Definition, - search::{FileReference, SearchScope, UsageSearchResult}, + helpers::mod_path_to_ast, + imports::insert_use::{insert_use, ImportScope}, + search::{FileReference, UsageSearchResult}, source_change::SourceChangeBuilder, }; +use itertools::Itertools; use syntax::{ ast::{ self, @@ -48,6 +52,7 @@ use crate::assist_context::{AssistContext, Assists}; pub(crate) fn bool_to_enum(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let BoolNodeData { target_node, name, ty_annotation, initializer, definition } = find_bool_node(ctx)?; + let target_module = ctx.sema.scope(&target_node)?.module(); let target = name.syntax().text_range(); acc.add( @@ -64,13 +69,10 @@ pub(crate) fn bool_to_enum(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option replace_bool_expr(edit, initializer); } - let usages = definition - .usages(&ctx.sema) - .in_scope(&SearchScope::single_file(ctx.file_id())) - .all(); - replace_usages(edit, &usages); + let usages = definition.usages(&ctx.sema).all(); - add_enum_def(edit, ctx, &usages, target_node); + add_enum_def(edit, ctx, &usages, target_node, &target_module); + replace_usages(edit, ctx, &usages, &target_module); }, ) } @@ -186,8 +188,45 @@ fn bool_expr_to_enum_expr(expr: ast::Expr) -> ast::Expr { } /// Replaces all usages of the target identifier, both when read and written to. -fn replace_usages(edit: &mut SourceChangeBuilder, usages: &UsageSearchResult) { - for (_, references) in usages.iter() { +fn replace_usages( + edit: &mut SourceChangeBuilder, + ctx: &AssistContext<'_>, + usages: &UsageSearchResult, + target_module: &hir::Module, +) { + for (file_id, references) in usages.iter() { + edit.edit_file(*file_id); + + // add imports across modules where needed + references + .iter() + .filter_map(|FileReference { name, .. }| { + ctx.sema.scope(name.syntax()).map(|scope| (name, scope.module())) + }) + .unique_by(|name_and_module| name_and_module.1) + .filter(|(_, module)| module != target_module) + .filter_map(|(name, module)| { + let import_scope = ImportScope::find_insert_use_container(name.syntax(), &ctx.sema); + let mod_path = module.find_use_path_prefixed( + ctx.sema.db, + ModuleDef::Module(*target_module), + ctx.config.insert_use.prefix_kind, + ctx.config.prefer_no_std, + ); + import_scope.zip(mod_path) + }) + .for_each(|(import_scope, mod_path)| { + let import_scope = match import_scope { + ImportScope::File(it) => ImportScope::File(edit.make_mut(it)), + ImportScope::Module(it) => ImportScope::Module(edit.make_mut(it)), + ImportScope::Block(it) => ImportScope::Block(edit.make_mut(it)), + }; + let path = + make::path_concat(mod_path_to_ast(&mod_path), make::path_from_text("Bool")); + insert_use(&import_scope, path, &ctx.config.insert_use); + }); + + // replace the usages in expressions references .into_iter() .filter_map(|FileReference { range, name, .. }| match name { @@ -213,7 +252,7 @@ fn replace_usages(edit: &mut SourceChangeBuilder, usages: &UsageSearchResult) { let record_field = edit.make_mut(record_field); let enum_expr = bool_expr_to_enum_expr(initializer); record_field.replace_expr(enum_expr); - } else if name_ref.syntax().ancestors().find_map(ast::Expr::cast).is_some() { + } else if name_ref.syntax().ancestors().find_map(ast::UseTree::cast).is_none() { // for any other usage in an expression, replace it with a check that it is the true variant edit.replace(range, format!("{} == Bool::True", name_ref.text())); } @@ -255,8 +294,15 @@ fn add_enum_def( ctx: &AssistContext<'_>, usages: &UsageSearchResult, target_node: SyntaxNode, + target_module: &hir::Module, ) { - let make_enum_pub = usages.iter().any(|(file_id, _)| file_id != &ctx.file_id()); + let make_enum_pub = usages + .iter() + .flat_map(|(_, refs)| refs) + .filter_map(|FileReference { name, .. }| { + ctx.sema.scope(name.syntax()).map(|scope| scope.module()) + }) + .any(|module| &module != target_module); let enum_def = make_bool_enum(make_enum_pub); let indent = IndentLevel::from_node(&target_node); @@ -649,7 +695,7 @@ fn main() { "#, r#" #[derive(PartialEq, Eq)] -enum $0Bool { True, False } +enum Bool { True, False } struct Foo { bar: Bool, @@ -713,6 +759,162 @@ fn main() { ) } + #[test] + fn const_in_module() { + check_assist( + bool_to_enum, + r#" +fn main() { + if foo::FOO { + println!("foo"); + } +} + +mod foo { + pub const $0FOO: bool = true; +} +"#, + r#" +use foo::Bool; + +fn main() { + if foo::FOO == Bool::True { + println!("foo"); + } +} + +mod foo { + #[derive(PartialEq, Eq)] + pub enum Bool { True, False } + + pub const FOO: Bool = Bool::True; +} +"#, + ) + } + + #[test] + fn const_in_module_with_import() { + check_assist( + bool_to_enum, + r#" +fn main() { + use foo::FOO; + + if FOO { + println!("foo"); + } +} + +mod foo { + pub const $0FOO: bool = true; +} +"#, + r#" +use crate::foo::Bool; + +fn main() { + use foo::FOO; + + if FOO == Bool::True { + println!("foo"); + } +} + +mod foo { + #[derive(PartialEq, Eq)] + pub enum Bool { True, False } + + pub const FOO: Bool = Bool::True; +} +"#, + ) + } + + #[test] + fn const_cross_file() { + check_assist( + bool_to_enum, + r#" +//- /main.rs +mod foo; + +fn main() { + if foo::FOO { + println!("foo"); + } +} + +//- /foo.rs +pub const $0FOO: bool = true; +"#, + r#" +//- /main.rs +use foo::Bool; + +mod foo; + +fn main() { + if foo::FOO == Bool::True { + println!("foo"); + } +} + +//- /foo.rs +#[derive(PartialEq, Eq)] +pub enum Bool { True, False } + +pub const FOO: Bool = Bool::True; +"#, + ) + } + + #[test] + fn const_cross_file_and_module() { + check_assist( + bool_to_enum, + r#" +//- /main.rs +mod foo; + +fn main() { + use foo::bar; + + if bar::BAR { + println!("foo"); + } +} + +//- /foo.rs +pub mod bar { + pub const $0BAR: bool = false; +} +"#, + r#" +//- /main.rs +use crate::foo::bar::Bool; + +mod foo; + +fn main() { + use foo::bar; + + if bar::BAR == Bool::True { + println!("foo"); + } +} + +//- /foo.rs +pub mod bar { + #[derive(PartialEq, Eq)] + pub enum Bool { True, False } + + pub const BAR: Bool = Bool::False; +} +"#, + ) + } + #[test] fn const_non_bool() { cov_mark::check!(not_applicable_non_bool_const); From 7ba2e130b975f62906fff6ea82aacff883a9e528 Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Sat, 9 Sep 2023 23:54:25 -0700 Subject: [PATCH 0064/1762] fix: add checks for overwriting incorrect ancestor --- .../ide-assists/src/handlers/bool_to_enum.rs | 166 +++++++++++++++++- 1 file changed, 165 insertions(+), 1 deletion(-) diff --git a/crates/ide-assists/src/handlers/bool_to_enum.rs b/crates/ide-assists/src/handlers/bool_to_enum.rs index f59b0528131e4..784a0d3559970 100644 --- a/crates/ide-assists/src/handlers/bool_to_enum.rs +++ b/crates/ide-assists/src/handlers/bool_to_enum.rs @@ -263,6 +263,11 @@ fn replace_usages( fn find_assignment_usage(name_ref: &ast::NameRef) -> Option { let bin_expr = name_ref.syntax().ancestors().find_map(ast::BinExpr::cast)?; + if !bin_expr.lhs()?.syntax().descendants().contains(name_ref.syntax()) { + cov_mark::hit!(dont_assign_incorrect_ref); + return None; + } + if let Some(ast::BinaryOp::Assignment { op: None }) = bin_expr.op_kind() { bin_expr.rhs() } else { @@ -273,6 +278,11 @@ fn find_assignment_usage(name_ref: &ast::NameRef) -> Option { fn find_negated_usage(name_ref: &ast::NameRef) -> Option<(ast::PrefixExpr, ast::Expr)> { let prefix_expr = name_ref.syntax().ancestors().find_map(ast::PrefixExpr::cast)?; + if !matches!(prefix_expr.expr()?, ast::Expr::PathExpr(_) | ast::Expr::FieldExpr(_)) { + cov_mark::hit!(dont_overwrite_expression_inside_negation); + return None; + } + if let Some(ast::UnaryOp::Not) = prefix_expr.op_kind() { let inner_expr = prefix_expr.expr()?; Some((prefix_expr, inner_expr)) @@ -285,7 +295,12 @@ fn find_record_expr_usage(name_ref: &ast::NameRef) -> Option<(ast::RecordExprFie let record_field = name_ref.syntax().ancestors().find_map(ast::RecordExprField::cast)?; let initializer = record_field.expr()?; - Some((record_field, initializer)) + if record_field.field_name()?.syntax().descendants().contains(name_ref.syntax()) { + Some((record_field, initializer)) + } else { + cov_mark::hit!(dont_overwrite_wrong_record_field); + None + } } /// Adds the definition of the new enum before the target node. @@ -561,6 +576,37 @@ fn main() { ) } + #[test] + fn local_variable_nested_in_negation() { + cov_mark::check!(dont_overwrite_expression_inside_negation); + check_assist( + bool_to_enum, + r#" +fn main() { + if !"foo".chars().any(|c| { + let $0foo = true; + foo + }) { + println!("foo"); + } +} +"#, + r#" +fn main() { + if !"foo".chars().any(|c| { + #[derive(PartialEq, Eq)] + enum Bool { True, False } + + let foo = Bool::True; + foo == Bool::True + }) { + println!("foo"); + } +} +"#, + ) + } + #[test] fn local_variable_non_bool() { cov_mark::check!(not_applicable_non_bool_local); @@ -638,6 +684,42 @@ fn main() { ) } + #[test] + fn field_negated() { + check_assist( + bool_to_enum, + r#" +struct Foo { + $0bar: bool, +} + +fn main() { + let foo = Foo { bar: false }; + + if !foo.bar { + println!("foo"); + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +struct Foo { + bar: Bool, +} + +fn main() { + let foo = Foo { bar: Bool::False }; + + if foo.bar == Bool::False { + println!("foo"); + } +} +"#, + ) + } + #[test] fn field_in_mod_properly_indented() { check_assist( @@ -714,6 +796,88 @@ fn main() { ) } + #[test] + fn field_assigned_to_another() { + cov_mark::check!(dont_assign_incorrect_ref); + check_assist( + bool_to_enum, + r#" +struct Foo { + $0foo: bool, +} + +struct Bar { + bar: bool, +} + +fn main() { + let foo = Foo { foo: true }; + let mut bar = Bar { bar: true }; + + bar.bar = foo.foo; +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +struct Foo { + foo: Bool, +} + +struct Bar { + bar: bool, +} + +fn main() { + let foo = Foo { foo: Bool::True }; + let mut bar = Bar { bar: true }; + + bar.bar = foo.foo == Bool::True; +} +"#, + ) + } + + #[test] + fn field_initialized_with_other() { + cov_mark::check!(dont_overwrite_wrong_record_field); + check_assist( + bool_to_enum, + r#" +struct Foo { + $0foo: bool, +} + +struct Bar { + bar: bool, +} + +fn main() { + let foo = Foo { foo: true }; + let bar = Bar { bar: foo.foo }; +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +struct Foo { + foo: Bool, +} + +struct Bar { + bar: bool, +} + +fn main() { + let foo = Foo { foo: Bool::True }; + let bar = Bar { bar: foo.foo == Bool::True }; +} +"#, + ) + } + #[test] fn field_non_bool() { cov_mark::check!(not_applicable_non_bool_field); From 5683df2965f9577ca1307f10af766aeecd6b560e Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Thu, 10 Aug 2023 01:18:05 +0200 Subject: [PATCH 0065/1762] Deunwrap inline call --- .../ide-assists/src/handlers/inline_call.rs | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/crates/ide-assists/src/handlers/inline_call.rs b/crates/ide-assists/src/handlers/inline_call.rs index ffab58509b182..b7787e4c33a41 100644 --- a/crates/ide-assists/src/handlers/inline_call.rs +++ b/crates/ide-assists/src/handlers/inline_call.rs @@ -116,7 +116,8 @@ pub(crate) fn inline_into_callers(acc: &mut Assists, ctx: &AssistContext<'_>) -> .into_iter() .map(|(call_info, mut_node)| { let replacement = - inline(&ctx.sema, def_file, function, &func_body, ¶ms, &call_info); + inline(&ctx.sema, def_file, function, &func_body, ¶ms, &call_info) + .unwrap(); ted::replace(mut_node, replacement.syntax()); }) .count(); @@ -218,13 +219,12 @@ pub(crate) fn inline_call(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< } let syntax = call_info.node.syntax().clone(); + let replacement = inline(&ctx.sema, file_id, function, &fn_body, ¶ms, &call_info)?; acc.add( AssistId("inline_call", AssistKind::RefactorInline), label, syntax.text_range(), |builder| { - let replacement = inline(&ctx.sema, file_id, function, &fn_body, ¶ms, &call_info); - builder.replace_ast( match call_info.node { ast::CallableExpr::Call(it) => ast::Expr::CallExpr(it), @@ -305,7 +305,7 @@ fn inline( fn_body: &ast::BlockExpr, params: &[(ast::Pat, Option, hir::Param)], CallInfo { node, arguments, generic_arg_list }: &CallInfo, -) -> ast::Expr { +) -> Option { let mut body = if sema.hir_file_for(fn_body.syntax()).is_macro() { cov_mark::hit!(inline_call_defined_in_macro); if let Some(body) = ast::BlockExpr::cast(insert_ws_into(fn_body.syntax().clone())) { @@ -363,16 +363,17 @@ fn inline( .collect(); if function.self_param(sema.db).is_some() { - let this = || make::name_ref("this").syntax().clone_for_update().first_token().unwrap(); + let this = || make::name_ref("this").syntax().clone_for_update().first_token(); if let Some(self_local) = params[0].2.as_local(sema.db) { - usages_for_locals(self_local) - .filter_map(|FileReference { name, range, .. }| match name { + let usages = usages_for_locals(self_local).filter_map( + |FileReference { name, range, .. }| match name { ast::NameLike::NameRef(_) => Some(body.syntax().covering_element(range)), _ => None, - }) - .for_each(|it| { - ted::replace(it, &this()); - }) + }, + ); + for usage in usages { + ted::replace(usage, &this()?); + } } } @@ -470,7 +471,7 @@ fn inline( } } else if let Some(stmt_list) = body.stmt_list() { ted::insert_all( - ted::Position::after(stmt_list.l_curly_token().unwrap()), + ted::Position::after(stmt_list.l_curly_token()?), let_stmts.into_iter().map(|stmt| stmt.syntax().clone().into()).collect(), ); } @@ -481,7 +482,7 @@ fn inline( }; body.reindent_to(original_indentation); - match body.tail_expr() { + Some(match body.tail_expr() { Some(expr) if !is_async_fn && body.statements().next().is_none() => expr, _ => match node .syntax() @@ -494,7 +495,7 @@ fn inline( } _ => ast::Expr::BlockExpr(body), }, - } + }) } fn path_expr_as_record_field(usage: &PathExpr) -> Option { From 68d24b69d45f357e7309c497f683c8b1e6ad691a Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Sat, 9 Sep 2023 14:35:26 +0200 Subject: [PATCH 0066/1762] Deunwrap inline call v2 --- .../ide-assists/src/handlers/inline_call.rs | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/crates/ide-assists/src/handlers/inline_call.rs b/crates/ide-assists/src/handlers/inline_call.rs index b7787e4c33a41..4751de3125595 100644 --- a/crates/ide-assists/src/handlers/inline_call.rs +++ b/crates/ide-assists/src/handlers/inline_call.rs @@ -117,7 +117,7 @@ pub(crate) fn inline_into_callers(acc: &mut Assists, ctx: &AssistContext<'_>) -> .map(|(call_info, mut_node)| { let replacement = inline(&ctx.sema, def_file, function, &func_body, ¶ms, &call_info) - .unwrap(); + .expect("inline() should return an Expr"); ted::replace(mut_node, replacement.syntax()); }) .count(); @@ -363,17 +363,23 @@ fn inline( .collect(); if function.self_param(sema.db).is_some() { - let this = || make::name_ref("this").syntax().clone_for_update().first_token(); + let this = || { + make::name_ref("this") + .syntax() + .clone_for_update() + .first_token() + .expect("NameRef should have had a token.") + }; if let Some(self_local) = params[0].2.as_local(sema.db) { - let usages = usages_for_locals(self_local).filter_map( - |FileReference { name, range, .. }| match name { + usages_for_locals(self_local) + .filter_map(|FileReference { name, range, .. }| match name { ast::NameLike::NameRef(_) => Some(body.syntax().covering_element(range)), _ => None, - }, - ); - for usage in usages { - ted::replace(usage, &this()?); - } + }) + .into_iter() + .for_each(|usage| { + ted::replace(usage, &this()); + }); } } @@ -471,7 +477,9 @@ fn inline( } } else if let Some(stmt_list) = body.stmt_list() { ted::insert_all( - ted::Position::after(stmt_list.l_curly_token()?), + ted::Position::after( + stmt_list.l_curly_token().expect("L_CURLY for StatementList is missing."), + ), let_stmts.into_iter().map(|stmt| stmt.syntax().clone().into()).collect(), ); } From 38491fcf0785a33720ccfd746d2ebe2ad2686207 Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Sun, 10 Sep 2023 22:14:58 +0200 Subject: [PATCH 0067/1762] v3 --- crates/ide-assists/src/handlers/inline_call.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/crates/ide-assists/src/handlers/inline_call.rs b/crates/ide-assists/src/handlers/inline_call.rs index 4751de3125595..9b4ac8a02a350 100644 --- a/crates/ide-assists/src/handlers/inline_call.rs +++ b/crates/ide-assists/src/handlers/inline_call.rs @@ -116,8 +116,7 @@ pub(crate) fn inline_into_callers(acc: &mut Assists, ctx: &AssistContext<'_>) -> .into_iter() .map(|(call_info, mut_node)| { let replacement = - inline(&ctx.sema, def_file, function, &func_body, ¶ms, &call_info) - .expect("inline() should return an Expr"); + inline(&ctx.sema, def_file, function, &func_body, ¶ms, &call_info); ted::replace(mut_node, replacement.syntax()); }) .count(); @@ -219,7 +218,7 @@ pub(crate) fn inline_call(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< } let syntax = call_info.node.syntax().clone(); - let replacement = inline(&ctx.sema, file_id, function, &fn_body, ¶ms, &call_info)?; + let replacement = inline(&ctx.sema, file_id, function, &fn_body, ¶ms, &call_info); acc.add( AssistId("inline_call", AssistKind::RefactorInline), label, @@ -305,7 +304,7 @@ fn inline( fn_body: &ast::BlockExpr, params: &[(ast::Pat, Option, hir::Param)], CallInfo { node, arguments, generic_arg_list }: &CallInfo, -) -> Option { +) -> ast::Expr { let mut body = if sema.hir_file_for(fn_body.syntax()).is_macro() { cov_mark::hit!(inline_call_defined_in_macro); if let Some(body) = ast::BlockExpr::cast(insert_ws_into(fn_body.syntax().clone())) { @@ -376,7 +375,6 @@ fn inline( ast::NameLike::NameRef(_) => Some(body.syntax().covering_element(range)), _ => None, }) - .into_iter() .for_each(|usage| { ted::replace(usage, &this()); }); @@ -490,7 +488,7 @@ fn inline( }; body.reindent_to(original_indentation); - Some(match body.tail_expr() { + match body.tail_expr() { Some(expr) if !is_async_fn && body.statements().next().is_none() => expr, _ => match node .syntax() @@ -503,7 +501,7 @@ fn inline( } _ => ast::Expr::BlockExpr(body), }, - }) + } } fn path_expr_as_record_field(usage: &PathExpr) -> Option { From 9c6257138de5f093b2dd03893aa694b6165ea157 Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Wed, 9 Aug 2023 17:09:57 +0200 Subject: [PATCH 0068/1762] Deunwrap convert_comment_block --- .../src/handlers/convert_comment_block.rs | 29 +++++++++------- .../src/handlers/desugar_doc_comment.rs | 33 +++++++++++-------- 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/crates/ide-assists/src/handlers/convert_comment_block.rs b/crates/ide-assists/src/handlers/convert_comment_block.rs index 1acd5ee97283f..b6ad2dc0b65df 100644 --- a/crates/ide-assists/src/handlers/convert_comment_block.rs +++ b/crates/ide-assists/src/handlers/convert_comment_block.rs @@ -78,21 +78,26 @@ fn line_to_block(acc: &mut Assists, comment: ast::Comment) -> Option<()> { // Establish the target of our edit based on the comments we found let target = TextRange::new( comments[0].syntax().text_range().start(), - comments.last().unwrap().syntax().text_range().end(), + comments.last()?.syntax().text_range().end(), ); + // We pick a single indentation level for the whole block comment based on the + // comment where the assist was invoked. This will be prepended to the + // contents of each line comment when they're put into the block comment. + let indentation = IndentLevel::from_token(comment.syntax()); + + let mut cms: Vec = Vec::new(); + for cm in comments { + let lcm = line_comment_text(indentation, cm)?; + cms.push(lcm); + } + acc.add( AssistId("line_to_block", AssistKind::RefactorRewrite), "Replace line comments with a single block comment", target, |edit| { - // We pick a single indentation level for the whole block comment based on the - // comment where the assist was invoked. This will be prepended to the - // contents of each line comment when they're put into the block comment. - let indentation = IndentLevel::from_token(comment.syntax()); - - let block_comment_body = - comments.into_iter().map(|c| line_comment_text(indentation, c)).join("\n"); + let block_comment_body = cms.into_iter().join("\n"); let block_prefix = CommentKind { shape: CommentShape::Block, ..comment.kind() }.prefix(); @@ -159,15 +164,15 @@ pub(crate) fn relevant_line_comments(comment: &ast::Comment) -> Vec { // */ // // But since such comments aren't idiomatic we're okay with this. -pub(crate) fn line_comment_text(indentation: IndentLevel, comm: ast::Comment) -> String { - let contents_without_prefix = comm.text().strip_prefix(comm.prefix()).unwrap(); +pub(crate) fn line_comment_text(indentation: IndentLevel, comm: ast::Comment) -> Option { + let contents_without_prefix = comm.text().strip_prefix(comm.prefix())?; let contents = contents_without_prefix.strip_prefix(' ').unwrap_or(contents_without_prefix); // Don't add the indentation if the line is empty if contents.is_empty() { - contents.to_owned() + Some(contents.to_owned()) } else { - indentation.to_string() + contents + Some(indentation.to_string() + contents) } } diff --git a/crates/ide-assists/src/handlers/desugar_doc_comment.rs b/crates/ide-assists/src/handlers/desugar_doc_comment.rs index ddc8a50ed4006..daa2c1df0cc48 100644 --- a/crates/ide-assists/src/handlers/desugar_doc_comment.rs +++ b/crates/ide-assists/src/handlers/desugar_doc_comment.rs @@ -57,25 +57,30 @@ pub(crate) fn desugar_doc_comment(acc: &mut Assists, ctx: &AssistContext<'_>) -> } }; + let text = match comments { + Either::Left(comment) => { + let text = comment.text(); + text[comment.prefix().len()..(text.len() - "*/".len())] + .trim() + .lines() + .map(|l| l.strip_prefix(&indentation).unwrap_or(l)) + .join("\n") + } + Either::Right(comments) => { + let mut cms: Vec = Vec::new(); + for cm in comments { + let lcm = line_comment_text(IndentLevel(0), cm)?; + cms.push(lcm); + } + cms.into_iter().join("\n") + } + }; + acc.add( AssistId("desugar_doc_comment", AssistKind::RefactorRewrite), "Desugar doc-comment to attribute macro", target, |edit| { - let text = match comments { - Either::Left(comment) => { - let text = comment.text(); - text[comment.prefix().len()..(text.len() - "*/".len())] - .trim() - .lines() - .map(|l| l.strip_prefix(&indentation).unwrap_or(l)) - .join("\n") - } - Either::Right(comments) => { - comments.into_iter().map(|c| line_comment_text(IndentLevel(0), c)).join("\n") - } - }; - let hashes = "#".repeat(required_hashes(&text)); let prefix = match placement { From b316bccc9716dc058120b4cf73c753973e2cd802 Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Wed, 9 Aug 2023 23:34:30 +0200 Subject: [PATCH 0069/1762] replace for loops with sth more idiomatic --- .../src/handlers/convert_comment_block.rs | 9 ++++----- .../src/handlers/desugar_doc_comment.rs | 15 ++++++--------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/crates/ide-assists/src/handlers/convert_comment_block.rs b/crates/ide-assists/src/handlers/convert_comment_block.rs index b6ad2dc0b65df..4cbb30ec15c5e 100644 --- a/crates/ide-assists/src/handlers/convert_comment_block.rs +++ b/crates/ide-assists/src/handlers/convert_comment_block.rs @@ -86,11 +86,10 @@ fn line_to_block(acc: &mut Assists, comment: ast::Comment) -> Option<()> { // contents of each line comment when they're put into the block comment. let indentation = IndentLevel::from_token(comment.syntax()); - let mut cms: Vec = Vec::new(); - for cm in comments { - let lcm = line_comment_text(indentation, cm)?; - cms.push(lcm); - } + let cms = comments + .into_iter() + .map(|c| line_comment_text(indentation, c)) + .collect::>>()?; acc.add( AssistId("line_to_block", AssistKind::RefactorRewrite), diff --git a/crates/ide-assists/src/handlers/desugar_doc_comment.rs b/crates/ide-assists/src/handlers/desugar_doc_comment.rs index daa2c1df0cc48..2f8cef1e4a7a0 100644 --- a/crates/ide-assists/src/handlers/desugar_doc_comment.rs +++ b/crates/ide-assists/src/handlers/desugar_doc_comment.rs @@ -50,7 +50,7 @@ pub(crate) fn desugar_doc_comment(acc: &mut Assists, ctx: &AssistContext<'_>) -> ( TextRange::new( comments[0].syntax().text_range().start(), - comments.last().unwrap().syntax().text_range().end(), + comments.last()?.syntax().text_range().end(), ), Either::Right(comments), ) @@ -66,14 +66,11 @@ pub(crate) fn desugar_doc_comment(acc: &mut Assists, ctx: &AssistContext<'_>) -> .map(|l| l.strip_prefix(&indentation).unwrap_or(l)) .join("\n") } - Either::Right(comments) => { - let mut cms: Vec = Vec::new(); - for cm in comments { - let lcm = line_comment_text(IndentLevel(0), cm)?; - cms.push(lcm); - } - cms.into_iter().join("\n") - } + Either::Right(comments) => comments + .into_iter() + .map(|cm| line_comment_text(IndentLevel(0), cm)) + .collect::>>()? + .join("\n"), }; acc.add( From a66dbd11ed1501c0efbea265f76bc92147554ab7 Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Tue, 15 Aug 2023 19:29:09 +0200 Subject: [PATCH 0070/1762] v2 --- crates/ide-assists/src/handlers/convert_comment_block.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/ide-assists/src/handlers/convert_comment_block.rs b/crates/ide-assists/src/handlers/convert_comment_block.rs index 4cbb30ec15c5e..a850d28e918fa 100644 --- a/crates/ide-assists/src/handlers/convert_comment_block.rs +++ b/crates/ide-assists/src/handlers/convert_comment_block.rs @@ -164,7 +164,8 @@ pub(crate) fn relevant_line_comments(comment: &ast::Comment) -> Vec { // // But since such comments aren't idiomatic we're okay with this. pub(crate) fn line_comment_text(indentation: IndentLevel, comm: ast::Comment) -> Option { - let contents_without_prefix = comm.text().strip_prefix(comm.prefix())?; + let text = comm.text(); + let contents_without_prefix = text.strip_prefix(comm.prefix()).unwrap_or(text); let contents = contents_without_prefix.strip_prefix(' ').unwrap_or(contents_without_prefix); // Don't add the indentation if the line is empty From 2fdf7e4b752c8ac50585879163a3aebccfa82d4f Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Sun, 10 Sep 2023 23:15:37 +0200 Subject: [PATCH 0071/1762] v3 --- .../src/handlers/convert_comment_block.rs | 16 ++++++---------- .../src/handlers/desugar_doc_comment.rs | 6 ++---- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/crates/ide-assists/src/handlers/convert_comment_block.rs b/crates/ide-assists/src/handlers/convert_comment_block.rs index a850d28e918fa..1f048ac10fa55 100644 --- a/crates/ide-assists/src/handlers/convert_comment_block.rs +++ b/crates/ide-assists/src/handlers/convert_comment_block.rs @@ -25,9 +25,7 @@ pub(crate) fn convert_comment_block(acc: &mut Assists, ctx: &AssistContext<'_>) let comment = ctx.find_token_at_offset::()?; // Only allow comments which are alone on their line if let Some(prev) = comment.syntax().prev_token() { - if Whitespace::cast(prev).filter(|w| w.text().contains('\n')).is_none() { - return None; - } + Whitespace::cast(prev).filter(|w| w.text().contains('\n'))?; } match comment.kind().shape { @@ -86,10 +84,8 @@ fn line_to_block(acc: &mut Assists, comment: ast::Comment) -> Option<()> { // contents of each line comment when they're put into the block comment. let indentation = IndentLevel::from_token(comment.syntax()); - let cms = comments - .into_iter() - .map(|c| line_comment_text(indentation, c)) - .collect::>>()?; + let cms = + comments.into_iter().map(|c| line_comment_text(indentation, c)).collect::>(); acc.add( AssistId("line_to_block", AssistKind::RefactorRewrite), @@ -163,16 +159,16 @@ pub(crate) fn relevant_line_comments(comment: &ast::Comment) -> Vec { // */ // // But since such comments aren't idiomatic we're okay with this. -pub(crate) fn line_comment_text(indentation: IndentLevel, comm: ast::Comment) -> Option { +pub(crate) fn line_comment_text(indentation: IndentLevel, comm: ast::Comment) -> String { let text = comm.text(); let contents_without_prefix = text.strip_prefix(comm.prefix()).unwrap_or(text); let contents = contents_without_prefix.strip_prefix(' ').unwrap_or(contents_without_prefix); // Don't add the indentation if the line is empty if contents.is_empty() { - Some(contents.to_owned()) + contents.to_owned() } else { - Some(indentation.to_string() + contents) + indentation.to_string() + contents } } diff --git a/crates/ide-assists/src/handlers/desugar_doc_comment.rs b/crates/ide-assists/src/handlers/desugar_doc_comment.rs index 2f8cef1e4a7a0..b7919bd1502cb 100644 --- a/crates/ide-assists/src/handlers/desugar_doc_comment.rs +++ b/crates/ide-assists/src/handlers/desugar_doc_comment.rs @@ -33,9 +33,7 @@ pub(crate) fn desugar_doc_comment(acc: &mut Assists, ctx: &AssistContext<'_>) -> // Only allow comments which are alone on their line if let Some(prev) = comment.syntax().prev_token() { - if Whitespace::cast(prev).filter(|w| w.text().contains('\n')).is_none() { - return None; - } + Whitespace::cast(prev).filter(|w| w.text().contains('\n'))?; } let indentation = IndentLevel::from_token(comment.syntax()).to_string(); @@ -69,7 +67,7 @@ pub(crate) fn desugar_doc_comment(acc: &mut Assists, ctx: &AssistContext<'_>) -> Either::Right(comments) => comments .into_iter() .map(|cm| line_comment_text(IndentLevel(0), cm)) - .collect::>>()? + .collect::>() .join("\n"), }; From 0a1e7453204383a109f4854cc588fd3b35c990c6 Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Sun, 10 Sep 2023 18:20:47 -0400 Subject: [PATCH 0072/1762] Return unsigned integers from some signed integer functions --- crates/core_simd/src/elements/int.rs | 37 +++++++++++++++------------- crates/core_simd/tests/ops_macros.rs | 8 +++--- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/crates/core_simd/src/elements/int.rs b/crates/core_simd/src/elements/int.rs index e81ed7bf601a1..c341c59545c85 100644 --- a/crates/core_simd/src/elements/int.rs +++ b/crates/core_simd/src/elements/int.rs @@ -1,6 +1,7 @@ use super::sealed::Sealed; use crate::simd::{ - intrinsics, LaneCount, Mask, Simd, SimdCast, SimdElement, SimdPartialOrd, SupportedLaneCount, + intrinsics, LaneCount, Mask, Simd, SimdCast, SimdElement, SimdPartialOrd, SimdUint, + SupportedLaneCount, }; /// Operations on SIMD vectors of signed integers. @@ -11,6 +12,9 @@ pub trait SimdInt: Copy + Sealed { /// Scalar type contained by this SIMD vector type. type Scalar; + /// A SIMD vector of unsigned integers with the same element size. + type Unsigned; + /// A SIMD vector with a different element type. type Cast; @@ -200,20 +204,20 @@ pub trait SimdInt: Copy + Sealed { fn reverse_bits(self) -> Self; /// Returns the number of leading zeros in the binary representation of each element. - fn leading_zeros(self) -> Self; + fn leading_zeros(self) -> Self::Unsigned; /// Returns the number of trailing zeros in the binary representation of each element. - fn trailing_zeros(self) -> Self; + fn trailing_zeros(self) -> Self::Unsigned; /// Returns the number of leading ones in the binary representation of each element. - fn leading_ones(self) -> Self; + fn leading_ones(self) -> Self::Unsigned; /// Returns the number of trailing ones in the binary representation of each element. - fn trailing_ones(self) -> Self; + fn trailing_ones(self) -> Self::Unsigned; } macro_rules! impl_trait { - { $($ty:ty),* } => { + { $($ty:ident ($unsigned:ident)),* } => { $( impl Sealed for Simd<$ty, LANES> where @@ -227,6 +231,7 @@ macro_rules! impl_trait { { type Mask = Mask<<$ty as SimdElement>::Mask, LANES>; type Scalar = $ty; + type Unsigned = Simd<$unsigned, LANES>; type Cast = Simd; #[inline] @@ -340,29 +345,27 @@ macro_rules! impl_trait { } #[inline] - fn leading_zeros(self) -> Self { - // Safety: `self` is an integer vector - unsafe { intrinsics::simd_ctlz(self) } + fn leading_zeros(self) -> Self::Unsigned { + self.cast::<$unsigned>().leading_zeros() } #[inline] - fn trailing_zeros(self) -> Self { - // Safety: `self` is an integer vector - unsafe { intrinsics::simd_cttz(self) } + fn trailing_zeros(self) -> Self::Unsigned { + self.cast::<$unsigned>().trailing_zeros() } #[inline] - fn leading_ones(self) -> Self { - (!self).leading_zeros() + fn leading_ones(self) -> Self::Unsigned { + self.cast::<$unsigned>().leading_ones() } #[inline] - fn trailing_ones(self) -> Self { - (!self).trailing_zeros() + fn trailing_ones(self) -> Self::Unsigned { + self.cast::<$unsigned>().trailing_ones() } } )* } } -impl_trait! { i8, i16, i32, i64, isize } +impl_trait! { i8 (u8), i16 (u16), i32 (u32), i64 (u64), isize (usize) } diff --git a/crates/core_simd/tests/ops_macros.rs b/crates/core_simd/tests/ops_macros.rs index 23e914e64b507..135f3ecf7b2c1 100644 --- a/crates/core_simd/tests/ops_macros.rs +++ b/crates/core_simd/tests/ops_macros.rs @@ -213,7 +213,7 @@ macro_rules! impl_common_integer_tests { fn leading_zeros() { test_helpers::test_unary_elementwise( &$vector::::leading_zeros, - &|x| x.leading_zeros() as $scalar, + &|x| x.leading_zeros() as _, &|_| true, ) } @@ -221,7 +221,7 @@ macro_rules! impl_common_integer_tests { fn trailing_zeros() { test_helpers::test_unary_elementwise( &$vector::::trailing_zeros, - &|x| x.trailing_zeros() as $scalar, + &|x| x.trailing_zeros() as _, &|_| true, ) } @@ -229,7 +229,7 @@ macro_rules! impl_common_integer_tests { fn leading_ones() { test_helpers::test_unary_elementwise( &$vector::::leading_ones, - &|x| x.leading_ones() as $scalar, + &|x| x.leading_ones() as _, &|_| true, ) } @@ -237,7 +237,7 @@ macro_rules! impl_common_integer_tests { fn trailing_ones() { test_helpers::test_unary_elementwise( &$vector::::trailing_ones, - &|x| x.trailing_ones() as $scalar, + &|x| x.trailing_ones() as _, &|_| true, ) } From 25b1b3e753c4cb6fa75b2004c8d753daf240b1c6 Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Sun, 10 Sep 2023 22:21:12 -0700 Subject: [PATCH 0073/1762] feat: add support for other ADT types and destructuring patterns --- .../ide-assists/src/handlers/bool_to_enum.rs | 494 +++++++++++++++--- 1 file changed, 430 insertions(+), 64 deletions(-) diff --git a/crates/ide-assists/src/handlers/bool_to_enum.rs b/crates/ide-assists/src/handlers/bool_to_enum.rs index 784a0d3559970..b9dbd6e98fcbf 100644 --- a/crates/ide-assists/src/handlers/bool_to_enum.rs +++ b/crates/ide-assists/src/handlers/bool_to_enum.rs @@ -6,6 +6,7 @@ use ide_db::{ imports::insert_use::{insert_use, ImportScope}, search::{FileReference, UsageSearchResult}, source_change::SourceChangeBuilder, + FxHashSet, }; use itertools::Itertools; use syntax::{ @@ -17,6 +18,7 @@ use syntax::{ }, ted, AstNode, NodeOrToken, SyntaxNode, T, }; +use text_edit::TextRange; use crate::assist_context::{AssistContext, Assists}; @@ -52,7 +54,7 @@ use crate::assist_context::{AssistContext, Assists}; pub(crate) fn bool_to_enum(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let BoolNodeData { target_node, name, ty_annotation, initializer, definition } = find_bool_node(ctx)?; - let target_module = ctx.sema.scope(&target_node)?.module(); + let target_module = ctx.sema.scope(&target_node)?.module().nearest_non_block_module(ctx.db()); let target = name.syntax().text_range(); acc.add( @@ -70,9 +72,8 @@ pub(crate) fn bool_to_enum(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option } let usages = definition.usages(&ctx.sema).all(); - add_enum_def(edit, ctx, &usages, target_node, &target_module); - replace_usages(edit, ctx, &usages, &target_module); + replace_usages(edit, ctx, &usages, definition, &target_module); }, ) } @@ -144,14 +145,14 @@ fn find_bool_node(ctx: &AssistContext<'_>) -> Option { return None; } - let strukt = field.syntax().ancestors().find_map(ast::Struct::cast)?; + let adt = field.syntax().ancestors().find_map(ast::Adt::cast)?; let def = ctx.sema.to_def(&field)?; if !def.ty(ctx.db()).is_bool() { cov_mark::hit!(not_applicable_non_bool_field); return None; } Some(BoolNodeData { - target_node: strukt.syntax().clone(), + target_node: adt.syntax().clone(), name, ty_annotation: field.ty(), initializer: None, @@ -192,78 +193,171 @@ fn replace_usages( edit: &mut SourceChangeBuilder, ctx: &AssistContext<'_>, usages: &UsageSearchResult, + target_definition: Definition, target_module: &hir::Module, ) { for (file_id, references) in usages.iter() { edit.edit_file(*file_id); - // add imports across modules where needed - references - .iter() - .filter_map(|FileReference { name, .. }| { - ctx.sema.scope(name.syntax()).map(|scope| (name, scope.module())) - }) - .unique_by(|name_and_module| name_and_module.1) - .filter(|(_, module)| module != target_module) - .filter_map(|(name, module)| { - let import_scope = ImportScope::find_insert_use_container(name.syntax(), &ctx.sema); - let mod_path = module.find_use_path_prefixed( - ctx.sema.db, - ModuleDef::Module(*target_module), - ctx.config.insert_use.prefix_kind, - ctx.config.prefer_no_std, - ); - import_scope.zip(mod_path) - }) - .for_each(|(import_scope, mod_path)| { - let import_scope = match import_scope { - ImportScope::File(it) => ImportScope::File(edit.make_mut(it)), - ImportScope::Module(it) => ImportScope::Module(edit.make_mut(it)), - ImportScope::Block(it) => ImportScope::Block(edit.make_mut(it)), - }; - let path = - make::path_concat(mod_path_to_ast(&mod_path), make::path_from_text("Bool")); - insert_use(&import_scope, path, &ctx.config.insert_use); - }); - - // replace the usages in expressions - references - .into_iter() - .filter_map(|FileReference { range, name, .. }| match name { - ast::NameLike::NameRef(name) => Some((*range, name)), - _ => None, - }) - .rev() - .for_each(|(range, name_ref)| { - if let Some(initializer) = find_assignment_usage(name_ref) { + let refs_with_imports = + augment_references_with_imports(edit, ctx, references, target_module); + + refs_with_imports.into_iter().rev().for_each( + |FileReferenceWithImport { range, old_name, new_name, import_data }| { + // replace the usages in patterns and expressions + if let Some(ident_pat) = old_name.syntax().ancestors().find_map(ast::IdentPat::cast) + { + cov_mark::hit!(replaces_record_pat_shorthand); + + let definition = ctx.sema.to_def(&ident_pat).map(Definition::Local); + if let Some(def) = definition { + replace_usages( + edit, + ctx, + &def.usages(&ctx.sema).all(), + target_definition, + target_module, + ) + } + } else if let Some(initializer) = find_assignment_usage(&new_name) { cov_mark::hit!(replaces_assignment); replace_bool_expr(edit, initializer); - } else if let Some((prefix_expr, inner_expr)) = find_negated_usage(name_ref) { + } else if let Some((prefix_expr, inner_expr)) = find_negated_usage(&new_name) { cov_mark::hit!(replaces_negation); edit.replace( prefix_expr.syntax().text_range(), format!("{} == Bool::False", inner_expr), ); - } else if let Some((record_field, initializer)) = find_record_expr_usage(name_ref) { + } else if let Some((record_field, initializer)) = old_name + .as_name_ref() + .and_then(ast::RecordExprField::for_field_name) + .and_then(|record_field| ctx.sema.resolve_record_field(&record_field)) + .and_then(|(got_field, _, _)| { + find_record_expr_usage(&new_name, got_field, target_definition) + }) + { cov_mark::hit!(replaces_record_expr); let record_field = edit.make_mut(record_field); let enum_expr = bool_expr_to_enum_expr(initializer); record_field.replace_expr(enum_expr); - } else if name_ref.syntax().ancestors().find_map(ast::UseTree::cast).is_none() { + } else if let Some(pat) = find_record_pat_field_usage(&old_name) { + match pat { + ast::Pat::IdentPat(ident_pat) => { + cov_mark::hit!(replaces_record_pat); + + let definition = ctx.sema.to_def(&ident_pat).map(Definition::Local); + if let Some(def) = definition { + replace_usages( + edit, + ctx, + &def.usages(&ctx.sema).all(), + target_definition, + target_module, + ) + } + } + ast::Pat::LiteralPat(literal_pat) => { + cov_mark::hit!(replaces_literal_pat); + + if let Some(expr) = literal_pat.literal().and_then(|literal| { + literal.syntax().ancestors().find_map(ast::Expr::cast) + }) { + replace_bool_expr(edit, expr); + } + } + _ => (), + } + } else if new_name.syntax().ancestors().find_map(ast::UseTree::cast).is_none() { // for any other usage in an expression, replace it with a check that it is the true variant - edit.replace(range, format!("{} == Bool::True", name_ref.text())); + if let Some((record_field, expr)) = new_name + .as_name_ref() + .and_then(ast::RecordExprField::for_field_name) + .and_then(|record_field| { + record_field.expr().map(|expr| (record_field, expr)) + }) + { + record_field.replace_expr( + make::expr_bin_op( + expr, + ast::BinaryOp::CmpOp(ast::CmpOp::Eq { negated: false }), + make::expr_path(make::path_from_text("Bool::True")), + ) + .clone_for_update(), + ); + } else { + edit.replace(range, format!("{} == Bool::True", new_name.text())); + } + } + + // add imports across modules where needed + if let Some((import_scope, path)) = import_data { + insert_use(&import_scope, path, &ctx.config.insert_use); } - }) + }, + ) } } -fn find_assignment_usage(name_ref: &ast::NameRef) -> Option { - let bin_expr = name_ref.syntax().ancestors().find_map(ast::BinExpr::cast)?; +struct FileReferenceWithImport { + range: TextRange, + old_name: ast::NameLike, + new_name: ast::NameLike, + import_data: Option<(ImportScope, ast::Path)>, +} - if !bin_expr.lhs()?.syntax().descendants().contains(name_ref.syntax()) { +fn augment_references_with_imports( + edit: &mut SourceChangeBuilder, + ctx: &AssistContext<'_>, + references: &[FileReference], + target_module: &hir::Module, +) -> Vec { + let mut visited_modules = FxHashSet::default(); + + references + .iter() + .filter_map(|FileReference { range, name, .. }| { + ctx.sema.scope(name.syntax()).map(|scope| (*range, name, scope.module())) + }) + .map(|(range, name, ref_module)| { + let old_name = name.clone(); + let new_name = edit.make_mut(name.clone()); + + // if the referenced module is not the same as the target one and has not been seen before, add an import + let import_data = if ref_module.nearest_non_block_module(ctx.db()) != *target_module + && !visited_modules.contains(&ref_module) + { + visited_modules.insert(ref_module); + + let import_scope = + ImportScope::find_insert_use_container(new_name.syntax(), &ctx.sema); + let path = ref_module + .find_use_path_prefixed( + ctx.sema.db, + ModuleDef::Module(*target_module), + ctx.config.insert_use.prefix_kind, + ctx.config.prefer_no_std, + ) + .map(|mod_path| { + make::path_concat(mod_path_to_ast(&mod_path), make::path_from_text("Bool")) + }); + + import_scope.zip(path) + } else { + None + }; + + FileReferenceWithImport { range, old_name, new_name, import_data } + }) + .collect() +} + +fn find_assignment_usage(name: &ast::NameLike) -> Option { + let bin_expr = name.syntax().ancestors().find_map(ast::BinExpr::cast)?; + + if !bin_expr.lhs()?.syntax().descendants().contains(name.syntax()) { cov_mark::hit!(dont_assign_incorrect_ref); return None; } @@ -275,8 +369,8 @@ fn find_assignment_usage(name_ref: &ast::NameRef) -> Option { } } -fn find_negated_usage(name_ref: &ast::NameRef) -> Option<(ast::PrefixExpr, ast::Expr)> { - let prefix_expr = name_ref.syntax().ancestors().find_map(ast::PrefixExpr::cast)?; +fn find_negated_usage(name: &ast::NameLike) -> Option<(ast::PrefixExpr, ast::Expr)> { + let prefix_expr = name.syntax().ancestors().find_map(ast::PrefixExpr::cast)?; if !matches!(prefix_expr.expr()?, ast::Expr::PathExpr(_) | ast::Expr::FieldExpr(_)) { cov_mark::hit!(dont_overwrite_expression_inside_negation); @@ -291,15 +385,31 @@ fn find_negated_usage(name_ref: &ast::NameRef) -> Option<(ast::PrefixExpr, ast:: } } -fn find_record_expr_usage(name_ref: &ast::NameRef) -> Option<(ast::RecordExprField, ast::Expr)> { - let record_field = name_ref.syntax().ancestors().find_map(ast::RecordExprField::cast)?; +fn find_record_expr_usage( + name: &ast::NameLike, + got_field: hir::Field, + target_definition: Definition, +) -> Option<(ast::RecordExprField, ast::Expr)> { + let name_ref = name.as_name_ref()?; + let record_field = ast::RecordExprField::for_field_name(name_ref)?; let initializer = record_field.expr()?; - if record_field.field_name()?.syntax().descendants().contains(name_ref.syntax()) { - Some((record_field, initializer)) - } else { - cov_mark::hit!(dont_overwrite_wrong_record_field); - None + if let Definition::Field(expected_field) = target_definition { + if got_field != expected_field { + return None; + } + } + + Some((record_field, initializer)) +} + +fn find_record_pat_field_usage(name: &ast::NameLike) -> Option { + let record_pat_field = name.syntax().parent().and_then(ast::RecordPatField::cast)?; + let pat = record_pat_field.pat()?; + + match pat { + ast::Pat::IdentPat(_) | ast::Pat::LiteralPat(_) | ast::Pat::WildcardPat(_) => Some(pat), + _ => None, } } @@ -317,7 +427,7 @@ fn add_enum_def( .filter_map(|FileReference { name, .. }| { ctx.sema.scope(name.syntax()).map(|scope| scope.module()) }) - .any(|module| &module != target_module); + .any(|module| module.nearest_non_block_module(ctx.db()) != *target_module); let enum_def = make_bool_enum(make_enum_pub); let indent = IndentLevel::from_node(&target_node); @@ -646,7 +756,7 @@ fn main() { } #[test] - fn field_basic() { + fn field_struct_basic() { cov_mark::check!(replaces_record_expr); check_assist( bool_to_enum, @@ -684,6 +794,263 @@ fn main() { ) } + #[test] + fn field_enum_basic() { + cov_mark::check!(replaces_record_pat); + check_assist( + bool_to_enum, + r#" +enum Foo { + Foo, + Bar { $0bar: bool }, +} + +fn main() { + let foo = Foo::Bar { bar: true }; + + if let Foo::Bar { bar: baz } = foo { + if baz { + println!("foo"); + } + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +enum Foo { + Foo, + Bar { bar: Bool }, +} + +fn main() { + let foo = Foo::Bar { bar: Bool::True }; + + if let Foo::Bar { bar: baz } = foo { + if baz == Bool::True { + println!("foo"); + } + } +} +"#, + ) + } + + #[test] + fn field_enum_cross_file() { + check_assist( + bool_to_enum, + r#" +//- /foo.rs +pub enum Foo { + Foo, + Bar { $0bar: bool }, +} + +fn foo() { + let foo = Foo::Bar { bar: true }; +} + +//- /main.rs +use foo::Foo; + +mod foo; + +fn main() { + let foo = Foo::Bar { bar: false }; +} +"#, + r#" +//- /foo.rs +#[derive(PartialEq, Eq)] +pub enum Bool { True, False } + +pub enum Foo { + Foo, + Bar { bar: Bool }, +} + +fn foo() { + let foo = Foo::Bar { bar: Bool::True }; +} + +//- /main.rs +use foo::{Foo, Bool}; + +mod foo; + +fn main() { + let foo = Foo::Bar { bar: Bool::False }; +} +"#, + ) + } + + #[test] + fn field_enum_shorthand() { + cov_mark::check!(replaces_record_pat_shorthand); + check_assist( + bool_to_enum, + r#" +enum Foo { + Foo, + Bar { $0bar: bool }, +} + +fn main() { + let foo = Foo::Bar { bar: true }; + + match foo { + Foo::Bar { bar } => { + if bar { + println!("foo"); + } + } + _ => (), + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +enum Foo { + Foo, + Bar { bar: Bool }, +} + +fn main() { + let foo = Foo::Bar { bar: Bool::True }; + + match foo { + Foo::Bar { bar } => { + if bar == Bool::True { + println!("foo"); + } + } + _ => (), + } +} +"#, + ) + } + + #[test] + fn field_enum_replaces_literal_patterns() { + cov_mark::check!(replaces_literal_pat); + check_assist( + bool_to_enum, + r#" +enum Foo { + Foo, + Bar { $0bar: bool }, +} + +fn main() { + let foo = Foo::Bar { bar: true }; + + if let Foo::Bar { bar: true } = foo { + println!("foo"); + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +enum Foo { + Foo, + Bar { bar: Bool }, +} + +fn main() { + let foo = Foo::Bar { bar: Bool::True }; + + if let Foo::Bar { bar: Bool::True } = foo { + println!("foo"); + } +} +"#, + ) + } + + #[test] + fn field_enum_keeps_wildcard_patterns() { + check_assist( + bool_to_enum, + r#" +enum Foo { + Foo, + Bar { $0bar: bool }, +} + +fn main() { + let foo = Foo::Bar { bar: true }; + + if let Foo::Bar { bar: _ } = foo { + println!("foo"); + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +enum Foo { + Foo, + Bar { bar: Bool }, +} + +fn main() { + let foo = Foo::Bar { bar: Bool::True }; + + if let Foo::Bar { bar: _ } = foo { + println!("foo"); + } +} +"#, + ) + } + + #[test] + fn field_union_basic() { + check_assist( + bool_to_enum, + r#" +union Foo { + $0foo: bool, + bar: usize, +} + +fn main() { + let foo = Foo { foo: true }; + + if unsafe { foo.foo } { + println!("foo"); + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +union Foo { + foo: Bool, + bar: usize, +} + +fn main() { + let foo = Foo { foo: Bool::True }; + + if unsafe { foo.foo == Bool::True } { + println!("foo"); + } +} +"#, + ) + } + #[test] fn field_negated() { check_assist( @@ -841,7 +1208,6 @@ fn main() { #[test] fn field_initialized_with_other() { - cov_mark::check!(dont_overwrite_wrong_record_field); check_assist( bool_to_enum, r#" From 0863024b1a03b61dbdff518eca52e550efe67b8f Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Mon, 11 Sep 2023 13:31:42 +0200 Subject: [PATCH 0074/1762] Make assist lazy again --- .../src/handlers/convert_comment_block.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/crates/ide-assists/src/handlers/convert_comment_block.rs b/crates/ide-assists/src/handlers/convert_comment_block.rs index 1f048ac10fa55..ef914cdb2cdf4 100644 --- a/crates/ide-assists/src/handlers/convert_comment_block.rs +++ b/crates/ide-assists/src/handlers/convert_comment_block.rs @@ -79,19 +79,21 @@ fn line_to_block(acc: &mut Assists, comment: ast::Comment) -> Option<()> { comments.last()?.syntax().text_range().end(), ); - // We pick a single indentation level for the whole block comment based on the - // comment where the assist was invoked. This will be prepended to the - // contents of each line comment when they're put into the block comment. - let indentation = IndentLevel::from_token(comment.syntax()); - - let cms = - comments.into_iter().map(|c| line_comment_text(indentation, c)).collect::>(); - acc.add( AssistId("line_to_block", AssistKind::RefactorRewrite), "Replace line comments with a single block comment", target, |edit| { + // We pick a single indentation level for the whole block comment based on the + // comment where the assist was invoked. This will be prepended to the + // contents of each line comment when they're put into the block comment. + let indentation = IndentLevel::from_token(comment.syntax()); + + let cms = comments + .into_iter() + .map(|c| line_comment_text(indentation, c)) + .collect::>(); + let block_comment_body = cms.into_iter().join("\n"); let block_prefix = From 893e19137e85e0fa11187a100b7fc716f7a99c76 Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Mon, 11 Sep 2023 13:33:26 +0200 Subject: [PATCH 0075/1762] Make assist lazy again --- crates/ide-assists/src/handlers/inline_call.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ide-assists/src/handlers/inline_call.rs b/crates/ide-assists/src/handlers/inline_call.rs index 9b4ac8a02a350..3c5a0be775e23 100644 --- a/crates/ide-assists/src/handlers/inline_call.rs +++ b/crates/ide-assists/src/handlers/inline_call.rs @@ -218,12 +218,12 @@ pub(crate) fn inline_call(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< } let syntax = call_info.node.syntax().clone(); - let replacement = inline(&ctx.sema, file_id, function, &fn_body, ¶ms, &call_info); acc.add( AssistId("inline_call", AssistKind::RefactorInline), label, syntax.text_range(), |builder| { + let replacement = inline(&ctx.sema, file_id, function, &fn_body, ¶ms, &call_info); builder.replace_ast( match call_info.node { ast::CallableExpr::Call(it) => ast::Expr::CallExpr(it), From 145a101fe86f570f4a7b236c8a4818f4ce7873c8 Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Mon, 11 Sep 2023 14:09:19 +0200 Subject: [PATCH 0076/1762] Deunwrap add_missing_match_arms --- .../src/handlers/add_missing_match_arms.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/crates/ide-assists/src/handlers/add_missing_match_arms.rs b/crates/ide-assists/src/handlers/add_missing_match_arms.rs index 3b162d7c4d82f..5376ece2f584b 100644 --- a/crates/ide-assists/src/handlers/add_missing_match_arms.rs +++ b/crates/ide-assists/src/handlers/add_missing_match_arms.rs @@ -273,9 +273,10 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) syntax::SyntaxElement::Token(it) => { // Don't have a way to make tokens mut, so instead make the parent mut // and find the token again - let parent = edit.make_syntax_mut(it.parent().unwrap()); + let parent = + edit.make_syntax_mut(it.parent().expect("Token must have a parent.")); let mut_token = - parent.covering_element(it.text_range()).into_token().unwrap(); + parent.covering_element(it.text_range()).into_token().expect("Covering element cannot be found. Range may be beyond the current node's range"); syntax::SyntaxElement::from(mut_token) } @@ -446,21 +447,23 @@ fn build_pat( mod_path_to_ast(&module.find_use_path(db, ModuleDef::from(var), prefer_no_std)?); // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though - let pat: ast::Pat = match var.source(db)?.value.kind() { + Some(match var.source(db)?.value.kind() { ast::StructKind::Tuple(field_list) => { let pats = iter::repeat(make::wildcard_pat().into()).take(field_list.fields().count()); make::tuple_struct_pat(path, pats).into() } ast::StructKind::Record(field_list) => { - let pats = field_list - .fields() - .map(|f| make::ext::simple_ident_pat(f.name().unwrap()).into()); + let pats = field_list.fields().map(|f| { + make::ext::simple_ident_pat( + f.name().expect("Record field must have a name"), + ) + .into() + }); make::record_pat(path, pats).into() } ast::StructKind::Unit => make::path_pat(path), - }; - Some(pat) + }) } ExtendedVariant::True => Some(ast::Pat::from(make::literal_pat("true"))), ExtendedVariant::False => Some(ast::Pat::from(make::literal_pat("false"))), From d79486529e5f39216f202971c076997486a0b1c5 Mon Sep 17 00:00:00 2001 From: dfireBird Date: Sat, 22 Jul 2023 19:51:34 +0530 Subject: [PATCH 0077/1762] remove `as _` on auto importing on trait that is aliased with `_` --- crates/ide-db/src/imports/merge_imports.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/ide-db/src/imports/merge_imports.rs b/crates/ide-db/src/imports/merge_imports.rs index 27b6321f3a7a5..ff84e9ffaeec7 100644 --- a/crates/ide-db/src/imports/merge_imports.rs +++ b/crates/ide-db/src/imports/merge_imports.rs @@ -78,6 +78,10 @@ fn try_merge_trees_mut(lhs: &ast::UseTree, rhs: &ast::UseTree, merge: MergeBehav { lhs.split_prefix(&lhs_prefix); rhs.split_prefix(&rhs_prefix); + } else { + ted::replace(lhs.syntax(), rhs.syntax()); + // we can safely return here, in this case `recursive_merge` doesn't do anything + return Some(()); } recursive_merge(lhs, rhs, merge) } @@ -123,6 +127,13 @@ fn recursive_merge(lhs: &ast::UseTree, rhs: &ast::UseTree, merge: MergeBehavior) // so they need to be handled explicitly .or_else(|| tree.star_token().map(|_| false)) }; + + if lhs_t.rename().and_then(|x| x.underscore_token()).is_some() { + ted::replace(lhs_t.syntax(), rhs_t.syntax()); + *lhs_t = rhs_t; + continue; + } + match (tree_contains_self(lhs_t), tree_contains_self(&rhs_t)) { (Some(true), None) => continue, (None, Some(true)) => { From df1239bf9246423f1006e4affb38ee504ef3dc79 Mon Sep 17 00:00:00 2001 From: dfireBird Date: Mon, 11 Sep 2023 17:36:09 +0530 Subject: [PATCH 0078/1762] add tests for insert use with renamed imports Tested for two cases: 1. Simple Use 2. Complex Use --- crates/ide-db/src/imports/insert_use/tests.rs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/crates/ide-db/src/imports/insert_use/tests.rs b/crates/ide-db/src/imports/insert_use/tests.rs index b92e367f7e12a..01d2f1970c38e 100644 --- a/crates/ide-db/src/imports/insert_use/tests.rs +++ b/crates/ide-db/src/imports/insert_use/tests.rs @@ -993,6 +993,46 @@ use foo::bar::qux; ); } +#[test] +fn insert_with_renamed_import_simple_use() { + check_with_config( + "use self::foo::Foo", + r#" +use self::foo::Foo as _; +"#, + r#" +use self::foo::Foo; +"#, + &InsertUseConfig { + granularity: ImportGranularity::Crate, + prefix_kind: hir::PrefixKind::BySelf, + enforce_granularity: true, + group: true, + skip_glob_imports: true, + }, + ); +} + +#[test] +fn insert_with_renamed_import_complex_use() { + check_with_config( + "use self::foo::Foo;", + r#" +use self::foo::{self, Foo as _, Bar}; +"#, + r#" +use self::foo::{self, Foo, Bar}; +"#, + &InsertUseConfig { + granularity: ImportGranularity::Crate, + prefix_kind: hir::PrefixKind::BySelf, + enforce_granularity: true, + group: true, + skip_glob_imports: true, + }, + ); +} + fn check_with_config( path: &str, ra_fixture_before: &str, From 2974416a81a64d75e0dc1c394b75fecd96230713 Mon Sep 17 00:00:00 2001 From: David Barsky Date: Tue, 12 Sep 2023 14:35:24 -0400 Subject: [PATCH 0079/1762] fix: ensure `rustfmt` runs when configured with `./` --- crates/rust-analyzer/src/handlers/request.rs | 41 +++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index b8a1a39be193d..ad6319586cfe4 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -4,6 +4,7 @@ use std::{ fs, io::Write as _, + path::PathBuf, process::{self, Stdio}, }; @@ -1995,7 +1996,43 @@ fn run_rustfmt( cmd } RustfmtConfig::CustomCommand { command, args } => { - let mut cmd = process::Command::new(command); + let cmd = PathBuf::from(&command); + let mut components = cmd.components(); + + // to support rustc's suggested, default configuration + let mut cmd = match components.next() { + Some(std::path::Component::CurDir) => { + let rest = components.as_path(); + + let roots = snap + .workspaces + .iter() + .flat_map(|ws| ws.workspace_definition_path()) + .collect::>(); + + let abs: Option = roots.into_iter().find_map(|base| { + let abs = base.join(rest); + std::fs::metadata(&abs).ok().map(|_| abs) + }); + + let command = match abs { + Some(cmd) => cmd, + None => { + tracing::error!( + rustfmt = ?command, + "Unable to make the format command an absolute path" + ); + anyhow::bail!( + "Unable to make the format command an absolute path: {}", + command + ); + } + }; + + process::Command::new(&command.as_os_str()) + } + _ => process::Command::new(command), + }; cmd.envs(snap.config.extra_env()); cmd.args(args); @@ -2003,6 +2040,8 @@ fn run_rustfmt( } }; + tracing::debug!(?command, "created format command"); + // try to chdir to the file so we can respect `rustfmt.toml` // FIXME: use `rustfmt --config-path` once // https://github.com/rust-lang/rustfmt/issues/4660 gets fixed From ebbbaaa90f4dd111b1bb9df7250925f612110925 Mon Sep 17 00:00:00 2001 From: shogo-nakano-desu <61229807+shogo-nakano-desu@users.noreply.github.com> Date: Fri, 15 Sep 2023 16:43:21 +0900 Subject: [PATCH 0080/1762] refactor: fix clippy lints --- crates/intern/src/lib.rs | 14 +++------- crates/parser/src/shortcuts.rs | 36 ++++++++++++-------------- crates/syntax/src/ast/edit_in_place.rs | 14 +++++++--- crates/syntax/src/lib.rs | 36 ++++++++++++-------------- crates/syntax/src/tests.rs | 4 +-- 5 files changed, 50 insertions(+), 54 deletions(-) diff --git a/crates/intern/src/lib.rs b/crates/intern/src/lib.rs index 2934d26674d7e..d784321c7c7af 100644 --- a/crates/intern/src/lib.rs +++ b/crates/intern/src/lib.rs @@ -33,13 +33,10 @@ impl Interned { // - if not, box it up, insert it, and return a clone // This needs to be atomic (locking the shard) to avoid races with other thread, which could // insert the same object between us looking it up and inserting it. - match shard.raw_entry_mut().from_key_hashed_nocheck(hash as u64, &obj) { + match shard.raw_entry_mut().from_key_hashed_nocheck(hash, &obj) { RawEntryMut::Occupied(occ) => Self { arc: occ.key().clone() }, RawEntryMut::Vacant(vac) => Self { - arc: vac - .insert_hashed_nocheck(hash as u64, Arc::new(obj), SharedValue::new(())) - .0 - .clone(), + arc: vac.insert_hashed_nocheck(hash, Arc::new(obj), SharedValue::new(())).0.clone(), }, } } @@ -54,13 +51,10 @@ impl Interned { // - if not, box it up, insert it, and return a clone // This needs to be atomic (locking the shard) to avoid races with other thread, which could // insert the same object between us looking it up and inserting it. - match shard.raw_entry_mut().from_key_hashed_nocheck(hash as u64, s) { + match shard.raw_entry_mut().from_key_hashed_nocheck(hash, s) { RawEntryMut::Occupied(occ) => Self { arc: occ.key().clone() }, RawEntryMut::Vacant(vac) => Self { - arc: vac - .insert_hashed_nocheck(hash as u64, Arc::from(s), SharedValue::new(())) - .0 - .clone(), + arc: vac.insert_hashed_nocheck(hash, Arc::from(s), SharedValue::new(())).0.clone(), }, } } diff --git a/crates/parser/src/shortcuts.rs b/crates/parser/src/shortcuts.rs index 2c47e3d086d61..57005a6834c90 100644 --- a/crates/parser/src/shortcuts.rs +++ b/crates/parser/src/shortcuts.rs @@ -32,29 +32,27 @@ impl LexedStr<'_> { let kind = self.kind(i); if kind.is_trivia() { was_joint = false + } else if kind == SyntaxKind::IDENT { + let token_text = self.text(i); + let contextual_kw = + SyntaxKind::from_contextual_keyword(token_text).unwrap_or(SyntaxKind::IDENT); + res.push_ident(contextual_kw); } else { - if kind == SyntaxKind::IDENT { - let token_text = self.text(i); - let contextual_kw = SyntaxKind::from_contextual_keyword(token_text) - .unwrap_or(SyntaxKind::IDENT); - res.push_ident(contextual_kw); - } else { - if was_joint { + if was_joint { + res.was_joint(); + } + res.push(kind); + // Tag the token as joint if it is float with a fractional part + // we use this jointness to inform the parser about what token split + // event to emit when we encounter a float literal in a field access + if kind == SyntaxKind::FLOAT_NUMBER { + if !self.text(i).ends_with('.') { res.was_joint(); - } - res.push(kind); - // Tag the token as joint if it is float with a fractional part - // we use this jointness to inform the parser about what token split - // event to emit when we encounter a float literal in a field access - if kind == SyntaxKind::FLOAT_NUMBER { - if !self.text(i).ends_with('.') { - res.was_joint(); - } else { - was_joint = false; - } } else { - was_joint = true; + was_joint = false; } + } else { + was_joint = true; } } } diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs index a150d9e6c07d8..a85e1d1d9d0eb 100644 --- a/crates/syntax/src/ast/edit_in_place.rs +++ b/crates/syntax/src/ast/edit_in_place.rs @@ -224,7 +224,7 @@ pub trait AttrsOwnerEdit: ast::HasAttrs { let after_attrs_and_comments = node .children_with_tokens() .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR)) - .map_or(Position::first_child_of(node), |it| Position::before(it)); + .map_or(Position::first_child_of(node), Position::before); ted::insert_all( after_attrs_and_comments, @@ -433,7 +433,9 @@ impl ast::UseTree { if &path == prefix && self.use_tree_list().is_none() { if self.star_token().is_some() { // path$0::* -> * - self.coloncolon_token().map(ted::remove); + if let Some(a) = self.coloncolon_token() { + ted::remove(a) + } ted::remove(prefix.syntax()); } else { // path$0 -> self @@ -460,7 +462,9 @@ impl ast::UseTree { for p in successors(parent.parent_path(), |it| it.parent_path()) { p.segment()?; } - prefix.parent_path().and_then(|p| p.coloncolon_token()).map(ted::remove); + if let Some(a) = prefix.parent_path().and_then(|p| p.coloncolon_token()) { + ted::remove(a) + } ted::remove(prefix.syntax()); Some(()) } @@ -976,7 +980,9 @@ enum Foo { fn check_add_variant(before: &str, expected: &str, variant: ast::Variant) { let enum_ = ast_mut_from_text::(before); - enum_.variant_list().map(|it| it.add_variant(variant)); + if let Some(it) = enum_.variant_list() { + it.add_variant(variant) + } let after = enum_.to_string(); assert_eq_text!(&trim_indent(expected.trim()), &trim_indent(after.trim())); } diff --git a/crates/syntax/src/lib.rs b/crates/syntax/src/lib.rs index 27c8a13e58d66..73eff358462a3 100644 --- a/crates/syntax/src/lib.rs +++ b/crates/syntax/src/lib.rs @@ -181,29 +181,27 @@ impl ast::TokenTree { let kind = t.kind(); if kind.is_trivia() { was_joint = false + } else if kind == SyntaxKind::IDENT { + let token_text = t.text(); + let contextual_kw = + SyntaxKind::from_contextual_keyword(token_text).unwrap_or(SyntaxKind::IDENT); + parser_input.push_ident(contextual_kw); } else { - if kind == SyntaxKind::IDENT { - let token_text = t.text(); - let contextual_kw = SyntaxKind::from_contextual_keyword(token_text) - .unwrap_or(SyntaxKind::IDENT); - parser_input.push_ident(contextual_kw); - } else { - if was_joint { + if was_joint { + parser_input.was_joint(); + } + parser_input.push(kind); + // Tag the token as joint if it is float with a fractional part + // we use this jointness to inform the parser about what token split + // event to emit when we encounter a float literal in a field access + if kind == SyntaxKind::FLOAT_NUMBER { + if !t.text().ends_with('.') { parser_input.was_joint(); - } - parser_input.push(kind); - // Tag the token as joint if it is float with a fractional part - // we use this jointness to inform the parser about what token split - // event to emit when we encounter a float literal in a field access - if kind == SyntaxKind::FLOAT_NUMBER { - if !t.text().ends_with('.') { - parser_input.was_joint(); - } else { - was_joint = false; - } } else { - was_joint = true; + was_joint = false; } + } else { + was_joint = true; } } } diff --git a/crates/syntax/src/tests.rs b/crates/syntax/src/tests.rs index 168439053c27a..3010d77d827e1 100644 --- a/crates/syntax/src/tests.rs +++ b/crates/syntax/src/tests.rs @@ -17,11 +17,11 @@ use crate::{ast, fuzz, AstNode, SourceFile, SyntaxError}; #[test] fn parse_smoke_test() { - let code = r##" + let code = r#" fn main() { println!("Hello, world!") } - "##; + "#; let parse = SourceFile::parse(code); // eprintln!("{:#?}", parse.syntax_node()); From 0bb2298ac604e0654652418e63a9eb15a82f155c Mon Sep 17 00:00:00 2001 From: shogo-nakano-desu <61229807+shogo-nakano-desu@users.noreply.github.com> Date: Fri, 15 Sep 2023 16:43:31 +0900 Subject: [PATCH 0081/1762] refactor: remove TODO with no explanation --- crates/syntax/src/ast/make.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs index 17e311c0c502f..20cd6abb5a812 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs @@ -433,7 +433,6 @@ pub fn record_field( ast_from_text(&format!("struct S {{ {visibility}{name}: {ty}, }}")) } -// TODO pub fn block_expr( stmts: impl IntoIterator, tail_expr: Option, From 96c333262a51d98a11fb0fed949e5ef55e40cd87 Mon Sep 17 00:00:00 2001 From: shogo-nakano-desu <61229807+shogo-nakano-desu@users.noreply.github.com> Date: Fri, 15 Sep 2023 16:47:39 +0900 Subject: [PATCH 0082/1762] refactor: fix clippy lint --- crates/flycheck/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs index 2de719af92ce9..61433313921a7 100644 --- a/crates/flycheck/src/lib.rs +++ b/crates/flycheck/src/lib.rs @@ -488,7 +488,9 @@ impl CargoActor { // Skip certain kinds of messages to only spend time on what's useful JsonMessage::Cargo(message) => match message { cargo_metadata::Message::CompilerArtifact(artifact) if !artifact.fresh => { - self.sender.send(CargoMessage::CompilerArtifact(artifact)).unwrap(); + self.sender + .send(CargoMessage::CompilerArtifact(Box::new(artifact))) + .unwrap(); } cargo_metadata::Message::CompilerMessage(msg) => { self.sender.send(CargoMessage::Diagnostic(msg.message)).unwrap(); @@ -533,7 +535,7 @@ impl CargoActor { } enum CargoMessage { - CompilerArtifact(cargo_metadata::Artifact), + CompilerArtifact(Box), Diagnostic(Diagnostic), } From f4704bc8ae92d150b2801e391692d9503f0c6126 Mon Sep 17 00:00:00 2001 From: hkalbasi Date: Fri, 15 Sep 2023 18:10:11 +0330 Subject: [PATCH 0083/1762] Switch to in-tree rustc dependencies with a cfg flag --- Cargo.lock | 22 ++++++++++----- Cargo.toml | 18 +++++-------- crates/hir-def/Cargo.toml | 8 +++--- crates/hir-def/src/data/adt.rs | 2 +- crates/hir-def/src/hir/format_args.rs | 2 +- crates/hir-def/src/lib.rs | 3 ++- crates/hir-ty/Cargo.toml | 5 +++- crates/hir-ty/src/layout.rs | 2 +- crates/parser/Cargo.toml | 5 +++- crates/parser/src/lexed_str.rs | 1 + crates/parser/src/lib.rs | 1 + crates/rust-analyzer/Cargo.toml | 12 ++++++++- crates/rustc-dependencies/Cargo.toml | 20 ++++++++++++++ crates/rustc-dependencies/src/lib.rs | 39 +++++++++++++++++++++++++++ crates/syntax/Cargo.toml | 4 +-- crates/syntax/src/ast/token_ext.rs | 2 ++ crates/syntax/src/lib.rs | 1 + crates/syntax/src/validation.rs | 2 +- 18 files changed, 118 insertions(+), 31 deletions(-) create mode 100644 crates/rustc-dependencies/Cargo.toml create mode 100644 crates/rustc-dependencies/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index bd6554bf8899a..e506aa834eba8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -531,8 +531,6 @@ dependencies = [ "fst", "hashbrown 0.12.3", "hir-expand", - "hkalbasi-rustc-ap-rustc_abi", - "hkalbasi-rustc-ap-rustc_index", "indexmap 2.0.0", "intern", "itertools", @@ -541,7 +539,7 @@ dependencies = [ "mbe", "once_cell", "profile", - "ra-ap-rustc_parse_format", + "rustc-dependencies", "rustc-hash", "smallvec", "stdx", @@ -594,7 +592,6 @@ dependencies = [ "expect-test", "hir-def", "hir-expand", - "hkalbasi-rustc-ap-rustc_index", "intern", "itertools", "la-arena 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -604,6 +601,7 @@ dependencies = [ "oorandom", "profile", "project-model", + "rustc-dependencies", "rustc-hash", "scoped-tls", "smallvec", @@ -1277,7 +1275,7 @@ dependencies = [ "drop_bomb", "expect-test", "limit", - "ra-ap-rustc_lexer", + "rustc-dependencies", "sourcegen", "stdx", ] @@ -1594,10 +1592,12 @@ dependencies = [ "oorandom", "parking_lot 0.12.1", "parking_lot_core 0.9.6", + "parser", "proc-macro-api", "profile", "project-model", "rayon", + "rustc-dependencies", "rustc-hash", "scip", "serde", @@ -1626,6 +1626,16 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-dependencies" +version = "0.0.0" +dependencies = [ + "hkalbasi-rustc-ap-rustc_abi", + "hkalbasi-rustc-ap-rustc_index", + "ra-ap-rustc_lexer", + "ra-ap-rustc_parse_format", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -1853,9 +1863,9 @@ dependencies = [ "proc-macro2", "profile", "quote", - "ra-ap-rustc_lexer", "rayon", "rowan", + "rustc-dependencies", "rustc-hash", "smol_str", "sourcegen", diff --git a/Cargo.toml b/Cargo.toml index cab88fc18cec6..ffac946b18f97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,6 +79,7 @@ toolchain = { path = "./crates/toolchain", version = "0.0.0" } tt = { path = "./crates/tt", version = "0.0.0" } vfs-notify = { path = "./crates/vfs-notify", version = "0.0.0" } vfs = { path = "./crates/vfs", version = "0.0.0" } +rustc-dependencies = { path = "./crates/rustc-dependencies", version = "0.0.0" } # local crates that aren't published to crates.io. These should not have versions. proc-macro-test = { path = "./crates/proc-macro-test" } @@ -90,9 +91,9 @@ lsp-server = { version = "0.7.4" } # non-local crates smallvec = { version = "1.10.0", features = [ - "const_new", - "union", - "const_generics", + "const_new", + "union", + "const_generics", ] } smol_str = "0.2.0" nohash-hasher = "0.2.0" @@ -101,11 +102,6 @@ serde = { version = "1.0.156", features = ["derive"] } serde_json = "1.0.96" triomphe = { version = "0.1.8", default-features = false, features = ["std"] } # can't upgrade due to dashmap depending on 0.12.3 currently -hashbrown = { version = "0.12.3", features = ["inline-more"], default-features = false } - -rustc_lexer = { version = "0.10.0", package = "ra-ap-rustc_lexer" } -rustc_parse_format = { version = "0.10.0", package = "ra-ap-rustc_parse_format", default-features = false } - -# Upstream broke this for us so we can't update it -rustc_abi = { version = "0.0.20221221", package = "hkalbasi-rustc-ap-rustc_abi", default-features = false } -rustc_index = { version = "0.0.20221221", package = "hkalbasi-rustc-ap-rustc_index", default-features = false } +hashbrown = { version = "0.12.3", features = [ + "inline-more", +], default-features = false } diff --git a/crates/hir-def/Cargo.toml b/crates/hir-def/Cargo.toml index 8cf61ee04d4e6..092aab011205a 100644 --- a/crates/hir-def/Cargo.toml +++ b/crates/hir-def/Cargo.toml @@ -31,10 +31,7 @@ smallvec.workspace = true hashbrown.workspace = true triomphe.workspace = true -rustc_abi.workspace = true -rustc_index.workspace = true -rustc_parse_format.workspace = true - +rustc-dependencies.workspace = true # local deps stdx.workspace = true @@ -53,3 +50,6 @@ expect-test = "1.4.0" # local deps test-utils.workspace = true + +[features] +in-rust-tree = ["rustc-dependencies/in-rust-tree"] diff --git a/crates/hir-def/src/data/adt.rs b/crates/hir-def/src/data/adt.rs index 224f7328f8cdc..b163112db9184 100644 --- a/crates/hir-def/src/data/adt.rs +++ b/crates/hir-def/src/data/adt.rs @@ -11,7 +11,7 @@ use hir_expand::{ }; use intern::Interned; use la_arena::{Arena, ArenaMap}; -use rustc_abi::{Align, Integer, IntegerType, ReprFlags, ReprOptions}; +use rustc_dependencies::abi::{Align, Integer, IntegerType, ReprFlags, ReprOptions}; use syntax::ast::{self, HasName, HasVisibility}; use triomphe::Arc; diff --git a/crates/hir-def/src/hir/format_args.rs b/crates/hir-def/src/hir/format_args.rs index 75025a984fcd2..46d24bd4a6142 100644 --- a/crates/hir-def/src/hir/format_args.rs +++ b/crates/hir-def/src/hir/format_args.rs @@ -2,7 +2,7 @@ use std::mem; use hir_expand::name::Name; -use rustc_parse_format as parse; +use rustc_dependencies::parse_format as parse; use syntax::{ ast::{self, IsString}, AstToken, SmolStr, TextRange, diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs index 3f87fe62b83cd..1abcb1835fd5b 100644 --- a/crates/hir-def/src/lib.rs +++ b/crates/hir-def/src/lib.rs @@ -8,6 +8,7 @@ //! actually true. #![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)] +#![cfg_attr(feature = "in-rust-tree", feature(rustc_private))] #[allow(unused)] macro_rules! eprintln { @@ -48,7 +49,7 @@ pub mod visibility; pub mod find_path; pub mod import_map; -pub use rustc_abi as layout; +pub use rustc_dependencies::abi as layout; use triomphe::Arc; #[cfg(test)] diff --git a/crates/hir-ty/Cargo.toml b/crates/hir-ty/Cargo.toml index b95ae05ccd458..6a0c26a8bc092 100644 --- a/crates/hir-ty/Cargo.toml +++ b/crates/hir-ty/Cargo.toml @@ -33,7 +33,7 @@ triomphe.workspace = true nohash-hasher.workspace = true typed-arena = "2.0.1" -rustc_index.workspace = true +rustc-dependencies.workspace = true # local deps stdx.workspace = true @@ -56,3 +56,6 @@ project-model = { path = "../project-model" } # local deps test-utils.workspace = true + +[features] +in-rust-tree = ["rustc-dependencies/in-rust-tree"] diff --git a/crates/hir-ty/src/layout.rs b/crates/hir-ty/src/layout.rs index 1a6106c0244c6..ee558956a761c 100644 --- a/crates/hir-ty/src/layout.rs +++ b/crates/hir-ty/src/layout.rs @@ -34,7 +34,7 @@ mod target; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct RustcEnumVariantIdx(pub LocalEnumVariantId); -impl rustc_index::vec::Idx for RustcEnumVariantIdx { +impl rustc_dependencies::index::vec::Idx for RustcEnumVariantIdx { fn new(idx: usize) -> Self { RustcEnumVariantIdx(Idx::from_raw(RawIdx::from(idx as u32))) } diff --git a/crates/parser/Cargo.toml b/crates/parser/Cargo.toml index 09e62c35278e9..efb326323f915 100644 --- a/crates/parser/Cargo.toml +++ b/crates/parser/Cargo.toml @@ -13,7 +13,7 @@ doctest = false [dependencies] drop_bomb = "0.1.5" -rustc_lexer.workspace = true +rustc-dependencies.workspace = true limit.workspace = true @@ -22,3 +22,6 @@ expect-test = "1.4.0" stdx.workspace = true sourcegen.workspace = true + +[features] +in-rust-tree = ["rustc-dependencies/in-rust-tree"] diff --git a/crates/parser/src/lexed_str.rs b/crates/parser/src/lexed_str.rs index 36c52953a0246..30c1c4f8c75be 100644 --- a/crates/parser/src/lexed_str.rs +++ b/crates/parser/src/lexed_str.rs @@ -8,6 +8,7 @@ //! Note that these tokens, unlike the tokens we feed into the parser, do //! include info about comments and whitespace. +use rustc_dependencies::lexer as rustc_lexer; use std::ops; use crate::{ diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index c155e8aaf67b3..fcfd1a50719bd 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -19,6 +19,7 @@ #![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)] #![allow(rustdoc::private_intra_doc_links)] +#![cfg_attr(feature = "in-rust-tree", feature(rustc_private))] mod lexed_str; mod token_set; diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index 7410f0a3a668e..0a5412c638c3f 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml @@ -57,6 +57,7 @@ flycheck.workspace = true hir-def.workspace = true hir-ty.workspace = true hir.workspace = true +rustc-dependencies.workspace = true ide-db.workspace = true # This should only be used in CLI ide-ssr.workspace = true @@ -67,6 +68,7 @@ profile.workspace = true project-model.workspace = true stdx.workspace = true syntax.workspace = true +parser.workspace = true toolchain.workspace = true vfs-notify.workspace = true vfs.workspace = true @@ -89,4 +91,12 @@ mbe.workspace = true jemalloc = ["jemallocator", "profile/jemalloc"] force-always-assert = ["always-assert/force"] sysroot-abi = [] -in-rust-tree = ["sysroot-abi", "ide/in-rust-tree", "syntax/in-rust-tree"] +in-rust-tree = [ + "sysroot-abi", + "ide/in-rust-tree", + "syntax/in-rust-tree", + "parser/in-rust-tree", + "rustc-dependencies/in-rust-tree", + "hir-def/in-rust-tree", + "hir-ty/in-rust-tree", +] diff --git a/crates/rustc-dependencies/Cargo.toml b/crates/rustc-dependencies/Cargo.toml new file mode 100644 index 0000000000000..901706d3d95f3 --- /dev/null +++ b/crates/rustc-dependencies/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "rustc-dependencies" +version = "0.0.0" +rust-version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +ra-ap-rustc_lexer = { version = "0.10.0" } +ra-ap-rustc_parse_format = { version = "0.10.0", default-features = false } + +# Upstream broke this for us so we can't update it +hkalbasi-rustc-ap-rustc_abi = { version = "0.0.20221221", default-features = false } +hkalbasi-rustc-ap-rustc_index = { version = "0.0.20221221", default-features = false } + +[features] +in-rust-tree = [] diff --git a/crates/rustc-dependencies/src/lib.rs b/crates/rustc-dependencies/src/lib.rs new file mode 100644 index 0000000000000..c1d3f05f34e7f --- /dev/null +++ b/crates/rustc-dependencies/src/lib.rs @@ -0,0 +1,39 @@ +//! A wrapper around rustc internal crates, which enables switching between compiler provided +//! ones and stable ones published in crates.io + +#![cfg_attr(feature = "in-rust-tree", feature(rustc_private))] + +#[cfg(feature = "in-rust-tree")] +extern crate rustc_lexer; + +#[cfg(feature = "in-rust-tree")] +pub mod lexer { + pub use ::rustc_lexer::*; +} + +#[cfg(not(feature = "in-rust-tree"))] +pub mod lexer { + pub use ::ra_ap_rustc_lexer::*; +} + +#[cfg(feature = "in-rust-tree")] +extern crate rustc_parse_format; + +#[cfg(feature = "in-rust-tree")] +pub mod parse_format { + pub use ::rustc_parse_format::*; +} + +#[cfg(not(feature = "in-rust-tree"))] +pub mod parse_format { + pub use ::ra_ap_rustc_parse_format::*; +} + +// Upstream broke this for us so we can't update it +pub mod abi { + pub use ::hkalbasi_rustc_ap_rustc_abi::*; +} + +pub mod index { + pub use ::hkalbasi_rustc_ap_rustc_index::*; +} diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml index 5ee0c4792846a..dc92366d1c7a2 100644 --- a/crates/syntax/Cargo.toml +++ b/crates/syntax/Cargo.toml @@ -23,7 +23,7 @@ indexmap = "2.0.0" smol_str.workspace = true triomphe.workspace = true -rustc_lexer.workspace = true +rustc-dependencies.workspace = true parser.workspace = true profile.workspace = true @@ -41,4 +41,4 @@ test-utils.workspace = true sourcegen.workspace = true [features] -in-rust-tree = [] +in-rust-tree = ["rustc-dependencies/in-rust-tree"] diff --git a/crates/syntax/src/ast/token_ext.rs b/crates/syntax/src/ast/token_ext.rs index 87fd51d703cff..8cc271d226c4e 100644 --- a/crates/syntax/src/ast/token_ext.rs +++ b/crates/syntax/src/ast/token_ext.rs @@ -2,6 +2,8 @@ use std::borrow::Cow; +use rustc_dependencies::lexer as rustc_lexer; + use rustc_lexer::unescape::{ unescape_byte, unescape_c_string, unescape_char, unescape_literal, CStrUnit, Mode, }; diff --git a/crates/syntax/src/lib.rs b/crates/syntax/src/lib.rs index 27c8a13e58d66..2cd82e3762e62 100644 --- a/crates/syntax/src/lib.rs +++ b/crates/syntax/src/lib.rs @@ -19,6 +19,7 @@ //! [RFC]: //! [Swift]: +#![cfg_attr(feature = "in-rust-tree", feature(rustc_private))] #![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)] #[allow(unused)] diff --git a/crates/syntax/src/validation.rs b/crates/syntax/src/validation.rs index e0ec6a242ffa7..2b1bbac08e528 100644 --- a/crates/syntax/src/validation.rs +++ b/crates/syntax/src/validation.rs @@ -5,7 +5,7 @@ mod block; use rowan::Direction; -use rustc_lexer::unescape::{self, unescape_literal, Mode}; +use rustc_dependencies::lexer::unescape::{self, unescape_literal, Mode}; use crate::{ algo, From 24b6922957dbe0b9f887c69508fc1192663019ca Mon Sep 17 00:00:00 2001 From: Peter Jaszkowiak Date: Sat, 16 Sep 2023 10:58:53 -0600 Subject: [PATCH 0084/1762] triagebot exclude_labels -> exclude_titles --- triagebot.toml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/triagebot.toml b/triagebot.toml index f0cd35399752f..95eed3ee172c0 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -11,5 +11,10 @@ allow-unauthenticated = [ new_pr = true [no-merges] -exclude_labels = ["sync"] +exclude_titles = [ # exclude syncs from subtree in rust-lang/rust + "Sync from downstream", + "sync from downstream", + "Sync from rust", + "sync from rust", +] labels = ["has-merge-commits", "S-waiting-on-author"] From cac796acb30d3af9a2eb9aff02acfae43a6da537 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Sat, 16 Sep 2023 12:59:17 -0700 Subject: [PATCH 0085/1762] Give `unmerge_use` a label explaining what it will affect. --- .../ide-assists/src/handlers/unmerge_use.rs | 40 +++++++++---------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/crates/ide-assists/src/handlers/unmerge_use.rs b/crates/ide-assists/src/handlers/unmerge_use.rs index dac216b69b727..52df30d9623fe 100644 --- a/crates/ide-assists/src/handlers/unmerge_use.rs +++ b/crates/ide-assists/src/handlers/unmerge_use.rs @@ -36,29 +36,25 @@ pub(crate) fn unmerge_use(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< let old_parent_range = use_.syntax().parent()?.text_range(); let new_parent = use_.syntax().parent()?; + // If possible, explain what is going to be done. + let label = match tree.path().and_then(|path| path.first_segment()) { + Some(name) => format!("Unmerge use of `{name}`"), + None => "Unmerge use".into(), + }; + let target = tree.syntax().text_range(); - acc.add( - AssistId("unmerge_use", AssistKind::RefactorRewrite), - "Unmerge use", - target, - |builder| { - let new_use = make::use_( - use_.visibility(), - make::use_tree( - path, - tree.use_tree_list(), - tree.rename(), - tree.star_token().is_some(), - ), - ) - .clone_for_update(); - - tree.remove(); - ted::insert(Position::after(use_.syntax()), new_use.syntax()); - - builder.replace(old_parent_range, new_parent.to_string()); - }, - ) + acc.add(AssistId("unmerge_use", AssistKind::RefactorRewrite), label, target, |builder| { + let new_use = make::use_( + use_.visibility(), + make::use_tree(path, tree.use_tree_list(), tree.rename(), tree.star_token().is_some()), + ) + .clone_for_update(); + + tree.remove(); + ted::insert(Position::after(use_.syntax()), new_use.syntax()); + + builder.replace(old_parent_range, new_parent.to_string()); + }) } fn resolve_full_path(tree: &ast::UseTree) -> Option { From 2a929813016946dd671a9b01a5cefdda168dcc0b Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Sat, 16 Sep 2023 21:39:58 -0700 Subject: [PATCH 0086/1762] rustdoc: stop preloading Source Serif 4 Bold According to 4198fac7390509128c42757fcfb89a0effde4a8e, italic fonts are not preloaded because they're rarely used, but bold fonts are. This seems to be true of bold Source Code Pro and bold Fira Sans, but bold and italic Source Serif Pro seem to be equally heavily used. This is, I assume, the result of using Fira Sans Bold and Source Code Bold headings, so you only get bold Serif text when the doc author uses strong `**` emphasis (or within certain kinds of tooltip, which shouldn't be preloaded because they only show up long after the page is loaded). To check this, run these two commands in the browser console to measure how much they're used. The measurement is extremely rough, but it gets the idea across: the two styles are about equally popular. // count bold elements Array.prototype.slice.call(document.querySelectorAll("*")).filter(x => { const y = document.defaultView.getComputedStyle(x); return y.fontFamily.indexOf("Source Serif 4") !== -1 && y.fontWeight > 400 }).length // count italic elements Array.prototype.slice.call(document.querySelectorAll("*")).filter(x => { const y = document.defaultView.getComputedStyle(x); return y.fontFamily.indexOf("Source Serif 4") !== -1 && y.fontStyle == "italic" }).length | URL | Bold | Italic | |---------|-----:|-------:| | [std] | 2 | 9 | | [Vec] | 8 | 89 | | [regex] | 33 | 17 | [std]: https://doc.rust-lang.org/nightly/std/index.html [Vec]: https://doc.rust-lang.org/nightly/std/vec/struct.Vec.html [regex]: https://docs.rs/regex/1.9.5/regex/index.html --- src/librustdoc/html/templates/page.html | 1 - 1 file changed, 1 deletion(-) diff --git a/src/librustdoc/html/templates/page.html b/src/librustdoc/html/templates/page.html index b4b9c31916532..370d392a39ea0 100644 --- a/src/librustdoc/html/templates/page.html +++ b/src/librustdoc/html/templates/page.html @@ -10,7 +10,6 @@ {# #} {# #} {# #} - {# #} {# #} {# #} From bcfc997eac54bb0c9957869591ad5ca70b60beb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Mon, 18 Sep 2023 12:32:37 +0300 Subject: [PATCH 0087/1762] Merge commit '258b15c506a2d3ad862fd17ae24eaf272443f477' into sync-from-ra --- .cargo/config.toml | 2 +- .github/workflows/ci.yaml | 8 + .github/workflows/metrics.yaml | 23 +- .github/workflows/release.yaml | 2 + Cargo.lock | 114 ++-- Cargo.toml | 12 +- crates/base-db/src/fixture.rs | 10 +- crates/cfg/src/lib.rs | 26 + crates/flycheck/src/lib.rs | 67 ++- crates/hir-def/Cargo.toml | 6 +- crates/hir-def/src/attr.rs | 177 +----- crates/hir-def/src/attr/builtin.rs | 5 +- crates/hir-def/src/body.rs | 37 +- crates/hir-def/src/body/lower.rs | 469 +++++++++++++++- crates/hir-def/src/body/pretty.rs | 53 +- crates/hir-def/src/body/tests.rs | 89 +++- crates/hir-def/src/data/adt.rs | 1 + crates/hir-def/src/find_path.rs | 103 +++- crates/hir-def/src/hir.rs | 18 +- crates/hir-def/src/hir/format_args.rs | 502 ++++++++++++++++++ crates/hir-def/src/import_map.rs | 60 ++- crates/hir-def/src/item_tree.rs | 2 +- crates/hir-def/src/item_tree/pretty.rs | 64 ++- crates/hir-def/src/lang_item.rs | 13 +- crates/hir-def/src/lower.rs | 3 +- .../macro_expansion_tests/builtin_fn_macro.rs | 78 ++- .../hir-def/src/macro_expansion_tests/mbe.rs | 6 +- .../macro_expansion_tests/mbe/regression.rs | 12 +- crates/hir-def/src/nameres.rs | 2 +- crates/hir-def/src/nameres/collector.rs | 6 +- crates/hir-def/src/path.rs | 31 +- crates/hir-def/src/pretty.rs | 72 ++- crates/hir-def/src/resolver.rs | 45 +- crates/hir-expand/src/builtin_fn_macro.rs | 170 +----- crates/hir-expand/src/hygiene.rs | 2 +- crates/hir-expand/src/name.rs | 25 + crates/hir-ty/Cargo.toml | 3 +- .../hir-ty/src/consteval/tests/intrinsics.rs | 36 +- crates/hir-ty/src/diagnostics/decl_check.rs | 67 ++- crates/hir-ty/src/infer.rs | 8 +- crates/hir-ty/src/infer/cast.rs | 12 +- crates/hir-ty/src/infer/closure.rs | 13 +- crates/hir-ty/src/infer/expr.rs | 80 ++- crates/hir-ty/src/infer/mutability.rs | 2 + crates/hir-ty/src/infer/pat.rs | 132 ++++- crates/hir-ty/src/infer/path.rs | 23 +- crates/hir-ty/src/layout.rs | 13 +- crates/hir-ty/src/layout/adt.rs | 2 +- crates/hir-ty/src/layout/tests.rs | 4 +- crates/hir-ty/src/layout/tests/closure.rs | 14 + crates/hir-ty/src/mir.rs | 141 +++-- crates/hir-ty/src/mir/borrowck.rs | 92 ++-- crates/hir-ty/src/mir/eval.rs | 85 ++- crates/hir-ty/src/mir/eval/shim.rs | 144 +++-- crates/hir-ty/src/mir/eval/shim/simd.rs | 4 +- crates/hir-ty/src/mir/lower.rs | 183 ++++--- crates/hir-ty/src/mir/lower/as_place.rs | 16 +- .../hir-ty/src/mir/lower/pattern_matching.rs | 58 +- crates/hir-ty/src/mir/pretty.rs | 2 +- crates/hir-ty/src/tests/simple.rs | 104 ++-- crates/hir-ty/src/tests/traits.rs | 28 +- crates/hir/src/attrs.rs | 131 ++--- crates/hir/src/diagnostics.rs | 11 +- crates/hir/src/lib.rs | 67 ++- crates/hir/src/semantics.rs | 446 ++++------------ .../src/handlers/bind_unused_param.rs | 159 ++++++ .../src/handlers/convert_bool_then.rs | 1 - .../extract_expressions_from_format_string.rs | 91 +--- .../src/handlers/extract_function.rs | 43 +- .../src/handlers/into_to_qualified_from.rs | 205 +++++++ .../src/handlers/promote_local_to_const.rs | 43 +- crates/ide-assists/src/handlers/remove_dbg.rs | 5 +- .../handlers/wrap_return_type_in_result.rs | 14 +- crates/ide-assists/src/lib.rs | 4 + crates/ide-assists/src/tests/generated.rs | 64 ++- crates/ide-assists/src/utils/suggest_name.rs | 1 - .../src/completions/attribute/cfg.rs | 57 +- .../src/completions/attribute/derive.rs | 4 +- .../src/completions/attribute/lint.rs | 4 +- .../src/completions/extern_crate.rs | 4 +- .../src/completions/format_string.rs | 22 +- .../src/completions/item_list/trait_impl.rs | 4 +- .../ide-completion/src/completions/postfix.rs | 8 +- .../ide-completion/src/completions/snippet.rs | 3 +- crates/ide-completion/src/item.rs | 7 +- crates/ide-completion/src/render.rs | 9 +- crates/ide-completion/src/render/literal.rs | 7 +- crates/ide-completion/src/render/macro_.rs | 4 +- crates/ide-completion/src/render/pattern.rs | 6 +- crates/ide-completion/src/tests/attribute.rs | 38 +- crates/ide-db/src/documentation.rs | 281 ++++++++++ crates/ide-db/src/lib.rs | 1 + crates/ide-db/src/rename.rs | 17 + crates/ide-db/src/rust_doc.rs | 169 ++++++ crates/ide-db/src/search.rs | 10 +- .../src/syntax_helpers/format_string.rs | 20 +- crates/ide-db/src/syntax_helpers/node_ext.rs | 6 +- .../src/handlers/macro_error.rs | 6 +- .../src/handlers/mismatched_arg_count.rs | 108 +++- .../src/handlers/missing_match_arms.rs | 4 + .../src/handlers/mutability_errors.rs | 25 +- .../src/handlers/no_such_field.rs | 101 +++- .../src/handlers/undeclared_label.rs | 19 + .../src/handlers/useless_braces.rs | 21 + crates/ide-diagnostics/src/lib.rs | 10 +- crates/ide/src/annotations.rs | 3 +- crates/ide/src/doc_links.rs | 5 +- crates/ide/src/doc_links/tests.rs | 7 +- crates/ide/src/hover/render.rs | 23 +- crates/ide/src/hover/tests.rs | 20 + crates/ide/src/inlay_hints.rs | 33 +- crates/ide/src/inlay_hints/adjustment.rs | 20 +- crates/ide/src/inlay_hints/bind_pat.rs | 1 + crates/ide/src/inlay_hints/binding_mode.rs | 6 +- crates/ide/src/inlay_hints/chaining.rs | 32 +- crates/ide/src/inlay_hints/closing_brace.rs | 1 + .../ide/src/inlay_hints/closure_captures.rs | 38 +- crates/ide/src/inlay_hints/closure_ret.rs | 1 + crates/ide/src/inlay_hints/discriminant.rs | 1 + crates/ide/src/inlay_hints/fn_lifetime_fn.rs | 3 + crates/ide/src/inlay_hints/implicit_static.rs | 3 +- crates/ide/src/inlay_hints/param_name.rs | 1 + crates/ide/src/lib.rs | 9 +- crates/ide/src/navigation_target.rs | 11 +- crates/ide/src/rename.rs | 29 + crates/ide/src/runnables.rs | 3 +- crates/ide/src/signature_help.rs | 29 +- crates/ide/src/static_index.rs | 2 + crates/ide/src/status.rs | 1 + crates/ide/src/syntax_highlighting/format.rs | 1 + .../ide/src/syntax_highlighting/highlight.rs | 1 + crates/ide/src/syntax_highlighting/inject.rs | 6 +- .../test_data/highlight_strings.html | 58 +- crates/ide/src/syntax_highlighting/tests.rs | 23 +- crates/ide/src/typing.rs | 248 ++++++++- crates/intern/Cargo.toml | 1 - crates/intern/src/lib.rs | 6 +- crates/parser/src/grammar/expressions/atom.rs | 88 ++- crates/parser/src/lexed_str.rs | 1 + crates/parser/src/syntax_kind/generated.rs | 19 +- .../parser/inline/ok/0132_box_expr.rast | 90 ---- .../parser/inline/ok/0132_box_expr.rs | 5 - .../parser/inline/ok/0207_builtin_expr.rast | 105 ++++ .../parser/inline/ok/0207_builtin_expr.rs | 5 + crates/proc-macro-test/build.rs | 4 + crates/project-model/src/rustc_cfg.rs | 91 +++- crates/project-model/src/sysroot.rs | 11 +- crates/project-model/src/workspace.rs | 97 ++-- .../cargo_hello_world_project_model.txt | 8 +- ...project_model_with_selective_overrides.txt | 8 +- ..._project_model_with_wildcard_overrides.txt | 8 +- ...rust_project_hello_world_project_model.txt | 44 +- crates/rust-analyzer/Cargo.toml | 1 - crates/rust-analyzer/src/caps.rs | 12 +- .../rust-analyzer/src/cli/analysis_stats.rs | 12 +- crates/rust-analyzer/src/cli/lsif.rs | 2 +- crates/rust-analyzer/src/cli/scip.rs | 2 +- crates/rust-analyzer/src/config.rs | 24 +- crates/rust-analyzer/src/diagnostics.rs | 40 +- .../rust-analyzer/src/diagnostics/to_proto.rs | 4 +- crates/rust-analyzer/src/dispatch.rs | 2 +- crates/rust-analyzer/src/global_state.rs | 61 ++- .../src/handlers/notification.rs | 23 +- crates/rust-analyzer/src/handlers/request.rs | 155 ++++-- crates/rust-analyzer/src/lib.rs | 30 +- crates/rust-analyzer/src/lsp.rs | 29 + .../src/{lsp_ext.rs => lsp/ext.rs} | 4 +- .../rust-analyzer/src/{ => lsp}/from_proto.rs | 4 +- .../src/{ => lsp}/semantic_tokens.rs | 0 .../rust-analyzer/src/{ => lsp}/to_proto.rs | 146 +++-- .../src/{lsp_utils.rs => lsp/utils.rs} | 4 +- crates/rust-analyzer/src/main_loop.rs | 173 +++--- crates/rust-analyzer/src/markdown.rs | 165 ------ crates/rust-analyzer/src/reload.rs | 1 + crates/rust-analyzer/tests/slow-tests/main.rs | 5 +- .../rust-analyzer/tests/slow-tests/support.rs | 6 +- crates/rust-analyzer/tests/slow-tests/tidy.rs | 8 +- crates/syntax/rust.ungram | 22 +- crates/syntax/src/ast/generated/nodes.rs | 169 +++++- crates/syntax/src/ast/prec.rs | 19 +- crates/syntax/src/tests/ast_src.rs | 19 +- crates/syntax/src/tests/sourcegen_ast.rs | 20 +- crates/test-utils/src/minicore.rs | 103 +++- crates/vfs-notify/Cargo.toml | 2 +- crates/vfs/src/lib.rs | 5 + docs/dev/architecture.md | 2 +- docs/dev/lsp-extensions.md | 6 +- editors/code/package.json | 11 +- editors/code/src/commands.ts | 20 +- editors/code/src/config.ts | 4 +- editors/code/src/ctx.ts | 35 +- editors/code/src/util.ts | 16 - lib/lsp-server/Cargo.toml | 5 +- xtask/src/flags.rs | 20 +- xtask/src/metrics.rs | 61 +-- 195 files changed, 5763 insertions(+), 2740 deletions(-) create mode 100644 crates/hir-def/src/hir/format_args.rs create mode 100644 crates/ide-assists/src/handlers/bind_unused_param.rs create mode 100644 crates/ide-assists/src/handlers/into_to_qualified_from.rs create mode 100644 crates/ide-db/src/documentation.rs delete mode 100644 crates/parser/test_data/parser/inline/ok/0132_box_expr.rast delete mode 100644 crates/parser/test_data/parser/inline/ok/0132_box_expr.rs create mode 100644 crates/parser/test_data/parser/inline/ok/0207_builtin_expr.rast create mode 100644 crates/parser/test_data/parser/inline/ok/0207_builtin_expr.rs create mode 100644 crates/rust-analyzer/src/lsp.rs rename crates/rust-analyzer/src/{lsp_ext.rs => lsp/ext.rs} (99%) rename crates/rust-analyzer/src/{ => lsp}/from_proto.rs (97%) rename crates/rust-analyzer/src/{ => lsp}/semantic_tokens.rs (100%) rename crates/rust-analyzer/src/{ => lsp}/to_proto.rs (94%) rename crates/rust-analyzer/src/{lsp_utils.rs => lsp/utils.rs} (99%) delete mode 100644 crates/rust-analyzer/src/markdown.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 24745d1c806fe..c9ad7803951ad 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -8,4 +8,4 @@ lint = "clippy --all-targets -- -Aclippy::collapsible_if -Aclippy::needless_pass linker = "rust-lld" [env] -CARGO_WORKSPACE_DIR = { value = "", relative = true } \ No newline at end of file +CARGO_WORKSPACE_DIR = { value = "", relative = true } diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9f246098e767b..fb7b4b07f9843 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -86,12 +86,20 @@ jobs: - name: Test run: cargo test ${{ env.USE_SYSROOT_ABI }} -- --nocapture --quiet + - name: Switch to stable toolchain + run: | + rustup update --no-self-update stable + rustup component add --toolchain stable rust-src + rustup default stable + - name: Run analysis-stats on rust-analyzer if: matrix.os == 'ubuntu-latest' run: target/${{ matrix.target }}/debug/rust-analyzer analysis-stats . - name: Run analysis-stats on rust std library if: matrix.os == 'ubuntu-latest' + env: + RUSTC_BOOTSTRAP: 1 run: target/${{ matrix.target }}/debug/rust-analyzer analysis-stats --with-deps $(rustc --print sysroot)/lib/rustlib/src/rust/library/std # Weird targets to catch non-portable code diff --git a/.github/workflows/metrics.yaml b/.github/workflows/metrics.yaml index 260e45ff517c1..bbeccd1621df6 100644 --- a/.github/workflows/metrics.yaml +++ b/.github/workflows/metrics.yaml @@ -67,7 +67,7 @@ jobs: other_metrics: strategy: matrix: - names: [self, ripgrep, webrender, diesel] + names: [self, ripgrep-13.0.0, webrender-2022, diesel-1.4.8, hyper-0.14.18] runs-on: ubuntu-latest needs: [setup_cargo, build_metrics] @@ -92,7 +92,7 @@ jobs: key: ${{ runner.os }}-target-${{ github.sha }} - name: Collect metrics - run: cargo xtask metrics ${{ matrix.names }} + run: cargo xtask metrics "${{ matrix.names }}" - name: Upload metrics uses: actions/upload-artifact@v3 @@ -118,25 +118,30 @@ jobs: with: name: self-${{ github.sha }} - - name: Download ripgrep metrics + - name: Download ripgrep-13.0.0 metrics uses: actions/download-artifact@v3 with: - name: ripgrep-${{ github.sha }} + name: ripgrep-13.0.0-${{ github.sha }} - - name: Download webrender metrics + - name: Download webrender-2022 metrics uses: actions/download-artifact@v3 with: - name: webrender-${{ github.sha }} + name: webrender-2022-${{ github.sha }} - - name: Download diesel metrics + - name: Download diesel-1.4.8 metrics uses: actions/download-artifact@v3 with: - name: diesel-${{ github.sha }} + name: diesel-1.4.8-${{ github.sha }} + + - name: Download hyper-0.14.18 metrics + uses: actions/download-artifact@v3 + with: + name: hyper-0.14.18-${{ github.sha }} - name: Combine json run: | git clone --depth 1 https://$METRICS_TOKEN@github.com/rust-analyzer/metrics.git - jq -s ".[0] * .[1] * .[2] * .[3] * .[4]" build.json self.json ripgrep.json webrender.json diesel.json -c >> metrics/metrics.json + jq -s ".[0] * .[1] * .[2] * .[3] * .[4] * .[5]" build.json self.json ripgrep-13.0.0.json webrender-2022.json diesel-1.4.8.json hyper-0.14.18.json -c >> metrics/metrics.json cd metrics git add . git -c user.name=Bot -c user.email=dummy@example.com commit --message 📈 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 43681c785fdc0..b5dbe30c3221c 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -128,6 +128,8 @@ jobs: - name: Run analysis-stats on rust std library if: matrix.target == 'x86_64-unknown-linux-gnu' + env: + RUSTC_BOOTSTRAP: 1 run: target/${{ matrix.target }}/release/rust-analyzer analysis-stats --with-deps $(rustc --print sysroot)/lib/rustlib/src/rust/library/std - name: Upload artifacts diff --git a/Cargo.lock b/Cargo.lock index 49e96236c5158..bd6554bf8899a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -381,14 +381,14 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.19" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9" +checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" dependencies = [ "cfg-if", "libc", - "redox_syscall", - "windows-sys 0.42.0", + "redox_syscall 0.3.5", + "windows-sys 0.48.0", ] [[package]] @@ -541,6 +541,7 @@ dependencies = [ "mbe", "once_cell", "profile", + "ra-ap-rustc_parse_format", "rustc-hash", "smallvec", "stdx", @@ -854,7 +855,6 @@ version = "0.0.0" dependencies = [ "dashmap", "hashbrown 0.12.3", - "once_cell", "rustc-hash", "triomphe", ] @@ -999,7 +999,7 @@ checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "lsp-server" -version = "0.7.3" +version = "0.7.4" dependencies = [ "crossbeam-channel", "log", @@ -1010,9 +1010,9 @@ dependencies = [ [[package]] name = "lsp-server" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72417faa455bfb4e5bf14b157d8e2ca2ed74b4e89b8cf42ea2d864825ae5c8a2" +checksum = "b52dccdf3302eefab8c8a1273047f0a3c3dca4b527c8458d00c09484c8371928" dependencies = [ "crossbeam-channel", "log", @@ -1149,20 +1149,21 @@ checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" [[package]] name = "notify" -version = "5.1.0" +version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ea850aa68a06e48fdb069c0ec44d0d64c8dbffa49bf3b6f7f0a901fdea1ba9" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.3.2", "crossbeam-channel", "filetime", "fsevent-sys", "inotify", "kqueue", "libc", + "log", "mio", "walkdir", - "windows-sys 0.42.0", + "windows-sys 0.48.0", ] [[package]] @@ -1251,7 +1252,7 @@ dependencies = [ "cfg-if", "instant", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "winapi", ] @@ -1264,7 +1265,7 @@ checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "windows-sys 0.42.0", ] @@ -1481,16 +1482,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "ra-ap-rustc_index" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07b5fa61d34da18e148dc3a81f654488ea07f40938d8aefb17f8b64bb78c6120" +dependencies = [ + "arrayvec", + "smallvec", +] + [[package]] name = "ra-ap-rustc_lexer" -version = "0.1.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1c145702ed3f237918e512685185dc8a4d0edc3a5326c63d20361d8ba9b45b3" +checksum = "f2e2f6b48422e4eed5218277ab7cc9733e60dd8f3167f4f36a49a0cafe4dc195" dependencies = [ - "unic-emoji-char", + "unicode-properties", "unicode-xid", ] +[[package]] +name = "ra-ap-rustc_parse_format" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7369ad01cc79f9e3513c9f6a6326f6b980100e4862a7ac71b9991c88108bb" +dependencies = [ + "ra-ap-rustc_index", + "ra-ap-rustc_lexer", +] + [[package]] name = "rayon" version = "1.7.0" @@ -1522,6 +1543,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "rowan" version = "0.15.11" @@ -1545,7 +1575,6 @@ dependencies = [ "crossbeam-channel", "dissimilar", "expect-test", - "filetime", "flycheck", "hir", "hir-def", @@ -1555,7 +1584,7 @@ dependencies = [ "ide-ssr", "itertools", "load-cargo", - "lsp-server 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "lsp-server 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", "lsp-types", "mbe", "mimalloc", @@ -2056,47 +2085,6 @@ version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e5df347f0bf3ec1d670aad6ca5c6a1859cd9ea61d2113125794654ccced68f" -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-emoji-char" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b07221e68897210270a38bde4babb655869637af0f69407f96053a34f76494d" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - [[package]] name = "unicase" version = "2.6.0" @@ -2127,6 +2115,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-properties" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f91c8b21fbbaa18853c3d0801c78f4fc94cdb976699bb03e832e75f7fd22f0" + [[package]] name = "unicode-segmentation" version = "1.10.1" diff --git a/Cargo.toml b/Cargo.toml index 5eb59d6db1163..cab88fc18cec6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,7 +86,7 @@ proc-macro-test = { path = "./crates/proc-macro-test" } # In-tree crates that are published separately and follow semver. See lib/README.md line-index = { version = "0.1.0-pre.1" } la-arena = { version = "0.3.1" } -lsp-server = { version = "0.7.3" } +lsp-server = { version = "0.7.4" } # non-local crates smallvec = { version = "1.10.0", features = [ @@ -97,11 +97,15 @@ smallvec = { version = "1.10.0", features = [ smol_str = "0.2.0" nohash-hasher = "0.2.0" text-size = "1.1.0" -# See https://github.com/serde-rs/serde/issues/2538#issuecomment-1684517372 for why we pin serde -serde = { version = "1.0.156, < 1.0.172", features = ["derive"] } +serde = { version = "1.0.156", features = ["derive"] } serde_json = "1.0.96" triomphe = { version = "0.1.8", default-features = false, features = ["std"] } # can't upgrade due to dashmap depending on 0.12.3 currently hashbrown = { version = "0.12.3", features = ["inline-more"], default-features = false } -rustc_lexer = { version = "0.1.0", package = "ra-ap-rustc_lexer" } +rustc_lexer = { version = "0.10.0", package = "ra-ap-rustc_lexer" } +rustc_parse_format = { version = "0.10.0", package = "ra-ap-rustc_parse_format", default-features = false } + +# Upstream broke this for us so we can't update it +rustc_abi = { version = "0.0.20221221", package = "hkalbasi-rustc-ap-rustc_abi", default-features = false } +rustc_index = { version = "0.0.20221221", package = "hkalbasi-rustc-ap-rustc_index", default-features = false } diff --git a/crates/base-db/src/fixture.rs b/crates/base-db/src/fixture.rs index aaac0fc37907a..3f5ccb621c767 100644 --- a/crates/base-db/src/fixture.rs +++ b/crates/base-db/src/fixture.rs @@ -179,8 +179,8 @@ impl ChangeFixture { meta.edition, Some(crate_name.clone().into()), version, - meta.cfg, - Default::default(), + meta.cfg.clone(), + Some(meta.cfg), meta.env, false, origin, @@ -200,7 +200,7 @@ impl ChangeFixture { } else if meta.path == "/main.rs" || meta.path == "/lib.rs" { assert!(default_crate_root.is_none()); default_crate_root = Some(file_id); - default_cfg = meta.cfg; + default_cfg.extend(meta.cfg.into_iter()); default_env.extend(meta.env.iter().map(|(x, y)| (x.to_owned(), y.to_owned()))); default_target_data_layout = meta.target_data_layout; } @@ -220,8 +220,8 @@ impl ChangeFixture { Edition::CURRENT, Some(CrateName::new("test").unwrap().into()), None, - default_cfg, - Default::default(), + default_cfg.clone(), + Some(default_cfg), default_env, false, CrateOrigin::Local { repo: None, name: None }, diff --git a/crates/cfg/src/lib.rs b/crates/cfg/src/lib.rs index 183b9b7d278c1..0aeb0b0505248 100644 --- a/crates/cfg/src/lib.rs +++ b/crates/cfg/src/lib.rs @@ -86,6 +86,32 @@ impl CfgOptions { } } +impl Extend for CfgOptions { + fn extend>(&mut self, iter: T) { + iter.into_iter().for_each(|cfg_flag| _ = self.enabled.insert(cfg_flag)); + } +} + +impl IntoIterator for CfgOptions { + type Item = as IntoIterator>::Item; + + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + as IntoIterator>::into_iter(self.enabled) + } +} + +impl<'a> IntoIterator for &'a CfgOptions { + type Item = <&'a FxHashSet as IntoIterator>::Item; + + type IntoIter = <&'a FxHashSet as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + <&FxHashSet as IntoIterator>::into_iter(&self.enabled) + } +} + #[derive(Default, Clone, Debug, PartialEq, Eq)] pub struct CfgDiff { // Invariants: No duplicates, no atom that's both in `enable` and `disable`. diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs index fbb943ccb99dd..2de719af92ce9 100644 --- a/crates/flycheck/src/lib.rs +++ b/crates/flycheck/src/lib.rs @@ -5,7 +5,9 @@ #![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)] use std::{ + ffi::OsString, fmt, io, + path::PathBuf, process::{ChildStderr, ChildStdout, Command, Stdio}, time::Duration, }; @@ -168,7 +170,7 @@ struct FlycheckActor { /// doesn't provide a way to read sub-process output without blocking, so we /// have to wrap sub-processes output handling in a thread and pass messages /// back over a channel. - cargo_handle: Option, + command_handle: Option, } enum Event { @@ -184,7 +186,7 @@ impl FlycheckActor { workspace_root: AbsPathBuf, ) -> FlycheckActor { tracing::info!(%id, ?workspace_root, "Spawning flycheck"); - FlycheckActor { id, sender, config, root: workspace_root, cargo_handle: None } + FlycheckActor { id, sender, config, root: workspace_root, command_handle: None } } fn report_progress(&self, progress: Progress) { @@ -192,7 +194,7 @@ impl FlycheckActor { } fn next_event(&self, inbox: &Receiver) -> Option { - let check_chan = self.cargo_handle.as_ref().map(|cargo| &cargo.receiver); + let check_chan = self.command_handle.as_ref().map(|cargo| &cargo.receiver); if let Ok(msg) = inbox.try_recv() { // give restarts a preference so check outputs don't block a restart or stop return Some(Event::RequestStateChange(msg)); @@ -221,21 +223,19 @@ impl FlycheckActor { } let command = self.check_command(); + let formatted_command = format!("{:?}", command); + tracing::debug!(?command, "will restart flycheck"); - match CargoHandle::spawn(command) { - Ok(cargo_handle) => { - tracing::debug!( - command = ?self.check_command(), - "did restart flycheck" - ); - self.cargo_handle = Some(cargo_handle); + match CommandHandle::spawn(command) { + Ok(command_handle) => { + tracing::debug!(command = formatted_command, "did restart flycheck"); + self.command_handle = Some(command_handle); self.report_progress(Progress::DidStart); } Err(error) => { self.report_progress(Progress::DidFailToRestart(format!( - "Failed to run the following command: {:?} error={}", - self.check_command(), - error + "Failed to run the following command: {} error={}", + formatted_command, error ))); } } @@ -244,12 +244,14 @@ impl FlycheckActor { tracing::debug!(flycheck_id = self.id, "flycheck finished"); // Watcher finished - let cargo_handle = self.cargo_handle.take().unwrap(); - let res = cargo_handle.join(); + let command_handle = self.command_handle.take().unwrap(); + let formatted_handle = format!("{:?}", command_handle); + + let res = command_handle.join(); if res.is_err() { tracing::error!( - "Flycheck failed to run the following command: {:?}", - self.check_command() + "Flycheck failed to run the following command: {}", + formatted_handle ); } self.report_progress(Progress::DidFinish(res)); @@ -284,12 +286,12 @@ impl FlycheckActor { } fn cancel_check_process(&mut self) { - if let Some(cargo_handle) = self.cargo_handle.take() { + if let Some(command_handle) = self.command_handle.take() { tracing::debug!( - command = ?self.check_command(), + command = ?command_handle, "did cancel flycheck" ); - cargo_handle.cancel(); + command_handle.cancel(); self.report_progress(Progress::DidCancel); } } @@ -391,19 +393,36 @@ impl Drop for JodGroupChild { } /// A handle to a cargo process used for fly-checking. -struct CargoHandle { +struct CommandHandle { /// The handle to the actual cargo process. As we cannot cancel directly from with /// a read syscall dropping and therefore terminating the process is our best option. child: JodGroupChild, thread: stdx::thread::JoinHandle>, receiver: Receiver, + program: OsString, + arguments: Vec, + current_dir: Option, } -impl CargoHandle { - fn spawn(mut command: Command) -> std::io::Result { +impl fmt::Debug for CommandHandle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CommandHandle") + .field("program", &self.program) + .field("arguments", &self.arguments) + .field("current_dir", &self.current_dir) + .finish() + } +} + +impl CommandHandle { + fn spawn(mut command: Command) -> std::io::Result { command.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null()); let mut child = command.group_spawn().map(JodGroupChild)?; + let program = command.get_program().into(); + let arguments = command.get_args().map(|arg| arg.into()).collect::>(); + let current_dir = command.get_current_dir().map(|arg| arg.to_path_buf()); + let stdout = child.0.inner().stdout.take().unwrap(); let stderr = child.0.inner().stderr.take().unwrap(); @@ -413,7 +432,7 @@ impl CargoHandle { .name("CargoHandle".to_owned()) .spawn(move || actor.run()) .expect("failed to spawn thread"); - Ok(CargoHandle { child, thread, receiver }) + Ok(CommandHandle { program, arguments, current_dir, child, thread, receiver }) } fn cancel(mut self) { diff --git a/crates/hir-def/Cargo.toml b/crates/hir-def/Cargo.toml index 30307deb79b88..8cf61ee04d4e6 100644 --- a/crates/hir-def/Cargo.toml +++ b/crates/hir-def/Cargo.toml @@ -31,8 +31,10 @@ smallvec.workspace = true hashbrown.workspace = true triomphe.workspace = true -rustc_abi = { version = "0.0.20221221", package = "hkalbasi-rustc-ap-rustc_abi", default-features = false } -rustc_index = { version = "0.0.20221221", package = "hkalbasi-rustc-ap-rustc_index", default-features = false } +rustc_abi.workspace = true +rustc_index.workspace = true +rustc_parse_format.workspace = true + # local deps stdx.workspace = true diff --git a/crates/hir-def/src/attr.rs b/crates/hir-def/src/attr.rs index a5db75a91ebdc..c6454eb9ea024 100644 --- a/crates/hir-def/src/attr.rs +++ b/crates/hir-def/src/attr.rs @@ -5,7 +5,7 @@ pub mod builtin; #[cfg(test)] mod tests; -use std::{hash::Hash, ops}; +use std::{hash::Hash, ops, slice::Iter as SliceIter}; use base_db::CrateId; use cfg::{CfgExpr, CfgOptions}; @@ -14,12 +14,11 @@ use hir_expand::{ attrs::{collect_attrs, Attr, AttrId, RawAttrs}, HirFileId, InFile, }; -use itertools::Itertools; use la_arena::{ArenaMap, Idx, RawIdx}; use mbe::DelimiterKind; use syntax::{ - ast::{self, HasAttrs, IsString}, - AstPtr, AstToken, SmolStr, TextRange, TextSize, + ast::{self, HasAttrs}, + AstPtr, SmolStr, }; use triomphe::Arc; @@ -33,26 +32,6 @@ use crate::{ LocalFieldId, Lookup, MacroId, VariantId, }; -/// Holds documentation -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Documentation(String); - -impl Documentation { - pub fn new(s: String) -> Self { - Documentation(s) - } - - pub fn as_str(&self) -> &str { - &self.0 - } -} - -impl From for String { - fn from(Documentation(string): Documentation) -> Self { - string - } -} - #[derive(Default, Debug, Clone, PartialEq, Eq)] pub struct Attrs(RawAttrs); @@ -221,33 +200,6 @@ impl Attrs { self.by_key("lang").string_value().and_then(|it| LangItem::from_str(it)) } - pub fn docs(&self) -> Option { - let docs = self.by_key("doc").attrs().filter_map(|attr| attr.string_value()); - let indent = doc_indent(self); - let mut buf = String::new(); - for doc in docs { - // str::lines doesn't yield anything for the empty string - if !doc.is_empty() { - buf.extend(Itertools::intersperse( - doc.lines().map(|line| { - line.char_indices() - .nth(indent) - .map_or(line, |(offset, _)| &line[offset..]) - .trim_end() - }), - "\n", - )); - } - buf.push('\n'); - } - buf.pop(); - if buf.is_empty() { - None - } else { - Some(Documentation(buf)) - } - } - pub fn has_doc_hidden(&self) -> bool { self.by_key("doc").tt_values().any(|tt| { tt.delimiter.kind == DelimiterKind::Parenthesis && @@ -299,7 +251,6 @@ impl Attrs { } } -use std::slice::Iter as SliceIter; #[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] pub enum DocAtom { /// eg. `#[doc(hidden)]` @@ -313,7 +264,6 @@ pub enum DocAtom { // Adapted from `CfgExpr` parsing code #[derive(Debug, Clone, PartialEq, Eq, Hash)] -// #[cfg_attr(test, derive(derive_arbitrary::Arbitrary))] pub enum DocExpr { Invalid, /// eg. `#[doc(hidden)]`, `#[doc(alias = "x")]` @@ -574,62 +524,6 @@ impl AttrsWithOwner { AttrSourceMap::new(owner.as_ref().map(|node| node as &dyn HasAttrs)) } - - pub fn docs_with_rangemap( - &self, - db: &dyn DefDatabase, - ) -> Option<(Documentation, DocsRangeMap)> { - let docs = - self.by_key("doc").attrs().filter_map(|attr| attr.string_value().map(|s| (s, attr.id))); - let indent = doc_indent(self); - let mut buf = String::new(); - let mut mapping = Vec::new(); - for (doc, idx) in docs { - if !doc.is_empty() { - let mut base_offset = 0; - for raw_line in doc.split('\n') { - let line = raw_line.trim_end(); - let line_len = line.len(); - let (offset, line) = match line.char_indices().nth(indent) { - Some((offset, _)) => (offset, &line[offset..]), - None => (0, line), - }; - let buf_offset = buf.len(); - buf.push_str(line); - mapping.push(( - TextRange::new(buf_offset.try_into().ok()?, buf.len().try_into().ok()?), - idx, - TextRange::at( - (base_offset + offset).try_into().ok()?, - line_len.try_into().ok()?, - ), - )); - buf.push('\n'); - base_offset += raw_line.len() + 1; - } - } else { - buf.push('\n'); - } - } - buf.pop(); - if buf.is_empty() { - None - } else { - Some((Documentation(buf), DocsRangeMap { mapping, source_map: self.source_map(db) })) - } - } -} - -fn doc_indent(attrs: &Attrs) -> usize { - attrs - .by_key("doc") - .attrs() - .filter_map(|attr| attr.string_value()) - .flat_map(|s| s.lines()) - .filter(|line| !line.chars().all(|c| c.is_whitespace())) - .map(|line| line.chars().take_while(|c| c.is_whitespace()).count()) - .min() - .unwrap_or(0) } #[derive(Debug)] @@ -673,7 +567,7 @@ impl AttrSourceMap { self.source_of_id(attr.id) } - fn source_of_id(&self, id: AttrId) -> InFile<&Either> { + pub fn source_of_id(&self, id: AttrId) -> InFile<&Either> { let ast_idx = id.ast_index(); let file_id = match self.mod_def_site_file_id { Some((file_id, def_site_cut)) if def_site_cut <= ast_idx => file_id, @@ -687,69 +581,6 @@ impl AttrSourceMap { } } -/// A struct to map text ranges from [`Documentation`] back to TextRanges in the syntax tree. -#[derive(Debug)] -pub struct DocsRangeMap { - source_map: AttrSourceMap, - // (docstring-line-range, attr_index, attr-string-range) - // a mapping from the text range of a line of the [`Documentation`] to the attribute index and - // the original (untrimmed) syntax doc line - mapping: Vec<(TextRange, AttrId, TextRange)>, -} - -impl DocsRangeMap { - /// Maps a [`TextRange`] relative to the documentation string back to its AST range - pub fn map(&self, range: TextRange) -> Option> { - let found = self.mapping.binary_search_by(|(probe, ..)| probe.ordering(range)).ok()?; - let (line_docs_range, idx, original_line_src_range) = self.mapping[found]; - if !line_docs_range.contains_range(range) { - return None; - } - - let relative_range = range - line_docs_range.start(); - - let InFile { file_id, value: source } = self.source_map.source_of_id(idx); - match source { - Either::Left(attr) => { - let string = get_doc_string_in_attr(attr)?; - let text_range = string.open_quote_text_range()?; - let range = TextRange::at( - text_range.end() + original_line_src_range.start() + relative_range.start(), - string.syntax().text_range().len().min(range.len()), - ); - Some(InFile { file_id, value: range }) - } - Either::Right(comment) => { - let text_range = comment.syntax().text_range(); - let range = TextRange::at( - text_range.start() - + TextSize::try_from(comment.prefix().len()).ok()? - + original_line_src_range.start() - + relative_range.start(), - text_range.len().min(range.len()), - ); - Some(InFile { file_id, value: range }) - } - } - } -} - -fn get_doc_string_in_attr(it: &ast::Attr) -> Option { - match it.expr() { - // #[doc = lit] - Some(ast::Expr::Literal(lit)) => match lit.kind() { - ast::LiteralKind::String(it) => Some(it), - _ => None, - }, - // #[cfg_attr(..., doc = "", ...)] - None => { - // FIXME: See highlight injection for what to do here - None - } - _ => None, - } -} - #[derive(Debug, Clone, Copy)] pub struct AttrQuery<'attr> { attrs: &'attr Attrs, diff --git a/crates/hir-def/src/attr/builtin.rs b/crates/hir-def/src/attr/builtin.rs index 9bd0c30b105cf..152f05b2c3b67 100644 --- a/crates/hir-def/src/attr/builtin.rs +++ b/crates/hir-def/src/attr/builtin.rs @@ -8,7 +8,8 @@ //! name resolution, and `BUILTIN_ATTRIBUTES` is almost entirely unchanged from the original, to //! ease updating. -use once_cell::sync::OnceCell; +use std::sync::OnceLock; + use rustc_hash::FxHashMap; /// Ignored attribute namespaces used by tools. @@ -29,7 +30,7 @@ pub struct AttributeTemplate { } pub fn find_builtin_attr_idx(name: &str) -> Option { - static BUILTIN_LOOKUP_TABLE: OnceCell> = OnceCell::new(); + static BUILTIN_LOOKUP_TABLE: OnceLock> = OnceLock::new(); BUILTIN_LOOKUP_TABLE .get_or_init(|| { INERT_ATTRIBUTES.iter().map(|attr| attr.name).enumerate().map(|(a, b)| (b, a)).collect() diff --git a/crates/hir-def/src/body.rs b/crates/hir-def/src/body.rs index f8d492d0e5252..c0baf6011f792 100644 --- a/crates/hir-def/src/body.rs +++ b/crates/hir-def/src/body.rs @@ -65,6 +65,8 @@ pub type LabelSource = InFile; pub type FieldPtr = AstPtr; pub type FieldSource = InFile; +pub type PatFieldPtr = AstPtr; +pub type PatFieldSource = InFile; /// An item body together with the mapping from syntax nodes to HIR expression /// IDs. This is needed to go from e.g. a position in a file to the HIR @@ -90,8 +92,8 @@ pub struct BodySourceMap { /// We don't create explicit nodes for record fields (`S { record_field: 92 }`). /// Instead, we use id of expression (`92`) to identify the field. - field_map: FxHashMap, field_map_back: FxHashMap, + pat_field_map_back: FxHashMap, expansions: FxHashMap>, HirFileId>, @@ -164,9 +166,10 @@ impl Body { }; let module = def.module(db); let expander = Expander::new(db, file_id, module); - let (mut body, source_map) = + let (mut body, mut source_map) = Body::new(db, def, expander, params, body, module.krate, is_async_fn); body.shrink_to_fit(); + source_map.shrink_to_fit(); (Arc::new(body), Arc::new(source_map)) } @@ -375,9 +378,8 @@ impl BodySourceMap { self.field_map_back[&expr].clone() } - pub fn node_field(&self, node: InFile<&ast::RecordExprField>) -> Option { - let src = node.map(AstPtr::new); - self.field_map.get(&src).cloned() + pub fn pat_field_syntax(&self, pat: PatId) -> PatFieldSource { + self.pat_field_map_back[&pat].clone() } pub fn macro_expansion_expr(&self, node: InFile<&ast::MacroExpr>) -> Option { @@ -389,4 +391,29 @@ impl BodySourceMap { pub fn diagnostics(&self) -> &[BodyDiagnostic] { &self.diagnostics } + + fn shrink_to_fit(&mut self) { + let Self { + expr_map, + expr_map_back, + pat_map, + pat_map_back, + label_map, + label_map_back, + field_map_back, + pat_field_map_back, + expansions, + diagnostics, + } = self; + expr_map.shrink_to_fit(); + expr_map_back.shrink_to_fit(); + pat_map.shrink_to_fit(); + pat_map_back.shrink_to_fit(); + label_map.shrink_to_fit(); + label_map_back.shrink_to_fit(); + field_map_back.shrink_to_fit(); + pat_field_map_back.shrink_to_fit(); + expansions.shrink_to_fit(); + diagnostics.shrink_to_fit(); + } } diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs index 7071fcb93945e..cc02df80a8ffd 100644 --- a/crates/hir-def/src/body/lower.rs +++ b/crates/hir-def/src/body/lower.rs @@ -25,13 +25,20 @@ use triomphe::Arc; use crate::{ body::{Body, BodyDiagnostic, BodySourceMap, ExprPtr, LabelPtr, PatPtr}, + builtin_type::BuiltinUint, data::adt::StructKind, db::DefDatabase, expander::Expander, hir::{ - dummy_expr_id, Array, Binding, BindingAnnotation, BindingId, BindingProblems, CaptureBy, - ClosureKind, Expr, ExprId, Label, LabelId, Literal, LiteralOrConst, MatchArm, Movability, - Pat, PatId, RecordFieldPat, RecordLitField, Statement, + dummy_expr_id, + format_args::{ + self, FormatAlignment, FormatArgs, FormatArgsPiece, FormatArgument, FormatArgumentKind, + FormatArgumentsCollector, FormatCount, FormatDebugHex, FormatOptions, + FormatPlaceholder, FormatSign, FormatTrait, + }, + Array, Binding, BindingAnnotation, BindingId, BindingProblems, CaptureBy, ClosureKind, + Expr, ExprId, InlineAsm, Label, LabelId, Literal, LiteralOrConst, MatchArm, Movability, + OffsetOf, Pat, PatId, RecordFieldPat, RecordLitField, Statement, }, item_scope::BuiltinShadowMode, lang_item::LangItem, @@ -42,6 +49,8 @@ use crate::{ AdtId, BlockId, BlockLoc, ConstBlockLoc, DefWithBodyId, ModuleDefId, UnresolvedMacro, }; +type FxIndexSet = indexmap::IndexSet>; + pub(super) fn lower( db: &dyn DefDatabase, owner: DefWithBodyId, @@ -437,7 +446,6 @@ impl ExprCollector<'_> { None => self.missing_expr(), }; let src = self.expander.to_source(AstPtr::new(&field)); - self.source_map.field_map.insert(src.clone(), expr); self.source_map.field_map_back.insert(expr, src); Some(RecordLitField { name, expr }) }) @@ -579,11 +587,6 @@ impl ExprCollector<'_> { syntax_ptr, ) } - ast::Expr::BoxExpr(e) => { - let expr = self.collect_expr_opt(e.expr()); - self.alloc_expr(Expr::Box { expr }, syntax_ptr) - } - ast::Expr::ArrayExpr(e) => { let kind = e.kind(); @@ -653,6 +656,16 @@ impl ExprCollector<'_> { } } ast::Expr::UnderscoreExpr(_) => self.alloc_expr(Expr::Underscore, syntax_ptr), + ast::Expr::AsmExpr(e) => { + let e = self.collect_expr_opt(e.expr()); + self.alloc_expr(Expr::InlineAsm(InlineAsm { e }), syntax_ptr) + } + ast::Expr::OffsetOfExpr(e) => { + let container = Interned::new(TypeRef::from_ast_opt(&self.ctx(), e.ty())); + let fields = e.fields().map(|it| it.as_name()).collect(); + self.alloc_expr(Expr::OffsetOf(OffsetOf { container, fields }), syntax_ptr) + } + ast::Expr::FormatArgsExpr(f) => self.collect_format_args(f, syntax_ptr), }) } @@ -663,6 +676,7 @@ impl ExprCollector<'_> { let result_expr_id = self.alloc_expr(Expr::Missing, syntax_ptr); let prev_binding_owner = self.current_binding_owner.take(); self.current_binding_owner = Some(result_expr_id); + (result_expr_id, prev_binding_owner) } @@ -744,7 +758,27 @@ impl ExprCollector<'_> { fn collect_while_loop(&mut self, syntax_ptr: AstPtr, e: ast::WhileExpr) -> ExprId { let label = e.label().map(|label| self.collect_label(label)); let body = self.collect_labelled_block_opt(label, e.loop_body()); - let condition = self.collect_expr_opt(e.condition()); + + // Labels can also be used in the condition expression, like this: + // ``` + // fn main() { + // let mut optional = Some(0); + // 'my_label: while let Some(a) = match optional { + // None => break 'my_label, + // Some(val) => Some(val), + // } { + // println!("{}", a); + // optional = None; + // } + // } + // ``` + let condition = match label { + Some(label) => { + self.with_labeled_rib(label, |this| this.collect_expr_opt(e.condition())) + } + None => self.collect_expr_opt(e.condition()), + }; + let break_expr = self.alloc_expr(Expr::Break { expr: None, label: None }, syntax_ptr.clone()); let if_expr = self.alloc_expr( @@ -1295,23 +1329,21 @@ impl ExprCollector<'_> { ast::Pat::RecordPat(p) => { let path = p.path().and_then(|path| self.expander.parse_path(self.db, path)).map(Box::new); - let args = p - .record_pat_field_list() - .expect("every struct should have a field list") + let record_pat_field_list = + &p.record_pat_field_list().expect("every struct should have a field list"); + let args = record_pat_field_list .fields() .filter_map(|f| { let ast_pat = f.pat()?; let pat = self.collect_pat(ast_pat, binding_list); let name = f.field_name()?.as_name(); + let src = self.expander.to_source(AstPtr::new(&f)); + self.source_map.pat_field_map_back.insert(pat, src); Some(RecordFieldPat { name, pat }) }) .collect(); - let ellipsis = p - .record_pat_field_list() - .expect("every struct should have a field list") - .rest_pat() - .is_some(); + let ellipsis = record_pat_field_list.rest_pat().is_some(); Pat::Record { path, args, ellipsis } } @@ -1531,6 +1563,401 @@ impl ExprCollector<'_> { } } // endregion: labels + + // region: format + fn expand_macros_to_string(&mut self, expr: ast::Expr) -> Option<(ast::String, bool)> { + let m = match expr { + ast::Expr::MacroExpr(m) => m, + ast::Expr::Literal(l) => { + return match l.kind() { + ast::LiteralKind::String(s) => Some((s, true)), + _ => None, + } + } + _ => return None, + }; + let e = m.macro_call()?; + let macro_ptr = AstPtr::new(&e); + let (exp, _) = self.collect_macro_call(e, macro_ptr, true, |this, expansion| { + expansion.and_then(|it| this.expand_macros_to_string(it)) + })?; + Some((exp, false)) + } + + fn collect_format_args( + &mut self, + f: ast::FormatArgsExpr, + syntax_ptr: AstPtr, + ) -> ExprId { + let mut args = FormatArgumentsCollector::new(); + f.args().for_each(|arg| { + args.add(FormatArgument { + kind: match arg.name() { + Some(name) => FormatArgumentKind::Named(name.as_name()), + None => FormatArgumentKind::Normal, + }, + expr: self.collect_expr_opt(arg.expr()), + }); + }); + let template = f.template(); + let fmt_snippet = template.as_ref().map(ToString::to_string); + let fmt = match template.and_then(|it| self.expand_macros_to_string(it)) { + Some((s, is_direct_literal)) => { + format_args::parse(&s, fmt_snippet, args, is_direct_literal, |name| { + self.alloc_expr_desugared(Expr::Path(Path::from(name))) + }) + } + None => FormatArgs { template: Default::default(), arguments: args.finish() }, + }; + + // Create a list of all _unique_ (argument, format trait) combinations. + // E.g. "{0} {0:x} {0} {1}" -> [(0, Display), (0, LowerHex), (1, Display)] + let mut argmap = FxIndexSet::default(); + for piece in fmt.template.iter() { + let FormatArgsPiece::Placeholder(placeholder) = piece else { continue }; + if let Ok(index) = placeholder.argument.index { + argmap.insert((index, ArgumentType::Format(placeholder.format_trait))); + } + } + + let lit_pieces = + fmt.template + .iter() + .enumerate() + .filter_map(|(i, piece)| { + match piece { + FormatArgsPiece::Literal(s) => Some( + self.alloc_expr_desugared(Expr::Literal(Literal::String(s.clone()))), + ), + &FormatArgsPiece::Placeholder(_) => { + // Inject empty string before placeholders when not already preceded by a literal piece. + if i == 0 + || matches!(fmt.template[i - 1], FormatArgsPiece::Placeholder(_)) + { + Some(self.alloc_expr_desugared(Expr::Literal(Literal::String( + "".into(), + )))) + } else { + None + } + } + } + }) + .collect(); + let lit_pieces = self.alloc_expr_desugared(Expr::Array(Array::ElementList { + elements: lit_pieces, + is_assignee_expr: false, + })); + let lit_pieces = self.alloc_expr_desugared(Expr::Ref { + expr: lit_pieces, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }); + let format_options = { + // Generate: + // &[format_spec_0, format_spec_1, format_spec_2] + let elements = fmt + .template + .iter() + .filter_map(|piece| { + let FormatArgsPiece::Placeholder(placeholder) = piece else { return None }; + Some(self.make_format_spec(placeholder, &mut argmap)) + }) + .collect(); + let array = self.alloc_expr_desugared(Expr::Array(Array::ElementList { + elements, + is_assignee_expr: false, + })); + self.alloc_expr_desugared(Expr::Ref { + expr: array, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }) + }; + let arguments = &*fmt.arguments.arguments; + + let args = if arguments.is_empty() { + let expr = self.alloc_expr_desugared(Expr::Array(Array::ElementList { + elements: Box::default(), + is_assignee_expr: false, + })); + self.alloc_expr_desugared(Expr::Ref { + expr, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }) + } else { + // Generate: + // &match (&arg0, &arg1, &…) { + // args => [ + // ::new_display(args.0), + // ::new_lower_hex(args.1), + // ::new_debug(args.0), + // … + // ] + // } + let args = argmap + .iter() + .map(|&(arg_index, ty)| { + let arg = self.alloc_expr_desugared(Expr::Ref { + expr: arguments[arg_index].expr, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }); + self.make_argument(arg, ty) + }) + .collect(); + let array = self.alloc_expr_desugared(Expr::Array(Array::ElementList { + elements: args, + is_assignee_expr: false, + })); + self.alloc_expr_desugared(Expr::Ref { + expr: array, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }) + }; + + // Generate: + // ::new_v1_formatted( + // lit_pieces, + // args, + // format_options, + // unsafe { ::core::fmt::UnsafeArg::new() } + // ) + + let Some(new_v1_formatted) = + LangItem::FormatArguments.ty_rel_path(self.db, self.krate, name![new_v1_formatted]) + else { + return self.missing_expr(); + }; + let Some(unsafe_arg_new) = + LangItem::FormatUnsafeArg.ty_rel_path(self.db, self.krate, name![new]) + else { + return self.missing_expr(); + }; + let new_v1_formatted = self.alloc_expr_desugared(Expr::Path(new_v1_formatted)); + + let unsafe_arg_new = self.alloc_expr_desugared(Expr::Path(unsafe_arg_new)); + let unsafe_arg_new = self.alloc_expr_desugared(Expr::Call { + callee: unsafe_arg_new, + args: Box::default(), + is_assignee_expr: false, + }); + let unsafe_arg_new = self.alloc_expr_desugared(Expr::Unsafe { + id: None, + statements: Box::default(), + tail: Some(unsafe_arg_new), + }); + + self.alloc_expr( + Expr::Call { + callee: new_v1_formatted, + args: Box::new([lit_pieces, args, format_options, unsafe_arg_new]), + is_assignee_expr: false, + }, + syntax_ptr, + ) + } + + /// Generate a hir expression for a format_args placeholder specification. + /// + /// Generates + /// + /// ```text + /// ::…, // alignment + /// …u32, // flags + /// , // width + /// , // precision + /// ) + /// ``` + fn make_format_spec( + &mut self, + placeholder: &FormatPlaceholder, + argmap: &mut FxIndexSet<(usize, ArgumentType)>, + ) -> ExprId { + let position = match placeholder.argument.index { + Ok(arg_index) => { + let (i, _) = + argmap.insert_full((arg_index, ArgumentType::Format(placeholder.format_trait))); + self.alloc_expr_desugared(Expr::Literal(Literal::Uint( + i as u128, + Some(BuiltinUint::Usize), + ))) + } + Err(_) => self.missing_expr(), + }; + let &FormatOptions { + ref width, + ref precision, + alignment, + fill, + sign, + alternate, + zero_pad, + debug_hex, + } = &placeholder.format_options; + let fill = self.alloc_expr_desugared(Expr::Literal(Literal::Char(fill.unwrap_or(' ')))); + + let align = { + let align = LangItem::FormatAlignment.ty_rel_path( + self.db, + self.krate, + match alignment { + Some(FormatAlignment::Left) => name![Left], + Some(FormatAlignment::Right) => name![Right], + Some(FormatAlignment::Center) => name![Center], + None => name![Unknown], + }, + ); + match align { + Some(path) => self.alloc_expr_desugared(Expr::Path(path)), + None => self.missing_expr(), + } + }; + // This needs to match `Flag` in library/core/src/fmt/rt.rs. + let flags: u32 = ((sign == Some(FormatSign::Plus)) as u32) + | ((sign == Some(FormatSign::Minus)) as u32) << 1 + | (alternate as u32) << 2 + | (zero_pad as u32) << 3 + | ((debug_hex == Some(FormatDebugHex::Lower)) as u32) << 4 + | ((debug_hex == Some(FormatDebugHex::Upper)) as u32) << 5; + let flags = self.alloc_expr_desugared(Expr::Literal(Literal::Uint( + flags as u128, + Some(BuiltinUint::U32), + ))); + let precision = self.make_count(&precision, argmap); + let width = self.make_count(&width, argmap); + + let format_placeholder_new = { + let format_placeholder_new = + LangItem::FormatPlaceholder.ty_rel_path(self.db, self.krate, name![new]); + match format_placeholder_new { + Some(path) => self.alloc_expr_desugared(Expr::Path(path)), + None => self.missing_expr(), + } + }; + + self.alloc_expr_desugared(Expr::Call { + callee: format_placeholder_new, + args: Box::new([position, fill, align, flags, precision, width]), + is_assignee_expr: false, + }) + } + + /// Generate a hir expression for a format_args Count. + /// + /// Generates: + /// + /// ```text + /// ::Is(…) + /// ``` + /// + /// or + /// + /// ```text + /// ::Param(…) + /// ``` + /// + /// or + /// + /// ```text + /// ::Implied + /// ``` + fn make_count( + &mut self, + count: &Option, + argmap: &mut FxIndexSet<(usize, ArgumentType)>, + ) -> ExprId { + match count { + Some(FormatCount::Literal(n)) => { + match LangItem::FormatCount.ty_rel_path(self.db, self.krate, name![Is]) { + Some(count_is) => { + let count_is = self.alloc_expr_desugared(Expr::Path(count_is)); + let args = self.alloc_expr_desugared(Expr::Literal(Literal::Uint( + *n as u128, + Some(BuiltinUint::Usize), + ))); + self.alloc_expr_desugared(Expr::Call { + callee: count_is, + args: Box::new([args]), + is_assignee_expr: false, + }) + } + None => self.missing_expr(), + } + } + Some(FormatCount::Argument(arg)) => { + if let Ok(arg_index) = arg.index { + let (i, _) = argmap.insert_full((arg_index, ArgumentType::Usize)); + + match LangItem::FormatCount.ty_rel_path(self.db, self.krate, name![Param]) { + Some(count_param) => { + let count_param = self.alloc_expr_desugared(Expr::Path(count_param)); + let args = self.alloc_expr_desugared(Expr::Literal(Literal::Uint( + i as u128, + Some(BuiltinUint::Usize), + ))); + self.alloc_expr_desugared(Expr::Call { + callee: count_param, + args: Box::new([args]), + is_assignee_expr: false, + }) + } + None => self.missing_expr(), + } + } else { + self.missing_expr() + } + } + None => match LangItem::FormatCount.ty_rel_path(self.db, self.krate, name![Implied]) { + Some(count_param) => self.alloc_expr_desugared(Expr::Path(count_param)), + None => self.missing_expr(), + }, + } + } + + /// Generate a hir expression representing an argument to a format_args invocation. + /// + /// Generates: + /// + /// ```text + /// ::new_…(arg) + /// ``` + fn make_argument(&mut self, arg: ExprId, ty: ArgumentType) -> ExprId { + use ArgumentType::*; + use FormatTrait::*; + match LangItem::FormatArgument.ty_rel_path( + self.db, + self.krate, + match ty { + Format(Display) => name![new_display], + Format(Debug) => name![new_debug], + Format(LowerExp) => name![new_lower_exp], + Format(UpperExp) => name![new_upper_exp], + Format(Octal) => name![new_octal], + Format(Pointer) => name![new_pointer], + Format(Binary) => name![new_binary], + Format(LowerHex) => name![new_lower_hex], + Format(UpperHex) => name![new_upper_hex], + Usize => name![from_usize], + }, + ) { + Some(new_fn) => { + let new_fn = self.alloc_expr_desugared(Expr::Path(new_fn)); + self.alloc_expr_desugared(Expr::Call { + callee: new_fn, + args: Box::new([arg]), + is_assignee_expr: false, + }) + } + None => self.missing_expr(), + } + } + // endregion: format } fn pat_literal_to_hir(lit: &ast::LiteralPat) -> Option<(Literal, ast::Literal)> { @@ -1606,3 +2033,9 @@ fn comma_follows_token(t: Option) -> bool { (|| syntax::algo::skip_trivia_token(t?.next_token()?, syntax::Direction::Next))() .map_or(false, |it| it.kind() == syntax::T![,]) } + +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +enum ArgumentType { + Format(FormatTrait), + Usize, +} diff --git a/crates/hir-def/src/body/pretty.rs b/crates/hir-def/src/body/pretty.rs index 5d71abe37cc5e..fad4d7a4da66e 100644 --- a/crates/hir-def/src/body/pretty.rs +++ b/crates/hir-def/src/body/pretty.rs @@ -2,7 +2,7 @@ use std::fmt::{self, Write}; -use hir_expand::db::ExpandDatabase; +use itertools::Itertools; use syntax::ast::HasName; use crate::{ @@ -51,8 +51,7 @@ pub(super) fn print_body_hir(db: &dyn DefDatabase, body: &Body, owner: DefWithBo } }; - let mut p = - Printer { db: db.upcast(), body, buf: header, indent_level: 0, needs_indent: false }; + let mut p = Printer { db, body, buf: header, indent_level: 0, needs_indent: false }; if let DefWithBodyId::FunctionId(it) = owner { p.buf.push('('); body.params.iter().zip(&db.function_data(it).params).for_each(|(¶m, ty)| { @@ -76,8 +75,7 @@ pub(super) fn print_expr_hir( _owner: DefWithBodyId, expr: ExprId, ) -> String { - let mut p = - Printer { db: db.upcast(), body, buf: String::new(), indent_level: 0, needs_indent: false }; + let mut p = Printer { db, body, buf: String::new(), indent_level: 0, needs_indent: false }; p.print_expr(expr); p.buf } @@ -98,7 +96,7 @@ macro_rules! wln { } struct Printer<'a> { - db: &'a dyn ExpandDatabase, + db: &'a dyn DefDatabase, body: &'a Body, buf: String, indent_level: usize, @@ -142,9 +140,14 @@ impl Printer<'_> { } fn newline(&mut self) { - match self.buf.chars().rev().find(|ch| *ch != ' ') { - Some('\n') | None => {} - _ => writeln!(self).unwrap(), + match self.buf.chars().rev().find_position(|ch| *ch != ' ') { + Some((_, '\n')) | None => {} + Some((idx, _)) => { + if idx != 0 { + self.buf.drain(self.buf.len() - idx..); + } + writeln!(self).unwrap() + } } } @@ -154,6 +157,19 @@ impl Printer<'_> { match expr { Expr::Missing => w!(self, "�"), Expr::Underscore => w!(self, "_"), + Expr::InlineAsm(_) => w!(self, "builtin#asm(_)"), + Expr::OffsetOf(offset_of) => { + w!(self, "builtin#offset_of("); + self.print_type_ref(&offset_of.container); + w!( + self, + ", {})", + offset_of + .fields + .iter() + .format_with(".", |field, f| f(&field.display(self.db.upcast()))) + ); + } Expr::Path(path) => self.print_path(path), Expr::If { condition, then_branch, else_branch } => { w!(self, "if "); @@ -173,7 +189,7 @@ impl Printer<'_> { } Expr::Loop { body, label } => { if let Some(lbl) = label { - w!(self, "{}: ", self.body[*lbl].name.display(self.db)); + w!(self, "{}: ", self.body[*lbl].name.display(self.db.upcast())); } w!(self, "loop "); self.print_expr(*body); @@ -193,7 +209,7 @@ impl Printer<'_> { } Expr::MethodCall { receiver, method_name, args, generic_args } => { self.print_expr(*receiver); - w!(self, ".{}", method_name.display(self.db)); + w!(self, ".{}", method_name.display(self.db.upcast())); if let Some(args) = generic_args { w!(self, "::<"); print_generic_args(self.db, args, self).unwrap(); @@ -231,13 +247,13 @@ impl Printer<'_> { Expr::Continue { label } => { w!(self, "continue"); if let Some(lbl) = label { - w!(self, " {}", self.body[*lbl].name.display(self.db)); + w!(self, " {}", self.body[*lbl].name.display(self.db.upcast())); } } Expr::Break { expr, label } => { w!(self, "break"); if let Some(lbl) = label { - w!(self, " {}", self.body[*lbl].name.display(self.db)); + w!(self, " {}", self.body[*lbl].name.display(self.db.upcast())); } if let Some(expr) = expr { self.whitespace(); @@ -276,7 +292,7 @@ impl Printer<'_> { w!(self, "{{"); self.indented(|p| { for field in &**fields { - w!(p, "{}: ", field.name.display(self.db)); + w!(p, "{}: ", field.name.display(self.db.upcast())); p.print_expr(field.expr); wln!(p, ","); } @@ -293,7 +309,7 @@ impl Printer<'_> { } Expr::Field { expr, name } => { self.print_expr(*expr); - w!(self, ".{}", name.display(self.db)); + w!(self, ".{}", name.display(self.db.upcast())); } Expr::Await { expr } => { self.print_expr(*expr); @@ -431,7 +447,8 @@ impl Printer<'_> { } Expr::Literal(lit) => self.print_literal(lit), Expr::Block { id: _, statements, tail, label } => { - let label = label.map(|lbl| format!("{}: ", self.body[lbl].name.display(self.db))); + let label = + label.map(|lbl| format!("{}: ", self.body[lbl].name.display(self.db.upcast()))); self.print_block(label.as_deref(), statements, tail); } Expr::Unsafe { id: _, statements, tail } => { @@ -507,7 +524,7 @@ impl Printer<'_> { w!(self, " {{"); self.indented(|p| { for arg in args.iter() { - w!(p, "{}: ", arg.name.display(self.db)); + w!(p, "{}: ", arg.name.display(self.db.upcast())); p.print_pat(arg.pat); wln!(p, ","); } @@ -666,6 +683,6 @@ impl Printer<'_> { BindingAnnotation::Ref => "ref ", BindingAnnotation::RefMut => "ref mut ", }; - w!(self, "{}{}", mode, name.display(self.db)); + w!(self, "{}{}", mode, name.display(self.db.upcast())); } } diff --git a/crates/hir-def/src/body/tests.rs b/crates/hir-def/src/body/tests.rs index d5582011645dc..1658757d2b6ed 100644 --- a/crates/hir-def/src/body/tests.rs +++ b/crates/hir-def/src/body/tests.rs @@ -1,13 +1,13 @@ mod block; use base_db::{fixture::WithFixture, SourceDatabase}; -use expect_test::Expect; +use expect_test::{expect, Expect}; use crate::{test_db::TestDB, ModuleDefId}; use super::*; -fn lower(ra_fixture: &str) -> Arc { +fn lower(ra_fixture: &str) -> (TestDB, Arc, DefWithBodyId) { let db = TestDB::with_files(ra_fixture); let krate = db.crate_graph().iter().next().unwrap(); @@ -21,8 +21,10 @@ fn lower(ra_fixture: &str) -> Arc { } } } + let fn_def = fn_def.unwrap().into(); - db.body(fn_def.unwrap().into()) + let body = db.body(fn_def); + (db, body, fn_def) } fn def_map_at(ra_fixture: &str) -> String { @@ -138,3 +140,84 @@ mod m { "#, ); } + +#[test] +fn desugar_builtin_format_args() { + // Regression test for a path resolution bug introduced with inner item handling. + let (db, body, def) = lower( + r#" +//- minicore: fmt +fn main() { + let are = "are"; + let count = 10; + builtin#format_args("hello {count:02} {} friends, we {are:?} {0}{last}", "fancy", last = "!"); +} +"#, + ); + + expect![[r#" + fn main() { + let are = "are"; + let count = 10; + builtin#lang(Arguments::new_v1_formatted)( + &[ + "\"hello ", " ", " friends, we ", " ", "", "\"", + ], + &[ + builtin#lang(Argument::new_display)( + &count, + ), builtin#lang(Argument::new_display)( + &"fancy", + ), builtin#lang(Argument::new_debug)( + &are, + ), builtin#lang(Argument::new_display)( + &"!", + ), + ], + &[ + builtin#lang(Placeholder::new)( + 0usize, + ' ', + builtin#lang(Alignment::Unknown), + 8u32, + builtin#lang(Count::Implied), + builtin#lang(Count::Is)( + 2usize, + ), + ), builtin#lang(Placeholder::new)( + 1usize, + ' ', + builtin#lang(Alignment::Unknown), + 0u32, + builtin#lang(Count::Implied), + builtin#lang(Count::Implied), + ), builtin#lang(Placeholder::new)( + 2usize, + ' ', + builtin#lang(Alignment::Unknown), + 0u32, + builtin#lang(Count::Implied), + builtin#lang(Count::Implied), + ), builtin#lang(Placeholder::new)( + 1usize, + ' ', + builtin#lang(Alignment::Unknown), + 0u32, + builtin#lang(Count::Implied), + builtin#lang(Count::Implied), + ), builtin#lang(Placeholder::new)( + 3usize, + ' ', + builtin#lang(Alignment::Unknown), + 0u32, + builtin#lang(Count::Implied), + builtin#lang(Count::Implied), + ), + ], + unsafe { + builtin#lang(UnsafeArg::new)() + }, + ); + }"#]] + .assert_eq(&body.pretty_print(&db, def)) +} diff --git a/crates/hir-def/src/data/adt.rs b/crates/hir-def/src/data/adt.rs index c8df3f3f96a54..224f7328f8cdc 100644 --- a/crates/hir-def/src/data/adt.rs +++ b/crates/hir-def/src/data/adt.rs @@ -447,6 +447,7 @@ impl VariantData { } } + // FIXME: Linear lookup pub fn field(&self, name: &Name) -> Option { self.fields().iter().find_map(|(id, data)| if &data.name == name { Some(id) } else { None }) } diff --git a/crates/hir-def/src/find_path.rs b/crates/hir-def/src/find_path.rs index b60e790910503..b9c5ff72792af 100644 --- a/crates/hir-def/src/find_path.rs +++ b/crates/hir-def/src/find_path.rs @@ -37,6 +37,20 @@ pub fn find_path_prefixed( find_path_inner(db, item, from, Some(prefix_kind), prefer_no_std) } +#[derive(Copy, Clone, Debug)] +enum Stability { + Unstable, + Stable, +} +use Stability::*; + +fn zip_stability(a: Stability, b: Stability) -> Stability { + match (a, b) { + (Stable, Stable) => Stable, + _ => Unstable, + } +} + const MAX_PATH_LEN: usize = 15; #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -95,7 +109,8 @@ fn find_path_inner( MAX_PATH_LEN, prefixed, prefer_no_std || db.crate_supports_no_std(crate_root.krate), - ); + ) + .map(|(item, _)| item); } // - if the item is already in scope, return the name under which it is @@ -143,6 +158,7 @@ fn find_path_inner( prefer_no_std || db.crate_supports_no_std(crate_root.krate), scope_name, ) + .map(|(item, _)| item) } fn find_path_for_module( @@ -155,7 +171,7 @@ fn find_path_for_module( max_len: usize, prefixed: Option, prefer_no_std: bool, -) -> Option { +) -> Option<(ModPath, Stability)> { if max_len == 0 { return None; } @@ -165,19 +181,19 @@ fn find_path_for_module( let scope_name = find_in_scope(db, def_map, from, ItemInNs::Types(module_id.into())); if prefixed.is_none() { if let Some(scope_name) = scope_name { - return Some(ModPath::from_segments(PathKind::Plain, Some(scope_name))); + return Some((ModPath::from_segments(PathKind::Plain, Some(scope_name)), Stable)); } } // - if the item is the crate root, return `crate` if module_id == crate_root { - return Some(ModPath::from_segments(PathKind::Crate, None)); + return Some((ModPath::from_segments(PathKind::Crate, None), Stable)); } // - if relative paths are fine, check if we are searching for a parent if prefixed.filter(PrefixKind::is_absolute).is_none() { if let modpath @ Some(_) = find_self_super(def_map, module_id, from) { - return modpath; + return modpath.zip(Some(Stable)); } } @@ -201,14 +217,14 @@ fn find_path_for_module( } else { PathKind::Plain }; - return Some(ModPath::from_segments(kind, Some(name))); + return Some((ModPath::from_segments(kind, Some(name)), Stable)); } } if let value @ Some(_) = find_in_prelude(db, &root_def_map, &def_map, ItemInNs::Types(module_id.into()), from) { - return value; + return value.zip(Some(Stable)); } calculate_best_path( db, @@ -301,11 +317,19 @@ fn calculate_best_path( mut prefixed: Option, prefer_no_std: bool, scope_name: Option, -) -> Option { +) -> Option<(ModPath, Stability)> { if max_len <= 1 { return None; } let mut best_path = None; + let update_best_path = + |best_path: &mut Option<_>, new_path: (ModPath, Stability)| match best_path { + Some((old_path, old_stability)) => { + *old_path = new_path.0; + *old_stability = zip_stability(*old_stability, new_path.1); + } + None => *best_path = Some(new_path), + }; // Recursive case: // - otherwise, look for modules containing (reexporting) it and import it from one of those if item.krate(db) == Some(from.krate) { @@ -328,14 +352,14 @@ fn calculate_best_path( prefixed, prefer_no_std, ) { - path.push_segment(name); + path.0.push_segment(name); - let new_path = match best_path { + let new_path = match best_path.take() { Some(best_path) => select_best_path(best_path, path, prefer_no_std), None => path, }; - best_path_len = new_path.len(); - best_path = Some(new_path); + best_path_len = new_path.0.len(); + update_best_path(&mut best_path, new_path); } } } else { @@ -354,7 +378,7 @@ fn calculate_best_path( // Determine best path for containing module and append last segment from `info`. // FIXME: we should guide this to look up the path locally, or from the same crate again? - let mut path = find_path_for_module( + let (mut path, path_stability) = find_path_for_module( db, def_map, visited_modules, @@ -367,16 +391,19 @@ fn calculate_best_path( )?; cov_mark::hit!(partially_imported); path.push_segment(info.name.clone()); - Some(path) + Some(( + path, + zip_stability(path_stability, if info.is_unstable { Unstable } else { Stable }), + )) }) }); for path in extern_paths { - let new_path = match best_path { + let new_path = match best_path.take() { Some(best_path) => select_best_path(best_path, path, prefer_no_std), None => path, }; - best_path = Some(new_path); + update_best_path(&mut best_path, new_path); } } if let Some(module) = item.module(db) { @@ -387,15 +414,24 @@ fn calculate_best_path( } match prefixed.map(PrefixKind::prefix) { Some(prefix) => best_path.or_else(|| { - scope_name.map(|scope_name| ModPath::from_segments(prefix, Some(scope_name))) + scope_name.map(|scope_name| (ModPath::from_segments(prefix, Some(scope_name)), Stable)) }), None => best_path, } } -fn select_best_path(old_path: ModPath, new_path: ModPath, prefer_no_std: bool) -> ModPath { +fn select_best_path( + old_path: (ModPath, Stability), + new_path: (ModPath, Stability), + prefer_no_std: bool, +) -> (ModPath, Stability) { + match (old_path.1, new_path.1) { + (Stable, Unstable) => return old_path, + (Unstable, Stable) => return new_path, + _ => {} + } const STD_CRATES: [Name; 3] = [known::std, known::core, known::alloc]; - match (old_path.segments().first(), new_path.segments().first()) { + match (old_path.0.segments().first(), new_path.0.segments().first()) { (Some(old), Some(new)) if STD_CRATES.contains(old) && STD_CRATES.contains(new) => { let rank = match prefer_no_std { false => |name: &Name| match name { @@ -416,7 +452,7 @@ fn select_best_path(old_path: ModPath, new_path: ModPath, prefer_no_std: bool) - match nrank.cmp(&orank) { Ordering::Less => old_path, Ordering::Equal => { - if new_path.len() < old_path.len() { + if new_path.0.len() < old_path.0.len() { new_path } else { old_path @@ -426,7 +462,7 @@ fn select_best_path(old_path: ModPath, new_path: ModPath, prefer_no_std: bool) - } } _ => { - if new_path.len() < old_path.len() { + if new_path.0.len() < old_path.0.len() { new_path } else { old_path @@ -1360,4 +1396,29 @@ pub mod ops { "std::ops::Deref", ); } + + #[test] + fn respect_unstable_modules() { + check_found_path( + r#" +//- /main.rs crate:main deps:std,core +#![no_std] +extern crate std; +$0 +//- /longer.rs crate:std deps:core +pub mod error { + pub use core::error::Error; +} +//- /core.rs crate:core +pub mod error { + #![unstable(feature = "error_in_core", issue = "103765")] + pub trait Error {} +} +"#, + "std::error::Error", + "std::error::Error", + "std::error::Error", + "std::error::Error", + ); + } } diff --git a/crates/hir-def/src/hir.rs b/crates/hir-def/src/hir.rs index 6591c92ac62d0..591ee77c70a48 100644 --- a/crates/hir-def/src/hir.rs +++ b/crates/hir-def/src/hir.rs @@ -13,6 +13,7 @@ //! See also a neighboring `body` module. pub mod type_ref; +pub mod format_args; use std::fmt; @@ -117,7 +118,6 @@ impl From for Literal { fn from(ast_lit_kind: ast::LiteralKind) -> Self { use ast::LiteralKind; match ast_lit_kind { - // FIXME: these should have actual values filled in, but unsure on perf impact LiteralKind::IntNumber(lit) => { if let builtin @ Some(_) = lit.suffix().and_then(BuiltinFloat::from_suffix) { Literal::Float( @@ -281,6 +281,19 @@ pub enum Expr { Array(Array), Literal(Literal), Underscore, + OffsetOf(OffsetOf), + InlineAsm(InlineAsm), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct OffsetOf { + pub container: Interned, + pub fields: Box<[Name]>, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct InlineAsm { + pub e: ExprId, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -341,7 +354,8 @@ impl Expr { pub fn walk_child_exprs(&self, mut f: impl FnMut(ExprId)) { match self { Expr::Missing => {} - Expr::Path(_) => {} + Expr::Path(_) | Expr::OffsetOf(_) => {} + Expr::InlineAsm(it) => f(it.e), Expr::If { condition, then_branch, else_branch } => { f(*condition); f(*then_branch); diff --git a/crates/hir-def/src/hir/format_args.rs b/crates/hir-def/src/hir/format_args.rs new file mode 100644 index 0000000000000..75025a984fcd2 --- /dev/null +++ b/crates/hir-def/src/hir/format_args.rs @@ -0,0 +1,502 @@ +//! Parses `format_args` input. +use std::mem; + +use hir_expand::name::Name; +use rustc_parse_format as parse; +use syntax::{ + ast::{self, IsString}, + AstToken, SmolStr, TextRange, +}; + +use crate::hir::ExprId; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FormatArgs { + pub template: Box<[FormatArgsPiece]>, + pub arguments: FormatArguments, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FormatArguments { + pub arguments: Box<[FormatArgument]>, + pub num_unnamed_args: usize, + pub num_explicit_args: usize, + pub names: Box<[(Name, usize)]>, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FormatArgsPiece { + Literal(Box), + Placeholder(FormatPlaceholder), +} + +#[derive(Copy, Debug, Clone, PartialEq, Eq)] +pub struct FormatPlaceholder { + /// Index into [`FormatArgs::arguments`]. + pub argument: FormatArgPosition, + /// The span inside the format string for the full `{…}` placeholder. + pub span: Option, + /// `{}`, `{:?}`, or `{:x}`, etc. + pub format_trait: FormatTrait, + /// `{}` or `{:.5}` or `{:-^20}`, etc. + pub format_options: FormatOptions, +} + +#[derive(Copy, Debug, Clone, PartialEq, Eq)] +pub struct FormatArgPosition { + /// Which argument this position refers to (Ok), + /// or would've referred to if it existed (Err). + pub index: Result, + /// What kind of position this is. See [`FormatArgPositionKind`]. + pub kind: FormatArgPositionKind, + /// The span of the name or number. + pub span: Option, +} + +#[derive(Copy, Debug, Clone, PartialEq, Eq)] +pub enum FormatArgPositionKind { + /// `{}` or `{:.*}` + Implicit, + /// `{1}` or `{:1$}` or `{:.1$}` + Number, + /// `{a}` or `{:a$}` or `{:.a$}` + Named, +} + +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub enum FormatTrait { + /// `{}` + Display, + /// `{:?}` + Debug, + /// `{:e}` + LowerExp, + /// `{:E}` + UpperExp, + /// `{:o}` + Octal, + /// `{:p}` + Pointer, + /// `{:b}` + Binary, + /// `{:x}` + LowerHex, + /// `{:X}` + UpperHex, +} + +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] +pub struct FormatOptions { + /// The width. E.g. `{:5}` or `{:width$}`. + pub width: Option, + /// The precision. E.g. `{:.5}` or `{:.precision$}`. + pub precision: Option, + /// The alignment. E.g. `{:>}` or `{:<}` or `{:^}`. + pub alignment: Option, + /// The fill character. E.g. the `.` in `{:.>10}`. + pub fill: Option, + /// The `+` or `-` flag. + pub sign: Option, + /// The `#` flag. + pub alternate: bool, + /// The `0` flag. E.g. the `0` in `{:02x}`. + pub zero_pad: bool, + /// The `x` or `X` flag (for `Debug` only). E.g. the `x` in `{:x?}`. + pub debug_hex: Option, +} +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum FormatSign { + /// The `+` flag. + Plus, + /// The `-` flag. + Minus, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum FormatDebugHex { + /// The `x` flag in `{:x?}`. + Lower, + /// The `X` flag in `{:X?}`. + Upper, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum FormatAlignment { + /// `{:<}` + Left, + /// `{:>}` + Right, + /// `{:^}` + Center, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum FormatCount { + /// `{:5}` or `{:.5}` + Literal(usize), + /// `{:.*}`, `{:.5$}`, or `{:a$}`, etc. + Argument(FormatArgPosition), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FormatArgument { + pub kind: FormatArgumentKind, + pub expr: ExprId, +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum FormatArgumentKind { + /// `format_args(…, arg)` + Normal, + /// `format_args(…, arg = 1)` + Named(Name), + /// `format_args("… {arg} …")` + Captured(Name), +} + +// Only used in parse_args and report_invalid_references, +// to indicate how a referred argument was used. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum PositionUsedAs { + Placeholder(Option), + Precision, + Width, +} +use PositionUsedAs::*; + +pub(crate) fn parse( + s: &ast::String, + fmt_snippet: Option, + mut args: FormatArgumentsCollector, + is_direct_literal: bool, + mut synth: impl FnMut(Name) -> ExprId, +) -> FormatArgs { + let text = s.text(); + let str_style = match s.quote_offsets() { + Some(offsets) => { + let raw = u32::from(offsets.quotes.0.len()) - 1; + (raw != 0).then_some(raw as usize) + } + None => None, + }; + let mut parser = + parse::Parser::new(text, str_style, fmt_snippet, false, parse::ParseMode::Format); + + let mut pieces = Vec::new(); + while let Some(piece) = parser.next() { + if !parser.errors.is_empty() { + break; + } else { + pieces.push(piece); + } + } + let is_source_literal = parser.is_source_literal; + if !parser.errors.is_empty() { + // FIXME: Diagnose + return FormatArgs { template: Default::default(), arguments: args.finish() }; + } + + let to_span = |inner_span: parse::InnerSpan| { + is_source_literal.then(|| { + TextRange::new(inner_span.start.try_into().unwrap(), inner_span.end.try_into().unwrap()) + }) + }; + + let mut used = vec![false; args.explicit_args().len()]; + let mut invalid_refs = Vec::new(); + let mut numeric_refences_to_named_arg = Vec::new(); + + enum ArgRef<'a> { + Index(usize), + Name(&'a str, Option), + } + let mut lookup_arg = |arg: ArgRef<'_>, + span: Option, + used_as: PositionUsedAs, + kind: FormatArgPositionKind| + -> FormatArgPosition { + let index = match arg { + ArgRef::Index(index) => { + if let Some(arg) = args.by_index(index) { + used[index] = true; + if arg.kind.ident().is_some() { + // This was a named argument, but it was used as a positional argument. + numeric_refences_to_named_arg.push((index, span, used_as)); + } + Ok(index) + } else { + // Doesn't exist as an explicit argument. + invalid_refs.push((index, span, used_as, kind)); + Err(index) + } + } + ArgRef::Name(name, _span) => { + let name = Name::new_text_dont_use(SmolStr::new(name)); + if let Some((index, _)) = args.by_name(&name) { + // Name found in `args`, so we resolve it to its index. + if index < args.explicit_args().len() { + // Mark it as used, if it was an explicit argument. + used[index] = true; + } + Ok(index) + } else { + // Name not found in `args`, so we add it as an implicitly captured argument. + if !is_direct_literal { + // For the moment capturing variables from format strings expanded from macros is + // disabled (see RFC #2795) + // FIXME: Diagnose + } + Ok(args.add(FormatArgument { + kind: FormatArgumentKind::Captured(name.clone()), + // FIXME: This is problematic, we might want to synthesize a dummy + // expression proper and/or desugar these. + expr: synth(name), + })) + } + } + }; + FormatArgPosition { index, kind, span } + }; + + let mut template = Vec::new(); + let mut unfinished_literal = String::new(); + let mut placeholder_index = 0; + + for piece in pieces { + match piece { + parse::Piece::String(s) => { + unfinished_literal.push_str(s); + } + parse::Piece::NextArgument(arg) => { + let parse::Argument { position, position_span, format } = *arg; + if !unfinished_literal.is_empty() { + template.push(FormatArgsPiece::Literal( + mem::take(&mut unfinished_literal).into_boxed_str(), + )); + } + + let span = parser.arg_places.get(placeholder_index).and_then(|&s| to_span(s)); + placeholder_index += 1; + + let position_span = to_span(position_span); + let argument = match position { + parse::ArgumentImplicitlyIs(i) => lookup_arg( + ArgRef::Index(i), + position_span, + Placeholder(span), + FormatArgPositionKind::Implicit, + ), + parse::ArgumentIs(i) => lookup_arg( + ArgRef::Index(i), + position_span, + Placeholder(span), + FormatArgPositionKind::Number, + ), + parse::ArgumentNamed(name) => lookup_arg( + ArgRef::Name(name, position_span), + position_span, + Placeholder(span), + FormatArgPositionKind::Named, + ), + }; + + let alignment = match format.align { + parse::AlignUnknown => None, + parse::AlignLeft => Some(FormatAlignment::Left), + parse::AlignRight => Some(FormatAlignment::Right), + parse::AlignCenter => Some(FormatAlignment::Center), + }; + + let format_trait = match format.ty { + "" => FormatTrait::Display, + "?" => FormatTrait::Debug, + "e" => FormatTrait::LowerExp, + "E" => FormatTrait::UpperExp, + "o" => FormatTrait::Octal, + "p" => FormatTrait::Pointer, + "b" => FormatTrait::Binary, + "x" => FormatTrait::LowerHex, + "X" => FormatTrait::UpperHex, + _ => { + // FIXME: Diagnose + FormatTrait::Display + } + }; + + let precision_span = format.precision_span.and_then(to_span); + let precision = match format.precision { + parse::CountIs(n) => Some(FormatCount::Literal(n)), + parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg( + ArgRef::Name(name, to_span(name_span)), + precision_span, + Precision, + FormatArgPositionKind::Named, + ))), + parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg( + ArgRef::Index(i), + precision_span, + Precision, + FormatArgPositionKind::Number, + ))), + parse::CountIsStar(i) => Some(FormatCount::Argument(lookup_arg( + ArgRef::Index(i), + precision_span, + Precision, + FormatArgPositionKind::Implicit, + ))), + parse::CountImplied => None, + }; + + let width_span = format.width_span.and_then(to_span); + let width = match format.width { + parse::CountIs(n) => Some(FormatCount::Literal(n)), + parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg( + ArgRef::Name(name, to_span(name_span)), + width_span, + Width, + FormatArgPositionKind::Named, + ))), + parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg( + ArgRef::Index(i), + width_span, + Width, + FormatArgPositionKind::Number, + ))), + parse::CountIsStar(_) => unreachable!(), + parse::CountImplied => None, + }; + + template.push(FormatArgsPiece::Placeholder(FormatPlaceholder { + argument, + span, + format_trait, + format_options: FormatOptions { + fill: format.fill, + alignment, + sign: format.sign.map(|s| match s { + parse::Sign::Plus => FormatSign::Plus, + parse::Sign::Minus => FormatSign::Minus, + }), + alternate: format.alternate, + zero_pad: format.zero_pad, + debug_hex: format.debug_hex.map(|s| match s { + parse::DebugHex::Lower => FormatDebugHex::Lower, + parse::DebugHex::Upper => FormatDebugHex::Upper, + }), + precision, + width, + }, + })); + } + } + } + + if !unfinished_literal.is_empty() { + template.push(FormatArgsPiece::Literal(unfinished_literal.into_boxed_str())); + } + + if !invalid_refs.is_empty() { + // FIXME: Diagnose + } + + let unused = used + .iter() + .enumerate() + .filter(|&(_, used)| !used) + .map(|(i, _)| { + let named = matches!(args.explicit_args()[i].kind, FormatArgumentKind::Named(_)); + (args.explicit_args()[i].expr, named) + }) + .collect::>(); + + if !unused.is_empty() { + // FIXME: Diagnose + } + + FormatArgs { template: template.into_boxed_slice(), arguments: args.finish() } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FormatArgumentsCollector { + arguments: Vec, + num_unnamed_args: usize, + num_explicit_args: usize, + names: Vec<(Name, usize)>, +} + +impl FormatArgumentsCollector { + pub(crate) fn finish(self) -> FormatArguments { + FormatArguments { + arguments: self.arguments.into_boxed_slice(), + num_unnamed_args: self.num_unnamed_args, + num_explicit_args: self.num_explicit_args, + names: self.names.into_boxed_slice(), + } + } + + pub fn new() -> Self { + Self { arguments: vec![], names: vec![], num_unnamed_args: 0, num_explicit_args: 0 } + } + + pub fn add(&mut self, arg: FormatArgument) -> usize { + let index = self.arguments.len(); + if let Some(name) = arg.kind.ident() { + self.names.push((name.clone(), index)); + } else if self.names.is_empty() { + // Only count the unnamed args before the first named arg. + // (Any later ones are errors.) + self.num_unnamed_args += 1; + } + if !matches!(arg.kind, FormatArgumentKind::Captured(..)) { + // This is an explicit argument. + // Make sure that all arguments so far are explicit. + assert_eq!( + self.num_explicit_args, + self.arguments.len(), + "captured arguments must be added last" + ); + self.num_explicit_args += 1; + } + self.arguments.push(arg); + index + } + + pub fn by_name(&self, name: &Name) -> Option<(usize, &FormatArgument)> { + let &(_, i) = self.names.iter().find(|(n, _)| n == name)?; + Some((i, &self.arguments[i])) + } + + pub fn by_index(&self, i: usize) -> Option<&FormatArgument> { + (i < self.num_explicit_args).then(|| &self.arguments[i]) + } + + pub fn unnamed_args(&self) -> &[FormatArgument] { + &self.arguments[..self.num_unnamed_args] + } + + pub fn named_args(&self) -> &[FormatArgument] { + &self.arguments[self.num_unnamed_args..self.num_explicit_args] + } + + pub fn explicit_args(&self) -> &[FormatArgument] { + &self.arguments[..self.num_explicit_args] + } + + pub fn all_args(&self) -> &[FormatArgument] { + &self.arguments[..] + } + + pub fn all_args_mut(&mut self) -> &mut Vec { + &mut self.arguments + } +} + +impl FormatArgumentKind { + pub fn ident(&self) -> Option<&Name> { + match self { + Self::Normal => None, + Self::Named(id) => Some(id), + Self::Captured(id) => Some(id), + } + } +} diff --git a/crates/hir-def/src/import_map.rs b/crates/hir-def/src/import_map.rs index 6038caaf8fced..44b7f1b4f63ed 100644 --- a/crates/hir-def/src/import_map.rs +++ b/crates/hir-def/src/import_map.rs @@ -32,6 +32,8 @@ pub struct ImportInfo { pub is_trait_assoc_item: bool, /// Whether this item is annotated with `#[doc(hidden)]`. pub is_doc_hidden: bool, + /// Whether this item is annotated with `#[unstable(..)]`. + pub is_unstable: bool, } /// A map from publicly exported items to its name. @@ -113,7 +115,6 @@ fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> FxIndexMap Some(id.into()), @@ -125,28 +126,59 @@ fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> FxIndexMap Some(id.into()), } }; - let is_doc_hidden = - attr_id.map_or(false, |attr_id| db.attrs(attr_id).has_doc_hidden()); + let status @ (is_doc_hidden, is_unstable) = + attr_id.map_or((false, false), |attr_id| { + let attrs = db.attrs(attr_id); + (attrs.has_doc_hidden(), attrs.is_unstable()) + }); let import_info = ImportInfo { name: name.clone(), container: module, is_trait_assoc_item: false, is_doc_hidden, + is_unstable, }; match depth_map.entry(item) { - Entry::Vacant(entry) => _ = entry.insert((depth, is_doc_hidden)), + Entry::Vacant(entry) => _ = entry.insert((depth, status)), Entry::Occupied(mut entry) => { - let &(occ_depth, occ_is_doc_hidden) = entry.get(); - // Prefer the one that is not doc(hidden), - // Otherwise, if both have the same doc(hidden)-ness and the new path is shorter, prefer that one. - let overwrite_entry = occ_is_doc_hidden && !is_doc_hidden - || occ_is_doc_hidden == is_doc_hidden && depth < occ_depth; - if !overwrite_entry { + let &(occ_depth, (occ_is_doc_hidden, occ_is_unstable)) = entry.get(); + (depth, occ_depth); + let overwrite = match ( + is_doc_hidden, + occ_is_doc_hidden, + is_unstable, + occ_is_unstable, + ) { + // no change of hiddeness or unstableness + (true, true, true, true) + | (true, true, false, false) + | (false, false, true, true) + | (false, false, false, false) => depth < occ_depth, + + // either less hidden or less unstable, accept + (true, true, false, true) + | (false, true, true, true) + | (false, true, false, true) + | (false, true, false, false) + | (false, false, false, true) => true, + // more hidden or unstable, discard + (true, true, true, false) + | (true, false, true, true) + | (true, false, true, false) + | (true, false, false, false) + | (false, false, true, false) => false, + + // exchanges doc(hidden) for unstable (and vice-versa), + (true, false, false, true) | (false, true, true, false) => { + depth < occ_depth + } + }; + if !overwrite { continue; } - entry.insert((depth, is_doc_hidden)); + entry.insert((depth, status)); } } @@ -171,7 +203,7 @@ fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> FxIndexMap String { - pretty::print_item_tree(db.upcast(), self) + pretty::print_item_tree(db, self) } fn data(&self) -> &ItemTreeData { diff --git a/crates/hir-def/src/item_tree/pretty.rs b/crates/hir-def/src/item_tree/pretty.rs index 4b852dd613e79..417bd37c8a9b0 100644 --- a/crates/hir-def/src/item_tree/pretty.rs +++ b/crates/hir-def/src/item_tree/pretty.rs @@ -2,8 +2,6 @@ use std::fmt::{self, Write}; -use hir_expand::db::ExpandDatabase; - use crate::{ generics::{TypeOrConstParamData, WherePredicate, WherePredicateTypeTarget}, pretty::{print_path, print_type_bounds, print_type_ref}, @@ -12,7 +10,7 @@ use crate::{ use super::*; -pub(super) fn print_item_tree(db: &dyn ExpandDatabase, tree: &ItemTree) -> String { +pub(super) fn print_item_tree(db: &dyn DefDatabase, tree: &ItemTree) -> String { let mut p = Printer { db, tree, buf: String::new(), indent_level: 0, needs_indent: true }; if let Some(attrs) = tree.attrs.get(&AttrOwner::TopLevel) { @@ -45,7 +43,7 @@ macro_rules! wln { } struct Printer<'a> { - db: &'a dyn ExpandDatabase, + db: &'a dyn DefDatabase, tree: &'a ItemTree, buf: String, indent_level: usize, @@ -91,7 +89,7 @@ impl Printer<'_> { self, "#{}[{}{}]{}", inner, - attr.path.display(self.db), + attr.path.display(self.db.upcast()), attr.input.as_ref().map(|it| it.to_string()).unwrap_or_default(), separated_by, ); @@ -106,7 +104,7 @@ impl Printer<'_> { fn print_visibility(&mut self, vis: RawVisibilityId) { match &self.tree[vis] { - RawVisibility::Module(path) => w!(self, "pub({}) ", path.display(self.db)), + RawVisibility::Module(path) => w!(self, "pub({}) ", path.display(self.db.upcast())), RawVisibility::Public => w!(self, "pub "), }; } @@ -121,7 +119,7 @@ impl Printer<'_> { let Field { visibility, name, type_ref, ast_id: _ } = &this.tree[field]; this.print_attrs_of(field, "\n"); this.print_visibility(*visibility); - w!(this, "{}: ", name.display(self.db)); + w!(this, "{}: ", name.display(self.db.upcast())); this.print_type_ref(type_ref); wln!(this, ","); } @@ -135,7 +133,7 @@ impl Printer<'_> { let Field { visibility, name, type_ref, ast_id: _ } = &this.tree[field]; this.print_attrs_of(field, "\n"); this.print_visibility(*visibility); - w!(this, "{}: ", name.display(self.db)); + w!(this, "{}: ", name.display(self.db.upcast())); this.print_type_ref(type_ref); wln!(this, ","); } @@ -168,20 +166,20 @@ impl Printer<'_> { fn print_use_tree(&mut self, use_tree: &UseTree) { match &use_tree.kind { UseTreeKind::Single { path, alias } => { - w!(self, "{}", path.display(self.db)); + w!(self, "{}", path.display(self.db.upcast())); if let Some(alias) = alias { w!(self, " as {}", alias); } } UseTreeKind::Glob { path } => { if let Some(path) = path { - w!(self, "{}::", path.display(self.db)); + w!(self, "{}::", path.display(self.db.upcast())); } w!(self, "*"); } UseTreeKind::Prefixed { prefix, list } => { if let Some(prefix) = prefix { - w!(self, "{}::", prefix.display(self.db)); + w!(self, "{}::", prefix.display(self.db.upcast())); } w!(self, "{{"); for (i, tree) in list.iter().enumerate() { @@ -209,7 +207,7 @@ impl Printer<'_> { ModItem::ExternCrate(it) => { let ExternCrate { name, alias, visibility, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - w!(self, "extern crate {}", name.display(self.db)); + w!(self, "extern crate {}", name.display(self.db.upcast())); if let Some(alias) = alias { w!(self, " as {}", alias); } @@ -256,7 +254,7 @@ impl Printer<'_> { if let Some(abi) = abi { w!(self, "extern \"{}\" ", abi); } - w!(self, "fn {}", name.display(self.db)); + w!(self, "fn {}", name.display(self.db.upcast())); self.print_generic_params(explicit_generic_params); w!(self, "("); if !params.is_empty() { @@ -290,7 +288,7 @@ impl Printer<'_> { ModItem::Struct(it) => { let Struct { visibility, name, fields, generic_params, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - w!(self, "struct {}", name.display(self.db)); + w!(self, "struct {}", name.display(self.db.upcast())); self.print_generic_params(generic_params); self.print_fields_and_where_clause(fields, generic_params); if matches!(fields, Fields::Record(_)) { @@ -302,7 +300,7 @@ impl Printer<'_> { ModItem::Union(it) => { let Union { name, visibility, fields, generic_params, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - w!(self, "union {}", name.display(self.db)); + w!(self, "union {}", name.display(self.db.upcast())); self.print_generic_params(generic_params); self.print_fields_and_where_clause(fields, generic_params); if matches!(fields, Fields::Record(_)) { @@ -314,14 +312,14 @@ impl Printer<'_> { ModItem::Enum(it) => { let Enum { name, visibility, variants, generic_params, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - w!(self, "enum {}", name.display(self.db)); + w!(self, "enum {}", name.display(self.db.upcast())); self.print_generic_params(generic_params); self.print_where_clause_and_opening_brace(generic_params); self.indented(|this| { for variant in variants.clone() { let Variant { name, fields, ast_id: _ } = &this.tree[variant]; this.print_attrs_of(variant, "\n"); - w!(this, "{}", name.display(self.db)); + w!(this, "{}", name.display(self.db.upcast())); this.print_fields(fields); wln!(this, ","); } @@ -333,7 +331,7 @@ impl Printer<'_> { self.print_visibility(*visibility); w!(self, "const "); match name { - Some(name) => w!(self, "{}", name.display(self.db)), + Some(name) => w!(self, "{}", name.display(self.db.upcast())), None => w!(self, "_"), } w!(self, ": "); @@ -347,7 +345,7 @@ impl Printer<'_> { if *mutable { w!(self, "mut "); } - w!(self, "{}: ", name.display(self.db)); + w!(self, "{}: ", name.display(self.db.upcast())); self.print_type_ref(type_ref); w!(self, " = _;"); wln!(self); @@ -369,7 +367,7 @@ impl Printer<'_> { if *is_auto { w!(self, "auto "); } - w!(self, "trait {}", name.display(self.db)); + w!(self, "trait {}", name.display(self.db.upcast())); self.print_generic_params(generic_params); self.print_where_clause_and_opening_brace(generic_params); self.indented(|this| { @@ -382,7 +380,7 @@ impl Printer<'_> { ModItem::TraitAlias(it) => { let TraitAlias { name, visibility, generic_params, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - w!(self, "trait {}", name.display(self.db)); + w!(self, "trait {}", name.display(self.db.upcast())); self.print_generic_params(generic_params); w!(self, " = "); self.print_where_clause(generic_params); @@ -415,7 +413,7 @@ impl Printer<'_> { let TypeAlias { name, visibility, bounds, type_ref, generic_params, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - w!(self, "type {}", name.display(self.db)); + w!(self, "type {}", name.display(self.db.upcast())); self.print_generic_params(generic_params); if !bounds.is_empty() { w!(self, ": "); @@ -432,7 +430,7 @@ impl Printer<'_> { ModItem::Mod(it) => { let Mod { name, visibility, kind, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - w!(self, "mod {}", name.display(self.db)); + w!(self, "mod {}", name.display(self.db.upcast())); match kind { ModKind::Inline { items } => { w!(self, " {{"); @@ -450,16 +448,16 @@ impl Printer<'_> { } ModItem::MacroCall(it) => { let MacroCall { path, ast_id: _, expand_to: _ } = &self.tree[it]; - wln!(self, "{}!(...);", path.display(self.db)); + wln!(self, "{}!(...);", path.display(self.db.upcast())); } ModItem::MacroRules(it) => { let MacroRules { name, ast_id: _ } = &self.tree[it]; - wln!(self, "macro_rules! {} {{ ... }}", name.display(self.db)); + wln!(self, "macro_rules! {} {{ ... }}", name.display(self.db.upcast())); } ModItem::MacroDef(it) => { let MacroDef { name, visibility, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - wln!(self, "macro {} {{ ... }}", name.display(self.db)); + wln!(self, "macro {} {{ ... }}", name.display(self.db.upcast())); } } @@ -491,7 +489,7 @@ impl Printer<'_> { } first = false; self.print_attrs_of(idx, " "); - w!(self, "{}", lt.name.display(self.db)); + w!(self, "{}", lt.name.display(self.db.upcast())); } for (idx, x) in params.type_or_consts.iter() { if !first { @@ -501,11 +499,11 @@ impl Printer<'_> { self.print_attrs_of(idx, " "); match x { TypeOrConstParamData::TypeParamData(ty) => match &ty.name { - Some(name) => w!(self, "{}", name.display(self.db)), + Some(name) => w!(self, "{}", name.display(self.db.upcast())), None => w!(self, "_anon_{}", idx.into_raw()), }, TypeOrConstParamData::ConstParamData(konst) => { - w!(self, "const {}: ", konst.name.display(self.db)); + w!(self, "const {}: ", konst.name.display(self.db.upcast())); self.print_type_ref(&konst.ty); } } @@ -540,8 +538,8 @@ impl Printer<'_> { wln!( this, "{}: {},", - target.name.display(self.db), - bound.name.display(self.db) + target.name.display(self.db.upcast()), + bound.name.display(self.db.upcast()) ); continue; } @@ -551,7 +549,7 @@ impl Printer<'_> { if i != 0 { w!(this, ", "); } - w!(this, "{}", lt.display(self.db)); + w!(this, "{}", lt.display(self.db.upcast())); } w!(this, "> "); (target, bound) @@ -562,7 +560,7 @@ impl Printer<'_> { WherePredicateTypeTarget::TypeRef(ty) => this.print_type_ref(ty), WherePredicateTypeTarget::TypeOrConstParam(id) => { match ¶ms.type_or_consts[*id].name() { - Some(name) => w!(this, "{}", name.display(self.db)), + Some(name) => w!(this, "{}", name.display(self.db.upcast())), None => w!(this, "_anon_{}", id.into_raw()), } } diff --git a/crates/hir-def/src/lang_item.rs b/crates/hir-def/src/lang_item.rs index 627479bb7c1b7..1ae6bd4c91949 100644 --- a/crates/hir-def/src/lang_item.rs +++ b/crates/hir-def/src/lang_item.rs @@ -2,6 +2,7 @@ //! //! This attribute to tell the compiler about semi built-in std library //! features, such as Fn family of traits. +use hir_expand::name::Name; use rustc_hash::FxHashMap; use syntax::SmolStr; use triomphe::Arc; @@ -238,7 +239,17 @@ impl LangItem { pub fn path(&self, db: &dyn DefDatabase, start_crate: CrateId) -> Option { let t = db.lang_item(start_crate, *self)?; - Some(Path::LangItem(t)) + Some(Path::LangItem(t, None)) + } + + pub fn ty_rel_path( + &self, + db: &dyn DefDatabase, + start_crate: CrateId, + seg: Name, + ) -> Option { + let t = db.lang_item(start_crate, *self)?; + Some(Path::LangItem(t, Some(seg))) } } diff --git a/crates/hir-def/src/lower.rs b/crates/hir-def/src/lower.rs index e523c229179b4..52781d9889212 100644 --- a/crates/hir-def/src/lower.rs +++ b/crates/hir-def/src/lower.rs @@ -1,10 +1,11 @@ //! Context for lowering paths. +use std::cell::OnceCell; + use hir_expand::{ ast_id_map::{AstIdMap, AstIdNode}, hygiene::Hygiene, AstId, HirFileId, InFile, }; -use once_cell::unsync::OnceCell; use syntax::ast; use triomphe::Arc; diff --git a/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs b/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs index b232651db96ef..4aedb22c6bc9a 100644 --- a/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs +++ b/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs @@ -22,6 +22,45 @@ fn main() { 0 as u32; } ); } +#[test] +fn test_asm_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! asm {() => {}} + +fn main() { + let i: u64 = 3; + let o: u64; + unsafe { + asm!( + "mov {0}, {1}", + "add {0}, 5", + out(reg) o, + in(reg) i, + ); + } +} +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! asm {() => {}} + +fn main() { + let i: u64 = 3; + let o: u64; + unsafe { + builtin #asm ( { + $crate::format_args!("mov {0}, {1}"); + $crate::format_args!("add {0}, 5"); + } + ); + } +} +"##]], + ); +} + #[test] fn test_line_expand() { check( @@ -201,7 +240,7 @@ macro_rules! format_args { } fn main() { - ::core::fmt::Arguments::new_v1(&["", " ", ], &[::core::fmt::ArgumentV1::new(&(arg1(a, b, c)), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(arg2), ::core::fmt::Debug::fmt), ]); + builtin #format_args ("{} {:?}", arg1(a, b, c), arg2); } "##]], ); @@ -219,10 +258,10 @@ macro_rules! format_args { fn main() { format_args!(x = 2); - format_args!(x =); - format_args!(x =, x = 2); - format_args!("{}", x =); - format_args!(=, "{}", x =); + format_args!/*+errors*/(x =); + format_args!/*+errors*/(x =, x = 2); + format_args!/*+errors*/("{}", x =); + format_args!/*+errors*/(=, "{}", x =); format_args!(x = 2, "{}", 5); } "#, @@ -234,12 +273,19 @@ macro_rules! format_args { } fn main() { - /* error: no rule matches input tokens */; - /* error: expected expression */; - /* error: expected expression, expected COMMA */; - /* error: expected expression */::core::fmt::Arguments::new_v1(&["", ], &[::core::fmt::ArgumentV1::new(&(), ::core::fmt::Display::fmt), ]); - /* error: expected expression, expected R_PAREN */; - ::core::fmt::Arguments::new_v1(&["", ], &[::core::fmt::ArgumentV1::new(&(5), ::core::fmt::Display::fmt), ]); + builtin #format_args (x = 2); + /* parse error: expected expression */ +builtin #format_args (x = ); + /* parse error: expected expression */ +/* parse error: expected R_PAREN */ +/* parse error: expected expression, item or let statement */ +builtin #format_args (x = , x = 2); + /* parse error: expected expression */ +builtin #format_args ("{}", x = ); + /* parse error: expected expression */ +/* parse error: expected expression */ +builtin #format_args ( = , "{}", x = ); + builtin #format_args (x = 2, "{}", 5); } "##]], ); @@ -267,7 +313,7 @@ macro_rules! format_args { } fn main() { - ::core::fmt::Arguments::new_v1(&["", " ", ], &[::core::fmt::ArgumentV1::new(&(a::()), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(b), ::core::fmt::Debug::fmt), ]); + builtin #format_args ("{} {:?}", a::(), b); } "##]], ); @@ -300,7 +346,7 @@ macro_rules! format_args { } fn main() { - ::core::fmt::Arguments::new_v1(&[r#""#, r#",mismatch,""#, r#"",""#, r#"""#, ], &[::core::fmt::ArgumentV1::new(&(location_csv_pat(db, &analysis, vfs, &sm, pat_id)), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(mismatch.expected.display(db)), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(mismatch.actual.display(db)), ::core::fmt::Display::fmt), ]); + builtin #format_args (r#"{},mismatch,"{}","{}""#, location_csv_pat(db, &analysis, vfs, &sm, pat_id), mismatch.expected.display(db), mismatch.actual.display(db)); } "##]], ); @@ -334,7 +380,7 @@ macro_rules! format_args { } fn main() { - ::core::fmt::Arguments::new_v1(&["xxx", "y", "zzz", ], &[::core::fmt::ArgumentV1::new(&(2), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(b), ::core::fmt::Debug::fmt), ]); + builtin #format_args (concat!("xxx{}y", "{:?}zzz"), 2, b); } "##]], ); @@ -364,8 +410,8 @@ macro_rules! format_args { fn main() { let _ = - /* error: expected field name or number *//* parse error: expected field name or number */ -::core::fmt::Arguments::new_v1(&["", " ", ], &[::core::fmt::ArgumentV1::new(&(a.), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(), ::core::fmt::Debug::fmt), ]); + /* parse error: expected field name or number */ +builtin #format_args ("{} {:?}", a.); } "##]], ); diff --git a/crates/hir-def/src/macro_expansion_tests/mbe.rs b/crates/hir-def/src/macro_expansion_tests/mbe.rs index 2170cadcf83bf..d0906213243d6 100644 --- a/crates/hir-def/src/macro_expansion_tests/mbe.rs +++ b/crates/hir-def/src/macro_expansion_tests/mbe.rs @@ -117,7 +117,7 @@ fn main(foo: ()) { macro_rules! format_args {} fn main(foo: ()) { - /* error: unresolved macro identity */::core::fmt::Arguments::new_v1(&["", " ", " ", ], &[::core::fmt::ArgumentV1::new(&(::core::fmt::Arguments::new_v1(&["", ], &[::core::fmt::ArgumentV1::new(&(0), ::core::fmt::Display::fmt), ])), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(foo), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(identity!(10)), ::core::fmt::Display::fmt), ]) + builtin #format_args ("{} {} {}", format_args!("{}", 0), foo, identity!(10), "bar") } "##]], ); @@ -150,8 +150,8 @@ macro_rules! identity { } fn main(foo: ()) { - // format_args/*+tokenids*/!("{} {} {}"#1,#3 format_args!("{}", 0#10),#12 foo#13,#14 identity!(10#18),#21 "bar"#22) -::core#4294967295::fmt#4294967295::Arguments#4294967295::new_v1#4294967295(�[#4294967295""#4294967295,#4294967295 " "#4294967295,#4294967295 " "#4294967295,#4294967295 ]#4294967295,#4294967295 �[::core#4294967295::fmt#4294967295::ArgumentV1#4294967295::new#4294967295(�(::core#4294967295::fmt#4294967295::Arguments#4294967295::new_v1#4294967295(�[#4294967295""#4294967295,#4294967295 ]#4294967295,#4294967295 �[::core#4294967295::fmt#4294967295::ArgumentV1#4294967295::new#4294967295(�(#42949672950#10)#4294967295,#4294967295 ::core#4294967295::fmt#4294967295::Display#4294967295::fmt#4294967295)#4294967295,#4294967295 ]#4294967295)#4294967295)#4294967295,#4294967295 ::core#4294967295::fmt#4294967295::Display#4294967295::fmt#4294967295)#4294967295,#4294967295 ::core#4294967295::fmt#4294967295::ArgumentV1#4294967295::new#4294967295(�(#4294967295foo#13)#4294967295,#4294967295 ::core#4294967295::fmt#4294967295::Display#4294967295::fmt#4294967295)#4294967295,#4294967295 ::core#4294967295::fmt#4294967295::ArgumentV1#4294967295::new#4294967295(�(#429496729510#18)#4294967295,#4294967295 ::core#4294967295::fmt#4294967295::Display#4294967295::fmt#4294967295)#4294967295,#4294967295 ]#4294967295)#4294967295 + // format_args/*+tokenids*/!("{} {} {}"#1,#2 format_args#3!#4("{}"#6,#7 0#8),#9 foo#10,#11 identity#12!#13(10#15),#16 "bar"#17) +builtin#4294967295 ##4294967295format_args#4294967295 (#0"{} {} {}"#1,#2 format_args#3!#4(#5"{}"#6,#7 0#8)#5,#9 foo#10,#11 identity#12!#13(#1410#15)#14,#16 "bar"#17)#0 } "##]], diff --git a/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs b/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs index 97554f93f1c18..b416f45ff208e 100644 --- a/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs +++ b/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs @@ -929,8 +929,8 @@ fn main() { macro_rules! format_args {} fn main() { - /* error: expected field name or number *//* parse error: expected field name or number */ -::core::fmt::Arguments::new_v1(&["", ], &[::core::fmt::ArgumentV1::new(&(line.1.), ::core::fmt::Display::fmt), ]); + /* parse error: expected field name or number */ +builtin #format_args ("{}", line.1.); } "##]], @@ -956,19 +956,15 @@ fn main() { macro_rules! format_args {} fn main() { - /* error: expected COMMA, expected R_BRACK, expected COMMA, expected COMMA, expected expression, expected R_PAREN *//* parse error: expected COMMA */ + /* parse error: expected COMMA */ /* parse error: expected R_BRACK */ /* parse error: expected COMMA */ /* parse error: expected COMMA */ /* parse error: expected expression */ /* parse error: expected R_PAREN */ -/* parse error: expected R_PAREN */ -/* parse error: expected expression, item or let statement */ -/* parse error: expected expression, item or let statement */ -/* parse error: expected expression, item or let statement */ /* parse error: expected expression, item or let statement */ /* parse error: expected expression, item or let statement */ -::core::fmt::Arguments::new_v1(&["", ], &[::core::fmt::ArgumentV1::new(&(&[0 2]), ::core::fmt::Display::fmt), ]); +builtin #format_args ("{}", &[0 2]); } "##]], diff --git a/crates/hir-def/src/nameres.rs b/crates/hir-def/src/nameres.rs index f2110410980f5..9a9fa0e02b082 100644 --- a/crates/hir-def/src/nameres.rs +++ b/crates/hir-def/src/nameres.rs @@ -93,7 +93,7 @@ use crate::{ #[derive(Debug, PartialEq, Eq)] pub struct DefMap { _c: Count, - /// When this is a block def map, this will hold the block id of the the block and module that + /// When this is a block def map, this will hold the block id of the block and module that /// contains this block. block: Option, /// The modules and their data declared in this crate. diff --git a/crates/hir-def/src/nameres/collector.rs b/crates/hir-def/src/nameres/collector.rs index e9e71a8747f8c..2d4586146db02 100644 --- a/crates/hir-def/src/nameres/collector.rs +++ b/crates/hir-def/src/nameres/collector.rs @@ -1828,7 +1828,11 @@ impl ModCollector<'_, '_> { let Some(paths) = attr.parse_path_comma_token_tree(db.upcast(), &hygiene) else { // `#[macro_use]` (without any paths) found, forget collected names and just import // all visible macros. - self.def_collector.import_macros_from_extern_crate(target_crate, None, Some(extern_crate_id)); + self.def_collector.import_macros_from_extern_crate( + target_crate, + None, + Some(extern_crate_id), + ); return; }; for path in paths { diff --git a/crates/hir-def/src/path.rs b/crates/hir-def/src/path.rs index 06530cc7ebd71..3894172a5ad86 100644 --- a/crates/hir-def/src/path.rs +++ b/crates/hir-def/src/path.rs @@ -47,7 +47,7 @@ pub enum Path { }, /// A link to a lang item. It is used in desugaring of things like `it?`. We can show these /// links via a normal path since they might be private and not accessible in the usage place. - LangItem(LangItemTarget), + LangItem(LangItemTarget, Option), } /// Generic arguments to a path segment (e.g. the `i32` in `Option`). This @@ -122,33 +122,40 @@ impl Path { pub fn kind(&self) -> &PathKind { match self { Path::Normal { mod_path, .. } => &mod_path.kind, - Path::LangItem(_) => &PathKind::Abs, + Path::LangItem(..) => &PathKind::Abs, } } pub fn type_anchor(&self) -> Option<&TypeRef> { match self { Path::Normal { type_anchor, .. } => type_anchor.as_deref(), - Path::LangItem(_) => None, + Path::LangItem(..) => None, } } pub fn segments(&self) -> PathSegments<'_> { - let Path::Normal { mod_path, generic_args, .. } = self else { - return PathSegments { segments: &[], generic_args: None }; - }; - let s = - PathSegments { segments: mod_path.segments(), generic_args: generic_args.as_deref() }; - if let Some(generic_args) = s.generic_args { - assert_eq!(s.segments.len(), generic_args.len()); + match self { + Path::Normal { mod_path, generic_args, .. } => { + let s = PathSegments { + segments: mod_path.segments(), + generic_args: generic_args.as_deref(), + }; + if let Some(generic_args) = s.generic_args { + assert_eq!(s.segments.len(), generic_args.len()); + } + s + } + Path::LangItem(_, seg) => PathSegments { + segments: seg.as_ref().map_or(&[], |seg| std::slice::from_ref(seg)), + generic_args: None, + }, } - s } pub fn mod_path(&self) -> Option<&ModPath> { match self { Path::Normal { mod_path, .. } => Some(&mod_path), - Path::LangItem(_) => None, + Path::LangItem(..) => None, } } diff --git a/crates/hir-def/src/pretty.rs b/crates/hir-def/src/pretty.rs index 11d58a6ba09ca..f4f5541e3733d 100644 --- a/crates/hir-def/src/pretty.rs +++ b/crates/hir-def/src/pretty.rs @@ -2,18 +2,54 @@ use std::fmt::{self, Write}; -use hir_expand::{db::ExpandDatabase, mod_path::PathKind}; +use hir_expand::mod_path::PathKind; use intern::Interned; use itertools::Itertools; use crate::{ + db::DefDatabase, + lang_item::LangItemTarget, path::{GenericArg, GenericArgs, Path}, type_ref::{Mutability, TraitBoundModifier, TypeBound, TypeRef}, }; -pub(crate) fn print_path(db: &dyn ExpandDatabase, path: &Path, buf: &mut dyn Write) -> fmt::Result { - if let Path::LangItem(it) = path { - return write!(buf, "$lang_item::{it:?}"); +pub(crate) fn print_path(db: &dyn DefDatabase, path: &Path, buf: &mut dyn Write) -> fmt::Result { + if let Path::LangItem(it, s) = path { + write!(buf, "builtin#lang(")?; + match *it { + LangItemTarget::ImplDef(it) => write!(buf, "{it:?}")?, + LangItemTarget::EnumId(it) => { + write!(buf, "{}", db.enum_data(it).name.display(db.upcast()))? + } + LangItemTarget::Function(it) => { + write!(buf, "{}", db.function_data(it).name.display(db.upcast()))? + } + LangItemTarget::Static(it) => { + write!(buf, "{}", db.static_data(it).name.display(db.upcast()))? + } + LangItemTarget::Struct(it) => { + write!(buf, "{}", db.struct_data(it).name.display(db.upcast()))? + } + LangItemTarget::Union(it) => { + write!(buf, "{}", db.union_data(it).name.display(db.upcast()))? + } + LangItemTarget::TypeAlias(it) => { + write!(buf, "{}", db.type_alias_data(it).name.display(db.upcast()))? + } + LangItemTarget::Trait(it) => { + write!(buf, "{}", db.trait_data(it).name.display(db.upcast()))? + } + LangItemTarget::EnumVariant(it) => write!( + buf, + "{}", + db.enum_data(it.parent).variants[it.local_id].name.display(db.upcast()) + )?, + } + + if let Some(s) = s { + write!(buf, "::{}", s.display(db.upcast()))?; + } + return write!(buf, ")"); } match path.type_anchor() { Some(anchor) => { @@ -44,7 +80,7 @@ pub(crate) fn print_path(db: &dyn ExpandDatabase, path: &Path, buf: &mut dyn Wri write!(buf, "::")?; } - write!(buf, "{}", segment.name.display(db))?; + write!(buf, "{}", segment.name.display(db.upcast()))?; if let Some(generics) = segment.args_and_bindings { write!(buf, "::<")?; print_generic_args(db, generics, buf)?; @@ -57,7 +93,7 @@ pub(crate) fn print_path(db: &dyn ExpandDatabase, path: &Path, buf: &mut dyn Wri } pub(crate) fn print_generic_args( - db: &dyn ExpandDatabase, + db: &dyn DefDatabase, generics: &GenericArgs, buf: &mut dyn Write, ) -> fmt::Result { @@ -83,7 +119,7 @@ pub(crate) fn print_generic_args( write!(buf, ", ")?; } first = false; - write!(buf, "{}", binding.name.display(db))?; + write!(buf, "{}", binding.name.display(db.upcast()))?; if !binding.bounds.is_empty() { write!(buf, ": ")?; print_type_bounds(db, &binding.bounds, buf)?; @@ -97,19 +133,19 @@ pub(crate) fn print_generic_args( } pub(crate) fn print_generic_arg( - db: &dyn ExpandDatabase, + db: &dyn DefDatabase, arg: &GenericArg, buf: &mut dyn Write, ) -> fmt::Result { match arg { GenericArg::Type(ty) => print_type_ref(db, ty, buf), - GenericArg::Const(c) => write!(buf, "{}", c.display(db)), - GenericArg::Lifetime(lt) => write!(buf, "{}", lt.name.display(db)), + GenericArg::Const(c) => write!(buf, "{}", c.display(db.upcast())), + GenericArg::Lifetime(lt) => write!(buf, "{}", lt.name.display(db.upcast())), } } pub(crate) fn print_type_ref( - db: &dyn ExpandDatabase, + db: &dyn DefDatabase, type_ref: &TypeRef, buf: &mut dyn Write, ) -> fmt::Result { @@ -143,7 +179,7 @@ pub(crate) fn print_type_ref( }; write!(buf, "&")?; if let Some(lt) = lt { - write!(buf, "{} ", lt.name.display(db))?; + write!(buf, "{} ", lt.name.display(db.upcast()))?; } write!(buf, "{mtbl}")?; print_type_ref(db, pointee, buf)?; @@ -151,7 +187,7 @@ pub(crate) fn print_type_ref( TypeRef::Array(elem, len) => { write!(buf, "[")?; print_type_ref(db, elem, buf)?; - write!(buf, "; {}]", len.display(db))?; + write!(buf, "; {}]", len.display(db.upcast()))?; } TypeRef::Slice(elem) => { write!(buf, "[")?; @@ -198,7 +234,7 @@ pub(crate) fn print_type_ref( } pub(crate) fn print_type_bounds( - db: &dyn ExpandDatabase, + db: &dyn DefDatabase, bounds: &[Interned], buf: &mut dyn Write, ) -> fmt::Result { @@ -216,10 +252,14 @@ pub(crate) fn print_type_bounds( print_path(db, path, buf)?; } TypeBound::ForLifetime(lifetimes, path) => { - write!(buf, "for<{}> ", lifetimes.iter().map(|it| it.display(db)).format(", "))?; + write!( + buf, + "for<{}> ", + lifetimes.iter().map(|it| it.display(db.upcast())).format(", ") + )?; print_path(db, path, buf)?; } - TypeBound::Lifetime(lt) => write!(buf, "{}", lt.name.display(db))?, + TypeBound::Lifetime(lt) => write!(buf, "{}", lt.name.display(db.upcast()))?, TypeBound::Error => write!(buf, "{{unknown}}")?, } } diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs index 2f9187009e2df..50da9ed06a0d9 100644 --- a/crates/hir-def/src/resolver.rs +++ b/crates/hir-def/src/resolver.rs @@ -156,22 +156,19 @@ impl Resolver { ) -> Option<(TypeNs, Option, Option)> { let path = match path { Path::Normal { mod_path, .. } => mod_path, - Path::LangItem(l) => { - return Some(( - match *l { - LangItemTarget::Union(it) => TypeNs::AdtId(it.into()), - LangItemTarget::TypeAlias(it) => TypeNs::TypeAliasId(it), - LangItemTarget::Struct(it) => TypeNs::AdtId(it.into()), - LangItemTarget::EnumVariant(it) => TypeNs::EnumVariantId(it), - LangItemTarget::EnumId(it) => TypeNs::AdtId(it.into()), - LangItemTarget::Trait(it) => TypeNs::TraitId(it), - LangItemTarget::Function(_) - | LangItemTarget::ImplDef(_) - | LangItemTarget::Static(_) => return None, - }, - None, - None, - )) + Path::LangItem(l, seg) => { + let type_ns = match *l { + LangItemTarget::Union(it) => TypeNs::AdtId(it.into()), + LangItemTarget::TypeAlias(it) => TypeNs::TypeAliasId(it), + LangItemTarget::Struct(it) => TypeNs::AdtId(it.into()), + LangItemTarget::EnumVariant(it) => TypeNs::EnumVariantId(it), + LangItemTarget::EnumId(it) => TypeNs::AdtId(it.into()), + LangItemTarget::Trait(it) => TypeNs::TraitId(it), + LangItemTarget::Function(_) + | LangItemTarget::ImplDef(_) + | LangItemTarget::Static(_) => return None, + }; + return Some((type_ns, seg.as_ref().map(|_| 1), None)); } }; let first_name = path.segments().first()?; @@ -256,7 +253,7 @@ impl Resolver { ) -> Option { let path = match path { Path::Normal { mod_path, .. } => mod_path, - Path::LangItem(l) => { + Path::LangItem(l, None) => { return Some(ResolveValueResult::ValueNs( match *l { LangItemTarget::Function(it) => ValueNs::FunctionId(it), @@ -272,6 +269,20 @@ impl Resolver { None, )) } + Path::LangItem(l, Some(_)) => { + let type_ns = match *l { + LangItemTarget::Union(it) => TypeNs::AdtId(it.into()), + LangItemTarget::TypeAlias(it) => TypeNs::TypeAliasId(it), + LangItemTarget::Struct(it) => TypeNs::AdtId(it.into()), + LangItemTarget::EnumVariant(it) => TypeNs::EnumVariantId(it), + LangItemTarget::EnumId(it) => TypeNs::AdtId(it.into()), + LangItemTarget::Trait(it) => TypeNs::TraitId(it), + LangItemTarget::Function(_) + | LangItemTarget::ImplDef(_) + | LangItemTarget::Static(_) => return None, + }; + return Some(ResolveValueResult::Partial(type_ns, 1, None)); + } }; let n_segments = path.segments().len(); let tmp = name![self]; diff --git a/crates/hir-expand/src/builtin_fn_macro.rs b/crates/hir-expand/src/builtin_fn_macro.rs index 95c6baf42da7c..30b19b6e51b8b 100644 --- a/crates/hir-expand/src/builtin_fn_macro.rs +++ b/crates/hir-expand/src/builtin_fn_macro.rs @@ -1,13 +1,9 @@ //! Builtin macro -use std::mem; - -use ::tt::Ident; use base_db::{AnchoredPath, Edition, FileId}; use cfg::CfgExpr; use either::Either; use mbe::{parse_exprs_with_sep, parse_to_token_tree, TokenMap}; -use rustc_hash::FxHashMap; use syntax::{ ast::{self, AstToken}, SmolStr, @@ -97,11 +93,11 @@ register_builtin! { (unreachable, Unreachable) => unreachable_expand, (log_syntax, LogSyntax) => log_syntax_expand, (trace_macros, TraceMacros) => trace_macros_expand, - - EAGER: (format_args, FormatArgs) => format_args_expand, (const_format_args, ConstFormatArgs) => format_args_expand, (format_args_nl, FormatArgsNl) => format_args_nl_expand, + + EAGER: (compile_error, CompileError) => compile_error_expand, (concat, Concat) => concat_expand, (concat_idents, ConcatIdents) => concat_idents_expand, @@ -247,151 +243,15 @@ fn format_args_expand_general( _db: &dyn ExpandDatabase, _id: MacroCallId, tt: &tt::Subtree, - end_string: &str, + // FIXME: Make use of this so that mir interpretation works properly + _end_string: &str, ) -> ExpandResult { - let args = parse_exprs_with_sep(tt, ','); - - let expand_error = - ExpandResult::new(tt::Subtree::empty(), mbe::ExpandError::NoMatchingRule.into()); - - let mut key_args = FxHashMap::default(); - let mut args = args.into_iter().filter_map(|mut arg| { - // Remove `key =`. - if matches!(arg.token_trees.get(1), Some(tt::TokenTree::Leaf(tt::Leaf::Punct(p))) if p.char == '=') - { - // but not with `==` - if !matches!(arg.token_trees.get(2), Some(tt::TokenTree::Leaf(tt::Leaf::Punct(p))) if p.char == '=') - { - let key = arg.token_trees.drain(..2).next().unwrap(); - key_args.insert(key.to_string(), arg); - return None; - } - } - Some(arg) - }).collect::>().into_iter(); - // ^^^^^^^ we need this collect, to enforce the side effect of the filter_map closure (building the `key_args`) - let Some(format_subtree) = args.next() else { - return expand_error; - }; - let format_string = (|| { - let token_tree = format_subtree.token_trees.get(0)?; - match token_tree { - tt::TokenTree::Leaf(l) => match l { - tt::Leaf::Literal(l) => { - if let Some(mut text) = l.text.strip_prefix('r') { - let mut raw_sharps = String::new(); - while let Some(t) = text.strip_prefix('#') { - text = t; - raw_sharps.push('#'); - } - text = - text.strip_suffix(&raw_sharps)?.strip_prefix('"')?.strip_suffix('"')?; - Some((text, l.span, Some(raw_sharps))) - } else { - let text = l.text.strip_prefix('"')?.strip_suffix('"')?; - let span = l.span; - Some((text, span, None)) - } - } - _ => None, - }, - tt::TokenTree::Subtree(_) => None, - } - })(); - let Some((format_string, _format_string_span, raw_sharps)) = format_string else { - return expand_error; - }; - let mut format_iter = format_string.chars().peekable(); - let mut parts = vec![]; - let mut last_part = String::new(); - let mut arg_tts = vec![]; - let mut err = None; - while let Some(c) = format_iter.next() { - // Parsing the format string. See https://doc.rust-lang.org/std/fmt/index.html#syntax for the grammar and more info - match c { - '{' => { - if format_iter.peek() == Some(&'{') { - format_iter.next(); - last_part.push('{'); - continue; - } - let mut argument = String::new(); - while ![Some(&'}'), Some(&':')].contains(&format_iter.peek()) { - argument.push(match format_iter.next() { - Some(c) => c, - None => return expand_error, - }); - } - let format_spec = match format_iter.next().unwrap() { - '}' => "".to_owned(), - ':' => { - let mut s = String::new(); - while let Some(c) = format_iter.next() { - if c == '}' { - break; - } - s.push(c); - } - s - } - _ => unreachable!(), - }; - parts.push(mem::take(&mut last_part)); - let arg_tree = if argument.is_empty() { - match args.next() { - Some(it) => it, - None => { - err = Some(mbe::ExpandError::NoMatchingRule.into()); - tt::Subtree::empty() - } - } - } else if let Some(tree) = key_args.get(&argument) { - tree.clone() - } else { - // FIXME: we should pick the related substring of the `_format_string_span` as the span. You - // can use `.char_indices()` instead of `.char()` for `format_iter` to find the substring interval. - let ident = Ident::new(argument, tt::TokenId::unspecified()); - quote!(#ident) - }; - let formatter = match &*format_spec { - "?" => quote!(::core::fmt::Debug::fmt), - "" => quote!(::core::fmt::Display::fmt), - _ => { - // FIXME: implement the rest and return expand error here - quote!(::core::fmt::Display::fmt) - } - }; - arg_tts.push(quote! { ::core::fmt::ArgumentV1::new(&(#arg_tree), #formatter), }); - } - '}' => { - if format_iter.peek() == Some(&'}') { - format_iter.next(); - last_part.push('}'); - } else { - return expand_error; - } - } - _ => last_part.push(c), - } - } - last_part += end_string; - if !last_part.is_empty() { - parts.push(last_part); - } - let part_tts = parts.into_iter().map(|it| { - let text = if let Some(raw) = &raw_sharps { - format!("r{raw}\"{}\"{raw}", it).into() - } else { - format!("\"{}\"", it).into() - }; - let l = tt::Literal { span: tt::TokenId::unspecified(), text }; - quote!(#l ,) + let pound = quote! {@PUNCT '#'}; + let mut tt = tt.clone(); + tt.delimiter.kind = tt::DelimiterKind::Parenthesis; + return ExpandResult::ok(quote! { + builtin #pound format_args #tt }); - let arg_tts = arg_tts.into_iter().flat_map(|arg| arg.token_trees); - let expanded = quote! { - ::core::fmt::Arguments::new_v1(&[##part_tts], &[##arg_tts]) - }; - ExpandResult { value: expanded, err } } fn asm_expand( @@ -415,10 +275,12 @@ fn asm_expand( } } - let expanded = quote! {{ - ##literals - loop {} - }}; + let pound = quote! {@PUNCT '#'}; + let expanded = quote! { + builtin #pound asm ( + {##literals} + ) + }; ExpandResult::ok(expanded) } @@ -692,7 +554,7 @@ pub(crate) fn include_arg_to_tt( arg_id: MacroCallId, ) -> Result<(triomphe::Arc<(::tt::Subtree<::tt::TokenId>, TokenMap)>, FileId), ExpandError> { let loc = db.lookup_intern_macro_call(arg_id); - let Some(EagerCallInfo { arg,arg_id, .. }) = loc.eager.as_deref() else { + let Some(EagerCallInfo { arg, arg_id, .. }) = loc.eager.as_deref() else { panic!("include_arg_to_tt called on non include macro call: {:?}", &loc.eager); }; let path = parse_string(&arg.0)?; diff --git a/crates/hir-expand/src/hygiene.rs b/crates/hir-expand/src/hygiene.rs index ade4a5928933d..ca65db1136ce1 100644 --- a/crates/hir-expand/src/hygiene.rs +++ b/crates/hir-expand/src/hygiene.rs @@ -242,7 +242,7 @@ impl HygieneFrame { krate, call_site: None, def_site: None, - } + }; }; let def_site = info.attr_input_or_mac_def_start.map(|it| db.hygiene_frame(it.file_id)); diff --git a/crates/hir-expand/src/name.rs b/crates/hir-expand/src/name.rs index 7c179c0cf959c..a876f48bda4e6 100644 --- a/crates/hir-expand/src/name.rs +++ b/crates/hir-expand/src/name.rs @@ -54,6 +54,12 @@ impl Name { Name(Repr::Text(text)) } + // FIXME: See above, unfortunately some places really need this right now + #[doc(hidden)] + pub const fn new_text_dont_use(text: SmolStr) -> Name { + Name(Repr::Text(text)) + } + pub fn new_tuple_field(idx: usize) -> Name { Name(Repr::TupleField(idx)) } @@ -302,6 +308,16 @@ pub mod known { rust_2018, rust_2021, v1, + new_display, + new_debug, + new_lower_exp, + new_upper_exp, + new_octal, + new_pointer, + new_binary, + new_lower_hex, + new_upper_hex, + from_usize, // Components of known path (type name) Iterator, IntoIterator, @@ -327,6 +343,13 @@ pub mod known { Not, None, Index, + Left, + Right, + Center, + Unknown, + Is, + Param, + Implied, // Components of known path (function name) filter_map, next, @@ -335,6 +358,8 @@ pub mod known { is_empty, as_str, new, + new_v1_formatted, + none, // Builtin macros asm, assert, diff --git a/crates/hir-ty/Cargo.toml b/crates/hir-ty/Cargo.toml index abc19d63abf0e..b95ae05ccd458 100644 --- a/crates/hir-ty/Cargo.toml +++ b/crates/hir-ty/Cargo.toml @@ -32,7 +32,8 @@ once_cell = "1.17.0" triomphe.workspace = true nohash-hasher.workspace = true typed-arena = "2.0.1" -rustc_index = { version = "0.0.20221221", package = "hkalbasi-rustc-ap-rustc_index", default-features = false } + +rustc_index.workspace = true # local deps stdx.workspace = true diff --git a/crates/hir-ty/src/consteval/tests/intrinsics.rs b/crates/hir-ty/src/consteval/tests/intrinsics.rs index 2855f789001a7..44a4ac27af071 100644 --- a/crates/hir-ty/src/consteval/tests/intrinsics.rs +++ b/crates/hir-ty/src/consteval/tests/intrinsics.rs @@ -499,24 +499,26 @@ fn offset() { r#" //- minicore: coerce_unsized, index, slice extern "rust-intrinsic" { - pub fn offset(dst: *const T, offset: isize) -> *const T; + pub fn offset(dst: Ptr, offset: Delta) -> Ptr; + pub fn arith_offset(dst: *const T, offset: isize) -> *const T; } - const GOAL: u8 = unsafe { - let ar: &[(u8, u8, u8)] = &[ + const GOAL: i32 = unsafe { + let ar: &[(i32, i32, i32)] = &[ (10, 11, 12), (20, 21, 22), (30, 31, 32), (40, 41, 42), (50, 51, 52), ]; - let ar: *const [(u8, u8, u8)] = ar; - let ar = ar as *const (u8, u8, u8); - let element = *offset(ar, 2); - element.1 + let ar: *const [(i32, i32, i32)] = ar; + let ar = ar as *const (i32, i32, i32); + let element3 = *offset(ar, 2usize); + let element4 = *arith_offset(ar, 3); + element3.1 * 100 + element4.0 }; "#, - 31, + 3140, ); } @@ -584,6 +586,24 @@ fn write_bytes() { ); } +#[test] +fn write_via_move() { + check_number( + r#" + extern "rust-intrinsic" { + fn write_via_move(ptr: *mut T, value: T); + } + + const GOAL: i32 = unsafe { + let mut x = 2; + write_via_move(&mut x, 100); + x + }; + "#, + 100, + ); +} + #[test] fn copy() { check_number( diff --git a/crates/hir-ty/src/diagnostics/decl_check.rs b/crates/hir-ty/src/diagnostics/decl_check.rs index a94a962c1f858..36d69edf9d5d9 100644 --- a/crates/hir-ty/src/diagnostics/decl_check.rs +++ b/crates/hir-ty/src/diagnostics/decl_check.rs @@ -163,25 +163,56 @@ impl<'a> DeclValidator<'a> { || allows.contains(allow::NONSTANDARD_STYLE) }) }; + let db = self.db.upcast(); + let file_id_is_derive = || { + match id { + AttrDefId::ModuleId(m) => { + m.def_map(db)[m.local_id].origin.file_id().map(Into::into) + } + AttrDefId::FunctionId(f) => Some(f.lookup(db).id.file_id()), + AttrDefId::StaticId(sid) => Some(sid.lookup(db).id.file_id()), + AttrDefId::ConstId(cid) => Some(cid.lookup(db).id.file_id()), + AttrDefId::TraitId(tid) => Some(tid.lookup(db).id.file_id()), + AttrDefId::TraitAliasId(taid) => Some(taid.lookup(db).id.file_id()), + AttrDefId::ImplId(iid) => Some(iid.lookup(db).id.file_id()), + AttrDefId::ExternBlockId(id) => Some(id.lookup(db).id.file_id()), + AttrDefId::ExternCrateId(id) => Some(id.lookup(db).id.file_id()), + AttrDefId::UseId(id) => Some(id.lookup(db).id.file_id()), + // These warnings should not explore macro definitions at all + AttrDefId::MacroId(_) => None, + AttrDefId::AdtId(aid) => match aid { + AdtId::StructId(sid) => Some(sid.lookup(db).id.file_id()), + AdtId::EnumId(eid) => Some(eid.lookup(db).id.file_id()), + // Unions aren't yet supported + AdtId::UnionId(_) => None, + }, + AttrDefId::FieldId(_) => None, + AttrDefId::EnumVariantId(_) => None, + AttrDefId::TypeAliasId(_) => None, + AttrDefId::GenericParamId(_) => None, + } + .map_or(false, |file_id| { + file_id.is_custom_derive(db.upcast()) || file_id.is_builtin_derive(db.upcast()) + }) + }; - is_allowed(id) - // go upwards one step or give up - || match id { - AttrDefId::ModuleId(m) => m.containing_module(self.db.upcast()).map(|v| v.into()), - AttrDefId::FunctionId(f) => Some(f.lookup(self.db.upcast()).container.into()), - AttrDefId::StaticId(sid) => Some(sid.lookup(self.db.upcast()).container.into()), - AttrDefId::ConstId(cid) => Some(cid.lookup(self.db.upcast()).container.into()), - AttrDefId::TraitId(tid) => Some(tid.lookup(self.db.upcast()).container.into()), - AttrDefId::TraitAliasId(taid) => Some(taid.lookup(self.db.upcast()).container.into()), - AttrDefId::ImplId(iid) => Some(iid.lookup(self.db.upcast()).container.into()), - AttrDefId::ExternBlockId(id) => Some(id.lookup(self.db.upcast()).container.into()), - AttrDefId::ExternCrateId(id) => Some(id.lookup(self.db.upcast()).container.into()), - AttrDefId::UseId(id) => Some(id.lookup(self.db.upcast()).container.into()), + let parent = || { + match id { + AttrDefId::ModuleId(m) => m.containing_module(db).map(|v| v.into()), + AttrDefId::FunctionId(f) => Some(f.lookup(db).container.into()), + AttrDefId::StaticId(sid) => Some(sid.lookup(db).container.into()), + AttrDefId::ConstId(cid) => Some(cid.lookup(db).container.into()), + AttrDefId::TraitId(tid) => Some(tid.lookup(db).container.into()), + AttrDefId::TraitAliasId(taid) => Some(taid.lookup(db).container.into()), + AttrDefId::ImplId(iid) => Some(iid.lookup(db).container.into()), + AttrDefId::ExternBlockId(id) => Some(id.lookup(db).container.into()), + AttrDefId::ExternCrateId(id) => Some(id.lookup(db).container.into()), + AttrDefId::UseId(id) => Some(id.lookup(db).container.into()), // These warnings should not explore macro definitions at all AttrDefId::MacroId(_) => None, AttrDefId::AdtId(aid) => match aid { - AdtId::StructId(sid) => Some(sid.lookup(self.db.upcast()).container.into()), - AdtId::EnumId(eid) => Some(eid.lookup(self.db.upcast()).container.into()), + AdtId::StructId(sid) => Some(sid.lookup(db).container.into()), + AdtId::EnumId(eid) => Some(eid.lookup(db).container.into()), // Unions aren't yet supported AdtId::UnionId(_) => None, }, @@ -191,6 +222,12 @@ impl<'a> DeclValidator<'a> { AttrDefId::GenericParamId(_) => None, } .is_some_and(|mid| self.allowed(mid, allow_name, true)) + }; + is_allowed(id) + // FIXME: this is a hack to avoid false positives in derive macros currently + || file_id_is_derive() + // go upwards one step or give up + || parent() } fn validate_func(&mut self, func: FunctionId) { diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 0fb4934444b4f..78d3c667a1f8d 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -194,7 +194,8 @@ pub(crate) type InferResult = Result, TypeError>; #[derive(Debug, PartialEq, Eq, Clone)] pub enum InferenceDiagnostic { NoSuchField { - expr: ExprId, + field: ExprOrPatId, + private: bool, }, PrivateField { expr: ExprId, @@ -228,6 +229,11 @@ pub enum InferenceDiagnostic { expected: usize, found: usize, }, + MismatchedTupleStructPatArgCount { + pat: ExprOrPatId, + expected: usize, + found: usize, + }, ExpectedFunction { call_expr: ExprId, found: Ty, diff --git a/crates/hir-ty/src/infer/cast.rs b/crates/hir-ty/src/infer/cast.rs index 9e1c74b16fa01..a116d444731c2 100644 --- a/crates/hir-ty/src/infer/cast.rs +++ b/crates/hir-ty/src/infer/cast.rs @@ -39,8 +39,14 @@ impl CastCheck { } fn check_ref_to_ptr_cast(expr_ty: Ty, cast_ty: Ty, table: &mut InferenceTable<'_>) -> bool { - let Some((expr_inner_ty, _, _)) = expr_ty.as_reference() else { return false; }; - let Some((cast_inner_ty, _)) = cast_ty.as_raw_ptr() else { return false; }; - let TyKind::Array(expr_elt_ty, _) = expr_inner_ty.kind(Interner) else { return false; }; + let Some((expr_inner_ty, _, _)) = expr_ty.as_reference() else { + return false; + }; + let Some((cast_inner_ty, _)) = cast_ty.as_raw_ptr() else { + return false; + }; + let TyKind::Array(expr_elt_ty, _) = expr_inner_ty.kind(Interner) else { + return false; + }; table.coerce(expr_elt_ty, cast_inner_ty).is_ok() } diff --git a/crates/hir-ty/src/infer/closure.rs b/crates/hir-ty/src/infer/closure.rs index 23efe616f4f64..13d6b5643ac4a 100644 --- a/crates/hir-ty/src/infer/closure.rs +++ b/crates/hir-ty/src/infer/closure.rs @@ -452,6 +452,8 @@ impl InferenceContext<'_> { fn walk_expr_without_adjust(&mut self, tgt_expr: ExprId) { match &self.body[tgt_expr] { + Expr::OffsetOf(_) => (), + Expr::InlineAsm(e) => self.walk_expr_without_adjust(e.e), Expr::If { condition, then_branch, else_branch } => { self.consume_expr(*condition); self.consume_expr(*then_branch); @@ -467,13 +469,13 @@ impl InferenceContext<'_> { Statement::Let { pat, type_ref: _, initializer, else_branch } => { if let Some(else_branch) = else_branch { self.consume_expr(*else_branch); - if let Some(initializer) = initializer { - self.consume_expr(*initializer); - } - return; } if let Some(initializer) = initializer { - self.walk_expr(*initializer); + if else_branch.is_some() { + self.consume_expr(*initializer); + } else { + self.walk_expr(*initializer); + } if let Some(place) = self.place_of_expr(*initializer) { self.consume_with_pat(place, *pat); } @@ -620,6 +622,7 @@ impl InferenceContext<'_> { | Expr::Tuple { exprs, is_assignee_expr: _ } => { self.consume_exprs(exprs.iter().copied()) } + Expr::Missing | Expr::Continue { .. } | Expr::Path(_) diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 8b3521410848f..0c3c725a7c743 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -514,9 +514,6 @@ impl InferenceContext<'_> { } Expr::RecordLit { path, fields, spread, .. } => { let (ty, def_id) = self.resolve_variant(path.as_deref(), false); - if let Some(variant) = def_id { - self.write_variant_resolution(tgt_expr.into(), variant); - } if let Some(t) = expected.only_has_type(&mut self.table) { self.unify(&ty, &t); @@ -526,26 +523,56 @@ impl InferenceContext<'_> { .as_adt() .map(|(_, s)| s.clone()) .unwrap_or_else(|| Substitution::empty(Interner)); - let field_types = def_id.map(|it| self.db.field_types(it)).unwrap_or_default(); - let variant_data = def_id.map(|it| it.variant_data(self.db.upcast())); - for field in fields.iter() { - let field_def = - variant_data.as_ref().and_then(|it| match it.field(&field.name) { - Some(local_id) => Some(FieldId { parent: def_id.unwrap(), local_id }), - None => { - self.push_diagnostic(InferenceDiagnostic::NoSuchField { - expr: field.expr, - }); - None - } - }); - let field_ty = field_def.map_or(self.err_ty(), |it| { - field_types[it.local_id].clone().substitute(Interner, &substs) - }); - // Field type might have some unknown types - // FIXME: we may want to emit a single type variable for all instance of type fields? - let field_ty = self.insert_type_vars(field_ty); - self.infer_expr_coerce(field.expr, &Expectation::has_type(field_ty)); + if let Some(variant) = def_id { + self.write_variant_resolution(tgt_expr.into(), variant); + } + match def_id { + _ if fields.is_empty() => {} + Some(def) => { + let field_types = self.db.field_types(def); + let variant_data = def.variant_data(self.db.upcast()); + let visibilities = self.db.field_visibilities(def); + for field in fields.iter() { + let field_def = { + match variant_data.field(&field.name) { + Some(local_id) => { + if !visibilities[local_id].is_visible_from( + self.db.upcast(), + self.resolver.module(), + ) { + self.push_diagnostic( + InferenceDiagnostic::NoSuchField { + field: field.expr.into(), + private: true, + }, + ); + } + Some(local_id) + } + None => { + self.push_diagnostic(InferenceDiagnostic::NoSuchField { + field: field.expr.into(), + private: false, + }); + None + } + } + }; + let field_ty = field_def.map_or(self.err_ty(), |it| { + field_types[it].clone().substitute(Interner, &substs) + }); + + // Field type might have some unknown types + // FIXME: we may want to emit a single type variable for all instance of type fields? + let field_ty = self.insert_type_vars(field_ty); + self.infer_expr_coerce(field.expr, &Expectation::has_type(field_ty)); + } + } + None => { + for field in fields.iter() { + self.infer_expr_coerce(field.expr, &Expectation::None); + } + } } if let Some(expr) = spread { self.infer_expr(*expr, &Expectation::has_type(ty.clone())); @@ -843,6 +870,11 @@ impl InferenceContext<'_> { }); expected } + Expr::OffsetOf(_) => TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(Interner), + Expr::InlineAsm(it) => { + self.infer_expr_no_expect(it.e); + self.result.standard_types.unit.clone() + } }; // use a new type variable if we got unknown here let ty = self.insert_type_vars_shallow(ty); @@ -1122,7 +1154,7 @@ impl InferenceContext<'_> { Expr::Underscore => rhs_ty.clone(), _ => { // `lhs` is a place expression, a unit struct, or an enum variant. - let lhs_ty = self.infer_expr(lhs, &Expectation::none()); + let lhs_ty = self.infer_expr_inner(lhs, &Expectation::none()); // This is the only branch where this function may coerce any type. // We are returning early to avoid the unifiability check below. diff --git a/crates/hir-ty/src/infer/mutability.rs b/crates/hir-ty/src/infer/mutability.rs index 396ca0044ff02..b8a1af96fba64 100644 --- a/crates/hir-ty/src/infer/mutability.rs +++ b/crates/hir-ty/src/infer/mutability.rs @@ -35,6 +35,8 @@ impl InferenceContext<'_> { fn infer_mut_expr_without_adjust(&mut self, tgt_expr: ExprId, mutability: Mutability) { match &self.body[tgt_expr] { Expr::Missing => (), + Expr::InlineAsm(e) => self.infer_mut_expr_without_adjust(e.e, Mutability::Not), + Expr::OffsetOf(_) => (), &Expr::If { condition, then_branch, else_branch } => { self.infer_mut_expr(condition, Mutability::Not); self.infer_mut_expr(then_branch, Mutability::Not); diff --git a/crates/hir-ty/src/infer/pat.rs b/crates/hir-ty/src/infer/pat.rs index 5da0ab76b880d..4e28ec06023ed 100644 --- a/crates/hir-ty/src/infer/pat.rs +++ b/crates/hir-ty/src/infer/pat.rs @@ -15,7 +15,8 @@ use crate::{ infer::{BindingMode, Expectation, InferenceContext, TypeMismatch}, lower::lower_to_chalk_mutability, primitive::UintTy, - static_lifetime, Interner, Scalar, Substitution, Ty, TyBuilder, TyExt, TyKind, + static_lifetime, InferenceDiagnostic, Interner, Scalar, Substitution, Ty, TyBuilder, TyExt, + TyKind, }; /// Used to generalize patterns and assignee expressions. @@ -74,29 +75,68 @@ impl InferenceContext<'_> { if let Some(variant) = def { self.write_variant_resolution(id.into(), variant); } + if let Some(var) = &var_data { + let cmp = if ellipsis.is_some() { usize::gt } else { usize::ne }; + + if cmp(&subs.len(), &var.fields().len()) { + self.push_diagnostic(InferenceDiagnostic::MismatchedTupleStructPatArgCount { + pat: id.into(), + expected: var.fields().len(), + found: subs.len(), + }); + } + } + self.unify(&ty, expected); let substs = ty.as_adt().map(|(_, s)| s.clone()).unwrap_or_else(|| Substitution::empty(Interner)); - let field_tys = def.map(|it| self.db.field_types(it)).unwrap_or_default(); - let (pre, post) = match ellipsis { - Some(idx) => subs.split_at(idx), - None => (subs, &[][..]), - }; - let post_idx_offset = field_tys.iter().count().saturating_sub(post.len()); - - let pre_iter = pre.iter().enumerate(); - let post_iter = (post_idx_offset..).zip(post.iter()); - for (i, &subpat) in pre_iter.chain(post_iter) { - let expected_ty = var_data - .as_ref() - .and_then(|d| d.field(&Name::new_tuple_field(i))) - .map_or(self.err_ty(), |field| { - field_tys[field].clone().substitute(Interner, &substs) - }); - let expected_ty = self.normalize_associated_types_in(expected_ty); - T::infer(self, subpat, &expected_ty, default_bm); + match def { + _ if subs.len() == 0 => {} + Some(def) => { + let field_types = self.db.field_types(def); + let variant_data = def.variant_data(self.db.upcast()); + let visibilities = self.db.field_visibilities(def); + + let (pre, post) = match ellipsis { + Some(idx) => subs.split_at(idx), + None => (subs, &[][..]), + }; + let post_idx_offset = field_types.iter().count().saturating_sub(post.len()); + + let pre_iter = pre.iter().enumerate(); + let post_iter = (post_idx_offset..).zip(post.iter()); + + for (i, &subpat) in pre_iter.chain(post_iter) { + let field_def = { + match variant_data.field(&Name::new_tuple_field(i)) { + Some(local_id) => { + if !visibilities[local_id] + .is_visible_from(self.db.upcast(), self.resolver.module()) + { + // FIXME(DIAGNOSE): private tuple field + } + Some(local_id) + } + None => None, + } + }; + + let expected_ty = field_def.map_or(self.err_ty(), |f| { + field_types[f].clone().substitute(Interner, &substs) + }); + let expected_ty = self.normalize_associated_types_in(expected_ty); + + T::infer(self, subpat, &expected_ty, default_bm); + } + } + None => { + let err_ty = self.err_ty(); + for &inner in subs { + T::infer(self, inner, &err_ty, default_bm); + } + } } ty @@ -109,7 +149,7 @@ impl InferenceContext<'_> { expected: &Ty, default_bm: T::BindingMode, id: T, - subs: impl Iterator, + subs: impl Iterator + ExactSizeIterator, ) -> Ty { let (ty, def) = self.resolve_variant(path, false); if let Some(variant) = def { @@ -121,17 +161,51 @@ impl InferenceContext<'_> { let substs = ty.as_adt().map(|(_, s)| s.clone()).unwrap_or_else(|| Substitution::empty(Interner)); - let field_tys = def.map(|it| self.db.field_types(it)).unwrap_or_default(); - let var_data = def.map(|it| it.variant_data(self.db.upcast())); + match def { + _ if subs.len() == 0 => {} + Some(def) => { + let field_types = self.db.field_types(def); + let variant_data = def.variant_data(self.db.upcast()); + let visibilities = self.db.field_visibilities(def); + + for (name, inner) in subs { + let field_def = { + match variant_data.field(&name) { + Some(local_id) => { + if !visibilities[local_id] + .is_visible_from(self.db.upcast(), self.resolver.module()) + { + self.push_diagnostic(InferenceDiagnostic::NoSuchField { + field: inner.into(), + private: true, + }); + } + Some(local_id) + } + None => { + self.push_diagnostic(InferenceDiagnostic::NoSuchField { + field: inner.into(), + private: false, + }); + None + } + } + }; - for (name, inner) in subs { - let expected_ty = var_data - .as_ref() - .and_then(|it| it.field(&name)) - .map_or(self.err_ty(), |f| field_tys[f].clone().substitute(Interner, &substs)); - let expected_ty = self.normalize_associated_types_in(expected_ty); + let expected_ty = field_def.map_or(self.err_ty(), |f| { + field_types[f].clone().substitute(Interner, &substs) + }); + let expected_ty = self.normalize_associated_types_in(expected_ty); - T::infer(self, inner, &expected_ty, default_bm); + T::infer(self, inner, &expected_ty, default_bm); + } + } + None => { + let err_ty = self.err_ty(); + for (_, inner) in subs { + T::infer(self, inner, &err_ty, default_bm); + } + } } ty diff --git a/crates/hir-ty/src/infer/path.rs b/crates/hir-ty/src/infer/path.rs index 2a51c84db3aa7..c6bbf2f614071 100644 --- a/crates/hir-ty/src/infer/path.rs +++ b/crates/hir-ty/src/infer/path.rs @@ -178,13 +178,30 @@ impl InferenceContext<'_> { remaining_index: usize, id: ExprOrPatId, ) -> Option<(ValueNs, Substitution)> { - assert!(remaining_index < path.segments().len()); // there may be more intermediate segments between the resolved one and // the end. Only the last segment needs to be resolved to a value; from // the segments before that, we need to get either a type or a trait ref. - let resolved_segment = path.segments().get(remaining_index - 1).unwrap(); - let remaining_segments = path.segments().skip(remaining_index); + let _d; + let (resolved_segment, remaining_segments) = match path { + Path::Normal { .. } => { + assert!(remaining_index < path.segments().len()); + ( + path.segments().get(remaining_index - 1).unwrap(), + path.segments().skip(remaining_index), + ) + } + Path::LangItem(..) => ( + PathSegment { + name: { + _d = hir_expand::name::known::Unknown; + &_d + }, + args_and_bindings: None, + }, + path.segments(), + ), + }; let is_before_last = remaining_segments.len() == 1; match (def, is_before_last) { diff --git a/crates/hir-ty/src/layout.rs b/crates/hir-ty/src/layout.rs index b15339d4434cb..1a6106c0244c6 100644 --- a/crates/hir-ty/src/layout.rs +++ b/crates/hir-ty/src/layout.rs @@ -24,7 +24,7 @@ pub use self::{ macro_rules! user_error { ($it: expr) => { - return Err(LayoutError::UserError(format!($it))) + return Err(LayoutError::UserError(format!($it).into())) }; } @@ -50,7 +50,7 @@ pub type Variants = hir_def::layout::Variants; #[derive(Debug, PartialEq, Eq, Clone)] pub enum LayoutError { - UserError(String), + UserError(Box), SizeOverflow, TargetLayoutNotAvailable, HasPlaceholder, @@ -109,7 +109,8 @@ fn layout_of_simd_ty( // * the homogeneous field type and the number of fields. let (e_ty, e_len, is_array) = if let TyKind::Array(e_ty, _) = f0_ty.kind(Interner) { // Extract the number of elements from the layout of the array field: - let FieldsShape::Array { count, .. } = db.layout_of_ty(f0_ty.clone(), env.clone())?.fields else { + let FieldsShape::Array { count, .. } = db.layout_of_ty(f0_ty.clone(), env.clone())?.fields + else { user_error!("Array with non array layout"); }; @@ -233,9 +234,9 @@ pub fn layout_of_ty_query( cx.univariant(dl, &fields, &ReprOptions::default(), kind).ok_or(LayoutError::Unknown)? } TyKind::Array(element, count) => { - let count = try_const_usize(db, &count).ok_or(LayoutError::UserError( - "unevaluated or mistyped const generic parameter".to_string(), - ))? as u64; + let count = try_const_usize(db, &count).ok_or(LayoutError::UserError(Box::from( + "unevaluated or mistyped const generic parameter", + )))? as u64; let element = db.layout_of_ty(element.clone(), trait_env.clone())?; let size = element.size.checked_mul(count, dl).ok_or(LayoutError::SizeOverflow)?; diff --git a/crates/hir-ty/src/layout/adt.rs b/crates/hir-ty/src/layout/adt.rs index 1c92e80f3355b..85ef649b89593 100644 --- a/crates/hir-ty/src/layout/adt.rs +++ b/crates/hir-ty/src/layout/adt.rs @@ -163,7 +163,7 @@ fn repr_discr( return Err(LayoutError::UserError( "Integer::repr_discr: `#[repr]` hint too small for \ discriminant range of enum " - .to_string(), + .into(), )); } return Ok((discr, ity.is_signed())); diff --git a/crates/hir-ty/src/layout/tests.rs b/crates/hir-ty/src/layout/tests.rs index 333ad473a8b2c..ffdbb9de934dc 100644 --- a/crates/hir-ty/src/layout/tests.rs +++ b/crates/hir-ty/src/layout/tests.rs @@ -212,14 +212,14 @@ fn recursive() { } check_fail( r#"struct Goal(Goal);"#, - LayoutError::UserError("infinite sized recursive type".to_string()), + LayoutError::UserError("infinite sized recursive type".into()), ); check_fail( r#" struct Foo(Foo); struct Goal(Foo); "#, - LayoutError::UserError("infinite sized recursive type".to_string()), + LayoutError::UserError("infinite sized recursive type".into()), ); } diff --git a/crates/hir-ty/src/layout/tests/closure.rs b/crates/hir-ty/src/layout/tests/closure.rs index 576e7f3fc619c..bbe855a14de7e 100644 --- a/crates/hir-ty/src/layout/tests/closure.rs +++ b/crates/hir-ty/src/layout/tests/closure.rs @@ -255,3 +255,17 @@ fn ellipsis_pattern() { } } } + +#[test] +fn regression_15623() { + size_and_align_expr! { + let a = 2; + let b = 3; + let c = 5; + move || { + let 0 = a else { return b; }; + let y = c; + y + } + } +} diff --git a/crates/hir-ty/src/mir.rs b/crates/hir-ty/src/mir.rs index 9be083d01177a..e953058ccca45 100644 --- a/crates/hir-ty/src/mir.rs +++ b/crates/hir-ty/src/mir.rs @@ -1,6 +1,6 @@ //! MIR definitions and implementation -use std::{fmt::Display, iter}; +use std::{collections::hash_map::Entry, fmt::Display, iter}; use crate::{ consteval::usize_const, @@ -37,6 +37,7 @@ pub use monomorphization::{ monomorphize_mir_body_bad, monomorphized_mir_body_for_closure_query, monomorphized_mir_body_query, monomorphized_mir_body_recover, }; +use rustc_hash::FxHashMap; use smallvec::{smallvec, SmallVec}; use stdx::{impl_from, never}; use triomphe::Arc; @@ -165,8 +166,8 @@ impl ProjectionElem { TyKind::Adt(_, subst) => { db.field_types(f.parent)[f.local_id].clone().substitute(Interner, subst) } - _ => { - never!("Only adt has field"); + ty => { + never!("Only adt has field, found {:?}", ty); return TyKind::Error.intern(Interner); } }, @@ -223,35 +224,93 @@ impl ProjectionElem { type PlaceElem = ProjectionElem; +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ProjectionId(u32); + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ProjectionStore { + id_to_proj: FxHashMap>, + proj_to_id: FxHashMap, ProjectionId>, +} + +impl Default for ProjectionStore { + fn default() -> Self { + let mut this = Self { id_to_proj: Default::default(), proj_to_id: Default::default() }; + // Ensure that [] will get the id 0 which is used in `ProjectionId::Empty` + this.intern(Box::new([])); + this + } +} + +impl ProjectionStore { + fn shrink_to_fit(&mut self) { + self.id_to_proj.shrink_to_fit(); + self.proj_to_id.shrink_to_fit(); + } + + fn intern_if_exist(&self, projection: &[PlaceElem]) -> Option { + self.proj_to_id.get(projection).copied() + } + + fn intern(&mut self, projection: Box<[PlaceElem]>) -> ProjectionId { + let new_id = ProjectionId(self.proj_to_id.len() as u32); + match self.proj_to_id.entry(projection) { + Entry::Occupied(id) => *id.get(), + Entry::Vacant(e) => { + let key_clone = e.key().clone(); + e.insert(new_id); + self.id_to_proj.insert(new_id, key_clone); + new_id + } + } + } +} + +impl ProjectionId { + const EMPTY: ProjectionId = ProjectionId(0); + + fn lookup(self, store: &ProjectionStore) -> &[PlaceElem] { + store.id_to_proj.get(&self).unwrap() + } + + fn project(self, projection: PlaceElem, store: &mut ProjectionStore) -> ProjectionId { + let mut current = self.lookup(store).to_vec(); + current.push(projection); + store.intern(current.into()) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Place { pub local: LocalId, - pub projection: Box<[PlaceElem]>, + pub projection: ProjectionId, } impl Place { - fn is_parent(&self, child: &Place) -> bool { - self.local == child.local && child.projection.starts_with(&self.projection) + fn is_parent(&self, child: &Place, store: &ProjectionStore) -> bool { + self.local == child.local + && child.projection.lookup(store).starts_with(&self.projection.lookup(store)) } /// The place itself is not included - fn iterate_over_parents(&self) -> impl Iterator + '_ { - (0..self.projection.len()) - .map(|x| &self.projection[0..x]) - .map(|x| Place { local: self.local, projection: x.to_vec().into() }) + fn iterate_over_parents<'a>( + &'a self, + store: &'a ProjectionStore, + ) -> impl Iterator + 'a { + let projection = self.projection.lookup(store); + (0..projection.len()).map(|x| &projection[0..x]).filter_map(move |x| { + Some(Place { local: self.local, projection: store.intern_if_exist(x)? }) + }) } - fn project(&self, projection: PlaceElem) -> Place { - Place { - local: self.local, - projection: self.projection.iter().cloned().chain([projection]).collect(), - } + fn project(&self, projection: PlaceElem, store: &mut ProjectionStore) -> Place { + Place { local: self.local, projection: self.projection.project(projection, store) } } } impl From for Place { fn from(local: LocalId) -> Self { - Self { local, projection: vec![].into() } + Self { local, projection: ProjectionId::EMPTY } } } @@ -997,6 +1056,7 @@ pub struct BasicBlock { #[derive(Debug, Clone, PartialEq, Eq)] pub struct MirBody { + pub projection_store: ProjectionStore, pub basic_blocks: Arena, pub locals: Arena, pub start_block: BasicBlockId, @@ -1009,11 +1069,15 @@ pub struct MirBody { } impl MirBody { - fn walk_places(&mut self, mut f: impl FnMut(&mut Place)) { - fn for_operand(op: &mut Operand, f: &mut impl FnMut(&mut Place)) { + fn walk_places(&mut self, mut f: impl FnMut(&mut Place, &mut ProjectionStore)) { + fn for_operand( + op: &mut Operand, + f: &mut impl FnMut(&mut Place, &mut ProjectionStore), + store: &mut ProjectionStore, + ) { match op { Operand::Copy(p) | Operand::Move(p) => { - f(p); + f(p, store); } Operand::Constant(_) | Operand::Static(_) => (), } @@ -1022,30 +1086,30 @@ impl MirBody { for statement in &mut block.statements { match &mut statement.kind { StatementKind::Assign(p, r) => { - f(p); + f(p, &mut self.projection_store); match r { Rvalue::ShallowInitBoxWithAlloc(_) => (), Rvalue::ShallowInitBox(o, _) | Rvalue::UnaryOp(_, o) | Rvalue::Cast(_, o, _) | Rvalue::Repeat(o, _) - | Rvalue::Use(o) => for_operand(o, &mut f), + | Rvalue::Use(o) => for_operand(o, &mut f, &mut self.projection_store), Rvalue::CopyForDeref(p) | Rvalue::Discriminant(p) | Rvalue::Len(p) - | Rvalue::Ref(_, p) => f(p), + | Rvalue::Ref(_, p) => f(p, &mut self.projection_store), Rvalue::CheckedBinaryOp(_, o1, o2) => { - for_operand(o1, &mut f); - for_operand(o2, &mut f); + for_operand(o1, &mut f, &mut self.projection_store); + for_operand(o2, &mut f, &mut self.projection_store); } Rvalue::Aggregate(_, ops) => { for op in ops.iter_mut() { - for_operand(op, &mut f); + for_operand(op, &mut f, &mut self.projection_store); } } } } - StatementKind::Deinit(p) => f(p), + StatementKind::Deinit(p) => f(p, &mut self.projection_store), StatementKind::StorageLive(_) | StatementKind::StorageDead(_) | StatementKind::Nop => (), @@ -1053,7 +1117,9 @@ impl MirBody { } match &mut block.terminator { Some(x) => match &mut x.kind { - TerminatorKind::SwitchInt { discr, .. } => for_operand(discr, &mut f), + TerminatorKind::SwitchInt { discr, .. } => { + for_operand(discr, &mut f, &mut self.projection_store) + } TerminatorKind::FalseEdge { .. } | TerminatorKind::FalseUnwind { .. } | TerminatorKind::Goto { .. } @@ -1063,23 +1129,24 @@ impl MirBody { | TerminatorKind::Return | TerminatorKind::Unreachable => (), TerminatorKind::Drop { place, .. } => { - f(place); + f(place, &mut self.projection_store); } TerminatorKind::DropAndReplace { place, value, .. } => { - f(place); - for_operand(value, &mut f); + f(place, &mut self.projection_store); + for_operand(value, &mut f, &mut self.projection_store); } TerminatorKind::Call { func, args, destination, .. } => { - for_operand(func, &mut f); - args.iter_mut().for_each(|x| for_operand(x, &mut f)); - f(destination); + for_operand(func, &mut f, &mut self.projection_store); + args.iter_mut() + .for_each(|x| for_operand(x, &mut f, &mut self.projection_store)); + f(destination, &mut self.projection_store); } TerminatorKind::Assert { cond, .. } => { - for_operand(cond, &mut f); + for_operand(cond, &mut f, &mut self.projection_store); } TerminatorKind::Yield { value, resume_arg, .. } => { - for_operand(value, &mut f); - f(resume_arg); + for_operand(value, &mut f, &mut self.projection_store); + f(resume_arg, &mut self.projection_store); } }, None => (), @@ -1096,7 +1163,9 @@ impl MirBody { binding_locals, param_locals, closures, + projection_store, } = self; + projection_store.shrink_to_fit(); basic_blocks.shrink_to_fit(); locals.shrink_to_fit(); binding_locals.shrink_to_fit(); diff --git a/crates/hir-ty/src/mir/borrowck.rs b/crates/hir-ty/src/mir/borrowck.rs index c70d7f63fd880..41fb129652a1e 100644 --- a/crates/hir-ty/src/mir/borrowck.rs +++ b/crates/hir-ty/src/mir/borrowck.rs @@ -42,30 +42,27 @@ pub struct BorrowckResult { fn all_mir_bodies( db: &dyn HirDatabase, def: DefWithBodyId, -) -> Box, MirLowerError>> + '_> { + mut cb: impl FnMut(Arc), +) -> Result<(), MirLowerError> { fn for_closure( db: &dyn HirDatabase, c: ClosureId, - ) -> Box, MirLowerError>> + '_> { + cb: &mut impl FnMut(Arc), + ) -> Result<(), MirLowerError> { match db.mir_body_for_closure(c) { Ok(body) => { - let closures = body.closures.clone(); - Box::new( - iter::once(Ok(body)) - .chain(closures.into_iter().flat_map(|it| for_closure(db, it))), - ) + cb(body.clone()); + body.closures.iter().map(|&it| for_closure(db, it, cb)).collect() } - Err(e) => Box::new(iter::once(Err(e))), + Err(e) => Err(e), } } match db.mir_body(def) { Ok(body) => { - let closures = body.closures.clone(); - Box::new( - iter::once(Ok(body)).chain(closures.into_iter().flat_map(|it| for_closure(db, it))), - ) + cb(body.clone()); + body.closures.iter().map(|&it| for_closure(db, it, &mut cb)).collect() } - Err(e) => Box::new(iter::once(Err(e))), + Err(e) => Err(e), } } @@ -74,17 +71,15 @@ pub fn borrowck_query( def: DefWithBodyId, ) -> Result, MirLowerError> { let _p = profile::span("borrowck_query"); - let r = all_mir_bodies(db, def) - .map(|body| { - let body = body?; - Ok(BorrowckResult { - mutability_of_locals: mutability_of_locals(db, &body), - moved_out_of_ref: moved_out_of_ref(db, &body), - mir_body: body, - }) - }) - .collect::, MirLowerError>>()?; - Ok(r.into()) + let mut res = vec![]; + all_mir_bodies(db, def, |body| { + res.push(BorrowckResult { + mutability_of_locals: mutability_of_locals(db, &body), + moved_out_of_ref: moved_out_of_ref(db, &body), + mir_body: body, + }); + })?; + Ok(res.into()) } fn moved_out_of_ref(db: &dyn HirDatabase, body: &MirBody) -> Vec { @@ -93,7 +88,7 @@ fn moved_out_of_ref(db: &dyn HirDatabase, body: &MirBody) -> Vec Operand::Copy(p) | Operand::Move(p) => { let mut ty: Ty = body.locals[p.local].ty.clone(); let mut is_dereference_of_ref = false; - for proj in &*p.projection { + for proj in p.projection.lookup(&body.projection_store) { if *proj == ProjectionElem::Deref && ty.as_reference().is_some() { is_dereference_of_ref = true; } @@ -125,6 +120,7 @@ fn moved_out_of_ref(db: &dyn HirDatabase, body: &MirBody) -> Vec Operand::Constant(_) | Operand::Static(_) => (), }; for (_, block) in body.basic_blocks.iter() { + db.unwind_if_cancelled(); for statement in &block.statements { match &statement.kind { StatementKind::Assign(_, r) => match r { @@ -183,6 +179,7 @@ fn moved_out_of_ref(db: &dyn HirDatabase, body: &MirBody) -> Vec None => (), } } + result.shrink_to_fit(); result } @@ -199,7 +196,7 @@ enum ProjectionCase { fn place_case(db: &dyn HirDatabase, body: &MirBody, lvalue: &Place) -> ProjectionCase { let mut is_part_of = false; let mut ty = body.locals[lvalue.local].ty.clone(); - for proj in lvalue.projection.iter() { + for proj in lvalue.projection.lookup(&body.projection_store).iter() { match proj { ProjectionElem::Deref if ty.as_adt().is_none() => return ProjectionCase::Indirect, // It's indirect in case of reference and raw ProjectionElem::Deref // It's direct in case of `Box` @@ -258,7 +255,7 @@ fn ever_initialized_map( for statement in &block.statements { match &statement.kind { StatementKind::Assign(p, _) => { - if p.projection.len() == 0 && p.local == l { + if p.projection.lookup(&body.projection_store).len() == 0 && p.local == l { is_ever_initialized = true; } } @@ -277,21 +274,37 @@ fn ever_initialized_map( ); return; }; - let targets = match &terminator.kind { - TerminatorKind::Goto { target } => vec![*target], - TerminatorKind::SwitchInt { targets, .. } => targets.all_targets().to_vec(), + let mut process = |target, is_ever_initialized| { + if !result[target].contains_idx(l) || !result[target][l] && is_ever_initialized { + result[target].insert(l, is_ever_initialized); + dfs(db, body, target, l, result); + } + }; + match &terminator.kind { + TerminatorKind::Goto { target } => process(*target, is_ever_initialized), + TerminatorKind::SwitchInt { targets, .. } => { + targets.all_targets().iter().for_each(|&it| process(it, is_ever_initialized)); + } TerminatorKind::UnwindResume | TerminatorKind::Abort | TerminatorKind::Return - | TerminatorKind::Unreachable => vec![], + | TerminatorKind::Unreachable => (), TerminatorKind::Call { target, cleanup, destination, .. } => { - if destination.projection.len() == 0 && destination.local == l { + if destination.projection.lookup(&body.projection_store).len() == 0 + && destination.local == l + { is_ever_initialized = true; } - target.into_iter().chain(cleanup.into_iter()).copied().collect() + target + .into_iter() + .chain(cleanup.into_iter()) + .for_each(|&it| process(it, is_ever_initialized)); } TerminatorKind::Drop { target, unwind, place: _ } => { - Some(target).into_iter().chain(unwind.into_iter()).copied().collect() + iter::once(target) + .into_iter() + .chain(unwind.into_iter()) + .for_each(|&it| process(it, is_ever_initialized)); } TerminatorKind::DropAndReplace { .. } | TerminatorKind::Assert { .. } @@ -300,13 +313,7 @@ fn ever_initialized_map( | TerminatorKind::FalseEdge { .. } | TerminatorKind::FalseUnwind { .. } => { never!("We don't emit these MIR terminators yet"); - vec![] - } - }; - for target in targets { - if !result[target].contains_idx(l) || !result[target][l] && is_ever_initialized { - result[target].insert(l, is_ever_initialized); - dfs(db, body, target, l, result); + () } } } @@ -315,6 +322,7 @@ fn ever_initialized_map( dfs(db, body, body.start_block, l, &mut result); } for l in body.locals.iter().map(|it| it.0) { + db.unwind_if_cancelled(); if !result[body.start_block].contains_idx(l) { result[body.start_block].insert(l, false); dfs(db, body, body.start_block, l, &mut result); @@ -384,7 +392,7 @@ fn mutability_of_locals( | TerminatorKind::Assert { .. } | TerminatorKind::Yield { .. } => (), TerminatorKind::Call { destination, .. } => { - if destination.projection.len() == 0 { + if destination.projection.lookup(&body.projection_store).len() == 0 { if ever_init_map.get(destination.local).copied().unwrap_or_default() { push_mut_span(destination.local, MirSpan::Unknown); } else { diff --git a/crates/hir-ty/src/mir/eval.rs b/crates/hir-ty/src/mir/eval.rs index 3944feb128cc1..4364e0d323bbc 100644 --- a/crates/hir-ty/src/mir/eval.rs +++ b/crates/hir-ty/src/mir/eval.rs @@ -46,8 +46,8 @@ use crate::{ use super::{ return_slot, AggregateKind, BasicBlockId, BinOp, CastKind, LocalId, MirBody, MirLowerError, - MirSpan, Operand, Place, PlaceElem, ProjectionElem, Rvalue, StatementKind, TerminatorKind, - UnOp, + MirSpan, Operand, Place, PlaceElem, ProjectionElem, ProjectionStore, Rvalue, StatementKind, + TerminatorKind, UnOp, }; mod shim; @@ -215,9 +215,7 @@ impl Interval { } fn write_from_interval(&self, memory: &mut Evaluator<'_>, interval: Interval) -> Result<()> { - // FIXME: this could be more efficient - let bytes = &interval.get(memory)?.to_vec(); - memory.write_memory(self.addr, bytes) + memory.copy_from_interval(self.addr, interval) } fn slice(self, range: Range) -> Interval { @@ -341,7 +339,7 @@ pub enum MirEvalError { InvalidVTableId(usize), CoerceUnsizedError(Ty), LangItemNotFound(LangItem), - BrokenLayout(Layout), + BrokenLayout(Box), } impl MirEvalError { @@ -410,7 +408,7 @@ impl MirEvalError { err.pretty_print(f, db, span_formatter)?; } MirEvalError::ConstEvalError(name, err) => { - MirLowerError::ConstEvalError(name.clone(), err.clone()).pretty_print( + MirLowerError::ConstEvalError((**name).into(), err.clone()).pretty_print( f, db, span_formatter, @@ -485,17 +483,18 @@ struct DropFlags { } impl DropFlags { - fn add_place(&mut self, p: Place) { - if p.iterate_over_parents().any(|it| self.need_drop.contains(&it)) { + fn add_place(&mut self, p: Place, store: &ProjectionStore) { + if p.iterate_over_parents(store).any(|it| self.need_drop.contains(&it)) { return; } - self.need_drop.retain(|it| !p.is_parent(it)); + self.need_drop.retain(|it| !p.is_parent(it, store)); self.need_drop.insert(p); } - fn remove_place(&mut self, p: &Place) -> bool { + fn remove_place(&mut self, p: &Place, store: &ProjectionStore) -> bool { // FIXME: replace parents with parts - if let Some(parent) = p.iterate_over_parents().find(|it| self.need_drop.contains(&it)) { + if let Some(parent) = p.iterate_over_parents(store).find(|it| self.need_drop.contains(&it)) + { self.need_drop.remove(&parent); return true; } @@ -656,7 +655,7 @@ impl Evaluator<'_> { let mut addr = locals.ptr[p.local].addr; let mut ty: Ty = locals.body.locals[p.local].ty.clone(); let mut metadata: Option = None; // locals are always sized - for proj in &*p.projection { + for proj in p.projection.lookup(&locals.body.projection_store) { let prev_ty = ty.clone(); ty = self.projected_ty(ty, proj.clone()); match proj { @@ -837,7 +836,9 @@ impl Evaluator<'_> { let addr = self.place_addr(l, &locals)?; let result = self.eval_rvalue(r, &mut locals)?.to_vec(&self)?; self.write_memory(addr, &result)?; - locals.drop_flags.add_place(l.clone()); + locals + .drop_flags + .add_place(l.clone(), &locals.body.projection_store); } StatementKind::Deinit(_) => not_supported!("de-init statement"), StatementKind::StorageLive(_) @@ -889,7 +890,9 @@ impl Evaluator<'_> { )?, it => not_supported!("unknown function type {it:?}"), }; - locals.drop_flags.add_place(destination.clone()); + locals + .drop_flags + .add_place(destination.clone(), &locals.body.projection_store); if let Some(stack_frame) = stack_frame { self.code_stack.push(my_stack_frame); current_block_idx = stack_frame.locals.body.start_block; @@ -970,7 +973,7 @@ impl Evaluator<'_> { ) -> Result<()> { let mut remain_args = body.param_locals.len(); for ((l, interval), value) in locals.ptr.iter().skip(1).zip(args) { - locals.drop_flags.add_place(l.into()); + locals.drop_flags.add_place(l.into(), &locals.body.projection_store); match value { IntervalOrOwned::Owned(value) => interval.write_from_bytes(self, &value)?, IntervalOrOwned::Borrowed(value) => interval.write_from_interval(self, value)?, @@ -1629,7 +1632,7 @@ impl Evaluator<'_> { if let Some((offset, size, value)) = tag { match result.get_mut(offset..offset + size) { Some(it) => it.copy_from_slice(&value.to_le_bytes()[0..size]), - None => return Err(MirEvalError::BrokenLayout(variant_layout.clone())), + None => return Err(MirEvalError::BrokenLayout(Box::new(variant_layout.clone()))), } } for (i, op) in values.enumerate() { @@ -1637,7 +1640,7 @@ impl Evaluator<'_> { let op = op.get(&self)?; match result.get_mut(offset..offset + op.len()) { Some(it) => it.copy_from_slice(op), - None => return Err(MirEvalError::BrokenLayout(variant_layout.clone())), + None => return Err(MirEvalError::BrokenLayout(Box::new(variant_layout.clone()))), } } Ok(result) @@ -1646,7 +1649,7 @@ impl Evaluator<'_> { fn eval_operand(&mut self, it: &Operand, locals: &mut Locals) -> Result { Ok(match it { Operand::Copy(p) | Operand::Move(p) => { - locals.drop_flags.remove_place(p); + locals.drop_flags.remove_place(p, &locals.body.projection_store); self.eval_place(p, locals)? } Operand::Static(st) => { @@ -1760,6 +1763,48 @@ impl Evaluator<'_> { Ok(()) } + fn copy_from_interval(&mut self, addr: Address, r: Interval) -> Result<()> { + if r.size == 0 { + return Ok(()); + } + + let oob = || MirEvalError::UndefinedBehavior("out of bounds memory write".to_string()); + + match (addr, r.addr) { + (Stack(dst), Stack(src)) => { + if self.stack.len() < src + r.size || self.stack.len() < dst + r.size { + return Err(oob()); + } + self.stack.copy_within(src..src + r.size, dst) + } + (Heap(dst), Heap(src)) => { + if self.stack.len() < src + r.size || self.stack.len() < dst + r.size { + return Err(oob()); + } + self.heap.copy_within(src..src + r.size, dst) + } + (Stack(dst), Heap(src)) => { + self.stack + .get_mut(dst..dst + r.size) + .ok_or_else(oob)? + .copy_from_slice(self.heap.get(src..src + r.size).ok_or_else(oob)?); + } + (Heap(dst), Stack(src)) => { + self.heap + .get_mut(dst..dst + r.size) + .ok_or_else(oob)? + .copy_from_slice(self.stack.get(src..src + r.size).ok_or_else(oob)?); + } + _ => { + return Err(MirEvalError::UndefinedBehavior(format!( + "invalid memory write at address {addr:?}" + ))) + } + } + + Ok(()) + } + fn size_align_of(&self, ty: &Ty, locals: &Locals) -> Result> { if let Some(layout) = self.layout_cache.borrow().get(ty) { return Ok(layout @@ -2468,7 +2513,7 @@ impl Evaluator<'_> { fn drop_place(&mut self, place: &Place, locals: &mut Locals, span: MirSpan) -> Result<()> { let (addr, ty, metadata) = self.place_addr_and_ty_and_metadata(place, locals)?; - if !locals.drop_flags.remove_place(place) { + if !locals.drop_flags.remove_place(place, &locals.body.projection_store) { return Ok(()); } let metadata = match metadata { diff --git a/crates/hir-ty/src/mir/eval/shim.rs b/crates/hir-ty/src/mir/eval/shim.rs index 52943e97ac072..803ef631f1e03 100644 --- a/crates/hir-ty/src/mir/eval/shim.rs +++ b/crates/hir-ty/src/mir/eval/shim.rs @@ -4,7 +4,10 @@ use std::cmp; use chalk_ir::TyKind; -use hir_def::resolver::HasResolver; +use hir_def::{ + builtin_type::{BuiltinInt, BuiltinUint}, + resolver::HasResolver, +}; use hir_expand::mod_path::ModPath; use super::*; @@ -300,21 +303,36 @@ impl Evaluator<'_> { BeginPanic => Err(MirEvalError::Panic("".to_string())), PanicFmt => { let message = (|| { - let resolver = self.db.crate_def_map(self.crate_id).crate_root().resolver(self.db.upcast()); + let resolver = self + .db + .crate_def_map(self.crate_id) + .crate_root() + .resolver(self.db.upcast()); let Some(format_fn) = resolver.resolve_path_in_value_ns_fully( self.db.upcast(), - &hir_def::path::Path::from_known_path_with_no_generic(ModPath::from_segments( - hir_expand::mod_path::PathKind::Abs, - [name![std], name![fmt], name![format]].into_iter(), - )), + &hir_def::path::Path::from_known_path_with_no_generic( + ModPath::from_segments( + hir_expand::mod_path::PathKind::Abs, + [name![std], name![fmt], name![format]].into_iter(), + ), + ), ) else { not_supported!("std::fmt::format not found"); }; - let hir_def::resolver::ValueNs::FunctionId(format_fn) = format_fn else { not_supported!("std::fmt::format is not a function") }; - let message_string = self.interpret_mir(self.db.mir_body(format_fn.into()).map_err(|e| MirEvalError::MirLowerError(format_fn, e))?, args.map(|x| IntervalOrOwned::Owned(x.clone())))?; - let addr = Address::from_bytes(&message_string[self.ptr_size()..2 * self.ptr_size()])?; + let hir_def::resolver::ValueNs::FunctionId(format_fn) = format_fn else { + not_supported!("std::fmt::format is not a function") + }; + let message_string = self.interpret_mir( + self.db + .mir_body(format_fn.into()) + .map_err(|e| MirEvalError::MirLowerError(format_fn, e))?, + args.map(|x| IntervalOrOwned::Owned(x.clone())), + )?; + let addr = + Address::from_bytes(&message_string[self.ptr_size()..2 * self.ptr_size()])?; let size = from_bytes!(usize, message_string[2 * self.ptr_size()..]); - Ok(std::string::String::from_utf8_lossy(self.read_memory(addr, size)?).into_owned()) + Ok(std::string::String::from_utf8_lossy(self.read_memory(addr, size)?) + .into_owned()) })() .unwrap_or_else(|e| format!("Failed to render panic format args: {e:?}")); Err(MirEvalError::Panic(message)) @@ -483,9 +501,7 @@ impl Evaluator<'_> { } "syscall" => { let Some((id, rest)) = args.split_first() else { - return Err(MirEvalError::TypeError( - "syscall arg1 is not provided", - )); + return Err(MirEvalError::TypeError("syscall arg1 is not provided")); }; let id = from_bytes!(i64, id.get(self)?); self.exec_syscall(id, rest, destination, locals, span) @@ -710,7 +726,8 @@ impl Evaluator<'_> { } match name { "size_of" => { - let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + let Some(ty) = + generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) else { return Err(MirEvalError::TypeError("size_of generic arg is not provided")); }; @@ -718,14 +735,17 @@ impl Evaluator<'_> { destination.write_from_bytes(self, &size.to_le_bytes()[0..destination.size]) } "min_align_of" | "pref_align_of" => { - let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) else { + let Some(ty) = + generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + else { return Err(MirEvalError::TypeError("align_of generic arg is not provided")); }; let align = self.layout(ty)?.align.abi.bytes(); destination.write_from_bytes(self, &align.to_le_bytes()[0..destination.size]) } "size_of_val" => { - let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + let Some(ty) = + generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) else { return Err(MirEvalError::TypeError("size_of_val generic arg is not provided")); }; @@ -741,8 +761,12 @@ impl Evaluator<'_> { } } "min_align_of_val" => { - let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) else { - return Err(MirEvalError::TypeError("min_align_of_val generic arg is not provided")); + let Some(ty) = + generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + else { + return Err(MirEvalError::TypeError( + "min_align_of_val generic arg is not provided", + )); }; let [arg] = args else { return Err(MirEvalError::TypeError("min_align_of_val args are not provided")); @@ -756,7 +780,8 @@ impl Evaluator<'_> { } } "type_name" => { - let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + let Some(ty) = + generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) else { return Err(MirEvalError::TypeError("type_name generic arg is not provided")); }; @@ -779,7 +804,8 @@ impl Evaluator<'_> { .write_from_bytes(self, &len.to_le_bytes()) } "needs_drop" => { - let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + let Some(ty) = + generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) else { return Err(MirEvalError::TypeError("size_of generic arg is not provided")); }; @@ -831,9 +857,12 @@ impl Evaluator<'_> { let lhs = i128::from_le_bytes(pad16(lhs.get(self)?, false)); let rhs = i128::from_le_bytes(pad16(rhs.get(self)?, false)); let ans = lhs.wrapping_sub(rhs); - let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + let Some(ty) = + generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) else { - return Err(MirEvalError::TypeError("ptr_offset_from generic arg is not provided")); + return Err(MirEvalError::TypeError( + "ptr_offset_from generic arg is not provided", + )); }; let size = self.size_of_sized(ty, locals, "ptr_offset_from arg")? as i128; let ans = ans / size; @@ -940,7 +969,8 @@ impl Evaluator<'_> { "copy_nonoverlapping args are not provided", )); }; - let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + let Some(ty) = + generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) else { return Err(MirEvalError::TypeError( "copy_nonoverlapping generic arg is not provided", @@ -959,9 +989,45 @@ impl Evaluator<'_> { let [ptr, offset] = args else { return Err(MirEvalError::TypeError("offset args are not provided")); }; - let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) - else { - return Err(MirEvalError::TypeError("offset generic arg is not provided")); + let ty = if name == "offset" { + let Some(ty0) = + generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + else { + return Err(MirEvalError::TypeError("offset generic arg is not provided")); + }; + let Some(ty1) = + generic_args.as_slice(Interner).get(1).and_then(|it| it.ty(Interner)) + else { + return Err(MirEvalError::TypeError("offset generic arg is not provided")); + }; + if !matches!( + ty1.as_builtin(), + Some( + BuiltinType::Int(BuiltinInt::Isize) + | BuiltinType::Uint(BuiltinUint::Usize) + ) + ) { + return Err(MirEvalError::TypeError( + "offset generic arg is not usize or isize", + )); + } + match ty0.as_raw_ptr() { + Some((ty, _)) => ty, + None => { + return Err(MirEvalError::TypeError( + "offset generic arg is not a raw pointer", + )); + } + } + } else { + let Some(ty) = + generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + else { + return Err(MirEvalError::TypeError( + "arith_offset generic arg is not provided", + )); + }; + ty }; let ptr = u128::from_le_bytes(pad16(ptr.get(self)?, false)); let offset = u128::from_le_bytes(pad16(offset.get(self)?, false)); @@ -1079,7 +1145,8 @@ impl Evaluator<'_> { let [arg] = args else { return Err(MirEvalError::TypeError("discriminant_value arg is not provided")); }; - let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + let Some(ty) = + generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) else { return Err(MirEvalError::TypeError( "discriminant_value generic arg is not provided", @@ -1133,17 +1200,32 @@ impl Evaluator<'_> { let addr = Address::from_bytes(arg.interval.get(self)?)?; destination.write_from_interval(self, Interval { addr, size: destination.size }) } + "write_via_move" => { + let [ptr, val] = args else { + return Err(MirEvalError::TypeError("write_via_move args are not provided")); + }; + let dst = Address::from_bytes(ptr.get(self)?)?; + let Some(ty) = + generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + else { + return Err(MirEvalError::TypeError( + "write_via_copy generic arg is not provided", + )); + }; + let size = self.size_of_sized(ty, locals, "write_via_move ptr type")?; + Interval { addr: dst, size }.write_from_interval(self, val.interval)?; + Ok(()) + } "write_bytes" => { let [dst, val, count] = args else { return Err(MirEvalError::TypeError("write_bytes args are not provided")); }; let count = from_bytes!(usize, count.get(self)?); let val = from_bytes!(u8, val.get(self)?); - let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + let Some(ty) = + generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) else { - return Err(MirEvalError::TypeError( - "write_bytes generic arg is not provided", - )); + return Err(MirEvalError::TypeError("write_bytes generic arg is not provided")); }; let dst = Address::from_bytes(dst.get(self)?)?; let size = self.size_of_sized(ty, locals, "copy_nonoverlapping ptr type")?; diff --git a/crates/hir-ty/src/mir/eval/shim/simd.rs b/crates/hir-ty/src/mir/eval/shim/simd.rs index ec74631048797..5190066242660 100644 --- a/crates/hir-ty/src/mir/eval/shim/simd.rs +++ b/crates/hir-ty/src/mir/eval/shim/simd.rs @@ -45,7 +45,9 @@ impl Evaluator<'_> { }; match try_const_usize(self.db, len) { Some(len) => { - let Some(ty) = subst.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) else { + let Some(ty) = + subst.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + else { return Err(MirEvalError::TypeError("simd type with no ty param")); }; Ok((len as usize, ty.clone())) diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs index 51cf882d05393..dd2dba717f95e 100644 --- a/crates/hir-ty/src/mir/lower.rs +++ b/crates/hir-ty/src/mir/lower.rs @@ -71,7 +71,7 @@ struct MirLowerCtx<'a> { #[derive(Debug, Clone, PartialEq, Eq)] pub enum MirLowerError { - ConstEvalError(String, Box), + ConstEvalError(Box, Box), LayoutError(LayoutError), IncompleteExpr, IncompletePattern, @@ -84,7 +84,7 @@ pub enum MirLowerError { UnsizedTemporary(Ty), MissingFunctionDefinition(DefWithBodyId, ExprId), TypeMismatch(TypeMismatch), - /// This should be never happen. Type mismatch should catch everything. + /// This should never happen. Type mismatch should catch everything. TypeError(&'static str), NotSupported(String), ContinueWithoutLoop, @@ -244,6 +244,7 @@ impl<'ctx> MirLowerCtx<'ctx> { let locals = Arena::new(); let binding_locals: ArenaMap = ArenaMap::new(); let mir = MirBody { + projection_store: ProjectionStore::default(), basic_blocks, locals, start_block, @@ -370,6 +371,12 @@ impl<'ctx> MirLowerCtx<'ctx> { mut current: BasicBlockId, ) -> Result> { match &self.body.exprs[expr_id] { + Expr::OffsetOf(_) => { + not_supported!("builtin#offset_of") + } + Expr::InlineAsm(_) => { + not_supported!("builtin#asm") + } Expr::Missing => { if let DefWithBodyId::FunctionId(f) = self.owner { let assoc = f.lookup(self.db.upcast()); @@ -803,36 +810,34 @@ impl<'ctx> MirLowerCtx<'ctx> { current = c; operands[u32::from(field_id.into_raw()) as usize] = Some(op); } - self.push_assignment( - current, - place, - Rvalue::Aggregate( - AggregateKind::Adt(variant_id, subst), - match spread_place { - Some(sp) => operands - .into_iter() - .enumerate() - .map(|(i, it)| match it { - Some(it) => it, - None => { - let p = - sp.project(ProjectionElem::Field(FieldId { - parent: variant_id, - local_id: LocalFieldId::from_raw( - RawIdx::from(i as u32), - ), - })); - Operand::Copy(p) - } - }) - .collect(), - None => operands.into_iter().collect::>().ok_or( - MirLowerError::TypeError("missing field in record literal"), - )?, - }, - ), - expr_id.into(), + let rvalue = Rvalue::Aggregate( + AggregateKind::Adt(variant_id, subst), + match spread_place { + Some(sp) => operands + .into_iter() + .enumerate() + .map(|(i, it)| match it { + Some(it) => it, + None => { + let p = sp.project( + ProjectionElem::Field(FieldId { + parent: variant_id, + local_id: LocalFieldId::from_raw(RawIdx::from( + i as u32, + )), + }), + &mut self.result.projection_store, + ); + Operand::Copy(p) + } + }) + .collect(), + None => operands.into_iter().collect::>().ok_or( + MirLowerError::TypeError("missing field in record literal"), + )?, + }, ); + self.push_assignment(current, place, rvalue, expr_id.into()); Ok(Some(current)) } VariantId::UnionId(union_id) => { @@ -841,10 +846,10 @@ impl<'ctx> MirLowerCtx<'ctx> { }; let local_id = variant_data.field(name).ok_or(MirLowerError::UnresolvedField)?; - let place = place.project(PlaceElem::Field(FieldId { - parent: union_id.into(), - local_id, - })); + let place = place.project( + PlaceElem::Field(FieldId { parent: union_id.into(), local_id }), + &mut self.result.projection_store, + ); self.lower_expr_to_place(*expr, place, current) } } @@ -898,7 +903,7 @@ impl<'ctx> MirLowerCtx<'ctx> { else { return Ok(None); }; - let p = place.project(ProjectionElem::Deref); + let p = place.project(ProjectionElem::Deref, &mut self.result.projection_store); self.push_assignment(current, p, operand.into(), expr_id.into()); Ok(Some(current)) } @@ -1120,27 +1125,31 @@ impl<'ctx> MirLowerCtx<'ctx> { for capture in captures.iter() { let p = Place { local: self.binding_local(capture.place.local)?, - projection: capture - .place - .projections - .clone() - .into_iter() - .map(|it| match it { - ProjectionElem::Deref => ProjectionElem::Deref, - ProjectionElem::Field(it) => ProjectionElem::Field(it), - ProjectionElem::TupleOrClosureField(it) => { - ProjectionElem::TupleOrClosureField(it) - } - ProjectionElem::ConstantIndex { offset, from_end } => { - ProjectionElem::ConstantIndex { offset, from_end } - } - ProjectionElem::Subslice { from, to } => { - ProjectionElem::Subslice { from, to } - } - ProjectionElem::OpaqueCast(it) => ProjectionElem::OpaqueCast(it), - ProjectionElem::Index(it) => match it {}, - }) - .collect(), + projection: self.result.projection_store.intern( + capture + .place + .projections + .clone() + .into_iter() + .map(|it| match it { + ProjectionElem::Deref => ProjectionElem::Deref, + ProjectionElem::Field(it) => ProjectionElem::Field(it), + ProjectionElem::TupleOrClosureField(it) => { + ProjectionElem::TupleOrClosureField(it) + } + ProjectionElem::ConstantIndex { offset, from_end } => { + ProjectionElem::ConstantIndex { offset, from_end } + } + ProjectionElem::Subslice { from, to } => { + ProjectionElem::Subslice { from, to } + } + ProjectionElem::OpaqueCast(it) => { + ProjectionElem::OpaqueCast(it) + } + ProjectionElem::Index(it) => match it {}, + }) + .collect(), + ), }; match &capture.kind { CaptureKind::ByRef(bk) => { @@ -1201,7 +1210,8 @@ impl<'ctx> MirLowerCtx<'ctx> { let Some(values) = elements .iter() .map(|it| { - let Some((o, c)) = self.lower_expr_to_some_operand(*it, current)? else { + let Some((o, c)) = self.lower_expr_to_some_operand(*it, current)? + else { return Ok(None); }; current = c; @@ -1254,12 +1264,12 @@ impl<'ctx> MirLowerCtx<'ctx> { match &self.body.exprs[lhs] { Expr::Tuple { exprs, is_assignee_expr: _ } => { for (i, expr) in exprs.iter().enumerate() { - let Some(c) = self.lower_destructing_assignment( - current, - *expr, - rhs.project(ProjectionElem::TupleOrClosureField(i)), - span, - )? else { + let rhs = rhs.project( + ProjectionElem::TupleOrClosureField(i), + &mut self.result.projection_store, + ); + let Some(c) = self.lower_destructing_assignment(current, *expr, rhs, span)? + else { return Ok(None); }; current = c; @@ -1268,8 +1278,7 @@ impl<'ctx> MirLowerCtx<'ctx> { } Expr::Underscore => Ok(Some(current)), _ => { - let Some((lhs_place, current)) = - self.lower_expr_as_place(current, lhs, false)? + let Some((lhs_place, current)) = self.lower_expr_as_place(current, lhs, false)? else { return Ok(None); }; @@ -1286,9 +1295,7 @@ impl<'ctx> MirLowerCtx<'ctx> { rhs: ExprId, span: MirSpan, ) -> Result> { - let Some((rhs_op, current)) = - self.lower_expr_to_some_operand(rhs, current)? - else { + let Some((rhs_op, current)) = self.lower_expr_to_some_operand(rhs, current)? else { return Ok(None); }; if matches!(&self.body.exprs[lhs], Expr::Underscore) { @@ -1303,9 +1310,7 @@ impl<'ctx> MirLowerCtx<'ctx> { self.push_assignment(current, temp.clone(), rhs_op.into(), span); return self.lower_destructing_assignment(current, lhs, temp, span); } - let Some((lhs_place, current)) = - self.lower_expr_as_place(current, lhs, false)? - else { + let Some((lhs_place, current)) = self.lower_expr_as_place(current, lhs, false)? else { return Ok(None); }; self.push_assignment(current, lhs_place, rhs_op.into(), span); @@ -1320,17 +1325,21 @@ impl<'ctx> MirLowerCtx<'ctx> { placeholder_subst } - fn push_field_projection(&self, place: &mut Place, expr_id: ExprId) -> Result<()> { + fn push_field_projection(&mut self, place: &mut Place, expr_id: ExprId) -> Result<()> { if let Expr::Field { expr, name } = &self.body[expr_id] { if let TyKind::Tuple(..) = self.expr_ty_after_adjustments(*expr).kind(Interner) { let index = name .as_tuple_index() .ok_or(MirLowerError::TypeError("named field on tuple"))?; - *place = place.project(ProjectionElem::TupleOrClosureField(index)) + *place = place.project( + ProjectionElem::TupleOrClosureField(index), + &mut self.result.projection_store, + ) } else { let field = self.infer.field_resolution(expr_id).ok_or(MirLowerError::UnresolvedField)?; - *place = place.project(ProjectionElem::Field(field)); + *place = + place.project(ProjectionElem::Field(field), &mut self.result.projection_store); } } else { not_supported!("") @@ -1447,7 +1456,7 @@ impl<'ctx> MirLowerCtx<'ctx> { let name = const_id.name(self.db.upcast()); self.db .const_eval(const_id.into(), subst, None) - .map_err(|e| MirLowerError::ConstEvalError(name, Box::new(e)))? + .map_err(|e| MirLowerError::ConstEvalError(name.into(), Box::new(e)))? }; Ok(Operand::Constant(c)) } @@ -1844,7 +1853,7 @@ impl<'ctx> MirLowerCtx<'ctx> { data.name.display(self.db.upcast()), data.variants[variant.local_id].name.display(self.db.upcast()) ); - Err(MirLowerError::ConstEvalError(name, Box::new(e))) + Err(MirLowerError::ConstEvalError(name.into(), Box::new(e))) } } } @@ -1992,13 +2001,14 @@ pub fn mir_body_for_closure_query( FnTrait::FnOnce => vec![], FnTrait::FnMut | FnTrait::Fn => vec![ProjectionElem::Deref], }; - ctx.result.walk_places(|p| { + ctx.result.walk_places(|p, store| { if let Some(it) = upvar_map.get(&p.local) { let r = it.iter().find(|it| { - if p.projection.len() < it.0.place.projections.len() { + if p.projection.lookup(&store).len() < it.0.place.projections.len() { return false; } - for (it, y) in p.projection.iter().zip(it.0.place.projections.iter()) { + for (it, y) in p.projection.lookup(&store).iter().zip(it.0.place.projections.iter()) + { match (it, y) { (ProjectionElem::Deref, ProjectionElem::Deref) => (), (ProjectionElem::Field(it), ProjectionElem::Field(y)) if it == y => (), @@ -2016,13 +2026,18 @@ pub fn mir_body_for_closure_query( p.local = closure_local; let mut next_projs = closure_projection.clone(); next_projs.push(PlaceElem::TupleOrClosureField(it.1)); - let prev_projs = mem::take(&mut p.projection); + let prev_projs = p.projection; if it.0.kind != CaptureKind::ByValue { next_projs.push(ProjectionElem::Deref); } - next_projs - .extend(prev_projs.iter().cloned().skip(it.0.place.projections.len())); - p.projection = next_projs.into(); + next_projs.extend( + prev_projs + .lookup(&store) + .iter() + .cloned() + .skip(it.0.place.projections.len()), + ); + p.projection = store.intern(next_projs.into()); } None => err = Some(p.clone()), } diff --git a/crates/hir-ty/src/mir/lower/as_place.rs b/crates/hir-ty/src/mir/lower/as_place.rs index 213f151ab6760..8c078eb4ad757 100644 --- a/crates/hir-ty/src/mir/lower/as_place.rs +++ b/crates/hir-ty/src/mir/lower/as_place.rs @@ -70,7 +70,7 @@ impl MirLowerCtx<'_> { else { return Ok(None); }; - it.0 = it.0.project(ProjectionElem::Deref); + it.0 = it.0.project(ProjectionElem::Deref, &mut self.result.projection_store); Ok(Some(it)) } Adjust::Deref(Some(od)) => { @@ -152,7 +152,10 @@ impl MirLowerCtx<'_> { Operand::Static(s).into(), expr_id.into(), ); - Ok(Some((temp.project(ProjectionElem::Deref), current))) + Ok(Some(( + temp.project(ProjectionElem::Deref, &mut self.result.projection_store), + current, + ))) } _ => try_rvalue(self), } @@ -203,7 +206,7 @@ impl MirLowerCtx<'_> { else { return Ok(None); }; - r = r.project(ProjectionElem::Deref); + r = r.project(ProjectionElem::Deref, &mut self.result.projection_store); Ok(Some((r, current))) } _ => try_rvalue(self), @@ -267,7 +270,8 @@ impl MirLowerCtx<'_> { else { return Ok(None); }; - p_base = p_base.project(ProjectionElem::Index(l_index)); + p_base = p_base + .project(ProjectionElem::Index(l_index), &mut self.result.projection_store); Ok(Some((p_base, current))) } _ => try_rvalue(self), @@ -308,7 +312,7 @@ impl MirLowerCtx<'_> { else { return Ok(None); }; - result = result.project(ProjectionElem::Deref); + result = result.project(ProjectionElem::Deref, &mut self.result.projection_store); Ok(Some((result, current))) } @@ -363,7 +367,7 @@ impl MirLowerCtx<'_> { else { return Ok(None); }; - result = result.project(ProjectionElem::Deref); + result = result.project(ProjectionElem::Deref, &mut self.result.projection_store); Ok(Some((result, current))) } } diff --git a/crates/hir-ty/src/mir/lower/pattern_matching.rs b/crates/hir-ty/src/mir/lower/pattern_matching.rs index 1cdfd9197422b..270f75ad9674c 100644 --- a/crates/hir-ty/src/mir/lower/pattern_matching.rs +++ b/crates/hir-ty/src/mir/lower/pattern_matching.rs @@ -81,13 +81,16 @@ impl MirLowerCtx<'_> { mode: MatchingMode, ) -> Result<(BasicBlockId, Option)> { let cnt = self.infer.pat_adjustments.get(&pattern).map(|x| x.len()).unwrap_or_default(); - cond_place.projection = cond_place - .projection - .iter() - .cloned() - .chain((0..cnt).map(|_| ProjectionElem::Deref)) - .collect::>() - .into(); + cond_place.projection = self.result.projection_store.intern( + cond_place + .projection + .lookup(&self.result.projection_store) + .iter() + .cloned() + .chain((0..cnt).map(|_| ProjectionElem::Deref)) + .collect::>() + .into(), + ); Ok(match &self.body.pats[pattern] { Pat::Missing => return Err(MirLowerError::IncompletePattern), Pat::Wild => (current, current_else), @@ -262,20 +265,23 @@ impl MirLowerCtx<'_> { } } for (i, &pat) in prefix.iter().enumerate() { - let next_place = (&mut cond_place).project(ProjectionElem::ConstantIndex { - offset: i as u64, - from_end: false, - }); + let next_place = (&mut cond_place).project( + ProjectionElem::ConstantIndex { offset: i as u64, from_end: false }, + &mut self.result.projection_store, + ); (current, current_else) = self.pattern_match_inner(current, current_else, next_place, pat, mode)?; } if let Some(slice) = slice { if mode == MatchingMode::Bind { if let Pat::Bind { id, subpat: _ } = self.body[*slice] { - let next_place = (&mut cond_place).project(ProjectionElem::Subslice { - from: prefix.len() as u64, - to: suffix.len() as u64, - }); + let next_place = (&mut cond_place).project( + ProjectionElem::Subslice { + from: prefix.len() as u64, + to: suffix.len() as u64, + }, + &mut self.result.projection_store, + ); (current, current_else) = self.pattern_match_binding( id, next_place, @@ -287,10 +293,10 @@ impl MirLowerCtx<'_> { } } for (i, &pat) in suffix.iter().enumerate() { - let next_place = (&mut cond_place).project(ProjectionElem::ConstantIndex { - offset: i as u64, - from_end: true, - }); + let next_place = (&mut cond_place).project( + ProjectionElem::ConstantIndex { offset: i as u64, from_end: true }, + &mut self.result.projection_store, + ); (current, current_else) = self.pattern_match_inner(current, current_else, next_place, pat, mode)?; } @@ -412,13 +418,11 @@ impl MirLowerCtx<'_> { mode, )? } - Pat::Ref { pat, mutability: _ } => self.pattern_match_inner( - current, - current_else, - cond_place.project(ProjectionElem::Deref), - *pat, - mode, - )?, + Pat::Ref { pat, mutability: _ } => { + let cond_place = + cond_place.project(ProjectionElem::Deref, &mut self.result.projection_store); + self.pattern_match_inner(current, current_else, cond_place, *pat, mode)? + } Pat::Box { .. } => not_supported!("box pattern"), Pat::ConstBlock(_) => not_supported!("const block pattern"), }) @@ -594,7 +598,7 @@ impl MirLowerCtx<'_> { mode: MatchingMode, ) -> Result<(BasicBlockId, Option)> { for (proj, arg) in args { - let cond_place = cond_place.project(proj); + let cond_place = cond_place.project(proj, &mut self.result.projection_store); (current, current_else) = self.pattern_match_inner(current, current_else, cond_place, arg, mode)?; } diff --git a/crates/hir-ty/src/mir/pretty.rs b/crates/hir-ty/src/mir/pretty.rs index 781ffaecad56d..0108859ff32ee 100644 --- a/crates/hir-ty/src/mir/pretty.rs +++ b/crates/hir-ty/src/mir/pretty.rs @@ -329,7 +329,7 @@ impl<'a> MirPrettyCtx<'a> { } } } - f(self, p.local, &p.projection); + f(self, p.local, &p.projection.lookup(&self.body.projection_store)); } fn operand(&mut self, r: &Operand) { diff --git a/crates/hir-ty/src/tests/simple.rs b/crates/hir-ty/src/tests/simple.rs index 2ad7946c8ac19..8140c4107b8dd 100644 --- a/crates/hir-ty/src/tests/simple.rs +++ b/crates/hir-ty/src/tests/simple.rs @@ -2,55 +2,6 @@ use expect_test::expect; use super::{check, check_infer, check_no_mismatches, check_types}; -#[test] -fn infer_box() { - check_types( - r#" -//- /main.rs crate:main deps:std -fn test() { - let x = box 1; - let t = (x, box x, box &1, box [1]); - t; -} //^ (Box, Box>, Box<&i32>, Box<[i32; 1]>) - -//- /std.rs crate:std -#[prelude_import] use prelude::*; -mod prelude {} - -mod boxed { - #[lang = "owned_box"] - pub struct Box { - inner: *mut T, - } -} -"#, - ); -} - -#[test] -fn infer_box_with_allocator() { - check_types( - r#" -//- /main.rs crate:main deps:std -fn test() { - let x = box 1; - let t = (x, box x, box &1, box [1]); - t; -} //^ (Box, Box, {unknown}>, Box<&i32, {unknown}>, Box<[i32; 1], {unknown}>) - -//- /std.rs crate:std -#[prelude_import] use prelude::*; -mod boxed { - #[lang = "owned_box"] - pub struct Box { - inner: *mut T, - allocator: A, - } -} -"#, - ); -} - #[test] fn infer_adt_self() { check_types( @@ -2763,8 +2714,8 @@ impl [T] { } fn test() { - let vec = <[_]>::into_vec(box [1i32]); - let v: Vec> = <[_]> :: into_vec(box [box Astruct]); + let vec = <[_]>::into_vec(#[rustc_box] Box::new([1i32])); + let v: Vec> = <[_]> :: into_vec(#[rustc_box] Box::new([#[rustc_box] Box::new(Astruct)])); } trait B{} @@ -2774,20 +2725,20 @@ impl B for Astruct {} expect![[r#" 604..608 'self': Box<[T], A> 637..669 '{ ... }': Vec - 683..796 '{ ...t]); }': () + 683..853 '{ ...])); }': () 693..696 'vec': Vec 699..714 '<[_]>::into_vec': fn into_vec(Box<[i32], Global>) -> Vec - 699..726 '<[_]>:...1i32])': Vec - 715..725 'box [1i32]': Box<[i32; 1], Global> - 719..725 '[1i32]': [i32; 1] - 720..724 '1i32': i32 - 736..737 'v': Vec, Global> - 757..774 '<[_]> ...to_vec': fn into_vec, Global>(Box<[Box], Global>) -> Vec, Global> - 757..793 '<[_]> ...ruct])': Vec, Global> - 775..792 'box [b...truct]': Box<[Box; 1], Global> - 779..792 '[box Astruct]': [Box; 1] - 780..791 'box Astruct': Box - 784..791 'Astruct': Astruct + 699..745 '<[_]>:...i32]))': Vec + 715..744 '#[rust...1i32])': Box<[i32; 1], Global> + 737..743 '[1i32]': [i32; 1] + 738..742 '1i32': i32 + 755..756 'v': Vec, Global> + 776..793 '<[_]> ...to_vec': fn into_vec, Global>(Box<[Box], Global>) -> Vec, Global> + 776..850 '<[_]> ...ct)]))': Vec, Global> + 794..849 '#[rust...uct)])': Box<[Box; 1], Global> + 816..848 '[#[rus...ruct)]': [Box; 1] + 817..847 '#[rust...truct)': Box + 839..846 'Astruct': Astruct "#]], ) } @@ -3649,3 +3600,30 @@ fn main() { "#, ); } + +#[test] +fn offset_of() { + check_types( + r#" +fn main() { + builtin#offset_of((,), 0); + // ^^^^^^^^^^^^^^^^^^^^^^^^^ usize +} +"#, + ); +} + +#[test] +fn builtin_format_args() { + check( + r#" +//- minicore: fmt +fn main() { + let are = "are"; + let count = 10; + builtin#format_args("hello {count:02} {} friends, we {are:?} {0}{last}", "fancy", last = "!"); + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type: Arguments<'_> +} +"#, + ); +} diff --git a/crates/hir-ty/src/tests/traits.rs b/crates/hir-ty/src/tests/traits.rs index 542df8b3468fa..d36b885ec14a4 100644 --- a/crates/hir-ty/src/tests/traits.rs +++ b/crates/hir-ty/src/tests/traits.rs @@ -162,16 +162,16 @@ unsafe impl Allocator for Global {} #[lang = "owned_box"] #[fundamental] -pub struct Box; +pub struct Box(T); impl, U: ?Sized, A: Allocator> CoerceUnsized> for Box {} fn send() -> Box + Send + 'static>{ - box async move {} + Box(async move {}) } fn not_send() -> Box + 'static> { - box async move {} + Box(async move {}) } "#, ); @@ -3057,7 +3057,7 @@ impl core::ops::Deref for Box { fn foo() { let s = None; - let f: Box)> = box (|ps| {}); + let f: Box)> = Box { inner: &mut (|ps| {}) }; f(&s); }"#, expect![[r#" @@ -3068,19 +3068,19 @@ fn foo() { 186..197 '*self.inner': T 187..191 'self': &Box 187..197 'self.inner': *mut T - 218..308 '{ ...&s); }': () + 218..324 '{ ...&s); }': () 228..229 's': Option 232..236 'None': Option 246..247 'f': Box)> - 281..294 'box (|ps| {})': Box)> - 286..293 '|ps| {}': impl Fn(&Option) - 287..289 'ps': &Option - 291..293 '{}': () - 300..301 'f': Box)> - 300..305 'f(&s)': () - 302..304 '&s': &Option - 303..304 's': Option - 281..294: expected Box)>, got Box)> + 281..310 'Box { ... {}) }': Box)> + 294..308 '&mut (|ps| {})': &mut impl Fn(&Option) + 300..307 '|ps| {}': impl Fn(&Option) + 301..303 'ps': &Option + 305..307 '{}': () + 316..317 'f': Box)> + 316..321 'f(&s)': () + 318..320 '&s': &Option + 319..320 's': Option "#]], ); } diff --git a/crates/hir/src/attrs.rs b/crates/hir/src/attrs.rs index 121e5a0a24931..796490abd7f23 100644 --- a/crates/hir/src/attrs.rs +++ b/crates/hir/src/attrs.rs @@ -1,39 +1,27 @@ //! Attributes & documentation for hir types. use hir_def::{ - attr::{AttrsWithOwner, Documentation}, + attr::AttrsWithOwner, item_scope::ItemInNs, path::{ModPath, Path}, per_ns::Namespace, resolver::{HasResolver, Resolver, TypeNs}, - AssocItemId, AttrDefId, GenericParamId, ModuleDefId, + AssocItemId, AttrDefId, ModuleDefId, }; use hir_expand::{hygiene::Hygiene, name::Name}; use hir_ty::db::HirDatabase; use syntax::{ast, AstNode}; use crate::{ - Adt, AsAssocItem, AssocItem, BuiltinType, Const, ConstParam, Enum, ExternCrateDecl, Field, - Function, GenericParam, Impl, LifetimeParam, Macro, Module, ModuleDef, Static, Struct, Trait, - TraitAlias, TypeAlias, TypeParam, Union, Variant, VariantDef, + Adt, AsAssocItem, AssocItem, BuiltinType, Const, ConstParam, DocLinkDef, Enum, ExternCrateDecl, + Field, Function, GenericParam, Impl, LifetimeParam, Macro, Module, ModuleDef, Static, Struct, + Trait, TraitAlias, TypeAlias, TypeParam, Union, Variant, VariantDef, }; pub trait HasAttrs { fn attrs(self, db: &dyn HirDatabase) -> AttrsWithOwner; - fn docs(self, db: &dyn HirDatabase) -> Option; - fn resolve_doc_path( - self, - db: &dyn HirDatabase, - link: &str, - ns: Option, - ) -> Option; -} - -/// Subset of `ide_db::Definition` that doc links can resolve to. -pub enum DocLinkDef { - ModuleDef(ModuleDef), - Field(Field), - SelfType(Trait), + #[doc(hidden)] + fn attr_id(self) -> AttrDefId; } macro_rules! impl_has_attrs { @@ -43,18 +31,8 @@ macro_rules! impl_has_attrs { let def = AttrDefId::$def_id(self.into()); db.attrs_with_owner(def) } - fn docs(self, db: &dyn HirDatabase) -> Option { - let def = AttrDefId::$def_id(self.into()); - db.attrs(def).docs() - } - fn resolve_doc_path( - self, - db: &dyn HirDatabase, - link: &str, - ns: Option - ) -> Option { - let def = AttrDefId::$def_id(self.into()); - resolve_doc_path(db, def, link, ns) + fn attr_id(self) -> AttrDefId { + AttrDefId::$def_id(self.into()) } } )*}; @@ -74,6 +52,7 @@ impl_has_attrs![ (Module, ModuleId), (GenericParam, GenericParamId), (Impl, ImplId), + (ExternCrateDecl, ExternCrateId), ]; macro_rules! impl_has_attrs_enum { @@ -82,16 +61,8 @@ macro_rules! impl_has_attrs_enum { fn attrs(self, db: &dyn HirDatabase) -> AttrsWithOwner { $enum::$variant(self).attrs(db) } - fn docs(self, db: &dyn HirDatabase) -> Option { - $enum::$variant(self).docs(db) - } - fn resolve_doc_path( - self, - db: &dyn HirDatabase, - link: &str, - ns: Option - ) -> Option { - $enum::$variant(self).resolve_doc_path(db, link, ns) + fn attr_id(self) -> AttrDefId { + $enum::$variant(self).attr_id() } } )*}; @@ -108,70 +79,35 @@ impl HasAttrs for AssocItem { AssocItem::TypeAlias(it) => it.attrs(db), } } - - fn docs(self, db: &dyn HirDatabase) -> Option { + fn attr_id(self) -> AttrDefId { match self { - AssocItem::Function(it) => it.docs(db), - AssocItem::Const(it) => it.docs(db), - AssocItem::TypeAlias(it) => it.docs(db), - } - } - - fn resolve_doc_path( - self, - db: &dyn HirDatabase, - link: &str, - ns: Option, - ) -> Option { - match self { - AssocItem::Function(it) => it.resolve_doc_path(db, link, ns), - AssocItem::Const(it) => it.resolve_doc_path(db, link, ns), - AssocItem::TypeAlias(it) => it.resolve_doc_path(db, link, ns), + AssocItem::Function(it) => it.attr_id(), + AssocItem::Const(it) => it.attr_id(), + AssocItem::TypeAlias(it) => it.attr_id(), } } } -impl HasAttrs for ExternCrateDecl { - fn attrs(self, db: &dyn HirDatabase) -> AttrsWithOwner { - let def = AttrDefId::ExternCrateId(self.into()); - db.attrs_with_owner(def) - } - fn docs(self, db: &dyn HirDatabase) -> Option { - let crate_docs = self.resolved_crate(db)?.root_module().attrs(db).docs().map(String::from); - let def = AttrDefId::ExternCrateId(self.into()); - let decl_docs = db.attrs(def).docs().map(String::from); - match (decl_docs, crate_docs) { - (None, None) => None, - (Some(decl_docs), None) => Some(decl_docs), - (None, Some(crate_docs)) => Some(crate_docs), - (Some(mut decl_docs), Some(crate_docs)) => { - decl_docs.push('\n'); - decl_docs.push('\n'); - decl_docs += &crate_docs; - Some(decl_docs) - } - } - .map(Documentation::new) - } - fn resolve_doc_path( - self, - db: &dyn HirDatabase, - link: &str, - ns: Option, - ) -> Option { - let def = AttrDefId::ExternCrateId(self.into()); - resolve_doc_path(db, def, link, ns) - } +/// Resolves the item `link` points to in the scope of `def`. +pub fn resolve_doc_path_on( + db: &dyn HirDatabase, + def: impl HasAttrs, + link: &str, + ns: Option, +) -> Option { + // AttrDefId::FieldId(it) => it.parent.resolver(db.upcast()), + // AttrDefId::EnumVariantId(it) => it.parent.resolver(db.upcast()), + + resolve_doc_path_on_(db, link, def.attr_id(), ns) } -/// Resolves the item `link` points to in the scope of `def`. -fn resolve_doc_path( +fn resolve_doc_path_on_( db: &dyn HirDatabase, - def: AttrDefId, link: &str, + attr_id: AttrDefId, ns: Option, ) -> Option { - let resolver = match def { + let resolver = match attr_id { AttrDefId::ModuleId(it) => it.resolver(db.upcast()), AttrDefId::FieldId(it) => it.parent.resolver(db.upcast()), AttrDefId::AdtId(it) => it.resolver(db.upcast()), @@ -187,12 +123,7 @@ fn resolve_doc_path( AttrDefId::UseId(it) => it.resolver(db.upcast()), AttrDefId::MacroId(it) => it.resolver(db.upcast()), AttrDefId::ExternCrateId(it) => it.resolver(db.upcast()), - AttrDefId::GenericParamId(it) => match it { - GenericParamId::TypeParamId(it) => it.parent(), - GenericParamId::ConstParamId(it) => it.parent(), - GenericParamId::LifetimeParamId(it) => it.parent, - } - .resolver(db.upcast()), + AttrDefId::GenericParamId(_) => return None, }; let mut modpath = modpath_from_str(db, link)?; diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index 80c3bcdca8b71..479138b67f829 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -43,6 +43,7 @@ diagnostics![ MacroExpansionParseError, MalformedDerive, MismatchedArgCount, + MismatchedTupleStructPatArgCount, MissingFields, MissingMatchArms, MissingUnsafe, @@ -172,7 +173,8 @@ pub struct MalformedDerive { #[derive(Debug)] pub struct NoSuchField { - pub field: InFile>, + pub field: InFile, AstPtr>>, + pub private: bool, } #[derive(Debug)] @@ -182,6 +184,13 @@ pub struct PrivateAssocItem { pub item: AssocItem, } +#[derive(Debug)] +pub struct MismatchedTupleStructPatArgCount { + pub expr_or_pat: InFile, AstPtr>>, + pub expected: usize, + pub found: usize, +} + #[derive(Debug)] pub struct ExpectedFunction { pub call: InFile>, diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 512fe7e0428a2..b215ed38f28b1 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -88,13 +88,14 @@ use triomphe::Arc; use crate::db::{DefDatabase, HirDatabase}; pub use crate::{ - attrs::{DocLinkDef, HasAttrs}, + attrs::{resolve_doc_path_on, HasAttrs}, diagnostics::{ AnyDiagnostic, BreakOutsideOfLoop, CaseType, ExpectedFunction, InactiveCode, IncoherentImpl, IncorrectCase, InvalidDeriveTarget, MacroDefError, MacroError, - MacroExpansionParseError, MalformedDerive, MismatchedArgCount, MissingFields, - MissingMatchArms, MissingUnsafe, MovedOutOfRef, NeedMut, NoSuchField, PrivateAssocItem, - PrivateField, ReplaceFilterMapNextWithFindMap, TypeMismatch, TypedHole, UndeclaredLabel, + MacroExpansionParseError, MalformedDerive, MismatchedArgCount, + MismatchedTupleStructPatArgCount, MissingFields, MissingMatchArms, MissingUnsafe, + MovedOutOfRef, NeedMut, NoSuchField, PrivateAssocItem, PrivateField, + ReplaceFilterMapNextWithFindMap, TypeMismatch, TypedHole, UndeclaredLabel, UnimplementedBuiltinMacro, UnreachableLabel, UnresolvedExternCrate, UnresolvedField, UnresolvedImport, UnresolvedMacroCall, UnresolvedMethodCall, UnresolvedModule, UnresolvedProcMacro, UnusedMut, @@ -115,7 +116,7 @@ pub use crate::{ pub use { cfg::{CfgAtom, CfgExpr, CfgOptions}, hir_def::{ - attr::{builtin::AttributeTemplate, Attrs, AttrsWithOwner, Documentation}, + attr::{builtin::AttributeTemplate, AttrSourceMap, Attrs, AttrsWithOwner}, data::adt::StructKind, find_path::PrefixKind, import_map, @@ -130,7 +131,7 @@ pub use { {AdtId, ModuleDefId}, }, hir_expand::{ - attrs::Attr, + attrs::{Attr, AttrId}, name::{known, Name}, ExpandResult, HirFileId, InFile, MacroFile, Origin, }, @@ -563,8 +564,8 @@ impl Module { emit_def_diagnostic(db, acc, diag); } - for decl in self.declarations(db) { - match decl { + for def in self.declarations(db) { + match def { ModuleDef::Module(m) => { // Only add diagnostics from inline modules if def_map[m.id.local_id].origin.is_inline() { @@ -575,7 +576,7 @@ impl Module { for diag in db.trait_data_with_diagnostics(t.id).1.iter() { emit_def_diagnostic(db, acc, diag); } - acc.extend(decl.diagnostics(db)) + acc.extend(def.diagnostics(db)) } ModuleDef::Adt(adt) => { match adt { @@ -599,10 +600,10 @@ impl Module { } } } - acc.extend(decl.diagnostics(db)) + acc.extend(def.diagnostics(db)) } ModuleDef::Macro(m) => emit_macro_def_diagnostics(db, acc, m), - _ => acc.extend(decl.diagnostics(db)), + _ => acc.extend(def.diagnostics(db)), } } self.legacy_macros(db).into_iter().for_each(|m| emit_macro_def_diagnostics(db, acc, m)); @@ -1446,6 +1447,7 @@ impl DefWithBody { } pub fn diagnostics(self, db: &dyn HirDatabase, acc: &mut Vec) { + db.unwind_if_cancelled(); let krate = self.module(db).id.krate(); let (body, source_map) = db.body_with_source_map(self.into()); @@ -1501,11 +1503,19 @@ impl DefWithBody { let infer = db.infer(self.into()); let source_map = Lazy::new(|| db.body_with_source_map(self.into()).1); let expr_syntax = |expr| source_map.expr_syntax(expr).expect("unexpected synthetic"); + let pat_syntax = |pat| source_map.pat_syntax(pat).expect("unexpected synthetic"); for d in &infer.diagnostics { match d { - &hir_ty::InferenceDiagnostic::NoSuchField { expr } => { - let field = source_map.field_syntax(expr); - acc.push(NoSuchField { field }.into()) + &hir_ty::InferenceDiagnostic::NoSuchField { field: expr, private } => { + let expr_or_pat = match expr { + ExprOrPatId::ExprId(expr) => { + source_map.field_syntax(expr).map(Either::Left) + } + ExprOrPatId::PatId(pat) => { + source_map.pat_field_syntax(pat).map(Either::Right) + } + }; + acc.push(NoSuchField { field: expr_or_pat, private }.into()) } &hir_ty::InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => { acc.push( @@ -1521,10 +1531,7 @@ impl DefWithBody { &hir_ty::InferenceDiagnostic::PrivateAssocItem { id, item } => { let expr_or_pat = match id { ExprOrPatId::ExprId(expr) => expr_syntax(expr).map(Either::Left), - ExprOrPatId::PatId(pat) => source_map - .pat_syntax(pat) - .expect("unexpected synthetic") - .map(Either::Right), + ExprOrPatId::PatId(pat) => pat_syntax(pat).map(Either::Right), }; let item = item.into(); acc.push(PrivateAssocItem { expr_or_pat, item }.into()) @@ -1596,6 +1603,23 @@ impl DefWithBody { .into(), ) } + &hir_ty::InferenceDiagnostic::MismatchedTupleStructPatArgCount { + pat, + expected, + found, + } => { + let expr_or_pat = match pat { + ExprOrPatId::ExprId(expr) => expr_syntax(expr).map(Either::Left), + ExprOrPatId::PatId(pat) => source_map + .pat_syntax(pat) + .expect("unexpected synthetic") + .map(|it| it.unwrap_left()) + .map(Either::Right), + }; + acc.push( + MismatchedTupleStructPatArgCount { expr_or_pat, expected, found }.into(), + ) + } } } for (pat_or_expr, mismatch) in infer.type_mismatches() { @@ -4838,3 +4862,10 @@ pub enum ItemContainer { ExternBlock(), Crate(CrateId), } + +/// Subset of `ide_db::Definition` that doc links can resolve to. +pub enum DocLinkDef { + ModuleDef(ModuleDef), + Field(Field), + SelfType(Trait), +} diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index b8d4ecd44142e..a42e0978b25f3 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -127,165 +127,24 @@ impl fmt::Debug for Semantics<'_, DB> { } } +impl<'db, DB> ops::Deref for Semantics<'db, DB> { + type Target = SemanticsImpl<'db>; + + fn deref(&self) -> &Self::Target { + &self.imp + } +} + impl<'db, DB: HirDatabase> Semantics<'db, DB> { pub fn new(db: &DB) -> Semantics<'_, DB> { let impl_ = SemanticsImpl::new(db); Semantics { db, imp: impl_ } } - pub fn parse(&self, file_id: FileId) -> ast::SourceFile { - self.imp.parse(file_id) - } - - pub fn parse_or_expand(&self, file_id: HirFileId) -> SyntaxNode { - self.imp.parse_or_expand(file_id) - } - - pub fn expand(&self, macro_call: &ast::MacroCall) -> Option { - self.imp.expand(macro_call) - } - - /// If `item` has an attribute macro attached to it, expands it. - pub fn expand_attr_macro(&self, item: &ast::Item) -> Option { - self.imp.expand_attr_macro(item) - } - - pub fn expand_derive_as_pseudo_attr_macro(&self, attr: &ast::Attr) -> Option { - self.imp.expand_derive_as_pseudo_attr_macro(attr) - } - - pub fn resolve_derive_macro(&self, derive: &ast::Attr) -> Option>> { - self.imp.resolve_derive_macro(derive) - } - - pub fn expand_derive_macro(&self, derive: &ast::Attr) -> Option> { - self.imp.expand_derive_macro(derive) - } - - pub fn is_attr_macro_call(&self, item: &ast::Item) -> bool { - self.imp.is_attr_macro_call(item) - } - - pub fn is_derive_annotated(&self, item: &ast::Adt) -> bool { - self.imp.is_derive_annotated(item) - } - - /// Expand the macro call with a different token tree, mapping the `token_to_map` down into the - /// expansion. `token_to_map` should be a token from the `speculative args` node. - pub fn speculative_expand( - &self, - actual_macro_call: &ast::MacroCall, - speculative_args: &ast::TokenTree, - token_to_map: SyntaxToken, - ) -> Option<(SyntaxNode, SyntaxToken)> { - self.imp.speculative_expand(actual_macro_call, speculative_args, token_to_map) - } - - /// Expand the macro call with a different item as the input, mapping the `token_to_map` down into the - /// expansion. `token_to_map` should be a token from the `speculative args` node. - pub fn speculative_expand_attr_macro( - &self, - actual_macro_call: &ast::Item, - speculative_args: &ast::Item, - token_to_map: SyntaxToken, - ) -> Option<(SyntaxNode, SyntaxToken)> { - self.imp.speculative_expand_attr(actual_macro_call, speculative_args, token_to_map) - } - - pub fn speculative_expand_derive_as_pseudo_attr_macro( - &self, - actual_macro_call: &ast::Attr, - speculative_args: &ast::Attr, - token_to_map: SyntaxToken, - ) -> Option<(SyntaxNode, SyntaxToken)> { - self.imp.speculative_expand_derive_as_pseudo_attr_macro( - actual_macro_call, - speculative_args, - token_to_map, - ) - } - - /// Descend the token into its macro call if it is part of one, returning the token in the - /// expansion that it is associated with. If `offset` points into the token's range, it will - /// be considered for the mapping in case of inline format args. - pub fn descend_into_macros_single(&self, token: SyntaxToken, offset: TextSize) -> SyntaxToken { - self.imp.descend_into_macros_single(token, offset) - } - - /// Descend the token into its macro call if it is part of one, returning the tokens in the - /// expansion that it is associated with. If `offset` points into the token's range, it will - /// be considered for the mapping in case of inline format args. - pub fn descend_into_macros( - &self, - token: SyntaxToken, - offset: TextSize, - ) -> SmallVec<[SyntaxToken; 1]> { - self.imp.descend_into_macros(token, offset) - } - - /// Descend the token into macrocalls to all its mapped counterparts that have the same text as the input token. - /// - /// Returns the original non descended token if none of the mapped counterparts have the same text. - pub fn descend_into_macros_with_same_text( - &self, - token: SyntaxToken, - offset: TextSize, - ) -> SmallVec<[SyntaxToken; 1]> { - self.imp.descend_into_macros_with_same_text(token, offset) - } - - pub fn descend_into_macros_with_kind_preference( - &self, - token: SyntaxToken, - offset: TextSize, - ) -> SyntaxToken { - self.imp.descend_into_macros_with_kind_preference(token, offset) - } - - /// Maps a node down by mapping its first and last token down. - pub fn descend_node_into_attributes(&self, node: N) -> SmallVec<[N; 1]> { - self.imp.descend_node_into_attributes(node) - } - - /// Search for a definition's source and cache its syntax tree - pub fn source(&self, def: Def) -> Option> - where - Def::Ast: AstNode, - { - self.imp.source(def) - } - pub fn hir_file_for(&self, syntax_node: &SyntaxNode) -> HirFileId { self.imp.find_file(syntax_node).file_id } - /// Attempts to map the node out of macro expanded files returning the original file range. - /// If upmapping is not possible, this will fall back to the range of the macro call of the - /// macro file the node resides in. - pub fn original_range(&self, node: &SyntaxNode) -> FileRange { - self.imp.original_range(node) - } - - /// Attempts to map the node out of macro expanded files returning the original file range. - pub fn original_range_opt(&self, node: &SyntaxNode) -> Option { - self.imp.original_range_opt(node) - } - - /// Attempts to map the node out of macro expanded files. - /// This only work for attribute expansions, as other ones do not have nodes as input. - pub fn original_ast_node(&self, node: N) -> Option { - self.imp.original_ast_node(node) - } - /// Attempts to map the node out of macro expanded files. - /// This only work for attribute expansions, as other ones do not have nodes as input. - pub fn original_syntax_node(&self, node: &SyntaxNode) -> Option { - self.imp.original_syntax_node(node) - } - - pub fn diagnostics_display_range(&self, diagnostics: InFile) -> FileRange { - self.imp.diagnostics_display_range(diagnostics) - } - pub fn token_ancestors_with_macros( &self, token: SyntaxToken, @@ -293,19 +152,6 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { token.parent().into_iter().flat_map(move |it| self.ancestors_with_macros(it)) } - /// Iterates the ancestors of the given node, climbing up macro expansions while doing so. - pub fn ancestors_with_macros(&self, node: SyntaxNode) -> impl Iterator + '_ { - self.imp.ancestors_with_macros(node) - } - - pub fn ancestors_at_offset_with_macros( - &self, - node: &SyntaxNode, - offset: TextSize, - ) -> impl Iterator + '_ { - self.imp.ancestors_at_offset_with_macros(node, offset) - } - /// Find an AstNode by offset inside SyntaxNode, if it is inside *Macrofile*, /// search up until it is of the target AstNode type pub fn find_node_at_offset_with_macros( @@ -336,53 +182,6 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { self.imp.descend_node_at_offset(node, offset).filter_map(|mut it| it.find_map(N::cast)) } - pub fn resolve_lifetime_param(&self, lifetime: &ast::Lifetime) -> Option { - self.imp.resolve_lifetime_param(lifetime) - } - - pub fn resolve_label(&self, lifetime: &ast::Lifetime) -> Option