Skip to content

Commit

Permalink
feat(experimental): Implement macro calls & splicing into Expr valu…
Browse files Browse the repository at this point in the history
…es (#5203)

# Description

## Problem\*

Resolves #4591 

## Summary\*

Implements macro calls `foo!()` and splicing via the `$` operator of
`Expr` values into `quote` expressions.

```rust
comptime fn my_macro(x: Field, y: Field) -> Expr {
    quote { $x + $y + x + y }
}

fn main(x: Field, y: pub Field) {
    let result = my_macro!(1, 2);
    assert_eq(result, 1 + 2 + x + y);
}
```

## Additional Context

This feature is elaborator-only and does not work with the resolver /
type checker. I've yet to add the integration test that pairs with this
as a result since I'd have to add a check to the build step to exclude
it from any runs without `--use-elaborator`. I'll add this later but the
PR should be ready for review at least now.

## Documentation\*

Check one:
- [ ] No documentation needed.
- [ ] Documentation included in this PR.
- [x] **[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: Maxim Vezenov <mvezenov@gmail.com>
  • Loading branch information
jfecher and vezenovm committed Jun 14, 2024
1 parent da1549c commit d9b4712
Show file tree
Hide file tree
Showing 29 changed files with 877 additions and 83 deletions.
2 changes: 1 addition & 1 deletion compiler/noirc_driver/src/abi_gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ pub(super) fn abi_type_from_hir_type(context: &Context, typ: &Type) -> AbiType {
| Type::TypeVariable(_, _)
| Type::NamedGeneric(..)
| Type::Forall(..)
| Type::Code
| Type::Expr
| Type::Slice(_)
| Type::Function(_, _, _) => unreachable!("{typ} cannot be used in the abi"),
Type::FmtString(_, _) => unreachable!("format strings cannot be used in the abi"),
Expand Down
11 changes: 11 additions & 0 deletions compiler/noirc_frontend/src/ast/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,17 @@ pub enum ExpressionKind {
Lambda(Box<Lambda>),
Parenthesized(Box<Expression>),
Quote(BlockExpression, Span),
Unquote(Box<Expression>),
Comptime(BlockExpression, Span),

/// Unquote expressions are replaced with UnquoteMarkers when Quoted
/// expressions are resolved. Since the expression being quoted remains an
/// ExpressionKind (rather than a resolved ExprId), the UnquoteMarker must be
/// here in the AST even though it is technically HIR-only.
/// Each usize in an UnquoteMarker is an index which corresponds to the index of the
/// expression in the Hir::Quote expression list to replace it with.
UnquoteMarker(usize),

// This variant is only emitted when inlining the result of comptime
// code. It is used to translate function values back into the AST while
// guaranteeing they have the same instantiated type and definition id without resolving again.
Expand Down Expand Up @@ -552,6 +561,8 @@ impl Display for ExpressionKind {
Comptime(block, _) => write!(f, "comptime {block}"),
Error => write!(f, "Error"),
Resolved(_) => write!(f, "?Resolved"),
Unquote(expr) => write!(f, "$({expr})"),
UnquoteMarker(index) => write!(f, "${index}"),
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions compiler/noirc_frontend/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ pub enum UnresolvedTypeData {
),

// The type of quoted code for metaprogramming
Code,
Expr,

Unspecified, // This is for when the user declares a variable without specifying it's type
Error,
Expand Down Expand Up @@ -216,7 +216,7 @@ impl std::fmt::Display for UnresolvedTypeData {
}
}
MutableReference(element) => write!(f, "&mut {element}"),
Code => write!(f, "Code"),
Expr => write!(f, "Expr"),
Unit => write!(f, "()"),
Error => write!(f, "error"),
Unspecified => write!(f, "unspecified"),
Expand Down
6 changes: 5 additions & 1 deletion compiler/noirc_frontend/src/ast/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -728,7 +728,11 @@ impl Display for LValue {
impl Display for Path {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let segments = vecmap(&self.segments, ToString::to_string);
write!(f, "{}::{}", self.kind, segments.join("::"))
if self.kind == PathKind::Plain {
write!(f, "{}", segments.join("::"))
} else {
write!(f, "{}::{}", self.kind, segments.join("::"))
}
}
}

Expand Down
104 changes: 98 additions & 6 deletions compiler/noirc_frontend/src/elaborator/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::{
HirArrayLiteral, HirBinaryOp, HirBlockExpression, HirCallExpression, HirCastExpression,
HirConstructorExpression, HirIfExpression, HirIndexExpression, HirInfixExpression,
HirLambda, HirMemberAccess, HirMethodCallExpression, HirMethodReference,
HirPrefixExpression,
HirPrefixExpression, HirQuoted,
},
traits::TraitConstraint,
},
Expand Down Expand Up @@ -64,6 +64,13 @@ impl<'context> Elaborator<'context> {
}
ExpressionKind::Resolved(id) => return (id, self.interner.id_type(id)),
ExpressionKind::Error => (HirExpression::Error, Type::Error),
ExpressionKind::Unquote(_) => {
self.push_err(ResolverError::UnquoteUsedOutsideQuote { span: expr.span });
(HirExpression::Error, Type::Error)
}
ExpressionKind::UnquoteMarker(index) => {
unreachable!("UnquoteMarker({index}) remaining in runtime code")
}
};
let id = self.interner.push_expr(hir_expr);
self.interner.push_expr_location(id, expr.span, self.file);
Expand Down Expand Up @@ -280,10 +287,22 @@ impl<'context> Elaborator<'context> {
(typ, arg, span)
});

// Avoid cloning arguments unless this is a macro call
let mut comptime_args = Vec::new();
if call.is_macro_call {
comptime_args = arguments.clone();
}

let location = Location::new(span, self.file);
let call = HirCallExpression { func, arguments, location };
let typ = self.type_check_call(&call, func_type, args, span);
(HirExpression::Call(call), typ)
let hir_call = HirCallExpression { func, arguments, location };
let typ = self.type_check_call(&hir_call, func_type, args, span);

if call.is_macro_call {
self.call_macro(func, comptime_args, location, typ)
.unwrap_or_else(|| (HirExpression::Error, Type::Error))
} else {
(HirExpression::Call(hir_call), typ)
}
}

