use std::{
any::TypeId,
cell::Cell,
cmp,
cmp::Ordering,
fmt::{self, Debug, Display, Formatter},
intrinsics::{likely, unlikely},
marker::PhantomData,
ops::Deref,
slice,
};
use gazebo::{
any::AnyLifetime,
coerce::{coerce, coerce_ref, Coerce},
prelude::*,
};
use serde::{ser::SerializeSeq, Serialize};
use crate::{
self as starlark,
environment::{Methods, MethodsStatic},
values::{
array::Array,
comparison::{compare_slice, equals_slice},
display::display_container,
error::ValueError,
index::{apply_slice, convert_index},
AllocFrozenValue, AllocValue, FrozenHeap, FrozenStringValue, FrozenValue, Heap,
StarlarkValue, UnpackValue, Value, ValueLike, ValueTyped,
},
};
#[derive(Clone, Default, Trace, Debug, AnyLifetime)]
#[repr(transparent)]
pub(crate) struct ListGen<T>(pub(crate) T);
#[derive(Trace, Debug, AnyLifetime)]
pub struct List<'v> {
pub(crate) content: Cell<ValueTyped<'v, Array<'v>>>,
}
#[derive(AnyLifetime)]
#[repr(C)]
pub struct FrozenList {
len: usize,
content: [FrozenValue; 0],
}
impl<'v> Debug for FrozenList {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("FrozenList")
.field("content", &self.content())
.finish()
}
}
impl ListGen<FrozenList> {
pub(crate) fn offset_of_content() -> usize {
memoffset::offset_of!(FrozenList, content)
}
}
#[repr(transparent)]
#[derive(Coerce)]
pub struct ListRef<'v> {
content: [Value<'v>],
}
impl<'v> ListRef<'v> {
fn new<'a>(slice: &'a [Value<'v>]) -> &'a ListRef<'v> {
coerce(slice)
}
pub fn content(&self) -> &[Value<'v>] {
&self.content
}
pub fn iter<'a>(&'a self) -> impl ExactSizeIterator<Item = Value<'v>> + 'a
where
'v: 'a,
{
self.content.iter().copied()
}
}
impl<'v> List<'v> {
pub fn from_value(x: Value<'v>) -> Option<&'v ListRef<'v>> {
if x.unpack_frozen().is_some() {
x.downcast_ref::<ListGen<FrozenList>>()
.map(|x| ListRef::new(coerce(x.0.content())))
} else {
let ptr = x.downcast_ref::<ListGen<List>>()?;
Some(ListRef::new(ptr.0.content()))
}
}
#[inline]
pub(crate) fn from_value_mut(x: Value<'v>) -> anyhow::Result<&'v Self> {
#[derive(thiserror::Error, Debug)]
#[error("Value is not list, value type: `{}`", .0)]
struct NotListError(&'static str);
#[cold]
#[inline(never)]
fn error<'v>(x: Value<'v>) -> anyhow::Error {
if x.downcast_ref::<ListGen<FrozenList>>().is_some() {
ValueError::CannotMutateImmutableValue.into()
} else {
NotListError(x.get_type()).into()
}
}
if let Some(x) = x.downcast_ref::<ListGen<List<'v>>>() {
x.0.check_can_mutate()?;
Ok(&x.0)
} else {
Err(error(x))
}
}
pub(crate) unsafe fn from_value_unchecked_mut(x: Value<'v>) -> &'v Self {
let list = x.downcast_ref_unchecked::<ListGen<List<'v>>>();
debug_assert!(list.0.check_can_mutate().is_ok());
&list.0
}
pub(crate) fn is_list_type(x: TypeId) -> bool {
x == TypeId::of::<ListGen<List>>() || x == TypeId::of::<ListGen<FrozenList>>()
}
fn check_can_mutate(&self) -> anyhow::Result<()> {
if unlikely(self.content.get().as_ref().iter_count_is_non_zero()) {
return Err(ValueError::MutationDuringIteration.into());
}
Ok(())
}
#[cold]
#[inline(never)]
fn reserve_additional_slow(&self, additional: usize, heap: &'v Heap) {
let new_cap = cmp::max(self.len() + additional, self.len() * 2);
let new_cap = cmp::max(new_cap, 4);
let new_array = heap.alloc_array(new_cap);
new_array.extend_from_slice(self.content());
self.content.set(new_array);
}
#[inline(always)]
fn reserve_additional(&self, additional: usize, heap: &'v Heap) {
if likely(self.content.get().as_ref().remaining_capacity() >= additional) {
return;
}
self.reserve_additional_slow(additional, heap);
}
pub(crate) fn double(&self, heap: &'v Heap) {
self.reserve_additional(self.len(), heap);
self.content.get().double();
}
#[inline]
pub(crate) fn extend<I: IntoIterator<Item = Value<'v>>>(&self, iter: I, heap: &'v Heap) {
let iter = iter.into_iter();
let (lo, hi) = iter.size_hint();
match hi {
Some(hi) if lo == hi => {
self.reserve_additional(lo, heap);
self.content.get().extend(iter);
}
Some(hi) if self.content.get().remaining_capacity() >= hi => {
self.content.get().extend(iter);
}
_ => {
self.reserve_additional(iter.size_hint().0, heap);
for item in iter {
self.push(item, heap);
}
}
}
}
pub(crate) fn push(&self, value: Value<'v>, heap: &'v Heap) {
self.reserve_additional(1, heap);
self.content.get().push(value);
}
pub(crate) fn clear(&self) {
self.content.get().clear();
}
pub(crate) fn insert(&self, index: usize, value: Value<'v>, heap: &'v Heap) {
self.reserve_additional(1, heap);
self.content.get().insert(index, value);
}
pub(crate) fn remove(&self, index: usize) -> Value<'v> {
self.content.get().remove(index)
}
}
impl<'v> Deref for ListRef<'v> {
type Target = [Value<'v>];
fn deref(&self) -> &[Value<'v>] {
&self.content
}
}
impl<'v, V: AllocValue<'v>> AllocValue<'v> for Vec<V> {
fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
heap.alloc_list_iter(self.into_map(|x| x.alloc_value(heap)))
}
}
impl<'v, V: AllocFrozenValue> AllocFrozenValue for Vec<V> {
fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
heap.alloc_list(&self.into_map(|x| x.alloc_frozen_value(heap)))
}
}
impl<'a, 'v, V: 'a> AllocValue<'v> for &'a [V]
where
&'a V: AllocValue<'v>,
{
fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
heap.alloc_list_iter(self.iter().map(|x| x.alloc_value(heap)))
}
}
impl<'a, 'v, V: 'a> AllocFrozenValue for &'a [V]
where
&'a V: AllocFrozenValue,
{
fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
heap.alloc_list(&self.map(|x| x.alloc_frozen_value(heap)))
}
}
impl FrozenList {
pub fn empty() -> impl AllocFrozenValue {
struct EmptyFrozenList;
impl AllocFrozenValue for EmptyFrozenList {
fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
heap.alloc_list(&[])
}
}
EmptyFrozenList
}
pub(crate) const unsafe fn new(len: usize) -> FrozenList {
FrozenList { len, content: [] }
}
pub(crate) fn len(&self) -> usize {
self.len
}
pub(crate) fn content(&self) -> &[FrozenValue] {
unsafe { slice::from_raw_parts(self.content.as_ptr(), self.len) }
}
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn from_frozen_value(x: &FrozenValue) -> Option<&FrozenList> {
x.downcast_ref::<ListGen<FrozenList>>().map(|x| &x.0)
}
}
impl<'v> List<'v> {
pub const TYPE: &'static str = "list";
pub fn get_type_value_static() -> FrozenStringValue {
ListGen::<FrozenList>::get_type_value_static()
}
pub(crate) fn new(content: ValueTyped<'v, Array<'v>>) -> Self {
List {
content: Cell::new(content),
}
}
pub fn len(&self) -> usize {
self.content.get().len()
}
pub fn content(&self) -> &[Value<'v>] {
self.content.get().as_ref().content()
}
pub fn iter<'a>(&'a self) -> impl ExactSizeIterator<Item = Value<'v>> + 'a
where
'v: 'a,
{
self.content.get().as_ref().iter()
}
}
impl<'v> Display for List<'v> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
display_list(self.content.get().content(), f)
}
}
impl Display for FrozenList {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
display_list(coerce_ref(&self.content()), f)
}
}
impl<'v> Display for ListRef<'v> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
display_list(&self.content, f)
}
}
impl FrozenList {
pub fn iter<'a, 'v>(&'a self) -> impl ExactSizeIterator<Item = Value<'v>> + 'a
where
'v: 'a,
{
self.content().iter().map(|e| e.to_value())
}
}
pub(crate) trait ListLike<'v>: Debug {
fn content(&self) -> &[Value<'v>];
fn set_at(&self, i: usize, v: Value<'v>) -> anyhow::Result<()>;
fn iterate<'a>(&'a self) -> Box<dyn Iterator<Item = Value<'v>> + 'a>
where
'v: 'a;
fn with_iterator(
&self,
f: &mut dyn FnMut(&mut dyn Iterator<Item = Value<'v>>) -> anyhow::Result<()>,
) -> anyhow::Result<()>;
}
impl<'v> ListLike<'v> for List<'v> {
fn content(&self) -> &[Value<'v>] {
self.content.get().as_ref().content()
}
fn set_at(&self, i: usize, v: Value<'v>) -> anyhow::Result<()> {
self.check_can_mutate()?;
self.content.get().set_at(i, v);
Ok(())
}
fn iterate<'a>(&'a self) -> Box<dyn Iterator<Item = Value<'v>> + 'a>
where
'v: 'a,
{
box self.content.get().as_ref().iter()
}
fn with_iterator(
&self,
f: &mut dyn FnMut(&mut dyn Iterator<Item = Value<'v>>) -> anyhow::Result<()>,
) -> anyhow::Result<()> {
f(&mut self.content.get().iter())
}
}
impl<'v> ListLike<'v> for FrozenList {
fn content(&self) -> &[Value<'v>] {
coerce(self.content())
}
fn set_at(&self, _i: usize, _v: Value<'v>) -> anyhow::Result<()> {
Err(ValueError::CannotMutateImmutableValue.into())
}
fn iterate<'a>(&'a self) -> Box<dyn Iterator<Item = Value<'v>> + 'a>
where
'v: 'a,
{
box coerce(self.content()).iter().copied()
}
fn with_iterator(
&self,
f: &mut dyn FnMut(&mut dyn Iterator<Item = Value<'v>>) -> anyhow::Result<()>,
) -> anyhow::Result<()> {
f(&mut coerce(self.content()).iter().copied())
}
}
impl<T: Display> Display for ListGen<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
pub(crate) fn display_list(xs: &[Value], f: &mut fmt::Formatter<'_>) -> fmt::Result {
display_container(f, "[", "]", xs.iter())
}
pub(crate) fn list_methods() -> Option<&'static Methods> {
static RES: MethodsStatic = MethodsStatic::new();
RES.methods(crate::stdlib::list::list_methods)
}
impl<'v, T: ListLike<'v>> StarlarkValue<'v> for ListGen<T>
where
Self: AnyLifetime<'v> + Display,
{
starlark_type!(List::TYPE);
fn is_special() -> bool
where
Self: Sized,
{
true
}
fn get_methods(&self) -> Option<&'static Methods> {
list_methods()
}
fn collect_repr(&self, s: &mut String) {
s.push('[');
for (i, v) in self.0.content().iter().enumerate() {
if i != 0 {
s.push_str(", ");
}
v.collect_repr(s);
}
s.push(']');
}
fn collect_repr_cycle(&self, collector: &mut String) {
collector.push_str("[...]");
}
fn to_bool(&self) -> bool {
!self.0.content().is_empty()
}
fn equals(&self, other: Value<'v>) -> anyhow::Result<bool> {
match List::from_value(other) {
None => Ok(false),
Some(other) => equals_slice(&*self.0.content(), &other.content, |x, y| x.equals(*y)),
}
}
fn compare(&self, other: Value<'v>) -> anyhow::Result<Ordering> {
match List::from_value(other) {
None => ValueError::unsupported_with(self, "cmp()", other),
Some(other) => compare_slice(&*self.0.content(), &other.content, |x, y| x.compare(*y)),
}
}
fn at(&self, index: Value, _heap: &'v Heap) -> anyhow::Result<Value<'v>> {
let i = convert_index(index, self.0.content().len() as i32)? as usize;
Ok(self.0.content()[i])
}
fn extra_memory(&self) -> usize {
0
}
fn length(&self) -> anyhow::Result<i32> {
Ok(self.0.content().len() as i32)
}
fn is_in(&self, other: Value<'v>) -> anyhow::Result<bool> {
for x in self.0.content().iter() {
if x.equals(other)? {
return Ok(true);
}
}
Ok(false)
}
fn slice(
&self,
start: Option<Value>,
stop: Option<Value>,
stride: Option<Value>,
heap: &'v Heap,
) -> anyhow::Result<Value<'v>> {
let xs = self.0.content();
let res = apply_slice(&*xs, start, stop, stride)?;
Ok(heap.alloc_list(&res))
}
fn iterate<'a>(
&'a self,
_heap: &'v Heap,
) -> anyhow::Result<Box<dyn Iterator<Item = Value<'v>> + 'a>>
where
'v: 'a,
{
Ok(self.0.iterate())
}
fn with_iterator(
&self,
_heap: &'v Heap,
f: &mut dyn FnMut(&mut dyn Iterator<Item = Value<'v>>) -> anyhow::Result<()>,
) -> anyhow::Result<()> {
self.0.with_iterator(f)
}
fn add(&self, other: Value<'v>, heap: &'v Heap) -> Option<anyhow::Result<Value<'v>>> {
List::from_value(other)
.map(|other| Ok(heap.alloc_list_concat(self.0.content(), other.content())))
}
fn mul(&self, other: Value, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
let l = i32::unpack_param(other)?;
let mut result = Vec::with_capacity(self.0.content().len() * cmp::max(0, l) as usize);
for _ in 0..l {
result.extend(self.0.content().iter());
}
Ok(heap.alloc_list(&result))
}
fn set_at(&self, index: Value<'v>, alloc_value: Value<'v>) -> anyhow::Result<()> {
let i = convert_index(index, self.0.content().len() as i32)? as usize;
self.0.set_at(i, alloc_value)
}
}
impl<'v, T: ListLike<'v>> Serialize for ListGen<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut seq_serializer = serializer.serialize_seq(Some(self.0.content().len()))?;
for e in self.0.content().iter() {
seq_serializer.serialize_element(&e.to_value())?;
}
seq_serializer.end()
}
}
pub struct ListOf<'v, V: UnpackValue<'v>> {
value: Value<'v>,
phantom: PhantomData<V>,
}
impl<'v, V: UnpackValue<'v>> ListOf<'v, V> {
pub fn to_vec(&self) -> Vec<V> {
List::from_value(self.value)
.expect("already validated as a list")
.iter()
.map(|v| V::unpack_value(v).expect("already validated value"))
.collect()
}
}
impl<'v> UnpackValue<'v> for &'v ListRef<'v> {
fn expected() -> String {
"list".to_owned()
}
fn unpack_value(value: Value<'v>) -> Option<Self> {
List::from_value(value)
}
}
impl<'v, V: UnpackValue<'v>> UnpackValue<'v> for ListOf<'v, V> {
fn expected() -> String {
format!("list of {}", V::expected())
}
fn unpack_value(value: Value<'v>) -> Option<Self> {
let list = List::from_value(value)?;
if list.iter().all(|v| V::unpack_value(v).is_some()) {
Some(ListOf {
value,
phantom: PhantomData,
})
} else {
None
}
}
}
impl<'v, V: UnpackValue<'v>> Deref for ListOf<'v, V> {
type Target = Value<'v>;
fn deref(&self) -> &Self::Target {
&self.value
}
}
#[cfg(test)]
mod tests {
use crate::assert::{self, Assert};
#[test]
fn test_to_str() {
assert::all_true(
r#"
str([1, 2, 3]) == "[1, 2, 3]"
str([1, [2, 3]]) == "[1, [2, 3]]"
str([]) == "[]"
"#,
);
}
#[test]
fn test_repr_cycle() {
assert::eq("l = []; l.append(l); repr(l)", "'[[...]]'");
assert::eq("l = []; l.append(l); str(l)", "'[[...]]'");
}
#[test]
fn test_mutate_list() {
assert::is_true(
r#"
v = [1, 2, 3]
v[1] = 1
v[2] = [2, 3]
v == [1, 1, [2, 3]]
"#,
);
}
#[test]
fn test_arithmetic_on_list() {
assert::all_true(
r#"
[1, 2, 3] + [2, 3] == [1, 2, 3, 2, 3]
[1, 2, 3] * 3 == [1, 2, 3, 1, 2, 3, 1, 2, 3]
"#,
);
}
#[test]
fn test_value_alias() {
assert::is_true(
r#"
v1 = [1, 2, 3]
v2 = v1
v2[2] = 4
v1 == [1, 2, 4] and v2 == [1, 2, 4]
"#,
);
}
#[test]
fn test_mutating_imports() {
let mut a = Assert::new();
a.module(
"x",
r#"
frozen_list = [1, 2]
frozen_list += [4]
def frozen_list_result():
return frozen_list
def list_result():
return [1, 2, 4]
"#,
);
a.fail("load('x','frozen_list')\nfrozen_list += [1]", "Immutable");
a.fail(
"load('x','frozen_list_result')\nx = frozen_list_result()\nx += [1]",
"Immutable",
);
a.is_true("load('x','list_result')\nx = list_result()\nx += [8]\nx == [1, 2, 4, 8]");
}
}