diff --git a/.noir-sync-commit b/.noir-sync-commit index e49b4b4cf18..4b13f99256c 100644 --- a/.noir-sync-commit +++ b/.noir-sync-commit @@ -1 +1 @@ -1c84038e4a1b2515f4f91aca4c833dd3b6c05d91 +de0f8eeeeaa2f35b6d1e3b7f77cd3e4a1b2db9ba diff --git a/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs b/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs index 43984e4a922..f700fefe0cd 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs @@ -153,9 +153,28 @@ pub struct ResolvedOpcodeLocation { /// map opcodes to debug information related to their context. pub enum OpcodeLocation { Acir(usize), + // TODO(https://github.com/noir-lang/noir/issues/5792): We can not get rid of this enum field entirely just yet as this format is still + // used for resolving assert messages which is a breaking serialization change. Brillig { acir_index: usize, brillig_index: usize }, } +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +pub struct BrilligOpcodeLocation(pub usize); + +impl OpcodeLocation { + // Utility method to allow easily comparing a resolved Brillig location and a debug Brillig location. + // This method is useful when fetching Brillig debug locations as this does not need an ACIR index, + // and just need the Brillig index. + pub fn to_brillig_location(self) -> Option { + match self { + OpcodeLocation::Brillig { brillig_index, .. } => { + Some(BrilligOpcodeLocation(brillig_index)) + } + _ => None, + } + } +} + impl std::fmt::Display for OpcodeLocation { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -204,6 +223,13 @@ impl FromStr for OpcodeLocation { } } +impl std::fmt::Display for BrilligOpcodeLocation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let index = self.0; + write!(f, "{index}") + } +} + impl Circuit { pub fn num_vars(&self) -> u32 { self.current_witness_index + 1 diff --git a/noir/noir-repo/compiler/noirc_errors/src/debug_info.rs b/noir/noir-repo/compiler/noirc_errors/src/debug_info.rs index 1a254175c0a..b480d20fde4 100644 --- a/noir/noir-repo/compiler/noirc_errors/src/debug_info.rs +++ b/noir/noir-repo/compiler/noirc_errors/src/debug_info.rs @@ -1,4 +1,5 @@ use acvm::acir::circuit::brillig::BrilligFunctionId; +use acvm::acir::circuit::BrilligOpcodeLocation; use acvm::acir::circuit::OpcodeLocation; use acvm::compiler::AcirTransformationMap; @@ -98,8 +99,8 @@ pub struct DebugInfo { /// that they should be serialized to/from strings. #[serde_as(as = "BTreeMap")] pub locations: BTreeMap>, - #[serde_as(as = "BTreeMap<_, BTreeMap>")] - pub brillig_locations: BTreeMap>>, + pub brillig_locations: + BTreeMap>>, pub variables: DebugVariables, pub functions: DebugFunctions, pub types: DebugTypes, @@ -116,7 +117,10 @@ pub struct OpCodesCount { impl DebugInfo { pub fn new( locations: BTreeMap>, - brillig_locations: BTreeMap>>, + brillig_locations: BTreeMap< + BrilligFunctionId, + BTreeMap>, + >, variables: DebugVariables, functions: DebugFunctions, types: DebugTypes, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs index 0cae7d77448..c2cea6e6a00 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs @@ -1933,6 +1933,9 @@ impl AcirContext { } Some(optional_value) => { let mut values = Vec::new(); + if let AcirValue::DynamicArray(_) = optional_value { + unreachable!("Dynamic array should already be initialized"); + } self.initialize_array_inner(&mut values, optional_value)?; values } @@ -1962,8 +1965,16 @@ impl AcirContext { self.initialize_array_inner(witnesses, value)?; } } - AcirValue::DynamicArray(_) => { - unreachable!("Dynamic array should already be initialized"); + AcirValue::DynamicArray(AcirDynamicArray { block_id, len, .. }) => { + let dynamic_array_values = try_vecmap(0..len, |i| { + let index_var = self.add_constant(i); + + let read = self.read_from_memory(block_id, &index_var)?; + Ok::(AcirValue::Var(read, AcirType::field())) + })?; + for value in dynamic_array_values { + self.initialize_array_inner(witnesses, value)?; + } } } Ok(()) diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs index 661371c5de6..004979ce84e 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs @@ -1,6 +1,6 @@ //! `GeneratedAcir` is constructed as part of the `acir_gen` pass to accumulate all of the ACIR //! program as it is being converted from SSA form. -use std::collections::BTreeMap; +use std::{collections::BTreeMap, u32}; use crate::{ brillig::{brillig_gen::brillig_directive, brillig_ir::artifact::GeneratedBrillig}, @@ -11,7 +11,7 @@ use acvm::acir::{ circuit::{ brillig::{BrilligFunctionId, BrilligInputs, BrilligOutputs}, opcodes::{BlackBoxFuncCall, FunctionInput, Opcode as AcirOpcode}, - AssertionPayload, OpcodeLocation, + AssertionPayload, BrilligOpcodeLocation, OpcodeLocation, }, native_types::Witness, BlackBoxFunc, @@ -53,7 +53,7 @@ pub(crate) struct GeneratedAcir { /// Brillig function id -> Opcodes locations map /// This map is used to prevent redundant locations being stored for the same Brillig entry point. - pub(crate) brillig_locations: BTreeMap, + pub(crate) brillig_locations: BTreeMap, /// Source code location of the current instruction being processed /// None if we do not know the location @@ -77,6 +77,8 @@ pub(crate) struct GeneratedAcir { /// Correspondence between an opcode index (in opcodes) and the source code call stack which generated it pub(crate) type OpcodeToLocationsMap = BTreeMap; +pub(crate) type BrilligOpcodeToLocationsMap = BTreeMap; + #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub(crate) enum BrilligStdlibFunc { Inverse, @@ -590,6 +592,7 @@ impl GeneratedAcir { return; } + // TODO(https://github.com/noir-lang/noir/issues/5792) for (brillig_index, message) in generated_brillig.assert_messages.iter() { self.assertion_payloads.insert( OpcodeLocation::Brillig { @@ -605,13 +608,10 @@ impl GeneratedAcir { } for (brillig_index, call_stack) in generated_brillig.locations.iter() { - self.brillig_locations.entry(brillig_function_index).or_default().insert( - OpcodeLocation::Brillig { - acir_index: self.opcodes.len() - 1, - brillig_index: *brillig_index, - }, - call_stack.clone(), - ); + self.brillig_locations + .entry(brillig_function_index) + .or_default() + .insert(BrilligOpcodeLocation(*brillig_index), call_stack.clone()); } } @@ -625,6 +625,7 @@ impl GeneratedAcir { OpcodeLocation::Acir(index) => index, _ => panic!("should not have brillig index"), }; + match &mut self.opcodes[acir_index] { AcirOpcode::BrilligCall { id, .. } => *id = brillig_function_index, _ => panic!("expected brillig call opcode"), diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs index 761da6c361d..b820e4664e3 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs @@ -374,9 +374,9 @@ fn resolve_external_dep( resolve_path_to_ns(&dep_directive, dep_module.krate, importing_crate, def_maps, path_references) } -// Issue an error if the given private function is being called from a non-child module, or -// if the given pub(crate) function is being called from another crate -fn can_reference_module_id( +// Returns false if the given private function is being called from a non-child module, or +// if the given pub(crate) function is being called from another crate. Otherwise returns true. +pub fn can_reference_module_id( def_maps: &BTreeMap, importing_crate: CrateId, current_module: LocalModuleId, diff --git a/noir/noir-repo/cspell.json b/noir/noir-repo/cspell.json index f285489530e..5b4977c2106 100644 --- a/noir/noir-repo/cspell.json +++ b/noir/noir-repo/cspell.json @@ -119,6 +119,7 @@ "keccakf", "krate", "libc", + "Linea", "losslessly", "lvalue", "Maddiaa", @@ -126,6 +127,7 @@ "memfs", "memset", "merkle", + "metaprogramming", "metas", "microcontroller", "minreq", @@ -166,6 +168,7 @@ "pseudocode", "pubkey", "quantile", + "quasiquote", "rangemap", "repr", "reqwest", @@ -204,6 +207,7 @@ "typevar", "typevars", "udiv", + "umap", "underconstrained", "uninstantiated", "unnormalized", diff --git a/noir/noir-repo/docs/docs/noir/concepts/comptime.md b/noir/noir-repo/docs/docs/noir/concepts/comptime.md new file mode 100644 index 00000000000..13bfbb13522 --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/concepts/comptime.md @@ -0,0 +1,261 @@ +--- +title: Compile-time Code & Metaprogramming +description: Learn how to use metaprogramming in Noir to create macros or derive your own traits +keywords: [Noir, comptime, compile-time, metaprogramming, macros, quote, unquote] +sidebar_position: 15 +--- + +# Overview + +Metaprogramming in Noir is comprised of three parts: +1. `comptime` code +2. Quoting and unquoting +3. The metaprogramming API in `std::meta` + +Each of these are explained in more detail in the next sections but the wide picture is that +`comptime` allows us to write code which runs at compile-time. In this `comptime` code we +can quote and unquote snippets of the program, manipulate them, and insert them in other +parts of the program. Comptime functions which do this are said to be macros. Additionally, +there's a compile-time API of built-in types and functions provided by the compiler which allows +for greater analysis and modification of programs. + +--- + +# Comptime + +`comptime` is a new keyword in Noir which marks an item as executing or existing at compile-time. It can be used in several ways: + +- `comptime fn` to define functions which execute exclusively during compile-time. +- `comptime global` to define a global variable which is evaluated at compile-time. + - Unlike runtime globals, `comptime global`s can be mutable. +- `comptime { ... }` to execute a block of statements during compile-time. +- `comptime let` to define a variable whose value is evaluated at compile-time. +- `comptime for` to run a for loop at compile-time. Syntax sugar for `comptime { for .. }`. + +## Scoping + +Note that while in a `comptime` context, any runtime variables _local to the current function_ are never visible. + +## Evaluating + +Evaluation rules of `comptime` follows the normal unconstrained evaluation rules for other Noir code. There are a few things to note though: + +- Certain built-in functions may not be available, although more may be added over time. +- Evaluation order of global items is currently unspecified. For example, given the following two functions we can't guarantee +which `println` will execute first. The ordering of the two printouts will be arbitrary, but should be stable across multiple compilations with the same `nargo` version as long as the program is also unchanged. + +```rust +fn one() { + comptime { println("one"); } +} + +fn two() { + comptime { println("two"); } +} +``` + +- Since evaluation order is unspecified, care should be taken when using mutable globals so that they do not rely on a particular ordering. +For example, using globals to generate unique ids should be fine but relying on certain ids always being produced (especially after edits to the program) should be avoided. +- Although most ordering of globals is unspecified, two are: + - Dependencies of a crate will always be evaluated before the dependent crate. + - Any annotations on a function will be run before the function itself is resolved. This is to allow the annotation to modify the function if necessary. Note that if the + function itself was called at compile-time previously, it will already be resolved and cannot be modified. To prevent accidentally calling functions you wish to modify + at compile-time, it may be helpful to sort your `comptime` annotation functions into a different crate along with any dependencies they require. + +## Lowering + +When a `comptime` value is used in runtime code it must be lowered into a runtime value. This means replacing the expression with the literal that it evaluated to. For example, the code: + +```rust +struct Foo { array: [Field; 2], len: u32 } + +fn main() { + println(comptime { + let mut foo = std::mem::zeroed::(); + foo.array[0] = 4; + foo.len = 1; + foo + }); +} +``` + +will be converted to the following after `comptime` expressions are evaluated: + +```rust +struct Foo { array: [Field; 2], len: u32 } + +fn main() { + println(Foo { array: [4, 0], len: 1 }); +} +``` + +Not all types of values can be lowered. For example, `Type`s and `TypeDefinition`s (among other types) cannot be lowered at all. + +```rust +fn main() { + // There's nothing we could inline here to create a Type value at runtime + // let _ = get_type!(); +} + +comptime fn get_type() -> Type { ... } +``` + +--- + +# (Quasi) Quote + +Macros in Noir are `comptime` functions which return code as a value which is inserted into the call site when it is lowered there. +A code value in this case is of type `Quoted` and can be created by a `quote { ... }` expression. +More specifically, the code value `quote` creates is a token stream - a representation of source code as a series of words, numbers, string literals, or operators. +For example, the expression `quote { Hi "there reader"! }` would quote three tokens: the word "hi", the string "there reader", and an exclamation mark. +You'll note that snippets that would otherwise be invalid syntax can still be quoted. + +When a `Quoted` value is used in runtime code, it is lowered into a `quote { ... }` expression. Since this expression is only valid +in compile-time code however, we'd get an error if we tried this. Instead, we can use macro insertion to insert each token into the +program at that point, and parse it as an expression. To do this, we have to add a `!` after the function name returning the `Quoted` value. +If the value was created locally and there is no function returning it, `std::meta::unquote!(_)` can be used instead. +Calling such a function at compile-time without `!` will just return the `Quoted` value to be further manipulated. For example: + +#include_code quote-example noir_stdlib/src/meta/mod.nr rust + +For those familiar with quoting from other languages (primarily lisps), Noir's `quote` is actually a _quasiquote_. +This means we can escape the quoting by using the unquote operator to splice values in the middle of quoted code. + +# Unquote + +The unquote operator `$` is usable within a `quote` expression. +It takes a variable as an argument, evaluates the variable, and splices the resulting value into the quoted token stream at that point. For example, + +```rust +comptime { + let x = 1 + 2; + let y = quote { $x + 4 }; +} +``` + +The value of `y` above will be the token stream containing `3`, `+`, and `4`. We can also use this to combine `Quoted` values into larger token streams: + +```rust +comptime { + let x = quote { 1 + 2 }; + let y = quote { $x + 4 }; +} +``` + +The value of `y` above is now the token stream containing five tokens: `1 + 2 + 4`. + +Note that to unquote something, a variable name _must_ follow the `$` operator in a token stream. +If it is an expression (even a parenthesized one), it will do nothing. Most likely a parse error will be given when the macro is later unquoted. + +Unquoting can also be avoided by escaping the `$` with a backslash: + +``` +comptime { + let x = quote { 1 + 2 }; + + // y contains the four tokens: `$x + 4` + let y = quote { \$x + 4 }; +} +``` + +--- + +# Annotations + +Annotations provide a way to run a `comptime` function on an item in the program. +When you use an annotation, the function with the same name will be called with that item as an argument: + +```rust +#[my_struct_annotation] +struct Foo {} + +comptime fn my_struct_annotation(s: StructDefinition) { + println("Called my_struct_annotation!"); +} + +#[my_function_annotation] +fn foo() {} + +comptime fn my_function_annotation(f: FunctionDefinition) { + println("Called my_function_annotation!"); +} +``` + +Anything returned from one of these functions will be inserted at top-level along with the original item. +Note that expressions are not valid at top-level so you'll get an error trying to return `3` or similar just as if you tried to write a program containing `3; struct Foo {}`. +You can insert other top-level items such as traits, structs, or functions this way though. +For example, this is the mechanism used to insert additional trait implementations into the program when deriving a trait impl from a struct: + +#include_code derive-field-count-example noir_stdlib/src/meta/mod.nr rust + +## Calling annotations with additional arguments + +Arguments may optionally be given to annotations. +When this is done, these additional arguments are passed to the annotation function after the item argument. + +#include_code annotation-arguments-example noir_stdlib/src/meta/mod.nr rust + +We can also take any number of arguments by adding the `varargs` annotation: + +#include_code annotation-varargs-example noir_stdlib/src/meta/mod.nr rust + +--- + +# Comptime API + +Although `comptime`, `quote`, and unquoting provide a flexible base for writing macros, +Noir's true metaprogramming ability comes from being able to interact with the compiler through a compile-time API. +This API can be accessed through built-in functions in `std::meta` as well as on methods of several `comptime` types. + +The following is an incomplete list of some `comptime` types along with some useful methods on them. + +- `Quoted`: A token stream +- `Type`: The type of a Noir type + - `fn implements(self, constraint: TraitConstraint) -> bool` + - Returns true if `self` implements the given trait constraint +- `Expr`: A syntactically valid expression. Can be used to recur on a program's parse tree to inspect how it is structured. + - Methods: + - `fn as_function_call(self) -> Option<(Expr, [Expr])>` + - If this is a function call expression, return `(function, arguments)` + - `fn as_block(self) -> Option<[Expr]>` + - If this is a block, return each statement in the block +- `FunctionDefinition`: A function definition + - Methods: + - `fn parameters(self) -> [(Quoted, Type)]` + - Returns a slice of `(name, type)` pairs for each parameter +- `StructDefinition`: A struct definition + - Methods: + - `fn as_type(self) -> Type` + - Returns this `StructDefinition` as a `Type`. Any generics are kept as-is + - `fn generics(self) -> [Quoted]` + - Return the name of each generic on this struct + - `fn fields(self) -> [(Quoted, Type)]` + - Return the name and type of each field +- `TraitConstraint`: A trait constraint such as `From` + +There are many more functions available by exploring the `std::meta` module and its submodules. +Using these methods is the key to writing powerful metaprogramming libraries. + +--- + +# Example: Derive + +Using all of the above, we can write a `derive` macro that behaves similarly to Rust's but is not built into the language. +From the user's perspective it will look like this: + +```rust +// Example usage +#[derive(Default, Eq, Cmp)] +struct MyStruct { my_field: u32 } +``` + +To implement `derive` we'll have to create a `comptime` function that accepts +a variable amount of traits. + +#include_code derive_example noir_stdlib/src/meta/mod.nr rust + +Registering a derive function could be done as follows: + +#include_code derive_via noir_stdlib/src/meta/mod.nr rust + +#include_code big-derive-usage-example noir_stdlib/src/meta/mod.nr rust diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/expr.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/expr.md new file mode 100644 index 00000000000..bcc9756c06c --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/expr.md @@ -0,0 +1,149 @@ +--- +title: Expr +--- + +`std::meta::expr` contains methods on the built-in `Expr` type for quoted, syntactically valid expressions. + +## Methods + +### as_array + +#include_code as_array noir_stdlib/src/meta/expr.nr rust + +If this expression is an array, this returns a slice of each element in the array. + +### as_integer + +#include_code as_integer noir_stdlib/src/meta/expr.nr rust + +If this element is an integer literal, return the integer as a field +as well as whether the integer is negative (true) or not (false). + +### as_binary_op + +#include_code as_binary_op noir_stdlib/src/meta/expr.nr rust + +If this expression is a binary operator operation ` `, +return the left-hand side, operator, and the right-hand side of the operation. + +### as_block + +#include_code as_block noir_stdlib/src/meta/expr.nr rust + +If this expression is a block `{ stmt1; stmt2; ...; stmtN }`, return +a slice containing each statement. + +### as_bool + +#include_code as_bool noir_stdlib/src/meta/expr.nr rust + +If this expression is a boolean literal, return that literal. + +### 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_function_call + +#include_code as_function_call noir_stdlib/src/meta/expr.nr rust + +If this expression is a function call `foo(arg1, ..., argN)`, return +the function and a slice of each argument. + +### as_if + +#include_code as_if noir_stdlib/src/meta/expr.nr rust + +If this expression is an `if condition { then_branch } else { else_branch }`, +return the condition, then branch, and else branch. If there is no else branch, +`None` is returned for that branch instead. + +### as_index + +#include_code as_index noir_stdlib/src/meta/expr.nr rust + +If this expression is an index into an array `array[index]`, return the +array and the index. + +### as_member_access + +#include_code as_member_access noir_stdlib/src/meta/expr.nr rust + +If this expression is a member access `foo.bar`, return the struct/tuple +expression and the field. The field will be represented as a quoted value. + +### as_repeated_element_array + +#include_code as_repeated_element_array noir_stdlib/src/meta/expr.nr rust + +If this expression is a repeated element array `[elem; length]`, return +the repeated element and the length expressions. + +### as_repeated_element_slice + +#include_code as_repeated_element_slice noir_stdlib/src/meta/expr.nr rust + +If this expression is a repeated element slice `[elem; length]`, return +the repeated element and the length expressions. + +### as_slice + +#include_code as_slice noir_stdlib/src/meta/expr.nr rust + +If this expression is a slice literal `&[elem1, ..., elemN]`, +return each element of the slice. + +### as_tuple + +#include_code as_tuple noir_stdlib/src/meta/expr.nr rust + +If this expression is a tuple `(field1, ..., fieldN)`, +return each element of the tuple. + +### as_unary_op + +#include_code as_unary_op noir_stdlib/src/meta/expr.nr rust + +If this expression is a unary operation ` `, +return the unary operator as well as the right-hand side expression. + +### as_unsafe + +#include_code as_unsafe noir_stdlib/src/meta/expr.nr rust + +If this expression is an `unsafe { stmt1; ...; stmtN }` block, +return each statement inside in a slice. + +### has_semicolon + +#include_code has_semicolon noir_stdlib/src/meta/expr.nr rust + +`true` if this expression is trailed by a semicolon. E.g. + +``` +comptime { + let expr1 = quote { 1 + 2 }.as_expr().unwrap(); + let expr2 = quote { 1 + 2; }.as_expr().unwrap(); + + assert(expr1.as_binary_op().is_some()); + assert(expr2.as_binary_op().is_some()); + + assert(!expr1.has_semicolon()); + assert(expr2.has_semicolon()); +} +``` + +### is_break + +#include_code is_break noir_stdlib/src/meta/expr.nr rust + +`true` if this expression is `break`. + +### is_continue + +#include_code is_continue noir_stdlib/src/meta/expr.nr rust + +`true` if this expression is `continue`. diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/function_def.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/function_def.md new file mode 100644 index 00000000000..4b359a9d343 --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/function_def.md @@ -0,0 +1,55 @@ +--- +title: FunctionDefinition +--- + +`std::meta::function_def` contains methods on the built-in `FunctionDefinition` type representing +a function definition in the source program. + +## Methods + +### name + +#include_code name noir_stdlib/src/meta/function_def.nr rust + +Returns the name of the function. + +### parameters + +#include_code parameters noir_stdlib/src/meta/function_def.nr rust + +Returns each parameter of the function as a tuple of (parameter pattern, parameter type). + +### return_type + +#include_code return_type noir_stdlib/src/meta/function_def.nr rust + +The return type of the function. + +### set_body + +#include_code set_body noir_stdlib/src/meta/function_def.nr rust + +Mutate the function body to a new expression. This is only valid +on functions in the current crate which have not yet been resolved. +This means any functions called at compile-time are invalid targets for this method. + +Requires the new body to be a valid expression. + +### set_parameters + +#include_code set_parameters noir_stdlib/src/meta/function_def.nr rust + +Mutates the function's parameters to a new set of parameters. This is only valid +on functions in the current crate which have not yet been resolved. +This means any functions called at compile-time are invalid targets for this method. + +Expects a slice of (parameter pattern, parameter type) for each parameter. Requires +each parameter pattern to be a syntactically valid parameter. + +### set_return_type + +#include_code set_return_type noir_stdlib/src/meta/function_def.nr rust + +Mutates the function's return type to a new type. This is only valid +on functions in the current crate which have not yet been resolved. +This means any functions called at compile-time are invalid targets for this method. diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/index.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/index.md new file mode 100644 index 00000000000..db0e5d0e411 --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/index.md @@ -0,0 +1,141 @@ +--- +title: Metaprogramming +description: Noir's Metaprogramming API +keywords: [metaprogramming, comptime, macros, macro, quote, unquote] +--- + +`std::meta` is the entry point for Noir's metaprogramming API. This consists of `comptime` functions +and types used for inspecting and modifying Noir programs. + +## Functions + +### type_of + +#include_code type_of noir_stdlib/src/meta/mod.nr rust + +Returns the type of a variable at compile-time. + +Example: +```rust +comptime { + let x: i32 = 1; + let x_type: Type = std::meta::type_of(x); + + assert_eq(x_type, quote { i32 }.as_type()); +} +``` + +### unquote + +#include_code unquote noir_stdlib/src/meta/mod.nr rust + +Unquotes the passed-in token stream where this function was called. + +Example: +```rust +comptime { + let code = quote { 1 + 2 }; + + // let x = 1 + 2; + let x = unquote!(code); +} +``` + +### derive + +#include_code derive noir_stdlib/src/meta/mod.nr rust + +Attribute placed on struct definitions. + +Creates a trait impl for each trait passed in as an argument. +To do this, the trait must have a derive handler registered +with `derive_via` beforehand. The traits in the stdlib that +can be derived this way are `Eq`, `Ord`, `Default`, and `Hash`. + +Example: +```rust +#[derive(Eq, Default)] +struct Foo { + x: i32, + y: T, +} + +fn main() { + let foo1 = Foo::default(); + let foo2 = Foo { x: 0, y: &[0] }; + assert_eq(foo1, foo2); +} +``` + +### derive_via + +#include_code derive_via_signature noir_stdlib/src/meta/mod.nr rust + +Attribute placed on trait definitions. + +Registers a function to create impls for the given trait +when the trait is used in a `derive` call. Users may use +this to register their own functions to enable their traits +to be derived by `derive`. + +Because this function requires a function as an argument which +should produce a trait impl for any given struct, users may find +it helpful to use a function like `std::meta::make_trait_impl` to +help creating these impls. + +Example: +```rust +#[derive_via(derive_do_nothing)] +trait DoNothing { + fn do_nothing(self); +} + +comptime fn derive_do_nothing(s: StructDefinition) -> Quoted { + let typ = s.as_type(); + quote { + impl DoNothing for $typ { + fn do_nothing(self) { + println("Nothing"); + } + } + } +} +``` + +As another example, `derive_eq` in the stdlib is used to derive the `Eq` +trait for any struct. It makes use of `make_trait_impl` to do this: + +#include_code derive_eq noir_stdlib/src/cmp.nr rust + +### make_trait_impl + +#include_code make_trait_impl noir_stdlib/src/meta/mod.nr rust + +A helper function to more easily create trait impls while deriving traits. + +Note that this function only works for traits which: +1. Have only one method +2. Have no generics on the trait itself. + - E.g. Using this on a trait such as `trait Foo { ... }` will result in the + generated impl incorrectly missing the `T` generic. + +If your trait fits these criteria then `make_trait_impl` is likely the easiest +way to write your derive handler. The arguments are as follows: + +- `s`: The struct to make the impl for +- `trait_name`: The name of the trait to derive. E.g. `quote { Eq }`. +- `function_signature`: The signature of the trait method to derive. E.g. `fn eq(self, other: Self) -> bool`. +- `for_each_field`: An operation to be performed on each field. E.g. `|name| quote { (self.$name == other.$name) }`. +- `join_fields_with`: A separator to join each result of `for_each_field` with. + E.g. `quote { & }`. You can also use an empty `quote {}` for no separator. +- `body`: The result of the field operations are passed into this function for any final processing. + This is the place to insert any setup/teardown code the trait requires. If the trait doesn't require + any such code, you can return the body as-is: `|body| body`. + +Example deriving `Hash`: + +#include_code derive_hash noir_stdlib/src/hash/mod.nr rust + +Example deriving `Ord`: + +#include_code derive_ord noir_stdlib/src/cmp.nr rust diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/module.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/module.md new file mode 100644 index 00000000000..d283f2da8b2 --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/module.md @@ -0,0 +1,27 @@ +--- +title: Module +--- + +`std::meta::module` contains methods on the built-in `Module` type which represents a module in the source program. +Note that this type represents a module generally, it isn't limited to only `mod my_submodule { ... }` +declarations in the source program. + +## Methods + +### name + +#include_code name noir_stdlib/src/meta/module.nr rust + +Returns the name of the module. + +### functions + +#include_code functions noir_stdlib/src/meta/module.nr rust + +Returns each function in the module. + +### is_contract + +#include_code is_contract noir_stdlib/src/meta/module.nr rust + +`true` if this module is a contract module (was declared via `contract foo { ... }`). diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/op.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/op.md new file mode 100644 index 00000000000..37d4cb746ac --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/op.md @@ -0,0 +1,134 @@ +--- +title: UnaryOp and BinaryOp +--- + +`std::meta::op` contains the `UnaryOp` and `BinaryOp` types as well as methods on them. +These types are used to represent a unary or binary operator respectively in Noir source code. + +## Types + +### UnaryOp + +Represents a unary operator. One of `-`, `!`, `&mut`, or `*`. + +### Methods + +#### is_minus + +#include_code is_minus noir_stdlib/src/meta/op.nr rust + +Returns `true` if this operator is `-`. + +#### is_not + +#include_code is_not noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `!` + +#### is_mutable_reference + +#include_code is_mutable_reference noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `&mut` + +#### is_dereference + +#include_code is_dereference noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `*` + +### BinaryOp + +Represents a binary operator. One of `+`, `-`, `*`, `/`, `%`, `==`, `!=`, `<`, `<=`, `>`, `>=`, `&`, `|`, `^`, `>>`, or `<<`. + +### Methods + +#### is_add + +#include_code is_add noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `+` + +#### is_subtract + +#include_code is_subtract noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `-` + +#### is_multiply + +#include_code is_multiply noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `*` + +#### is_divide + +#include_code is_divide noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `/` + +#### is_modulo + +#include_code is_modulo noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `%` + +#### is_equal + +#include_code is_equal noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `==` + +#### is_not_equal + +#include_code is_not_equal noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `!=` + +#### is_less_than + +#include_code is_less_than noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `<` + +#### is_less_than_or_equal + +#include_code is_less_than_or_equal noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `<=` + +#### is_greater_than + +#include_code is_greater_than noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `>` + +#### is_greater_than_or_equal + +#include_code is_greater_than_or_equal noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `>=` + +#### is_and + +#include_code is_and noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `&` + +#### is_or + +#include_code is_or noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `|` + +#### is_shift_right + +#include_code is_shift_right noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `>>` + +#### is_shift_left + +#include_code is_shift_right noir_stdlib/src/meta/op.nr rust + +`true` if this operator is `<<` diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/quoted.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/quoted.md new file mode 100644 index 00000000000..13d27ce8344 --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/quoted.md @@ -0,0 +1,57 @@ +--- +title: Quoted +--- + +`std::meta::quoted` contains methods on the built-in `Quoted` type which represents +quoted token streams and is the result of the `quote { ... }` expression. + +## Methods + +### as_expr + +#include_code as_expr noir_stdlib/src/meta/quoted.nr rust + +Parses the quoted token stream as an expression. Returns `Option::none()` if +the expression failed to parse. + +Example: + +#include_code as_expr_example noir_stdlib/src/meta/quoted.nr rust + +### as_module + +#include_code as_module noir_stdlib/src/meta/quoted.nr rust + +Interprets this token stream as a module path leading to the name of a module. +Returns `Option::none()` if the module isn't found or this token stream cannot be parsed as a path. + +Example: + +#include_code as_module_example test_programs/compile_success_empty/comptime_module/src/main.nr rust + +### as_trait_constraint + +#include_code as_trait_constraint noir_stdlib/src/meta/quoted.nr rust + +Interprets this token stream as a trait constraint (without an object type). +Note that this function panics instead of returning `Option::none()` if the token +stream does not parse and resolve to a valid trait constraint. + +Example: + +#include_code implements_example noir_stdlib/src/meta/quoted.nr rust + +### as_type + +#include_code as_type noir_stdlib/src/meta/quoted.nr rust + +Interprets this token stream as a resolved type. Panics if the token +stream doesn't parse to a type or if the type isn't a valid type in scope. + +#include_code implements_example noir_stdlib/src/meta/quoted.nr rust + +## Trait Implementations + +```rust +impl Eq for Quoted +``` diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/struct_def.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/struct_def.md new file mode 100644 index 00000000000..ab3ea4e0698 --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/struct_def.md @@ -0,0 +1,45 @@ +--- +title: StructDefinition +--- + +`std::meta::struct_def` contains methods on the built-in `StructDefinition` type. +This type corresponds to `struct Name { field1: Type1, ... }` items in the source program. + +## Methods + +### as_type + +#include_code as_type noir_stdlib/src/meta/struct_def.nr rust + +Returns this struct as a type in the source program. If this struct has +any generics, the generics are also included as-is. + +### generics + +#include_code generics noir_stdlib/src/meta/struct_def.nr rust + +Returns each generic on this struct. + +Example: + +``` +#[example] +struct Foo { + bar: [T; 2], + baz: Baz, +} + +comptime fn example(foo: StructDefinition) { + assert_eq(foo.generics().len(), 2); + + // Fails because `T` isn't in scope + // let t = quote { T }.as_type(); + // assert_eq(foo.generics()[0], t); +} +``` + +### fields + +#include_code fields noir_stdlib/src/meta/struct_def.nr rust + +Returns each field of this struct as a pair of (field name, field type). diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/trait_constraint.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/trait_constraint.md new file mode 100644 index 00000000000..3106f732b5a --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/trait_constraint.md @@ -0,0 +1,17 @@ +--- +title: TraitConstraint +--- + +`std::meta::trait_constraint` contains methods on the built-in `TraitConstraint` type which represents +a trait constraint that can be used to search for a trait implementation. This is similar +syntactically to just the trait itself, but can also contain generic arguments. E.g. `Eq`, `Default`, +`BuildHasher`. + +This type currently has no public methods but it can be used alongside `Type` in `implements` or `get_trait_impl`. + +## Trait Implementations + +```rust +impl Eq for TraitConstraint +impl Hash for TraitConstraint +``` diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/trait_def.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/trait_def.md new file mode 100644 index 00000000000..b6e8bf4ff76 --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/trait_def.md @@ -0,0 +1,22 @@ +--- +title: TraitDefinition +--- + +`std::meta::trait_def` contains methods on the built-in `TraitDefinition` type. This type +represents trait definitions such as `trait Foo { .. }` at the top-level of a program. + +## Methods + +### as_trait_constraint + +#include_code as_trait_constraint noir_stdlib/src/meta/trait_def.nr rust + +Converts this trait into a trait constraint. If there are any generics on this +trait, they will be kept as-is without instantiating or replacing them. + +## Trait Implementations + +```rust +impl Eq for TraitDefinition +impl Hash for TraitDefinition +``` diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/trait_impl.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/trait_impl.md new file mode 100644 index 00000000000..659c6aad719 --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/trait_impl.md @@ -0,0 +1,52 @@ +--- +title: TraitImpl +--- + +`std::meta::trait_impl` contains methods on the built-in `TraitImpl` type which represents a trait +implementation such as `impl Foo for Bar { ... }`. + +## Methods + +### trait_generic_args + +#include_code trait_generic_args noir_stdlib/src/meta/trait_impl.nr rust + +Returns any generic arguments on the trait of this trait implementation, if any. + +```rs +impl Foo for Bar { ... } + +comptime { + let bar_type = quote { Bar }.as_type(); + let foo = quote { Foo }.as_trait_constraint(); + + let my_impl: TraitImpl = bar_type.get_trait_impl(foo).unwrap(); + + let generics = my_impl.trait_generic_args(); + assert_eq(generics.len(), 2); + + assert_eq(generics[0], quote { i32 }.as_type()); + assert_eq(generics[1], quote { Field }.as_type()); +} +``` + +### methods + +#include_code methods noir_stdlib/src/meta/trait_impl.nr rust + +Returns each method in this trait impl. + +Example: + +```rs +comptime { + let i32_type = quote { i32 }.as_type(); + let eq = quote { Eq }.as_trait_constraint(); + + let impl_eq_for_i32: TraitImpl = i32_type.get_trait_impl(eq).unwrap(); + let methods = impl_eq_for_i32.methods(); + + assert_eq(methods.len(), 1); + assert_eq(methods[0].name(), quote { eq }); +} +``` diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/typ.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/typ.md new file mode 100644 index 00000000000..bad6435e94a --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/typ.md @@ -0,0 +1,126 @@ +--- +title: Type +--- + +`std::meta::typ` contains methods on the built-in `Type` type used for representing +a type in the source program. + +## Methods + +### as_array + +#include_code as_array noir_stdlib/src/meta/typ.nr rust + +If this type is an array, return a pair of (element type, size type). + +Example: + +```rust +comptime { + let array_type = quote { [Field; 3] }.as_type(); + let (field_type, three_type) = array_type.as_array().unwrap(); + + assert(field_type.is_field()); + assert_eq(three_type.as_constant().unwrap(), 3); +} +``` + +### as_constant + +#include_code as_constant noir_stdlib/src/meta/typ.nr rust + +If this type is a constant integer (such as the `3` in the array type `[Field; 3]`), +return the numeric constant. + +### as_integer + +#include_code as_integer noir_stdlib/src/meta/typ.nr rust + +If this is an integer type, return a boolean which is `true` +if the type is signed, as well as the number of bits of this integer type. + +### as_slice + +#include_code as_slice noir_stdlib/src/meta/typ.nr rust + +If this is a slice type, return the element type of the slice. + +### as_struct + +#include_code as_struct noir_stdlib/src/meta/typ.nr rust + +If this is a struct type, returns the struct in addition to +any generic arguments on this type. + +### as_tuple + +#include_code as_tuple noir_stdlib/src/meta/typ.nr rust + +If this is a tuple type, returns each element type of the tuple. + +### get_trait_impl + +#include_code get_trait_impl noir_stdlib/src/meta/typ.nr rust + +Retrieves the trait implementation that implements the given +trait constraint for this type. If the trait constraint is not +found, `None` is returned. Note that since the concrete trait implementation +for a trait constraint specified from a `where` clause is unknown, +this function will return `None` in these cases. If you only want to know +whether a type implements a trait, use `implements` instead. + +Example: + +```rust +comptime { + let field_type = quote { Field }.as_type(); + let default = quote { Default }.as_trait_constraint(); + + let the_impl: TraitImpl = field_type.get_trait_impl(default).unwrap(); + assert(the_impl.methods().len(), 1); +} +``` + +### implements + +#include_code implements noir_stdlib/src/meta/typ.nr rust + +`true` if this type implements the given trait. Note that unlike +`get_trait_impl` this will also return true for any `where` constraints +in scope. + +Example: + +```rust +fn foo() where T: Default { + comptime { + let field_type = quote { Field }.as_type(); + let default = quote { Default }.as_trait_constraint(); + assert(field_type.implements(default)); + + let t = quote { T }.as_type(); + assert(t.implements(default)); + } +} +``` + +### is_bool + +#include_code is_bool noir_stdlib/src/meta/typ.nr rust + +`true` if this type is `bool`. + +### is_field + +#include_code is_field noir_stdlib/src/meta/typ.nr rust + +`true` if this type is `Field`. + +## Trait Implementations + +```rust +impl Eq for Type +``` +Note that this is syntactic equality, this is not the same as whether two types will type check +to be the same type. Unless type inference or generics are being used however, users should not +typically have to worry about this distinction. diff --git a/noir/noir-repo/noir_stdlib/src/cmp.nr b/noir/noir-repo/noir_stdlib/src/cmp.nr index ec979d60753..b7f473429a7 100644 --- a/noir/noir-repo/noir_stdlib/src/cmp.nr +++ b/noir/noir-repo/noir_stdlib/src/cmp.nr @@ -7,12 +7,14 @@ trait Eq { } // docs:end:eq-trait +// docs:start:derive_eq comptime fn derive_eq(s: StructDefinition) -> Quoted { let signature = quote { fn eq(_self: Self, _other: Self) -> bool }; let for_each_field = |name| quote { (_self.$name == _other.$name) }; let body = |fields| fields; crate::meta::make_trait_impl(s, quote { Eq }, signature, for_each_field, quote { & }, body) } +// docs:end:derive_eq impl Eq for Field { fn eq(self, other: Field) -> bool { self == other } } @@ -118,6 +120,7 @@ trait Ord { } // docs:end:ord-trait +// docs:start:derive_ord comptime fn derive_ord(s: StructDefinition) -> Quoted { let signature = quote { fn cmp(_self: Self, _other: Self) -> std::cmp::Ordering }; let for_each_field = |name| quote { @@ -132,6 +135,7 @@ comptime fn derive_ord(s: StructDefinition) -> Quoted { }; crate::meta::make_trait_impl(s, quote { Ord }, signature, for_each_field, quote {}, body) } +// docs:end:derive_ord // Note: Field deliberately does not implement Ord diff --git a/noir/noir-repo/noir_stdlib/src/hash/mod.nr b/noir/noir-repo/noir_stdlib/src/hash/mod.nr index d77b655398a..657e1cd8309 100644 --- a/noir/noir-repo/noir_stdlib/src/hash/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/hash/mod.nr @@ -144,12 +144,14 @@ trait Hash { fn hash(self, state: &mut H) where H: Hasher; } +// docs:start:derive_hash comptime fn derive_hash(s: StructDefinition) -> Quoted { let name = quote { Hash }; let signature = quote { fn hash(_self: Self, _state: &mut H) where H: std::hash::Hasher }; let for_each_field = |name| quote { _self.$name.hash(_state); }; crate::meta::make_trait_impl(s, name, signature, for_each_field, quote {}, |fields| fields) } +// docs:end:derive_hash // Hasher trait shall be implemented by algorithms to provide hash-agnostic means. // TODO: consider making the types generic here ([u8], [Field], etc.) diff --git a/noir/noir-repo/noir_stdlib/src/meta/expr.nr b/noir/noir-repo/noir_stdlib/src/meta/expr.nr index aa9db196acd..718ba9607d4 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/expr.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/expr.nr @@ -4,64 +4,102 @@ use crate::meta::op::BinaryOp; impl Expr { #[builtin(expr_as_array)] + // docs:start:as_array fn as_array(self) -> Option<[Expr]> {} + // docs:end:as_array #[builtin(expr_as_integer)] + // docs:start:as_integer fn as_integer(self) -> Option<(Field, bool)> {} + // docs:end:as_integer #[builtin(expr_as_binary_op)] + // docs:start:as_binary_op fn as_binary_op(self) -> Option<(Expr, BinaryOp, Expr)> {} + // docs:end:as_binary_op #[builtin(expr_as_block)] + // docs:start:as_block fn as_block(self) -> Option<[Expr]> {} + // docs:end:as_block #[builtin(expr_as_bool)] + // docs:start:as_bool fn as_bool(self) -> Option {} + // docs:end:as_bool #[builtin(expr_as_cast)] fn as_cast(self) -> Option<(Expr, UnresolvedType)> {} #[builtin(expr_as_comptime)] + // docs:start:as_comptime fn as_comptime(self) -> Option<[Expr]> {} + // docs:end:as_comptime #[builtin(expr_as_function_call)] + // docs:start:as_function_call fn as_function_call(self) -> Option<(Expr, [Expr])> {} + // docs:end:as_function_call #[builtin(expr_as_if)] + // docs:start:as_if fn as_if(self) -> Option<(Expr, Expr, Option)> {} + // docs:end:as_if #[builtin(expr_as_index)] + // docs:start:as_index fn as_index(self) -> Option<(Expr, Expr)> {} + // docs:end:as_index #[builtin(expr_as_member_access)] + // docs:start:as_member_access fn as_member_access(self) -> Option<(Expr, Quoted)> {} + // docs:end:as_member_access #[builtin(expr_as_repeated_element_array)] + // docs:start:as_repeated_element_array fn as_repeated_element_array(self) -> Option<(Expr, Expr)> {} + // docs:end:as_repeated_element_array #[builtin(expr_as_repeated_element_slice)] + // docs:start:as_repeated_element_slice fn as_repeated_element_slice(self) -> Option<(Expr, Expr)> {} + // docs:end:as_repeated_element_slice #[builtin(expr_as_slice)] + // docs:start:as_slice fn as_slice(self) -> Option<[Expr]> {} + // docs:end:as_slice #[builtin(expr_as_tuple)] + // docs:start:as_tuple fn as_tuple(self) -> Option<[Expr]> {} + // docs:end:as_tuple #[builtin(expr_as_unary_op)] + // docs:start:as_unary_op fn as_unary_op(self) -> Option<(UnaryOp, Expr)> {} + // docs:end:as_unary_op #[builtin(expr_as_unsafe)] + // docs:start:as_unsafe fn as_unsafe(self) -> Option<[Expr]> {} + // docs:end:as_unsafe #[builtin(expr_has_semicolon)] + // docs:start:has_semicolon fn has_semicolon(self) -> bool {} + // docs:end:has_semicolon #[builtin(expr_is_break)] + // docs:start:is_break fn is_break(self) -> bool {} + // docs:end:is_break #[builtin(expr_is_continue)] + // docs:start:is_continue fn is_continue(self) -> bool {} + // docs:end:is_continue } mod tests { @@ -120,8 +158,10 @@ mod tests { assert(get_binary_op(quote { x / y }).is_divide()); assert(get_binary_op(quote { x == y }).is_equal()); assert(get_binary_op(quote { x != y }).is_not_equal()); - assert(get_binary_op(quote { x > y }).is_greater()); - assert(get_binary_op(quote { x >= y }).is_greater_or_equal()); + assert(get_binary_op(quote { x < y }).is_less_than()); + assert(get_binary_op(quote { x <= y }).is_less_than_or_equal()); + assert(get_binary_op(quote { x > y }).is_greater_than()); + assert(get_binary_op(quote { x >= y }).is_greater_than_or_equal()); assert(get_binary_op(quote { x & y }).is_and()); assert(get_binary_op(quote { x | y }).is_or()); assert(get_binary_op(quote { x ^ y }).is_xor()); @@ -176,6 +216,9 @@ mod tests { } } + // This test can't only be around the comptime block since that will cause + // `nargo fmt` to remove the comptime keyword. + // docs:start:as_expr_example #[test] fn test_expr_as_function_call() { comptime @@ -186,6 +229,7 @@ mod tests { assert_eq(args[0].as_integer().unwrap(), (42, false)); } } + // docs:end:as_expr_example #[test] fn test_expr_as_if() { diff --git a/noir/noir-repo/noir_stdlib/src/meta/function_def.nr b/noir/noir-repo/noir_stdlib/src/meta/function_def.nr index 2b5ddd008ea..7ac8803e7e4 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/function_def.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/function_def.nr @@ -1,19 +1,31 @@ impl FunctionDefinition { #[builtin(function_def_name)] + // docs:start:name fn name(self) -> Quoted {} + // docs:end:name #[builtin(function_def_parameters)] + // docs:start:parameters fn parameters(self) -> [(Quoted, Type)] {} + // docs:end:parameters #[builtin(function_def_return_type)] + // docs:start:return_type fn return_type(self) -> Type {} + // docs:end:return_type #[builtin(function_def_set_body)] + // docs:start:set_body fn set_body(self, body: Quoted) {} + // docs:end:set_body #[builtin(function_def_set_parameters)] + // docs:start:set_parameters fn set_parameters(self, parameters: [(Quoted, Type)]) {} + // docs:end:set_parameters #[builtin(function_def_set_return_type)] + // docs:start:set_return_type fn set_return_type(self, return_type: Type) {} + // docs:end:set_return_type } diff --git a/noir/noir-repo/noir_stdlib/src/meta/mod.nr b/noir/noir-repo/noir_stdlib/src/meta/mod.nr index d16a8648bc2..1ace2d028a4 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/mod.nr @@ -1,7 +1,3 @@ -use crate::collections::umap::UHashMap; -use crate::hash::BuildHasherDefault; -use crate::hash::poseidon2::Poseidon2Hasher; - mod expr; mod function_def; mod module; @@ -16,20 +12,38 @@ mod quoted; /// Calling unquote as a macro (via `unquote!(arg)`) will unquote /// its argument. Since this is the effect `!` already does, `unquote` /// itself does not need to do anything besides return its argument. +// docs:start:unquote pub comptime fn unquote(code: Quoted) -> Quoted { + // docs:end:unquote code } /// Returns the type of any value #[builtin(type_of)] +// docs:start:type_of pub comptime fn type_of(x: T) -> Type {} +// docs:end:type_of + +// docs:start:derive_example +// These are needed for the unconstrained hashmap we're using to store derive functions +use crate::collections::umap::UHashMap; +use crate::hash::BuildHasherDefault; +use crate::hash::poseidon2::Poseidon2Hasher; +// A derive function is one that given a struct definition can +// create us a quoted trait impl from it. type DeriveFunction = fn(StructDefinition) -> Quoted; +// We'll keep a global HANDLERS map to keep track of the derive handler for each trait comptime mut global HANDLERS: UHashMap> = UHashMap::default(); +// Given a struct and a slice of traits to derive, create trait impls for each. +// This function is as simple as iterating over the slice, checking if we have a trait +// handler registered for the given trait, calling it, and appending the result. +// docs:start:derive #[varargs] pub comptime fn derive(s: StructDefinition, traits: [TraitDefinition]) -> Quoted { + // docs:end:derive let mut result = quote {}; for trait_to_derive in traits { @@ -44,10 +58,16 @@ pub comptime fn derive(s: StructDefinition, traits: [TraitDefinition]) -> Quoted result } +// docs:end:derive_example +// docs:start:derive_via +// To register a handler for a trait, just add it to our handlers map +// docs:start:derive_via_signature pub comptime fn derive_via(t: TraitDefinition, f: DeriveFunction) { + // docs:end:derive_via_signature HANDLERS.insert(t, f); } +// docs:end:derive_via /// `make_impl` is a helper function to make a simple impl, usually while deriving a trait. /// This impl has a couple assumptions: @@ -61,6 +81,7 @@ pub comptime fn derive_via(t: TraitDefinition, f: DeriveFunction) { /// any final processing - e.g. wrapping each field in a `StructConstructor { .. }` expression. /// /// See `derive_eq` and `derive_default` for example usage. +// docs:start:make_trait_impl pub comptime fn make_trait_impl( s: StructDefinition, trait_name: Quoted, @@ -69,6 +90,7 @@ pub comptime fn make_trait_impl( join_fields_with: Quoted, body: fn[Env2](Quoted) -> Quoted ) -> Quoted { + // docs:end:make_trait_impl let typ = s.as_type(); let impl_generics = s.generics().map(|g| quote { $g }).join(quote {,}); let where_clause = s.generics().map(|name| quote { $name: $trait_name }).join(quote {,}); @@ -90,3 +112,124 @@ pub comptime fn make_trait_impl( } } } + +mod tests { + // docs:start:quote-example + comptime fn quote_one() -> Quoted { + quote { 1 } + } + + fn returning_versus_macro_insertion() { + comptime + { + // let _a: Quoted = quote { 1 }; + let _a: Quoted = quote_one(); + + // let _b: i32 = 1; + let _b: i32 = quote_one!(); + } + } + // docs:end:quote-example + + // docs:start:derive-field-count-example + trait FieldCount { + fn field_count() -> u32; + } + + #[derive_field_count] + struct Bar { x: Field, y: [Field; 2] } + + comptime fn derive_field_count(s: StructDefinition) -> Quoted { + let typ = s.as_type(); + let field_count = s.fields().len(); + quote { + impl FieldCount for $typ { + fn field_count() -> u32 { + $field_count + } + } + } + } + // docs:end:derive-field-count-example + + // docs:start:annotation-arguments-example + #[assert_field_is_type(quote { i32 }.as_type())] + struct MyStruct { my_field: i32 } + + comptime fn assert_field_is_type(s: StructDefinition, typ: Type) { + // Assert the first field in `s` has type `typ` + let fields = s.fields(); + assert_eq(fields[0].1, typ); + } + // docs:end:annotation-arguments-example + + // docs:end:annotation-varargs-example + #[assert_three_args(1, 2, 3)] + struct MyOtherStruct { my_other_field: u32 } + + #[varargs] + comptime fn assert_three_args(_s: StructDefinition, args: [Field]) { + assert_eq(args.len(), 3); + } + // docs:end:annotation-varargs-example + + // docs:start:big-derive-usage-example + // Finally, to register a handler we call the above function as an annotation + // with our handler function. + #[derive_via(derive_do_nothing)] + trait DoNothing { + fn do_nothing(self); + } + + comptime fn derive_do_nothing(s: StructDefinition) -> Quoted { + // This is simplified since we don't handle generics or where clauses! + // In a real example we'd likely also need to introduce each of + // `s.generics()` as well as a trait constraint for each generic + // to ensure they also implement the trait. + let typ = s.as_type(); + quote { + impl DoNothing for $typ { + fn do_nothing(self) { + // Traits can't tell us what to do + println("something"); + } + } + } + } + + // Since `DoNothing` is a simple trait which: + // 1. Only has one method + // 2. Does not have any generics on the trait itself + // We can use `std::meta::make_trait_impl` to help us out. + // This helper function will generate our impl for us along with any + // necessary where clauses and still provides a flexible interface + // for us to work on each field on the struct. + comptime fn derive_do_nothing_alt(s: StructDefinition) -> Quoted { + let trait_name = quote { DoNothing }; + let method_signature = quote { fn do_nothing(self) }; + + // Call `do_nothing` recursively on each field in the struct + let for_each_field = |field_name| quote { self.$field_name.do_nothing(); }; + + // Some traits like Eq want to join each field expression with something like `&`. + // We don't need that here + let join_fields_with = quote {}; + + // The body function is a spot to insert any extra setup/teardown needed. + // We'll insert our println here. Since we recur on each field, we should see + // one println for the struct itself, followed by a println for every field (recursively). + let body = |body| quote { + println("something"); + $body + }; + crate::meta::make_trait_impl( + s, + trait_name, + method_signature, + for_each_field, + join_fields_with, + body + ) + } + // docs:end:big-derive-usage-example +} diff --git a/noir/noir-repo/noir_stdlib/src/meta/module.nr b/noir/noir-repo/noir_stdlib/src/meta/module.nr index ee00f360806..6ea3ca55fb1 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/module.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/module.nr @@ -1,10 +1,16 @@ impl Module { #[builtin(module_is_contract)] +// docs:start:is_contract fn is_contract(self) -> bool {} + // docs:end:is_contract #[builtin(module_functions)] +// docs:start:functions fn functions(self) -> [FunctionDefinition] {} + // docs:end:functions #[builtin(module_name)] +// docs:start:name fn name(self) -> Quoted {} + // docs:end:name } diff --git a/noir/noir-repo/noir_stdlib/src/meta/op.nr b/noir/noir-repo/noir_stdlib/src/meta/op.nr index ebd89677c50..9c892c4d80b 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/op.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/op.nr @@ -3,19 +3,27 @@ struct UnaryOp { } impl UnaryOp { - fn is_minus(self) -> bool { + // docs:start:is_minus + pub fn is_minus(self) -> bool { + // docs:end:is_minus self.op == 0 } - fn is_not(self) -> bool { + // docs:start:is_not + pub fn is_not(self) -> bool { + // docs:end:is_not self.op == 1 } - fn is_mutable_reference(self) -> bool { + // docs:start:is_mutable_reference + pub fn is_mutable_reference(self) -> bool { + // docs:end:is_mutable_reference self.op == 2 } - fn is_dereference(self) -> bool { + // docs:start:is_dereference + pub fn is_dereference(self) -> bool { + // docs:end:is_dereference self.op == 3 } } @@ -25,67 +33,99 @@ struct BinaryOp { } impl BinaryOp { - fn is_add(self) -> bool { + // docs:start:is_add + pub fn is_add(self) -> bool { + // docs:end:is_add self.op == 0 } - fn is_subtract(self) -> bool { + // docs:start:is_subtract + pub fn is_subtract(self) -> bool { + // docs:end:is_subtract self.op == 1 } - fn is_multiply(self) -> bool { + // docs:start:is_multiply + pub fn is_multiply(self) -> bool { + // docs:end:is_multiply self.op == 2 } - fn is_divide(self) -> bool { + // docs:start:is_divide + pub fn is_divide(self) -> bool { + // docs:end:is_divide self.op == 3 } - fn is_equal(self) -> bool { + // docs:start:is_equal + pub fn is_equal(self) -> bool { + // docs:end:is_equal self.op == 4 } - fn is_not_equal(self) -> bool { + // docs:start:is_not_equal + pub fn is_not_equal(self) -> bool { + // docs:end:is_not_equal self.op == 5 } - fn is_less(self) -> bool { + // docs:start:is_less_than + pub fn is_less_than(self) -> bool { + // docs:end:is_less_than self.op == 6 } - fn is_less_equal(self) -> bool { + // docs:start:is_less_than_or_equal + pub fn is_less_than_or_equal(self) -> bool { + // docs:end:is_less_than_or_equal self.op == 7 } - fn is_greater(self) -> bool { + // docs:start:is_greater_than + pub fn is_greater_than(self) -> bool { + // docs:end:is_greater_than self.op == 8 } - fn is_greater_or_equal(self) -> bool { + // docs:start:is_greater_than_or_equal + pub fn is_greater_than_or_equal(self) -> bool { + // docs:end:is_greater_than_or_equal self.op == 9 } - fn is_and(self) -> bool { + // docs:start:is_and + pub fn is_and(self) -> bool { + // docs:end:is_and self.op == 10 } - fn is_or(self) -> bool { + // docs:start:is_or + pub fn is_or(self) -> bool { + // docs:end:is_or self.op == 11 } - fn is_xor(self) -> bool { + // docs:start:is_xor + pub fn is_xor(self) -> bool { + // docs:end:is_xor self.op == 12 } - fn is_shift_right(self) -> bool { + // docs:start:is_shift_right + pub fn is_shift_right(self) -> bool { + // docs:end:is_shift_right self.op == 13 } - fn is_shift_left(self) -> bool { + // docs:start:is_shift_left + pub fn is_shift_left(self) -> bool { + // docs:end:is_shift_left self.op == 14 } - fn is_modulo(self) -> bool { + // docs:start:is_modulo + pub fn is_modulo(self) -> bool { + // docs:end:is_modulo self.op == 15 } } diff --git a/noir/noir-repo/noir_stdlib/src/meta/quoted.nr b/noir/noir-repo/noir_stdlib/src/meta/quoted.nr index cccc3fe0f12..9fd1e9026ba 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/quoted.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/quoted.nr @@ -3,16 +3,24 @@ use crate::option::Option; impl Quoted { #[builtin(quoted_as_expr)] +// docs:start:as_expr fn as_expr(self) -> Option {} + // docs:end:as_expr #[builtin(quoted_as_module)] +// docs:start:as_module fn as_module(self) -> Option {} + // docs:end:as_module #[builtin(quoted_as_trait_constraint)] +// docs:start:as_trait_constraint fn as_trait_constraint(self) -> TraitConstraint {} + // docs:end:as_trait_constraint #[builtin(quoted_as_type)] +// docs:start:as_type fn as_type(self) -> Type {} + // docs:end:as_type } impl Eq for Quoted { diff --git a/noir/noir-repo/noir_stdlib/src/meta/struct_def.nr b/noir/noir-repo/noir_stdlib/src/meta/struct_def.nr index 8d3f9ceb8a5..60fdeba21aa 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/struct_def.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/struct_def.nr @@ -2,14 +2,20 @@ impl StructDefinition { /// Return a syntactic version of this struct definition as a type. /// For example, `as_type(quote { type Foo { ... } })` would return `Foo` #[builtin(struct_def_as_type)] +// docs:start:as_type fn as_type(self) -> Type {} + // docs:end:as_type /// Return each generic on this struct. #[builtin(struct_def_generics)] +// docs:start:generics fn generics(self) -> [Type] {} + // docs:end:generics /// Returns (name, type) pairs of each field in this struct. Each type is as-is /// with any generic arguments unchanged. #[builtin(struct_def_fields)] +// docs:start:fields fn fields(self) -> [(Quoted, Type)] {} + // docs:end:fields } diff --git a/noir/noir-repo/noir_stdlib/src/meta/trait_def.nr b/noir/noir-repo/noir_stdlib/src/meta/trait_def.nr index ca381cb8e16..c26b571240b 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/trait_def.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/trait_def.nr @@ -3,7 +3,9 @@ use crate::cmp::Eq; impl TraitDefinition { #[builtin(trait_def_as_trait_constraint)] +// docs:start:as_trait_constraint fn as_trait_constraint(_self: Self) -> TraitConstraint {} + // docs:end:as_trait_constraint } impl Eq for TraitDefinition { diff --git a/noir/noir-repo/noir_stdlib/src/meta/trait_impl.nr b/noir/noir-repo/noir_stdlib/src/meta/trait_impl.nr index 2f82ee5f434..15b02eac6bd 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/trait_impl.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/trait_impl.nr @@ -1,7 +1,11 @@ impl TraitImpl { #[builtin(trait_impl_trait_generic_args)] +// docs:start:trait_generic_args fn trait_generic_args(self) -> [Type] {} + // docs:end:trait_generic_args #[builtin(trait_impl_methods)] +// docs:start:methods fn methods(self) -> [FunctionDefinition] {} + // docs:end:methods } diff --git a/noir/noir-repo/noir_stdlib/src/meta/typ.nr b/noir/noir-repo/noir_stdlib/src/meta/typ.nr index 67ad2a96739..e68d016be19 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/typ.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/typ.nr @@ -3,34 +3,57 @@ use crate::option::Option; impl Type { #[builtin(type_as_array)] +// docs:start:as_array fn as_array(self) -> Option<(Type, Type)> {} + // docs:end:as_array #[builtin(type_as_constant)] +// docs:start:as_constant fn as_constant(self) -> Option {} + // docs:end:as_constant #[builtin(type_as_integer)] +// docs:start:as_integer fn as_integer(self) -> Option<(bool, u8)> {} + // docs:end:as_integer #[builtin(type_as_slice)] +// docs:start:as_slice fn as_slice(self) -> Option {} + // docs:end:as_slice #[builtin(type_as_struct)] +// docs:start:as_struct fn as_struct(self) -> Option<(StructDefinition, [Type])> {} + // docs:end:as_struct #[builtin(type_as_tuple)] +// docs:start:as_tuple fn as_tuple(self) -> Option<[Type]> {} + // docs:end:as_tuple + + #[builtin(type_get_trait_impl)] +// docs:start:get_trait_impl + fn get_trait_impl(self, constraint: TraitConstraint) -> Option {} + // docs:end:get_trait_impl #[builtin(type_get_trait_impl)] fn get_trait_impl(self, constraint: TraitConstraint) -> Option {} #[builtin(type_implements)] +// docs:start:implements fn implements(self, constraint: TraitConstraint) -> bool {} + // docs:end:implements #[builtin(type_is_bool)] +// docs:start:is_bool fn is_bool(self) -> bool {} + // docs:end:is_bool #[builtin(type_is_field)] +// docs:start:is_field fn is_field(self) -> bool {} + // docs:end:is_field } impl Eq for Type { diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_module/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_module/src/main.nr index e9c9817cfd8..8d834381fea 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_module/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_module/src/main.nr @@ -24,3 +24,18 @@ fn main() { assert_eq(bar.name(), quote { bar }); } } + +// docs:start:as_module_example +mod baz { + mod qux {} +} + +#[test] +fn as_module_test() { + comptime + { + let my_mod = quote { baz::qux }.as_module().unwrap(); + assert_eq(my_mod.name(), quote { qux }); + } +} +// docs:end:as_module_example diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_type/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_type/src/main.nr index f0b53a392ed..6d98d1d173b 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_type/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_type/src/main.nr @@ -113,6 +113,7 @@ fn main() { } } +// docs:start:implements_example fn function_with_where(_x: T) where T: SomeTrait { comptime { @@ -123,3 +124,4 @@ fn function_with_where(_x: T) where T: SomeTrait { assert(t.get_trait_impl(some_trait_i32).is_none()); } } +// docs:end:implements_example diff --git a/noir/noir-repo/test_programs/execution_success/nested_dyn_array_regression_5782/Nargo.toml b/noir/noir-repo/test_programs/execution_success/nested_dyn_array_regression_5782/Nargo.toml new file mode 100644 index 00000000000..b5cdd19e186 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/nested_dyn_array_regression_5782/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "nested_dyn_array_regression_5782" +type = "bin" +authors = [""] +compiler_version = ">=0.33.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/nested_dyn_array_regression_5782/Prover.toml b/noir/noir-repo/test_programs/execution_success/nested_dyn_array_regression_5782/Prover.toml new file mode 100644 index 00000000000..de2960def06 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/nested_dyn_array_regression_5782/Prover.toml @@ -0,0 +1,2 @@ +array = [5, 10] +i = 1 diff --git a/noir/noir-repo/test_programs/execution_success/nested_dyn_array_regression_5782/src/main.nr b/noir/noir-repo/test_programs/execution_success/nested_dyn_array_regression_5782/src/main.nr new file mode 100644 index 00000000000..b6a1238a9de --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/nested_dyn_array_regression_5782/src/main.nr @@ -0,0 +1,13 @@ +fn main(mut array: [Field; 2], i: u32) { + assert_eq(array[i - 1], 5); + assert_eq(array[i], 10); + + array[i] = 2; + + let array2 = [array, array]; + + assert_eq(array2[0][0], 5); + assert_eq(array2[0][i], 2); + assert_eq(array2[i][0], 5); + assert_eq(array2[i][i], 2); +} diff --git a/noir/noir-repo/tooling/debugger/src/context.rs b/noir/noir-repo/tooling/debugger/src/context.rs index 890732b579c..0d348cf172d 100644 --- a/noir/noir-repo/tooling/debugger/src/context.rs +++ b/noir/noir-repo/tooling/debugger/src/context.rs @@ -442,16 +442,15 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { self.debug_artifact.debug_symbols[debug_location.circuit_id as usize] .opcode_location(&debug_location.opcode_location) .unwrap_or_else(|| { - if let Some(brillig_function_id) = debug_location.brillig_function_id { + if let (Some(brillig_function_id), Some(brillig_location)) = ( + debug_location.brillig_function_id, + debug_location.opcode_location.to_brillig_location(), + ) { let brillig_locations = self.debug_artifact.debug_symbols [debug_location.circuit_id as usize] .brillig_locations .get(&brillig_function_id); - brillig_locations - .unwrap() - .get(&debug_location.opcode_location) - .cloned() - .unwrap_or_default() + brillig_locations.unwrap().get(&brillig_location).cloned().unwrap_or_default() } else { vec![] } @@ -660,8 +659,9 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { fn get_current_acir_index(&self) -> Option { self.get_current_debug_location().map(|debug_location| { match debug_location.opcode_location { - OpcodeLocation::Acir(acir_index) => acir_index, - OpcodeLocation::Brillig { acir_index, .. } => acir_index, + OpcodeLocation::Acir(acir_index) | OpcodeLocation::Brillig { acir_index, .. } => { + acir_index + } } }) } @@ -893,8 +893,19 @@ fn build_source_to_opcode_debug_mappings( ); for (brillig_function_id, brillig_locations_map) in &debug_symbols.brillig_locations { + let brillig_locations_map = brillig_locations_map + .iter() + .map(|(key, val)| { + ( + // TODO: this is a temporary placeholder until the debugger is updated to handle the new brillig debug locations. + OpcodeLocation::Brillig { acir_index: 0, brillig_index: key.0 }, + val.clone(), + ) + }) + .collect(); + add_opcode_locations_map( - brillig_locations_map, + &brillig_locations_map, &mut result, &simple_files, circuit_id, diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion.rs b/noir/noir-repo/tooling/lsp/src/requests/completion.rs index 15b5be1690c..4d6bba6574d 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion.rs @@ -16,16 +16,19 @@ use noirc_errors::{Location, Span}; use noirc_frontend::{ ast::{ AsTraitPath, BlockExpression, CallExpression, ConstructorExpression, Expression, - ExpressionKind, ForLoopStatement, Ident, IfExpression, LValue, Lambda, LetStatement, - MemberAccessExpression, MethodCallExpression, NoirFunction, NoirStruct, NoirTraitImpl, - Path, PathKind, PathSegment, Pattern, Statement, StatementKind, TraitItem, TypeImpl, - UnresolvedGeneric, UnresolvedGenerics, UnresolvedType, UnresolvedTypeData, UseTree, - UseTreeKind, + ExpressionKind, ForLoopStatement, Ident, IfExpression, ItemVisibility, LValue, Lambda, + LetStatement, MemberAccessExpression, MethodCallExpression, NoirFunction, NoirStruct, + NoirTraitImpl, Path, PathKind, PathSegment, Pattern, Statement, StatementKind, TraitItem, + TypeImpl, UnresolvedGeneric, UnresolvedGenerics, UnresolvedType, UnresolvedTypeData, + UseTree, UseTreeKind, }, graph::{CrateId, Dependency}, hir::{ def_map::{CrateDefMap, LocalModuleId, ModuleId}, - resolution::path_resolver::{PathResolver, StandardPathResolver}, + resolution::{ + import::can_reference_module_id, + path_resolver::{PathResolver, StandardPathResolver}, + }, }, hir_def::traits::Trait, macros_api::{ModuleDefId, NodeInterner}, @@ -1238,29 +1241,33 @@ impl<'a> NodeFinder<'a> { if name_matches(name, prefix) { let per_ns = module_data.find_name(ident); - if let Some((module_def_id, _, _)) = per_ns.types { - if let Some(completion_item) = self.module_def_id_completion_item( - module_def_id, - name.clone(), - function_completion_kind, - function_kind, - requested_items, - ) { - self.completion_items.push(completion_item); - self.suggested_module_def_ids.insert(module_def_id); + if let Some((module_def_id, visibility, _)) = per_ns.types { + if is_visible(module_id, self.module_id, visibility, self.def_maps) { + if let Some(completion_item) = self.module_def_id_completion_item( + module_def_id, + name.clone(), + function_completion_kind, + function_kind, + requested_items, + ) { + self.completion_items.push(completion_item); + self.suggested_module_def_ids.insert(module_def_id); + } } } - if let Some((module_def_id, _, _)) = per_ns.values { - if let Some(completion_item) = self.module_def_id_completion_item( - module_def_id, - name.clone(), - function_completion_kind, - function_kind, - requested_items, - ) { - self.completion_items.push(completion_item); - self.suggested_module_def_ids.insert(module_def_id); + if let Some((module_def_id, visibility, _)) = per_ns.values { + if is_visible(module_id, self.module_id, visibility, self.def_maps) { + if let Some(completion_item) = self.module_def_id_completion_item( + module_def_id, + name.clone(), + function_completion_kind, + function_kind, + requested_items, + ) { + self.completion_items.push(completion_item); + self.suggested_module_def_ids.insert(module_def_id); + } } } } @@ -1382,6 +1389,23 @@ fn module_def_id_from_reference_id(reference_id: ReferenceId) -> Option None, } + + true +} + +fn is_visible( + target_module_id: ModuleId, + current_module_id: ModuleId, + visibility: ItemVisibility, + def_maps: &BTreeMap, +) -> bool { + can_reference_module_id( + def_maps, + current_module_id.krate, + current_module_id.local_id, + target_module_id, + visibility, + ) } #[cfg(test)] diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs b/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs index 07ac8885bd5..59e007c5a70 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs @@ -182,7 +182,8 @@ mod completion_tests { async fn test_use_function() { let src = r#" mod foo { - fn bar(x: i32) -> u64 { 0 } + pub fn bar(x: i32) -> u64 { 0 } + fn bar_is_private(x: i32) -> u64 { 0 } } use foo::>|< "#; @@ -1703,7 +1704,7 @@ mod completion_tests { async fn test_completes_after_colon_in_the_middle_of_an_ident_middle_segment() { let src = r#" mod foo { - fn bar() {} + pub fn bar() {} } fn main() { @@ -1725,7 +1726,7 @@ mod completion_tests { async fn test_completes_at_function_call_name() { let src = r#" mod foo { - fn bar() {} + pub fn bar() {} } fn main() { diff --git a/noir/noir-repo/tooling/nargo/src/errors.rs b/noir/noir-repo/tooling/nargo/src/errors.rs index f9668653d0b..b5571ff7758 100644 --- a/noir/noir-repo/tooling/nargo/src/errors.rs +++ b/noir/noir-repo/tooling/nargo/src/errors.rs @@ -158,13 +158,16 @@ fn extract_locations_from_error( debug[resolved_location.acir_function_index] .opcode_location(&resolved_location.opcode_location) .unwrap_or_else(|| { - if let Some(brillig_function_id) = brillig_function_id { + if let (Some(brillig_function_id), Some(brillig_location)) = ( + brillig_function_id, + &resolved_location.opcode_location.to_brillig_location(), + ) { let brillig_locations = debug[resolved_location.acir_function_index] .brillig_locations .get(&brillig_function_id); brillig_locations .unwrap() - .get(&resolved_location.opcode_location) + .get(brillig_location) .cloned() .unwrap_or_default() } else { diff --git a/noir/noir-repo/tooling/profiler/src/cli/gates_flamegraph_cmd.rs b/noir/noir-repo/tooling/profiler/src/cli/gates_flamegraph_cmd.rs index 0fa12239d05..d5fefc4ecda 100644 --- a/noir/noir-repo/tooling/profiler/src/cli/gates_flamegraph_cmd.rs +++ b/noir/noir-repo/tooling/profiler/src/cli/gates_flamegraph_cmd.rs @@ -22,7 +22,7 @@ pub(crate) struct GatesFlamegraphCommand { backend_path: String, /// Command to get a gates report from the backend. Defaults to "gates" - #[clap(long, short, default_value = "gates")] + #[clap(long, short = 'g', default_value = "gates")] backend_gates_command: String, #[arg(trailing_var_arg = true, allow_hyphen_values = true)] @@ -87,6 +87,7 @@ fn run_with_provider( opcode: AcirOrBrilligOpcode::Acir(opcode), call_stack: vec![OpcodeLocation::Acir(index)], count: gates, + brillig_function_id: None, }) .collect(); diff --git a/noir/noir-repo/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs b/noir/noir-repo/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs index d7f3cbb9b85..863d45b96d1 100644 --- a/noir/noir-repo/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs +++ b/noir/noir-repo/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs @@ -1,5 +1,6 @@ use std::path::{Path, PathBuf}; +use acir::circuit::brillig::BrilligFunctionId; use acir::circuit::{Circuit, Opcode, OpcodeLocation}; use clap::Args; use color_eyre::eyre::{self, Context}; @@ -20,7 +21,7 @@ pub(crate) struct OpcodesFlamegraphCommand { #[clap(long, short)] output: String, - /// Wether to skip brillig functions + /// Whether to skip brillig functions #[clap(long, short, action)] skip_brillig: bool, } @@ -62,6 +63,7 @@ fn run_with_generator( opcode: AcirOrBrilligOpcode::Acir(opcode.clone()), call_stack: vec![OpcodeLocation::Acir(index)], count: 1, + brillig_function_id: None, }) .collect(); @@ -101,6 +103,7 @@ fn run_with_generator( brillig_index, }], count: 1, + brillig_function_id: Some(BrilligFunctionId(brillig_fn_index as u32)), }) .collect(); diff --git a/noir/noir-repo/tooling/profiler/src/flamegraph.rs b/noir/noir-repo/tooling/profiler/src/flamegraph.rs index da76f9b9938..488079de50a 100644 --- a/noir/noir-repo/tooling/profiler/src/flamegraph.rs +++ b/noir/noir-repo/tooling/profiler/src/flamegraph.rs @@ -1,6 +1,7 @@ use std::path::Path; use std::{collections::BTreeMap, io::BufWriter}; +use acir::circuit::brillig::BrilligFunctionId; use acir::circuit::OpcodeLocation; use acir::AcirField; use color_eyre::eyre::{self}; @@ -19,6 +20,7 @@ pub(crate) struct Sample { pub(crate) opcode: AcirOrBrilligOpcode, pub(crate) call_stack: Vec, pub(crate) count: usize, + pub(crate) brillig_function_id: Option, } #[derive(Debug, Default)] @@ -90,9 +92,24 @@ fn generate_folded_sorted_lines<'files, F: AcirField>( let mut location_names: Vec = sample .call_stack .into_iter() - .flat_map(|opcode_location| debug_symbols.locations.get(&opcode_location)) - .flatten() - .map(|location| location_to_callsite_label(*location, files)) + .flat_map(|opcode_location| { + debug_symbols.opcode_location(&opcode_location).unwrap_or_else(|| { + if let (Some(brillig_function_id), Some(brillig_location)) = + (sample.brillig_function_id, opcode_location.to_brillig_location()) + { + let brillig_locations = + debug_symbols.brillig_locations.get(&brillig_function_id); + if let Some(brillig_locations) = brillig_locations { + brillig_locations.get(&brillig_location).cloned().unwrap_or_default() + } else { + vec![] + } + } else { + vec![] + } + }) + }) + .map(|location| location_to_callsite_label(location, files)) .collect(); if location_names.is_empty() { @@ -286,11 +303,13 @@ mod tests { opcode: AcirOrBrilligOpcode::Acir(AcirOpcode::AssertZero(Expression::default())), call_stack: vec![OpcodeLocation::Acir(0)], count: 10, + brillig_function_id: None, }, Sample { opcode: AcirOrBrilligOpcode::Acir(AcirOpcode::AssertZero(Expression::default())), call_stack: vec![OpcodeLocation::Acir(1)], count: 20, + brillig_function_id: None, }, Sample { opcode: AcirOrBrilligOpcode::Acir(AcirOpcode::MemoryInit { @@ -300,6 +319,7 @@ mod tests { }), call_stack: vec![OpcodeLocation::Acir(2)], count: 30, + brillig_function_id: None, }, ];