mirror of
https://github.com/rust-lang/rust.git
synced 2025-02-17 01:13:11 +00:00
Auto merge of #75388 - JohnTitor:rollup-9tgkxnl, r=JohnTitor
Rollup of 10 pull requests Successful merges: - #74744 (Update RELEASES.md for 1.46.0) - #75085 (Transmute big endian `s6_addr` and `[u16; 8]`) - #75226 (Miri: Renamed "undef" to "uninit") - #75333 (polymorphize: constrain unevaluated const handling) - #75338 (move stack size check to const_eval machine) - #75347 (Rustdoc: Fix natural ordering to look at all numbers.) - #75352 (Tweak conditions for E0026 and E0769) - #75353 (Tiny cleanup, remove unnecessary `unwrap`) - #75359 (unused_delims: trim expr) - #75360 (Add sample fix for E0749) Failed merges: r? @ghost
This commit is contained in:
commit
4b9ac51617
103
RELEASES.md
103
RELEASES.md
@ -1,3 +1,104 @@
|
||||
Version 1.46.0 (2020-08-27)
|
||||
==========================
|
||||
|
||||
Language
|
||||
--------
|
||||
- [`if`, `match`, and `loop` expressions can now be used in const functions.][72437]
|
||||
- [Additionally you are now also able to coerce and cast to slices (`&[T]`) in
|
||||
const functions.][73862]
|
||||
- [The `#[track_caller]` attribute can now be added to functions to use the
|
||||
function's caller's location information for panic messages.][72445]
|
||||
- [Recursively indexing into tuples no longer needs parentheses.][71322] E.g.
|
||||
`x.0.0` over `(x.0).0`.
|
||||
- [`mem::transmute` can now be used in static and constants.][72920] **Note**
|
||||
You currently can't use `mem::transmute` in constant functions.
|
||||
|
||||
Compiler
|
||||
--------
|
||||
- [You can now use the `cdylib` target on Apple iOS and tvOS platforms.][73516]
|
||||
- [Enabled static "Position Independent Executables" by default
|
||||
for `x86_64-unknown-linux-musl`.][70740]
|
||||
|
||||
Libraries
|
||||
---------
|
||||
- [`mem::forget` is now a `const fn`.][73887]
|
||||
- [`String` now implements `From<char>`.][73466]
|
||||
- [The `leading_ones`, and `trailing_ones` methods have been stabilised for all
|
||||
integer types.][73032]
|
||||
- [`vec::IntoIter<T>` now implements `AsRef<[T]>`.][72583]
|
||||
- [All non-zero integer types (`NonZeroU8`) now implement `TryFrom` for their
|
||||
zero-able equivalent (e.g. `TryFrom<u8>`).][72717]
|
||||
- [`&[T]` and `&mut [T]` now implement `PartialEq<Vec<T>>`.][71660]
|
||||
- [`(String, u16)` now implements `ToSocketAddrs`.][73007]
|
||||
- [`vec::Drain<'_, T>` now implements `AsRef<[T]>`.][72584]
|
||||
|
||||
Stabilized APIs
|
||||
---------------
|
||||
- [`Option::zip`]
|
||||
- [`vec::Drain::as_slice`]
|
||||
|
||||
Cargo
|
||||
-----
|
||||
Added a number of new environment variables that are now available when
|
||||
compiling your crate.
|
||||
|
||||
- [`CARGO_BIN_NAME` and `CARGO_CRATE_NAME`][cargo/8270] Providing the name of
|
||||
the specific binary being compiled and the name of the crate.
|
||||
- [`CARGO_PKG_LICENSE`][cargo/8325] The license from the manifest of the package.
|
||||
- [`CARGO_PKG_LICENSE_FILE`][cargo/8387] The path to the license file.
|
||||
|
||||
Compatibility Notes
|
||||
-------------------
|
||||
- [The target configuration option `abi_blacklist` has been renamed
|
||||
to `unsupported_abis`.][74150] The old name will still continue to work.
|
||||
- [Rustc will now warn if you have a C-like enum that implements `Drop`.][72331]
|
||||
This was previously accepted but will become a hard error in a future release.
|
||||
- [Rustc will fail to compile if you have a struct with
|
||||
`#[repr(i128)]` or `#[repr(u128)]`.][74109] This representation is currently only
|
||||
allowed on `enum`s.
|
||||
- [Tokens passed to `macro_rules!` are now always captured.][73293] This helps
|
||||
ensure that spans have the correct information, and may cause breakage if you
|
||||
were relying on receiving spans with dummy information.
|
||||
- [The InnoSetup installer for Windows is no longer available.][72569] This was
|
||||
a legacy installer that was replaced by a MSI installer a few years ago but
|
||||
was still being built.
|
||||
- [`{f32, f64}::asinh` now returns the correct values for negative numbers.][72486]
|
||||
- [Rustc will no longer accept overlapping trait implementations that only
|
||||
differ in how the lifetime was bound.][72493]
|
||||
- [Rustc now correctly relates the lifetime of an existential associated
|
||||
type.][71896] This fixes some edge cases where `rustc` would erroneously allow
|
||||
you to pass a shorter lifetime than expected.
|
||||
|
||||
[74109]: https://github.com/rust-lang/rust/pull/74109/
|
||||
[74150]: https://github.com/rust-lang/rust/pull/74150/
|
||||
[73862]: https://github.com/rust-lang/rust/pull/73862/
|
||||
[73887]: https://github.com/rust-lang/rust/pull/73887/
|
||||
[73466]: https://github.com/rust-lang/rust/pull/73466/
|
||||
[73516]: https://github.com/rust-lang/rust/pull/73516/
|
||||
[73293]: https://github.com/rust-lang/rust/pull/73293/
|
||||
[73007]: https://github.com/rust-lang/rust/pull/73007/
|
||||
[73032]: https://github.com/rust-lang/rust/pull/73032/
|
||||
[72920]: https://github.com/rust-lang/rust/pull/72920/
|
||||
[72569]: https://github.com/rust-lang/rust/pull/72569/
|
||||
[72583]: https://github.com/rust-lang/rust/pull/72583/
|
||||
[72584]: https://github.com/rust-lang/rust/pull/72584/
|
||||
[72717]: https://github.com/rust-lang/rust/pull/72717/
|
||||
[72437]: https://github.com/rust-lang/rust/pull/72437/
|
||||
[72445]: https://github.com/rust-lang/rust/pull/72445/
|
||||
[72486]: https://github.com/rust-lang/rust/pull/72486/
|
||||
[72493]: https://github.com/rust-lang/rust/pull/72493/
|
||||
[72331]: https://github.com/rust-lang/rust/pull/72331/
|
||||
[71896]: https://github.com/rust-lang/rust/pull/71896/
|
||||
[71660]: https://github.com/rust-lang/rust/pull/71660/
|
||||
[71322]: https://github.com/rust-lang/rust/pull/71322/
|
||||
[70740]: https://github.com/rust-lang/rust/pull/70740/
|
||||
[cargo/8270]: https://github.com/rust-lang/cargo/pull/8270/
|
||||
[cargo/8325]: https://github.com/rust-lang/cargo/pull/8325/
|
||||
[cargo/8387]: https://github.com/rust-lang/cargo/pull/8387/
|
||||
[`Option::zip`]: https://doc.rust-lang.org/stable/std/option/enum.Option.html#method.zip
|
||||
[`vec::Drain::as_slice`]: https://doc.rust-lang.org/stable/std/vec/struct.Drain.html#method.as_slice
|
||||
|
||||
|
||||
Version 1.45.2 (2020-08-03)
|
||||
==========================
|
||||
|
||||
@ -7,6 +108,7 @@ Version 1.45.2 (2020-08-03)
|
||||
[74954]: https://github.com/rust-lang/rust/issues/74954
|
||||
[74784]: https://github.com/rust-lang/rust/issues/74784
|
||||
|
||||
|
||||
Version 1.45.1 (2020-07-30)
|
||||
==========================
|
||||
|
||||
@ -20,6 +122,7 @@ Version 1.45.1 (2020-07-30)
|
||||
[74509]: https://github.com/rust-lang/rust/pull/74509
|
||||
[74457]: https://github.com/rust-lang/rust/pull/74457
|
||||
|
||||
|
||||
Version 1.45.0 (2020-07-16)
|
||||
==========================
|
||||
|
||||
|
@ -249,6 +249,7 @@
|
||||
#![feature(clamp)]
|
||||
#![feature(concat_idents)]
|
||||
#![feature(const_cstr_unchecked)]
|
||||
#![feature(const_fn_transmute)]
|
||||
#![feature(const_raw_ptr_deref)]
|
||||
#![feature(container_error_extra)]
|
||||
#![feature(core_intrinsics)]
|
||||
|
@ -10,6 +10,7 @@ use crate::cmp::Ordering;
|
||||
use crate::fmt::{self, Write as FmtWrite};
|
||||
use crate::hash;
|
||||
use crate::io::Write as IoWrite;
|
||||
use crate::mem::transmute;
|
||||
use crate::sys::net::netc as c;
|
||||
use crate::sys_common::{AsInner, FromInner};
|
||||
|
||||
@ -1045,27 +1046,23 @@ impl Ipv6Addr {
|
||||
/// ```
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
#[rustc_const_stable(feature = "const_ipv6", since = "1.32.0")]
|
||||
#[allow_internal_unstable(const_fn_transmute)]
|
||||
pub const fn new(a: u16, b: u16, c: u16, d: u16, e: u16, f: u16, g: u16, h: u16) -> Ipv6Addr {
|
||||
let addr16 = [
|
||||
a.to_be(),
|
||||
b.to_be(),
|
||||
c.to_be(),
|
||||
d.to_be(),
|
||||
e.to_be(),
|
||||
f.to_be(),
|
||||
g.to_be(),
|
||||
h.to_be(),
|
||||
];
|
||||
Ipv6Addr {
|
||||
inner: c::in6_addr {
|
||||
s6_addr: [
|
||||
(a >> 8) as u8,
|
||||
a as u8,
|
||||
(b >> 8) as u8,
|
||||
b as u8,
|
||||
(c >> 8) as u8,
|
||||
c as u8,
|
||||
(d >> 8) as u8,
|
||||
d as u8,
|
||||
(e >> 8) as u8,
|
||||
e as u8,
|
||||
(f >> 8) as u8,
|
||||
f as u8,
|
||||
(g >> 8) as u8,
|
||||
g as u8,
|
||||
(h >> 8) as u8,
|
||||
h as u8,
|
||||
],
|
||||
// All elements in `addr16` are big endian.
|
||||
// SAFETY: `[u16; 8]` is always safe to transmute to `[u8; 16]`.
|
||||
s6_addr: unsafe { transmute::<_, [u8; 16]>(addr16) },
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -1108,16 +1105,19 @@ impl Ipv6Addr {
|
||||
/// ```
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
pub fn segments(&self) -> [u16; 8] {
|
||||
let arr = &self.inner.s6_addr;
|
||||
// All elements in `s6_addr` must be big endian.
|
||||
// SAFETY: `[u8; 16]` is always safe to transmute to `[u16; 8]`.
|
||||
let [a, b, c, d, e, f, g, h] = unsafe { transmute::<_, [u16; 8]>(self.inner.s6_addr) };
|
||||
// We want native endian u16
|
||||
[
|
||||
u16::from_be_bytes([arr[0], arr[1]]),
|
||||
u16::from_be_bytes([arr[2], arr[3]]),
|
||||
u16::from_be_bytes([arr[4], arr[5]]),
|
||||
u16::from_be_bytes([arr[6], arr[7]]),
|
||||
u16::from_be_bytes([arr[8], arr[9]]),
|
||||
u16::from_be_bytes([arr[10], arr[11]]),
|
||||
u16::from_be_bytes([arr[12], arr[13]]),
|
||||
u16::from_be_bytes([arr[14], arr[15]]),
|
||||
u16::from_be(a),
|
||||
u16::from_be(b),
|
||||
u16::from_be(c),
|
||||
u16::from_be(d),
|
||||
u16::from_be(e),
|
||||
u16::from_be(f),
|
||||
u16::from_be(g),
|
||||
u16::from_be(h),
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ pub fn const_alloc_to_llvm(cx: &CodegenCx<'ll, '_>, alloc: &Allocation) -> &'ll
|
||||
// some arbitrary byte value.
|
||||
//
|
||||
// FIXME: relay undef bytes to codegen as undef const bytes
|
||||
let bytes = alloc.inspect_with_undef_and_ptr_outside_interpreter(next_offset..offset);
|
||||
let bytes = alloc.inspect_with_uninit_and_ptr_outside_interpreter(next_offset..offset);
|
||||
llvals.push(cx.const_bytes(bytes));
|
||||
}
|
||||
let ptr_offset = read_target_uint(
|
||||
@ -49,7 +49,7 @@ pub fn const_alloc_to_llvm(cx: &CodegenCx<'ll, '_>, alloc: &Allocation) -> &'ll
|
||||
// This `inspect` is okay since it is within the bounds of the allocation, it doesn't
|
||||
// affect interpreter execution (we inspect the result after interpreter execution),
|
||||
// and we properly interpret the relocation as a relocation pointer offset.
|
||||
alloc.inspect_with_undef_and_ptr_outside_interpreter(offset..(offset + pointer_size)),
|
||||
alloc.inspect_with_uninit_and_ptr_outside_interpreter(offset..(offset + pointer_size)),
|
||||
)
|
||||
.expect("const_alloc_to_llvm: could not read relocation pointer")
|
||||
as u64;
|
||||
@ -74,7 +74,7 @@ pub fn const_alloc_to_llvm(cx: &CodegenCx<'ll, '_>, alloc: &Allocation) -> &'ll
|
||||
// arbitrary byte value.
|
||||
//
|
||||
// FIXME: relay undef bytes to codegen as undef const bytes
|
||||
let bytes = alloc.inspect_with_undef_and_ptr_outside_interpreter(range);
|
||||
let bytes = alloc.inspect_with_uninit_and_ptr_outside_interpreter(range);
|
||||
llvals.push(cx.const_bytes(bytes));
|
||||
}
|
||||
|
||||
@ -452,7 +452,7 @@ impl StaticMethods for CodegenCx<'ll, 'tcx> {
|
||||
// BSS.
|
||||
let all_bytes_are_zero = alloc.relocations().is_empty()
|
||||
&& alloc
|
||||
.inspect_with_undef_and_ptr_outside_interpreter(0..alloc.len())
|
||||
.inspect_with_uninit_and_ptr_outside_interpreter(0..alloc.len())
|
||||
.iter()
|
||||
.all(|&byte| byte == 0);
|
||||
|
||||
@ -480,7 +480,7 @@ impl StaticMethods for CodegenCx<'ll, 'tcx> {
|
||||
// because we are doing this access to inspect the final interpreter state (not
|
||||
// as part of the interpreter execution).
|
||||
let bytes =
|
||||
alloc.inspect_with_undef_and_ptr_outside_interpreter(0..alloc.len());
|
||||
alloc.inspect_with_uninit_and_ptr_outside_interpreter(0..alloc.len());
|
||||
let alloc = llvm::LLVMMDStringInContext(
|
||||
self.llcx,
|
||||
bytes.as_ptr().cast(),
|
||||
|
@ -11,9 +11,19 @@ trait MyTrait {
|
||||
impl !MyTrait for u32 {
|
||||
type Foo = i32; // error!
|
||||
}
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
Negative impls are not allowed to have any items. Negative impls declare that a
|
||||
trait is **not** implemented (and never will be) and hence there is no need to
|
||||
specify the values for trait methods or other items.
|
||||
|
||||
One way to fix this is to remove the items in negative impls:
|
||||
|
||||
```
|
||||
# #![feature(negative_impls)]
|
||||
trait MyTrait {
|
||||
type Foo;
|
||||
}
|
||||
|
||||
impl !MyTrait for u32 {}
|
||||
```
|
||||
|
@ -481,25 +481,27 @@ trait UnusedDelimLint {
|
||||
let mut err = lint.build(&span_msg);
|
||||
let mut ate_left_paren = false;
|
||||
let mut ate_right_paren = false;
|
||||
let parens_removed = pattern.trim_matches(|c| match c {
|
||||
'(' | '{' => {
|
||||
if ate_left_paren {
|
||||
false
|
||||
} else {
|
||||
ate_left_paren = true;
|
||||
true
|
||||
let parens_removed = pattern
|
||||
.trim_matches(|c| match c {
|
||||
'(' | '{' => {
|
||||
if ate_left_paren {
|
||||
false
|
||||
} else {
|
||||
ate_left_paren = true;
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
')' | '}' => {
|
||||
if ate_right_paren {
|
||||
false
|
||||
} else {
|
||||
ate_right_paren = true;
|
||||
true
|
||||
')' | '}' => {
|
||||
if ate_right_paren {
|
||||
false
|
||||
} else {
|
||||
ate_right_paren = true;
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
});
|
||||
_ => false,
|
||||
})
|
||||
.trim();
|
||||
|
||||
let replace = {
|
||||
let mut replace = if keep_space.0 {
|
||||
|
@ -154,10 +154,10 @@ impl<Tag, Extra> Allocation<Tag, Extra> {
|
||||
}
|
||||
|
||||
/// Looks at a slice which may describe uninitialized bytes or describe a relocation. This differs
|
||||
/// from `get_bytes_with_undef_and_ptr` in that it does no relocation checks (even on the
|
||||
/// from `get_bytes_with_uninit_and_ptr` in that it does no relocation checks (even on the
|
||||
/// edges) at all. It further ignores `AllocationExtra` callbacks.
|
||||
/// This must not be used for reads affecting the interpreter execution.
|
||||
pub fn inspect_with_undef_and_ptr_outside_interpreter(&self, range: Range<usize>) -> &[u8] {
|
||||
pub fn inspect_with_uninit_and_ptr_outside_interpreter(&self, range: Range<usize>) -> &[u8] {
|
||||
&self.bytes[range]
|
||||
}
|
||||
|
||||
@ -194,7 +194,7 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
|
||||
|
||||
/// The last argument controls whether we error out when there are uninitialized
|
||||
/// or pointer bytes. You should never call this, call `get_bytes` or
|
||||
/// `get_bytes_with_undef_and_ptr` instead,
|
||||
/// `get_bytes_with_uninit_and_ptr` instead,
|
||||
///
|
||||
/// This function also guarantees that the resulting pointer will remain stable
|
||||
/// even when new allocations are pushed to the `HashMap`. `copy_repeatedly` relies
|
||||
@ -244,7 +244,7 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
|
||||
///
|
||||
/// It is the caller's responsibility to check bounds and alignment beforehand.
|
||||
#[inline]
|
||||
pub fn get_bytes_with_undef_and_ptr(
|
||||
pub fn get_bytes_with_uninit_and_ptr(
|
||||
&self,
|
||||
cx: &impl HasDataLayout,
|
||||
ptr: Pointer<Tag>,
|
||||
@ -302,19 +302,19 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
|
||||
}
|
||||
|
||||
/// Validates that `ptr.offset` and `ptr.offset + size` do not point to the middle of a
|
||||
/// relocation. If `allow_ptr_and_undef` is `false`, also enforces that the memory in the
|
||||
/// relocation. If `allow_uninit_and_ptr` is `false`, also enforces that the memory in the
|
||||
/// given range contains neither relocations nor uninitialized bytes.
|
||||
pub fn check_bytes(
|
||||
&self,
|
||||
cx: &impl HasDataLayout,
|
||||
ptr: Pointer<Tag>,
|
||||
size: Size,
|
||||
allow_ptr_and_undef: bool,
|
||||
allow_uninit_and_ptr: bool,
|
||||
) -> InterpResult<'tcx> {
|
||||
// Check bounds and relocations on the edges.
|
||||
self.get_bytes_with_undef_and_ptr(cx, ptr, size)?;
|
||||
self.get_bytes_with_uninit_and_ptr(cx, ptr, size)?;
|
||||
// Check uninit and ptr.
|
||||
if !allow_ptr_and_undef {
|
||||
if !allow_uninit_and_ptr {
|
||||
self.check_init(ptr, size)?;
|
||||
self.check_relocations(cx, ptr, size)?;
|
||||
}
|
||||
@ -361,7 +361,7 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
|
||||
size: Size,
|
||||
) -> InterpResult<'tcx, ScalarMaybeUninit<Tag>> {
|
||||
// `get_bytes_unchecked` tests relocation edges.
|
||||
let bytes = self.get_bytes_with_undef_and_ptr(cx, ptr, size)?;
|
||||
let bytes = self.get_bytes_with_uninit_and_ptr(cx, ptr, size)?;
|
||||
// Uninit check happens *after* we established that the alignment is correct.
|
||||
// We must not return `Ok()` for unaligned pointers!
|
||||
if self.is_init(ptr, size).is_err() {
|
||||
@ -594,7 +594,7 @@ impl InitMaskCompressed {
|
||||
/// Transferring the initialization mask to other allocations.
|
||||
impl<Tag, Extra> Allocation<Tag, Extra> {
|
||||
/// Creates a run-length encoding of the initialization mask.
|
||||
pub fn compress_undef_range(&self, src: Pointer<Tag>, size: Size) -> InitMaskCompressed {
|
||||
pub fn compress_uninit_range(&self, src: Pointer<Tag>, size: Size) -> InitMaskCompressed {
|
||||
// Since we are copying `size` bytes from `src` to `dest + i * size` (`for i in 0..repeat`),
|
||||
// a naive initialization mask copying algorithm would repeatedly have to read the initialization mask from
|
||||
// the source and write it to the destination. Even if we optimized the memory accesses,
|
||||
@ -636,8 +636,8 @@ impl<Tag, Extra> Allocation<Tag, Extra> {
|
||||
size: Size,
|
||||
repeat: u64,
|
||||
) {
|
||||
// An optimization where we can just overwrite an entire range of definedness bits if
|
||||
// they are going to be uniformly `1` or `0`.
|
||||
// An optimization where we can just overwrite an entire range of initialization
|
||||
// bits if they are going to be uniformly `1` or `0`.
|
||||
if defined.ranges.len() <= 1 {
|
||||
self.init_mask.set_range_inbounds(
|
||||
dest.offset,
|
||||
|
@ -58,7 +58,7 @@ impl<'tcx> ConstValue<'tcx> {
|
||||
|
||||
pub fn try_to_str_slice(&self) -> Option<&'tcx str> {
|
||||
if let ConstValue::Slice { data, start, end } = *self {
|
||||
::std::str::from_utf8(data.inspect_with_undef_and_ptr_outside_interpreter(start..end))
|
||||
::std::str::from_utf8(data.inspect_with_uninit_and_ptr_outside_interpreter(start..end))
|
||||
.ok()
|
||||
} else {
|
||||
None
|
||||
|
@ -1107,7 +1107,7 @@ pub trait PrettyPrinter<'tcx>:
|
||||
// The `inspect` here is okay since we checked the bounds, and there are
|
||||
// no relocations (we have an active slice reference here). We don't use
|
||||
// this result to affect interpreter execution.
|
||||
let byte_str = data.inspect_with_undef_and_ptr_outside_interpreter(start..end);
|
||||
let byte_str = data.inspect_with_uninit_and_ptr_outside_interpreter(start..end);
|
||||
self.pretty_print_byte_str(byte_str)
|
||||
}
|
||||
(
|
||||
@ -1117,7 +1117,7 @@ pub trait PrettyPrinter<'tcx>:
|
||||
// The `inspect` here is okay since we checked the bounds, and there are no
|
||||
// relocations (we have an active `str` reference here). We don't use this
|
||||
// result to affect interpreter execution.
|
||||
let slice = data.inspect_with_undef_and_ptr_outside_interpreter(start..end);
|
||||
let slice = data.inspect_with_uninit_and_ptr_outside_interpreter(start..end);
|
||||
let s = ::std::str::from_utf8(slice).expect("non utf8 str from miri");
|
||||
p!(write("{:?}", s));
|
||||
Ok(self)
|
||||
|
@ -301,6 +301,15 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn after_stack_push(ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> {
|
||||
// Enforce stack size limit.
|
||||
if !ecx.tcx.sess.recursion_limit().value_within_limit(ecx.stack().len()) {
|
||||
throw_exhaust!(StackFrameLimitReached)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn stack(
|
||||
ecx: &'a InterpCx<'mir, 'tcx, Self>,
|
||||
|
@ -687,11 +687,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||
M::after_stack_push(self)?;
|
||||
info!("ENTERING({}) {}", self.frame_idx(), self.frame().instance);
|
||||
|
||||
if !self.tcx.sess.recursion_limit().value_within_limit(self.stack().len()) {
|
||||
throw_exhaust!(StackFrameLimitReached)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Jump to the given block.
|
||||
|
@ -926,7 +926,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
|
||||
// first copy the relocations to a temporary buffer, because
|
||||
// `get_bytes_mut` will clear the relocations, which is correct,
|
||||
// since we don't want to keep any relocations at the target.
|
||||
// (`get_bytes_with_undef_and_ptr` below checks that there are no
|
||||
// (`get_bytes_with_uninit_and_ptr` below checks that there are no
|
||||
// relocations overlapping the edges; those would not be handled correctly).
|
||||
let relocations =
|
||||
self.get_raw(src.alloc_id)?.prepare_relocation_copy(self, src, size, dest, length);
|
||||
@ -935,7 +935,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
|
||||
|
||||
// This checks relocation edges on the src.
|
||||
let src_bytes =
|
||||
self.get_raw(src.alloc_id)?.get_bytes_with_undef_and_ptr(&tcx, src, size)?.as_ptr();
|
||||
self.get_raw(src.alloc_id)?.get_bytes_with_uninit_and_ptr(&tcx, src, size)?.as_ptr();
|
||||
let dest_bytes =
|
||||
self.get_raw_mut(dest.alloc_id)?.get_bytes_mut(&tcx, dest, size * length)?; // `Size` multiplication
|
||||
|
||||
@ -948,7 +948,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
|
||||
let dest_bytes = dest_bytes.as_mut_ptr();
|
||||
|
||||
// Prepare a copy of the initialization mask.
|
||||
let compressed = self.get_raw(src.alloc_id)?.compress_undef_range(src, size);
|
||||
let compressed = self.get_raw(src.alloc_id)?.compress_uninit_range(src, size);
|
||||
|
||||
if compressed.no_bytes_init() {
|
||||
// Fast path: If all bytes are `uninit` then there is nothing to copy. The target range
|
||||
|
@ -106,7 +106,7 @@ impl<Tag: Copy> std::fmt::Display for ImmTy<'tcx, Tag> {
|
||||
}
|
||||
ScalarMaybeUninit::Uninit => cx.typed_value(
|
||||
|mut this| {
|
||||
this.write_str("{undef ")?;
|
||||
this.write_str("{uninit ")?;
|
||||
Ok(this)
|
||||
},
|
||||
|this| this.print_type(ty),
|
||||
|
@ -61,7 +61,7 @@ impl<Tag> MemPlaceMeta<Tag> {
|
||||
pub struct MemPlace<Tag = ()> {
|
||||
/// A place may have an integral pointer for ZSTs, and since it might
|
||||
/// be turned back into a reference before ever being dereferenced.
|
||||
/// However, it may never be undef.
|
||||
/// However, it may never be uninit.
|
||||
pub ptr: Scalar<Tag>,
|
||||
pub align: Align,
|
||||
/// Metadata for unsized places. Interpretation is up to the type.
|
||||
@ -729,7 +729,7 @@ where
|
||||
"Size mismatch when writing bits"
|
||||
)
|
||||
}
|
||||
Immediate::Scalar(ScalarMaybeUninit::Uninit) => {} // undef can have any size
|
||||
Immediate::Scalar(ScalarMaybeUninit::Uninit) => {} // uninit can have any size
|
||||
Immediate::ScalarPair(_, _) => {
|
||||
// FIXME: Can we check anything here?
|
||||
}
|
||||
|
@ -508,12 +508,12 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
||||
}
|
||||
} else {
|
||||
// At run-time, for now, we accept *anything* for these types, including
|
||||
// undef. We should fix that, but let's start low.
|
||||
// uninit. We should fix that, but let's start low.
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
ty::RawPtr(..) => {
|
||||
// We are conservative with undef for integers, but try to
|
||||
// We are conservative with uninit for integers, but try to
|
||||
// actually enforce the strict rules for raw pointers (mostly because
|
||||
// that lets us re-use `ref_to_mplace`).
|
||||
let place = try_validation!(
|
||||
@ -807,12 +807,12 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
|
||||
// reject it. However, that's good: We don't inherently want
|
||||
// to reject those pointers, we just do not have the machinery to
|
||||
// talk about parts of a pointer.
|
||||
// We also accept undef, for consistency with the slow path.
|
||||
// We also accept uninit, for consistency with the slow path.
|
||||
match self.ecx.memory.get_raw(ptr.alloc_id)?.check_bytes(
|
||||
self.ecx,
|
||||
ptr,
|
||||
size,
|
||||
/*allow_ptr_and_undef*/ self.ref_tracking_for_consts.is_none(),
|
||||
/*allow_uninit_and_ptr*/ self.ref_tracking_for_consts.is_none(),
|
||||
) {
|
||||
// In the happy case, we needn't check anything else.
|
||||
Ok(()) => {}
|
||||
|
@ -269,15 +269,21 @@ impl<'a, 'tcx> TypeVisitor<'tcx> for UsedGenericParametersVisitor<'a, 'tcx> {
|
||||
self.unused_parameters.clear(param.index);
|
||||
false
|
||||
}
|
||||
ty::ConstKind::Unevaluated(_, _, Some(p)) => {
|
||||
ty::ConstKind::Unevaluated(def, _, Some(p))
|
||||
// Avoid considering `T` unused when constants are of the form:
|
||||
// `<Self as Foo<T>>::foo::promoted[p]`
|
||||
if self.def_id == def.did && !self.tcx.generics_of(def.did).has_self =>
|
||||
{
|
||||
// If there is a promoted, don't look at the substs - since it will always contain
|
||||
// the generic parameters, instead, traverse the promoted MIR.
|
||||
let promoted = self.tcx.promoted_mir(self.def_id);
|
||||
let promoted = self.tcx.promoted_mir(def.did);
|
||||
self.visit_body(&promoted[p]);
|
||||
false
|
||||
}
|
||||
ty::ConstKind::Unevaluated(def_id, unevaluated_substs, None) => {
|
||||
self.visit_child_body(def_id.did, unevaluated_substs);
|
||||
ty::ConstKind::Unevaluated(def, unevaluated_substs, None)
|
||||
if self.tcx.def_kind(def.did) == DefKind::AnonConst =>
|
||||
{
|
||||
self.visit_child_body(def.did, unevaluated_substs);
|
||||
false
|
||||
}
|
||||
_ => c.super_visit_with(self),
|
||||
|
@ -1076,7 +1076,7 @@ impl<'mir, 'tcx> MutVisitor<'tcx> for ConstPropagator<'mir, 'tcx> {
|
||||
// ```rust
|
||||
// let mut x = 42;
|
||||
// x = SOME_MUTABLE_STATIC;
|
||||
// // x must now be undefined
|
||||
// // x must now be uninit
|
||||
// ```
|
||||
// FIXME: we overzealously erase the entire local, because that's easier to
|
||||
// implement.
|
||||
|
@ -743,8 +743,8 @@ fn write_allocation_bytes<Tag: Copy + Debug, Extra>(
|
||||
if let Some(&(tag, target_id)) = alloc.relocations().get(&i) {
|
||||
// Memory with a relocation must be defined
|
||||
let j = i.bytes_usize();
|
||||
let offset =
|
||||
alloc.inspect_with_undef_and_ptr_outside_interpreter(j..j + ptr_size.bytes_usize());
|
||||
let offset = alloc
|
||||
.inspect_with_uninit_and_ptr_outside_interpreter(j..j + ptr_size.bytes_usize());
|
||||
let offset = read_target_uint(tcx.data_layout.endian, offset).unwrap();
|
||||
let offset = Size::from_bytes(offset);
|
||||
let relocation_width = |bytes| bytes * 3;
|
||||
@ -803,7 +803,7 @@ fn write_allocation_bytes<Tag: Copy + Debug, Extra>(
|
||||
|
||||
// Checked definedness (and thus range) and relocations. This access also doesn't
|
||||
// influence interpreter execution but is only for debugging.
|
||||
let c = alloc.inspect_with_undef_and_ptr_outside_interpreter(j..j + 1)[0];
|
||||
let c = alloc.inspect_with_uninit_and_ptr_outside_interpreter(j..j + 1)[0];
|
||||
write!(w, "{:02x}", c)?;
|
||||
if c.is_ascii_control() || c >= 0x80 {
|
||||
ascii.push('.');
|
||||
|
@ -392,15 +392,15 @@ impl<'a> LateResolutionVisitor<'a, '_, '_> {
|
||||
/// return the span of whole call and the span for all arguments expect the first one (`self`).
|
||||
fn call_has_self_arg(&self, source: PathSource<'_>) -> Option<(Span, Option<Span>)> {
|
||||
let mut has_self_arg = None;
|
||||
if let PathSource::Expr(parent) = source {
|
||||
match &parent?.kind {
|
||||
if let PathSource::Expr(Some(parent)) = source {
|
||||
match &parent.kind {
|
||||
ExprKind::Call(_, args) if !args.is_empty() => {
|
||||
let mut expr_kind = &args[0].kind;
|
||||
loop {
|
||||
match expr_kind {
|
||||
ExprKind::Path(_, arg_name) if arg_name.segments.len() == 1 => {
|
||||
if arg_name.segments[0].ident.name == kw::SelfLower {
|
||||
let call_span = parent.unwrap().span;
|
||||
let call_span = parent.span;
|
||||
let tail_args_span = if args.len() > 1 {
|
||||
Some(Span::new(
|
||||
args[1].span.lo(),
|
||||
|
@ -1229,8 +1229,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
|
||||
// we don't want to throw `E0027` in case we have thrown `E0026` for them
|
||||
unmentioned_fields.retain(|&x| x.name != suggested_name);
|
||||
// When we have a tuple struct used with struct we don't want to suggest using
|
||||
// the (valid) struct syntax with numeric field names. Instead we want to
|
||||
// suggest the expected syntax. We infer that this is the case by parsing the
|
||||
// `Ident` into an unsized integer. The suggestion will be emitted elsewhere in
|
||||
// `smart_resolve_context_dependent_help`.
|
||||
if suggested_name.to_ident_string().parse::<usize>().is_err() {
|
||||
// We don't want to throw `E0027` in case we have thrown `E0026` for them.
|
||||
unmentioned_fields.retain(|&x| x.name != suggested_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1903,23 +1903,41 @@ fn document_non_exhaustive(w: &mut Buffer, item: &clean::Item) {
|
||||
}
|
||||
}
|
||||
|
||||
fn name_key(name: &str) -> (&str, u64, usize) {
|
||||
let end = name.bytes().rposition(|b| b.is_ascii_digit()).map_or(name.len(), |i| i + 1);
|
||||
|
||||
// find number at end
|
||||
let split = name[0..end].bytes().rposition(|b| !b.is_ascii_digit()).map_or(0, |i| i + 1);
|
||||
|
||||
// count leading zeroes
|
||||
let after_zeroes =
|
||||
name[split..end].bytes().position(|b| b != b'0').map_or(name.len(), |extra| split + extra);
|
||||
|
||||
// sort leading zeroes last
|
||||
let num_zeroes = after_zeroes - split;
|
||||
|
||||
match name[split..end].parse() {
|
||||
Ok(n) => (&name[..split], n, num_zeroes),
|
||||
Err(_) => (name, 0, num_zeroes),
|
||||
/// Compare two strings treating multi-digit numbers as single units (i.e. natural sort order).
|
||||
pub fn compare_names(mut lhs: &str, mut rhs: &str) -> Ordering {
|
||||
/// Takes a non-numeric and a numeric part from the given &str.
|
||||
fn take_parts<'a>(s: &mut &'a str) -> (&'a str, &'a str) {
|
||||
let i = s.find(|c: char| c.is_ascii_digit());
|
||||
let (a, b) = s.split_at(i.unwrap_or(s.len()));
|
||||
let i = b.find(|c: char| !c.is_ascii_digit());
|
||||
let (b, c) = b.split_at(i.unwrap_or(b.len()));
|
||||
*s = c;
|
||||
(a, b)
|
||||
}
|
||||
|
||||
while !lhs.is_empty() || !rhs.is_empty() {
|
||||
let (la, lb) = take_parts(&mut lhs);
|
||||
let (ra, rb) = take_parts(&mut rhs);
|
||||
// First process the non-numeric part.
|
||||
match la.cmp(ra) {
|
||||
Ordering::Equal => (),
|
||||
x => return x,
|
||||
}
|
||||
// Then process the numeric part, if both sides have one (and they fit in a u64).
|
||||
if let (Ok(ln), Ok(rn)) = (lb.parse::<u64>(), rb.parse::<u64>()) {
|
||||
match ln.cmp(&rn) {
|
||||
Ordering::Equal => (),
|
||||
x => return x,
|
||||
}
|
||||
}
|
||||
// Then process the numeric part again, but this time as strings.
|
||||
match lb.cmp(rb) {
|
||||
Ordering::Equal => (),
|
||||
x => return x,
|
||||
}
|
||||
}
|
||||
|
||||
Ordering::Equal
|
||||
}
|
||||
|
||||
fn item_module(w: &mut Buffer, cx: &Context, item: &clean::Item, items: &[clean::Item]) {
|
||||
@ -1962,7 +1980,7 @@ fn item_module(w: &mut Buffer, cx: &Context, item: &clean::Item, items: &[clean:
|
||||
}
|
||||
let lhs = i1.name.as_ref().map_or("", |s| &**s);
|
||||
let rhs = i2.name.as_ref().map_or("", |s| &**s);
|
||||
name_key(lhs).cmp(&name_key(rhs))
|
||||
compare_names(lhs, rhs)
|
||||
}
|
||||
|
||||
if cx.shared.sort_modules_alphabetically {
|
||||
@ -2395,7 +2413,7 @@ fn compare_impl<'a, 'b>(lhs: &'a &&Impl, rhs: &'b &&Impl) -> Ordering {
|
||||
let rhs = format!("{}", rhs.inner_impl().print());
|
||||
|
||||
// lhs and rhs are formatted as HTML, which may be unnecessary
|
||||
name_key(&lhs).cmp(&name_key(&rhs))
|
||||
compare_names(&lhs, &rhs)
|
||||
}
|
||||
|
||||
fn item_trait(w: &mut Buffer, cx: &Context, it: &clean::Item, t: &clean::Trait, cache: &Cache) {
|
||||
|
@ -1,24 +1,40 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_name_key() {
|
||||
assert_eq!(name_key("0"), ("", 0, 1));
|
||||
assert_eq!(name_key("123"), ("", 123, 0));
|
||||
assert_eq!(name_key("Fruit"), ("Fruit", 0, 0));
|
||||
assert_eq!(name_key("Fruit0"), ("Fruit", 0, 1));
|
||||
assert_eq!(name_key("Fruit0000"), ("Fruit", 0, 4));
|
||||
assert_eq!(name_key("Fruit01"), ("Fruit", 1, 1));
|
||||
assert_eq!(name_key("Fruit10"), ("Fruit", 10, 0));
|
||||
assert_eq!(name_key("Fruit123"), ("Fruit", 123, 0));
|
||||
fn test_compare_names() {
|
||||
for &(a, b) in &[
|
||||
("hello", "world"),
|
||||
("", "world"),
|
||||
("123", "hello"),
|
||||
("123", ""),
|
||||
("123test", "123"),
|
||||
("hello", ""),
|
||||
("hello", "hello"),
|
||||
("hello123", "hello123"),
|
||||
("hello123", "hello12"),
|
||||
("hello12", "hello123"),
|
||||
("hello01abc", "hello01xyz"),
|
||||
("hello0abc", "hello0"),
|
||||
("hello0", "hello0abc"),
|
||||
("01", "1"),
|
||||
] {
|
||||
assert_eq!(compare_names(a, b), a.cmp(b), "{:?} - {:?}", a, b);
|
||||
}
|
||||
assert_eq!(compare_names("u8", "u16"), Ordering::Less);
|
||||
assert_eq!(compare_names("u32", "u16"), Ordering::Greater);
|
||||
assert_eq!(compare_names("u8_to_f64", "u16_to_f64"), Ordering::Less);
|
||||
assert_eq!(compare_names("u32_to_f64", "u16_to_f64"), Ordering::Greater);
|
||||
assert_eq!(compare_names("u16_to_f64", "u16_to_f64"), Ordering::Equal);
|
||||
assert_eq!(compare_names("u16_to_f32", "u16_to_f64"), Ordering::Less);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_name_sorting() {
|
||||
let names = [
|
||||
"Apple", "Banana", "Fruit", "Fruit0", "Fruit00", "Fruit1", "Fruit01", "Fruit2", "Fruit02",
|
||||
"Apple", "Banana", "Fruit", "Fruit0", "Fruit00", "Fruit01", "Fruit1", "Fruit02", "Fruit2",
|
||||
"Fruit20", "Fruit30x", "Fruit100", "Pear",
|
||||
];
|
||||
let mut sorted = names.to_owned();
|
||||
sorted.sort_by_key(|&s| name_key(s));
|
||||
sorted.sort_by(|&l, r| compare_names(l, r));
|
||||
assert_eq!(names, sorted);
|
||||
}
|
||||
|
@ -10,6 +10,6 @@ struct A<const N: usize>;
|
||||
|
||||
fn main() {
|
||||
let _: A<7>; // ok
|
||||
let _: A< 7 >; //~ WARN unnecessary braces
|
||||
let _: A<7>; //~ WARN unnecessary braces
|
||||
let _: A<{ 3 + 5 }>; // ok
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ enum MyOption<T> {
|
||||
fn main() {
|
||||
match MyOption::MySome(42) {
|
||||
MyOption::MySome { x: 42 } => (),
|
||||
//~^ ERROR variant `MyOption::MySome` does not have a field named `x`
|
||||
//~^ ERROR tuple variant `MyOption::MySome` written as struct variant
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,9 @@
|
||||
error[E0026]: variant `MyOption::MySome` does not have a field named `x`
|
||||
--> $DIR/issue-17800.rs:8:28
|
||||
error[E0769]: tuple variant `MyOption::MySome` written as struct variant
|
||||
--> $DIR/issue-17800.rs:8:9
|
||||
|
|
||||
LL | MyOption::MySome { x: 42 } => (),
|
||||
| ^
|
||||
| |
|
||||
| variant `MyOption::MySome` does not have this field
|
||||
| help: a field with a similar name exists: `0`
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the tuple variant pattern syntax instead: `MyOption::MySome(42)`
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
For more information about this error, try `rustc --explain E0026`.
|
||||
For more information about this error, try `rustc --explain E0769`.
|
||||
|
@ -23,18 +23,18 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
if true {
|
||||
if true {
|
||||
//~^ WARN unnecessary braces
|
||||
}
|
||||
|
||||
while false {
|
||||
while false {
|
||||
//~^ WARN unnecessary braces
|
||||
}
|
||||
|
||||
let _: [u8; 3 ];
|
||||
let _: [u8; 3];
|
||||
//~^ WARN unnecessary braces
|
||||
|
||||
consume( 7 );
|
||||
consume(7);
|
||||
//~^ WARN unnecessary braces
|
||||
|
||||
// Do not emit lint for multiline blocks.
|
||||
|
@ -21,6 +21,6 @@ fn main() {
|
||||
};
|
||||
|
||||
consume(&{ a.b });
|
||||
consume( a.b );
|
||||
consume(a.b);
|
||||
//~^ WARN unnecessary braces
|
||||
}
|
||||
|
14
src/test/ui/polymorphization/promoted-function-3.rs
Normal file
14
src/test/ui/polymorphization/promoted-function-3.rs
Normal file
@ -0,0 +1,14 @@
|
||||
// run-pass
|
||||
// compile-flags: -Zpolymorphize=on -Zmir-opt-level=3
|
||||
|
||||
fn caller<T, U>() -> &'static usize {
|
||||
callee::<U>()
|
||||
}
|
||||
|
||||
fn callee<T>() -> &'static usize {
|
||||
&std::mem::size_of::<T>()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
assert_eq!(caller::<(), ()>(), &0);
|
||||
}
|
@ -11,7 +11,7 @@ fn main() {
|
||||
consume(try {});
|
||||
//~^ WARN unnecessary parentheses
|
||||
|
||||
consume( try {} );
|
||||
consume(try {});
|
||||
//~^ WARN unnecessary braces
|
||||
|
||||
match try {} {
|
||||
|
@ -517,7 +517,7 @@ pub fn miri_to_const(result: &ty::Const<'_>) -> Option<Constant> {
|
||||
ty::ConstKind::Value(ConstValue::Slice { data, start, end }) => match result.ty.kind {
|
||||
ty::Ref(_, tam, _) => match tam.kind {
|
||||
ty::Str => String::from_utf8(
|
||||
data.inspect_with_undef_and_ptr_outside_interpreter(start..end)
|
||||
data.inspect_with_uninit_and_ptr_outside_interpreter(start..end)
|
||||
.to_owned(),
|
||||
)
|
||||
.ok()
|
||||
@ -530,7 +530,7 @@ pub fn miri_to_const(result: &ty::Const<'_>) -> Option<Constant> {
|
||||
ty::Array(sub_type, len) => match sub_type.kind {
|
||||
ty::Float(FloatTy::F32) => match miri_to_const(len) {
|
||||
Some(Constant::Int(len)) => alloc
|
||||
.inspect_with_undef_and_ptr_outside_interpreter(0..(4 * len as usize))
|
||||
.inspect_with_uninit_and_ptr_outside_interpreter(0..(4 * len as usize))
|
||||
.to_owned()
|
||||
.chunks(4)
|
||||
.map(|chunk| {
|
||||
@ -544,7 +544,7 @@ pub fn miri_to_const(result: &ty::Const<'_>) -> Option<Constant> {
|
||||
},
|
||||
ty::Float(FloatTy::F64) => match miri_to_const(len) {
|
||||
Some(Constant::Int(len)) => alloc
|
||||
.inspect_with_undef_and_ptr_outside_interpreter(0..(8 * len as usize))
|
||||
.inspect_with_uninit_and_ptr_outside_interpreter(0..(8 * len as usize))
|
||||
.to_owned()
|
||||
.chunks(8)
|
||||
.map(|chunk| {
|
||||
|
Loading…
Reference in New Issue
Block a user