1use crate::compiler::prelude::*;
2
3fn push(list: Value, item: Value) -> Resolved {
4 let mut list = list.try_array()?;
5 list.push(item);
6 Ok(list.into())
7}
8
9#[derive(Clone, Copy, Debug)]
10pub struct Push;
11
12impl Function for Push {
13 fn identifier(&self) -> &'static str {
14 "push"
15 }
16
17 fn usage(&self) -> &'static str {
18 "Adds the `item` to the end of the `value` array."
19 }
20
21 fn category(&self) -> &'static str {
22 Category::Array.as_ref()
23 }
24
25 fn return_kind(&self) -> u16 {
26 kind::ARRAY
27 }
28
29 fn return_rules(&self) -> &'static [&'static str] {
30 &["Returns a new array. The `value` is _not_ modified in place."]
31 }
32
33 fn parameters(&self) -> &'static [Parameter] {
34 const PARAMETERS: &[Parameter] = &[
35 Parameter::required("value", kind::ARRAY, "The target array."),
36 Parameter::required("item", kind::ANY, "The item to push."),
37 ];
38 PARAMETERS
39 }
40
41 fn examples(&self) -> &'static [Example] {
42 &[
43 example! {
44 title: "Push an item onto an array",
45 source: r"push([1, 2], 3)",
46 result: Ok(r"[1, 2, 3]"),
47 },
48 example! {
49 title: "Empty array",
50 source: r#"push([], "bar")"#,
51 result: Ok(r#"["bar"]"#),
52 },
53 ]
54 }
55
56 fn compile(
57 &self,
58 _state: &state::TypeState,
59 _ctx: &mut FunctionCompileContext,
60 arguments: ArgumentList,
61 ) -> Compiled {
62 let value = arguments.required("value");
63 let item = arguments.required("item");
64
65 Ok(PushFn { value, item }.as_expr())
66 }
67}
68
69#[derive(Debug, Clone)]
70struct PushFn {
71 value: Box<dyn Expression>,
72 item: Box<dyn Expression>,
73}
74
75impl FunctionExpression for PushFn {
76 fn resolve(&self, ctx: &mut Context) -> Resolved {
77 let list = self.value.resolve(ctx)?;
78 let item = self.item.resolve(ctx)?;
79
80 push(list, item)
81 }
82
83 fn type_def(&self, state: &state::TypeState) -> TypeDef {
84 let item = self.item.type_def(state).kind().clone().upgrade_undefined();
85 let mut typedef = self.value.type_def(state).restrict_array();
86
87 let array = typedef.as_array_mut().expect("must be an array");
88
89 if let Some(exact_len) = array.exact_length() {
90 array.known_mut().insert(exact_len.into(), item);
92 } else {
93 array.set_unknown(array.unknown_kind().union(item));
95 }
96
97 typedef.infallible()
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104 use crate::btreemap;
105 use crate::value;
106
107 test_function![
108 push => Push;
109
110 empty_array {
111 args: func_args![value: value!([]), item: value!("foo")],
112 want: Ok(value!(["foo"])),
113 tdef: TypeDef::array(btreemap! {
114 Index::from(0) => Kind::bytes(),
115 }),
116 }
117
118 new_item {
119 args: func_args![value: value!([11, false, 42.5]), item: value!("foo")],
120 want: Ok(value!([11, false, 42.5, "foo"])),
121 tdef: TypeDef::array(btreemap! {
122 Index::from(0) => Kind::integer(),
123 Index::from(1) => Kind::boolean(),
124 Index::from(2) => Kind::float(),
125 Index::from(3) => Kind::bytes(),
126 }),
127 }
128
129 already_exists_item {
130 args: func_args![value: value!([11, false, 42.5]), item: value!(42.5)],
131 want: Ok(value!([11, false, 42.5, 42.5])),
132 tdef: TypeDef::array(btreemap! {
133 Index::from(0) => Kind::integer(),
134 Index::from(1) => Kind::boolean(),
135 Index::from(2) => Kind::float(),
136 Index::from(3) => Kind::float(),
137 }),
138 }
139 ];
140}