Skip to content

Commit

Permalink
feat: Add a comptime string type for string handling at compile-time (
Browse files Browse the repository at this point in the history
#6026)

# Description

## Problem\*

Resolves <!-- Link to GitHub Issue -->

## Summary\*

Adds CtString, a dynamically sized string type available at compile-time
only.

## Additional Context

- Named `CtString` because I didn't want to use up the more general
`String` in case we wanted one later. `CtString` also avoids users
accidentally using this in runtime code before they know what it is.
- Had to make a small change to allow `comptime` on trait functions so
that I could have one return `CtString` which is only allowed in
comptime code.

## 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
jfecher authored Sep 13, 2024
1 parent dfed81b commit 5d2984f
Show file tree
Hide file tree
Showing 20 changed files with 341 additions and 20 deletions.
12 changes: 11 additions & 1 deletion aztec_macros/src/utils/parse_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,17 @@ fn empty_noir_function(noir_function: &mut NoirFunction) {

fn empty_trait_item(trait_item: &mut TraitItem) {
match trait_item {
TraitItem::Function { name, generics, parameters, return_type, where_clause, body } => {
TraitItem::Function {
name,
generics,
parameters,
return_type,
where_clause,
body,
is_unconstrained: _,
visibility: _,
is_comptime: _,
} => {
empty_ident(name);
empty_unresolved_generics(generics);
for (name, typ) in parameters.iter_mut() {
Expand Down
27 changes: 24 additions & 3 deletions compiler/noirc_frontend/src/ast/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::ast::{
use crate::macros_api::SecondaryAttribute;
use crate::node_interner::TraitId;

use super::{Documented, GenericTypeArgs};
use super::{Documented, GenericTypeArgs, ItemVisibility};

/// AST node for trait definitions:
/// `trait name<generics> { ... items ... }`
Expand All @@ -29,6 +29,9 @@ pub struct NoirTrait {
#[derive(Clone, Debug)]
pub enum TraitItem {
Function {
is_unconstrained: bool,
visibility: ItemVisibility,
is_comptime: bool,
name: Ident,
generics: UnresolvedGenerics,
parameters: Vec<(Ident, UnresolvedType)>,
Expand Down Expand Up @@ -146,7 +149,17 @@ impl Display for NoirTrait {
impl Display for TraitItem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TraitItem::Function { name, generics, parameters, return_type, where_clause, body } => {
TraitItem::Function {
name,
generics,
parameters,
return_type,
where_clause,
body,
is_unconstrained,
visibility,
is_comptime,
} => {
let generics = vecmap(generics, |generic| generic.to_string());
let parameters = vecmap(parameters, |(name, typ)| format!("{name}: {typ}"));
let where_clause = vecmap(where_clause, ToString::to_string);
Expand All @@ -155,9 +168,17 @@ impl Display for TraitItem {
let parameters = parameters.join(", ");
let where_clause = where_clause.join(", ");

let unconstrained = if *is_unconstrained { "unconstrained " } else { "" };
let visibility = if *visibility == ItemVisibility::Private {
"".to_string()
} else {
visibility.to_string()
};
let is_comptime = if *is_comptime { "comptime " } else { "" };

write!(
f,
"fn {name}<{generics}>({parameters}) -> {return_type} where {where_clause}"
"{unconstrained}{visibility}{is_comptime}fn {name}<{generics}>({parameters}) -> {return_type} where {where_clause}"
)?;

if let Some(body) = body {
Expand Down
12 changes: 11 additions & 1 deletion compiler/noirc_frontend/src/ast/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,17 @@ impl TraitItem {

pub fn accept_children(&self, visitor: &mut impl Visitor) {
match self {
TraitItem::Function { name, generics, parameters, return_type, where_clause, body } => {
TraitItem::Function {
name,
generics,
parameters,
return_type,
where_clause,
body,
is_unconstrained: _,
visibility: _,
is_comptime: _,
} => {
if visitor.visit_trait_item_function(
name,
generics,
Expand Down
12 changes: 9 additions & 3 deletions compiler/noirc_frontend/src/elaborator/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ impl<'context> Elaborator<'context> {
return_type,
where_clause,
body: _,
is_unconstrained,
visibility: _,
is_comptime: _,
} = &item.item
{
self.recover_generics(|this| {
Expand Down Expand Up @@ -134,9 +137,12 @@ impl<'context> Elaborator<'context> {
};

let no_environment = Box::new(Type::Unit);
// TODO: unconstrained
let function_type =
Type::Function(arguments, Box::new(return_type), no_environment, false);
let function_type = Type::Function(
arguments,
Box::new(return_type),
no_environment,
*is_unconstrained,
);

functions.push(TraitFunction {
name: name.clone(),
Expand Down
36 changes: 35 additions & 1 deletion compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ use crate::{
Kind, QuotedType, ResolvedGeneric, Shared, Type, TypeVariable,
};

use self::builtin_helpers::{eq_item, get_array, get_str, get_u8, hash_item};
use self::builtin_helpers::{eq_item, get_array, get_ctstring, get_str, get_u8, hash_item};
use super::Interpreter;

pub(crate) mod builtin_helpers;
Expand All @@ -60,6 +60,8 @@ impl<'local, 'context> Interpreter<'local, 'context> {
"array_len" => array_len(interner, arguments, location),
"assert_constant" => Ok(Value::Bool(true)),
"as_slice" => as_slice(interner, arguments, location),
"ctstring_eq" => ctstring_eq(arguments, location),
"ctstring_hash" => ctstring_hash(arguments, location),
"expr_as_array" => expr_as_array(interner, arguments, return_type, location),
"expr_as_assert" => expr_as_assert(interner, arguments, return_type, location),
"expr_as_assert_eq" => expr_as_assert_eq(interner, arguments, return_type, location),
Expand Down Expand Up @@ -97,6 +99,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_as_ctstring" => fmtstr_as_ctstring(interner, arguments, location),
"fmtstr_quoted_contents" => fmtstr_quoted_contents(interner, arguments, location),
"fresh_type_variable" => fresh_type_variable(interner),
"function_def_add_attribute" => function_def_add_attribute(self, arguments, location),
Expand Down Expand Up @@ -151,6 +154,7 @@ impl<'local, 'context> Interpreter<'local, 'context> {
"slice_push_front" => slice_push_front(interner, arguments, location),
"slice_remove" => slice_remove(interner, arguments, location, call_stack),
"str_as_bytes" => str_as_bytes(interner, arguments, location),
"str_as_ctstring" => str_as_ctstring(interner, arguments, location),
"struct_def_add_attribute" => struct_def_add_attribute(interner, arguments, location),
"struct_def_add_generic" => struct_def_add_generic(interner, arguments, location),
"struct_def_as_type" => struct_def_as_type(interner, arguments, location),
Expand Down Expand Up @@ -297,6 +301,17 @@ fn str_as_bytes(
Ok(Value::Array(bytes, byte_array_type))
}

// fn str_as_ctstring(self) -> CtString
fn str_as_ctstring(
interner: &NodeInterner,
arguments: Vec<(Value, Location)>,
location: Location,
) -> IResult<Value> {
let self_argument = check_one_argument(arguments, location)?;
let string = get_str(interner, self_argument)?;
Ok(Value::CtString(string))
}

// fn add_attribute<let N: u32>(self, attribute: str<N>)
fn struct_def_add_attribute(
interner: &mut NodeInterner,
Expand Down Expand Up @@ -1863,6 +1878,17 @@ fn unwrap_expr_value(interner: &NodeInterner, mut expr_value: ExprValue) -> Expr
expr_value
}

// fn fmtstr_as_ctstring(self) -> CtString
fn fmtstr_as_ctstring(
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)?;
Ok(Value::CtString(string))
}

// fn quoted_contents(self) -> Quoted
fn fmtstr_quoted_contents(
interner: &NodeInterner,
Expand Down Expand Up @@ -2435,3 +2461,11 @@ pub(crate) fn extract_option_generic_type(typ: Type) -> Type {

generics.pop().expect("Expected Option to have a T generic type")
}

fn ctstring_eq(arguments: Vec<(Value, Location)>, location: Location) -> IResult<Value> {
eq_item(arguments, location, get_ctstring)
}

fn ctstring_hash(arguments: Vec<(Value, Location)>, location: Location) -> IResult<Value> {
hash_item(arguments, location, get_ctstring)
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,13 @@ pub(crate) fn get_str(
}
}

pub(crate) fn get_ctstring((value, location): (Value, Location)) -> IResult<Rc<String>> {
match value {
Value::CtString(string) => Ok(string),
value => type_mismatch(value, Type::Quoted(QuotedType::CtString), location),
}
}

pub(crate) fn get_tuple(
interner: &NodeInterner,
(value, location): (Value, Location),
Expand Down
11 changes: 9 additions & 2 deletions compiler/noirc_frontend/src/hir/comptime/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ pub enum Value {
U64(u64),
String(Rc<String>),
FormatString(Rc<String>, Type),
CtString(Rc<String>),
Function(FuncId, Type, Rc<TypeBindings>),
Closure(HirLambda, Vec<Value>, Type),
Tuple(Vec<Value>),
Expand Down Expand Up @@ -151,6 +152,7 @@ impl Value {
Value::Expr(_) => Type::Quoted(QuotedType::Expr),
Value::TypedExpr(_) => Type::Quoted(QuotedType::TypedExpr),
Value::UnresolvedType(_) => Type::Quoted(QuotedType::UnresolvedType),
Value::CtString(_) => Type::Quoted(QuotedType::CtString),
})
}

Expand Down Expand Up @@ -202,7 +204,9 @@ impl Value {
Value::U64(value) => {
ExpressionKind::Literal(Literal::Integer((value as u128).into(), false))
}
Value::String(value) => ExpressionKind::Literal(Literal::Str(unwrap_rc(value))),
Value::String(value) | Value::CtString(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)))
Expand Down Expand Up @@ -349,7 +353,9 @@ impl Value {
Value::U64(value) => {
HirExpression::Literal(HirLiteral::Integer((value as u128).into(), false))
}
Value::String(value) => HirExpression::Literal(HirLiteral::Str(unwrap_rc(value))),
Value::String(value) | Value::CtString(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)))
Expand Down Expand Up @@ -589,6 +595,7 @@ impl<'value, 'interner> Display for ValuePrinter<'value, 'interner> {
Value::U32(value) => write!(f, "{value}"),
Value::U64(value) => write!(f, "{value}"),
Value::String(value) => write!(f, "{value}"),
Value::CtString(value) => write!(f, "{value}"),
Value::FormatString(value, _) => write!(f, "{value}"),
Value::Function(..) => write!(f, "(function)"),
Value::Closure(_, _, _) => write!(f, "(closure)"),
Expand Down
7 changes: 5 additions & 2 deletions compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,9 @@ impl<'a> ModCollector<'a> {
return_type,
where_clause,
body,
is_unconstrained,
visibility: _,
is_comptime,
} => {
let func_id = context.def_interner.push_empty_fn();
method_ids.insert(name.to_string(), func_id);
Expand All @@ -434,9 +437,9 @@ impl<'a> ModCollector<'a> {
visibility: ItemVisibility::Public,
// TODO(Maddiaa): Investigate trait implementations with attributes see: https://github.com/noir-lang/noir/issues/2629
attributes: crate::token::Attributes::empty(),
is_unconstrained: false,
is_unconstrained: *is_unconstrained,
generic_count: generics.len(),
is_comptime: false,
is_comptime: *is_comptime,
name_location: location,
};

Expand Down
2 changes: 2 additions & 0 deletions compiler/noirc_frontend/src/hir_def/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ pub enum QuotedType {
UnresolvedType,
FunctionDefinition,
Module,
CtString,
}

/// A list of TypeVariableIds to bind to a type. Storing the
Expand Down Expand Up @@ -759,6 +760,7 @@ impl std::fmt::Display for QuotedType {
QuotedType::UnresolvedType => write!(f, "UnresolvedType"),
QuotedType::FunctionDefinition => write!(f, "FunctionDefinition"),
QuotedType::Module => write!(f, "Module"),
QuotedType::CtString => write!(f, "CtString"),
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions compiler/noirc_frontend/src/lexer/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1023,6 +1023,7 @@ pub enum Keyword {
Continue,
Contract,
Crate,
CtString,
Dep,
Else,
Expr,
Expand Down Expand Up @@ -1079,6 +1080,7 @@ impl fmt::Display for Keyword {
Keyword::Continue => write!(f, "continue"),
Keyword::Contract => write!(f, "contract"),
Keyword::Crate => write!(f, "crate"),
Keyword::CtString => write!(f, "CtString"),
Keyword::Dep => write!(f, "dep"),
Keyword::Else => write!(f, "else"),
Keyword::Expr => write!(f, "Expr"),
Expand Down Expand Up @@ -1138,6 +1140,7 @@ impl Keyword {
"continue" => Keyword::Continue,
"contract" => Keyword::Contract,
"crate" => Keyword::Crate,
"CtString" => Keyword::CtString,
"dep" => Keyword::Dep,
"else" => Keyword::Else,
"Expr" => Keyword::Expr,
Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_frontend/src/parser/parser/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ pub(super) fn function_definition(allow_self: bool) -> impl NoirParser<NoirFunct
/// function_modifiers: 'unconstrained'? (visibility)?
///
/// returns (is_unconstrained, visibility) for whether each keyword was present
fn function_modifiers() -> impl NoirParser<(bool, ItemVisibility, bool)> {
pub(super) fn function_modifiers() -> impl NoirParser<(bool, ItemVisibility, bool)> {
keyword(Keyword::Unconstrained).or_not().then(item_visibility()).then(maybe_comp_time()).map(
|((unconstrained, visibility), comptime)| (unconstrained.is_some(), visibility, comptime),
)
Expand Down
25 changes: 19 additions & 6 deletions compiler/noirc_frontend/src/parser/parser/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use chumsky::prelude::*;

use super::attributes::{attributes, validate_secondary_attributes};
use super::doc_comments::outer_doc_comments;
use super::function::function_return_type;
use super::function::{function_modifiers, function_return_type};
use super::path::path_no_turbofish;
use super::{
block, expression, fresh_statement, function, function_declaration_parameters, let_statement,
Expand Down Expand Up @@ -101,16 +101,29 @@ fn trait_function_declaration() -> impl NoirParser<TraitItem> {
}
});

keyword(Keyword::Fn)
.ignore_then(ident())
function_modifiers()
.then_ignore(keyword(Keyword::Fn))
.then(ident())
.then(function::generics())
.then(parenthesized(function_declaration_parameters()))
.then(function_return_type().map(|(_, typ)| typ))
.then(where_clause())
.then(trait_function_body_or_semicolon_or_error)
.map(|(((((name, generics), parameters), return_type), where_clause), body)| {
TraitItem::Function { name, generics, parameters, return_type, where_clause, body }
})
.map(
|((((((modifiers, name), generics), parameters), return_type), where_clause), body)| {
TraitItem::Function {
name,
generics,
parameters,
return_type,
where_clause,
body,
is_unconstrained: modifiers.0,
visibility: modifiers.1,
is_comptime: modifiers.2,
}
},
)
}

/// trait_type_declaration: 'type' ident generics
Expand Down
7 changes: 7 additions & 0 deletions compiler/noirc_frontend/src/parser/parser/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ pub(super) fn comptime_type() -> impl NoirParser<UnresolvedType> {
top_level_item_type(),
quoted_type(),
typed_expr_type(),
comptime_string_type(),
))
}

Expand Down Expand Up @@ -166,6 +167,12 @@ fn typed_expr_type() -> impl NoirParser<UnresolvedType> {
.map_with_span(|_, span| UnresolvedTypeData::Quoted(QuotedType::TypedExpr).with_span(span))
}

/// This is the `CtString` type for dynamically-sized compile-time strings
fn comptime_string_type() -> impl NoirParser<UnresolvedType> {
keyword(Keyword::CtString)
.map_with_span(|_, span| UnresolvedTypeData::Quoted(QuotedType::CtString).with_span(span))
}

/// This is the type of an already resolved type.
/// The only way this can appear in the token input is if an already resolved `Type` object
/// was spliced into a macro's token stream via the `$` operator.
Expand Down
Loading

0 comments on commit 5d2984f

Please sign in to comment.