1use 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
16macro_rules! mismatch {
18 ($fmt:expr, $($value:expr),* $(,)?) => {
19 return Err(eco_format!($fmt, $($value.ty()),*).into())
20 };
21}
22
23pub 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(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
57pub 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
83pub 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
101pub 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(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 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
199pub 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
243pub 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
313pub 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
369fn 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
386fn 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
391fn 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
397pub 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
405pub 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
413pub 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
421pub 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
430pub 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 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
454pub fn equal(lhs: &Value, rhs: &Value, sink: &mut dyn DeprecationSink) -> bool {
456 use Value::*;
457 match (lhs, rhs) {
458 (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 (&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(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
509pub 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 (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
543fn 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
549fn 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
555fn 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 Ok(Ordering::Equal) => None,
563 result => Some(result),
566 }
567 })
568 .unwrap_or_else(|| {
569 Ok(a.len().cmp(&b.len()))
572 })
573}
574
575pub 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
588pub 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
601pub 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(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 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}