ruff/crates/ruff_python_ast/src/name.rs

1008 lines
27 KiB
Rust

use std::borrow::{Borrow, Cow};
use std::fmt::{Debug, Display, Formatter, Write};
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use crate::{nodes, Expr};
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "cache", derive(ruff_macros::CacheKey))]
pub struct Name(compact_str::CompactString);
impl Name {
#[inline]
pub fn empty() -> Self {
Self(compact_str::CompactString::default())
}
#[inline]
pub fn new(name: impl AsRef<str>) -> Self {
Self(compact_str::CompactString::new(name))
}
#[inline]
pub const fn new_static(name: &'static str) -> Self {
Self(compact_str::CompactString::const_new(name))
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl Debug for Name {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Name({:?})", self.as_str())
}
}
impl AsRef<str> for Name {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Deref for Name {
type Target = str;
#[inline]
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl Borrow<str> for Name {
#[inline]
fn borrow(&self) -> &str {
self.as_str()
}
}
impl<'a> From<&'a str> for Name {
#[inline]
fn from(s: &'a str) -> Self {
Name(s.into())
}
}
impl From<String> for Name {
#[inline]
fn from(s: String) -> Self {
Name(s.into())
}
}
impl<'a> From<&'a String> for Name {
#[inline]
fn from(s: &'a String) -> Self {
Name(s.into())
}
}
impl<'a> From<Cow<'a, str>> for Name {
#[inline]
fn from(cow: Cow<'a, str>) -> Self {
Name(cow.into())
}
}
impl From<Box<str>> for Name {
#[inline]
fn from(b: Box<str>) -> Self {
Name(b.into())
}
}
impl From<compact_str::CompactString> for Name {
#[inline]
fn from(value: compact_str::CompactString) -> Self {
Self(value)
}
}
impl From<Name> for compact_str::CompactString {
#[inline]
fn from(name: Name) -> Self {
name.0
}
}
impl FromIterator<char> for Name {
fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> Self {
Self(iter.into_iter().collect())
}
}
impl std::fmt::Display for Name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl PartialEq<str> for Name {
#[inline]
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<Name> for str {
#[inline]
fn eq(&self, other: &Name) -> bool {
other == self
}
}
impl PartialEq<&str> for Name {
#[inline]
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
impl PartialEq<Name> for &str {
#[inline]
fn eq(&self, other: &Name) -> bool {
other == self
}
}
impl PartialEq<String> for Name {
fn eq(&self, other: &String) -> bool {
self == other.as_str()
}
}
impl PartialEq<Name> for String {
#[inline]
fn eq(&self, other: &Name) -> bool {
other == self
}
}
impl PartialEq<&String> for Name {
#[inline]
fn eq(&self, other: &&String) -> bool {
self.as_str() == *other
}
}
impl PartialEq<Name> for &String {
#[inline]
fn eq(&self, other: &Name) -> bool {
other == self
}
}
#[cfg(feature = "schemars")]
impl schemars::JsonSchema for Name {
fn is_referenceable() -> bool {
String::is_referenceable()
}
fn schema_name() -> String {
String::schema_name()
}
fn schema_id() -> std::borrow::Cow<'static, str> {
String::schema_id()
}
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
String::json_schema(gen)
}
fn _schemars_private_non_optional_json_schema(
gen: &mut schemars::gen::SchemaGenerator,
) -> schemars::schema::Schema {
String::_schemars_private_non_optional_json_schema(gen)
}
fn _schemars_private_is_option() -> bool {
String::_schemars_private_is_option()
}
}
/// A representation of a qualified name, like `typing.List`.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct QualifiedName<'a>(SegmentsVec<'a>);
impl<'a> QualifiedName<'a> {
/// Create a [`QualifiedName`] from a dotted name.
///
/// ```rust
/// # use ruff_python_ast::name::QualifiedName;
///
/// assert_eq!(QualifiedName::from_dotted_name("typing.List").segments(), ["typing", "List"]);
/// assert_eq!(QualifiedName::from_dotted_name("list").segments(), ["", "list"]);
/// ```
#[inline]
pub fn from_dotted_name(name: &'a str) -> Self {
if let Some(dot) = name.find('.') {
let mut builder = QualifiedNameBuilder::default();
builder.push(&name[..dot]);
builder.extend(name[dot + 1..].split('.'));
builder.build()
} else {
Self::builtin(name)
}
}
/// Creates a name that's guaranteed not be a built in
#[inline]
pub fn user_defined(name: &'a str) -> Self {
name.split('.').collect()
}
/// Creates a qualified name for a built in
#[inline]
pub fn builtin(name: &'a str) -> Self {
debug_assert!(!name.contains('.'));
Self(SegmentsVec::Stack(SegmentsStack::from_slice(&["", name])))
}
#[inline]
pub fn segments(&self) -> &[&'a str] {
self.0.as_slice()
}
/// If the first segment is empty, the `CallPath` represents a "builtin binding".
///
/// A builtin binding is the binding that a symbol has if it was part of Python's
/// global scope without any imports taking place. However, if builtin members are
/// accessed explicitly via the `builtins` module, they will not have a
/// "builtin binding", so this method will return `false`.
///
/// Ex) `["", "bool"]` -> `"bool"`
fn is_builtin(&self) -> bool {
matches!(self.segments(), ["", ..])
}
/// If the call path is dot-prefixed, it's an unresolved relative import.
/// Ex) `[".foo", "bar"]` -> `".foo.bar"`
pub fn is_unresolved_import(&self) -> bool {
matches!(self.segments(), [".", ..])
}
pub fn starts_with(&self, other: &QualifiedName<'_>) -> bool {
self.segments().starts_with(other.segments())
}
/// Appends a member to the qualified name.
#[must_use]
pub fn append_member(self, member: &'a str) -> Self {
let mut inner = self.0;
inner.push(member);
Self(inner)
}
}
impl Display for QualifiedName<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let segments = self.segments();
if self.is_unresolved_import() {
let mut iter = segments.iter();
for segment in iter.by_ref() {
if *segment == "." {
f.write_char('.')?;
} else {
f.write_str(segment)?;
break;
}
}
for segment in iter {
f.write_char('.')?;
f.write_str(segment)?;
}
} else {
let segments = if self.is_builtin() {
&segments[1..]
} else {
segments
};
let mut first = true;
for segment in segments {
if !first {
f.write_char('.')?;
}
f.write_str(segment)?;
first = false;
}
}
Ok(())
}
}
impl<'a> FromIterator<&'a str> for QualifiedName<'a> {
fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
Self(SegmentsVec::from_iter(iter))
}
}
#[derive(Debug, Clone, Default)]
pub struct QualifiedNameBuilder<'a> {
segments: SegmentsVec<'a>,
}
impl<'a> QualifiedNameBuilder<'a> {
pub fn with_capacity(capacity: usize) -> Self {
Self {
segments: SegmentsVec::with_capacity(capacity),
}
}
#[inline]
pub fn is_empty(&self) -> bool {
self.segments.is_empty()
}
#[inline]
pub fn push(&mut self, segment: &'a str) {
self.segments.push(segment);
}
#[inline]
pub fn pop(&mut self) {
self.segments.pop();
}
#[inline]
pub fn extend(&mut self, segments: impl IntoIterator<Item = &'a str>) {
self.segments.extend(segments);
}
#[inline]
pub fn extend_from_slice(&mut self, segments: &[&'a str]) {
self.segments.extend_from_slice(segments);
}
pub fn build(self) -> QualifiedName<'a> {
QualifiedName(self.segments)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct UnqualifiedName<'a>(SegmentsVec<'a>);
impl<'a> UnqualifiedName<'a> {
/// Convert an `Expr` to its [`UnqualifiedName`] (like `["typing", "List"]`).
pub fn from_expr(expr: &'a Expr) -> Option<Self> {
// Unroll the loop up to eight times, to match the maximum number of expected attributes.
// In practice, unrolling appears to give about a 4x speed-up on this hot path.
let attr1 = match expr {
Expr::Attribute(attr1) => attr1,
// Ex) `foo`
Expr::Name(nodes::ExprName { id, .. }) => {
return Some(Self::from_slice(&[id.as_str()]))
}
_ => return None,
};
let attr2 = match attr1.value.as_ref() {
Expr::Attribute(attr2) => attr2,
// Ex) `foo.bar`
Expr::Name(nodes::ExprName { id, .. }) => {
return Some(Self::from_slice(&[id.as_str(), attr1.attr.as_str()]))
}
_ => return None,
};
let attr3 = match attr2.value.as_ref() {
Expr::Attribute(attr3) => attr3,
// Ex) `foo.bar.baz`
Expr::Name(nodes::ExprName { id, .. }) => {
return Some(Self::from_slice(&[
id.as_str(),
attr2.attr.as_str(),
attr1.attr.as_str(),
]));
}
_ => return None,
};
let attr4 = match attr3.value.as_ref() {
Expr::Attribute(attr4) => attr4,
// Ex) `foo.bar.baz.bop`
Expr::Name(nodes::ExprName { id, .. }) => {
return Some(Self::from_slice(&[
id.as_str(),
attr3.attr.as_str(),
attr2.attr.as_str(),
attr1.attr.as_str(),
]));
}
_ => return None,
};
let attr5 = match attr4.value.as_ref() {
Expr::Attribute(attr5) => attr5,
// Ex) `foo.bar.baz.bop.bap`
Expr::Name(nodes::ExprName { id, .. }) => {
return Some(Self::from_slice(&[
id.as_str(),
attr4.attr.as_str(),
attr3.attr.as_str(),
attr2.attr.as_str(),
attr1.attr.as_str(),
]));
}
_ => return None,
};
let attr6 = match attr5.value.as_ref() {
Expr::Attribute(attr6) => attr6,
// Ex) `foo.bar.baz.bop.bap.bab`
Expr::Name(nodes::ExprName { id, .. }) => {
return Some(Self::from_slice(&[
id.as_str(),
attr5.attr.as_str(),
attr4.attr.as_str(),
attr3.attr.as_str(),
attr2.attr.as_str(),
attr1.attr.as_str(),
]));
}
_ => return None,
};
let attr7 = match attr6.value.as_ref() {
Expr::Attribute(attr7) => attr7,
// Ex) `foo.bar.baz.bop.bap.bab.bob`
Expr::Name(nodes::ExprName { id, .. }) => {
return Some(Self::from_slice(&[
id.as_str(),
attr6.attr.as_str(),
attr5.attr.as_str(),
attr4.attr.as_str(),
attr3.attr.as_str(),
attr2.attr.as_str(),
attr1.attr.as_str(),
]));
}
_ => return None,
};
let attr8 = match attr7.value.as_ref() {
Expr::Attribute(attr8) => attr8,
// Ex) `foo.bar.baz.bop.bap.bab.bob.bib`
Expr::Name(nodes::ExprName { id, .. }) => {
return Some(Self(SegmentsVec::from([
id.as_str(),
attr7.attr.as_str(),
attr6.attr.as_str(),
attr5.attr.as_str(),
attr4.attr.as_str(),
attr3.attr.as_str(),
attr2.attr.as_str(),
attr1.attr.as_str(),
])));
}
_ => return None,
};
let mut segments = Vec::with_capacity(SMALL_LEN * 2);
let mut current = &*attr8.value;
loop {
current = match current {
Expr::Attribute(attr) => {
segments.push(attr.attr.as_str());
&*attr.value
}
Expr::Name(nodes::ExprName { id, .. }) => {
segments.push(id.as_str());
break;
}
_ => {
return None;
}
}
}
segments.reverse();
// Append the attributes we visited before calling into the recursion.
segments.extend_from_slice(&[
attr8.attr.as_str(),
attr7.attr.as_str(),
attr6.attr.as_str(),
attr5.attr.as_str(),
attr4.attr.as_str(),
attr3.attr.as_str(),
attr2.attr.as_str(),
attr1.attr.as_str(),
]);
Some(Self(SegmentsVec::from(segments)))
}
#[inline]
pub fn from_slice(segments: &[&'a str]) -> Self {
Self(SegmentsVec::from_slice(segments))
}
pub fn segments(&self) -> &[&'a str] {
self.0.as_slice()
}
}
impl Display for UnqualifiedName<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut first = true;
for segment in self.segments() {
if !first {
f.write_char('.')?;
}
f.write_str(segment)?;
first = false;
}
Ok(())
}
}
impl<'a> FromIterator<&'a str> for UnqualifiedName<'a> {
#[inline]
fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
Self(iter.into_iter().collect())
}
}
/// A smallvec like storage for qualified and unqualified name segments.
///
/// Stores up to 8 segments inline, and falls back to a heap-allocated vector for names with more segments.
///
/// ## Note
/// The implementation doesn't use `SmallVec` v1 because its type definition has a variance problem.
/// The incorrect variance leads the lifetime inference in the `SemanticModel` astray, causing
/// all sort of "strange" lifetime errors. We can switch back to `SmallVec` when v2 is released.
#[derive(Clone)]
enum SegmentsVec<'a> {
Stack(SegmentsStack<'a>),
Heap(Vec<&'a str>),
}
impl<'a> SegmentsVec<'a> {
/// Creates an empty segment vec.
fn new() -> Self {
Self::Stack(SegmentsStack::default())
}
/// Creates a segment vec that has reserved storage for up to `capacity` items.
fn with_capacity(capacity: usize) -> Self {
if capacity <= SMALL_LEN {
Self::new()
} else {
Self::Heap(Vec::with_capacity(capacity))
}
}
#[cfg(test)]
const fn is_spilled(&self) -> bool {
matches!(self, SegmentsVec::Heap(_))
}
/// Initializes the segments from a slice.
#[inline]
fn from_slice(slice: &[&'a str]) -> Self {
if slice.len() <= SMALL_LEN {
SegmentsVec::Stack(SegmentsStack::from_slice(slice))
} else {
SegmentsVec::Heap(slice.to_vec())
}
}
/// Returns the segments as a slice.
#[inline]
fn as_slice(&self) -> &[&'a str] {
match self {
Self::Stack(stack) => stack.as_slice(),
Self::Heap(heap) => heap.as_slice(),
}
}
/// Pushes `name` to the end of the segments.
///
/// Spills to the heap if the segments are stored on the stack and the 9th segment is pushed.
#[inline]
fn push(&mut self, name: &'a str) {
match self {
SegmentsVec::Stack(stack) => {
if let Err(segments) = stack.push(name) {
*self = SegmentsVec::Heap(segments);
}
}
SegmentsVec::Heap(heap) => {
heap.push(name);
}
}
}
/// Pops the last segment from the end and returns it.
///
/// Returns `None` if the vector is empty.
#[inline]
fn pop(&mut self) -> Option<&'a str> {
match self {
SegmentsVec::Stack(stack) => stack.pop(),
SegmentsVec::Heap(heap) => heap.pop(),
}
}
#[inline]
fn extend_from_slice(&mut self, slice: &[&'a str]) {
match self {
SegmentsVec::Stack(stack) => {
if let Err(segments) = stack.extend_from_slice(slice) {
*self = SegmentsVec::Heap(segments);
}
}
SegmentsVec::Heap(heap) => heap.extend_from_slice(slice),
}
}
}
impl Default for SegmentsVec<'_> {
fn default() -> Self {
Self::new()
}
}
impl Debug for SegmentsVec<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_list().entries(self.as_slice()).finish()
}
}
impl<'a> Deref for SegmentsVec<'a> {
type Target = [&'a str];
fn deref(&self) -> &Self::Target {
self.as_slice()
}
}
impl<'a, 'b> PartialEq<SegmentsVec<'b>> for SegmentsVec<'a> {
fn eq(&self, other: &SegmentsVec<'b>) -> bool {
self.as_slice() == other.as_slice()
}
}
impl Eq for SegmentsVec<'_> {}
impl Hash for SegmentsVec<'_> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_slice().hash(state);
}
}
impl<'a> FromIterator<&'a str> for SegmentsVec<'a> {
#[inline]
fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
let mut segments = SegmentsVec::default();
segments.extend(iter);
segments
}
}
impl<'a> From<[&'a str; 8]> for SegmentsVec<'a> {
#[inline]
fn from(segments: [&'a str; 8]) -> Self {
SegmentsVec::Stack(SegmentsStack {
segments,
len: segments.len(),
})
}
}
impl<'a> From<Vec<&'a str>> for SegmentsVec<'a> {
#[inline]
fn from(segments: Vec<&'a str>) -> Self {
SegmentsVec::Heap(segments)
}
}
impl<'a> Extend<&'a str> for SegmentsVec<'a> {
#[inline]
fn extend<T: IntoIterator<Item = &'a str>>(&mut self, iter: T) {
match self {
SegmentsVec::Stack(stack) => {
if let Err(segments) = stack.extend(iter) {
*self = SegmentsVec::Heap(segments);
}
}
SegmentsVec::Heap(heap) => {
heap.extend(iter);
}
}
}
}
const SMALL_LEN: usize = 8;
#[derive(Debug, Clone, Default)]
struct SegmentsStack<'a> {
segments: [&'a str; SMALL_LEN],
len: usize,
}
impl<'a> SegmentsStack<'a> {
#[inline]
fn from_slice(slice: &[&'a str]) -> Self {
assert!(slice.len() <= SMALL_LEN);
let mut segments: [&'a str; SMALL_LEN] = Default::default();
segments[..slice.len()].copy_from_slice(slice);
SegmentsStack {
segments,
len: slice.len(),
}
}
const fn capacity(&self) -> usize {
SMALL_LEN - self.len
}
#[inline]
fn as_slice(&self) -> &[&'a str] {
&self.segments[..self.len]
}
/// Pushes `name` to the end of the segments.
///
/// Returns `Err` with a `Vec` containing all items (including `name`) if there's not enough capacity to push the name.
#[inline]
fn push(&mut self, name: &'a str) -> Result<(), Vec<&'a str>> {
if self.len < self.segments.len() {
self.segments[self.len] = name;
self.len += 1;
Ok(())
} else {
let mut segments = Vec::with_capacity(self.len * 2);
segments.extend_from_slice(&self.segments);
segments.push(name);
Err(segments)
}
}
/// Reserves spaces for `additional` segments.
///
/// Returns `Err` with a `Vec` containing all segments and a capacity to store `additional` segments if
/// the stack needs to spill over to the heap to store `additional` segments.
#[inline]
fn reserve(&mut self, additional: usize) -> Result<(), Vec<&'a str>> {
if self.capacity() >= additional {
Ok(())
} else {
let mut segments = Vec::with_capacity(self.len + additional);
segments.extend_from_slice(self.as_slice());
Err(segments)
}
}
#[inline]
fn pop(&mut self) -> Option<&'a str> {
if self.len > 0 {
self.len -= 1;
Some(self.segments[self.len])
} else {
None
}
}
/// Extends the segments by appending `slice` to the end.
///
/// Returns `Err` with a `Vec` containing all segments and the segments in `slice` if there's not enough capacity to append the names.
#[inline]
fn extend_from_slice(&mut self, slice: &[&'a str]) -> Result<(), Vec<&'a str>> {
let new_len = self.len + slice.len();
if slice.len() <= self.capacity() {
self.segments[self.len..new_len].copy_from_slice(slice);
self.len = new_len;
Ok(())
} else {
let mut segments = Vec::with_capacity(new_len);
segments.extend_from_slice(self.as_slice());
segments.extend_from_slice(slice);
Err(segments)
}
}
#[inline]
fn extend<I>(&mut self, iter: I) -> Result<(), Vec<&'a str>>
where
I: IntoIterator<Item = &'a str>,
{
let mut iter = iter.into_iter();
let (lower, _) = iter.size_hint();
// There's not enough space to store the lower bound of items. Spill to the heap.
if let Err(mut segments) = self.reserve(lower) {
segments.extend(iter);
return Err(segments);
}
// Copy over up to capacity items
for name in iter.by_ref().take(self.capacity()) {
self.segments[self.len] = name;
self.len += 1;
}
let Some(item) = iter.next() else {
// There are no more items to copy over and they all fit into capacity.
return Ok(());
};
// There are more items and there's not enough capacity to store them on the stack.
// Spill over to the heap and append the remaining items.
let mut segments = Vec::with_capacity(self.len * 2);
segments.extend_from_slice(self.as_slice());
segments.push(item);
segments.extend(iter);
Err(segments)
}
}
#[cfg(test)]
mod tests {
use crate::name::SegmentsVec;
#[test]
fn empty_vec() {
let empty = SegmentsVec::new();
assert_eq!(empty.as_slice(), &[] as &[&str]);
assert!(!empty.is_spilled());
}
#[test]
fn from_slice_stack() {
let stack = SegmentsVec::from_slice(&["a", "b", "c"]);
assert_eq!(stack.as_slice(), &["a", "b", "c"]);
assert!(!stack.is_spilled());
}
#[test]
fn from_slice_stack_capacity() {
let stack = SegmentsVec::from_slice(&["a", "b", "c", "d", "e", "f", "g", "h"]);
assert_eq!(stack.as_slice(), &["a", "b", "c", "d", "e", "f", "g", "h"]);
assert!(!stack.is_spilled());
}
#[test]
fn from_slice_heap() {
let heap = SegmentsVec::from_slice(&["a", "b", "c", "d", "e", "f", "g", "h", "i"]);
assert_eq!(
heap.as_slice(),
&["a", "b", "c", "d", "e", "f", "g", "h", "i"]
);
assert!(heap.is_spilled());
}
#[test]
fn push_stack() {
let mut stack = SegmentsVec::from_slice(&["a", "b", "c"]);
stack.push("d");
stack.push("e");
assert_eq!(stack.as_slice(), &["a", "b", "c", "d", "e"]);
assert!(!stack.is_spilled());
}
#[test]
fn push_stack_spill() {
let mut stack = SegmentsVec::from_slice(&["a", "b", "c", "d", "e", "f", "g"]);
stack.push("h");
assert!(!stack.is_spilled());
stack.push("i");
assert_eq!(
stack.as_slice(),
&["a", "b", "c", "d", "e", "f", "g", "h", "i"]
);
assert!(stack.is_spilled());
}
#[test]
fn pop_stack() {
let mut stack = SegmentsVec::from_slice(&["a", "b", "c", "d", "e"]);
assert_eq!(stack.pop(), Some("e"));
assert_eq!(stack.pop(), Some("d"));
assert_eq!(stack.pop(), Some("c"));
assert_eq!(stack.pop(), Some("b"));
assert_eq!(stack.pop(), Some("a"));
assert_eq!(stack.pop(), None);
assert!(!stack.is_spilled());
}
#[test]
fn pop_heap() {
let mut heap = SegmentsVec::from_slice(&["a", "b", "c", "d", "e", "f", "g", "h", "i"]);
assert_eq!(heap.pop(), Some("i"));
assert_eq!(heap.pop(), Some("h"));
assert_eq!(heap.pop(), Some("g"));
assert!(heap.is_spilled());
}
#[test]
fn extend_from_slice_stack() {
let mut stack = SegmentsVec::from_slice(&["a", "b", "c"]);
stack.extend_from_slice(&["d", "e", "f"]);
assert_eq!(stack.as_slice(), &["a", "b", "c", "d", "e", "f"]);
assert!(!stack.is_spilled());
}
#[test]
fn extend_from_slice_stack_spill() {
let mut spilled = SegmentsVec::from_slice(&["a", "b", "c", "d", "e", "f"]);
spilled.extend_from_slice(&["g", "h", "i", "j"]);
assert_eq!(
spilled.as_slice(),
&["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
);
assert!(spilled.is_spilled());
}
#[test]
fn extend_from_slice_heap() {
let mut heap = SegmentsVec::from_slice(&["a", "b", "c", "d", "e", "f", "g", "h", "i"]);
assert!(heap.is_spilled());
heap.extend_from_slice(&["j", "k", "l"]);
assert_eq!(
heap.as_slice(),
&["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"]
);
}
#[test]
fn extend_stack() {
let mut stack = SegmentsVec::from_slice(&["a", "b", "c"]);
stack.extend(["d", "e", "f"]);
assert_eq!(stack.as_slice(), &["a", "b", "c", "d", "e", "f"]);
assert!(!stack.is_spilled());
}
#[test]
fn extend_stack_spilled() {
let mut stack = SegmentsVec::from_slice(&["a", "b", "c", "d", "e", "f"]);
stack.extend(["g", "h", "i", "j"]);
assert_eq!(
stack.as_slice(),
&["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
);
assert!(stack.is_spilled());
}
#[test]
fn extend_heap() {
let mut heap = SegmentsVec::from_slice(&["a", "b", "c", "d", "e", "f", "g", "h", "i"]);
assert!(heap.is_spilled());
heap.extend(["j", "k", "l"]);
assert_eq!(
heap.as_slice(),
&["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"]
);
}
}