fix(tvix/eval): Inline List.sort_by, and propagate errors
In order to correctly propagate errors in the comparator passed to builtins.sort, we need to do all the sorting in a context where we can short-circuit return `Value`s (because catchables are Values on the `Ok` side of the Result , not `Err`s). Unfortunately this means we have to *inline* the List `sort_by` implementation into the builtin_sort function - fortunately this is the only place that was called so this is relatively low cost. This does that, and adds the requisite `try_value!` invocation to allow us to propagate comparator errors here. As before, this doesn't include tests, primarily since those are coming in the next commit. Change-Id: I8453c3aa2cd82299eae89828e2a2bb118da4cd48 Reviewed-on: https://cl.tvl.fyi/c/depot/+/10754 Tested-by: BuildkiteCI Reviewed-by: raitobezarius <tvl@lahfa.xyz>
This commit is contained in:
parent
ddb7bc8d18
commit
8446cd1c8b
2 changed files with 45 additions and 53 deletions
|
@ -87,7 +87,7 @@ mod pure_builtins {
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use os_str_bytes::OsStringBytes;
|
use os_str_bytes::OsStringBytes;
|
||||||
|
|
||||||
use crate::{value::PointerEquality, NixContext, NixContextElement};
|
use crate::{value::PointerEquality, AddContext, NixContext, NixContextElement};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
@ -1223,8 +1223,50 @@ mod pure_builtins {
|
||||||
#[builtin("sort")]
|
#[builtin("sort")]
|
||||||
async fn builtin_sort(co: GenCo, comparator: Value, list: Value) -> Result<Value, ErrorKind> {
|
async fn builtin_sort(co: GenCo, comparator: Value, list: Value) -> Result<Value, ErrorKind> {
|
||||||
let list = list.to_list()?;
|
let list = list.to_list()?;
|
||||||
let sorted = list.sort_by(&co, comparator).await?;
|
let mut len = list.len();
|
||||||
Ok(Value::List(sorted))
|
let mut data = list.into_inner();
|
||||||
|
|
||||||
|
// Asynchronous sorting algorithm in which the comparator can make use of
|
||||||
|
// VM requests (required as `builtins.sort` uses comparators written in
|
||||||
|
// Nix).
|
||||||
|
//
|
||||||
|
// This is a simple, optimised bubble sort implementation. The choice of
|
||||||
|
// algorithm is constrained by the comparator in Nix not being able to
|
||||||
|
// yield equality, and us being unable to use the standard library
|
||||||
|
// implementation of sorting (which is a lot longer, but a lot more
|
||||||
|
// efficient) here.
|
||||||
|
// TODO(amjoseph): Investigate potential impl in Nix code, or Tvix bytecode.
|
||||||
|
loop {
|
||||||
|
let mut new_len = 0;
|
||||||
|
for i in 1..len {
|
||||||
|
if try_value!(
|
||||||
|
generators::request_force(
|
||||||
|
&co,
|
||||||
|
generators::request_call_with(
|
||||||
|
&co,
|
||||||
|
comparator.clone(),
|
||||||
|
[data[i].clone(), data[i - 1].clone()],
|
||||||
|
)
|
||||||
|
.await,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
)
|
||||||
|
.as_bool()
|
||||||
|
.context("evaluating comparator in `builtins.sort`")?
|
||||||
|
{
|
||||||
|
data.swap(i, i - 1);
|
||||||
|
new_len = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if new_len == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
len = new_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Value::List(data.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[builtin("splitVersion")]
|
#[builtin("splitVersion")]
|
||||||
|
|
|
@ -6,11 +6,6 @@ use imbl::{vector, Vector};
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::generators;
|
|
||||||
use crate::generators::GenCo;
|
|
||||||
use crate::AddContext;
|
|
||||||
use crate::ErrorKind;
|
|
||||||
|
|
||||||
use super::thunk::ThunkSet;
|
use super::thunk::ThunkSet;
|
||||||
use super::TotalDisplay;
|
use super::TotalDisplay;
|
||||||
use super::Value;
|
use super::Value;
|
||||||
|
@ -78,51 +73,6 @@ impl NixList {
|
||||||
pub fn from_vec(vs: Vec<Value>) -> Self {
|
pub fn from_vec(vs: Vec<Value>) -> Self {
|
||||||
Self(Rc::new(Vector::from_iter(vs)))
|
Self(Rc::new(Vector::from_iter(vs)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Asynchronous sorting algorithm in which the comparator can make use of
|
|
||||||
/// VM requests (required as `builtins.sort` uses comparators written in
|
|
||||||
/// Nix).
|
|
||||||
///
|
|
||||||
/// This is a simple, optimised bubble sort implementation. The choice of
|
|
||||||
/// algorithm is constrained by the comparator in Nix not being able to
|
|
||||||
/// yield equality, and us being unable to use the standard library
|
|
||||||
/// implementation of sorting (which is a lot longer, but a lot more
|
|
||||||
/// efficient) here.
|
|
||||||
// TODO(amjoseph): Investigate potential impl in Nix code, or Tvix bytecode.
|
|
||||||
pub async fn sort_by(self, co: &GenCo, cmp: Value) -> Result<Self, ErrorKind> {
|
|
||||||
let mut len = self.len();
|
|
||||||
let mut data = self.into_inner();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let mut new_len = 0;
|
|
||||||
for i in 1..len {
|
|
||||||
if generators::request_force(
|
|
||||||
co,
|
|
||||||
generators::request_call_with(
|
|
||||||
co,
|
|
||||||
cmp.clone(),
|
|
||||||
[data[i].clone(), data[i - 1].clone()],
|
|
||||||
)
|
|
||||||
.await,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.as_bool()
|
|
||||||
.context("evaluating comparator in `builtins.sort`")?
|
|
||||||
{
|
|
||||||
data.swap(i, i - 1);
|
|
||||||
new_len = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if new_len == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
len = new_len;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(data.into())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoIterator for NixList {
|
impl IntoIterator for NixList {
|
||||||
|
|
Loading…
Reference in a new issue