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}