typst_library/foundations/
ops.rs

1//! Operations on values.
2
3use std::cmp::Ordering;
4
5use ecow::eco_format;
6use typst_utils::Numeric;
7
8use crate::diag::{bail, DeprecationSink, HintedStrResult, StrResult};
9use crate::foundations::{
10    format_str, Datetime, IntoValue, Regex, Repr, SymbolElem, Value,
11};
12use crate::layout::{Alignment, Length, Rel};
13use crate::text::TextElem;
14use crate::visualize::Stroke;
15
16/// Bail with a type mismatch error.
17macro_rules! mismatch {
18    ($fmt:expr, $($value:expr),* $(,)?) => {
19        return Err(eco_format!($fmt, $($value.ty()),*).into())
20    };
21}
22
23/// Join a value with another value.
24pub fn join(lhs: Value, rhs: Value, sink: &mut dyn DeprecationSink) -> StrResult<Value> {
25    use Value::*;
26    Ok(match (lhs, rhs) {
27        (a, None) => a,
28        (None, b) => b,
29        (Symbol(a), Symbol(b)) => Str(format_str!("{a}{b}")),
30        (Str(a), Str(b)) => Str(a + b),
31        (Str(a), Symbol(b)) => Str(format_str!("{a}{b}")),
32        (Symbol(a), Str(b)) => Str(format_str!("{a}{b}")),
33        (Bytes(a), Bytes(b)) => Bytes(a + b),
34        (Content(a), Content(b)) => Content(a + b),
35        (Content(a), Symbol(b)) => Content(a + SymbolElem::packed(b.get())),
36        (Content(a), Str(b)) => Content(a + TextElem::packed(b)),
37        (Str(a), Content(b)) => Content(TextElem::packed(a) + b),
38        (Symbol(a), Content(b)) => Content(SymbolElem::packed(a.get()) + b),
39        (Array(a), Array(b)) => Array(a + b),
40        (Dict(a), Dict(b)) => Dict(a + b),
41        (Args(a), Args(b)) => Args(a + b),
42
43        // Type compatibility.
44        (Type(a), Str(b)) => {
45            warn_type_str_join(sink);
46            Str(format_str!("{a}{b}"))
47        }
48        (Str(a), Type(b)) => {
49            warn_type_str_join(sink);
50            Str(format_str!("{a}{b}"))
51        }
52
53        (a, b) => mismatch!("cannot join {} with {}", a, b),
54    })
55}
56
57/// Apply the unary plus operator to a value.
58pub fn pos(value: Value) -> HintedStrResult<Value> {
59    use Value::*;
60    Ok(match value {
61        Int(v) => Int(v),
62        Float(v) => Float(v),
63        Decimal(v) => Decimal(v),
64        Length(v) => Length(v),
65        Angle(v) => Angle(v),
66        Ratio(v) => Ratio(v),
67        Relative(v) => Relative(v),
68        Fraction(v) => Fraction(v),
69        Symbol(_) | Str(_) | Bytes(_) | Content(_) | Array(_) | Dict(_) | Datetime(_) => {
70            mismatch!("cannot apply unary '+' to {}", value)
71        }
72        Dyn(d) => {
73            if d.is::<Alignment>() {
74                mismatch!("cannot apply unary '+' to {}", d)
75            } else {
76                mismatch!("cannot apply '+' to {}", d)
77            }
78        }
79        v => mismatch!("cannot apply '+' to {}", v),
80    })
81}
82
83/// Compute the negation of a value.
84pub fn neg(value: Value) -> HintedStrResult<Value> {
85    use Value::*;
86    Ok(match value {
87        Int(v) => Int(v.checked_neg().ok_or_else(too_large)?),
88        Float(v) => Float(-v),
89        Decimal(v) => Decimal(-v),
90        Length(v) => Length(-v),
91        Angle(v) => Angle(-v),
92        Ratio(v) => Ratio(-v),
93        Relative(v) => Relative(-v),
94        Fraction(v) => Fraction(-v),
95        Duration(v) => Duration(-v),
96        Datetime(_) => mismatch!("cannot apply unary '-' to {}", value),
97        v => mismatch!("cannot apply '-' to {}", v),
98    })
99}
100
101/// Compute the sum of two values.
102pub fn add(
103    lhs: Value,
104    rhs: Value,
105    sink: &mut dyn DeprecationSink,
106) -> HintedStrResult<Value> {
107    use Value::*;
108    Ok(match (lhs, rhs) {
109        (a, None) => a,
110        (None, b) => b,
111
112        (Int(a), Int(b)) => Int(a.checked_add(b).ok_or_else(too_large)?),
113        (Int(a), Float(b)) => Float(a as f64 + b),
114        (Float(a), Int(b)) => Float(a + b as f64),
115        (Float(a), Float(b)) => Float(a + b),
116
117        (Decimal(a), Decimal(b)) => Decimal(a.checked_add(b).ok_or_else(too_large)?),
118        (Decimal(a), Int(b)) => Decimal(
119            a.checked_add(crate::foundations::Decimal::from(b))
120                .ok_or_else(too_large)?,
121        ),
122        (Int(a), Decimal(b)) => Decimal(
123            crate::foundations::Decimal::from(a)
124                .checked_add(b)
125                .ok_or_else(too_large)?,
126        ),
127
128        (Angle(a), Angle(b)) => Angle(a + b),
129
130        (Length(a), Length(b)) => Length(a + b),
131        (Length(a), Ratio(b)) => Relative(b + a),
132        (Length(a), Relative(b)) => Relative(b + a),
133
134        (Ratio(a), Length(b)) => Relative(a + b),
135        (Ratio(a), Ratio(b)) => Ratio(a + b),
136        (Ratio(a), Relative(b)) => Relative(b + a),
137
138        (Relative(a), Length(b)) => Relative(a + b),
139        (Relative(a), Ratio(b)) => Relative(a + b),
140        (Relative(a), Relative(b)) => Relative(a + b),
141
142        (Fraction(a), Fraction(b)) => Fraction(a + b),
143
144        (Symbol(a), Symbol(b)) => Str(format_str!("{a}{b}")),
145        (Str(a), Str(b)) => Str(a + b),
146        (Str(a), Symbol(b)) => Str(format_str!("{a}{b}")),
147        (Symbol(a), Str(b)) => Str(format_str!("{a}{b}")),
148        (Bytes(a), Bytes(b)) => Bytes(a + b),
149        (Content(a), Content(b)) => Content(a + b),
150        (Content(a), Symbol(b)) => Content(a + SymbolElem::packed(b.get())),
151        (Content(a), Str(b)) => Content(a + TextElem::packed(b)),
152        (Str(a), Content(b)) => Content(TextElem::packed(a) + b),
153        (Symbol(a), Content(b)) => Content(SymbolElem::packed(a.get()) + b),
154
155        (Array(a), Array(b)) => Array(a + b),
156        (Dict(a), Dict(b)) => Dict(a + b),
157        (Args(a), Args(b)) => Args(a + b),
158
159        (Color(color), Length(thickness)) | (Length(thickness), Color(color)) => {
160            Stroke::from_pair(color, thickness).into_value()
161        }
162        (Gradient(gradient), Length(thickness))
163        | (Length(thickness), Gradient(gradient)) => {
164            Stroke::from_pair(gradient, thickness).into_value()
165        }
166        (Tiling(tiling), Length(thickness)) | (Length(thickness), Tiling(tiling)) => {
167            Stroke::from_pair(tiling, thickness).into_value()
168        }
169
170        (Duration(a), Duration(b)) => Duration(a + b),
171        (Datetime(a), Duration(b)) => Datetime(a + b),
172        (Duration(a), Datetime(b)) => Datetime(b + a),
173
174        // Type compatibility.
175        (Type(a), Str(b)) => {
176            warn_type_str_add(sink);
177            Str(format_str!("{a}{b}"))
178        }
179        (Str(a), Type(b)) => {
180            warn_type_str_add(sink);
181            Str(format_str!("{a}{b}"))
182        }
183
184        (Dyn(a), Dyn(b)) => {
185            // Alignments can be summed.
186            if let (Some(&a), Some(&b)) =
187                (a.downcast::<Alignment>(), b.downcast::<Alignment>())
188            {
189                return Ok((a + b)?.into_value());
190            }
191
192            mismatch!("cannot add {} and {}", a, b);
193        }
194
195        (a, b) => mismatch!("cannot add {} and {}", a, b),
196    })
197}
198
199/// Compute the difference of two values.
200pub fn sub(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
201    use Value::*;
202    Ok(match (lhs, rhs) {
203        (Int(a), Int(b)) => Int(a.checked_sub(b).ok_or_else(too_large)?),
204        (Int(a), Float(b)) => Float(a as f64 - b),
205        (Float(a), Int(b)) => Float(a - b as f64),
206        (Float(a), Float(b)) => Float(a - b),
207
208        (Decimal(a), Decimal(b)) => Decimal(a.checked_sub(b).ok_or_else(too_large)?),
209        (Decimal(a), Int(b)) => Decimal(
210            a.checked_sub(crate::foundations::Decimal::from(b))
211                .ok_or_else(too_large)?,
212        ),
213        (Int(a), Decimal(b)) => Decimal(
214            crate::foundations::Decimal::from(a)
215                .checked_sub(b)
216                .ok_or_else(too_large)?,
217        ),
218
219        (Angle(a), Angle(b)) => Angle(a - b),
220
221        (Length(a), Length(b)) => Length(a - b),
222        (Length(a), Ratio(b)) => Relative(-b + a),
223        (Length(a), Relative(b)) => Relative(-b + a),
224
225        (Ratio(a), Length(b)) => Relative(a + -b),
226        (Ratio(a), Ratio(b)) => Ratio(a - b),
227        (Ratio(a), Relative(b)) => Relative(-b + a),
228
229        (Relative(a), Length(b)) => Relative(a + -b),
230        (Relative(a), Ratio(b)) => Relative(a + -b),
231        (Relative(a), Relative(b)) => Relative(a - b),
232
233        (Fraction(a), Fraction(b)) => Fraction(a - b),
234
235        (Duration(a), Duration(b)) => Duration(a - b),
236        (Datetime(a), Duration(b)) => Datetime(a - b),
237        (Datetime(a), Datetime(b)) => Duration((a - b)?),
238
239        (a, b) => mismatch!("cannot subtract {1} from {0}", a, b),
240    })
241}
242
243/// Compute the product of two values.
244pub fn mul(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
245    use Value::*;
246    Ok(match (lhs, rhs) {
247        (Int(a), Int(b)) => Int(a.checked_mul(b).ok_or_else(too_large)?),
248        (Int(a), Float(b)) => Float(a as f64 * b),
249        (Float(a), Int(b)) => Float(a * b as f64),
250        (Float(a), Float(b)) => Float(a * b),
251
252        (Decimal(a), Decimal(b)) => Decimal(a.checked_mul(b).ok_or_else(too_large)?),
253        (Decimal(a), Int(b)) => Decimal(
254            a.checked_mul(crate::foundations::Decimal::from(b))
255                .ok_or_else(too_large)?,
256        ),
257        (Int(a), Decimal(b)) => Decimal(
258            crate::foundations::Decimal::from(a)
259                .checked_mul(b)
260                .ok_or_else(too_large)?,
261        ),
262
263        (Length(a), Int(b)) => Length(a * b as f64),
264        (Length(a), Float(b)) => Length(a * b),
265        (Length(a), Ratio(b)) => Length(a * b.get()),
266        (Int(a), Length(b)) => Length(b * a as f64),
267        (Float(a), Length(b)) => Length(b * a),
268        (Ratio(a), Length(b)) => Length(b * a.get()),
269
270        (Angle(a), Int(b)) => Angle(a * b as f64),
271        (Angle(a), Float(b)) => Angle(a * b),
272        (Angle(a), Ratio(b)) => Angle(a * b.get()),
273        (Int(a), Angle(b)) => Angle(a as f64 * b),
274        (Float(a), Angle(b)) => Angle(a * b),
275        (Ratio(a), Angle(b)) => Angle(a.get() * b),
276
277        (Ratio(a), Ratio(b)) => Ratio(a * b),
278        (Ratio(a), Int(b)) => Ratio(a * b as f64),
279        (Ratio(a), Float(b)) => Ratio(a * b),
280        (Int(a), Ratio(b)) => Ratio(a as f64 * b),
281        (Float(a), Ratio(b)) => Ratio(a * b),
282
283        (Relative(a), Int(b)) => Relative(a * b as f64),
284        (Relative(a), Float(b)) => Relative(a * b),
285        (Relative(a), Ratio(b)) => Relative(a * b.get()),
286        (Int(a), Relative(b)) => Relative(a as f64 * b),
287        (Float(a), Relative(b)) => Relative(a * b),
288        (Ratio(a), Relative(b)) => Relative(a.get() * b),
289
290        (Fraction(a), Int(b)) => Fraction(a * b as f64),
291        (Fraction(a), Float(b)) => Fraction(a * b),
292        (Fraction(a), Ratio(b)) => Fraction(a * b.get()),
293        (Int(a), Fraction(b)) => Fraction(a as f64 * b),
294        (Float(a), Fraction(b)) => Fraction(a * b),
295        (Ratio(a), Fraction(b)) => Fraction(a.get() * b),
296
297        (Str(a), Int(b)) => Str(a.repeat(Value::Int(b).cast()?)?),
298        (Int(a), Str(b)) => Str(b.repeat(Value::Int(a).cast()?)?),
299        (Array(a), Int(b)) => Array(a.repeat(Value::Int(b).cast()?)?),
300        (Int(a), Array(b)) => Array(b.repeat(Value::Int(a).cast()?)?),
301        (Content(a), b @ Int(_)) => Content(a.repeat(b.cast()?)),
302        (a @ Int(_), Content(b)) => Content(b.repeat(a.cast()?)),
303
304        (Int(a), Duration(b)) => Duration(b * (a as f64)),
305        (Float(a), Duration(b)) => Duration(b * a),
306        (Duration(a), Int(b)) => Duration(a * (b as f64)),
307        (Duration(a), Float(b)) => Duration(a * b),
308
309        (a, b) => mismatch!("cannot multiply {} with {}", a, b),
310    })
311}
312
313/// Compute the quotient of two values.
314pub fn div(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
315    use Value::*;
316    if is_zero(&rhs) {
317        bail!("cannot divide by zero");
318    }
319
320    Ok(match (lhs, rhs) {
321        (Int(a), Int(b)) => Float(a as f64 / b as f64),
322        (Int(a), Float(b)) => Float(a as f64 / b),
323        (Float(a), Int(b)) => Float(a / b as f64),
324        (Float(a), Float(b)) => Float(a / b),
325
326        (Decimal(a), Decimal(b)) => Decimal(a.checked_div(b).ok_or_else(too_large)?),
327        (Decimal(a), Int(b)) => Decimal(
328            a.checked_div(crate::foundations::Decimal::from(b))
329                .ok_or_else(too_large)?,
330        ),
331        (Int(a), Decimal(b)) => Decimal(
332            crate::foundations::Decimal::from(a)
333                .checked_div(b)
334                .ok_or_else(too_large)?,
335        ),
336
337        (Length(a), Int(b)) => Length(a / b as f64),
338        (Length(a), Float(b)) => Length(a / b),
339        (Length(a), Length(b)) => Float(try_div_length(a, b)?),
340        (Length(a), Relative(b)) if b.rel.is_zero() => Float(try_div_length(a, b.abs)?),
341
342        (Angle(a), Int(b)) => Angle(a / b as f64),
343        (Angle(a), Float(b)) => Angle(a / b),
344        (Angle(a), Angle(b)) => Float(a / b),
345
346        (Ratio(a), Int(b)) => Ratio(a / b as f64),
347        (Ratio(a), Float(b)) => Ratio(a / b),
348        (Ratio(a), Ratio(b)) => Float(a / b),
349        (Ratio(a), Relative(b)) if b.abs.is_zero() => Float(a / b.rel),
350
351        (Relative(a), Int(b)) => Relative(a / b as f64),
352        (Relative(a), Float(b)) => Relative(a / b),
353        (Relative(a), Length(b)) if a.rel.is_zero() => Float(try_div_length(a.abs, b)?),
354        (Relative(a), Ratio(b)) if a.abs.is_zero() => Float(a.rel / b),
355        (Relative(a), Relative(b)) => Float(try_div_relative(a, b)?),
356
357        (Fraction(a), Int(b)) => Fraction(a / b as f64),
358        (Fraction(a), Float(b)) => Fraction(a / b),
359        (Fraction(a), Fraction(b)) => Float(a / b),
360
361        (Duration(a), Int(b)) => Duration(a / (b as f64)),
362        (Duration(a), Float(b)) => Duration(a / b),
363        (Duration(a), Duration(b)) => Float(a / b),
364
365        (a, b) => mismatch!("cannot divide {} by {}", a, b),
366    })
367}
368
369/// Whether a value is a numeric zero.
370fn is_zero(v: &Value) -> bool {
371    use Value::*;
372    match *v {
373        Int(v) => v == 0,
374        Float(v) => v == 0.0,
375        Decimal(v) => v.is_zero(),
376        Length(v) => v.is_zero(),
377        Angle(v) => v.is_zero(),
378        Ratio(v) => v.is_zero(),
379        Relative(v) => v.is_zero(),
380        Fraction(v) => v.is_zero(),
381        Duration(v) => v.is_zero(),
382        _ => false,
383    }
384}
385
386/// Try to divide two lengths.
387fn try_div_length(a: Length, b: Length) -> StrResult<f64> {
388    a.try_div(b).ok_or_else(|| "cannot divide these two lengths".into())
389}
390
391/// Try to divide two relative lengths.
392fn try_div_relative(a: Rel<Length>, b: Rel<Length>) -> StrResult<f64> {
393    a.try_div(b)
394        .ok_or_else(|| "cannot divide these two relative lengths".into())
395}
396
397/// Compute the logical "not" of a value.
398pub fn not(value: Value) -> HintedStrResult<Value> {
399    match value {
400        Value::Bool(b) => Ok(Value::Bool(!b)),
401        v => mismatch!("cannot apply 'not' to {}", v),
402    }
403}
404
405/// Compute the logical "and" of two values.
406pub fn and(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
407    match (lhs, rhs) {
408        (Value::Bool(a), Value::Bool(b)) => Ok(Value::Bool(a && b)),
409        (a, b) => mismatch!("cannot apply 'and' to {} and {}", a, b),
410    }
411}
412
413/// Compute the logical "or" of two values.
414pub fn or(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
415    match (lhs, rhs) {
416        (Value::Bool(a), Value::Bool(b)) => Ok(Value::Bool(a || b)),
417        (a, b) => mismatch!("cannot apply 'or' to {} and {}", a, b),
418    }
419}
420
421/// Compute whether two values are equal.
422pub fn eq(
423    lhs: Value,
424    rhs: Value,
425    sink: &mut dyn DeprecationSink,
426) -> HintedStrResult<Value> {
427    Ok(Value::Bool(equal(&lhs, &rhs, sink)))
428}
429
430/// Compute whether two values are unequal.
431pub fn neq(
432    lhs: Value,
433    rhs: Value,
434    sink: &mut dyn DeprecationSink,
435) -> HintedStrResult<Value> {
436    Ok(Value::Bool(!equal(&lhs, &rhs, sink)))
437}
438
439macro_rules! comparison {
440    ($name:ident, $op:tt, $($pat:tt)*) => {
441        /// Compute how a value compares with another value.
442        pub fn $name(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
443            let ordering = compare(&lhs, &rhs)?;
444            Ok(Value::Bool(matches!(ordering, $($pat)*)))
445        }
446    };
447}
448
449comparison!(lt, "<", Ordering::Less);
450comparison!(leq, "<=", Ordering::Less | Ordering::Equal);
451comparison!(gt, ">", Ordering::Greater);
452comparison!(geq, ">=", Ordering::Greater | Ordering::Equal);
453
454/// Determine whether two values are equal.
455pub fn equal(lhs: &Value, rhs: &Value, sink: &mut dyn DeprecationSink) -> bool {
456    use Value::*;
457    match (lhs, rhs) {
458        // Compare reflexively.
459        (None, None) => true,
460        (Auto, Auto) => true,
461        (Bool(a), Bool(b)) => a == b,
462        (Int(a), Int(b)) => a == b,
463        (Float(a), Float(b)) => a == b,
464        (Decimal(a), Decimal(b)) => a == b,
465        (Length(a), Length(b)) => a == b,
466        (Angle(a), Angle(b)) => a == b,
467        (Ratio(a), Ratio(b)) => a == b,
468        (Relative(a), Relative(b)) => a == b,
469        (Fraction(a), Fraction(b)) => a == b,
470        (Color(a), Color(b)) => a == b,
471        (Symbol(a), Symbol(b)) => a == b,
472        (Version(a), Version(b)) => a == b,
473        (Str(a), Str(b)) => a == b,
474        (Bytes(a), Bytes(b)) => a == b,
475        (Label(a), Label(b)) => a == b,
476        (Content(a), Content(b)) => a == b,
477        (Array(a), Array(b)) => a == b,
478        (Dict(a), Dict(b)) => a == b,
479        (Func(a), Func(b)) => a == b,
480        (Args(a), Args(b)) => a == b,
481        (Type(a), Type(b)) => a == b,
482        (Module(a), Module(b)) => a == b,
483        (Datetime(a), Datetime(b)) => a == b,
484        (Duration(a), Duration(b)) => a == b,
485        (Dyn(a), Dyn(b)) => a == b,
486
487        // Some technically different things should compare equal.
488        (&Int(i), &Float(f)) | (&Float(f), &Int(i)) => i as f64 == f,
489        (&Int(i), &Decimal(d)) | (&Decimal(d), &Int(i)) => {
490            crate::foundations::Decimal::from(i) == d
491        }
492        (&Length(len), &Relative(rel)) | (&Relative(rel), &Length(len)) => {
493            len == rel.abs && rel.rel.is_zero()
494        }
495        (&Ratio(rat), &Relative(rel)) | (&Relative(rel), &Ratio(rat)) => {
496            rat == rel.rel && rel.abs.is_zero()
497        }
498
499        // Type compatibility.
500        (Type(ty), Str(str)) | (Str(str), Type(ty)) => {
501            warn_type_str_equal(sink, str);
502            ty.compat_name() == str.as_str()
503        }
504
505        _ => false,
506    }
507}
508
509/// Compare two values.
510pub fn compare(lhs: &Value, rhs: &Value) -> StrResult<Ordering> {
511    use Value::*;
512    Ok(match (lhs, rhs) {
513        (Bool(a), Bool(b)) => a.cmp(b),
514        (Int(a), Int(b)) => a.cmp(b),
515        (Float(a), Float(b)) => try_cmp_values(a, b)?,
516        (Decimal(a), Decimal(b)) => a.cmp(b),
517        (Length(a), Length(b)) => try_cmp_values(a, b)?,
518        (Angle(a), Angle(b)) => a.cmp(b),
519        (Ratio(a), Ratio(b)) => a.cmp(b),
520        (Relative(a), Relative(b)) => try_cmp_values(a, b)?,
521        (Fraction(a), Fraction(b)) => a.cmp(b),
522        (Version(a), Version(b)) => a.cmp(b),
523        (Str(a), Str(b)) => a.cmp(b),
524
525        // Some technically different things should be comparable.
526        (Int(a), Float(b)) => try_cmp_values(&(*a as f64), b)?,
527        (Float(a), Int(b)) => try_cmp_values(a, &(*b as f64))?,
528        (Int(a), Decimal(b)) => crate::foundations::Decimal::from(*a).cmp(b),
529        (Decimal(a), Int(b)) => a.cmp(&crate::foundations::Decimal::from(*b)),
530        (Length(a), Relative(b)) if b.rel.is_zero() => try_cmp_values(a, &b.abs)?,
531        (Ratio(a), Relative(b)) if b.abs.is_zero() => a.cmp(&b.rel),
532        (Relative(a), Length(b)) if a.rel.is_zero() => try_cmp_values(&a.abs, b)?,
533        (Relative(a), Ratio(b)) if a.abs.is_zero() => a.rel.cmp(b),
534
535        (Duration(a), Duration(b)) => a.cmp(b),
536        (Datetime(a), Datetime(b)) => try_cmp_datetimes(a, b)?,
537        (Array(a), Array(b)) => try_cmp_arrays(a.as_slice(), b.as_slice())?,
538
539        _ => mismatch!("cannot compare {} and {}", lhs, rhs),
540    })
541}
542
543/// Try to compare two values.
544fn try_cmp_values<T: PartialOrd + Repr>(a: &T, b: &T) -> StrResult<Ordering> {
545    a.partial_cmp(b)
546        .ok_or_else(|| eco_format!("cannot compare {} with {}", a.repr(), b.repr()))
547}
548
549/// Try to compare two datetimes.
550fn try_cmp_datetimes(a: &Datetime, b: &Datetime) -> StrResult<Ordering> {
551    a.partial_cmp(b)
552        .ok_or_else(|| eco_format!("cannot compare {} and {}", a.kind(), b.kind()))
553}
554
555/// Try to compare arrays of values lexicographically.
556fn try_cmp_arrays(a: &[Value], b: &[Value]) -> StrResult<Ordering> {
557    a.iter()
558        .zip(b.iter())
559        .find_map(|(first, second)| {
560            match compare(first, second) {
561                // Keep searching for a pair of elements that isn't equal.
562                Ok(Ordering::Equal) => None,
563                // Found a pair which either is not equal or not comparable, so
564                // we stop searching.
565                result => Some(result),
566            }
567        })
568        .unwrap_or_else(|| {
569            // The two arrays are equal up to the shortest array's extent,
570            // so compare their lengths instead.
571            Ok(a.len().cmp(&b.len()))
572        })
573}
574
575/// Test whether one value is "in" another one.
576pub fn in_(
577    lhs: Value,
578    rhs: Value,
579    sink: &mut dyn DeprecationSink,
580) -> HintedStrResult<Value> {
581    if let Some(b) = contains(&lhs, &rhs, sink) {
582        Ok(Value::Bool(b))
583    } else {
584        mismatch!("cannot apply 'in' to {} and {}", lhs, rhs)
585    }
586}
587
588/// Test whether one value is "not in" another one.
589pub fn not_in(
590    lhs: Value,
591    rhs: Value,
592    sink: &mut dyn DeprecationSink,
593) -> HintedStrResult<Value> {
594    if let Some(b) = contains(&lhs, &rhs, sink) {
595        Ok(Value::Bool(!b))
596    } else {
597        mismatch!("cannot apply 'not in' to {} and {}", lhs, rhs)
598    }
599}
600
601/// Test for containment.
602pub fn contains(
603    lhs: &Value,
604    rhs: &Value,
605    sink: &mut dyn DeprecationSink,
606) -> Option<bool> {
607    use Value::*;
608    match (lhs, rhs) {
609        (Str(a), Str(b)) => Some(b.as_str().contains(a.as_str())),
610        (Dyn(a), Str(b)) => a.downcast::<Regex>().map(|regex| regex.is_match(b)),
611        (Str(a), Dict(b)) => Some(b.contains(a)),
612        (a, Array(b)) => Some(b.contains_impl(a, sink)),
613
614        // Type compatibility.
615        (Type(a), Str(b)) => {
616            warn_type_in_str(sink);
617            Some(b.as_str().contains(a.compat_name()))
618        }
619        (Type(a), Dict(b)) => {
620            warn_type_in_dict(sink);
621            Some(b.contains(a.compat_name()))
622        }
623
624        _ => Option::None,
625    }
626}
627
628#[cold]
629fn too_large() -> &'static str {
630    "value is too large"
631}
632
633#[cold]
634fn warn_type_str_add(sink: &mut dyn DeprecationSink) {
635    sink.emit_with_hints(
636        "adding strings and types is deprecated",
637        &["convert the type to a string with `str` first"],
638    );
639}
640
641#[cold]
642fn warn_type_str_join(sink: &mut dyn DeprecationSink) {
643    sink.emit_with_hints(
644        "joining strings and types is deprecated",
645        &["convert the type to a string with `str` first"],
646    );
647}
648
649#[cold]
650fn warn_type_str_equal(sink: &mut dyn DeprecationSink, s: &str) {
651    // Only warn if `s` looks like a type name to prevent false positives.
652    if is_compat_type_name(s) {
653        sink.emit_with_hints(
654            "comparing strings with types is deprecated",
655            &[
656                "compare with the literal type instead",
657                "this comparison will always return `false` in future Typst releases",
658            ],
659        );
660    }
661}
662
663#[cold]
664fn warn_type_in_str(sink: &mut dyn DeprecationSink) {
665    sink.emit_with_hints(
666        "checking whether a type is contained in a string is deprecated",
667        &["this compatibility behavior only exists because `type` used to return a string"],
668    );
669}
670
671#[cold]
672fn warn_type_in_dict(sink: &mut dyn DeprecationSink) {
673    sink.emit_with_hints(
674        "checking whether a type is contained in a dictionary is deprecated",
675        &["this compatibility behavior only exists because `type` used to return a string"],
676    );
677}
678
679fn is_compat_type_name(s: &str) -> bool {
680    matches!(
681        s,
682        "boolean"
683            | "alignment"
684            | "angle"
685            | "arguments"
686            | "array"
687            | "bytes"
688            | "color"
689            | "content"
690            | "counter"
691            | "datetime"
692            | "decimal"
693            | "dictionary"
694            | "direction"
695            | "duration"
696            | "float"
697            | "fraction"
698            | "function"
699            | "gradient"
700            | "integer"
701            | "label"
702            | "length"
703            | "location"
704            | "module"
705            | "pattern"
706            | "ratio"
707            | "regex"
708            | "relative length"
709            | "selector"
710            | "state"
711            | "string"
712            | "stroke"
713            | "symbol"
714            | "tiling"
715            | "type"
716            | "version"
717    )
718}