Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add Option<T> to noir stdlib #1781

Merged
merged 8 commits into from
Aug 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -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(),
};
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 {
jfecher marked this conversation as resolved.
Show resolved Hide resolved
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()
}
}
}
Loading