Skip to content

Commit

Permalink
feat: Module::add_item (#5947)
Browse files Browse the repository at this point in the history
# Description

## Problem

Resolves #5875

## Summary


## Additional Context



## 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 6, 2024
1 parent 6004067 commit af50a7b
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 43 deletions.
44 changes: 34 additions & 10 deletions compiler/noirc_frontend/src/elaborator/comptime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,41 @@ impl<'context> Elaborator<'context> {
/// Elaborate an expression from the middle of a comptime scope.
/// When this happens we require additional information to know
/// what variables should be in scope.
pub fn elaborate_item_from_comptime<'a, T>(
pub fn elaborate_item_from_comptime_in_function<'a, T>(
&'a mut self,
current_function: Option<FuncId>,
f: impl FnOnce(&mut Elaborator<'a>) -> T,
) -> T {
self.elaborate_item_from_comptime(f, |elaborator| {
if let Some(function) = current_function {
let meta = elaborator.interner.function_meta(&function);
elaborator.current_item = Some(DependencyId::Function(function));
elaborator.crate_id = meta.source_crate;
elaborator.local_module = meta.source_module;
elaborator.file = meta.source_file;
elaborator.introduce_generics_into_scope(meta.all_generics.clone());
}
})
}

pub fn elaborate_item_from_comptime_in_module<'a, T>(
&'a mut self,
module: ModuleId,
file: FileId,
f: impl FnOnce(&mut Elaborator<'a>) -> T,
) -> T {
self.elaborate_item_from_comptime(f, |elaborator| {
elaborator.current_item = None;
elaborator.crate_id = module.krate;
elaborator.local_module = module.local_id;
elaborator.file = file;
})
}

fn elaborate_item_from_comptime<'a, T>(
&'a mut self,
f: impl FnOnce(&mut Elaborator<'a>) -> T,
setup: impl FnOnce(&mut Elaborator<'a>),
) -> T {
// Create a fresh elaborator to ensure no state is changed from
// this elaborator
Expand All @@ -70,14 +101,7 @@ impl<'context> Elaborator<'context> {
elaborator.function_context.push(FunctionContext::default());
elaborator.scopes.start_function();

if let Some(function) = current_function {
let meta = elaborator.interner.function_meta(&function);
elaborator.current_item = Some(DependencyId::Function(function));
elaborator.crate_id = meta.source_crate;
elaborator.local_module = meta.source_module;
elaborator.file = meta.source_file;
elaborator.introduce_generics_into_scope(meta.all_generics.clone());
}
setup(&mut elaborator);

elaborator.populate_scope_from_comptime_scopes();

Expand Down Expand Up @@ -351,7 +375,7 @@ impl<'context> Elaborator<'context> {
}
}

fn add_item(
pub(crate) fn add_item(
&mut self,
item: TopLevelStatement,
generated_items: &mut CollectedItems,
Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_frontend/src/elaborator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ impl<'context> Elaborator<'context> {
this
}

fn elaborate_items(&mut self, mut items: CollectedItems) {
pub(crate) fn elaborate_items(&mut self, mut items: CollectedItems) {
// We must first resolve and intern the globals before we can resolve any stmts inside each function.
// Each function uses its own resolver with a newly created ScopeForest, and must be resolved again to be within a function's scope
//
Expand Down
22 changes: 18 additions & 4 deletions compiler/noirc_frontend/src/hir/comptime/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::collections::VecDeque;
use std::{collections::hash_map::Entry, rc::Rc};

use acvm::{acir::AcirField, FieldElement};
use fm::FileId;
use im::Vector;
use iter_extended::try_vecmap;
use noirc_errors::Location;
Expand All @@ -10,6 +11,7 @@ use rustc_hash::FxHashMap as HashMap;
use crate::ast::{BinaryOpKind, FunctionKind, IntegerBitSize, Signedness};
use crate::elaborator::Elaborator;
use crate::graph::CrateId;
use crate::hir::def_map::ModuleId;
use crate::hir_def::expr::ImplKind;
use crate::hir_def::function::FunctionBody;
use crate::macros_api::UnaryOp;
Expand Down Expand Up @@ -170,7 +172,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {
Some(body) => Ok(body),
None => {
if matches!(&meta.function_body, FunctionBody::Unresolved(..)) {
self.elaborate_item(None, |elaborator| {
self.elaborate_in_function(None, |elaborator| {
elaborator.elaborate_function(function);
});

Expand All @@ -183,13 +185,25 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {
}
}

fn elaborate_item<T>(
fn elaborate_in_function<T>(
&mut self,
function: Option<FuncId>,
f: impl FnOnce(&mut Elaborator) -> T,
) -> T {
self.unbind_generics_from_previous_function();
let result = self.elaborator.elaborate_item_from_comptime(function, f);
let result = self.elaborator.elaborate_item_from_comptime_in_function(function, f);
self.rebind_generics_from_previous_function();
result
}

fn elaborate_in_module<T>(
&mut self,
module: ModuleId,
file: FileId,
f: impl FnOnce(&mut Elaborator) -> T,
) -> T {
self.unbind_generics_from_previous_function();
let result = self.elaborator.elaborate_item_from_comptime_in_module(module, file, f);
self.rebind_generics_from_previous_function();
result
}
Expand Down Expand Up @@ -1244,7 +1258,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {
let mut result = self.call_function(function_id, arguments, bindings, location)?;
if call.is_macro_call {
let expr = result.into_expression(self.elaborator.interner, location)?;
let expr = self.elaborate_item(self.current_function, |elaborator| {
let expr = self.elaborate_in_function(self.current_function, |elaborator| {
elaborator.elaborate_expression(expr).0
});
result = self.evaluate(expr)?;
Expand Down
81 changes: 57 additions & 24 deletions compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use crate::{
FunctionReturnType, IntegerBitSize, LValue, Literal, Statement, StatementKind, UnaryOp,
UnresolvedType, UnresolvedTypeData, Visibility,
},
hir::def_collector::dc_crate::CollectedItems,
hir::{
comptime::{
errors::IResult,
Expand Down Expand Up @@ -117,6 +118,7 @@ impl<'local, 'context> Interpreter<'local, 'context> {
"function_def_set_return_public" => {
function_def_set_return_public(self, arguments, location)
}
"module_add_item" => module_add_item(self, arguments, location),
"module_functions" => module_functions(self, arguments, location),
"module_has_named_attribute" => module_has_named_attribute(self, arguments, location),
"module_is_contract" => module_is_contract(self, arguments, location),
Expand Down Expand Up @@ -663,9 +665,10 @@ fn quoted_as_module(

let path = parse(argument, parser::path_no_turbofish(), "a path").ok();
let option_value = path.and_then(|path| {
let module = interpreter.elaborate_item(interpreter.current_function, |elaborator| {
elaborator.resolve_module_by_path(path)
});
let module = interpreter
.elaborate_in_function(interpreter.current_function, |elaborator| {
elaborator.resolve_module_by_path(path)
});
module.map(Value::ModuleDefinition)
});

Expand All @@ -681,7 +684,7 @@ fn quoted_as_trait_constraint(
let argument = check_one_argument(arguments, location)?;
let trait_bound = parse(argument, parser::trait_bound(), "a trait constraint")?;
let bound = interpreter
.elaborate_item(interpreter.current_function, |elaborator| {
.elaborate_in_function(interpreter.current_function, |elaborator| {
elaborator.resolve_trait_bound(&trait_bound, Type::Unit)
})
.ok_or(InterpreterError::FailedToResolveTraitBound { trait_bound, location })?;
Expand All @@ -697,8 +700,8 @@ fn quoted_as_type(
) -> IResult<Value> {
let argument = check_one_argument(arguments, location)?;
let typ = parse(argument, parser::parse_type(), "a type")?;
let typ =
interpreter.elaborate_item(interpreter.current_function, |elab| elab.resolve_type(typ));
let typ = interpreter
.elaborate_in_function(interpreter.current_function, |elab| elab.resolve_type(typ));
Ok(Value::Type(typ))
}

Expand Down Expand Up @@ -1768,23 +1771,25 @@ fn expr_resolve(
interpreter.current_function
};

let value = interpreter.elaborate_item(function_to_resolve_in, |elaborator| match expr_value {
ExprValue::Expression(expression_kind) => {
let expr = Expression { kind: expression_kind, span: self_argument_location.span };
let (expr_id, _) = elaborator.elaborate_expression(expr);
Value::TypedExpr(TypedExpr::ExprId(expr_id))
}
ExprValue::Statement(statement_kind) => {
let statement = Statement { kind: statement_kind, span: self_argument_location.span };
let (stmt_id, _) = elaborator.elaborate_statement(statement);
Value::TypedExpr(TypedExpr::StmtId(stmt_id))
}
ExprValue::LValue(lvalue) => {
let expr = lvalue.as_expression();
let (expr_id, _) = elaborator.elaborate_expression(expr);
Value::TypedExpr(TypedExpr::ExprId(expr_id))
}
});
let value =
interpreter.elaborate_in_function(function_to_resolve_in, |elaborator| match expr_value {
ExprValue::Expression(expression_kind) => {
let expr = Expression { kind: expression_kind, span: self_argument_location.span };
let (expr_id, _) = elaborator.elaborate_expression(expr);
Value::TypedExpr(TypedExpr::ExprId(expr_id))
}
ExprValue::Statement(statement_kind) => {
let statement =
Statement { kind: statement_kind, span: self_argument_location.span };
let (stmt_id, _) = elaborator.elaborate_statement(statement);
Value::TypedExpr(TypedExpr::StmtId(stmt_id))
}
ExprValue::LValue(lvalue) => {
let expr = lvalue.as_expression();
let (expr_id, _) = elaborator.elaborate_expression(expr);
Value::TypedExpr(TypedExpr::ExprId(expr_id))
}
});

Ok(value)
}
Expand Down Expand Up @@ -2052,7 +2057,7 @@ fn function_def_set_parameters(
"a pattern",
)?;

let hir_pattern = interpreter.elaborate_item(Some(func_id), |elaborator| {
let hir_pattern = interpreter.elaborate_in_function(Some(func_id), |elaborator| {
elaborator.elaborate_pattern_and_store_ids(
parameter_pattern,
parameter_type.clone(),
Expand Down Expand Up @@ -2119,6 +2124,34 @@ fn function_def_set_return_public(
Ok(Value::Unit)
}

// fn add_item(self, item: Quoted)
fn module_add_item(
interpreter: &mut Interpreter,
arguments: Vec<(Value, Location)>,
location: Location,
) -> IResult<Value> {
let (self_argument, item) = check_two_arguments(arguments, location)?;
let module_id = get_module(self_argument)?;
let module_data = interpreter.elaborator.get_module(module_id);

let parser = parser::top_level_items();
let top_level_statements = parse(item, parser, "a top-level item")?;

interpreter.elaborate_in_module(module_id, module_data.location.file, |elaborator| {
let mut generated_items = CollectedItems::default();

for top_level_statement in top_level_statements {
elaborator.add_item(top_level_statement, &mut generated_items, location);
}

if !generated_items.is_empty() {
elaborator.elaborate_items(generated_items);
}
});

Ok(Value::Unit)
}

// fn functions(self) -> [FunctionDefinition]
fn module_functions(
interpreter: &Interpreter,
Expand Down
4 changes: 2 additions & 2 deletions compiler/noirc_frontend/src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ use noirc_errors::Span;
pub use parser::path::path_no_turbofish;
pub use parser::traits::trait_bound;
pub use parser::{
block, expression, fresh_statement, lvalue, parse_program, parse_type, pattern,
top_level_items, visibility,
block, expression, fresh_statement, lvalue, module, parse_program, parse_type, pattern,
top_level_items, top_level_statement, visibility,
};

#[derive(Debug, Clone)]
Expand Down
4 changes: 2 additions & 2 deletions compiler/noirc_frontend/src/parser/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ fn program() -> impl NoirParser<ParsedModule> {

/// module: top_level_statement module
/// | %empty
fn module() -> impl NoirParser<ParsedModule> {
pub fn module() -> impl NoirParser<ParsedModule> {
recursive(|module_parser| {
empty()
.to(ParsedModule::default())
Expand All @@ -202,7 +202,7 @@ pub fn top_level_items() -> impl NoirParser<Vec<TopLevelStatement>> {
/// | module_declaration
/// | use_statement
/// | global_declaration
fn top_level_statement<'a>(
pub fn top_level_statement<'a>(
module_parser: impl NoirParser<ParsedModule> + 'a,
) -> impl NoirParser<TopLevelStatement> + 'a {
choice((
Expand Down
8 changes: 8 additions & 0 deletions docs/docs/noir/standard_library/meta/module.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ declarations in the source program.

## Methods

### add_item

#include_code add_item noir_stdlib/src/meta/module.nr rust

Adds a top-level item (a function, a struct, a global, etc.) to the module.
Adding multiple items in one go is also valid if the `Quoted` value has multiple items in it.
Note that the items are type-checked as if they are inside the module they are being added to.

### name

#include_code name noir_stdlib/src/meta/module.nr rust
Expand Down
5 changes: 5 additions & 0 deletions noir_stdlib/src/meta/module.nr
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
impl Module {
#[builtin(module_add_item)]
// docs:start:add_item
fn add_item(self, item: Quoted) {}
// docs:end:add_item

#[builtin(module_has_named_attribute)]
// docs:start:has_named_attribute
fn has_named_attribute(self, name: Quoted) -> bool {}
Expand Down
18 changes: 18 additions & 0 deletions test_programs/compile_success_empty/comptime_module/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,22 @@ fn outer_attribute_separate_module(m: Module) {
increment_counter();
}

struct Foo {}

#[add_function]
mod add_to_me {
fn add_to_me_function() {}
}

fn add_function(m: Module) {
m.add_item(
quote { pub fn added_function() -> super::Foo {
add_to_me_function();
super::Foo {}
} }
);
}

fn main() {
comptime
{
Expand Down Expand Up @@ -73,6 +89,8 @@ fn main() {

yet_another_module::generated_outer_function();
yet_another_module::generated_inner_function();

let _ = add_to_me::added_function();
}

// docs:start:as_module_example
Expand Down

0 comments on commit af50a7b

Please sign in to comment.