-
Notifications
You must be signed in to change notification settings - Fork 200
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(ssa refactor): Implement
acir_gen
errors (#2071)
* implement acir gen errors * comment * remove unwrap * rename to internal error * comment * comment * . * . * . * review * . * chore: fix merge conflict * chore: make multiplication of 2 witnesses more explicit * Update crates/noirc_evaluator/src/errors.rs --------- Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com> Co-authored-by: TomAFrench <tom@tomfren.ch> Co-authored-by: jfecher <jake@aztecprotocol.com>
- Loading branch information
1 parent
8981c7d
commit 9b417da
Showing
7 changed files
with
459 additions
and
423 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,151 +1,104 @@ | ||
//! Noir Evaluator has two types of errors | ||
//! | ||
//! [RuntimeError]s that should be displayed to the user | ||
//! | ||
//! [InternalError]s that are used for checking internal logics of the SSA | ||
//! | ||
//! An Error of the former is a user Error | ||
//! | ||
//! An Error of the latter is an error in the implementation of the compiler | ||
use acvm::FieldElement; | ||
use noirc_errors::{CustomDiagnostic as Diagnostic, FileDiagnostic, Location}; | ||
use thiserror::Error; | ||
|
||
#[derive(Debug)] | ||
pub struct RuntimeError { | ||
pub location: Option<Location>, | ||
pub kind: RuntimeErrorKind, | ||
} | ||
|
||
impl RuntimeError { | ||
// XXX: In some places, we strip the span because we do not want span to | ||
// be introduced into the binary op or low level function code, for simplicity. | ||
// | ||
// It's possible to have it there, but it means we will need to proliferate the code with span | ||
// | ||
// This does make error reporting, less specific! | ||
pub fn remove_span(self) -> RuntimeErrorKind { | ||
self.kind | ||
} | ||
|
||
pub fn new(kind: RuntimeErrorKind, location: Option<Location>) -> RuntimeError { | ||
RuntimeError { location, kind } | ||
} | ||
|
||
// Keep one of the two location which is Some, if possible | ||
// This is used when we optimize instructions so that we do not lose track of location | ||
pub fn merge_location(a: Option<Location>, b: Option<Location>) -> Option<Location> { | ||
match (a, b) { | ||
(Some(loc), _) | (_, Some(loc)) => Some(loc), | ||
(None, None) => None, | ||
} | ||
} | ||
#[derive(Debug, PartialEq, Eq, Clone, Error)] | ||
pub enum RuntimeError { | ||
// We avoid showing the actual lhs and rhs since most of the time they are just 0 | ||
// and 1 respectively. This would confuse users if a constraint such as | ||
// assert(foo < bar) fails with "failed constraint: 0 = 1." | ||
#[error("Failed constraint")] | ||
FailedConstraint { lhs: FieldElement, rhs: FieldElement, location: Option<Location> }, | ||
#[error(transparent)] | ||
InternalError(#[from] InternalError), | ||
#[error("Index out of bounds, array has size {index:?}, but index was {array_size:?}")] | ||
IndexOutOfBounds { index: usize, array_size: usize, location: Option<Location> }, | ||
#[error("All Witnesses are by default u{num_bits:?} Applying this type does not apply any constraints.\n We also currently do not allow integers of size more than {num_bits:?}, this will be handled by BigIntegers.")] | ||
InvalidRangeConstraint { num_bits: u32, location: Option<Location> }, | ||
#[error("Expected array index to fit into a u64")] | ||
TypeConversion { from: String, into: String, location: Option<Location> }, | ||
#[error("{name:?} is not initialized")] | ||
UnInitialized { name: String, location: Option<Location> }, | ||
#[error("Integer sized {num_bits:?} is over the max supported size of {max_num_bits:?}")] | ||
UnsupportedIntegerSize { num_bits: u32, max_num_bits: u32, location: Option<Location> }, | ||
} | ||
|
||
impl From<RuntimeErrorKind> for RuntimeError { | ||
fn from(kind: RuntimeErrorKind) -> RuntimeError { | ||
RuntimeError { location: None, kind } | ||
} | ||
#[derive(Debug, PartialEq, Eq, Clone, Error)] | ||
pub enum InternalError { | ||
#[error("ICE: Both expressions should have degree<=1")] | ||
DegreeNotReduced { location: Option<Location> }, | ||
#[error("Try to get element from empty array")] | ||
EmptyArray { location: Option<Location> }, | ||
#[error("ICE: {message:?}")] | ||
General { message: String, location: Option<Location> }, | ||
#[error("ICE: {name:?} missing {arg:?} arg")] | ||
MissingArg { name: String, arg: String, location: Option<Location> }, | ||
#[error("ICE: {name:?} should be a constant")] | ||
NotAConstant { name: String, location: Option<Location> }, | ||
#[error("{name:?} is not implemented yet")] | ||
NotImplemented { name: String, location: Option<Location> }, | ||
#[error("ICE: Undeclared AcirVar")] | ||
UndeclaredAcirVar { location: Option<Location> }, | ||
#[error("ICE: Expected {expected:?}, found {found:?}")] | ||
UnExpected { expected: String, found: String, location: Option<Location> }, | ||
} | ||
|
||
impl From<RuntimeError> for FileDiagnostic { | ||
fn from(err: RuntimeError) -> Self { | ||
let file_id = err.location.map(|loc| loc.file).unwrap(); | ||
FileDiagnostic { file_id, diagnostic: err.into() } | ||
fn from(error: RuntimeError) -> Self { | ||
match error { | ||
RuntimeError::InternalError(ref ice_error) => match ice_error { | ||
InternalError::DegreeNotReduced { location } | ||
| InternalError::EmptyArray { location } | ||
| InternalError::General { location, .. } | ||
| InternalError::MissingArg { location, .. } | ||
| InternalError::NotAConstant { location, .. } | ||
| InternalError::NotImplemented { location, .. } | ||
| InternalError::UndeclaredAcirVar { location } | ||
| InternalError::UnExpected { location, .. } => { | ||
let file_id = location.map(|loc| loc.file).unwrap(); | ||
FileDiagnostic { file_id, diagnostic: error.into() } | ||
} | ||
}, | ||
RuntimeError::FailedConstraint { location, .. } | ||
| RuntimeError::IndexOutOfBounds { location, .. } | ||
| RuntimeError::InvalidRangeConstraint { location, .. } | ||
| RuntimeError::TypeConversion { location, .. } | ||
| RuntimeError::UnInitialized { location, .. } | ||
| RuntimeError::UnsupportedIntegerSize { location, .. } => { | ||
let file_id = location.map(|loc| loc.file).unwrap(); | ||
FileDiagnostic { file_id, diagnostic: error.into() } | ||
} | ||
} | ||
} | ||
} | ||
|
||
#[derive(Error, Debug)] | ||
pub enum RuntimeErrorKind { | ||
// Array errors | ||
#[error("Out of bounds")] | ||
ArrayOutOfBounds { index: u128, bound: u128 }, | ||
|
||
#[error("index out of bounds: the len is {index} but the index is {bound}")] | ||
IndexOutOfBounds { index: u32, bound: u128 }, | ||
|
||
#[error("cannot call {func_name} function in non main function")] | ||
FunctionNonMainContext { func_name: String }, | ||
|
||
// Environment errors | ||
#[error("Cannot find Array")] | ||
ArrayNotFound { found_type: String, name: String }, | ||
|
||
#[error("Not an object")] | ||
NotAnObject, | ||
|
||
#[error("Invalid id")] | ||
InvalidId, | ||
|
||
#[error("Attempt to divide by zero")] | ||
DivisionByZero, | ||
|
||
#[error("Failed range constraint when constraining to {0} bits")] | ||
FailedRangeConstraint(u32), | ||
|
||
#[error("Unsupported integer size of {num_bits} bits. The maximum supported size is {max_num_bits} bits.")] | ||
UnsupportedIntegerSize { num_bits: u32, max_num_bits: u32 }, | ||
|
||
#[error("Failed constraint")] | ||
FailedConstraint, | ||
|
||
#[error( | ||
"All Witnesses are by default u{0}. Applying this type does not apply any constraints." | ||
)] | ||
DefaultWitnesses(u32), | ||
|
||
#[error("Constraint is always false")] | ||
ConstraintIsAlwaysFalse, | ||
|
||
#[error("ICE: cannot convert signed {0} bit size into field")] | ||
CannotConvertSignedIntoField(u32), | ||
|
||
#[error("we do not allow private ABI inputs to be returned as public outputs")] | ||
PrivateAbiInput, | ||
|
||
#[error("unimplemented")] | ||
Unimplemented(String), | ||
|
||
#[error("Unsupported operation error")] | ||
UnsupportedOp { op: String, first_type: String, second_type: String }, | ||
} | ||
|
||
impl From<RuntimeError> for Diagnostic { | ||
fn from(error: RuntimeError) -> Diagnostic { | ||
let span = | ||
if let Some(loc) = error.location { loc.span } else { noirc_errors::Span::new(0..0) }; | ||
match &error.kind { | ||
RuntimeErrorKind::ArrayOutOfBounds { index, bound } => Diagnostic::simple_error( | ||
"index out of bounds".to_string(), | ||
format!("out of bounds error, index is {index} but length is {bound}"), | ||
span, | ||
), | ||
RuntimeErrorKind::ArrayNotFound { found_type, name } => Diagnostic::simple_error( | ||
format!("cannot find an array with name {name}"), | ||
format!("{found_type} has type"), | ||
span, | ||
match error { | ||
RuntimeError::InternalError(_) => Diagnostic::simple_error( | ||
"Internal Consistency Evaluators Errors: \n | ||
This is likely a bug. Consider Opening an issue at https://github.com/noir-lang/noir/issues".to_owned(), | ||
"".to_string(), | ||
noirc_errors::Span::new(0..0) | ||
), | ||
RuntimeErrorKind::NotAnObject | ||
| RuntimeErrorKind::InvalidId | ||
| RuntimeErrorKind::DivisionByZero | ||
| RuntimeErrorKind::FailedRangeConstraint(_) | ||
| RuntimeErrorKind::UnsupportedIntegerSize { .. } | ||
| RuntimeErrorKind::FailedConstraint | ||
| RuntimeErrorKind::DefaultWitnesses(_) | ||
| RuntimeErrorKind::CannotConvertSignedIntoField(_) | ||
| RuntimeErrorKind::IndexOutOfBounds { .. } | ||
| RuntimeErrorKind::PrivateAbiInput => { | ||
Diagnostic::simple_error("".to_owned(), error.kind.to_string(), span) | ||
RuntimeError::FailedConstraint { location, .. } | ||
| RuntimeError::IndexOutOfBounds { location, .. } | ||
| RuntimeError::InvalidRangeConstraint { location, .. } | ||
| RuntimeError::TypeConversion { location, .. } | ||
| RuntimeError::UnInitialized { location, .. } | ||
| RuntimeError::UnsupportedIntegerSize { location, .. } => { | ||
let span = if let Some(loc) = location { loc.span } else { noirc_errors::Span::new(0..0) }; | ||
Diagnostic::simple_error("".to_owned(), error.to_string(), span) | ||
} | ||
RuntimeErrorKind::UnsupportedOp { op, first_type, second_type } => { | ||
Diagnostic::simple_error( | ||
"unsupported operation".to_owned(), | ||
format!("no support for {op} with types {first_type} and {second_type}"), | ||
span, | ||
) | ||
} | ||
RuntimeErrorKind::ConstraintIsAlwaysFalse if error.location.is_some() => { | ||
Diagnostic::simple_error("".to_owned(), error.kind.to_string(), span) | ||
} | ||
RuntimeErrorKind::ConstraintIsAlwaysFalse => { | ||
Diagnostic::from_message(&error.kind.to_string()) | ||
} | ||
RuntimeErrorKind::Unimplemented(message) => Diagnostic::from_message(message), | ||
RuntimeErrorKind::FunctionNonMainContext { func_name } => Diagnostic::simple_error( | ||
"cannot call function outside of main".to_owned(), | ||
format!("function {func_name} can only be called in main"), | ||
span, | ||
), | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,3 @@ | ||
pub(crate) mod acir_variable; | ||
pub(crate) mod errors; | ||
pub(crate) mod generated_acir; | ||
pub(crate) mod sort; |
Oops, something went wrong.