## Summary
Setting `TY_MEMORY_REPORT=full` will generate and print a memory usage
report to the CLI after a `ty check` run:
```
=======SALSA STRUCTS=======
`Definition` metadata=7.24MB fields=17.38MB count=181062
`Expression` metadata=4.45MB fields=5.94MB count=92804
`member_lookup_with_policy_::interned_arguments` metadata=1.97MB fields=2.25MB count=35176
...
=======SALSA QUERIES=======
`File -> ty_python_semantic::semantic_index::SemanticIndex`
metadata=11.46MB fields=88.86MB count=1638
`Definition -> ty_python_semantic::types::infer::TypeInference`
metadata=24.52MB fields=86.68MB count=146018
`File -> ruff_db::parsed::ParsedModule`
metadata=0.12MB fields=69.06MB count=1642
...
=======SALSA SUMMARY=======
TOTAL MEMORY USAGE: 577.61MB
struct metadata = 29.00MB
struct fields = 35.68MB
memo metadata = 103.87MB
memo fields = 409.06MB
```
Eventually, we should integrate these numbers into CI in some form. The
one limitation currently is that heap allocations in salsa structs (e.g.
interned values) are not tracked, but memoized values should have full
coverage. We may also want a peak memory usage counter (that accounts
for non-salsa memory), but that is relatively simple to profile manually
(e.g. `time -v ty check`) and would require a compile-time option to
avoid runtime overhead.
547 lines
15 KiB
Rust
547 lines
15 KiB
Rust
use cmp::Ordering;
|
|
|
|
use {
|
|
crate::TextSize,
|
|
std::{
|
|
cmp, fmt,
|
|
ops::{Add, AddAssign, Bound, Index, IndexMut, Range, RangeBounds, Sub, SubAssign},
|
|
},
|
|
};
|
|
|
|
/// A range in text, represented as a pair of [`TextSize`][struct@TextSize].
|
|
///
|
|
/// It is a logic error for `start` to be greater than `end`.
|
|
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
|
|
#[cfg_attr(feature = "get-size", derive(get_size2::GetSize))]
|
|
pub struct TextRange {
|
|
// Invariant: start <= end
|
|
start: TextSize,
|
|
end: TextSize,
|
|
}
|
|
|
|
impl fmt::Debug for TextRange {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{}..{}", self.start().raw, self.end().raw)
|
|
}
|
|
}
|
|
|
|
impl TextRange {
|
|
/// Creates a new `TextRange` with the given `start` and `end` (`start..end`).
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// Panics if `end < start`.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```rust
|
|
/// # use ruff_text_size::*;
|
|
/// let start = TextSize::from(5);
|
|
/// let end = TextSize::from(10);
|
|
/// let range = TextRange::new(start, end);
|
|
///
|
|
/// assert_eq!(range.start(), start);
|
|
/// assert_eq!(range.end(), end);
|
|
/// assert_eq!(range.len(), end - start);
|
|
/// ```
|
|
#[inline]
|
|
#[track_caller]
|
|
pub const fn new(start: TextSize, end: TextSize) -> TextRange {
|
|
assert!(start.raw <= end.raw);
|
|
TextRange { start, end }
|
|
}
|
|
|
|
/// Create a new `TextRange` with the given `offset` and `len` (`offset..offset + len`).
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```rust
|
|
/// # use ruff_text_size::*;
|
|
/// let text = "0123456789";
|
|
///
|
|
/// let offset = TextSize::from(2);
|
|
/// let length = TextSize::from(5);
|
|
/// let range = TextRange::at(offset, length);
|
|
///
|
|
/// assert_eq!(range, TextRange::new(offset, offset + length));
|
|
/// assert_eq!(&text[range], "23456")
|
|
/// ```
|
|
#[inline]
|
|
pub fn at(offset: TextSize, len: TextSize) -> TextRange {
|
|
TextRange::new(offset, offset + len)
|
|
}
|
|
|
|
/// Create a zero-length range at the specified offset (`offset..offset`).
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```rust
|
|
/// # use ruff_text_size::*;
|
|
/// let point: TextSize;
|
|
/// # point = TextSize::from(3);
|
|
/// let range = TextRange::empty(point);
|
|
/// assert!(range.is_empty());
|
|
/// assert_eq!(range, TextRange::new(point, point));
|
|
/// ```
|
|
#[inline]
|
|
pub fn empty(offset: TextSize) -> TextRange {
|
|
TextRange {
|
|
start: offset,
|
|
end: offset,
|
|
}
|
|
}
|
|
|
|
/// Create a range up to the given end (`..end`).
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```rust
|
|
/// # use ruff_text_size::*;
|
|
/// let point: TextSize;
|
|
/// # point = TextSize::from(12);
|
|
/// let range = TextRange::up_to(point);
|
|
///
|
|
/// assert_eq!(range.len(), point);
|
|
/// assert_eq!(range, TextRange::new(0.into(), point));
|
|
/// assert_eq!(range, TextRange::at(0.into(), point));
|
|
/// ```
|
|
#[inline]
|
|
pub fn up_to(end: TextSize) -> TextRange {
|
|
TextRange {
|
|
start: 0.into(),
|
|
end,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Identity methods.
|
|
impl TextRange {
|
|
/// The start point of this range.
|
|
#[inline]
|
|
pub const fn start(self) -> TextSize {
|
|
self.start
|
|
}
|
|
|
|
/// The end point of this range.
|
|
#[inline]
|
|
pub const fn end(self) -> TextSize {
|
|
self.end
|
|
}
|
|
|
|
/// The size of this range.
|
|
#[inline]
|
|
pub const fn len(self) -> TextSize {
|
|
// HACK for const fn: math on primitives only
|
|
TextSize {
|
|
raw: self.end().raw - self.start().raw,
|
|
}
|
|
}
|
|
|
|
/// Check if this range is empty.
|
|
#[inline]
|
|
pub const fn is_empty(self) -> bool {
|
|
// HACK for const fn: math on primitives only
|
|
self.start().raw == self.end().raw
|
|
}
|
|
}
|
|
|
|
/// Manipulation methods.
|
|
impl TextRange {
|
|
/// Check if this range contains an offset.
|
|
///
|
|
/// The end index is considered excluded.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```rust
|
|
/// # use ruff_text_size::*;
|
|
/// let (start, end): (TextSize, TextSize);
|
|
/// # start = 10.into(); end = 20.into();
|
|
/// let range = TextRange::new(start, end);
|
|
/// assert!(range.contains(start));
|
|
/// assert!(!range.contains(end));
|
|
/// ```
|
|
#[inline]
|
|
pub fn contains(self, offset: TextSize) -> bool {
|
|
self.start() <= offset && offset < self.end()
|
|
}
|
|
|
|
/// Check if this range contains an offset.
|
|
///
|
|
/// The end index is considered included.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```rust
|
|
/// # use ruff_text_size::*;
|
|
/// let (start, end): (TextSize, TextSize);
|
|
/// # start = 10.into(); end = 20.into();
|
|
/// let range = TextRange::new(start, end);
|
|
/// assert!(range.contains_inclusive(start));
|
|
/// assert!(range.contains_inclusive(end));
|
|
/// ```
|
|
#[inline]
|
|
pub fn contains_inclusive(self, offset: TextSize) -> bool {
|
|
self.start() <= offset && offset <= self.end()
|
|
}
|
|
|
|
/// Check if this range completely contains another range.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```rust
|
|
/// # use ruff_text_size::*;
|
|
/// let larger = TextRange::new(0.into(), 20.into());
|
|
/// let smaller = TextRange::new(5.into(), 15.into());
|
|
/// assert!(larger.contains_range(smaller));
|
|
/// assert!(!smaller.contains_range(larger));
|
|
///
|
|
/// // a range always contains itself
|
|
/// assert!(larger.contains_range(larger));
|
|
/// assert!(smaller.contains_range(smaller));
|
|
/// ```
|
|
#[inline]
|
|
pub fn contains_range(self, other: TextRange) -> bool {
|
|
self.start() <= other.start() && other.end() <= self.end()
|
|
}
|
|
|
|
/// The range covered by both ranges, if it exists.
|
|
/// If the ranges touch but do not overlap, the output range is empty.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```rust
|
|
/// # use ruff_text_size::*;
|
|
/// assert_eq!(
|
|
/// TextRange::intersect(
|
|
/// TextRange::new(0.into(), 10.into()),
|
|
/// TextRange::new(5.into(), 15.into()),
|
|
/// ),
|
|
/// Some(TextRange::new(5.into(), 10.into())),
|
|
/// );
|
|
/// ```
|
|
#[inline]
|
|
pub fn intersect(self, other: TextRange) -> Option<TextRange> {
|
|
let start = cmp::max(self.start(), other.start());
|
|
let end = cmp::min(self.end(), other.end());
|
|
if end < start {
|
|
return None;
|
|
}
|
|
Some(TextRange::new(start, end))
|
|
}
|
|
|
|
/// Extends the range to cover `other` as well.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```rust
|
|
/// # use ruff_text_size::*;
|
|
/// assert_eq!(
|
|
/// TextRange::cover(
|
|
/// TextRange::new(0.into(), 5.into()),
|
|
/// TextRange::new(15.into(), 20.into()),
|
|
/// ),
|
|
/// TextRange::new(0.into(), 20.into()),
|
|
/// );
|
|
/// ```
|
|
#[inline]
|
|
#[must_use]
|
|
pub fn cover(self, other: TextRange) -> TextRange {
|
|
let start = cmp::min(self.start(), other.start());
|
|
let end = cmp::max(self.end(), other.end());
|
|
TextRange::new(start, end)
|
|
}
|
|
|
|
/// Extends the range to cover `other` offsets as well.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```rust
|
|
/// # use ruff_text_size::*;
|
|
/// assert_eq!(
|
|
/// TextRange::empty(0.into()).cover_offset(20.into()),
|
|
/// TextRange::new(0.into(), 20.into()),
|
|
/// )
|
|
/// ```
|
|
#[inline]
|
|
#[must_use]
|
|
pub fn cover_offset(self, offset: TextSize) -> TextRange {
|
|
self.cover(TextRange::empty(offset))
|
|
}
|
|
|
|
/// Add an offset to this range.
|
|
///
|
|
/// Note that this is not appropriate for changing where a `TextRange` is
|
|
/// within some string; rather, it is for changing the reference anchor
|
|
/// that the `TextRange` is measured against.
|
|
///
|
|
/// The unchecked version (`Add::add`) will _always_ panic on overflow,
|
|
/// in contrast to primitive integers, which check in debug mode only.
|
|
#[inline]
|
|
pub fn checked_add(self, offset: TextSize) -> Option<TextRange> {
|
|
Some(TextRange {
|
|
start: self.start.checked_add(offset)?,
|
|
end: self.end.checked_add(offset)?,
|
|
})
|
|
}
|
|
|
|
/// Subtract an offset from this range.
|
|
///
|
|
/// Note that this is not appropriate for changing where a `TextRange` is
|
|
/// within some string; rather, it is for changing the reference anchor
|
|
/// that the `TextRange` is measured against.
|
|
///
|
|
/// The unchecked version (`Sub::sub`) will _always_ panic on overflow,
|
|
/// in contrast to primitive integers, which check in debug mode only.
|
|
#[inline]
|
|
pub fn checked_sub(self, offset: TextSize) -> Option<TextRange> {
|
|
Some(TextRange {
|
|
start: self.start.checked_sub(offset)?,
|
|
end: self.end.checked_sub(offset)?,
|
|
})
|
|
}
|
|
|
|
/// Relative order of the two ranges (overlapping ranges are considered
|
|
/// equal).
|
|
///
|
|
///
|
|
/// This is useful when, for example, binary searching an array of disjoint
|
|
/// ranges.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// # use ruff_text_size::*;
|
|
/// # use std::cmp::Ordering;
|
|
///
|
|
/// let a = TextRange::new(0.into(), 3.into());
|
|
/// let b = TextRange::new(4.into(), 5.into());
|
|
/// assert_eq!(a.ordering(b), Ordering::Less);
|
|
///
|
|
/// let a = TextRange::new(0.into(), 3.into());
|
|
/// let b = TextRange::new(3.into(), 5.into());
|
|
/// assert_eq!(a.ordering(b), Ordering::Less);
|
|
///
|
|
/// let a = TextRange::new(0.into(), 3.into());
|
|
/// let b = TextRange::new(2.into(), 5.into());
|
|
/// assert_eq!(a.ordering(b), Ordering::Equal);
|
|
///
|
|
/// let a = TextRange::new(0.into(), 3.into());
|
|
/// let b = TextRange::new(2.into(), 2.into());
|
|
/// assert_eq!(a.ordering(b), Ordering::Equal);
|
|
///
|
|
/// let a = TextRange::new(2.into(), 3.into());
|
|
/// let b = TextRange::new(2.into(), 2.into());
|
|
/// assert_eq!(a.ordering(b), Ordering::Greater);
|
|
/// ```
|
|
#[inline]
|
|
pub fn ordering(self, other: TextRange) -> Ordering {
|
|
if self.end() <= other.start() {
|
|
Ordering::Less
|
|
} else if other.end() <= self.start() {
|
|
Ordering::Greater
|
|
} else {
|
|
Ordering::Equal
|
|
}
|
|
}
|
|
|
|
/// Subtracts an offset from the start position.
|
|
///
|
|
///
|
|
/// ## Panics
|
|
/// If `start - amount` is less than zero.
|
|
///
|
|
/// ## Examples
|
|
///
|
|
/// ```
|
|
/// use ruff_text_size::{Ranged, TextRange, TextSize};
|
|
///
|
|
/// let range = TextRange::new(TextSize::from(5), TextSize::from(10));
|
|
/// assert_eq!(range.sub_start(TextSize::from(2)), TextRange::new(TextSize::from(3), TextSize::from(10)));
|
|
/// ```
|
|
#[inline]
|
|
#[must_use]
|
|
pub fn sub_start(&self, amount: TextSize) -> TextRange {
|
|
TextRange::new(self.start() - amount, self.end())
|
|
}
|
|
|
|
/// Adds an offset to the start position.
|
|
///
|
|
/// ## Panics
|
|
/// If `start + amount > end`
|
|
///
|
|
/// ## Examples
|
|
///
|
|
/// ```
|
|
/// use ruff_text_size::{Ranged, TextRange, TextSize};
|
|
///
|
|
/// let range = TextRange::new(TextSize::from(5), TextSize::from(10));
|
|
/// assert_eq!(range.add_start(TextSize::from(3)), TextRange::new(TextSize::from(8), TextSize::from(10)));
|
|
/// ```
|
|
#[inline]
|
|
#[must_use]
|
|
pub fn add_start(&self, amount: TextSize) -> TextRange {
|
|
TextRange::new(self.start() + amount, self.end())
|
|
}
|
|
|
|
/// Subtracts an offset from the end position.
|
|
///
|
|
///
|
|
/// ## Panics
|
|
/// If `end - amount < 0` or `end - amount < start`
|
|
///
|
|
/// ## Examples
|
|
///
|
|
/// ```
|
|
/// use ruff_text_size::{Ranged, TextRange, TextSize};
|
|
///
|
|
/// let range = TextRange::new(TextSize::from(5), TextSize::from(10));
|
|
/// assert_eq!(range.sub_end(TextSize::from(2)), TextRange::new(TextSize::from(5), TextSize::from(8)));
|
|
/// ```
|
|
#[inline]
|
|
#[must_use]
|
|
pub fn sub_end(&self, amount: TextSize) -> TextRange {
|
|
TextRange::new(self.start(), self.end() - amount)
|
|
}
|
|
|
|
/// Adds an offset to the end position.
|
|
///
|
|
///
|
|
/// ## Panics
|
|
/// If `end + amount > u32::MAX`
|
|
///
|
|
/// ## Examples
|
|
///
|
|
/// ```
|
|
/// use ruff_text_size::{Ranged, TextRange, TextSize};
|
|
///
|
|
/// let range = TextRange::new(TextSize::from(5), TextSize::from(10));
|
|
/// assert_eq!(range.add_end(TextSize::from(2)), TextRange::new(TextSize::from(5), TextSize::from(12)));
|
|
/// ```
|
|
#[inline]
|
|
#[must_use]
|
|
pub fn add_end(&self, amount: TextSize) -> TextRange {
|
|
TextRange::new(self.start(), self.end() + amount)
|
|
}
|
|
}
|
|
|
|
impl Index<TextRange> for str {
|
|
type Output = str;
|
|
#[inline]
|
|
fn index(&self, index: TextRange) -> &str {
|
|
&self[Range::<usize>::from(index)]
|
|
}
|
|
}
|
|
|
|
impl Index<TextRange> for String {
|
|
type Output = str;
|
|
#[inline]
|
|
fn index(&self, index: TextRange) -> &str {
|
|
&self[Range::<usize>::from(index)]
|
|
}
|
|
}
|
|
|
|
impl IndexMut<TextRange> for str {
|
|
#[inline]
|
|
fn index_mut(&mut self, index: TextRange) -> &mut str {
|
|
&mut self[Range::<usize>::from(index)]
|
|
}
|
|
}
|
|
|
|
impl IndexMut<TextRange> for String {
|
|
#[inline]
|
|
fn index_mut(&mut self, index: TextRange) -> &mut str {
|
|
&mut self[Range::<usize>::from(index)]
|
|
}
|
|
}
|
|
|
|
impl RangeBounds<TextSize> for TextRange {
|
|
fn start_bound(&self) -> Bound<&TextSize> {
|
|
Bound::Included(&self.start)
|
|
}
|
|
|
|
fn end_bound(&self) -> Bound<&TextSize> {
|
|
Bound::Excluded(&self.end)
|
|
}
|
|
}
|
|
|
|
impl From<Range<TextSize>> for TextRange {
|
|
#[inline]
|
|
fn from(r: Range<TextSize>) -> Self {
|
|
TextRange::new(r.start, r.end)
|
|
}
|
|
}
|
|
|
|
impl<T> From<TextRange> for Range<T>
|
|
where
|
|
T: From<TextSize>,
|
|
{
|
|
#[inline]
|
|
fn from(r: TextRange) -> Self {
|
|
r.start().into()..r.end().into()
|
|
}
|
|
}
|
|
|
|
macro_rules! ops {
|
|
(impl $Op:ident for TextRange by fn $f:ident = $op:tt) => {
|
|
impl $Op<&TextSize> for TextRange {
|
|
type Output = TextRange;
|
|
#[inline]
|
|
fn $f(self, other: &TextSize) -> TextRange {
|
|
self $op *other
|
|
}
|
|
}
|
|
impl<T> $Op<T> for &TextRange
|
|
where
|
|
TextRange: $Op<T, Output=TextRange>,
|
|
{
|
|
type Output = TextRange;
|
|
#[inline]
|
|
fn $f(self, other: T) -> TextRange {
|
|
*self $op other
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
impl Add<TextSize> for TextRange {
|
|
type Output = TextRange;
|
|
#[inline]
|
|
fn add(self, offset: TextSize) -> TextRange {
|
|
self.checked_add(offset)
|
|
.expect("TextRange +offset overflowed")
|
|
}
|
|
}
|
|
|
|
impl Sub<TextSize> for TextRange {
|
|
type Output = TextRange;
|
|
#[inline]
|
|
fn sub(self, offset: TextSize) -> TextRange {
|
|
self.checked_sub(offset)
|
|
.expect("TextRange -offset overflowed")
|
|
}
|
|
}
|
|
|
|
ops!(impl Add for TextRange by fn add = +);
|
|
ops!(impl Sub for TextRange by fn sub = -);
|
|
|
|
impl<A> AddAssign<A> for TextRange
|
|
where
|
|
TextRange: Add<A, Output = TextRange>,
|
|
{
|
|
#[inline]
|
|
fn add_assign(&mut self, rhs: A) {
|
|
*self = *self + rhs;
|
|
}
|
|
}
|
|
|
|
impl<S> SubAssign<S> for TextRange
|
|
where
|
|
TextRange: Sub<S, Output = TextRange>,
|
|
{
|
|
#[inline]
|
|
fn sub_assign(&mut self, rhs: S) {
|
|
*self = *self - rhs;
|
|
}
|
|
}
|