fn elaborate_method_call(
Expand Down Expand Up @@ -627,8 +646,11 @@ impl<'context> Elaborator<'context> {
(expr, Type::Function(arg_types, Box::new(body_type), Box::new(env_type)))
}

fn elaborate_quote(&mut self, block: BlockExpression) -> (HirExpression, Type) {
(HirExpression::Quote(block), Type::Code)
fn elaborate_quote(&mut self, mut block: BlockExpression) -> (HirExpression, Type) {
let mut unquoted_exprs = Vec::new();
self.find_unquoted_exprs_in_block(&mut block, &mut unquoted_exprs);
let quoted = HirQuoted { quoted_block: block, unquoted_exprs };
(HirExpression::Quote(quoted), Type::Expr)
}

fn elaborate_comptime_block(&mut self, block: BlockExpression, span: Span) -> (ExprId, Type) {
Expand Down Expand Up @@ -661,4 +683,74 @@ impl<'context> Elaborator<'context> {
Err(error) => make_error(self, error),
}
}

fn try_get_comptime_function(
&mut self,
func: ExprId,
location: Location,
) -> Result<FuncId, ResolverError> {
match self.interner.expression(&func) {
HirExpression::Ident(ident, _generics) => {
let definition = self.interner.definition(ident.id);
if let DefinitionKind::Function(function) = definition.kind {
let meta = self.interner.function_modifiers(&function);
if meta.is_comptime {
Ok(function)
} else {
Err(ResolverError::MacroIsNotComptime { span: location.span })
}
} else {
Err(ResolverError::InvalidSyntaxInMacroCall { span: location.span })
}
}
_ => Err(ResolverError::InvalidSyntaxInMacroCall { span: location.span }),
}
}

/// Call a macro function and inlines its code at the call site.
/// This will also perform a type check to ensure that the return type is an `Expr` value.
fn call_macro(
&mut self,
func: ExprId,
arguments: Vec<ExprId>,
location: Location,
return_type: Type,
) -> Option<(HirExpression, Type)> {
self.unify(&return_type, &Type::Expr, || TypeCheckError::MacroReturningNonExpr {
typ: return_type.clone(),
span: location.span,
});

let function = match self.try_get_comptime_function(func, location) {
Ok(function) => function,
Err(error) => {
self.push_err(error);
return None;
}
};

let mut interpreter = Interpreter::new(self.interner, &mut self.comptime_scopes);

let mut comptime_args = Vec::new();
let mut errors = Vec::new();

for argument in arguments {
match interpreter.evaluate(argument) {
Ok(arg) => {
let location = interpreter.interner.expr_location(&argument);
comptime_args.push((arg, location));
}
Err(error) => errors.push((error.into(), self.file)),
}
}

if !errors.is_empty() {
self.errors.append(&mut errors);
return None;
}

let result = interpreter.call_function(function, comptime_args, location);
let (expr_id, typ) = self.inline_comptime_value(result, location.span);
Some((self.interner.expression(&expr_id), typ))
}
}
1 change: 1 addition & 0 deletions compiler/noirc_frontend/src/elaborator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ mod scope;
mod statements;
mod traits;
mod types;
mod unquote;

use fm::FileId;
use iter_extended::vecmap;
Expand Down
4 changes: 2 additions & 2 deletions compiler/noirc_frontend/src/elaborator/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ impl<'context> Elaborator<'context> {
let fields = self.resolve_type_inner(*fields);
Type::FmtString(Box::new(resolved_size), Box::new(fields))
}
Code => Type::Code,
Expr => Type::Expr,
Unit => Type::Unit,
Unspecified => Type::Error,
Error => Type::Error,
Expand Down Expand Up @@ -1396,7 +1396,7 @@ impl<'context> Elaborator<'context> {
| Type::TypeVariable(_, _)
| Type::Constant(_)
| Type::NamedGeneric(_, _)
| Type::Code
| Type::Expr
| Type::Forall(_, _) => (),

Type::TraitAsType(_, _, args) => {
Expand Down
Loading

0 comments on commit d9b4712

Please sign in to comment.