diff --git a/src/c/select.php b/src/c/select.php index f5f4d9b2..1fa80d6a 100644 --- a/src/c/select.php +++ b/src/c/select.php @@ -397,3 +397,51 @@ function onlyx( /* HH_FIXME[4110] $first is false implies $result is set to T */ return $result; } + +/** + * Removes the last element from a Container and returns it. + * If the Container is empty, null will be returned. + * + * When an immutable Hack Collection is passed, the result will + * be defined by your version of hhvm and not give the expected results. + * + * For non-empty Containers, see `pop_backx`. + * + * Time complexity: O(1 or N) If the operation can happen in-place, O(1) + * if it must copy the Container, O(N). + * Space complexity: O(1 or N) If the operation can happen in-place, O(1) + * if it must copy the Container, O(N). + */ +function pop_back, Tv>(inout T $container): ?Tv { + if (is_empty($container)) { + return null; + } + /* HH_FIXME[2049] __PHPStdLib */ + /* HH_FIXME[4107] __PHPStdLib */ + return \array_pop(inout $container); +} + +/** + * Removes the last element from a Container and returns it. + * If the Container is empty, an `InvariantException` is thrown. + * + * When an immutable Hack Collection is passed, the result will + * be defined by your version of hhvm and not give the expected results. + * + * For maybe empty Containers, see `pop_back`. + * + * Time complexity: O(1 or N) If the operation can happen in-place, O(1) + * if it must copy the Container, O(N). + * Space complexity: O(1 or N) If the operation can happen in-place, O(1) + * if it must copy the Container, O(N). + */ +function pop_backx, Tv>(inout T $container): Tv { + invariant( + !is_empty($container), + '%s: Expected at least one element', + __FUNCTION__, + ); + /* HH_FIXME[2049] __PHPStdLib */ + /* HH_FIXME[4107] __PHPStdLib */ + return \array_pop(inout $container); +} diff --git a/tests/c/CSelectTest.php b/tests/c/CSelectTest.php index ac2b727e..bd8751a3 100644 --- a/tests/c/CSelectTest.php +++ b/tests/c/CSelectTest.php @@ -790,4 +790,85 @@ public function testOnlyxWithCustomMessage(): void { ) ->toEqual(42); } + + public static function provideTestPopBack( + ): vec<(Container, Container, mixed)> { + return vec[ + tuple(vec[1], vec[], 1), + tuple(vec[1, 2, 3], vec[1, 2], 3), + tuple(vec[], vec[], null), + tuple(vec[null], vec[], null), + tuple(keyset['apple', 'banana'], keyset['apple'], 'banana'), + tuple(varray[1, 2, 3], varray[1, 2], 3), + tuple(dict['a' => 1, 'b' => 2], dict['a' => 1], 2), + tuple(Vector {1, 2, 3}, Vector {1, 2}, 3), + tuple(Set {}, Set {}, null), + ]; + } + + <> + public function testPopBack( + Container $before, + Container $after, + mixed $value, + ): void { + $return_value = C\pop_back(inout $before); + invariant( + $after is KeyedContainer<_, _>, + '->toHaveSameContentAs() takes a KeyedContainer.'. + 'There are currently no Containers in Hack which are not also KeyedContainers.', + ); + expect($before)->toHaveSameContentAs($after); + expect($return_value)->toEqual($value); + } + + public static function provideTestPopBackx( + ): vec<(Container, Container, mixed)> { + return vec[ + tuple(vec[1], vec[], 1), + tuple(vec[1, 2, 3], vec[1, 2], 3), + tuple(keyset['apple', 'banana'], keyset['apple'], 'banana'), + tuple(varray[1, 2, 3], varray[1, 2], 3), + tuple(dict['a' => 1, 'b' => 2], dict['a' => 1], 2), + tuple(Vector {1, 2, 3}, Vector {1, 2}, 3), + tuple(vec[null], vec[], null), + ]; + } + + <> + public function testPopBackx( + Container $before, + Container $after, + mixed $value, + ): void { + $return_value = C\pop_backx(inout $before); + invariant( + $after is KeyedContainer<_, _>, + '->toHaveSameContentAs() takes a KeyedContainer.'. + 'There are currently no Containers in Hack which are not also KeyedContainers.', + ); + expect($before)->toHaveSameContentAs($after); + expect($return_value)->toEqual($value); + } + + public static function provideTestPopBackxThrowsOnEmptyContainers( + ): vec<(Container)> { + return vec[ + tuple(vec[]), + tuple(dict[]), + tuple(keyset[]), + tuple(Vector {}), + tuple(Map {}), + ]; + } + + <> + public function testPopBackxThrowOnEmptyContainers( + Container $container, + ): void { + expect(() ==> C\pop_backx(inout $container))->toThrow( + InvariantException::class, + 'Expected at least one element', + ); + } }