diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs index 1035387f0a4..b0fde492d39 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs @@ -1,3 +1,4 @@ +use std::collections::VecDeque; use std::{collections::hash_map::Entry, rc::Rc}; use acvm::{acir::AcirField, FieldElement}; @@ -457,16 +458,50 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { self.evaluate_integer(value, is_negative, id) } HirLiteral::Str(string) => Ok(Value::String(Rc::new(string))), - HirLiteral::FmtStr(_, _) => { - let item = "format strings in a comptime context".into(); - let location = self.elaborator.interner.expr_location(&id); - Err(InterpreterError::Unimplemented { item, location }) + HirLiteral::FmtStr(string, captures) => { + self.evaluate_format_string(string, captures, id) } HirLiteral::Array(array) => self.evaluate_array(array, id), HirLiteral::Slice(array) => self.evaluate_slice(array, id), } } + fn evaluate_format_string( + &mut self, + string: String, + captures: Vec, + id: ExprId, + ) -> IResult { + let mut result = String::new(); + let mut escaped = false; + let mut consuming = false; + + let mut values: VecDeque<_> = + captures.into_iter().map(|capture| self.evaluate(capture)).collect::>()?; + + for character in string.chars() { + match character { + '\\' => escaped = true, + '{' if !escaped => consuming = true, + '}' if !escaped && consuming => { + consuming = false; + + if let Some(value) = values.pop_front() { + result.push_str(&value.to_string()); + } + } + other if !consuming => { + escaped = false; + result.push(other); + } + _ => (), + } + } + + let typ = self.elaborator.interner.id_type(id); + Ok(Value::FormatString(Rc::new(result), typ)) + } + fn evaluate_integer( &self, value: FieldElement, diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index 5d8294128c9..92890cb66b8 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -57,7 +57,7 @@ impl<'local, 'context> Interpreter<'local, 'context> { quoted_as_trait_constraint(interner, arguments, location) } "quoted_as_type" => quoted_as_type(self, arguments, location), - "zeroed" => zeroed(return_type, location), + "zeroed" => zeroed(return_type), _ => { let item = format!("Comptime evaluation for builtin function {name}"); Err(InterpreterError::Unimplemented { item, location }) @@ -461,12 +461,12 @@ fn trait_constraint_eq( } // fn zeroed() -> T -fn zeroed(return_type: Type, location: Location) -> IResult { +fn zeroed(return_type: Type) -> IResult { match return_type { Type::FieldElement => Ok(Value::Field(0u128.into())), Type::Array(length_type, elem) => { if let Some(length) = length_type.evaluate_to_u32() { - let element = zeroed(elem.as_ref().clone(), location)?; + let element = zeroed(elem.as_ref().clone())?; let array = std::iter::repeat(element).take(length as usize).collect(); Ok(Value::Array(array, Type::Array(length_type, elem))) } else { @@ -496,33 +496,37 @@ fn zeroed(return_type: Type, location: Location) -> IResult { Ok(Value::Zeroed(Type::String(length_type))) } } - Type::FmtString(_, _) => { - let item = "format strings in a comptime context".into(); - Err(InterpreterError::Unimplemented { item, location }) + Type::FmtString(length_type, captures) => { + let length = length_type.evaluate_to_u32(); + let typ = Type::FmtString(length_type, captures); + if let Some(length) = length { + Ok(Value::FormatString(Rc::new("\0".repeat(length as usize)), typ)) + } else { + // Assume we can resolve the length later + Ok(Value::Zeroed(typ)) + } } Type::Unit => Ok(Value::Unit), - Type::Tuple(fields) => { - Ok(Value::Tuple(try_vecmap(fields, |field| zeroed(field, location))?)) - } + Type::Tuple(fields) => Ok(Value::Tuple(try_vecmap(fields, zeroed)?)), Type::Struct(struct_type, generics) => { let fields = struct_type.borrow().get_fields(&generics); let mut values = HashMap::default(); for (field_name, field_type) in fields { - let field_value = zeroed(field_type, location)?; + let field_value = zeroed(field_type)?; values.insert(Rc::new(field_name), field_value); } let typ = Type::Struct(struct_type, generics); Ok(Value::Struct(values, typ)) } - Type::Alias(alias, generics) => zeroed(alias.borrow().get_type(&generics), location), + Type::Alias(alias, generics) => zeroed(alias.borrow().get_type(&generics)), typ @ Type::Function(..) => { // Using Value::Zeroed here is probably safer than using FuncId::dummy_id() or similar Ok(Value::Zeroed(typ)) } Type::MutableReference(element) => { - let element = zeroed(*element, location)?; + let element = zeroed(*element)?; Ok(Value::Pointer(Shared::new(element), false)) } Type::Quoted(QuotedType::TraitConstraint) => Ok(Value::TraitConstraint(TraitBound { diff --git a/compiler/noirc_frontend/src/hir/comptime/value.rs b/compiler/noirc_frontend/src/hir/comptime/value.rs index 9788ff5b7ef..d20372e6812 100644 --- a/compiler/noirc_frontend/src/hir/comptime/value.rs +++ b/compiler/noirc_frontend/src/hir/comptime/value.rs @@ -38,6 +38,7 @@ pub enum Value { U32(u32), U64(u64), String(Rc), + FormatString(Rc, Type), Function(FuncId, Type, Rc), Closure(HirLambda, Vec, Type), Tuple(Vec), @@ -74,6 +75,7 @@ impl Value { let length = Type::Constant(value.len() as u32); Type::String(Box::new(length)) } + Value::FormatString(_, typ) => return Cow::Borrowed(typ), Value::Function(_, typ, _) => return Cow::Borrowed(typ), Value::Closure(_, _, typ) => return Cow::Borrowed(typ), Value::Tuple(fields) => { @@ -150,6 +152,10 @@ impl Value { ExpressionKind::Literal(Literal::Integer((value as u128).into(), false)) } Value::String(value) => ExpressionKind::Literal(Literal::Str(unwrap_rc(value))), + // Format strings are lowered as normal strings since they are already interpolated. + Value::FormatString(value, _) => { + ExpressionKind::Literal(Literal::Str(unwrap_rc(value))) + } Value::Function(id, typ, bindings) => { let id = interner.function_definition_id(id); let impl_kind = ImplKind::NotATraitMethod; @@ -280,6 +286,10 @@ impl Value { HirExpression::Literal(HirLiteral::Integer((value as u128).into(), false)) } Value::String(value) => HirExpression::Literal(HirLiteral::Str(unwrap_rc(value))), + // Format strings are lowered as normal strings since they are already interpolated. + Value::FormatString(value, _) => { + HirExpression::Literal(HirLiteral::Str(unwrap_rc(value))) + } Value::Function(id, typ, bindings) => { let id = interner.function_definition_id(id); let impl_kind = ImplKind::NotATraitMethod; @@ -424,6 +434,7 @@ impl Display for Value { Value::U32(value) => write!(f, "{value}"), Value::U64(value) => write!(f, "{value}"), Value::String(value) => write!(f, "{value}"), + Value::FormatString(value, _) => write!(f, "{value}"), Value::Function(..) => write!(f, "(function)"), Value::Closure(_, _, _) => write!(f, "(closure)"), Value::Tuple(fields) => { diff --git a/test_programs/compile_success_empty/comptime_fmt_strings/Nargo.toml b/test_programs/compile_success_empty/comptime_fmt_strings/Nargo.toml new file mode 100644 index 00000000000..84162d3c093 --- /dev/null +++ b/test_programs/compile_success_empty/comptime_fmt_strings/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_fmt_strings" +type = "bin" +authors = [""] +compiler_version = ">=0.32.0" + +[dependencies] diff --git a/test_programs/compile_success_empty/comptime_fmt_strings/src/main.nr b/test_programs/compile_success_empty/comptime_fmt_strings/src/main.nr new file mode 100644 index 00000000000..19572fd15a1 --- /dev/null +++ b/test_programs/compile_success_empty/comptime_fmt_strings/src/main.nr @@ -0,0 +1,15 @@ +fn main() { + // format strings are lowered as normal strings + let (s1, s2): (str<39>, str<4>) = comptime { + let x = 4; + let y = 5; + + // Can't print these at compile-time here since printing to stdout while + // compiling breaks the test runner. + let s1 = f"x is {x}, fake interpolation: \{y}, y is {y}"; + let s2 = std::unsafe::zeroed::>(); + (s1, s2) + }; + assert_eq(s1, "x is 4, fake interpolation: {y}, y is 5"); + assert_eq(s2, "\0\0\0\0"); +}