Skip to content

Commit f0a7e32

Browse files
peter-jerry-yebobzhang
authored andcommitted
fix(array): respect capacity when growing
1 parent 1867620 commit f0a7e32

6 files changed

Lines changed: 409 additions & 163 deletions

File tree

builtin/array.mbt

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -394,33 +394,6 @@ pub impl[T] Add for Array[T] with fn add(self, other) {
394394
}
395395
}
396396

397-
///|
398-
/// Appends all elements from one array to the end of another array. The elements
399-
/// are added in-place, modifying the original array.
400-
///
401-
/// Parameters:
402-
///
403-
/// * `self` : The array to append to.
404-
/// * `other` : The array whose elements will be appended.
405-
///
406-
/// Example:
407-
///
408-
/// ```mbt check
409-
/// test {
410-
/// let v1 = [1, 2, 3]
411-
/// let v2 : ReadOnlyArray[Int] = [4, 5, 6]
412-
/// v1.append(v2)
413-
/// debug_inspect(v1, content="[1, 2, 3, 4, 5, 6]")
414-
/// let v1 = [1, 2, 3]
415-
/// let v2 : ReadOnlyArray[Int] = []
416-
/// v1.append(v2)
417-
/// debug_inspect(v1, content="[1, 2, 3]")
418-
/// }
419-
/// ```
420-
pub fn[T] Array::append(self : Array[T], other : ArrayView[T]) -> Unit {
421-
other.blit_to(self, dst_offset=self.length())
422-
}
423-
424397
///|
425398
/// Iterates through each element of the array in order, applying the given
426399
/// function to each element.

builtin/array_block.mbt

Lines changed: 20 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -107,106 +107,6 @@ pub fn[A] Array::unsafe_blit_fixed(
107107
)
108108
}
109109

