diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs index d8e62b66eca..9f559b7c5e6 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs @@ -586,7 +586,19 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { consuming = false; if let Some(value) = values.pop_front() { - result.push_str(&value.display(self.elaborator.interner).to_string()); + // When interpolating a quoted value inside a format string, we don't include the + // surrounding `quote {` ... `}` as if we are unquoting the quoted value inside the string. + if let Value::Quoted(tokens) = value { + for (index, token) in tokens.iter().enumerate() { + if index > 0 { + result.push(' '); + } + result + .push_str(&token.display(self.elaborator.interner).to_string()); + } + } else { + result.push_str(&value.display(self.elaborator.interner).to_string()); + } } } other if !consuming => { diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index d1fcc76c55b..d2c9e4ffc0c 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -7,9 +7,9 @@ use acvm::{AcirField, FieldElement}; use builtin_helpers::{ block_expression_to_value, check_argument_count, check_function_not_yet_resolved, check_one_argument, check_three_arguments, check_two_arguments, get_expr, get_field, - get_function_def, get_module, get_quoted, get_slice, get_struct, get_trait_constraint, - get_trait_def, get_trait_impl, get_tuple, get_type, get_typed_expr, get_u32, - get_unresolved_type, hir_pattern_to_tokens, mutate_func_meta_type, parse, + get_format_string, get_function_def, get_module, get_quoted, get_slice, get_struct, + get_trait_constraint, get_trait_def, get_trait_impl, get_tuple, get_type, get_typed_expr, + get_u32, get_unresolved_type, hir_pattern_to_tokens, mutate_func_meta_type, parse, replace_func_meta_parameters, replace_func_meta_return_type, }; use chumsky::{prelude::choice, Parser}; @@ -32,6 +32,7 @@ use crate::{ InterpreterError, Value, }, hir_def::function::FunctionBody, + lexer::Lexer, macros_api::{HirExpression, HirLiteral, ModuleDefId, NodeInterner, Signedness}, node_interner::{DefinitionKind, TraitImplKind}, parser::{self}, @@ -95,6 +96,7 @@ impl<'local, 'context> Interpreter<'local, 'context> { "expr_is_continue" => expr_is_continue(interner, arguments, location), "expr_resolve" => expr_resolve(self, arguments, location), "is_unconstrained" => Ok(Value::Bool(true)), + "fmtstr_quoted_contents" => fmtstr_quoted_contents(interner, arguments, location), "function_def_body" => function_def_body(interner, arguments, location), "function_def_has_named_attribute" => { function_def_has_named_attribute(interner, arguments, location) @@ -1576,6 +1578,23 @@ fn unwrap_expr_value(interner: &NodeInterner, mut expr_value: ExprValue) -> Expr expr_value } +// fn quoted_contents(self) -> Quoted +fn fmtstr_quoted_contents( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let (string, _) = get_format_string(interner, self_argument)?; + let (tokens, _) = Lexer::lex(&string); + let mut tokens: Vec<_> = tokens.0.into_iter().map(|token| token.into_token()).collect(); + if let Some(Token::EOF) = tokens.last() { + tokens.pop(); + } + + Ok(Value::Quoted(Rc::new(tokens))) +} + // fn body(self) -> Expr fn function_def_body( interner: &NodeInterner, diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs index 14a0e177544..ff3da6d253f 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs @@ -189,6 +189,20 @@ pub(crate) fn get_expr( } } +pub(crate) fn get_format_string( + interner: &NodeInterner, + (value, location): (Value, Location), +) -> IResult<(Rc, Type)> { + match value { + Value::FormatString(value, typ) => Ok((value, typ)), + value => { + let n = Box::new(interner.next_type_variable()); + let e = Box::new(interner.next_type_variable()); + type_mismatch(value, Type::FmtString(n, e), location) + } + } +} + pub(crate) fn get_function_def((value, location): (Value, Location)) -> IResult { match value { Value::FunctionDefinition(id) => Ok(id), diff --git a/compiler/noirc_frontend/src/hir/comptime/value.rs b/compiler/noirc_frontend/src/hir/comptime/value.rs index fdac95a07fe..7d6e4475c7b 100644 --- a/compiler/noirc_frontend/src/hir/comptime/value.rs +++ b/compiler/noirc_frontend/src/hir/comptime/value.rs @@ -605,33 +605,7 @@ impl<'value, 'interner> Display for ValuePrinter<'value, 'interner> { write!(f, "quote {{")?; for token in tokens.iter() { write!(f, " ")?; - - match token { - Token::QuotedType(id) => { - write!(f, "{}", self.interner.get_quoted_type(*id))?; - } - Token::InternedExpr(id) => { - let value = Value::expression(ExpressionKind::Interned(*id)); - value.display(self.interner).fmt(f)?; - } - Token::InternedStatement(id) => { - let value = Value::statement(StatementKind::Interned(*id)); - value.display(self.interner).fmt(f)?; - } - Token::InternedLValue(id) => { - let value = Value::lvalue(LValue::Interned(*id, Span::default())); - value.display(self.interner).fmt(f)?; - } - Token::InternedUnresolvedTypeData(id) => { - let value = Value::UnresolvedType(UnresolvedTypeData::Interned(*id)); - value.display(self.interner).fmt(f)?; - } - Token::UnquoteMarker(id) => { - let value = Value::TypedExpr(TypedExpr::ExprId(*id)); - value.display(self.interner).fmt(f)?; - } - other => write!(f, "{other}")?, - } + token.display(self.interner).fmt(f)?; } write!(f, " }}") } @@ -713,6 +687,51 @@ impl<'value, 'interner> Display for ValuePrinter<'value, 'interner> { } } +impl Token { + pub fn display<'token, 'interner>( + &'token self, + interner: &'interner NodeInterner, + ) -> TokenPrinter<'token, 'interner> { + TokenPrinter { token: self, interner } + } +} + +pub struct TokenPrinter<'token, 'interner> { + token: &'token Token, + interner: &'interner NodeInterner, +} + +impl<'token, 'interner> Display for TokenPrinter<'token, 'interner> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.token { + Token::QuotedType(id) => { + write!(f, "{}", self.interner.get_quoted_type(*id)) + } + Token::InternedExpr(id) => { + let value = Value::expression(ExpressionKind::Interned(*id)); + value.display(self.interner).fmt(f) + } + Token::InternedStatement(id) => { + let value = Value::statement(StatementKind::Interned(*id)); + value.display(self.interner).fmt(f) + } + Token::InternedLValue(id) => { + let value = Value::lvalue(LValue::Interned(*id, Span::default())); + value.display(self.interner).fmt(f) + } + Token::InternedUnresolvedTypeData(id) => { + let value = Value::UnresolvedType(UnresolvedTypeData::Interned(*id)); + value.display(self.interner).fmt(f) + } + Token::UnquoteMarker(id) => { + let value = Value::TypedExpr(TypedExpr::ExprId(*id)); + value.display(self.interner).fmt(f) + } + other => write!(f, "{other}"), + } + } +} + fn display_trait_constraint(interner: &NodeInterner, trait_constraint: &TraitConstraint) -> String { let trait_ = interner.get_trait(trait_constraint.trait_id); format!("{}: {}{}", trait_constraint.typ, trait_.name, trait_constraint.trait_generics) diff --git a/docs/docs/noir/standard_library/fmtstr.md b/docs/docs/noir/standard_library/fmtstr.md new file mode 100644 index 00000000000..293793e23ff --- /dev/null +++ b/docs/docs/noir/standard_library/fmtstr.md @@ -0,0 +1,13 @@ +--- +title: fmtstr +--- + +`fmtstr` is the type resulting from using format string (`f"..."`). + +## Methods + +### quoted_contents + +#include_code quoted_contents noir_stdlib/src/meta/format_string.nr rust + +Returns the format string contents (that is, without the leading and trailing double quotes) as a `Quoted` value. \ No newline at end of file diff --git a/noir_stdlib/src/meta/format_string.nr b/noir_stdlib/src/meta/format_string.nr new file mode 100644 index 00000000000..44b69719efe --- /dev/null +++ b/noir_stdlib/src/meta/format_string.nr @@ -0,0 +1,6 @@ +impl fmtstr { + #[builtin(fmtstr_quoted_contents)] + // docs:start:quoted_contents + fn quoted_contents(self) -> Quoted {} + // docs:end:quoted_contents +} diff --git a/noir_stdlib/src/meta/mod.nr b/noir_stdlib/src/meta/mod.nr index 24398054467..9fc399ddbf9 100644 --- a/noir_stdlib/src/meta/mod.nr +++ b/noir_stdlib/src/meta/mod.nr @@ -1,4 +1,5 @@ mod expr; +mod format_string; mod function_def; mod module; mod op; 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 index 705a1b2ab4e..0e2d459a00f 100644 --- a/test_programs/compile_success_empty/comptime_fmt_strings/src/main.nr +++ b/test_programs/compile_success_empty/comptime_fmt_strings/src/main.nr @@ -12,4 +12,18 @@ fn main() { }; assert_eq(s1, "x is 4, fake interpolation: {y}, y is 5"); assert_eq(s2, "\0\0\0\0"); + + // Mainly test fmtstr::quoted_contents + call!(glue(quote { hello }, quote { world })); +} + +fn glue(x: Quoted, y: Quoted) -> Quoted { + f"{x}_{y}".quoted_contents() } + +fn hello_world() {} + +comptime fn call(x: Quoted) -> Quoted { + quote { $x() } +} + diff --git a/test_programs/compile_success_empty/unquote_struct/src/main.nr b/test_programs/compile_success_empty/unquote_struct/src/main.nr index e90711dd710..603440b5c76 100644 --- a/test_programs/compile_success_empty/unquote_struct/src/main.nr +++ b/test_programs/compile_success_empty/unquote_struct/src/main.nr @@ -10,11 +10,13 @@ fn foo(x: Field, y: u32) -> u32 { // Given a function, wrap its parameters in a struct definition comptime fn output_struct(f: FunctionDefinition) -> Quoted { - let fields = f.parameters().map(|param: (Quoted, Type)| { + let fields = f.parameters().map( + |param: (Quoted, Type)| { let name = param.0; let typ = param.1; quote { $name: $typ, } - }).join(quote {}); + } + ).join(quote {}); quote { struct Foo { $fields } diff --git a/tooling/lsp/src/requests/completion/tests.rs b/tooling/lsp/src/requests/completion/tests.rs index ca959f5d5ca..a7cfa77a73d 100644 --- a/tooling/lsp/src/requests/completion/tests.rs +++ b/tooling/lsp/src/requests/completion/tests.rs @@ -336,7 +336,7 @@ mod completion_tests { fo>|< } "#; - assert_completion(src, vec![module_completion_item("foobar")]).await; + assert_completion_excluding_auto_import(src, vec![module_completion_item("foobar")]).await; } #[test]