vectorscan/sources.rs
1/* Copyright 2022-2024 Danny McClanahan */
2/* SPDX-License-Identifier: BSD-3-Clause */
3
4//! Wrappers for types of string data which can be searched and indexed.
5//!
6//! These objects are used to provide string data for inputs to search functions
7//! like [`scan_sync()`](crate::state::Scratch::scan_sync), and subsets of those
8//! arguments are received as outputs by match callbacks to produce match info
9//! such as [`Match`](crate::matchers::Match).
10
11use std::{
12 fmt, ops,
13 os::raw::{c_char, c_uint, c_ulonglong},
14 slice, str,
15};
16
17/// A [`slice`](prim@slice) of [`u8`] with an associated lifetime.
18///
19/// This is currently implemented as
20/// a [`#[repr(transparent)]`](https://doc.rust-lang.org/nomicon/other-reprs.html#reprtransparent)
21/// wrapper over `&'a [u8]`.
22#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
23#[repr(transparent)]
24pub struct ByteSlice<'a>(&'a [u8]);
25
26impl<'a> fmt::Debug for ByteSlice<'a> {
27 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
28 let b = self.as_slice();
29 match str::from_utf8(b) {
30 Ok(s) => write!(f, "ByteSlice({:?})", s),
31 Err(_) => write!(f, "ByteSlice(non-utf8: {:?})", b),
32 }
33 }
34}
35
36impl<'a> fmt::Display for ByteSlice<'a> {
37 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
38 let b = self.as_slice();
39 match str::from_utf8(b) {
40 Ok(s) => write!(f, "{}", s),
41 Err(_) => write!(f, "(non-utf8: {:?})", b),
42 }
43 }
44}
45
46/// # Byte-Oriented Interface
47/// Vectorscan can search over arbitrary byte patterns in any encoding, so
48/// [`Self::from_slice()`] and [`Self::as_slice()`] offer the most general
49/// byte-oriented interface. This may be particularly useful when matching
50/// against non-UTF8 data, possibly with [`Literal`](crate::expression::Literal)
51/// pattern strings (although non-literal patterns can also be used to
52/// match non-UTF8 data).
53///
54/// [`From`] implementations are also provided to convert from references to
55/// native [arrays](prim@array) and [slices](prim@slice):
56///
57///```
58/// use vectorscan::sources::ByteSlice;
59///
60/// let b1 = ByteSlice::from_slice(b"asdf");
61/// let b2: ByteSlice = b"asdf".into();
62/// let b3: ByteSlice = [b'a', b's', b'd', b'f'].as_ref().into();
63/// assert_eq!(b1, b2);
64/// assert_eq!(b2, b3);
65/// assert_eq!(b1.as_slice(), b"asdf");
66/// ```
67///
68/// Note however that a [`From`] implementation is not provided to convert from
69/// an array `[u8; N]` by value, as this wrapper requires a lifetime to
70/// associate the data with, even if it's just the local `'_` lifetime or the
71/// global `'static` lifetime.
72impl<'a> ByteSlice<'a> {
73 /// Wrap a byte slice so it can be used by vectorscan.
74 ///
75 /// This method is [`const`](https://doc.rust-lang.org/std/keyword.const.html) so it can be used
76 /// to define `const` values as well as
77 /// [`static`](https://doc.rust-lang.org/std/keyword.static.html) initializers.
78 pub const fn from_slice(data: &'a [u8]) -> Self { Self(data) }
79
80 /// Extract the byte slice.
81 ///
82 /// A [slice](prim@slice) can be split into a pointer/length pair which is
83 /// consumed by vectorscan's underlying C ABI.
84 pub const fn as_slice(&self) -> &'a [u8] { self.0 }
85
86 pub(crate) const fn as_ptr(&self) -> *const c_char { self.as_slice().as_ptr() as *const c_char }
87
88 pub(crate) const fn native_len(&self) -> c_uint { self.as_slice().len() as c_uint }
89}
90
91impl<'a> From<&'a [u8]> for ByteSlice<'a> {
92 fn from(x: &'a [u8]) -> Self { Self::from_slice(x) }
93}
94
95impl<'a, const N: usize> From<&'a [u8; N]> for ByteSlice<'a> {
96 fn from(x: &'a [u8; N]) -> Self { Self::from_slice(x.as_ref()) }
97}
98
99/// # String-Oriented Interface
100/// When vectorscan is being used with UTF8-encoded inputs (e.g. with
101/// [`Self::from_str()`]), it will produce UTF8 encoded match outputs, and
102/// [`Self::as_str()`] can be invoked safely on match results.
103///
104/// A [`From`] implementation is also provided to convert from a native
105/// [`str`](prim@str):
106///
107///```
108/// use vectorscan::sources::ByteSlice;
109///
110/// let b1 = ByteSlice::from_str("asdf");
111/// let b2: ByteSlice = "asdf".into();
112/// assert_eq!(b1, b2);
113/// assert_eq!(unsafe { b1.as_str() }, "asdf");
114/// ```
115///
116/// ## The `UTF8` Flag
117/// It is important to note that vectorscan itself does not assume any
118/// particular string encoding, and the function of e.g.
119/// [`Flags::UTF8`](crate::flags::Flags::UTF8) is to determine which bytes
120/// should be included in the state machine, *not* the encoding of any
121/// particular input. This means that the UTF8 flag may be disabled for UTF8
122/// inputs to produce a much smaller state machine (as it is when using
123/// [`Flags::default()`](crate::flags::Flags::default)). Note however that
124/// enabling the UTF8 flag for non-UTF8 inputs produces undefined behavior.
125impl<'a> ByteSlice<'a> {
126 /// Wrap a UTF8-encoded byte slice so it can be used by vectorscan.
127 ///
128 /// As with [`Self::from_slice()`], this method is `const` and can produce
129 /// `const` values or `static` initializers.
130 pub const fn from_str(data: &'a str) -> Self { Self::from_slice(data.as_bytes()) }
131
132 /// Extract the byte slice, and assert that it is correctly UTF8-encoded.
133 ///
134 /// # Safety
135 /// This method passes the result of [`Self::as_slice()`] to
136 /// [`str::from_utf8_unchecked()`] in order to avoid the overhead of
137 /// repeatedly validating the underlying string data in the common case
138 /// where all strings are UTF-8. Where this is not certain, the slice may be
139 /// provided to methods such as [`str::from_utf8()`] or
140 /// [`String::from_utf8_lossy()`] that check for UTF-8 validity:
141 ///
142 ///```
143 /// use vectorscan::sources::ByteSlice;
144 /// use std::{borrow::Cow, str};
145 ///
146 /// // All-or-nothing UTF8 conversion with error:
147 /// let b = ByteSlice::from_slice(b"asdf");
148 /// let s = str::from_utf8(b.as_slice()).unwrap();
149 /// assert_eq!(s, "asdf");
150 ///
151 /// // Error-coercing UTF8 conversion which replaces invalid characters:
152 /// let b = ByteSlice::from_slice(b"Hello \xF0\x90\x80World");
153 /// let s: Cow<'_, str> = String::from_utf8_lossy(b.as_slice());
154 /// assert_eq!(s, "Hello �World");
155 /// ```
156 pub const unsafe fn as_str(&self) -> &'a str { str::from_utf8_unchecked(self.as_slice()) }
157}
158
159impl<'a> From<&'a str> for ByteSlice<'a> {
160 fn from(x: &'a str) -> Self { Self::from_str(x) }
161}
162
163/// # Subsetting
164/// Match callbacks return subsets of the input argument. These methods apply a
165/// fallible subsetting operation which is used to convert match offsets to
166/// substrings.
167impl<'a> ByteSlice<'a> {
168 /// Return a subset of the input, or [`None`] if the result would be out of
169 /// range:
170 ///
171 ///```
172 /// use vectorscan::sources::ByteSlice;
173 ///
174 /// let b: ByteSlice = "asdf".into();
175 /// let b2 = b.index_range(0..2).unwrap();
176 /// assert_eq!(unsafe { b2.as_str() }, "as");
177 /// assert!(b.index_range(0..5).is_none());
178 /// ```
179 ///
180 /// This method is largely intended for internal use inside this library, but
181 /// is exposed in the public API to make it clear how the match callback
182 /// converts match offsets to substrings of the original input data.
183 pub fn index_range(&self, range: impl slice::SliceIndex<[u8], Output=[u8]>) -> Option<Self> {
184 self.as_slice().get(range).map(Self::from_slice)
185 }
186}
187
188#[cfg(feature = "vectored")]
189pub(crate) mod vectored {
190 use super::{ByteSlice, Range};
191
192 use std::{
193 cmp, mem, ops,
194 os::raw::{c_char, c_uint},
195 slice,
196 };
197
198 /// A [`slice`](prim@slice) of [`ByteSlice`].
199 ///
200 /// This struct wraps non-contiguous slices of string data to pass to the
201 /// [`scan_sync_vectored()`](crate::state::Scratch::scan_sync_vectored) search
202 /// method, which produces match results of type
203 /// [`VectoredMatch`](crate::matchers::VectoredMatch)
204 /// pointing to a subset of the original data.
205 ///
206 /// This is currently implemented as
207 /// a [`#[repr(transparent)]`](https://doc.rust-lang.org/nomicon/other-reprs.html#reprtransparent)
208 /// wrapper over `&'slice [ByteSlice<'string>]`.
209 #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
210 #[repr(transparent)]
211 pub struct VectoredByteSlices<'string, 'slice>(&'slice [ByteSlice<'string>]);
212
213 /// # Byte-Oriented Interface
214 /// Because vectorscan only partially supports vectored string inputs, this
215 /// library does not attempt to provide a UTF8-encoded [`str`](prim@str)
216 /// interface for vectored strings as with [`ByteSlice`].
217 ///
218 /// However, [`From`] implementations are still provided to convert from
219 /// references to [arrays](prim@array) and [slices](prim@slice):
220 ///
221 ///```
222 /// use vectorscan::sources::VectoredByteSlices;
223 ///
224 /// let b1 = b"asdf";
225 /// let b2 = b"bbbb";
226 /// let bb = [b1.into(), b2.into()];
227 /// let bs = VectoredByteSlices::from_slices(&bb);
228 /// let bb2 = [b1.as_ref(), b2.as_ref()];
229 /// let bs2: VectoredByteSlices = bb2.as_ref().into();
230 /// assert_eq!(bs, bs2);
231 /// ```
232 impl<'string, 'slice> VectoredByteSlices<'string, 'slice> {
233 /// Wrap a slice of byte slices so they can be scanned in vectored mode.
234 ///
235 /// Like [`ByteSlice::from_slice()`], this method is `const` so it can be
236 /// used in `const` values and `static` initializers.
237 pub const fn from_slices(data: &'slice [ByteSlice<'string>]) -> Self { Self(data) }
238
239 #[cfg(feature = "vectored")]
240 pub(crate) fn from_io_slices(
241 cache: &'slice mut Vec<mem::MaybeUninit<ByteSlice<'static>>>,
242 bufs: &'string [std::io::IoSlice<'string>],
243 ) -> Self {
244 cache.clear();
245 cache.reserve(bufs.len());
246 unsafe {
247 cache.set_len(bufs.len());
248 }
249 for (out_slice, in_slice) in cache.iter_mut().zip(bufs.iter()) {
250 let in_slice: &'static std::io::IoSlice<'static> = unsafe { mem::transmute(in_slice) };
251 out_slice.write(ByteSlice::from_slice(in_slice));
252 }
253 let bufs: &'slice [ByteSlice<'string>] = unsafe { mem::transmute(&cache[..]) };
254 Self::from_slices(bufs)
255 }
256
257 /// Return the sum of all lengths of all slices.
258 pub fn length_sum(&self) -> usize { self.0.iter().map(|s| s.as_slice().len()).sum() }
259
260 pub(crate) fn pointers_and_lengths(&self) -> (Vec<*const c_char>, Vec<c_uint>) {
261 let lengths: Vec<c_uint> = self.0.iter().map(|col| col.native_len()).collect();
262 let data_pointers: Vec<*const c_char> = self.0.iter().map(|col| col.as_ptr()).collect();
263 (data_pointers, lengths)
264 }
265
266 pub(crate) const fn native_len(&self) -> c_uint { self.0.len() as c_uint }
267 }
268
269 impl<'string, 'slice> From<&'slice [ByteSlice<'string>]> for VectoredByteSlices<'string, 'slice> {
270 fn from(x: &'slice [ByteSlice<'string>]) -> Self { Self::from_slices(x) }
271 }
272
273 impl<'string, 'slice, const N: usize> From<&'slice [ByteSlice<'string>; N]>
274 for VectoredByteSlices<'string, 'slice>
275 {
276 fn from(x: &'slice [ByteSlice<'string>; N]) -> Self { Self::from_slices(x.as_ref()) }
277 }
278
279 impl<'string, 'slice> From<&'slice [&'string [u8]]> for VectoredByteSlices<'string, 'slice> {
280 fn from(x: &'slice [&'string [u8]]) -> Self {
281 let x: &'slice [ByteSlice<'string>] = unsafe { mem::transmute(x) };
282 Self(x)
283 }
284 }
285
286 impl<'string, 'slice, const N: usize> From<&'slice [&'string [u8]; N]>
287 for VectoredByteSlices<'string, 'slice>
288 {
289 fn from(x: &'slice [&'string [u8]; N]) -> Self {
290 let x: &'slice [ByteSlice<'string>; N] = unsafe { mem::transmute(x) };
291 x.into()
292 }
293 }
294
295 /// # Ownership and Indexing
296 /// Keeping track of a subset of vectored strings
297 /// requires some more work than for [`ByteSlice`], since a subset of vectored
298 /// data may start or stop in the middle of a particular slice. As a result,
299 /// [`Self::index_range()`] cannot simply return `Self` and return a reference
300 /// to itself without allocating new memory the way
301 /// [`ByteSlice::index_range()`] can.
302 ///
303 /// However, it *is* possible to avoid dynamic memory allocation when
304 /// extracting subsets of vectored data; instead, [`Self::index_range()`]
305 /// returns [`VectoredSubset`], which tracks offsets within the vectored
306 /// string data without additional allocations.
307 impl<'string, 'slice> VectoredByteSlices<'string, 'slice> {
308 fn find_index_at(
309 &self,
310 mut column: usize,
311 mut within_column_index: usize,
312 mut remaining: usize,
313 ) -> Option<(usize, usize)> {
314 let num_columns = self.0.len();
315 if column >= num_columns {
316 return None;
317 }
318 if remaining == 0 {
319 return Some((column, within_column_index));
320 }
321
322 let within_column_index = loop {
323 let cur_col = &self.0[column];
324 let (prev, max_diff) = if within_column_index > 0 {
325 let x = within_column_index;
326 within_column_index = 0;
327 assert_ne!(cur_col.0.len(), x);
328 (x, cur_col.0.len() - x)
329 } else {
330 (0, cur_col.0.len())
331 };
332 assert_ne!(max_diff, 0);
333 let new_offset = cmp::min(remaining, max_diff);
334 let cur_ind = prev + new_offset;
335 remaining -= new_offset;
336 if remaining == 0 {
337 break cur_ind;
338 } else if column == (num_columns - 1) {
339 return None;
340 } else {
341 column += 1;
342 }
343 };
344
345 Some((column, within_column_index))
346 }
347
348 fn collect_slices_range(
349 &self,
350 range: Range,
351 start: (usize, usize),
352 end: (usize, usize),
353 ) -> Option<VectoredSubset<'string, 'slice>> {
354 let (start_col, start_ind) = start;
355 let (end_col, end_ind) = end;
356 assert!(end_col >= start_col);
357
358 if start_col == end_col {
359 assert!(end_ind >= start_ind);
360 let col_substring = self.0[start_col].index_range(start_ind..end_ind)?;
361 Some(VectoredSubset::from_single_slice(range, col_substring))
362 } else {
363 Some(VectoredSubset {
364 range,
365 start: Some(self.0[start_col].index_range(start_ind..)?),
366 directly_referenced: &self.0[(start_col + 1)..end_col],
367 end: Some(self.0[end_col].index_range(..end_ind)?),
368 })
369 }
370 }
371
372 /// Return a subset of the input, or [`None`] if the result would be out of
373 /// range:
374 ///
375 ///```
376 /// use vectorscan::sources::VectoredByteSlices;
377 ///
378 /// let b1 = "asdf";
379 /// let b2 = "ok";
380 /// let b3 = "bsdf";
381 /// let bb = [b1.into(), b2.into(), b3.into()];
382 /// let bs = VectoredByteSlices::from_slices(&bb);
383 ///
384 /// let sub = bs.index_range(2..8).unwrap();
385 /// let collected: Vec<&str> = sub.iter_slices().map(|s| unsafe { s.as_str() }).collect();
386 /// assert_eq!(&collected, &["df", "ok", "bs"]);
387 /// ```
388 ///
389 /// This method is largely intended for internal use inside this library,
390 /// but is exposed in the public API to make it clear how the match
391 /// callback converts match offsets to substrings of the original input
392 /// data.
393 pub fn index_range(&self, range: ops::Range<usize>) -> Option<VectoredSubset<'string, 'slice>> {
394 let ops::Range { start, end } = range.clone();
395 let range: Range = range.into();
396 let (start_col, start_ind) = self.find_index_at(0, 0, start)?;
397 let (end_col, end_ind) = self.find_index_at(start_col, start_ind, end - start)?;
398 self.collect_slices_range(range, (start_col, start_ind), (end_col, end_ind))
399 }
400
401 /// Iterate over all of the original vectored data.
402 ///
403 /// This is the corollary to [`VectoredSubset::iter_slices()`] and is mainly
404 /// intended to aid in debugging.
405 ///
406 ///```
407 /// use vectorscan::sources::VectoredByteSlices;
408 ///
409 /// let b1 = "asdf";
410 /// let b2 = "ok";
411 /// let b3 = "bbbb";
412 /// let bb = [b1.into(), b2.into(), b3.into()];
413 /// let bs = VectoredByteSlices::from_slices(&bb);
414 ///
415 /// let collected: Vec<&str> = bs.as_slices().iter().map(|s| unsafe { s.as_str() }).collect();
416 /// assert_eq!(&collected, &["asdf", "ok", "bbbb"]);
417 /// ```
418 pub fn as_slices(&self) -> &'slice [ByteSlice<'string>] { unsafe { mem::transmute(self.0) } }
419 }
420
421 /// A "ragged" subset of [`VectoredByteSlices`].
422 ///
423 /// This struct is able to reference a contiguous subset of the vectored
424 /// string data contained in a [`VectoredByteSlices`], including any
425 /// "ragged" start or end component which does not span the entirety of the
426 /// corresponding slice from the input data. This allows the match callback
427 /// provided to
428 /// [`scan_sync_vectored()`](crate::state::Scratch::scan_sync_vectored) to
429 /// receive [`VectoredMatch`](crate::matchers::VectoredMatch)
430 /// instances that reference the input data without introducing
431 /// any additional dynamic allocations.
432 #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
433 pub struct VectoredSubset<'string, 'slice> {
434 /// The offsets for the entire match, without reference to the data
435 /// slices.
436 pub range: Range,
437 start: Option<ByteSlice<'string>>,
438 directly_referenced: &'slice [ByteSlice<'string>],
439 end: Option<ByteSlice<'string>>,
440 }
441
442 impl<'string, 'slice> VectoredSubset<'string, 'slice> {
443 pub(crate) fn from_single_slice(range: Range, data: ByteSlice<'string>) -> Self {
444 Self {
445 range,
446 start: Some(data),
447 directly_referenced: &[],
448 end: None,
449 }
450 }
451
452 /// Iterate over the referenced data.
453 ///
454 ///```
455 /// use vectorscan::sources::VectoredByteSlices;
456 ///
457 /// let b1 = "asdf";
458 /// let b2 = "ok";
459 /// let b3 = "bsdf";
460 /// let bb = [b1.into(), b2.into(), b3.into()];
461 /// let bs = VectoredByteSlices::from_slices(&bb);
462 ///
463 /// let sub = bs.index_range(2..8).unwrap();
464 /// let collected: Vec<&str> = sub.iter_slices().map(|s| unsafe { s.as_str() }).collect();
465 /// assert_eq!(&collected, &["df", "ok", "bs"]);
466 /// ```
467 ///
468 /// This iterator is the only interface exposed to access the data because
469 /// "ragged" start and end components cannot be expressed as simple
470 /// subslices of the vectored data in a [`VectoredByteSlices`], so the first
471 /// and/or last iteration result must come from additional references to
472 /// ragged substrings which are also stored in this struct.
473 pub fn iter_slices(
474 &self,
475 ) -> impl ExactSizeIterator<Item=ByteSlice<'string>>+DoubleEndedIterator+'_ {
476 VectorIter::new(self)
477 }
478 }
479
480 struct VectorIter<'string, 'slice> {
481 done_start: Option<&'slice ByteSlice<'string>>,
482 done_inner: slice::Iter<'slice, ByteSlice<'string>>,
483 done_end: Option<&'slice ByteSlice<'string>>,
484 }
485
486 impl<'string, 'slice> VectorIter<'string, 'slice> {
487 pub fn new(data: &'slice VectoredSubset<'string, 'slice>) -> Self {
488 Self {
489 done_start: data.start.as_ref(),
490 done_inner: data.directly_referenced.iter(),
491 done_end: data.end.as_ref(),
492 }
493 }
494
495 fn remaining_len(&self) -> usize {
496 let mut len: usize = 0;
497 if self.done_start.is_some() {
498 len += 1;
499 }
500 len += self.done_inner.as_slice().len();
501 if self.done_end.is_some() {
502 len += 1;
503 }
504 len
505 }
506 }
507
508 impl<'string, 'slice> Iterator for VectorIter<'string, 'slice> {
509 type Item = ByteSlice<'string>;
510
511 fn next(&mut self) -> Option<Self::Item> {
512 if let Some(start) = self.done_start.take() {
513 return Some(*start);
514 }
515
516 if let Some(inner) = self.done_inner.next() {
517 return Some(*inner);
518 }
519
520 if let Some(end) = self.done_end.take() {
521 return Some(*end);
522 }
523
524 None
525 }
526
527 fn size_hint(&self) -> (usize, Option<usize>) {
528 let rem = self.remaining_len();
529 (rem, Some(rem))
530 }
531 }
532
533 impl<'string, 'slice> ExactSizeIterator for VectorIter<'string, 'slice> {}
534
535 impl<'string, 'slice> DoubleEndedIterator for VectorIter<'string, 'slice> {
536 fn next_back(&mut self) -> Option<Self::Item> {
537 if let Some(end) = self.done_end.take() {
538 return Some(*end);
539 }
540
541 if let Some(inner) = self.done_inner.next_back() {
542 return Some(*inner);
543 }
544
545 if let Some(start) = self.done_start.take() {
546 return Some(*start);
547 }
548
549 None
550 }
551 }
552}
553#[cfg(feature = "vectored")]
554#[cfg_attr(docsrs, doc(cfg(feature = "vectored")))]
555pub use vectored::{VectoredByteSlices, VectoredSubset};
556
557/// An [`ops::Range`] that is also [`Copy`].
558#[allow(missing_docs)]
559#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
560pub struct Range {
561 pub from: usize,
562 pub to: usize,
563}
564
565static_assertions::assert_eq_size!(Range, ops::Range<usize>);
566static_assertions::assert_eq_size!(usize, c_ulonglong);
567static_assertions::assert_eq_size!((c_ulonglong, c_ulonglong), ops::Range<usize>);
568
569impl Range {
570 #[allow(missing_docs)]
571 pub const fn from_range(x: ops::Range<usize>) -> Self {
572 let ops::Range { start, end } = x;
573 Self {
574 from: start,
575 to: end,
576 }
577 }
578
579 #[allow(missing_docs)]
580 pub const fn into_range(self) -> ops::Range<usize> {
581 let Self { from, to } = self;
582 from..to
583 }
584
585 /// Calculate the distance between the ends of the range.
586 pub const fn len(&self) -> usize {
587 assert!(self.to >= self.from);
588 self.to - self.from
589 }
590
591 /// Whether [`Self::len()`] is 0.
592 pub const fn is_empty(&self) -> bool { self.len() == 0 }
593}
594
595impl From<ops::Range<usize>> for Range {
596 fn from(x: ops::Range<usize>) -> Self { Self::from_range(x) }
597}
598
599impl From<Range> for ops::Range<usize> {
600 fn from(x: Range) -> Self { x.into_range() }
601}