110-
///|
111-
/// Copies elements from one array to another array, with support for growing the
112-
/// destination array if needed. The arrays may overlap, in which case the copy
113-
/// is performed in a way that preserves the data.
114-
///
115-
/// Parameters:
116-
///
117-
/// * `self` : The array to copy elements from.
118-
/// * `dst` : The array to copy elements to. Will be automatically grown
119-
/// if needed to accommodate the copied elements.
120-
/// * `len` : The number of elements to copy.
121-
/// * `src_offset` : Starting index in the source array. Defaults to 0.
122-
/// * `dst_offset` : Starting index in the destination array. Defaults to
123-
/// 0.
124-
///
125-
/// Example:
126-
///
127-
/// ```mbt check
128-
/// test {
129-
/// let src = [1, 2, 3, 4, 5]
130-
/// let dst = [0, 0]
131-
/// src[:3].blit_to(dst, dst_offset=1)
132-
/// @debug.debug_inspect(dst, content="[0, 1, 2, 3]")
133-
/// }
134-
/// ```
135-
///
136-
/// Panics if:
137-
///
138-
/// * `len` is negative
139-
/// * `src_offset` is negative
140-
/// * `dst_offset` is negative
141-
/// * `dst_offset` exceeds the length of destination array
142-
/// * `src_offset + len` exceeds the length of source array
143-
// TODO: make len optional and deprecate it
144-
#label_migration(src_offset, fill=false, msg="Use ArrayView::blit_to instead")
145-
#label_migration(len, fill=false, msg="Use ArrayView::blit_to instead")
146-
pub fn[A] Array::blit_to(
147-
self : Array[A],
148-
dst : Array[A],
149-
len? : Int = self.length(),
150-
src_offset? : Int = 0,
151-
dst_offset? : Int = 0,
152-
) -> Unit {
153-
guard len >= 0 &&
154-
dst_offset >= 0 &&
155-
src_offset >= 0 &&
156-
dst_offset <= dst.length() &&
157-
src_offset + len <= self.length()
158-
if dst_offset + len > dst.length() {
159-
dst.unsafe_grow_to_length(dst_offset + len)
160-
}
161-
Array::unsafe_blit(dst, dst_offset, self, src_offset, len)
162-
}
163-
164-
///|
165-
/// Copies all elements from an array view to a destination array, with support
166-
/// for growing the destination array if needed.
167-
///
168-
/// Parameters:
169-
///
170-
/// * `self` : The array view to copy elements from.
171-
/// * `dst` : The array to copy elements to. Will be automatically grown
172-
/// if needed to accommodate the copied elements.
173-
/// * `dst_offset` : Starting index in the destination array. Defaults to 0.
174-
///
175-
/// Example:
176-
///
177-
/// ```mbt check
178-
/// test {
179-
/// let src = [1, 2, 3, 4, 5]
180-
/// let view = src[1:4] // view = [2, 3, 4]
181-
/// let dst = [0, 0]
182-
/// view.blit_to(dst, dst_offset=1)
183-
/// @debug.debug_inspect(dst, content="[0, 2, 3, 4]")
184-
/// }
185-
/// ```
186-
///
187-
/// Panics if:
188-
///
189-
/// * `dst_offset` is negative
190-
/// * `dst_offset` exceeds the length of destination array
191-
pub fn[A] ArrayView::blit_to(
192-
self : ArrayView[A],
193-
dst : Array[A],
194-
dst_offset? : Int = 0,
195-
) -> Unit {
196-
let len = self.len()
197-
guard dst_offset >= 0 && dst_offset <= dst.length()
198-
if dst_offset + len > dst.length() {
199-
dst.unsafe_grow_to_length(dst_offset + len)
200-
}
201-
UninitializedArray::unsafe_blit(
202-
dst.buffer(),
203-
dst_offset,
204-
self.buf(),
205-
self.start(),
206-
len,
207-
)
208-
}
209-
210110
///|
211111
test "Array::blit_to/basic" {
212112
let src = [1, 2, 3, 4, 5]
@@ -235,6 +135,18 @@ test "Array::blit_to/grow_destination" {
235135
assert_true(dst == [0, 1, 2, 3])
236136
}
237137

138+
///|
139+
#warnings("-deprecated")
140+
test "Array::blit_to/grow_destination_directly" {
141+
let src = [1, 2, 3, 4]
142+
let dst = [0]
143+
Array::blit_to(src, dst, len=2, src_offset=1, dst_offset=1)
144+
assert_true(dst == [0, 2, 3])
145+
let self = [1, 2, 3]
146+
Array::blit_to(self, self, len=2, dst_offset=3)
147+
assert_true(self == [1, 2, 3, 1, 2])
148+
}
149+
238150
///|
239151
test "Array::blit_to/edge_cases" {
240152
// Test with src_offset and dst_offset
@@ -289,6 +201,14 @@ test "panic Array::blit_to/boundary_cases4" {
289201
ignore(src[0:5].blit_to(dst, dst_offset=6))
290202
}
291203

204+
///|
205+
#warnings("-deprecated")
206+
test "panic Array::blit_to/reject_overflowed_source_range" {
207+
let src = [1]
208+
let dst = [0]
209+
Array::blit_to(src, dst, len=0x7fffffff, src_offset=1)
210+
}
211+
292212
///|
293213
/// TODO
294214
/// 1. allow skip

builtin/array_make_blit_bench_test.mbt

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,56 @@ test "bench ArrayView::add Ref n=500000+500000" (it : @bench.T) {
5656
it.bench(fn() { it.keep(left_view + right_view) })
5757
}
5858

59+
///|
60+
test "bench Array::blit_to Ref n=1000000 overwrite" (it : @bench.T) {
61+
let src = make_blit_ref_array(make_blit_ref_bench_size)
62+
let dst = Array::make(make_blit_ref_bench_size, "dst")
63+
it.bench(fn() {
64+
src.blit_to(dst)
65+
it.keep(dst)
66+
})
67+
}
68+
69+
///|
70+
test "bench Array::blit_to Ref n=1000000 grow" (it : @bench.T) {
71+
let src = make_blit_ref_array(make_blit_ref_bench_size)
72+
it.bench(fn() {
73+
let dst : Array[String] = []
74+
src.blit_to(dst)
75+
it.keep(dst)
76+
})
77+
}
78+
79+
///|
80+
test "bench ArrayView::blit_to Ref n=1000000 overwrite" (it : @bench.T) {
81+
let src = make_blit_ref_array(make_blit_ref_bench_size)[:]
82+
let dst = Array::make(make_blit_ref_bench_size, "dst")
83+
it.bench(fn() {
84+
src.blit_to(dst)
85+
it.keep(dst)
86+
})
87+
}
88+
89+
///|
90+
test "bench ArrayView::blit_to Ref n=1000000 grow" (it : @bench.T) {
91+
let src = make_blit_ref_array(make_blit_ref_bench_size)[:]
92+
it.bench(fn() {
93+
let dst : Array[String] = []
94+
src.blit_to(dst)
95+
it.keep(dst)
96+
})
97+
}
98+
99+
///|
100+
test "bench Array::append Ref n=1000000 grow" (it : @bench.T) {
101+
let src = make_blit_ref_array(make_blit_ref_bench_size)[:]
102+
it.bench(fn() {
103+
let dst : Array[String] = []
104+
dst.append(src)
105+
it.keep(dst)
106+
})
107+
}
108+
59109
///|
60110
test "bench Array::push Ref n=1000000 resize" (it : @bench.T) {
61111
it.bench(fn() {

builtin/array_nonjs_test.mbt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,34 @@ test "array_capacity" {
1818
assert_true(arr.capacity() >= 10)
1919
}
2020

21+
///|
22+
test "array_append_reuses_capacity" {
23+
let arr = Array::new(capacity=4)
24+
arr.push(1)
25+
arr.append([2, 3])
26+
inspect(arr.capacity(), content="4")
27+
debug_inspect(arr, content="[1, 2, 3]")
28+
}
29+
30+
///|
31+
test "array_append_grows_geometrically" {
32+
let arr = Array::new(capacity=4)
33+
arr.append([1, 2, 3, 4, 5])
34+
inspect(arr.capacity(), content="8")
35+
debug_inspect(arr, content="[1, 2, 3, 4, 5]")
36+
}
37+
38+
///|
39+
test "array_blit_to_reuses_and_grows_capacity" {
40+
let dst = Array::new(capacity=4)
41+
dst.push(0)
42+
[1, 2][:].blit_to(dst, dst_offset=1)
43+
inspect(dst.capacity(), content="4")
44+
[3, 4][:].blit_to(dst, dst_offset=3)
45+
inspect(dst.capacity(), content="8")
46+
debug_inspect(dst, content="[0, 1, 2, 3, 4]")
47+
}
48+
2149
///|
2250
test "array_retain" {
2351
let arr = [1, 2, 3, 4, 5]

builtin/arraycore_js.mbt

Lines changed: 98 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,19 @@ extern "js" fn JSArray::set_length(self : JSArray, new_len : Int) -> Unit =
5050
extern "js" fn JSArray::push(self : JSArray, value : JSValue) -> Unit =
5151
#| (arr, val) => { arr.push(val); }
5252

53+
///|
54+
extern "js" fn JSArray::append_view(
55+
self : JSArray,
56+
src : JSArray,
57+
src_offset : Int,
58+
len : Int,
59+
) -> Unit =
60+
#| (dst, src, src_offset, len) => {
61+
#| for (let i = 0; i < len; i++) {
62+
#| dst.push(src[src_offset + i]);
63+
#| }
64+
#| }
65+
5366
///|
5467
extern "js" fn JSArray::pop(self : JSArray) -> JSValue =
5568
#| (arr) => arr.pop()
@@ -248,6 +261,91 @@ pub fn[T] Array::push(self : Array[T], value : T) -> Unit {
248261
JSArray::ofAnyArray(self).push(JSValue::ofAny(value))
249262
}
250263

264+
///|
265+
/// Appends all elements from one array to the end of another array. The elements
266+
/// are added in-place, modifying the original array.
267+
///
268+
/// Parameters:
269+
///
270+
/// * `self` : The array to append to.
271+
/// * `other` : The array whose elements will be appended.
272+
///
273+
/// Example:
274+
///
275+
/// ```mbt check
276+
/// test {
277+
/// let v1 = [1, 2, 3]
278+
/// let v2 : ReadOnlyArray[Int] = [4, 5, 6]
279+
/// v1.append(v2)
280+
/// debug_inspect(v1, content="[1, 2, 3, 4, 5, 6]")
281+
/// let v1 = [1, 2, 3]
282+
/// let v2 : ReadOnlyArray[Int] = []
283+
/// v1.append(v2)
284+
/// debug_inspect(v1, content="[1, 2, 3]")
285+
/// }
286+
/// ```
287+
pub fn[T] Array::append(self : Array[T], other : ArrayView[T]) -> Unit {
288+
let old_len = self.length()
289+
let append_len = other.len()
290+
guard old_len + append_len >= old_len
291+
JSArray::ofAnyArray(self).append_view(
292+
JSArray::ofAnyFixedArray(other.buf().0),
293+
other.start(),
294+
append_len,
295+
)
296+
}
297+
298+
///|
299+
/// JavaScript arrays use their native length as the writable extent, so extend
300+
/// it before blitting into the new range.
301+
#label_migration(src_offset, fill=false, msg="Use ArrayView::blit_to instead")
302+
#label_migration(len, fill=false, msg="Use ArrayView::blit_to instead")
303+
pub fn[A] Array::blit_to(
304+
self : Array[A],
305+
dst : Array[A],
306+
len? : Int = self.length(),
307+
src_offset? : Int = 0,
308+
dst_offset? : Int = 0,
309+
) -> Unit {
310+
let old_len = dst.length()
311+
guard len >= 0 &&
312+
dst_offset >= 0 &&
313+
src_offset >= 0 &&
314+
dst_offset <= old_len &&
315+
len <= self.length() - src_offset
316+
let new_len = dst_offset + len
317+
guard new_len >= 0
318+
if new_len > old_len {
319+
JSArray::ofAnyArray(dst).set_length(new_len)
320+
}
321+
Array::unsafe_blit(dst, dst_offset, self, src_offset, len)
322+
}
323+
324+
///|
325+
/// JavaScript arrays use their native length as the writable extent, so extend
326+
/// it before blitting into the new range.
327+
pub fn[A] ArrayView::blit_to(
328+
self : ArrayView[A],
329+
dst : Array[A],
330+
dst_offset? : Int = 0,
331+
) -> Unit {
332+
let len = self.len()
333+
let old_len = dst.length()
334+
guard dst_offset >= 0 && dst_offset <= old_len
335+
let new_len = dst_offset + len
336+
guard new_len >= 0
337+
if new_len > old_len {
338+
JSArray::ofAnyArray(dst).set_length(new_len)
339+
}
340+
UninitializedArray::unsafe_blit(
341+
dst.buffer(),
342+
dst_offset,
343+
self.buf(),
344+
self.start(),
345+
len,
346+
)
347+
}
348+
251349
///|
252350
/// Removes the last element from an array and returns it, or `None` if it is empty.
253351
///
@@ -354,18 +452,6 @@ pub fn[T] Array::insert(self : Array[T], index : Int, value : T) -> Unit {
354452
let _ = JSArray::ofAnyArray(self).splice1(index, 0, JSValue::ofAny(value))
355453
}
356454

357-
///|
358-
/// Resize the array in-place so that `len` is equal to `new_len`.
359-
///
360-
/// If `new_len` is greater than `len`, the array will be extended by the
361-
/// difference, and the values in the new slots are left uninitilized.
362-
/// If `new_len` is less than `len`, it will panic
363-
///
364-
fn[T] Array::unsafe_grow_to_length(self : Array[T], new_len : Int) -> Unit {
365-
guard new_len >= self.length()
366-
JSArray::ofAnyArray(self).set_length(new_len)
367-
}
368-
369455
///|
370456
/// Fills an Array with a specified value.
371457
///

0 commit comments

Comments
 (0)