diff --git a/crates/nargo_cli/tests/test_data/option/Nargo.toml b/crates/nargo_cli/tests/test_data/option/Nargo.toml new file mode 100644 index 00000000000..2248e9c06dd --- /dev/null +++ b/crates/nargo_cli/tests/test_data/option/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "option" +authors = [""] +compiler_version = "0.7.0" + +[dependencies] diff --git a/crates/nargo_cli/tests/test_data/option/src/main.nr b/crates/nargo_cli/tests/test_data/option/src/main.nr new file mode 100644 index 00000000000..0a41b9a629c --- /dev/null +++ b/crates/nargo_cli/tests/test_data/option/src/main.nr @@ -0,0 +1,53 @@ +use dep::std::option::Option; + +fn main() { + let none = Option::none(); + let some = Option::some(3); + + assert(none.is_none()); + assert(some.is_some()); + + assert(some.unwrap() == 3); + + assert(none.unwrap_or(2) == 2); + assert(some.unwrap_or(2) == 3); + + assert(none.unwrap_or_else(|| 5) == 5); + assert(some.unwrap_or_else(|| 5) == 3); + + assert(none.map(|x| x * 2).is_none()); + assert(some.map(|x| x * 2).unwrap() == 6); + + assert(none.map_or(0, |x| x * 2) == 0); + assert(some.map_or(0, |x| x * 2) == 6); + + assert(none.map_or_else(|| 0, |x| x * 2) == 0); + assert(some.map_or_else(|| 0, |x| x * 2) == 6); + + assert(none.and(none).is_none()); + assert(none.and(some).is_none()); + assert(some.and(none).is_none()); + assert(some.and(some).is_some()); + + let add1_u64 = |value: Field| Option::some(value as u64 + 1); + + assert(none.and_then(|_value| Option::none()).is_none()); + assert(none.and_then(add1_u64).is_none()); + assert(some.and_then(|_value| Option::none()).is_none()); + assert(some.and_then(add1_u64).unwrap() == 4); + + assert(none.or(none).is_none()); + assert(none.or(some).is_some()); + assert(some.or(none).is_some()); + assert(some.or(some).is_some()); + + assert(none.or_else(|| Option::none()).is_none()); + assert(none.or_else(|| Option::some(5)).is_some()); + assert(some.or_else(|| Option::none()).is_some()); + assert(some.or_else(|| Option::some(5)).is_some()); + + assert(none.xor(none).is_none()); + assert(none.xor(some).is_some()); + assert(some.xor(none).is_some()); + assert(some.xor(some).is_none()); +} diff --git a/crates/noirc_evaluator/src/ssa_refactor/acir_gen/mod.rs b/crates/noirc_evaluator/src/ssa_refactor/acir_gen/mod.rs index da8409431ce..77467c116d9 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/acir_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/acir_gen/mod.rs @@ -998,7 +998,7 @@ impl Context { } Intrinsic::ArrayLen => { let len = match self.convert_value(arguments[0], dfg) { - AcirValue::Var(_, _) => unreachable!("Non-array passed to array.len() method"), + AcirValue::Var(_, _) => unreachable!("Non-array passed to array.len() method"), AcirValue::Array(values) => (values.len() as u128).into(), AcirValue::DynamicArray(array) => (array.len as u128).into(), }; diff --git a/noir_stdlib/src/lib.nr b/noir_stdlib/src/lib.nr index f6c01ecdfaa..5923d4be96b 100644 --- a/noir_stdlib/src/lib.nr +++ b/noir_stdlib/src/lib.nr @@ -14,6 +14,7 @@ mod ec; mod unsafe; mod collections; mod compat; +mod option; // Oracle calls are required to be wrapped in an unconstrained function // Thus, the only argument to the `println` oracle is expected to always be an ident diff --git a/noir_stdlib/src/option.nr b/noir_stdlib/src/option.nr new file mode 100644 index 00000000000..5cc4dfae887 --- /dev/null +++ b/noir_stdlib/src/option.nr @@ -0,0 +1,157 @@ +struct Option { + _is_some: bool, + value: T, +} + +impl Option { + /// Constructs a None value + fn none() -> Self { + Self { _is_some: false, value: crate::unsafe::zeroed() } + } + + /// Constructs a Some wrapper around the given value + fn some(value: T) -> Self { + Self { _is_some: true, value } + } + + /// True if this Option is None + fn is_none(self) -> bool { + !self._is_some + } + + /// True if this Option is Some + fn is_some(self) -> bool { + self._is_some + } + + /// Asserts `self.is_some()` and returns the wrapped value. + fn unwrap(self) -> T { + assert(self._is_some); + self.value + } + + /// Returns the wrapped value if `self.is_some()`. Otherwise, returns the given default value. + fn unwrap_or(self, default: T) -> T { + if self._is_some { + self.value + } else { + default + } + } + + /// Returns the wrapped value if `self.is_some()`. Otherwise, calls the given function to return + /// a default value. + fn unwrap_or_else(self, default: fn() -> T) -> T { + if self._is_some { + self.value + } else { + default() + } + } + + /// If self is `Some(x)`, this returns `Some(f(x))`. Otherwise, this returns `None`. + fn map(self, f: fn(T) -> U) -> Option { + if self._is_some { + Option::some(f(self.value)) + } else { + Option::none() + } + } + + /// If self is `Some(x)`, this returns `f(x)`. Otherwise, this returns the given default value. + fn map_or(self, default: U, f: fn(T) -> U) -> U { + if self._is_some { + f(self.value) + } else { + default + } + } + + /// If self is `Some(x)`, this returns `f(x)`. Otherwise, this returns `default()`. + fn map_or_else(self, default: fn() -> U, f: fn(T) -> U) -> U { + if self._is_some { + f(self.value) + } else { + default() + } + } + + /// Returns None if self is None. Otherwise, this returns `other`. + fn and(self, other: Self) -> Self { + if self.is_none() { + Option::none() + } else { + other + } + } + + /// If self is None, this returns None. Otherwise, this calls the given function + /// with the Some value contained within self, and returns the result of that call. + /// + /// In some languages this function is called `flat_map` or `bind`. + fn and_then(self, f: fn(T) -> Option) -> Option { + if self._is_some { + f(self.value) + } else { + Option::none() + } + } + + /// If self is Some, return self. Otherwise, return `other`. + fn or(self, other: Self) -> Self { + if self._is_some { + self + } else { + other + } + } + + /// If self is Some, return self. Otherwise, return `default()`. + fn or_else(self, default: fn() -> Self) -> Self { + if self._is_some { + self + } else { + default() + } + } + + // If only one of the two Options is Some, return that option. + // Otherwise, if both options are Some or both are None, None is returned. + fn xor(self, other: Self) -> Self { + if self._is_some { + if other._is_some { + Option::none() + } else { + self + } + } else if other._is_some { + other + } else { + Option::none() + } + } + + /// Returns `Some(x)` if self is `Some(x)` and `predicate(x)` is true. + /// Otherwise, this returns `None` + fn filter(self, predicate: fn(T) -> bool) -> Self { + if self._is_some { + if predicate(self.value) { + self + } else { + Option::none() + } + } else { + Option::none() + } + } + + /// Flattens an Option> into a Option. + /// This returns None if the outer Option is None. Otherwise, this returns the inner Option. + fn flatten(option: Option>) -> Option { + if option._is_some { + option.value + } else { + Option::none() + } + } +}