Skip to content

Commit

Permalink
feat: Add Option<T> to noir stdlib (#1781)
Browse files Browse the repository at this point in the history
* Add Option

* Fix path

* Add option test

* Move test

* Add docs and filter, flatten methods

* Fix stdlib
  • Loading branch information
jfecher authored Aug 1, 2023
1 parent 3c82721 commit 920a900
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 1 deletion.
6 changes: 6 additions & 0 deletions crates/nargo_cli/tests/test_data/option/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "option"
authors = [""]
compiler_version = "0.7.0"

[dependencies]
53 changes: 53 additions & 0 deletions crates/nargo_cli/tests/test_data/option/src/main.nr
Original file line number Diff line number Diff line change
@@ -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());
}
2 changes: 1 addition & 1 deletion crates/noirc_evaluator/src/ssa_refactor/acir_gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1002,7 +1002,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(),
};
Expand Down
1 change: 1 addition & 0 deletions noir_stdlib/src/lib.nr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
157 changes: 157 additions & 0 deletions noir_stdlib/src/option.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
struct Option<T> {
_is_some: bool,
value: T,
}

impl<T> Option<T> {
/// 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<U>(self, f: fn(T) -> U) -> Option<U> {
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<U>(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<U>(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<U>(self, f: fn(T) -> Option<U>) -> Option<U> {
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<U>(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<Option<T>> into a Option<T>.
/// This returns None if the outer Option is None. Otherwise, this returns the inner Option.
fn flatten(option: Option<Option<T>>) -> Option<T> {
if option._is_some {
option.value
} else {
Option::none()
}
}
}

0 comments on commit 920a900

Please sign in to comment.