diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 873da5a0c5e..e60308aaddd 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -623,6 +623,21 @@ impl<'context> Elaborator<'context> { } } + pub fn resolve_module_by_path(&self, path: Path) -> Option { + let path_resolver = StandardPathResolver::new(self.module_id()); + + match path_resolver.resolve(self.def_maps, path.clone(), &mut None) { + Ok(PathResolution { module_def_id: ModuleDefId::ModuleId(module_id), error }) => { + if error.is_some() { + None + } else { + Some(module_id) + } + } + _ => None, + } + } + fn resolve_trait_by_path(&mut self, path: Path) -> Option { let path_resolver = StandardPathResolver::new(self.module_id()); @@ -870,7 +885,11 @@ impl<'context> Elaborator<'context> { /// Since they should be within a child module, they should be elaborated as if /// `in_contract` is `false` so we can still resolve them in the parent module without them being in a contract. fn in_contract(&self) -> bool { - self.module_id().module(self.def_maps).is_contract + self.module_is_contract(self.module_id()) + } + + pub(crate) fn module_is_contract(&self, module_id: ModuleId) -> bool { + module_id.module(self.def_maps).is_contract } fn is_entry_point_function(&self, func: &NoirFunction, in_contract: bool) -> bool { @@ -1069,6 +1088,11 @@ impl<'context> Elaborator<'context> { self.self_type = None; } + pub fn get_module(&self, module: ModuleId) -> &ModuleData { + let message = "A crate should always be present for a given crate id"; + &self.def_maps.get(&module.krate).expect(message).modules[module.local_id.0] + } + fn get_module_mut(def_maps: &mut DefMaps, module: ModuleId) -> &mut ModuleData { let message = "A crate should always be present for a given crate id"; &mut def_maps.get_mut(&module.krate).expect(message).modules[module.local_id.0] diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index 20527f8bea0..c12fb4d1113 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -6,8 +6,8 @@ use std::{ use acvm::{AcirField, FieldElement}; use builtin_helpers::{ check_argument_count, check_one_argument, check_three_arguments, check_two_arguments, - get_function_def, get_quoted, get_slice, get_trait_constraint, get_trait_def, get_type, - get_u32, hir_pattern_to_tokens, + get_function_def, get_module, get_quoted, get_slice, get_trait_constraint, get_trait_def, + get_type, get_u32, hir_pattern_to_tokens, }; use chumsky::Parser; use iter_extended::{try_vecmap, vecmap}; @@ -17,7 +17,7 @@ use rustc_hash::FxHashMap as HashMap; use crate::{ ast::IntegerBitSize, hir::comptime::{errors::IResult, value::add_token_spans, InterpreterError, Value}, - macros_api::{NodeInterner, Signedness}, + macros_api::{ModuleDefId, NodeInterner, Signedness}, parser, token::Token, QuotedType, Shared, Type, @@ -40,13 +40,18 @@ impl<'local, 'context> Interpreter<'local, 'context> { "array_len" => array_len(interner, arguments, location), "as_slice" => as_slice(interner, arguments, location), "is_unconstrained" => Ok(Value::Bool(true)), + "function_def_name" => function_def_name(interner, arguments, location), "function_def_parameters" => function_def_parameters(interner, arguments, location), "function_def_return_type" => function_def_return_type(interner, arguments, location), + "module_functions" => module_functions(self, arguments, location), + "module_is_contract" => module_is_contract(self, arguments, location), + "module_name" => module_name(interner, arguments, location), "modulus_be_bits" => modulus_be_bits(interner, arguments, location), "modulus_be_bytes" => modulus_be_bytes(interner, arguments, location), "modulus_le_bits" => modulus_le_bits(interner, arguments, location), "modulus_le_bytes" => modulus_le_bytes(interner, arguments, location), "modulus_num_bits" => modulus_num_bits(interner, arguments, location), + "quoted_as_module" => quoted_as_module(self, arguments, return_type, location), "quoted_as_trait_constraint" => quoted_as_trait_constraint(self, arguments, location), "quoted_as_type" => quoted_as_type(self, arguments, location), "quoted_eq" => quoted_eq(arguments, location), @@ -307,6 +312,29 @@ fn slice_insert( Ok(Value::Slice(values, typ)) } +// fn as_module(quoted: Quoted) -> Option +fn quoted_as_module( + interpreter: &mut Interpreter, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + let argument = check_one_argument(arguments, location)?; + + let tokens = get_quoted(argument, location)?; + let quoted = add_token_spans(tokens.clone(), location.span); + + let path = parser::path_no_turbofish().parse(quoted).ok(); + let option_value = path.and_then(|path| { + let module = interpreter.elaborate_item(interpreter.current_function, |elaborator| { + elaborator.resolve_module_by_path(path) + }); + module.map(Value::ModuleDefinition) + }); + + option(return_type, option_value) +} + // fn as_trait_constraint(quoted: Quoted) -> TraitConstraint fn quoted_as_trait_constraint( interpreter: &mut Interpreter, @@ -652,6 +680,19 @@ fn zeroed(return_type: Type) -> IResult { } } +// fn name(self) -> Quoted +fn function_def_name( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let func_id = get_function_def(self_argument, location)?; + let name = interner.function_name(&func_id).to_string(); + let tokens = Rc::new(vec![Token::Ident(name)]); + Ok(Value::Quoted(tokens)) +} + // fn parameters(self) -> [(Quoted, Type)] fn function_def_parameters( interner: &NodeInterner, @@ -693,6 +734,54 @@ fn function_def_return_type( Ok(Value::Type(func_meta.return_type().follow_bindings())) } +// fn functions(self) -> [FunctionDefinition] +fn module_functions( + interpreter: &Interpreter, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let module_id = get_module(self_argument, location)?; + let module_data = interpreter.elaborator.get_module(module_id); + let func_ids = module_data + .value_definitions() + .filter_map(|module_def_id| { + if let ModuleDefId::FunctionId(func_id) = module_def_id { + Some(Value::FunctionDefinition(func_id)) + } else { + None + } + }) + .collect(); + + let slice_type = Type::Slice(Box::new(Type::Quoted(QuotedType::FunctionDefinition))); + Ok(Value::Slice(func_ids, slice_type)) +} + +// fn is_contract(self) -> bool +fn module_is_contract( + interpreter: &Interpreter, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let module_id = get_module(self_argument, location)?; + Ok(Value::Bool(interpreter.elaborator.module_is_contract(module_id))) +} + +// fn name(self) -> Quoted +fn module_name( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let module_id = get_module(self_argument, location)?; + let name = &interner.module_attributes(&module_id).name; + let tokens = Rc::new(vec![Token::Ident(name.clone())]); + Ok(Value::Quoted(tokens)) +} + fn modulus_be_bits( _interner: &mut NodeInterner, arguments: Vec<(Value, Location)>, diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs index 45e4b13a47c..1c73f946587 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs @@ -5,7 +5,10 @@ use noirc_errors::Location; use crate::{ ast::{IntegerBitSize, Signedness}, - hir::comptime::{errors::IResult, InterpreterError, Value}, + hir::{ + comptime::{errors::IResult, InterpreterError, Value}, + def_map::ModuleId, + }, hir_def::stmt::HirPattern, macros_api::NodeInterner, node_interner::{FuncId, TraitId}, @@ -124,6 +127,17 @@ pub(crate) fn get_function_def(value: Value, location: Location) -> IResult IResult { + match value { + Value::ModuleDefinition(module_id) => Ok(module_id), + value => { + let expected = Type::Quoted(QuotedType::Module); + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { expected, actual, location }) + } + } +} + pub(crate) fn get_trait_constraint( value: Value, location: Location, diff --git a/compiler/noirc_frontend/src/parser/mod.rs b/compiler/noirc_frontend/src/parser/mod.rs index 677d741b5e0..cba1aa931ef 100644 --- a/compiler/noirc_frontend/src/parser/mod.rs +++ b/compiler/noirc_frontend/src/parser/mod.rs @@ -22,7 +22,9 @@ use chumsky::primitive::Container; pub use errors::ParserError; pub use errors::ParserErrorReason; use noirc_errors::Span; -pub use parser::{expression, parse_program, parse_type, top_level_items, trait_bound}; +pub use parser::path::path_no_turbofish; +pub use parser::traits::trait_bound; +pub use parser::{expression, parse_program, parse_type, top_level_items}; #[derive(Debug, Clone)] pub enum TopLevelStatement { diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index 9772814027f..4ccb158fad6 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -37,8 +37,8 @@ use super::{spanned, Item, ItemKind}; use crate::ast::{ BinaryOp, BinaryOpKind, BlockExpression, ForLoopStatement, ForRange, Ident, IfExpression, InfixExpression, LValue, Literal, ModuleDeclaration, NoirTypeAlias, Param, Path, Pattern, - Recoverable, Statement, TraitBound, TypeImpl, UnaryRhsMemberAccess, UnaryRhsMethodCall, - UseTree, UseTreeKind, Visibility, + Recoverable, Statement, TypeImpl, UnaryRhsMemberAccess, UnaryRhsMethodCall, UseTree, + UseTreeKind, Visibility, }; use crate::ast::{ Expression, ExpressionKind, LetStatement, StatementKind, UnresolvedType, UnresolvedTypeData, @@ -58,10 +58,10 @@ mod attributes; mod function; mod lambdas; mod literals; -mod path; +pub(super) mod path; mod primitives; mod structs; -mod traits; +pub(super) mod traits; mod types; // synthesized by LALRPOP @@ -71,7 +71,7 @@ lalrpop_mod!(pub noir_parser); mod test_helpers; use literals::literal; -use path::{maybe_empty_path, path, path_no_turbofish}; +use path::{maybe_empty_path, path}; use primitives::{dereference, ident, negation, not, nothing, right_shift_operator, token_kind}; use traits::where_clause; @@ -366,10 +366,6 @@ fn function_declaration_parameters() -> impl NoirParser impl NoirParser { - traits::trait_bound() -} - fn block_expr<'a>( statement: impl NoirParser + 'a, ) -> impl NoirParser + 'a { @@ -431,7 +427,7 @@ fn rename() -> impl NoirParser> { fn use_tree() -> impl NoirParser { recursive(|use_tree| { - let simple = path_no_turbofish().then(rename()).map(|(mut prefix, alias)| { + let simple = path::path_no_turbofish().then(rename()).map(|(mut prefix, alias)| { let ident = prefix.pop().ident; UseTree { prefix, kind: UseTreeKind::Path(ident, alias) } }); diff --git a/compiler/noirc_frontend/src/parser/parser/path.rs b/compiler/noirc_frontend/src/parser/parser/path.rs index 140650af1a2..4a5085db172 100644 --- a/compiler/noirc_frontend/src/parser/parser/path.rs +++ b/compiler/noirc_frontend/src/parser/parser/path.rs @@ -14,7 +14,7 @@ pub(super) fn path<'a>( path_inner(path_segment(type_parser)) } -pub(super) fn path_no_turbofish() -> impl NoirParser { +pub fn path_no_turbofish() -> impl NoirParser { path_inner(path_segment_no_turbofish()) } diff --git a/compiler/noirc_frontend/src/parser/parser/traits.rs b/compiler/noirc_frontend/src/parser/parser/traits.rs index ffcf7e07629..00c13f1e613 100644 --- a/compiler/noirc_frontend/src/parser/parser/traits.rs +++ b/compiler/noirc_frontend/src/parser/parser/traits.rs @@ -182,7 +182,7 @@ fn trait_bounds() -> impl NoirParser> { trait_bound().separated_by(just(Token::Plus)).at_least(1).allow_trailing() } -pub(super) fn trait_bound() -> impl NoirParser { +pub fn trait_bound() -> impl NoirParser { path_no_turbofish().then(generic_type_args(parse_type())).map(|(trait_path, trait_generics)| { TraitBound { trait_path, trait_generics, trait_id: None } }) diff --git a/noir_stdlib/src/meta/function_def.nr b/noir_stdlib/src/meta/function_def.nr index 284e8a0c9c0..fca6a3641fa 100644 --- a/noir_stdlib/src/meta/function_def.nr +++ b/noir_stdlib/src/meta/function_def.nr @@ -1,4 +1,7 @@ impl FunctionDefinition { + #[builtin(function_def_name)] + fn name(self) -> Quoted {} + #[builtin(function_def_parameters)] fn parameters(self) -> [(Quoted, Type)] {} diff --git a/noir_stdlib/src/meta/mod.nr b/noir_stdlib/src/meta/mod.nr index 615b4e5aa14..e11ddcc9baa 100644 --- a/noir_stdlib/src/meta/mod.nr +++ b/noir_stdlib/src/meta/mod.nr @@ -3,6 +3,7 @@ use crate::hash::BuildHasherDefault; use crate::hash::poseidon2::Poseidon2Hasher; mod function_def; +mod module; mod struct_def; mod trait_constraint; mod trait_def; diff --git a/noir_stdlib/src/meta/module.nr b/noir_stdlib/src/meta/module.nr new file mode 100644 index 00000000000..ee00f360806 --- /dev/null +++ b/noir_stdlib/src/meta/module.nr @@ -0,0 +1,10 @@ +impl Module { + #[builtin(module_is_contract)] + fn is_contract(self) -> bool {} + + #[builtin(module_functions)] + fn functions(self) -> [FunctionDefinition] {} + + #[builtin(module_name)] + fn name(self) -> Quoted {} +} diff --git a/noir_stdlib/src/meta/quoted.nr b/noir_stdlib/src/meta/quoted.nr index 0fd00b8bf82..9c8de23258c 100644 --- a/noir_stdlib/src/meta/quoted.nr +++ b/noir_stdlib/src/meta/quoted.nr @@ -1,6 +1,10 @@ use crate::cmp::Eq; +use crate::option::Option; impl Quoted { + #[builtin(quoted_as_module)] + fn as_module(self) -> Option {} + #[builtin(quoted_as_trait_constraint)] fn as_trait_constraint(self) -> TraitConstraint {} diff --git a/test_programs/compile_success_empty/comptime_function_definition/src/main.nr b/test_programs/compile_success_empty/comptime_function_definition/src/main.nr index b0eeca2bb7f..9297bdb4dfb 100644 --- a/test_programs/compile_success_empty/comptime_function_definition/src/main.nr +++ b/test_programs/compile_success_empty/comptime_function_definition/src/main.nr @@ -28,6 +28,9 @@ comptime fn function_attr(f: FunctionDefinition) { // Check FunctionDefinition::return_type assert_eq(f.return_type(), type_of(an_i32)); + + // Check FunctionDefinition::name + assert_eq(f.name(), quote { foo }); } fn main() {} diff --git a/test_programs/compile_success_empty/comptime_module/Nargo.toml b/test_programs/compile_success_empty/comptime_module/Nargo.toml new file mode 100644 index 00000000000..cb295ca1e17 --- /dev/null +++ b/test_programs/compile_success_empty/comptime_module/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_module" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_success_empty/comptime_module/src/main.nr b/test_programs/compile_success_empty/comptime_module/src/main.nr new file mode 100644 index 00000000000..e9c9817cfd8 --- /dev/null +++ b/test_programs/compile_success_empty/comptime_module/src/main.nr @@ -0,0 +1,26 @@ +mod foo { + fn x() {} + fn y() {} +} + +contract bar {} + +fn main() { + comptime + { + // Check Module::is_contract + let foo = quote { foo }.as_module().unwrap(); + assert(!foo.is_contract()); + + let bar = quote { bar }.as_module().unwrap(); + assert(bar.is_contract()); + + // Check Module::functions + assert_eq(foo.functions().len(), 2); + assert_eq(bar.functions().len(), 0); + + // Check Module::name + assert_eq(foo.name(), quote { foo }); + assert_eq(bar.name(), quote { bar }); + } +}