Skip to content

Commit

Permalink
feat: add Expr::as_constructor (#5980)
Browse files Browse the repository at this point in the history
# Description

## Problem

Part of #5668

## Summary

Also add doc comments for `expr.nr`.

## Additional Context

I wasn't sure how to represent a `Path`... so I did it with a `Quoted`
value. I first thought about using a `Variable`, but then that wouldn't
be parsed right when parsing a constructor, so it had to be some kind of
Path.

## 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.

---------

Co-authored-by: jfecher <jake@aztecprotocol.com>
  • Loading branch information
asterite and jfecher authored Sep 13, 2024
1 parent 4149607 commit 76dea7b
Show file tree
Hide file tree
Showing 21 changed files with 301 additions and 233 deletions.
8 changes: 5 additions & 3 deletions aztec_macros/src/transforms/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,9 +222,11 @@ pub fn generate_storage_implementation(
})
.collect();

let storage_constructor_statement = make_statement(StatementKind::Expression(expression(
ExpressionKind::constructor((chained_path!(storage_struct_name), field_constructors)),
)));
let storage_constructor_statement =
make_statement(StatementKind::Expression(expression(ExpressionKind::constructor((
UnresolvedType::from_path(chained_path!(storage_struct_name)),
field_constructors,
)))));

// This is the type over which the impl is generic.
let generic_context_ident = ident("Context");
Expand Down
2 changes: 1 addition & 1 deletion aztec_macros/src/utils/parse_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ fn empty_method_call_expression(method_call_expression: &mut MethodCallExpressio
}

