diff --git a/compiler/noirc_frontend/src/elaborator/types.rs b/compiler/noirc_frontend/src/elaborator/types.rs index d4cbdac3507..a106f6687df 100644 --- a/compiler/noirc_frontend/src/elaborator/types.rs +++ b/compiler/noirc_frontend/src/elaborator/types.rs @@ -10,6 +10,7 @@ use crate::{ UnresolvedTypeExpression, }, hir::{ + comptime::{Interpreter, Value}, def_map::ModuleDefId, resolution::{ errors::ResolverError, @@ -498,6 +499,17 @@ impl<'context> Elaborator<'context> { BinaryOpKind::Modulo => Ok(lhs % rhs), } } + HirExpression::Cast(cast) => { + let lhs = self.try_eval_array_length_id_with_fuel(cast.lhs, span, fuel - 1)?; + let lhs_value = Value::Field(lhs.into()); + let evaluated_value = + Interpreter::evaluate_cast_one_step(&cast, rhs, lhs_value, self.interner) + .map_err(|error| Some(ResolverError::ArrayLengthInterpreter { error }))?; + + evaluated_value + .to_u128() + .ok_or_else(|| Some(ResolverError::InvalidArrayLengthExpr { span })) + } _other => Err(Some(ResolverError::InvalidArrayLengthExpr { span })), } } diff --git a/compiler/noirc_frontend/src/hir/comptime/errors.rs b/compiler/noirc_frontend/src/hir/comptime/errors.rs index 20e3fd94b7d..7b38f2fd32e 100644 --- a/compiler/noirc_frontend/src/hir/comptime/errors.rs +++ b/compiler/noirc_frontend/src/hir/comptime/errors.rs @@ -5,7 +5,7 @@ use noirc_errors::{CustomDiagnostic, Location}; use super::value::Value; /// The possible errors that can halt the interpreter. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum InterpreterError { ArgumentCountMismatch { expected: usize, actual: usize, location: Location }, TypeMismatch { expected: Type, value: Value, location: Location }, diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs index 82e7d70141d..314ea26e52c 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs @@ -290,7 +290,7 @@ impl<'a> Interpreter<'a> { HirExpression::MemberAccess(access) => self.evaluate_access(access, id), HirExpression::Call(call) => self.evaluate_call(call, id), HirExpression::MethodCall(call) => self.evaluate_method_call(call, id), - HirExpression::Cast(cast) => self.evaluate_cast(cast, id), + HirExpression::Cast(cast) => self.evaluate_cast(&cast, id), HirExpression::If(if_) => self.evaluate_if(if_, id), HirExpression::Tuple(tuple) => self.evaluate_tuple(tuple), HirExpression::Lambda(lambda) => self.evaluate_lambda(lambda, id), @@ -929,7 +929,18 @@ impl<'a> Interpreter<'a> { } } - fn evaluate_cast(&mut self, cast: HirCastExpression, id: ExprId) -> IResult { + fn evaluate_cast(&mut self, cast: &HirCastExpression, id: ExprId) -> IResult { + let evaluated_lhs = self.evaluate(cast.lhs)?; + Self::evaluate_cast_one_step(cast, id, evaluated_lhs, self.interner) + } + + /// evaluate_cast without recursion + pub fn evaluate_cast_one_step( + cast: &HirCastExpression, + id: ExprId, + evaluated_lhs: Value, + interner: &NodeInterner, + ) -> IResult { macro_rules! signed_int_to_field { ($x:expr) => {{ // Need to convert the signed integer to an i128 before @@ -943,7 +954,7 @@ impl<'a> Interpreter<'a> { }}; } - let (mut lhs, lhs_is_negative) = match self.evaluate(cast.lhs)? { + let (mut lhs, lhs_is_negative) = match evaluated_lhs { Value::Field(value) => (value, false), Value::U8(value) => ((value as u128).into(), false), Value::U16(value) => ((value as u128).into(), false), @@ -957,7 +968,7 @@ impl<'a> Interpreter<'a> { (if value { FieldElement::one() } else { FieldElement::zero() }, false) } value => { - let location = self.interner.expr_location(&id); + let location = interner.expr_location(&id); return Err(InterpreterError::NonNumericCasted { value, location }); } }; @@ -982,8 +993,8 @@ impl<'a> Interpreter<'a> { } Type::Integer(sign, bit_size) => match (sign, bit_size) { (Signedness::Unsigned, IntegerBitSize::One) => { - let location = self.interner.expr_location(&id); - Err(InterpreterError::TypeUnsupported { typ: cast.r#type, location }) + let location = interner.expr_location(&id); + Err(InterpreterError::TypeUnsupported { typ: cast.r#type.clone(), location }) } (Signedness::Unsigned, IntegerBitSize::Eight) => cast_to_int!(lhs, to_u128, u8, U8), (Signedness::Unsigned, IntegerBitSize::Sixteen) => { @@ -996,8 +1007,8 @@ impl<'a> Interpreter<'a> { cast_to_int!(lhs, to_u128, u64, U64) } (Signedness::Signed, IntegerBitSize::One) => { - let location = self.interner.expr_location(&id); - Err(InterpreterError::TypeUnsupported { typ: cast.r#type, location }) + let location = interner.expr_location(&id); + Err(InterpreterError::TypeUnsupported { typ: cast.r#type.clone(), location }) } (Signedness::Signed, IntegerBitSize::Eight) => cast_to_int!(lhs, to_i128, i8, I8), (Signedness::Signed, IntegerBitSize::Sixteen) => { @@ -1012,7 +1023,7 @@ impl<'a> Interpreter<'a> { }, Type::Bool => Ok(Value::Bool(!lhs.is_zero() || lhs_is_negative)), typ => { - let location = self.interner.expr_location(&id); + let location = interner.expr_location(&id); Err(InterpreterError::CastToNonNumericType { typ, location }) } } diff --git a/compiler/noirc_frontend/src/hir/comptime/value.rs b/compiler/noirc_frontend/src/hir/comptime/value.rs index f2ff93b8929..6cf0a1d9011 100644 --- a/compiler/noirc_frontend/src/hir/comptime/value.rs +++ b/compiler/noirc_frontend/src/hir/comptime/value.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, rc::Rc}; -use acvm::FieldElement; +use acvm::{AcirField, FieldElement}; use im::Vector; use iter_extended::{try_vecmap, vecmap}; use noirc_errors::Location; @@ -283,6 +283,23 @@ impl Value { interner.push_expr_type(id, typ); Ok(id) } + + /// Converts any unsigned `Value` into a `u128`. + /// Returns `None` for negative integers. + pub(crate) fn to_u128(&self) -> Option { + match self { + Self::Field(value) => Some(value.to_u128()), + Self::I8(value) => (*value >= 0).then_some(*value as u128), + Self::I16(value) => (*value >= 0).then_some(*value as u128), + Self::I32(value) => (*value >= 0).then_some(*value as u128), + Self::I64(value) => (*value >= 0).then_some(*value as u128), + Self::U8(value) => Some(*value as u128), + Self::U16(value) => Some(*value as u128), + Self::U32(value) => Some(*value as u128), + Self::U64(value) => Some(*value as u128), + _ => None, + } + } } /// Unwraps an Rc value without cloning the inner value if the reference count is 1. Clones otherwise. diff --git a/compiler/noirc_frontend/src/hir/resolution/errors.rs b/compiler/noirc_frontend/src/hir/resolution/errors.rs index fa4ea96316a..e8985deda11 100644 --- a/compiler/noirc_frontend/src/hir/resolution/errors.rs +++ b/compiler/noirc_frontend/src/hir/resolution/errors.rs @@ -2,7 +2,7 @@ pub use noirc_errors::Span; use noirc_errors::{CustomDiagnostic as Diagnostic, FileDiagnostic}; use thiserror::Error; -use crate::{ast::Ident, parser::ParserError, Type}; +use crate::{ast::Ident, hir::comptime::InterpreterError, parser::ParserError, Type}; use super::import::PathResolutionError; @@ -94,6 +94,8 @@ pub enum ResolverError { NoPredicatesAttributeOnUnconstrained { ident: Ident }, #[error("#[fold] attribute is only allowed on constrained functions")] FoldAttributeOnUnconstrained { ident: Ident }, + #[error("Invalid array length construction")] + ArrayLengthInterpreter { error: InterpreterError }, } impl ResolverError { @@ -386,6 +388,7 @@ impl<'a> From<&'a ResolverError> for Diagnostic { diag.add_note("The `#[fold]` attribute specifies whether a constrained function should be treated as a separate circuit rather than inlined into the program entry point".to_owned()); diag } + ResolverError::ArrayLengthInterpreter { error } => Diagnostic::from(error), } } } diff --git a/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/compiler/noirc_frontend/src/hir/resolution/resolver.rs index 8f5e99bacb9..8f15d7688de 100644 --- a/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -36,7 +36,11 @@ use crate::ast::{ }; use crate::graph::CrateId; use crate::hir::def_map::{ModuleDefId, TryFromModuleDefId, MAIN_FUNCTION}; -use crate::hir::{def_map::CrateDefMap, resolution::path_resolver::PathResolver}; +use crate::hir::{ + comptime::{Interpreter, Value}, + def_map::CrateDefMap, + resolution::path_resolver::PathResolver, +}; use crate::hir_def::stmt::{HirAssignStatement, HirForStatement, HirLValue, HirPattern}; use crate::node_interner::{ DefinitionId, DefinitionKind, DependencyId, ExprId, FuncId, GlobalId, NodeInterner, StmtId, @@ -2067,6 +2071,17 @@ impl<'a> Resolver<'a> { BinaryOpKind::Modulo => Ok(lhs % rhs), } } + HirExpression::Cast(cast) => { + let lhs = self.try_eval_array_length_id_with_fuel(cast.lhs, span, fuel - 1)?; + let lhs_value = Value::Field(lhs.into()); + let evaluated_value = + Interpreter::evaluate_cast_one_step(&cast, rhs, lhs_value, self.interner) + .map_err(|error| Some(ResolverError::ArrayLengthInterpreter { error }))?; + + evaluated_value + .to_u128() + .ok_or_else(|| Some(ResolverError::InvalidArrayLengthExpr { span })) + } _other => Err(Some(ResolverError::InvalidArrayLengthExpr { span })), } } diff --git a/compiler/noirc_frontend/src/hir/type_check/errors.rs b/compiler/noirc_frontend/src/hir/type_check/errors.rs index 549d8138f1d..0d8a9f3e717 100644 --- a/compiler/noirc_frontend/src/hir/type_check/errors.rs +++ b/compiler/noirc_frontend/src/hir/type_check/errors.rs @@ -28,7 +28,7 @@ pub enum Source { Return(FunctionReturnType, Span), } -#[derive(Error, Debug, Clone, PartialEq, Eq)] +#[derive(Error, Debug, Clone)] pub enum TypeCheckError { #[error("Operator {op:?} cannot be used in a {place:?}")] OpCannotBeUsed { op: HirBinaryOp, place: &'static str, span: Span }, diff --git a/test_programs/execution_success/cast_and_shift_global/Nargo.toml b/test_programs/execution_success/cast_and_shift_global/Nargo.toml new file mode 100644 index 00000000000..92af6ed8333 --- /dev/null +++ b/test_programs/execution_success/cast_and_shift_global/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "cast_and_shift_global" +type = "bin" +authors = [""] +compiler_version = ">=0.30.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/execution_success/cast_and_shift_global/Prover.toml b/test_programs/execution_success/cast_and_shift_global/Prover.toml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test_programs/execution_success/cast_and_shift_global/src/main.nr b/test_programs/execution_success/cast_and_shift_global/src/main.nr new file mode 100644 index 00000000000..577de78465c --- /dev/null +++ b/test_programs/execution_success/cast_and_shift_global/src/main.nr @@ -0,0 +1,8 @@ +global THREE: u64 = 3; +global EIGHT: u64 = 1 << THREE as u8; +global SEVEN: u64 = EIGHT - 1; + +fn main() { + assert([0; EIGHT] == [0; 8]); + assert([0; SEVEN] == [0; 7]); +}