1use std::ffi::c_void;
2use std::fmt::Write as _;
3use std::ops::Deref;
4use std::ptr::NonNull;
5
6use crate::convert::{UnboxedValueGuard, implicitly_convert_to_int, implicitly_convert_to_string};
7use crate::extn::prelude::*;
8use crate::fmt::WriteError;
9
10pub mod args;
11mod ffi;
12pub(in crate::extn) mod mruby;
13pub(super) mod trampoline;
14mod wrapper;
15
16#[doc(inline)]
17pub use wrapper::{Array, RawParts};
18
19pub fn initialize(
20 interp: &mut Artichoke,
21 first: Option<Value>,
22 second: Option<Value>,
23 block: Option<Block>,
24) -> Result<Array, Error> {
25 let ary = match (first, second, block) {
26 (Some(mut array_or_len), default, None) => {
27 if let Ok(len) = array_or_len.try_convert_into::<i64>(interp) {
28 let len = usize::try_from(len).map_err(|_| ArgumentError::with_message("negative array size"))?;
29 let default = default.unwrap_or_else(Value::nil);
30 Array::with_len_and_default(len, default)
31 } else {
32 let unboxed = unsafe { Array::unbox_from_value(&mut array_or_len, interp) };
33 if let Ok(ary) = unboxed {
34 ary.clone()
35 } else if array_or_len.respond_to(interp, "to_ary")? {
36 let mut other = array_or_len.funcall(interp, "to_ary", &[], None)?;
37 let unboxed = unsafe { Array::unbox_from_value(&mut other, interp) };
38 if let Ok(other) = unboxed {
39 other.clone()
40 } else {
41 let mut message = String::from("can't convert ");
42 let name = interp.inspect_type_name_for_value(array_or_len);
43 message.push_str(name);
44 message.push_str(" to Array (");
45 message.push_str(name);
46 message.push_str("#to_ary gives ");
47 message.push_str(interp.inspect_type_name_for_value(other));
48 return Err(TypeError::from(message).into());
49 }
50 } else {
51 let len = implicitly_convert_to_int(interp, array_or_len)?;
52 let len = usize::try_from(len).map_err(|_| ArgumentError::with_message("negative array size"))?;
53 let default = default.unwrap_or_else(Value::nil);
54 Array::with_len_and_default(len, default)
55 }
56 }
57 }
58 (Some(mut array_or_len), default, Some(block)) => {
59 if let Ok(len) = array_or_len.try_convert_into::<i64>(interp) {
60 let len = usize::try_from(len).map_err(|_| ArgumentError::with_message("negative array size"))?;
61 if default.is_some() {
62 interp.warn(b"warning: block supersedes default value argument")?;
63 }
64 let mut buffer = Array::with_capacity(len);
65 for idx in 0..len {
66 let idx = i64::try_from(idx)
67 .map_err(|_| RangeError::with_message("bignum too big to convert into `long'"))?;
68 let idx = interp.convert(idx);
69 let elem = block.yield_arg(interp, &idx)?;
70 buffer.push(elem);
71 }
72 buffer
73 } else {
74 let unboxed = unsafe { Array::unbox_from_value(&mut array_or_len, interp) };
75 if let Ok(ary) = unboxed {
76 ary.clone()
77 } else if array_or_len.respond_to(interp, "to_ary")? {
78 let mut other = array_or_len.funcall(interp, "to_ary", &[], None)?;
79 let unboxed = unsafe { Array::unbox_from_value(&mut other, interp) };
80 if let Ok(other) = unboxed {
81 other.clone()
82 } else {
83 let mut message = String::from("can't convert ");
84 let name = interp.inspect_type_name_for_value(array_or_len);
85 message.push_str(name);
86 message.push_str(" to Array (");
87 message.push_str(name);
88 message.push_str("#to_ary gives ");
89 message.push_str(interp.inspect_type_name_for_value(other));
90 return Err(TypeError::from(message).into());
91 }
92 } else {
93 let len = implicitly_convert_to_int(interp, array_or_len)?;
94 let len = usize::try_from(len).map_err(|_| ArgumentError::with_message("negative array size"))?;
95 if default.is_some() {
96 interp.warn(b"warning: block supersedes default value argument")?;
97 }
98 let mut buffer = Array::with_capacity(len);
99 for idx in 0..len {
100 let idx = i64::try_from(idx)
101 .map_err(|_| RangeError::with_message("bignum too big to convert into `long'"))?;
102 let idx = interp.convert(idx);
103 let elem = block.yield_arg(interp, &idx)?;
104 buffer.push(elem);
105 }
106 buffer
107 }
108 }
109 }
110 (None, None, _) => Array::new(),
111 (None, Some(_), _) => {
112 let err_msg = "default cannot be set if first arg is missing in Array#initialize";
113 return Err(Fatal::from(err_msg).into());
114 }
115 };
116 Ok(ary)
117}
118
119pub fn repeat(ary: &Array, n: usize) -> Result<Array, ArgumentError> {
120 ary.repeat(n)
121 .ok_or_else(|| ArgumentError::with_message("argument too big"))
122}
123
124pub fn join(interp: &mut Artichoke, ary: &Array, sep: &[u8]) -> Result<Vec<u8>, Error> {
125 fn flatten(interp: &mut Artichoke, mut value: Value, out: &mut Vec<Vec<u8>>) -> Result<(), Error> {
126 match value.ruby_type() {
127 Ruby::Array => {
128 let ary = unsafe { Array::unbox_from_value(&mut value, interp)? };
129 out.reserve(ary.len());
130 for elem in &*ary {
131 flatten(interp, elem, out)?;
132 }
133 }
134 Ruby::Fixnum => {
135 let mut buf = String::new();
136 let int = unsafe { sys::mrb_sys_fixnum_to_cint(value.inner()) };
137 write!(&mut buf, "{int}").map_err(WriteError::from)?;
138 out.push(buf.into_bytes());
139 }
140 Ruby::Float => {
141 let float = unsafe { sys::mrb_sys_float_to_cdouble(value.inner()) };
142 let mut buf = String::new();
143 write!(&mut buf, "{float}").map_err(WriteError::from)?;
144 out.push(buf.into_bytes());
145 }
146 _ => {
147 if let Ok(s) = unsafe { implicitly_convert_to_string(interp, &mut value) } {
152 out.push(s.to_vec());
153 } else {
154 out.push(value.to_s(interp));
155 }
156 }
157 }
158 Ok(())
159 }
160
161 let mut vec = Vec::with_capacity(ary.len());
162 for elem in ary {
163 flatten(interp, elem, &mut vec)?;
164 }
165
166 Ok(bstr::join(sep, vec))
167}
168
169fn aref(interp: &mut Artichoke, ary: &Array, index: Value, len: Option<Value>) -> Result<Option<Value>, Error> {
170 let (index, len) = match args::element_reference(interp, index, len, ary.len())? {
171 args::ElementReference::Empty => return Ok(None),
172 args::ElementReference::Index(index) => (index, None),
173 args::ElementReference::StartLen(index, len) => (index, Some(len)),
174 };
175 let start = if let Some(start) = aref::offset_to_index(index, ary.len()) {
176 start
177 } else {
178 return Ok(None);
179 };
180 if start > ary.len() {
181 return Ok(None);
182 }
183 if let Some(len) = len {
184 let result = ary.slice(start, len);
185 let result = Array::alloc_value(result.into(), interp)?;
186 Ok(Some(result))
187 } else {
188 Ok(ary.get(start))
189 }
190}
191
192fn aset(
193 interp: &mut Artichoke,
194 ary: &mut Array,
195 first: Value,
196 second: Value,
197 third: Option<Value>,
198) -> Result<Value, Error> {
199 let (start, drain, mut elem) = args::element_assignment(interp, first, second, third, ary.len())?;
200
201 if let Some(drain) = drain {
202 if let Ok(other) = unsafe { Array::unbox_from_value(&mut elem, interp) } {
203 ary.set_slice(start, drain, other.as_slice());
204 } else if elem.respond_to(interp, "to_ary")? {
205 let mut other = elem.funcall(interp, "to_ary", &[], None)?;
206 if let Ok(other) = unsafe { Array::unbox_from_value(&mut other, interp) } {
207 ary.set_slice(start, drain, other.as_slice());
208 } else {
209 let mut message = String::from("can't convert ");
210 let name = interp.inspect_type_name_for_value(elem);
211 message.push_str(name);
212 message.push_str(" to Array (");
213 message.push_str(name);
214 message.push_str("#to_ary gives ");
215 message.push_str(interp.inspect_type_name_for_value(other));
216 return Err(TypeError::from(message).into());
217 }
218 } else {
219 ary.set_with_drain(start, drain, elem);
220 }
221 } else {
222 ary.set(start, elem);
223 }
224
225 Ok(elem)
226}
227
228impl BoxUnboxVmValue for Array {
229 type Unboxed = Self;
230 type Guarded = Array;
231
232 const RUBY_TYPE: &'static str = "Array";
233
234 #[expect(
235 clippy::cast_possible_truncation,
236 clippy::cast_sign_loss,
237 reason = "mruby stores sizes as int64_t instead of size_t"
238 )]
239 unsafe fn unbox_from_value<'a>(
240 value: &'a mut Value,
241 interp: &mut Artichoke,
242 ) -> Result<UnboxedValueGuard<'a, Self::Guarded>, Error> {
243 let _ = interp;
244
245 if value.ruby_type() != Ruby::Array {
247 let mut message = String::from("uninitialized ");
248 message.push_str(Self::RUBY_TYPE);
249 return Err(TypeError::from(message).into());
250 }
251
252 let value = value.inner();
253 let ary = sys::mrb_sys_basic_ptr(value).cast::<sys::RArray>();
256
257 let Some(mut ptr) = NonNull::new((*ary).as_.heap.ptr) else {
258 return Ok(UnboxedValueGuard::new(Array::new()));
260 };
261 let length = (*ary).as_.heap.len as usize;
262 let capacity = (*ary).as_.heap.aux.capa as usize;
263
264 let array = Array::from_raw_parts(RawParts {
265 ptr: ptr.as_mut(),
266 length,
267 capacity,
268 });
269
270 Ok(UnboxedValueGuard::new(array))
271 }
272
273 fn alloc_value(value: Self::Unboxed, interp: &mut Artichoke) -> Result<Value, Error> {
274 let RawParts { ptr, length, capacity } = Array::into_raw_parts(value);
275 let value = unsafe {
276 interp.with_ffi_boundary(|mrb| {
277 let length = sys::mrb_int::try_from(length)
287 .expect("Length of an `Array` cannot exceed isize::MAX == i64::MAX == mrb_int::MAX");
288 let capa = sys::mrb_int::try_from(capacity)
289 .expect("Capacity of an `Array` cannot exceed isize::MAX == i64::MAX == mrb_int::MAX");
290 sys::mrb_sys_alloc_rarray(mrb, ptr, length, capa)
291 })?
292 };
293 Ok(interp.protect(value.into()))
294 }
295
296 #[expect(
297 clippy::cast_possible_wrap,
298 reason = "mruby stores sizes as int64_t instead of size_t"
299 )]
300 fn box_into_value(value: Self::Unboxed, into: Value, interp: &mut Artichoke) -> Result<Value, Error> {
301 assert_eq!(
305 into.ruby_type(),
306 Ruby::Array,
307 "Tried to box Array into {:?} value",
308 into.ruby_type()
309 );
310
311 let RawParts { ptr, length, capacity } = Array::into_raw_parts(value);
312 unsafe {
313 sys::mrb_sys_repack_into_rarray(ptr, length as sys::mrb_int, capacity as sys::mrb_int, into.inner());
314 }
315
316 Ok(interp.protect(into))
317 }
318
319 fn free(data: *mut c_void) {
320 let _ = data;
325 unreachable!("<Array as BoxUnboxVmValue>::free is never called");
326 }
327}
328
329impl Deref for UnboxedValueGuard<'_, Array> {
330 type Target = Array;
331
332 fn deref(&self) -> &Self::Target {
333 self.as_inner_ref()
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use crate::test::prelude::*;
340
341 const SUBJECT: &str = "Array";
342 const FUNCTIONAL_TEST: &[u8] = include_bytes!("array_functional_test.rb");
343
344 #[test]
345 fn functional() {
346 let mut interp = interpreter();
347 let result = interp.eval(FUNCTIONAL_TEST);
348 unwrap_or_panic_with_backtrace(&mut interp, SUBJECT, result);
349 let result = interp.eval(b"spec");
350 unwrap_or_panic_with_backtrace(&mut interp, SUBJECT, result);
351 }
352
353 #[test]
354 fn allocated_but_uninitialized_array_can_be_garbage_collected() {
355 let mut interp = interpreter();
356 let test = r"
357 1_000_000.times do
358 Array.allocate
359 end
360 ";
361 let result = interp.eval(test.as_bytes());
362 unwrap_or_panic_with_backtrace(&mut interp, SUBJECT, result);
363 interp.full_gc().unwrap();
364 }
365
366 #[test]
367 fn allocated_but_uninitialized_array_can_be_read() {
368 let mut interp = interpreter();
369 let test = r"
385 a = Array.allocate
386 raise 'Array.allocate is not an instance of Array' unless a.is_a?(Array)
387 raise 'Array.allocate is not empty' unless a.empty?
388 raise 'Array.allocate.size is not 0' unless a.size == 0
389 raise 'Array.allocate.inspect is not a String' unless a.inspect.is_a?(String)
390 raise 'Array.allocate.inspect is not empty' unless a.inspect == '[]'
391 ";
392 let result = interp.eval(test.as_bytes());
393 unwrap_or_panic_with_backtrace(&mut interp, SUBJECT, result);
394 }
395
396 #[test]
397 fn allocated_but_uninitialized_array_can_be_modified() {
398 let mut interp = interpreter();
399 let test = r#"
413 a = Array.allocate
414 a.push(1)
415 a.push(2)
416 raise "expected 2 elements" unless a.size == 2
417 raise "array had unexpected contents" unless a == [1, 2]
418 "#;
419 let result = interp.eval(test.as_bytes());
420 unwrap_or_panic_with_backtrace(&mut interp, SUBJECT, result);
421 }
422
423 #[test]
424 fn reinitializing_a_frozen_array_raises_frozen_error() {
425 let mut interp = interpreter();
426 let test = r"
433 x = [1, 2, 3].freeze
434 begin
435 x.send(:initialize)
436 rescue FrozenError
437 # expected
438 else
439 raise 'expected FrozenError for zero arg form'
440 end
441
442 begin
443 x.send(:initialize, 3)
444 rescue FrozenError
445 # expected
446 else
447 raise 'expected FrozenError for 1 arg form'
448 end
449
450 begin
451 x.send(:initialize, 3, 4)
452 rescue FrozenError
453 # expected
454 else
455 raise 'expected FrozenError for 2 arg form'
456 end
457
458 begin
459 x.send(:initialize, 3) { 2 }
460 rescue FrozenError
461 # expected
462 else
463 raise 'expected FrozenError for block form'
464 end
465 ";
466 let result = interp.eval(test.as_bytes());
467 unwrap_or_panic_with_backtrace(&mut interp, SUBJECT, result);
468 }
469
470 #[test]
471 fn reinitializing_an_array_replaces_the_array_contents() {
472 let mut interp = interpreter();
473 let test = r"
492 x = [1, 2, 3]
493 x.send(:initialize)
494 raise 'expected empty array from zero arg form' unless x == []
495
496 x = [1, 2, 3]
497 x.send(:initialize, 5)
498 raise 'expected array of nils from 1 arg form' unless x == [nil, nil, nil, nil, nil]
499 x.send(:initialize, 0)
500 raise 'expected empty array from 1 arg form with zero val' unless x == []
501
502 x = [1, 2, 3]
503 x.send(:initialize, 5, 4)
504 raise 'expected array of 4s from 2 arg form' unless x == [4, 4, 4, 4, 4]
505 x.send(:initialize, 0, 4)
506 raise 'expected empty array from 2 arg form with zero val' unless x == []
507
508 x = [1, 2, 3]
509 x.send(:initialize, 5) { 2 }
510 raise 'expected array of 2s from block form' unless x == [2, 2, 2, 2, 2]
511 x.send(:initialize, 0) { 2 }
512 raise 'expected empty array from block form with zero val' unless x == []
513 ";
514 let result = interp.eval(test.as_bytes());
515 unwrap_or_panic_with_backtrace(&mut interp, SUBJECT, result);
516 }
517}