fn empty_constructor_expression(constructor_expression: &mut ConstructorExpression) {
empty_path(&mut constructor_expression.type_name);
empty_unresolved_type(&mut constructor_expression.typ);
for (name, expression) in constructor_expression.fields.iter_mut() {
empty_ident(name);
empty_expression(expression);
Expand Down
10 changes: 6 additions & 4 deletions compiler/noirc_frontend/src/ast/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,11 @@ impl ExpressionKind {
ExpressionKind::Literal(Literal::FmtStr(contents))
}

pub fn constructor((type_name, fields): (Path, Vec<(Ident, Expression)>)) -> ExpressionKind {
pub fn constructor(
(typ, fields): (UnresolvedType, Vec<(Ident, Expression)>),
) -> ExpressionKind {
ExpressionKind::Constructor(Box::new(ConstructorExpression {
type_name,
typ,
fields,
struct_type: None,
}))
Expand Down Expand Up @@ -536,7 +538,7 @@ pub struct MethodCallExpression {

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ConstructorExpression {
pub type_name: Path,
pub typ: UnresolvedType,
pub fields: Vec<(Ident, Expression)>,

/// This may be filled out during macro expansion
Expand Down Expand Up @@ -717,7 +719,7 @@ impl Display for ConstructorExpression {
let fields =
self.fields.iter().map(|(ident, expr)| format!("{ident}: {expr}")).collect::<Vec<_>>();

write!(f, "({} {{ {} }})", self.type_name, fields.join(", "))
write!(f, "({} {{ {} }})", self.typ, fields.join(", "))
}
}

Expand Down
13 changes: 13 additions & 0 deletions compiler/noirc_frontend/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,19 @@ impl UnresolvedType {
pub(crate) fn is_type_expression(&self) -> bool {
matches!(&self.typ, UnresolvedTypeData::Expression(_))
}

pub fn from_path(mut path: Path) -> Self {
let span = path.span;
let last_segment = path.segments.last_mut().unwrap();
let generics = last_segment.generics.take();
let generic_type_args = if let Some(generics) = generics {
GenericTypeArgs { ordered_args: generics, named_args: Vec::new() }
} else {
GenericTypeArgs::default()
};
let typ = UnresolvedTypeData::Named(path, generic_type_args, true);
UnresolvedType { typ, span }
}
}

impl UnresolvedTypeData {
Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_frontend/src/ast/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,7 @@ impl Pattern {
}
Some(Expression {
kind: ExpressionKind::Constructor(Box::new(ConstructorExpression {
type_name: path.clone(),
typ: UnresolvedType::from_path(path.clone()),
fields,
struct_type: None,
})),
Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_frontend/src/ast/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -948,7 +948,7 @@ impl ConstructorExpression {
}

pub fn accept_children(&self, visitor: &mut impl Visitor) {
self.type_name.accept(visitor);
self.typ.accept(visitor);

for (_field_name, expression) in &self.fields {
expression.accept(visitor);
Expand Down
33 changes: 27 additions & 6 deletions compiler/noirc_frontend/src/elaborator/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use rustc_hash::FxHashSet as HashSet;
use crate::{
ast::{
ArrayLiteral, ConstructorExpression, IfExpression, InfixExpression, Lambda,
UnresolvedTypeExpression,
UnresolvedTypeData, UnresolvedTypeExpression,
},
hir::{
comptime::{self, InterpreterError},
Expand Down Expand Up @@ -436,22 +436,43 @@ impl<'context> Elaborator<'context> {
&mut self,
constructor: ConstructorExpression,
) -> (HirExpression, Type) {
let span = constructor.typ.span;

// A constructor type can either be a Path or an interned UnresolvedType.
// We represent both as UnresolvedType (with Path being a Named UnresolvedType)
// and error if we don't get a Named path.
let mut typ = constructor.typ.typ;
if let UnresolvedTypeData::Interned(id) = typ {
typ = self.interner.get_unresolved_type_data(id).clone();
}
let UnresolvedTypeData::Named(mut path, generics, _) = typ else {
self.push_err(ResolverError::NonStructUsedInConstructor { typ: typ.to_string(), span });
return (HirExpression::Error, Type::Error);
};

let last_segment = path.segments.last_mut().unwrap();
if !generics.ordered_args.is_empty() {
last_segment.generics = Some(generics.ordered_args);
}

let exclude_last_segment = true;
self.check_unsupported_turbofish_usage(&constructor.type_name, exclude_last_segment);
self.check_unsupported_turbofish_usage(&path, exclude_last_segment);

let span = constructor.type_name.span();
let last_segment = constructor.type_name.last_segment();
let last_segment = path.last_segment();
let is_self_type = last_segment.ident.is_self_type_name();

let (r#type, struct_generics) = if let Some(struct_id) = constructor.struct_type {
let typ = self.interner.get_struct(struct_id);
let generics = typ.borrow().instantiate(self.interner);
(typ, generics)
} else {
match self.lookup_type_or_error(constructor.type_name) {
match self.lookup_type_or_error(path) {
Some(Type::Struct(r#type, struct_generics)) => (r#type, struct_generics),
Some(typ) => {
self.push_err(ResolverError::NonStructUsedInConstructor { typ, span });
self.push_err(ResolverError::NonStructUsedInConstructor {
typ: typ.to_string(),
span,
});
return (HirExpression::Error, Type::Error);
}
None => return (HirExpression::Error, Type::Error),
Expand Down
1 change: 1 addition & 0 deletions compiler/noirc_frontend/src/elaborator/patterns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ impl<'context> Elaborator<'context> {
Some(Type::Struct(struct_type, generics)) => (struct_type, generics),
None => return error_identifier(self),
Some(typ) => {
let typ = typ.to_string();
self.push_err(ResolverError::NonStructUsedInConstructor { typ, span });
return error_identifier(self);
}
Expand Down
4 changes: 2 additions & 2 deletions compiler/noirc_frontend/src/hir/comptime/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,8 +394,8 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic {
let mut diagnostic =
CustomDiagnostic::simple_error(primary, secondary, location.span);

// Only take at most 3 frames starting from the top of the stack to avoid producing too much output
for frame in call_stack.iter().rev().take(3) {
// Only take at most 5 frames starting from the top of the stack to avoid producing too much output
for frame in call_stack.iter().rev().take(5) {
diagnostic.add_secondary_with_file("".to_string(), frame.span, frame.file);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ impl HirExpression {
let struct_type = None;

ExpressionKind::Constructor(Box::new(ConstructorExpression {
type_name,
typ: UnresolvedType::from_path(type_name),
fields,
struct_type,
}))
Expand Down
50 changes: 41 additions & 9 deletions compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use builtin_helpers::{
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, has_named_attribute, hir_pattern_to_tokens,
mutate_func_meta_type, parse, replace_func_meta_parameters, replace_func_meta_return_type,
mutate_func_meta_type, parse, quote_ident, replace_func_meta_parameters,
replace_func_meta_return_type,
};
use chumsky::{chain::Chain, prelude::choice, Parser};
use im::Vector;
Expand All @@ -22,13 +23,13 @@ use crate::{
FunctionReturnType, IntegerBitSize, LValue, Literal, Pattern, Statement, StatementKind,
UnaryOp, UnresolvedType, UnresolvedTypeData, Visibility,
},
hir::def_collector::dc_crate::CollectedItems,
hir::{
comptime::{
errors::IResult,
value::{ExprValue, TypedExpr},
InterpreterError, Value,
},
def_collector::dc_crate::CollectedItems,
def_map::ModuleId,
},
hir_def::function::FunctionBody,
Expand Down Expand Up @@ -71,6 +72,9 @@ impl<'local, 'context> Interpreter<'local, 'context> {
"expr_as_bool" => expr_as_bool(interner, arguments, return_type, location),
"expr_as_cast" => expr_as_cast(interner, arguments, return_type, location),
"expr_as_comptime" => expr_as_comptime(interner, arguments, return_type, location),
"expr_as_constructor" => {
expr_as_constructor(interner, arguments, return_type, location)
}
"expr_as_function_call" => {
expr_as_function_call(interner, arguments, return_type, location)
}
Expand Down Expand Up @@ -1412,6 +1416,38 @@ fn expr_as_comptime(
})
}

// fn as_constructor(self) -> Option<(Quoted, [(Quoted, Expr)])>
fn expr_as_constructor(
interner: &mut NodeInterner,
arguments: Vec<(Value, Location)>,
return_type: Type,
location: Location,
) -> IResult<Value> {
let self_argument = check_one_argument(arguments, location)?;
let expr_value = get_expr(interner, self_argument)?;
let expr_value = unwrap_expr_value(interner, expr_value);

let option_value =
if let ExprValue::Expression(ExpressionKind::Constructor(constructor)) = expr_value {
let typ = Value::UnresolvedType(constructor.typ.typ);
let fields = constructor.fields.into_iter();
let fields = fields.map(|(name, value)| {
Value::Tuple(vec![quote_ident(&name), Value::expression(value.kind)])
});
let fields = fields.collect();
let fields_type = Type::Slice(Box::new(Type::Tuple(vec![
Type::Quoted(QuotedType::Quoted),
Type::Quoted(QuotedType::Expr),
])));
let fields = Value::Slice(fields, fields_type);
Some(Value::Tuple(vec![typ, fields]))
} else {
None
};

option(return_type, option_value)
}

// fn as_function_call(self) -> Option<(Expr, [Expr])>
fn expr_as_function_call(
interner: &NodeInterner,
Expand Down Expand Up @@ -1553,15 +1589,13 @@ fn expr_as_member_access(
) -> IResult<Value> {
expr_as(interner, arguments, return_type, location, |expr| match expr {
ExprValue::Expression(ExpressionKind::MemberAccess(member_access)) => {
let tokens = Rc::new(vec![Token::Ident(member_access.rhs.0.contents.clone())]);
Some(Value::Tuple(vec![
Value::expression(member_access.lhs.kind),
Value::Quoted(tokens),
quote_ident(&member_access.rhs),
]))
}
ExprValue::LValue(crate::ast::LValue::MemberAccess { object, field_name, span: _ }) => {
let tokens = Rc::new(vec![Token::Ident(field_name.0.contents.clone())]);
Some(Value::Tuple(vec![Value::lvalue(*object), Value::Quoted(tokens)]))
Some(Value::Tuple(vec![Value::lvalue(*object), quote_ident(&field_name)]))
}
_ => None,
})
Expand All @@ -1578,9 +1612,7 @@ fn expr_as_method_call(
if let ExprValue::Expression(ExpressionKind::MethodCall(method_call)) = expr {
let object = Value::expression(method_call.object.kind);

let name_tokens =
Rc::new(vec![Token::Ident(method_call.method_name.0.contents.clone())]);
let name = Value::Quoted(name_tokens);
let name = quote_ident(&method_call.method_name);

let generics = method_call.generics.unwrap_or_default().into_iter();
let generics = generics.map(|generic| Value::UnresolvedType(generic.typ)).collect();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use noirc_errors::Location;

use crate::{
ast::{
BlockExpression, ExpressionKind, IntegerBitSize, LValue, Pattern, Signedness,
BlockExpression, ExpressionKind, Ident, IntegerBitSize, LValue, Pattern, Signedness,
StatementKind, UnresolvedTypeData,
},
hir::{
Expand Down Expand Up @@ -468,6 +468,14 @@ pub(super) fn has_named_attribute(name: &str, attributes: &[SecondaryAttribute])
false
}

pub(super) fn quote_ident(ident: &Ident) -> Value {
Value::Quoted(ident_to_tokens(ident))
}

pub(super) fn ident_to_tokens(ident: &Ident) -> Rc<Vec<Token>> {
Rc::new(vec![Token::Ident(ident.0.contents.clone())])
}

pub(super) fn hash_item<T: Hash>(
arguments: Vec<(Value, Location)>,
location: Location,
Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_frontend/src/hir/comptime/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ impl Value {
// Since we've provided the struct_type, the path should be ignored.
let type_name = Path::from_single(String::new(), location.span);
ExpressionKind::Constructor(Box::new(ConstructorExpression {
type_name,
typ: UnresolvedType::from_path(type_name),
fields,
struct_type,
}))
Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_frontend/src/hir/resolution/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ pub enum ResolverError {
#[error("Test functions are not allowed to have any parameters")]
TestFunctionHasParameters { span: Span },
#[error("Only struct types can be used in constructor expressions")]
NonStructUsedInConstructor { typ: Type, span: Span },
NonStructUsedInConstructor { typ: String, span: Span },
#[error("Only struct types can have generics")]
NonStructWithGenerics { span: Span },
#[error("Cannot apply generics on Self type")]
Expand Down
7 changes: 6 additions & 1 deletion compiler/noirc_frontend/src/parser/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use self::primitives::{keyword, macro_quote_marker, mutable_reference, variable}
use self::types::{generic_type_args, maybe_comp_time};
use attributes::{attributes, inner_attribute, validate_secondary_attributes};
use doc_comments::{inner_doc_comments, outer_doc_comments};
use types::interned_unresolved_type;
pub use types::parse_type;
use visibility::item_visibility;
pub use visibility::visibility;
Expand Down Expand Up @@ -1228,7 +1229,11 @@ fn constructor(expr_parser: impl ExprParser) -> impl NoirParser<ExpressionKind>
.allow_trailing()
.delimited_by(just(Token::LeftBrace), just(Token::RightBrace));

path(super::parse_type()).then(args).map(ExpressionKind::constructor)
let path = path(super::parse_type()).map(UnresolvedType::from_path);
let interned_unresolved_type = interned_unresolved_type();
let typ = choice((path, interned_unresolved_type));

typ.then(args).map(ExpressionKind::constructor)
}

fn constructor_field<P>(expr_parser: P) -> impl NoirParser<(Ident, Expression)>
Expand Down
14 changes: 14 additions & 0 deletions docs/docs/noir/standard_library/meta/expr.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,27 @@ a slice containing each statement.

If this expression is a boolean literal, return that literal.

### as_cast

#include_code as_cast noir_stdlib/src/meta/expr.nr rust

If this expression is a cast expression (`expr as type`), returns the casted
expression and the type to cast to.

### as_comptime

#include_code as_comptime noir_stdlib/src/meta/expr.nr rust

If this expression is a `comptime { stmt1; stmt2; ...; stmtN }` block,
return each statement in the block.

### as_constructor

#include_code as_constructor noir_stdlib/src/meta/expr.nr rust

If this expression is a constructor `Type { field1: expr1, ..., fieldN: exprN }`,
return the type and the fields.

### as_function_call

#include_code as_function_call noir_stdlib/src/meta/expr.nr rust
Expand Down
Loading

0 comments on commit 76dea7b

Please sign in to comment.