From 3154dfd2511140f27bcd50b20fb35d9fca68db76 Mon Sep 17 00:00:00 2001 From: Damir Shamanaev Date: Fri, 28 Jun 2024 19:53:35 +0100 Subject: [PATCH] [stdlib] Adds macros for option (#18421) --- .../packages/move-stdlib/sources/option.move | 96 +++++++++++++++++++ .../move-stdlib/tests/option_tests.move | 48 ++++++++++ 2 files changed, 144 insertions(+) diff --git a/crates/sui-framework/packages/move-stdlib/sources/option.move b/crates/sui-framework/packages/move-stdlib/sources/option.move index d746804dea9c70..b0f1862c91ba58 100644 --- a/crates/sui-framework/packages/move-stdlib/sources/option.move +++ b/crates/sui-framework/packages/move-stdlib/sources/option.move @@ -141,4 +141,100 @@ module std::option { let Option { vec } = t; vec } + + // === Macro Functions === + + /// Destroy `Option` and call the closure `f` on the value inside if it holds one. + public macro fun destroy<$T>($o: Option<$T>, $f: |$T|) { + let o = $o; + o.do!($f); + } + + /// Destroy `Option` and call the closure `f` on the value inside if it holds one. + public macro fun do<$T>($o: Option<$T>, $f: |$T|) { + let o = $o; + if (o.is_some()) { + $f(o.destroy_some()); + } + } + + /// Execute a closure on the value inside `t` if it holds one. + public macro fun do_ref<$T>($o: &Option<$T>, $f: |&$T|) { + let o = $o; + if (o.is_some()) { + $f(o.borrow()); + } + } + + /// Execute a closure on the mutable reference to the value inside `t` if it holds one. + public macro fun do_mut<$T>($o: &mut Option<$T>, $f: |&mut $T|) { + let o = $o; + if (o.is_some()) $f(o.borrow_mut()); + } + + /// Select the first `Some` value from the two options, or `None` if both are `None`. + /// Equivalent to Rust's `a.or(b)`. + public macro fun or<$T>($o: Option<$T>, $default: Option<$T>): Option<$T> { + let o = $o; + if (o.is_some()) o + else $default + } + + /// If the value is `Some`, call the closure `f` on it. Otherwise, return `None`. + /// Equivalent to Rust's `t.and_then(f)`. + public macro fun and<$T, $U>($o: Option<$T>, $f: |$T| -> Option<$U>): Option<$U> { + let o = $o; + if (o.is_some()) $f(o.extract()) + else none() + } + + /// If the value is `Some`, call the closure `f` on it. Otherwise, return `None`. + /// Equivalent to Rust's `t.and_then(f)`. + public macro fun and_ref<$T, $U>($o: &Option<$T>, $f: |&$T| -> Option<$U>): Option<$U> { + let o = $o; + if (o.is_some()) $f(o.borrow()) + else none() + } + + /// Map an `Option` to `Option` by applying a function to a contained value. + /// Equivalent to Rust's `t.map(f)`. + public macro fun map<$T, $U>($o: Option<$T>, $f: |$T| -> $U): Option<$U> { + let mut o = $o; + if (o.is_some()) some($f(o.extract())) + else none() + } + + /// Map an `Option` value to `Option` by applying a function to a contained value by reference. + /// Original `Option` is preserved. + /// Equivalent to Rust's `t.map(f)`. + public macro fun map_ref<$T, $U>($o: &Option<$T>, $f: |&$T| -> $U): Option<$U> { + let o = $o; + if (o.is_some()) some($f(o.borrow())) + else none() + } + + /// Return `None` if the value is `None`, otherwise return `Option` if the predicate `f` returns true. + public macro fun filter<$T: drop>($o: Option<$T>, $f: |&$T| -> bool): Option<$T> { + let o = $o; + if (o.is_some() && $f(o.borrow())) o + else none() + } + + /// Return `false` if the value is `None`, otherwise return the result of the predicate `f`. + public macro fun is_some_and<$T>($o: &Option<$T>, $f: |&$T| -> bool): bool { + let o = $o; + o.is_some() && $f(o.borrow()) + } + + /// Destroy `Option` and return the value inside if it holds one, or `default` otherwise. + /// Equivalent to Rust's `t.unwrap_or(default)`. + /// + /// Note: this function is a more efficient version of `destroy_with_default`, as it does not + /// evaluate the default value unless necessary. The `destroy_with_default` function should be + /// deprecated in favor of this function. + public macro fun destroy_or<$T>($o: Option<$T>, $default: $T): $T { + let o = $o; + if (o.is_some()) o.destroy_some() + else $default + } } diff --git a/crates/sui-framework/packages/move-stdlib/tests/option_tests.move b/crates/sui-framework/packages/move-stdlib/tests/option_tests.move index e3ee79f88aba89..a8cb8875d54959 100644 --- a/crates/sui-framework/packages/move-stdlib/tests/option_tests.move +++ b/crates/sui-framework/packages/move-stdlib/tests/option_tests.move @@ -169,4 +169,52 @@ module std::option_tests { let v: vector = option::none().to_vec(); assert!(v.is_empty()); } + + // === Macros === + + #[test] + fun do_destroy() { + let mut counter = 0; + option::some(5).destroy!(|x| counter = x); + option::some(10).do!(|x| counter = counter + x); + + assert!(counter == 15); + } + + #[test] + fun do_ref_mut() { + let mut counter = 0; + let mut opt = option::some(5); + opt.do_mut!(|x| *x = 100); + opt.do_ref!(|x| counter = *x); + + assert!(counter == 100); + } + + #[test] + fun map_map_ref() { + assert!(option::some(5).map!(|x| vector[x]) == option::some(vector[5])); + assert!(option::some(5).map_ref!(|x| vector[*x]) == option::some(vector[5])); + assert!(option::none().map!(|x| vector[x]) == option::none()); + assert!(option::none().map_ref!(|x| vector[*x]) == option::none()); + } + + #[test] + fun filter() { + assert!(option::some(5).filter!(|x| *x == 5) == option::some(5)); + assert!(option::some(5).filter!(|x| *x == 6) == option::none()); + } + + #[test] + fun is_some_and() { + assert!(option::some(5).is_some_and!(|x| *x == 5)); + assert!(!option::some(5).is_some_and!(|x| *x == 6)); + assert!(!option::none().is_some_and!(|x| *x == 5)); + } + + #[test] + fun destroy_or() { + assert!(option::none().destroy_or!(10) == 10); + assert!(option::some(5).destroy_or!(10) == 5); + } }