Skip to content

Commit

Permalink
feat: add fmtstr::contents (#5928)
Browse files Browse the repository at this point in the history
# Description

## Problem

Resolves #5899
Resolves #5914

## Summary

Two things here:
1. When interpolating quotes values inside a format string, we do that
without producing the `quote {` and `}` parts, which is likely what a
user would expect (similar to unquoting those values).
2. In order to create identifiers (or any piece of code in general) by
joining severa quoted values you can use format strings together with
the new `fmtstr::contents` method, which returns a `Quoted` value with
the string contents (that is, without the leading and trailing double
quotes).

## Additional Context

I originally thought about a method like `fmtstr::as_identifier` that
would try to parse the string contents as an identifier (maybe an
`Ident`, or maybe a `Path`), returning `Option<Quoted>` and `None` in
case it couldn't be parsed to that. But I think in general it could be
more useful to just get the string contents as a `Quoted` value. After
all, if it isn't an identifier you'll learn it later on once the value
is unquoted or interpolated.

## Documentation

Check one:
- [ ] No documentation needed.
- [x] Documentation included in this PR.
- [ ] **[For Experimental Features]** Documentation to be submitted in a
separate PR.

# PR Checklist

- [x] I have tested the changes locally.
- [x] I have formatted the changes with [Prettier](https://prettier.io/)
and/or `cargo fmt` on default settings.
  • Loading branch information
asterite authored Sep 4, 2024
1 parent 2c22fe5 commit f18e9ca
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 32 deletions.
14 changes: 13 additions & 1 deletion compiler/noirc_frontend/src/hir/comptime/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down
25 changes: 22 additions & 3 deletions compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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},
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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<Value> {
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,20 @@ pub(crate) fn get_expr(
}
}

pub(crate) fn get_format_string(
interner: &NodeInterner,
(value, location): (Value, Location),
) -> IResult<(Rc<String>, 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<FuncId> {
match value {
Value::FunctionDefinition(id) => Ok(id),
Expand Down
73 changes: 46 additions & 27 deletions compiler/noirc_frontend/src/hir/comptime/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, " }}")
}
Expand Down Expand Up @@ -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)
Expand Down
13 changes: 13 additions & 0 deletions docs/docs/noir/standard_library/fmtstr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
title: fmtstr
---

`fmtstr<N, T>` 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.
6 changes: 6 additions & 0 deletions noir_stdlib/src/meta/format_string.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
impl <let N: u32, T> fmtstr<N, T> {
#[builtin(fmtstr_quoted_contents)]
// docs:start:quoted_contents
fn quoted_contents(self) -> Quoted {}
// docs:end:quoted_contents
}
1 change: 1 addition & 0 deletions noir_stdlib/src/meta/mod.nr
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod expr;
mod format_string;
mod function_def;
mod module;
mod op;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() }
}

2 changes: 1 addition & 1 deletion tooling/lsp/src/requests/completion/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down

0 comments on commit f18e9ca

Please sign in to comment.