rhai/func/
builtin.rs

1//! Built-in implementations for common operators.
2
3#![allow(clippy::float_cmp)]
4
5use super::call::FnCallArgs;
6use super::native::FnBuiltin;
7#[allow(clippy::enum_glob_use)]
8use crate::tokenizer::{Token, Token::*};
9use crate::types::dynamic::Union;
10use crate::{
11    Dynamic, ExclusiveRange, ImmutableString, InclusiveRange, NativeCallContext, RhaiResult,
12    SmartString, INT,
13};
14use std::any::TypeId;
15#[cfg(feature = "no_std")]
16use std::prelude::v1::*;
17
18#[cfg(not(feature = "no_float"))]
19use crate::FLOAT;
20
21#[cfg(not(feature = "no_float"))]
22#[cfg(feature = "no_std")]
23use num_traits::Float;
24
25#[cfg(feature = "decimal")]
26use rust_decimal::Decimal;
27
28/// The `unchecked` feature is not active.
29const CHECKED_BUILD: bool = cfg!(not(feature = "unchecked"));
30
31/// A function that returns `true`.
32#[inline(always)]
33#[allow(clippy::unnecessary_wraps)]
34fn const_true_fn(_: Option<NativeCallContext>, _: &mut [&mut Dynamic]) -> RhaiResult {
35    Ok(Dynamic::TRUE)
36}
37/// A function that returns `false`.
38#[inline(always)]
39#[allow(clippy::unnecessary_wraps)]
40fn const_false_fn(_: Option<NativeCallContext>, _: &mut [&mut Dynamic]) -> RhaiResult {
41    Ok(Dynamic::FALSE)
42}
43/// Returns true if the type is numeric.
44#[inline(always)]
45fn is_numeric(typ: TypeId) -> bool {
46    if typ == TypeId::of::<INT>() {
47        return true;
48    }
49
50    #[cfg(not(feature = "no_float"))]
51    if typ == TypeId::of::<f32>() || typ == TypeId::of::<f64>() {
52        return true;
53    }
54
55    #[cfg(feature = "decimal")]
56    if typ == TypeId::of::<Decimal>() {
57        return true;
58    }
59
60    #[cfg(not(feature = "only_i32"))]
61    #[cfg(not(feature = "only_i64"))]
62    if typ == TypeId::of::<u8>()
63        || typ == TypeId::of::<u16>()
64        || typ == TypeId::of::<u32>()
65        || typ == TypeId::of::<u64>()
66        || typ == TypeId::of::<i8>()
67        || typ == TypeId::of::<i16>()
68        || typ == TypeId::of::<i32>()
69        || typ == TypeId::of::<i64>()
70    {
71        return true;
72    }
73
74    #[cfg(not(feature = "only_i32"))]
75    #[cfg(not(feature = "only_i64"))]
76    #[cfg(not(target_family = "wasm"))]
77    if typ == TypeId::of::<u128>() || typ == TypeId::of::<i128>() {
78        return true;
79    }
80
81    false
82}
83
84/// Build in common binary operator implementations to avoid the cost of calling a registered function.
85///
86/// The return function will be registered as a _method_, so the first parameter cannot be consumed.
87#[must_use]
88pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option<FnBuiltin> {
89    macro_rules! impl_op {
90        ($xx:ident $op:tt $yy:ident) => { Some((|_, args| {
91            let x = &*args[0].read_lock::<$xx>().unwrap();
92            let y = &*args[1].read_lock::<$yy>().unwrap();
93            Ok((x $op y).into())
94        }, false)) };
95        ($xx:ident . $func:ident ( $yy:ty )) => { Some((|_, args| {
96            let x = &*args[0].read_lock::<$xx>().unwrap();
97            let y = &*args[1].read_lock::<$yy>().unwrap();
98            Ok(x.$func(y).into())
99        }, false)) };
100        ($xx:ident . $func:ident ( $yy:ident . $yyy:ident () )) => { Some((|_, args| {
101            let x = &*args[0].read_lock::<$xx>().unwrap();
102            let y = &*args[1].read_lock::<$yy>().unwrap();
103            Ok(x.$func(y.$yyy()).into())
104        }, false)) };
105        ($func:ident ( $op:tt )) => { Some((|_, args| {
106            let (x, y) = $func(args);
107            Ok((x $op y).into())
108        }, false)) };
109        ($base:ty => $xx:ident $op:tt $yy:ident) => { Some((|_, args| {
110            let x = args[0].$xx().unwrap() as $base;
111            let y = args[1].$yy().unwrap() as $base;
112            Ok((x $op y).into())
113        }, false)) };
114        ($base:ty => $xx:ident . $func:ident ( $yy:ident as $yyy:ty)) => { Some((|_, args| {
115            let x = args[0].$xx().unwrap() as $base;
116            let y = args[1].$yy().unwrap() as $base;
117            Ok(x.$func(y as $yyy).into())
118        }, false)) };
119        ($base:ty => Ok($func:ident ( $xx:ident, $yy:ident ))) => { Some((|_, args| {
120            let x = args[0].$xx().unwrap() as $base;
121            let y = args[1].$yy().unwrap() as $base;
122            Ok($func(x, y).into())
123        }, false)) };
124        ($base:ty => $func:ident ( $xx:ident, $yy:ident )) => { Some((|_, args| {
125            let x = args[0].$xx().unwrap() as $base;
126            let y = args[1].$yy().unwrap() as $base;
127            $func(x, y).map(Into::into)
128        }, false)) };
129        (from $base:ty => $xx:ident $op:tt $yy:ident) => { Some((|_, args| {
130            let x = <$base>::from(args[0].$xx().unwrap());
131            let y = <$base>::from(args[1].$yy().unwrap());
132            Ok((x $op y).into())
133        }, false)) };
134        (from $base:ty => $xx:ident . $func:ident ( $yy:ident )) => { Some((|_, args| {
135            let x = <$base>::from(args[0].$xx().unwrap());
136            let y = <$base>::from(args[1].$yy().unwrap());
137            Ok(x.$func(y).into())
138        }, false)) };
139        (from $base:ty => Ok($func:ident ( $xx:ident, $yy:ident ))) => { Some((|_, args| {
140            let x = <$base>::from(args[0].$xx().unwrap());
141            let y = <$base>::from(args[1].$yy().unwrap());
142            Ok($func(x, y).into())
143        }, false)) };
144        (from $base:ty => $func:ident ( $xx:ident, $yy:ident )) => { Some((|_, args| {
145            let x = <$base>::from(args[0].$xx().unwrap());
146            let y = <$base>::from(args[1].$yy().unwrap());
147            $func(x, y).map(Into::into)
148        }, false)) };
149    }
150
151    #[cfg(not(feature = "no_float"))]
152    macro_rules! impl_float {
153        ($xx:ident, $yy:ident) => {
154            return match op {
155                Plus                => impl_op!(FLOAT => $xx + $yy),
156                Minus               => impl_op!(FLOAT => $xx - $yy),
157                Multiply            => impl_op!(FLOAT => $xx * $yy),
158                Divide              => impl_op!(FLOAT => $xx / $yy),
159                Modulo              => impl_op!(FLOAT => $xx % $yy),
160                PowerOf             => impl_op!(FLOAT => $xx.powf($yy as FLOAT)),
161
162                #[cfg(feature = "unchecked")]
163                EqualsTo            => impl_op!(FLOAT => $xx == $yy),
164                #[cfg(not(feature = "unchecked"))]
165                EqualsTo            => Some((|_, args| {
166                    let x = args[0].$xx().unwrap() as FLOAT;
167                    let y = args[1].$yy().unwrap() as FLOAT;
168                    let max = if x * y == 0.0 { 1.0 } else { x.abs().max(y.abs()) };
169                    if max == 0.0 { return Ok(Dynamic::TRUE); }
170                    Ok(((x - y).abs()/max <= FLOAT::EPSILON).into())
171                }, false)),
172
173                #[cfg(feature = "unchecked")]
174                NotEqualsTo         => impl_op!(FLOAT => $xx != $yy),
175                #[cfg(not(feature = "unchecked"))]
176                NotEqualsTo         => Some((|_, args| {
177                    let x = args[0].$xx().unwrap() as FLOAT;
178                    let y = args[1].$yy().unwrap() as FLOAT;
179                    let max = if x * y == 0.0 { 1.0 } else { x.abs().max(y.abs()) };
180                    if max == 0.0 { return Ok(Dynamic::FALSE); }
181                    Ok(((x - y).abs()/max > FLOAT::EPSILON).into())
182                }, false)),
183
184                #[cfg(feature = "unchecked")]
185                GreaterThan         => impl_op!(FLOAT => $xx > $yy),
186                #[cfg(not(feature = "unchecked"))]
187                GreaterThan         => Some((|_, args| {
188                    let x = args[0].$xx().unwrap() as FLOAT;
189                    let y = args[1].$yy().unwrap() as FLOAT;
190                    let max = if x * y == 0.0 { 1.0 } else { x.abs().max(y.abs()) };
191                    if max == 0.0 { return Ok(Dynamic::FALSE); }
192                    Ok(((x - y)/max > FLOAT::EPSILON).into())
193                }, false)),
194
195                #[cfg(feature = "unchecked")]
196                GreaterThanEqualsTo => impl_op!(FLOAT => $xx >= $yy),
197                #[cfg(not(feature = "unchecked"))]
198                GreaterThanEqualsTo => Some((|_, args| {
199                    let x = args[0].$xx().unwrap() as FLOAT;
200                    let y = args[1].$yy().unwrap() as FLOAT;
201                    let max = if x * y == 0.0 { 1.0 } else { x.abs().max(y.abs()) };
202                    if max == 0.0 { return Ok(Dynamic::TRUE); }
203                    Ok(((x - y)/max > -FLOAT::EPSILON).into())
204                }, false)),
205
206                #[cfg(feature = "unchecked")]
207                LessThan            => impl_op!(FLOAT => $xx < $yy),
208                #[cfg(not(feature = "unchecked"))]
209                LessThan            => Some((|_, args| {
210                    let x = args[0].$xx().unwrap() as FLOAT;
211                    let y = args[1].$yy().unwrap() as FLOAT;
212                    let max = if x * y == 0.0 { 1.0 } else { x.abs().max(y.abs()) };
213                    if max == 0.0 { return Ok(Dynamic::FALSE); }
214                    Ok(((y - x)/max > FLOAT::EPSILON).into())
215                }, false)),
216
217                #[cfg(feature = "unchecked")]
218                LessThanEqualsTo    => impl_op!(FLOAT => $xx <= $yy),
219                #[cfg(not(feature = "unchecked"))]
220                LessThanEqualsTo    => Some((|_, args| {
221                    let x = args[0].$xx().unwrap() as FLOAT;
222                    let y = args[1].$yy().unwrap() as FLOAT;
223                    let max = if x * y == 0.0 { 1.0 } else { x.abs().max(y.abs()) };
224                    if max == 0.0 { return Ok(Dynamic::TRUE); }
225                    Ok(((y - x)/max > -FLOAT::EPSILON).into())
226                }, false)),
227
228                _                   => None,
229            }
230        };
231    }
232
233    #[cfg(feature = "decimal")]
234    macro_rules! impl_decimal {
235        ($xx:ident, $yy:ident) => {
236            {
237                #[cfg(not(feature = "unchecked"))]
238                #[allow(clippy::wildcard_imports)]
239                use crate::packages::arithmetic::decimal_functions::builtin::*;
240
241                #[cfg(not(feature = "unchecked"))]
242                match op {
243                    Plus     => return impl_op!(from Decimal => add($xx, $yy)),
244                    Minus    => return impl_op!(from Decimal => subtract($xx, $yy)),
245                    Multiply => return impl_op!(from Decimal => multiply($xx, $yy)),
246                    Divide   => return impl_op!(from Decimal => divide($xx, $yy)),
247                    Modulo   => return impl_op!(from Decimal => modulo($xx, $yy)),
248                    PowerOf  => return impl_op!(from Decimal => power($xx, $yy)),
249                    _        => ()
250                }
251
252                #[cfg(feature = "unchecked")]
253                use rust_decimal::MathematicalOps;
254
255                #[cfg(feature = "unchecked")]
256                match op {
257                    Plus     => return impl_op!(from Decimal => $xx + $yy),
258                    Minus    => return impl_op!(from Decimal => $xx - $yy),
259                    Multiply => return impl_op!(from Decimal => $xx * $yy),
260                    Divide   => return impl_op!(from Decimal => $xx / $yy),
261                    Modulo   => return impl_op!(from Decimal => $xx % $yy),
262                    PowerOf  => return impl_op!(from Decimal => $xx.powd($yy)),
263                    _        => ()
264                }
265
266                return match op {
267                    EqualsTo            => impl_op!(from Decimal => $xx == $yy),
268                    NotEqualsTo         => impl_op!(from Decimal => $xx != $yy),
269                    GreaterThan         => impl_op!(from Decimal => $xx > $yy),
270                    GreaterThanEqualsTo => impl_op!(from Decimal => $xx >= $yy),
271                    LessThan            => impl_op!(from Decimal => $xx < $yy),
272                    LessThanEqualsTo    => impl_op!(from Decimal => $xx <= $yy),
273                    _                   => None
274                };
275            }
276        };
277    }
278
279    // Check for common patterns
280    match (&x.0, &y.0, op) {
281        (Union::Int(..), Union::Int(..), _) => {
282            #[cfg(not(feature = "unchecked"))]
283            #[allow(clippy::wildcard_imports)]
284            use crate::packages::arithmetic::arith_basic::INT::functions::*;
285
286            #[cfg(not(feature = "unchecked"))]
287            match op {
288                Plus => return impl_op!(INT => add(as_int, as_int)),
289                Minus => return impl_op!(INT => subtract(as_int, as_int)),
290                Multiply => return impl_op!(INT => multiply(as_int, as_int)),
291                Divide => return impl_op!(INT => divide(as_int, as_int)),
292                Modulo => return impl_op!(INT => modulo(as_int, as_int)),
293                PowerOf => return impl_op!(INT => power(as_int, as_int)),
294                RightShift => return impl_op!(INT => Ok(shift_right(as_int, as_int))),
295                LeftShift => return impl_op!(INT => Ok(shift_left(as_int, as_int))),
296                Ampersand => return impl_op!(INT => Ok(binary_and(as_int, as_int))),
297                Pipe => return impl_op!(INT => Ok(binary_or(as_int, as_int))),
298                XOr => return impl_op!(INT => Ok(binary_xor(as_int, as_int))),
299                _ => (),
300            }
301
302            #[cfg(feature = "unchecked")]
303            match op {
304                Plus => return impl_op!(INT => as_int + as_int),
305                Minus => return impl_op!(INT => as_int - as_int),
306                Multiply => return impl_op!(INT => as_int * as_int),
307                Divide => return impl_op!(INT => as_int / as_int),
308                Modulo => return impl_op!(INT => as_int % as_int),
309                PowerOf => return impl_op!(INT => as_int.pow(as_int as u32)),
310                RightShift => {
311                    return Some((
312                        |_, args| {
313                            let x = args[0].as_int().unwrap();
314                            let y = args[1].as_int().unwrap();
315                            Ok((if y < 0 { x << -y } else { x >> y }).into())
316                        },
317                        false,
318                    ))
319                }
320                LeftShift => {
321                    return Some((
322                        |_, args| {
323                            let x = args[0].as_int().unwrap();
324                            let y = args[1].as_int().unwrap();
325                            Ok((if y < 0 { x >> -y } else { x << y }).into())
326                        },
327                        false,
328                    ))
329                }
330                Ampersand => return impl_op!(INT => as_int & as_int),
331                Pipe => return impl_op!(INT => as_int | as_int),
332                XOr => return impl_op!(INT => as_int ^ as_int),
333                _ => (),
334            }
335
336            return match op {
337                EqualsTo => impl_op!(INT => as_int == as_int),
338                NotEqualsTo => impl_op!(INT => as_int != as_int),
339                GreaterThan => impl_op!(INT => as_int > as_int),
340                GreaterThanEqualsTo => impl_op!(INT => as_int >= as_int),
341                LessThan => impl_op!(INT => as_int < as_int),
342                LessThanEqualsTo => impl_op!(INT => as_int <= as_int),
343                Ampersand => impl_op!(INT => as_int & as_int),
344                Pipe => impl_op!(INT => as_int | as_int),
345                XOr => impl_op!(INT => as_int ^ as_int),
346                ExclusiveRange => impl_op!(INT => as_int .. as_int),
347                InclusiveRange => impl_op!(INT => as_int ..= as_int),
348                _ => None,
349            };
350        }
351
352        (Union::Bool(..), Union::Bool(..), _) => {
353            return match op {
354                EqualsTo => impl_op!(bool => as_bool == as_bool),
355                NotEqualsTo => impl_op!(bool => as_bool != as_bool),
356                GreaterThan => impl_op!(bool => as_bool > as_bool),
357                GreaterThanEqualsTo => impl_op!(bool => as_bool >= as_bool),
358                LessThan => impl_op!(bool => as_bool < as_bool),
359                LessThanEqualsTo => impl_op!(bool => as_bool <= as_bool),
360                Ampersand => impl_op!(bool => as_bool & as_bool),
361                Pipe => impl_op!(bool => as_bool | as_bool),
362                XOr => impl_op!(bool => as_bool ^ as_bool),
363                _ => None,
364            };
365        }
366
367        (Union::Str(..), Union::Str(..), _) => {
368            return match op {
369                Plus => Some((
370                    |_ctx, args| {
371                        let s1 = &*args[0].as_immutable_string_ref().unwrap();
372                        let s2 = &*args[1].as_immutable_string_ref().unwrap();
373
374                        #[cfg(not(feature = "unchecked"))]
375                        _ctx.unwrap()
376                            .engine()
377                            .throw_on_size((0, 0, s1.len() + s2.len()))?;
378
379                        Ok((s1 + s2).into())
380                    },
381                    CHECKED_BUILD,
382                )),
383                Minus => impl_op!(ImmutableString - ImmutableString),
384                EqualsTo => impl_op!(ImmutableString == ImmutableString),
385                NotEqualsTo => impl_op!(ImmutableString != ImmutableString),
386                GreaterThan => impl_op!(ImmutableString > ImmutableString),
387                GreaterThanEqualsTo => impl_op!(ImmutableString >= ImmutableString),
388                LessThan => impl_op!(ImmutableString < ImmutableString),
389                LessThanEqualsTo => impl_op!(ImmutableString <= ImmutableString),
390                _ => None,
391            };
392        }
393
394        (Union::Char(..), Union::Char(..), _) => {
395            return match op {
396                Plus => Some((
397                    |_ctx, args| {
398                        let x = args[0].as_char().unwrap();
399                        let y = args[1].as_char().unwrap();
400
401                        let mut result = SmartString::new_const();
402                        result.push(x);
403                        result.push(y);
404
405                        #[cfg(not(feature = "unchecked"))]
406                        _ctx.unwrap().engine().throw_on_size((0, 0, result.len()))?;
407
408                        Ok(result.into())
409                    },
410                    CHECKED_BUILD,
411                )),
412                EqualsTo => impl_op!(char => as_char == as_char),
413                NotEqualsTo => impl_op!(char => as_char != as_char),
414                GreaterThan => impl_op!(char => as_char > as_char),
415                GreaterThanEqualsTo => impl_op!(char => as_char >= as_char),
416                LessThan => impl_op!(char => as_char < as_char),
417                LessThanEqualsTo => impl_op!(char => as_char <= as_char),
418                _ => None,
419            };
420        }
421
422        #[cfg(not(feature = "no_index"))]
423        (Union::Blob(..), Union::Blob(..), _) => {
424            use crate::Blob;
425
426            return match op {
427                Plus => Some((
428                    |_ctx, args| {
429                        let b2 = &*args[1].as_blob_ref().unwrap();
430                        if b2.is_empty() {
431                            return Ok(args[0].flatten_clone());
432                        }
433                        let b1 = &*args[0].as_blob_ref().unwrap();
434                        if b1.is_empty() {
435                            return Ok(args[1].flatten_clone());
436                        }
437
438                        #[cfg(not(feature = "unchecked"))]
439                        _ctx.unwrap()
440                            .engine()
441                            .throw_on_size((b1.len() + b2.len(), 0, 0))?;
442
443                        let mut blob = b1.clone();
444                        blob.extend(b2);
445                        Ok(Dynamic::from_blob(blob))
446                    },
447                    CHECKED_BUILD,
448                )),
449                EqualsTo => impl_op!(Blob == Blob),
450                NotEqualsTo => impl_op!(Blob != Blob),
451                _ => None,
452            };
453        }
454
455        (Union::Unit(..), Union::Unit(..), _) => {
456            return match op {
457                EqualsTo => Some((const_true_fn, false)),
458                NotEqualsTo | GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo => {
459                    Some((const_false_fn, false))
460                }
461                _ => None,
462            };
463        }
464
465        #[cfg(not(feature = "no_float"))]
466        (Union::Float(..), Union::Float(..), _) => {
467            impl_float!(as_float, as_float)
468        }
469
470        #[cfg(not(feature = "no_float"))]
471        (Union::Float(..), Union::Int(..), _) => {
472            impl_float!(as_float, as_int)
473        }
474
475        #[cfg(not(feature = "no_float"))]
476        (Union::Int(..), Union::Float(..), _) => {
477            impl_float!(as_int, as_float)
478        }
479
480        #[cfg(feature = "decimal")]
481        (Union::Decimal(..), Union::Decimal(..), _) => {
482            impl_decimal!(as_decimal, as_decimal)
483        }
484        #[cfg(feature = "decimal")]
485        (Union::Decimal(..), Union::Int(..), _) => {
486            impl_decimal!(as_decimal, as_int)
487        }
488        #[cfg(feature = "decimal")]
489        (Union::Int(..), Union::Decimal(..), _) => {
490            impl_decimal!(as_int, as_decimal)
491        }
492
493        // Ranges
494        (Union::Int(..), Union::Unit(..), ExclusiveRange) => {
495            return Some((
496                |_ctx, args| Ok((args[0].as_int().unwrap()..INT::MAX).into()),
497                false,
498            ))
499        }
500        (Union::Unit(..), Union::Int(..), ExclusiveRange) => {
501            return Some((
502                |_ctx, args| Ok((0..args[1].as_int().unwrap()).into()),
503                false,
504            ))
505        }
506        (Union::Int(..), Union::Unit(..), InclusiveRange) => {
507            return Some((
508                |_ctx, args| Ok((args[0].as_int().unwrap()..=INT::MAX).into()),
509                false,
510            ))
511        }
512        (Union::Unit(..), Union::Int(..), InclusiveRange) => {
513            return Some((
514                |_ctx, args| Ok((0..=args[1].as_int().unwrap()).into()),
515                false,
516            ))
517        }
518
519        // char op string
520        (Union::Char(..), Union::Str(..), _) => {
521            fn get_s1s2(args: &FnCallArgs) -> ([Option<char>; 2], [Option<char>; 2]) {
522                let x = args[0].as_char().unwrap();
523                let y = &*args[1].as_immutable_string_ref().unwrap();
524                let s1 = [Some(x), None];
525                let mut y = y.chars();
526                let s2 = [y.next(), y.next()];
527                (s1, s2)
528            }
529
530            return match op {
531                Plus => Some((
532                    |_ctx, args| {
533                        let x = args[0].as_char().unwrap();
534                        let y = &*args[1].as_immutable_string_ref().unwrap();
535
536                        let mut result = SmartString::new_const();
537                        result.push(x);
538                        result.push_str(y);
539
540                        #[cfg(not(feature = "unchecked"))]
541                        _ctx.unwrap().engine().throw_on_size((0, 0, result.len()))?;
542
543                        Ok(result.into())
544                    },
545                    CHECKED_BUILD,
546                )),
547                EqualsTo => impl_op!(get_s1s2(==)),
548                NotEqualsTo => impl_op!(get_s1s2(!=)),
549                GreaterThan => impl_op!(get_s1s2(>)),
550                GreaterThanEqualsTo => impl_op!(get_s1s2(>=)),
551                LessThan => impl_op!(get_s1s2(<)),
552                LessThanEqualsTo => impl_op!(get_s1s2(<=)),
553                _ => None,
554            };
555        }
556        // string op char
557        (Union::Str(..), Union::Char(..), _) => {
558            fn get_s1s2(args: &FnCallArgs) -> ([Option<char>; 2], [Option<char>; 2]) {
559                let x = &*args[0].as_immutable_string_ref().unwrap();
560                let y = args[1].as_char().unwrap();
561                let mut x = x.chars();
562                let s1 = [x.next(), x.next()];
563                let s2 = [Some(y), None];
564                (s1, s2)
565            }
566
567            return match op {
568                Plus => Some((
569                    |_ctx, args| {
570                        let x = &*args[0].as_immutable_string_ref().unwrap();
571                        let y = args[1].as_char().unwrap();
572                        let result = x + y;
573
574                        #[cfg(not(feature = "unchecked"))]
575                        _ctx.unwrap().engine().throw_on_size((0, 0, result.len()))?;
576
577                        Ok(result.into())
578                    },
579                    CHECKED_BUILD,
580                )),
581                Minus => Some((
582                    |_, args| {
583                        let x = &*args[0].as_immutable_string_ref().unwrap();
584                        let y = args[1].as_char().unwrap();
585                        Ok((x - y).into())
586                    },
587                    false,
588                )),
589                EqualsTo => impl_op!(get_s1s2(==)),
590                NotEqualsTo => impl_op!(get_s1s2(!=)),
591                GreaterThan => impl_op!(get_s1s2(>)),
592                GreaterThanEqualsTo => impl_op!(get_s1s2(>=)),
593                LessThan => impl_op!(get_s1s2(<)),
594                LessThanEqualsTo => impl_op!(get_s1s2(<=)),
595                _ => None,
596            };
597        }
598        // () op string
599        (Union::Unit(..), Union::Str(..), _) => {
600            return match op {
601                Plus => Some((|_, args| Ok(args[1].clone()), false)),
602                EqualsTo | GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo => {
603                    Some((const_false_fn, false))
604                }
605                NotEqualsTo => Some((const_true_fn, false)),
606                _ => None,
607            }
608        }
609        // string op ()
610        (Union::Str(..), Union::Unit(..), _) => {
611            return match op {
612                Plus => Some((|_, args| Ok(args[0].clone()), false)),
613                EqualsTo | GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo => {
614                    Some((const_false_fn, false))
615                }
616                NotEqualsTo => Some((const_true_fn, false)),
617                _ => None,
618            };
619        }
620
621        // blob
622        #[cfg(not(feature = "no_index"))]
623        (Union::Blob(..), Union::Char(..), _) => {
624            return match op {
625                Plus => Some((
626                    |_ctx, args| {
627                        let mut blob = args[0].as_blob_ref().unwrap().clone();
628                        let mut buf = [0_u8; 4];
629                        let x = args[1].as_char().unwrap().encode_utf8(&mut buf);
630
631                        #[cfg(not(feature = "unchecked"))]
632                        _ctx.unwrap()
633                            .engine()
634                            .throw_on_size((blob.len() + x.len(), 0, 0))?;
635
636                        blob.extend(x.as_bytes());
637                        Ok(Dynamic::from_blob(blob))
638                    },
639                    CHECKED_BUILD,
640                )),
641                _ => None,
642            }
643        }
644
645        _ => (),
646    }
647
648    // Check detailed types
649
650    let type1 = x.type_id();
651    let type2 = y.type_id();
652
653    if type1 == TypeId::of::<ExclusiveRange>() && type2 == TypeId::of::<ExclusiveRange>() {
654        return match op {
655            EqualsTo => impl_op!(ExclusiveRange == ExclusiveRange),
656            NotEqualsTo => impl_op!(ExclusiveRange != ExclusiveRange),
657            _ => None,
658        };
659    }
660    if type1 == TypeId::of::<InclusiveRange>() && type2 == TypeId::of::<InclusiveRange>() {
661        return match op {
662            EqualsTo => impl_op!(InclusiveRange == InclusiveRange),
663            NotEqualsTo => impl_op!(InclusiveRange != InclusiveRange),
664            _ => None,
665        };
666    }
667
668    // Incompatible ranges
669    if (type1 == TypeId::of::<ExclusiveRange>() && type2 == TypeId::of::<InclusiveRange>())
670        || (type1 == TypeId::of::<InclusiveRange>() && type2 == TypeId::of::<ExclusiveRange>())
671    {
672        return match op {
673            NotEqualsTo => Some((const_true_fn, false)),
674            Equals => Some((const_false_fn, false)),
675            _ => None,
676        };
677    }
678
679    // Default comparison operators for different, non-numeric types
680    if type1 != type2 {
681        return match op {
682            NotEqualsTo if !is_numeric(type1) || !is_numeric(type2) => Some((const_true_fn, false)),
683            EqualsTo | GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo
684                if !is_numeric(type1) || !is_numeric(type2) =>
685            {
686                Some((const_false_fn, false))
687            }
688            _ => None,
689        };
690    }
691
692    // Same type but no built-in
693    None
694}
695
696/// Build in common operator assignment implementations to avoid the cost of calling a registered function.
697///
698/// The return function is registered as a _method_, so the first parameter cannot be consumed.
699#[must_use]
700pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option<FnBuiltin> {
701    macro_rules! impl_op {
702        ($x:ty = x $op:tt $yy:ident) => { Some((|_, args| {
703            let x = args[0].$yy().unwrap();
704            let y = args[1].$yy().unwrap() as $x;
705            Ok((*args[0].write_lock::<$x>().unwrap() = x $op y).into())
706        }, false)) };
707        ($x:ident $op:tt $yy:ident) => { Some((|_, args| {
708            let y = args[1].$yy().unwrap() as $x;
709            Ok((*args[0].write_lock::<$x>().unwrap() $op y).into())
710        }, false)) };
711        ($x:ident $op:tt $yy:ident as $yyy:ty) => { Some((|_, args| {
712            let y = args[1].$yy().unwrap() as $yyy;
713            Ok((*args[0].write_lock::<$x>().unwrap() $op y).into())
714        }, false)) };
715        ($x:ty => $xx:ident . $func:ident ( $yy:ident as $yyy:ty )) => { Some((|_, args| {
716            let x = args[0].$xx().unwrap();
717            let y = args[1].$yy().unwrap() as $x;
718            Ok((*args[0].write_lock::<$x>().unwrap() = x.$func(y as $yyy)).into())
719        }, false)) };
720        ($x:ty => Ok($func:ident ( $xx:ident, $yy:ident ))) => { Some((|_, args| {
721            let x = args[0].$xx().unwrap();
722            let y = args[1].$yy().unwrap() as $x;
723            let v: Dynamic = $func(x, y).into();
724            Ok((*args[0].write_lock().unwrap() = v).into())
725        }, false)) };
726        ($x:ty => $func:ident ( $xx:ident, $yy:ident )) => { Some((|_, args| {
727            let x = args[0].$xx().unwrap();
728            let y = args[1].$yy().unwrap() as $x;
729            Ok((*args[0].write_lock().unwrap() = $func(x, y)?).into())
730        }, false)) };
731        (from $x:ident $op:tt $yy:ident) => { Some((|_, args| {
732            let y = <$x>::from(args[1].$yy().unwrap());
733            Ok((*args[0].write_lock::<$x>().unwrap() $op y).into())
734        }, false)) };
735        (from $x:ty => $xx:ident . $func:ident ( $yy:ident )) => { Some((|_, args| {
736            let x = args[0].$xx().unwrap();
737            let y = <$x>::from(args[1].$yy().unwrap());
738            Ok((*args[0].write_lock::<$x>().unwrap() = x.$func(y)).into())
739        }, false)) };
740        (from $x:ty => Ok($func:ident ( $xx:ident, $yy:ident ))) => { Some((|_, args| {
741            let x = args[0].$xx().unwrap();
742            let y = <$x>::from(args[1].$yy().unwrap());
743            Ok((*args[0].write_lock().unwrap() = $func(x, y).into()).into())
744        }, false)) };
745        (from $x:ty => $func:ident ( $xx:ident, $yy:ident )) => { Some((|_, args| {
746            let x = args[0].$xx().unwrap();
747            let y = <$x>::from(args[1].$yy().unwrap());
748            Ok((*args[0].write_lock().unwrap() = $func(x, y)?).into())
749        }, false)) };
750    }
751
752    #[cfg(not(feature = "no_float"))]
753    macro_rules! impl_float {
754        ($x:ident, $xx:ident, $yy:ident) => {
755            return match op {
756                PlusAssign      => impl_op!($x += $yy),
757                MinusAssign     => impl_op!($x -= $yy),
758                MultiplyAssign  => impl_op!($x *= $yy),
759                DivideAssign    => impl_op!($x /= $yy),
760                ModuloAssign    => impl_op!($x %= $yy),
761                PowerOfAssign   => impl_op!($x => $xx.powf($yy as $x)),
762                _               => None,
763            }
764        }
765    }
766
767    #[cfg(feature = "decimal")]
768    macro_rules! impl_decimal {
769        ($x:ident, $xx:ident, $yy:ident) => {
770            {
771                #[cfg(not(feature = "unchecked"))]
772                #[allow(clippy::wildcard_imports)]
773                use crate::packages::arithmetic::decimal_functions::builtin::*;
774
775                #[cfg(not(feature = "unchecked"))]
776                return match op {
777                    PlusAssign      => impl_op!(from $x => add($xx, $yy)),
778                    MinusAssign     => impl_op!(from $x => subtract($xx, $yy)),
779                    MultiplyAssign  => impl_op!(from $x => multiply($xx, $yy)),
780                    DivideAssign    => impl_op!(from $x => divide($xx, $yy)),
781                    ModuloAssign    => impl_op!(from $x => modulo($xx, $yy)),
782                    PowerOfAssign   => impl_op!(from $x => power($xx, $yy)),
783                    _               => None,
784                };
785
786                #[cfg(feature = "unchecked")]
787                use rust_decimal::MathematicalOps;
788
789                #[cfg(feature = "unchecked")]
790                return match op {
791                    PlusAssign      => impl_op!(from $x += $yy),
792                    MinusAssign     => impl_op!(from $x -= $yy),
793                    MultiplyAssign  => impl_op!(from $x *= $yy),
794                    DivideAssign    => impl_op!(from $x /= $yy),
795                    ModuloAssign    => impl_op!(from $x %= $yy),
796                    PowerOfAssign   => impl_op!(from $x => $xx.powd($yy)),
797                    _               => None,
798                };
799            }
800        };
801    }
802
803    // Check for common patterns
804    match (&x.0, &y.0, op) {
805        (Union::Int(..), Union::Int(..), _) => {
806            #[cfg(not(feature = "unchecked"))]
807            #[allow(clippy::wildcard_imports)]
808            use crate::packages::arithmetic::arith_basic::INT::functions::*;
809
810            #[cfg(not(feature = "unchecked"))]
811            match op {
812                PlusAssign => return impl_op!(INT => add(as_int, as_int)),
813                MinusAssign => return impl_op!(INT => subtract(as_int, as_int)),
814                MultiplyAssign => return impl_op!(INT => multiply(as_int, as_int)),
815                DivideAssign => return impl_op!(INT => divide(as_int, as_int)),
816                ModuloAssign => return impl_op!(INT => modulo(as_int, as_int)),
817                PowerOfAssign => return impl_op!(INT => power(as_int, as_int)),
818                RightShiftAssign => return impl_op!(INT => Ok(shift_right(as_int, as_int))),
819                LeftShiftAssign => return impl_op!(INT => Ok(shift_left(as_int, as_int))),
820                AndAssign => return impl_op!(INT => Ok(binary_and(as_int, as_int))),
821                OrAssign => return impl_op!(INT => Ok(binary_or(as_int, as_int))),
822                XOrAssign => return impl_op!(INT => Ok(binary_xor(as_int, as_int))),
823                _ => (),
824            }
825
826            #[cfg(feature = "unchecked")]
827            match op {
828                PlusAssign => return impl_op!(INT += as_int),
829                MinusAssign => return impl_op!(INT -= as_int),
830                MultiplyAssign => return impl_op!(INT *= as_int),
831                DivideAssign => return impl_op!(INT /= as_int),
832                ModuloAssign => return impl_op!(INT %= as_int),
833                PowerOfAssign => return impl_op!(INT => as_int.pow(as_int as u32)),
834                RightShiftAssign => {
835                    return Some((
836                        |_, args| {
837                            let x = args[0].as_int().unwrap();
838                            let y = args[1].as_int().unwrap();
839                            let v = if y < 0 { x << -y } else { x >> y };
840                            *args[0].write_lock::<Dynamic>().unwrap() = v.into();
841                            Ok(Dynamic::UNIT)
842                        },
843                        false,
844                    ))
845                }
846                LeftShiftAssign => {
847                    return Some((
848                        |_, args| {
849                            let x = args[0].as_int().unwrap();
850                            let y = args[1].as_int().unwrap();
851                            let v = if y < 0 { x >> -y } else { x << y };
852                            *args[0].write_lock::<Dynamic>().unwrap() = v.into();
853                            Ok(Dynamic::UNIT)
854                        },
855                        false,
856                    ))
857                }
858                AndAssign => return impl_op!(INT &= as_int),
859                OrAssign => return impl_op!(INT |= as_int),
860                XOrAssign => return impl_op!(INT ^= as_int),
861                _ => (),
862            }
863
864            match op {
865                AndAssign => impl_op!(INT &= as_int),
866                OrAssign => impl_op!(INT |= as_int),
867                XOrAssign => impl_op!(INT ^= as_int),
868                _ => None,
869            }
870        }
871
872        (Union::Bool(..), Union::Bool(..), _) => match op {
873            AndAssign => impl_op!(bool = x && as_bool),
874            OrAssign => impl_op!(bool = x || as_bool),
875            XOrAssign => impl_op!(bool = x ^ as_bool),
876            _ => None,
877        },
878
879        // char += char
880        (Union::Char(..), Union::Char(..), PlusAssign) => Some((
881            |_, args| {
882                let y = args[1].as_char().unwrap();
883                let x = &mut *args[0].write_lock::<Dynamic>().unwrap();
884
885                let mut buf = SmartString::new_const();
886                buf.push(x.as_char().unwrap());
887                buf.push(y);
888
889                *x = buf.into();
890
891                Ok(Dynamic::UNIT)
892            },
893            false,
894        )),
895
896        (Union::Str(..), Union::Str(..), _) => match op {
897            PlusAssign => Some((
898                |_ctx, args| {
899                    let (first, second) = args.split_first_mut().unwrap();
900                    let x = &mut *first.as_immutable_string_mut().unwrap();
901                    let y = &*second[0].as_immutable_string_ref().unwrap();
902
903                    #[cfg(not(feature = "unchecked"))]
904                    if !x.is_empty() && !y.is_empty() {
905                        let total_len = x.len() + y.len();
906                        _ctx.unwrap().engine().throw_on_size((0, 0, total_len))?;
907                    }
908
909                    *x += y;
910
911                    Ok(Dynamic::UNIT)
912                },
913                CHECKED_BUILD,
914            )),
915            MinusAssign => Some((
916                |_, args| {
917                    let (first, second) = args.split_first_mut().unwrap();
918                    let x = &mut *first.as_immutable_string_mut().unwrap();
919                    let y = &*second[0].as_immutable_string_ref().unwrap();
920                    *x -= y;
921                    Ok(Dynamic::UNIT)
922                },
923                false,
924            )),
925            _ => None,
926        },
927
928        // array += array
929        #[cfg(not(feature = "no_index"))]
930        (Union::Array(..), Union::Array(..), PlusAssign) => {
931            #[allow(clippy::wildcard_imports)]
932            use crate::packages::array_basic::array_functions::*;
933
934            Some((
935                |_ctx, args| {
936                    let x = args[1].take().into_array().unwrap();
937
938                    if x.is_empty() {
939                        return Ok(Dynamic::UNIT);
940                    }
941
942                    #[cfg(not(feature = "unchecked"))]
943                    if !args[0].as_array_ref().unwrap().is_empty() {
944                        _ctx.unwrap().engine().check_data_size(
945                            &*args[0].read_lock().unwrap(),
946                            crate::Position::NONE,
947                        )?;
948                    }
949
950                    let array = &mut *args[0].as_array_mut().unwrap();
951
952                    append(array, x);
953
954                    Ok(Dynamic::UNIT)
955                },
956                CHECKED_BUILD,
957            ))
958        }
959
960        // blob += blob
961        #[cfg(not(feature = "no_index"))]
962        (Union::Blob(..), Union::Blob(..), PlusAssign) => {
963            #[allow(clippy::wildcard_imports)]
964            use crate::packages::blob_basic::blob_functions::*;
965
966            Some((
967                |_ctx, args| {
968                    let blob2 = args[1].take().into_blob().unwrap();
969                    let blob1 = &mut *args[0].as_blob_mut().unwrap();
970
971                    #[cfg(not(feature = "unchecked"))]
972                    _ctx.unwrap()
973                        .engine()
974                        .throw_on_size((blob1.len() + blob2.len(), 0, 0))?;
975
976                    append(blob1, blob2);
977
978                    Ok(Dynamic::UNIT)
979                },
980                CHECKED_BUILD,
981            ))
982        }
983
984        #[cfg(not(feature = "no_float"))]
985        (Union::Float(..), Union::Float(..), _) => {
986            impl_float!(FLOAT, as_float, as_float)
987        }
988
989        #[cfg(not(feature = "no_float"))]
990        (Union::Float(..), Union::Int(..), _) => {
991            impl_float!(FLOAT, as_float, as_int)
992        }
993
994        #[cfg(feature = "decimal")]
995        (Union::Decimal(..), Union::Decimal(..), _) => {
996            impl_decimal!(Decimal, as_decimal, as_decimal)
997        }
998
999        #[cfg(feature = "decimal")]
1000        (Union::Decimal(..), Union::Int(..), _) => {
1001            impl_decimal!(Decimal, as_decimal, as_int)
1002        }
1003
1004        // string op= char
1005        (Union::Str(..), Union::Char(..), _) => match op {
1006            PlusAssign => Some((
1007                |_ctx, args| {
1008                    let mut buf = [0_u8; 4];
1009                    let ch = &*args[1].as_char().unwrap().encode_utf8(&mut buf);
1010                    let mut x = args[0].as_immutable_string_mut().unwrap();
1011
1012                    #[cfg(not(feature = "unchecked"))]
1013                    _ctx.unwrap()
1014                        .engine()
1015                        .throw_on_size((0, 0, x.len() + ch.len()))?;
1016
1017                    *x += ch;
1018
1019                    Ok(Dynamic::UNIT)
1020                },
1021                CHECKED_BUILD,
1022            )),
1023            MinusAssign => impl_op!(ImmutableString -= as_char as char),
1024            _ => None,
1025        },
1026        // char += string
1027        (Union::Char(..), Union::Str(..), PlusAssign) => Some((
1028            |_ctx, args| {
1029                let ch = {
1030                    let s = &*args[1].as_immutable_string_ref().unwrap();
1031
1032                    if s.is_empty() {
1033                        return Ok(Dynamic::UNIT);
1034                    }
1035
1036                    let mut ch = args[0].as_char().unwrap().to_string();
1037
1038                    #[cfg(not(feature = "unchecked"))]
1039                    _ctx.unwrap()
1040                        .engine()
1041                        .throw_on_size((0, 0, ch.len() + s.len()))?;
1042
1043                    ch += s;
1044                    ch
1045                };
1046
1047                *args[0].write_lock::<Dynamic>().unwrap() = ch.into();
1048
1049                Ok(Dynamic::UNIT)
1050            },
1051            CHECKED_BUILD,
1052        )),
1053
1054        // array += any
1055        #[cfg(not(feature = "no_index"))]
1056        (Union::Array(..), _, PlusAssign) => {
1057            #[allow(clippy::wildcard_imports)]
1058            use crate::packages::array_basic::array_functions::*;
1059
1060            Some((
1061                |_ctx, args| {
1062                    {
1063                        let x = args[1].take();
1064                        let array = &mut *args[0].as_array_mut().unwrap();
1065                        push(array, x);
1066                    }
1067
1068                    #[cfg(not(feature = "unchecked"))]
1069                    _ctx.unwrap()
1070                        .engine()
1071                        .check_data_size(&*args[0].read_lock().unwrap(), crate::Position::NONE)?;
1072
1073                    Ok(Dynamic::UNIT)
1074                },
1075                CHECKED_BUILD,
1076            ))
1077        }
1078
1079        // blob += int
1080        #[cfg(not(feature = "no_index"))]
1081        (Union::Blob(..), Union::Int(..), PlusAssign) => {
1082            #[allow(clippy::wildcard_imports)]
1083            use crate::packages::blob_basic::blob_functions::*;
1084
1085            Some((
1086                |_ctx, args| {
1087                    let x = args[1].as_int().unwrap();
1088                    let blob = &mut *args[0].as_blob_mut().unwrap();
1089
1090                    #[cfg(not(feature = "unchecked"))]
1091                    _ctx.unwrap()
1092                        .engine()
1093                        .throw_on_size((blob.len() + 1, 0, 0))?;
1094
1095                    push(blob, x);
1096
1097                    Ok(Dynamic::UNIT)
1098                },
1099                CHECKED_BUILD,
1100            ))
1101        }
1102
1103        // blob += char
1104        #[cfg(not(feature = "no_index"))]
1105        (Union::Blob(..), Union::Char(..), PlusAssign) => {
1106            #[allow(clippy::wildcard_imports)]
1107            use crate::packages::blob_basic::blob_functions::*;
1108
1109            Some((
1110                |_ctx, args| {
1111                    let x = args[1].as_char().unwrap();
1112                    let blob = &mut *args[0].as_blob_mut().unwrap();
1113
1114                    #[cfg(not(feature = "unchecked"))]
1115                    _ctx.unwrap()
1116                        .engine()
1117                        .throw_on_size((blob.len() + 1, 0, 0))?;
1118
1119                    append_char(blob, x);
1120
1121                    Ok(Dynamic::UNIT)
1122                },
1123                CHECKED_BUILD,
1124            ))
1125        }
1126
1127        // blob += string
1128        #[cfg(not(feature = "no_index"))]
1129        (Union::Blob(..), Union::Str(..), PlusAssign) => {
1130            #[allow(clippy::wildcard_imports)]
1131            use crate::packages::blob_basic::blob_functions::*;
1132
1133            Some((
1134                |_ctx, args| {
1135                    let (first, second) = args.split_first_mut().unwrap();
1136                    let blob = &mut *first.as_blob_mut().unwrap();
1137                    let s = &*second[0].as_immutable_string_ref().unwrap();
1138
1139                    if s.is_empty() {
1140                        return Ok(Dynamic::UNIT);
1141                    }
1142
1143                    #[cfg(not(feature = "unchecked"))]
1144                    _ctx.unwrap()
1145                        .engine()
1146                        .throw_on_size((blob.len() + s.len(), 0, 0))?;
1147
1148                    append_str(blob, s);
1149
1150                    Ok(Dynamic::UNIT)
1151                },
1152                CHECKED_BUILD,
1153            ))
1154        }
1155
1156        _ => None,
1157    }
1158}