Repository: typst/typst
File: crates/typst-library/src/foundations/array.rs (Rust)

Code before:
        ///   "length",
        /// ).sorted(
        ///   key: s => s.len(),
        ///   compare: (x, y) => y - x,
        /// )
        /// ```
        #[named]
        compare: Option<Func>,
    ) -> SourceResult<Array> {
        let mut result = Ok(());
        let mut vec = self.0;
        let mut compare = |x: Value, y: Value| {
            let mut key_of = |x: Value| match &key {
                // NOTE: We are relying on `comemo`'s memoization of function
                // evaluation to not excessively reevaluate the `key`.
                Some(f) => f.call(engine, context, [x]),
                None => Ok(x),
            };
            let x = key_of(x)?;
            let y = key_of(y)?;
            match &compare {
                Some(f) => Ok(match f.call(engine, context, [x, y])? {
                    Value::Int(x) => x.cmp(&0),
                    x => bail!(
                        span,
                        "expected integer from `compare` function; got {}",
                        x.repr()
                    ),
                }),
                None => ops::compare(&x, &y).at(span),
            }
        };
        // We use `glidesort` instead of the standard library sorting algorithm
        // to prevent panics (see https://github.com/typst/typst/pull/5627).
        glidesort::sort_by(vec.make_mut(), |a, b| {
            // Until we get `try` blocks :)
            compare(a.clone(), b.clone()).unwrap_or_else(|err| {
                if result.is_ok() {
                    result = Err(err);
                }
                Ordering::Equal
            })
        });
        result.map(|_| vec.into())
    }

    /// Deduplicates all items in the array.
    ///
    /// Returns a new array with all duplicate items removed. Only the first
    /// element of each duplicate is kept.
    ///

Review comment (suggestion):
```suggestion
                        "expected integer from `compare` function, got {}",
```

Code after:
                    }
                    match by.call(engine, context, [x, y])? {
                        Value::Bool(b) => Ok(b),
                        x => {
                            bail!(
                                span,
                                "expected boolean from `by` function, got {}",
                                x.ty(),
                            )
                        }
                    }
                };
                // If a comparison function is provided, we use `glidesort`
                // instead of the standard library sorting algorithm to prevent
                // panics in case the comparison function does not define a
                // valid order (see https://github.com/typst/typst/pull/5627).
                let mut result = Ok(());
                let mut vec = self.0.into_iter().enumerate().collect::<Vec<_>>();
                glidesort::sort_by(&mut vec, |(i, x), (j, y)| {
                    // Because we use booleans for the comparison function, in
                    // order to keep the sort stable, we need to compare in the
                    // right order.
                    if i < j {
                        // If `x` and `y` appear in this order in the original
                        // array, then we should change their order (i.e.,
                        // return `Ordering::Greater`) iff `y` is strictly less
                        // than `x` (i.e., `compare(x, y)` returns `false`).
                        // Otherwise, we should keep them in the same order
                        // (i.e., return `Ordering::Less`).
                        match are_in_order(x.clone(), y.clone()) {
                            Ok(false) => Ordering::Greater,
                            Ok(true) => Ordering::Less,
                            Err(err) => {
                                if result.is_ok() {
                                    result = Err(err);
                                }
                                Ordering::Equal
                            }
                        }
                    } else {
                        // If `x` and `y` appear in the opposite order in the
                        // original array, then we should change their order
                        // (i.e., return `Ordering::Less`) iff `x` is strictly
                        // less than `y` (i.e., `compare(y, x)` returns
                        // `false`). Otherwise, we should keep them in the same
                        // order (i.e., return `Ordering::Less`).
                        match are_in_order(y.clone(), x.clone()) {
                            Ok(false) => Ordering::Less,
                            Ok(true) => Ordering::Greater,
                            Err(err) => {
                                if result.is_ok() {