diff --git a/.noir-sync-commit b/.noir-sync-commit index 3222858403e..e426436db9b 100644 --- a/.noir-sync-commit +++ b/.noir-sync-commit @@ -1 +1 @@ -bb6913ac53620fabd73e24ca1a2b1369225903ec +a213c15275892581e5d8f7235baf08a6cb137da4 diff --git a/noir/noir-repo/.github/workflows/test-js-packages.yml b/noir/noir-repo/.github/workflows/test-js-packages.yml index 9f46e6f98e8..06436af8d27 100644 --- a/noir/noir-repo/.github/workflows/test-js-packages.yml +++ b/noir/noir-repo/.github/workflows/test-js-packages.yml @@ -509,6 +509,59 @@ jobs: working-directory: ./examples/codegen_verifier run: ./test.sh + external-repo-checks: + needs: [build-nargo] + runs-on: ubuntu-22.04 + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + project: + - { repo: AztecProtocol/aztec-nr, path: ./ } + - { repo: AztecProtocol/aztec-packages, path: ./noir-projects/noir-contracts } + # Disabled as aztec-packages requires a setup-step in order to generate a `Nargo.toml` + #- { repo: AztecProtocol/aztec-packages, path: ./noir-projects/noir-protocol-circuits } + - { repo: zac-williamson/noir-edwards, path: ./, ref: 0016ce82cd58b6ebb0c43c271725590bcff4e755 } + # TODO: Enable these once they're passing against master again. + # - { repo: zac-williamson/noir-bignum, path: ./, ref: 030c2acce1e6b97c44a3bbbf3429ed96f20d72d3 } + # - { repo: vlayer-xyz/monorepo, path: ./, ref: ee46af88c025863872234eb05d890e1e447907cb } + # - { repo: hashcloak/noir-bigint, path: ./, ref: 940ddba3a5201b508e7b37a2ef643551afcf5ed8 } + + name: Check external repo - ${{ matrix.project.repo }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + repository: ${{ matrix.project.repo }} + path: test-repo + ref: ${{ matrix.project.ref }} + + - name: Download nargo binary + uses: actions/download-artifact@v4 + with: + name: nargo + path: ./nargo + + - name: Set nargo on PATH + run: | + nargo_binary="${{ github.workspace }}/nargo/nargo" + chmod +x $nargo_binary + echo "$(dirname $nargo_binary)" >> $GITHUB_PATH + export PATH="$PATH:$(dirname $nargo_binary)" + nargo -V + + - name: Remove requirements on compiler version + working-directory: ./test-repo + run: | + # Github actions seems to not expand "**" in globs by default. + shopt -s globstar + sed -i '/^compiler_version/d' ./**/Nargo.toml + + - name: Run nargo check + working-directory: ./test-repo/${{ matrix.project.path }} + run: nargo check + # This is a job which depends on all test jobs and reports the overall status. # This allows us to add/remove test jobs without having to update the required workflows. tests-end: diff --git a/noir/noir-repo/Cargo.lock b/noir/noir-repo/Cargo.lock index 1df84a80bc7..5d487f4f23f 100644 --- a/noir/noir-repo/Cargo.lock +++ b/noir/noir-repo/Cargo.lock @@ -2594,6 +2594,7 @@ dependencies = [ "dirs", "fm", "nargo", + "noirc_driver", "noirc_frontend", "semver", "serde", 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 115ecbf7992..5d749e709b3 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs @@ -360,7 +360,7 @@ mod tests { use std::collections::BTreeSet; use super::{ - opcodes::{BlackBoxFuncCall, ConstantOrWitnessEnum, FunctionInput}, + opcodes::{BlackBoxFuncCall, FunctionInput}, Circuit, Compression, Opcode, PublicInputs, }; use crate::{ diff --git a/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs b/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs index 7c560a0a346..6a301ec5115 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs @@ -277,7 +277,8 @@ impl BlackBoxFuncCall { | BlackBoxFuncCall::BigIntDiv { .. } | BlackBoxFuncCall::BigIntToLeBytes { .. } => Vec::new(), BlackBoxFuncCall::MultiScalarMul { points, scalars, .. } => { - let mut inputs: Vec> = Vec::with_capacity(points.len() * 2); + let mut inputs: Vec> = + Vec::with_capacity(points.len() + scalars.len()); inputs.extend(points.iter().copied()); inputs.extend(scalars.iter().copied()); inputs @@ -520,7 +521,7 @@ mod tests { use crate::{circuit::Opcode, native_types::Witness}; use acir_field::{AcirField, FieldElement}; - use super::{BlackBoxFuncCall, ConstantOrWitnessEnum, FunctionInput}; + use super::{BlackBoxFuncCall, FunctionInput}; fn keccakf1600_opcode() -> Opcode { let inputs: Box<[FunctionInput; 25]> = diff --git a/noir/noir-repo/acvm-repo/acir/tests/test_program_serialization.rs b/noir/noir-repo/acvm-repo/acir/tests/test_program_serialization.rs index 3a42cc41d47..3047ac002e8 100644 --- a/noir/noir-repo/acvm-repo/acir/tests/test_program_serialization.rs +++ b/noir/noir-repo/acvm-repo/acir/tests/test_program_serialization.rs @@ -14,7 +14,7 @@ use std::collections::BTreeSet; use acir::{ circuit::{ brillig::{BrilligBytecode, BrilligInputs, BrilligOutputs}, - opcodes::{BlackBoxFuncCall, BlockId, ConstantOrWitnessEnum, FunctionInput, MemOp}, + opcodes::{BlackBoxFuncCall, BlockId, FunctionInput, MemOp}, Circuit, Opcode, Program, PublicInputs, }, native_types::{Expression, Witness}, diff --git a/noir/noir-repo/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs b/noir/noir-repo/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs index 87a026148d7..b03b6715abe 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs @@ -150,7 +150,7 @@ mod tests { use crate::compiler::optimizers::redundant_range::RangeOptimizer; use acir::{ circuit::{ - opcodes::{BlackBoxFuncCall, ConstantOrWitnessEnum, FunctionInput}, + opcodes::{BlackBoxFuncCall, FunctionInput}, Circuit, ExpressionWidth, Opcode, PublicInputs, }, native_types::{Expression, Witness}, diff --git a/noir/noir-repo/acvm-repo/acvm_js/build.sh b/noir/noir-repo/acvm-repo/acvm_js/build.sh index c07d2d8a4c1..16fb26e55db 100755 --- a/noir/noir-repo/acvm-repo/acvm_js/build.sh +++ b/noir/noir-repo/acvm-repo/acvm_js/build.sh @@ -25,7 +25,7 @@ function run_if_available { require_command jq require_command cargo require_command wasm-bindgen -#require_command wasm-opt +require_command wasm-opt self_path=$(dirname "$(readlink -f "$0")") pname=$(cargo read-manifest | jq -r '.name') diff --git a/noir/noir-repo/acvm-repo/blackbox_solver/src/bigint.rs b/noir/noir-repo/acvm-repo/blackbox_solver/src/bigint.rs index 5b19f03a238..b8bc9dc0d70 100644 --- a/noir/noir-repo/acvm-repo/blackbox_solver/src/bigint.rs +++ b/noir/noir-repo/acvm-repo/blackbox_solver/src/bigint.rs @@ -18,7 +18,7 @@ pub struct BigIntSolver { } impl BigIntSolver { - pub(crate) fn get_bigint( + pub fn get_bigint( &self, id: u32, func: BlackBoxFunc, @@ -32,7 +32,7 @@ impl BigIntSolver { .cloned() } - pub(crate) fn get_modulus( + pub fn get_modulus( &self, id: u32, func: BlackBoxFunc, diff --git a/noir/noir-repo/acvm-repo/brillig_vm/src/black_box.rs b/noir/noir-repo/acvm-repo/brillig_vm/src/black_box.rs index 53599f79bc7..d37258036fc 100644 --- a/noir/noir-repo/acvm-repo/brillig_vm/src/black_box.rs +++ b/noir/noir-repo/acvm-repo/brillig_vm/src/black_box.rs @@ -421,6 +421,14 @@ impl BrilligBigintSolver { rhs: u32, func: BlackBoxFunc, ) -> Result { + let modulus_lhs = self.bigint_solver.get_modulus(lhs, func)?; + let modulus_rhs = self.bigint_solver.get_modulus(rhs, func)?; + if modulus_lhs != modulus_rhs { + return Err(BlackBoxResolutionError::Failed( + func, + "moduli should be identical in BigInt operation".to_string(), + )); + } let id = self.create_bigint_id(); self.bigint_solver.bigint_op(lhs, rhs, id, func)?; Ok(id) diff --git a/noir/noir-repo/aztec_macros/src/transforms/note_interface.rs b/noir/noir-repo/aztec_macros/src/transforms/note_interface.rs index 49525fc2ae1..1a25950e6c8 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/note_interface.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/note_interface.rs @@ -73,6 +73,7 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt generics: vec![], methods: vec![], where_clause: vec![], + is_comptime: false, }; module.impls.push(default_impl.clone()); module.impls.last_mut().unwrap() diff --git a/noir/noir-repo/aztec_macros/src/transforms/storage.rs b/noir/noir-repo/aztec_macros/src/transforms/storage.rs index c302dd87aa5..1c6ef634070 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/storage.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/storage.rs @@ -248,6 +248,7 @@ pub fn generate_storage_implementation( methods: vec![(init, Span::default())], where_clause: vec![], + is_comptime: false, }; module.impls.push(storage_impl); diff --git a/noir/noir-repo/aztec_macros/src/utils/hir_utils.rs b/noir/noir-repo/aztec_macros/src/utils/hir_utils.rs index 7198ed5bd3d..784fb9b5617 100644 --- a/noir/noir-repo/aztec_macros/src/utils/hir_utils.rs +++ b/noir/noir-repo/aztec_macros/src/utils/hir_utils.rs @@ -2,15 +2,15 @@ use acvm::acir::AcirField; use iter_extended::vecmap; use noirc_errors::Location; use noirc_frontend::ast; +use noirc_frontend::elaborator::Elaborator; +use noirc_frontend::hir::def_collector::dc_crate::{ + CollectedItems, UnresolvedFunctions, UnresolvedGlobal, +}; use noirc_frontend::macros_api::{HirExpression, HirLiteral}; use noirc_frontend::node_interner::{NodeInterner, TraitImplKind}; use noirc_frontend::{ graph::CrateId, - hir::{ - def_map::{LocalModuleId, ModuleId}, - resolution::{path_resolver::StandardPathResolver, resolver::Resolver}, - type_check::type_check_func, - }, + hir::def_map::{LocalModuleId, ModuleId}, macros_api::{FileId, HirContext, MacroError, ModuleDefId, StructId}, node_interner::{FuncId, TraitId}, Shared, StructType, Type, @@ -190,24 +190,17 @@ pub fn inject_fn( span: None, })?; - let def_maps = &mut context.def_maps; - - let path_resolver = - StandardPathResolver::new(ModuleId { local_id: module_id, krate: *crate_id }); - - let resolver = Resolver::new(&mut context.def_interner, &path_resolver, def_maps, file_id); - - let (hir_func, meta, _) = resolver.resolve_function(func, func_id); + let mut items = CollectedItems::default(); + let functions = vec![(module_id, func_id, func)]; + let trait_id = None; + items.functions.push(UnresolvedFunctions { file_id, functions, trait_id, self_type: None }); - context.def_interner.push_fn_meta(meta, func_id); - context.def_interner.update_fn(func_id, hir_func); - - let errors = type_check_func(&mut context.def_interner, func_id); + let errors = Elaborator::elaborate(context, *crate_id, items, None); if !errors.is_empty() { return Err(MacroError { primary_message: "Failed to type check autogenerated function".to_owned(), - secondary_message: Some(errors.iter().map(|err| err.to_string()).collect::()), + secondary_message: Some(errors.iter().map(|err| err.0.to_string()).collect::()), span: None, }); } @@ -243,17 +236,10 @@ pub fn inject_global( ) }); - let def_maps = &mut context.def_maps; - - let path_resolver = - StandardPathResolver::new(ModuleId { local_id: module_id, krate: *crate_id }); - - let mut resolver = Resolver::new(&mut context.def_interner, &path_resolver, def_maps, file_id); - - let hir_stmt = resolver.resolve_global_let(global, global_id); + let mut items = CollectedItems::default(); + items.globals.push(UnresolvedGlobal { file_id, module_id, global_id, stmt_def: global }); - let statement_id = context.def_interner.get_global(global_id).let_statement; - context.def_interner.replace_statement(statement_id, hir_stmt); + let _errors = Elaborator::elaborate(context, *crate_id, items, None); } pub fn fully_qualified_note_path(context: &HirContext, note_id: StructId) -> Option { diff --git a/noir/noir-repo/compiler/fm/Cargo.toml b/noir/noir-repo/compiler/fm/Cargo.toml index 1a356d93d89..b48f445be36 100644 --- a/noir/noir-repo/compiler/fm/Cargo.toml +++ b/noir/noir-repo/compiler/fm/Cargo.toml @@ -10,6 +10,7 @@ license.workspace = true [dependencies] codespan-reporting.workspace = true +iter-extended.workspace = true serde.workspace = true [dev-dependencies] diff --git a/noir/noir-repo/compiler/fm/src/lib.rs b/noir/noir-repo/compiler/fm/src/lib.rs index 2e52d802479..37da29fc982 100644 --- a/noir/noir-repo/compiler/fm/src/lib.rs +++ b/noir/noir-repo/compiler/fm/src/lib.rs @@ -7,6 +7,7 @@ mod file_map; pub use file_map::{File, FileId, FileMap, PathString}; +use iter_extended::vecmap; // Re-export for the lsp pub use codespan_reporting::files as codespan_files; @@ -103,6 +104,26 @@ impl FileManager { pub fn name_to_id(&self, file_name: PathBuf) -> Option { self.file_map.get_file_id(&PathString::from_path(file_name)) } + + /// Find a file by its path suffix, e.g. "src/main.nr" is a suffix of + /// "some_dir/package_name/src/main.nr"` + pub fn find_by_path_suffix(&self, suffix: &str) -> Result, Vec> { + let suffix_path: Vec<_> = Path::new(suffix).components().rev().collect(); + let results: Vec<_> = self + .path_to_id + .iter() + .filter(|(path, _id)| { + path.components().rev().zip(suffix_path.iter()).all(|(x, y)| &x == y) + }) + .collect(); + if results.is_empty() { + Ok(None) + } else if results.len() == 1 { + Ok(Some(*results[0].1)) + } else { + Err(vecmap(results, |(path, _id)| path.clone())) + } + } } pub trait NormalizePath { diff --git a/noir/noir-repo/compiler/integration-tests/package.json b/noir/noir-repo/compiler/integration-tests/package.json index 0638fffe547..a88e55b2321 100644 --- a/noir/noir-repo/compiler/integration-tests/package.json +++ b/noir/noir-repo/compiler/integration-tests/package.json @@ -25,7 +25,7 @@ "eslint": "^8.57.0", "eslint-plugin-prettier": "^5.1.3", "ethers": "^6.7.1", - "hardhat": "^2.17.4", + "hardhat": "^2.22.6", "prettier": "3.2.5", "smol-toml": "^1.1.2", "toml": "^3.0.0", diff --git a/noir/noir-repo/compiler/noirc_driver/src/lib.rs b/noir/noir-repo/compiler/noirc_driver/src/lib.rs index 4ba9b85f967..dd774a1eeec 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/lib.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/lib.rs @@ -52,9 +52,9 @@ pub const NOIR_ARTIFACT_VERSION_STRING: &str = #[derive(Args, Clone, Debug, Default)] pub struct CompileOptions { - /// Override the expression width requested by the backend. - #[arg(long, value_parser = parse_expression_width, default_value = "4")] - pub expression_width: ExpressionWidth, + /// Specify the backend expression width that should be targeted + #[arg(long, value_parser = parse_expression_width)] + pub expression_width: Option, /// Force a full recompilation. #[arg(long = "force")] @@ -99,16 +99,17 @@ pub struct CompileOptions { #[arg(long, hide = true)] pub force_brillig: bool, - /// Use the deprecated name resolution & type checking passes instead of the elaborator - #[arg(long, hide = true)] - pub use_legacy: bool, + /// Enable printing results of comptime evaluation: provide a path suffix + /// for the module to debug, e.g. "package_name/src/main.nr" + #[arg(long)] + pub debug_comptime_in_file: Option, /// Outputs the paths to any modified artifacts #[arg(long, hide = true)] pub show_artifact_paths: bool, } -fn parse_expression_width(input: &str) -> Result { +pub fn parse_expression_width(input: &str) -> Result { use std::io::{Error, ErrorKind}; let width = input .parse::() @@ -257,13 +258,13 @@ pub fn check_crate( crate_id: CrateId, deny_warnings: bool, disable_macros: bool, - use_legacy: bool, + debug_comptime_in_file: Option<&str>, ) -> CompilationResult<()> { let macros: &[&dyn MacroProcessor] = if disable_macros { &[] } else { &[&aztec_macros::AztecMacro as &dyn MacroProcessor] }; let mut errors = vec![]; - let diagnostics = CrateDefMap::collect_defs(crate_id, context, use_legacy, macros); + let diagnostics = CrateDefMap::collect_defs(crate_id, context, debug_comptime_in_file, macros); errors.extend(diagnostics.into_iter().map(|(error, file_id)| { let diagnostic = CustomDiagnostic::from(&error); diagnostic.in_file(file_id) @@ -300,7 +301,7 @@ pub fn compile_main( crate_id, options.deny_warnings, options.disable_macros, - options.use_legacy, + options.debug_comptime_in_file.as_deref(), )?; let main = context.get_main_function(&crate_id).ok_or_else(|| { @@ -341,7 +342,7 @@ pub fn compile_contract( crate_id, options.deny_warnings, options.disable_macros, - options.use_legacy, + options.debug_comptime_in_file.as_deref(), )?; // TODO: We probably want to error if contracts is empty diff --git a/noir/noir-repo/compiler/noirc_driver/tests/stdlib_warnings.rs b/noir/noir-repo/compiler/noirc_driver/tests/stdlib_warnings.rs index 47ce893d202..d2474444d13 100644 --- a/noir/noir-repo/compiler/noirc_driver/tests/stdlib_warnings.rs +++ b/noir/noir-repo/compiler/noirc_driver/tests/stdlib_warnings.rs @@ -25,7 +25,7 @@ fn stdlib_does_not_produce_constant_warnings() -> Result<(), ErrorsAndWarnings> let root_crate_id = prepare_crate(&mut context, file_name); let ((), warnings) = - noirc_driver::check_crate(&mut context, root_crate_id, false, false, false)?; + noirc_driver::check_crate(&mut context, root_crate_id, false, false, None)?; assert_eq!(warnings, Vec::new(), "stdlib is producing {} warnings", warnings.len()); diff --git a/noir/noir-repo/compiler/noirc_errors/src/position.rs b/noir/noir-repo/compiler/noirc_errors/src/position.rs index 007ec58ca27..9f9879e1d1b 100644 --- a/noir/noir-repo/compiler/noirc_errors/src/position.rs +++ b/noir/noir-repo/compiler/noirc_errors/src/position.rs @@ -90,6 +90,10 @@ impl Span { self.start() <= other.start() && self.end() >= other.end() } + pub fn intersects(&self, other: &Span) -> bool { + self.end() > other.start() && self.start() < other.end() + } + pub fn is_smaller(&self, other: &Span) -> bool { let self_distance = self.end() - self.start(); let other_distance = other.end() - other.start(); diff --git a/noir/noir-repo/compiler/noirc_errors/src/reporter.rs b/noir/noir-repo/compiler/noirc_errors/src/reporter.rs index ea7c97b1f25..3ce0f268715 100644 --- a/noir/noir-repo/compiler/noirc_errors/src/reporter.rs +++ b/noir/noir-repo/compiler/noirc_errors/src/reporter.rs @@ -19,6 +19,7 @@ pub enum DiagnosticKind { Error, Bug, Warning, + Info, } /// A count of errors that have been already reported to stderr @@ -37,30 +38,57 @@ impl CustomDiagnostic { } } - pub fn simple_error( + fn simple_with_kind( primary_message: String, secondary_message: String, secondary_span: Span, + kind: DiagnosticKind, ) -> CustomDiagnostic { CustomDiagnostic { message: primary_message, secondaries: vec![CustomLabel::new(secondary_message, secondary_span)], notes: Vec::new(), - kind: DiagnosticKind::Error, + kind, } } + pub fn simple_error( + primary_message: String, + secondary_message: String, + secondary_span: Span, + ) -> CustomDiagnostic { + Self::simple_with_kind( + primary_message, + secondary_message, + secondary_span, + DiagnosticKind::Error, + ) + } + pub fn simple_warning( primary_message: String, secondary_message: String, secondary_span: Span, ) -> CustomDiagnostic { - CustomDiagnostic { - message: primary_message, - secondaries: vec![CustomLabel::new(secondary_message, secondary_span)], - notes: Vec::new(), - kind: DiagnosticKind::Warning, - } + Self::simple_with_kind( + primary_message, + secondary_message, + secondary_span, + DiagnosticKind::Warning, + ) + } + + pub fn simple_info( + primary_message: String, + secondary_message: String, + secondary_span: Span, + ) -> CustomDiagnostic { + Self::simple_with_kind( + primary_message, + secondary_message, + secondary_span, + DiagnosticKind::Info, + ) } pub fn simple_bug( @@ -96,6 +124,10 @@ impl CustomDiagnostic { matches!(self.kind, DiagnosticKind::Warning) } + pub fn is_info(&self) -> bool { + matches!(self.kind, DiagnosticKind::Info) + } + pub fn is_bug(&self) -> bool { matches!(self.kind, DiagnosticKind::Bug) } @@ -191,6 +223,7 @@ fn convert_diagnostic( ) -> Diagnostic { let diagnostic = match (cd.kind, deny_warnings) { (DiagnosticKind::Warning, false) => Diagnostic::warning(), + (DiagnosticKind::Info, _) => Diagnostic::note(), (DiagnosticKind::Bug, ..) => Diagnostic::bug(), _ => Diagnostic::error(), }; diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs index aa9cb8cd7a3..8e2b2fb7a29 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs @@ -6,7 +6,7 @@ use acvm::{ use crate::brillig::brillig_ir::{ brillig_variable::{BrilligVariable, BrilligVector, SingleAddrVariable}, debug_show::DebugToString, - BrilligBinaryOp, BrilligContext, + BrilligContext, }; /// Transforms SSA's black box function calls into the corresponding brillig instructions @@ -239,11 +239,10 @@ pub(crate) fn convert_black_box_call( BlackBoxFunc::RecursiveAggregation => {} BlackBoxFunc::BigIntAdd => { if let ( - [BrilligVariable::SingleAddr(lhs), BrilligVariable::SingleAddr(lhs_modulus), BrilligVariable::SingleAddr(rhs), BrilligVariable::SingleAddr(rhs_modulus)], - [BrilligVariable::SingleAddr(output), BrilligVariable::SingleAddr(modulus_id)], + [BrilligVariable::SingleAddr(lhs), BrilligVariable::SingleAddr(_lhs_modulus), BrilligVariable::SingleAddr(rhs), BrilligVariable::SingleAddr(_rhs_modulus)], + [BrilligVariable::SingleAddr(output), BrilligVariable::SingleAddr(_modulus_id)], ) = (function_arguments, function_results) { - prepare_bigint_output(brillig_context, lhs_modulus, rhs_modulus, modulus_id); brillig_context.black_box_op_instruction(BlackBoxOp::BigIntAdd { lhs: lhs.address, rhs: rhs.address, @@ -257,11 +256,10 @@ pub(crate) fn convert_black_box_call( } BlackBoxFunc::BigIntSub => { if let ( - [BrilligVariable::SingleAddr(lhs), BrilligVariable::SingleAddr(lhs_modulus), BrilligVariable::SingleAddr(rhs), BrilligVariable::SingleAddr(rhs_modulus)], - [BrilligVariable::SingleAddr(output), BrilligVariable::SingleAddr(modulus_id)], + [BrilligVariable::SingleAddr(lhs), BrilligVariable::SingleAddr(_lhs_modulus), BrilligVariable::SingleAddr(rhs), BrilligVariable::SingleAddr(_rhs_modulus)], + [BrilligVariable::SingleAddr(output), BrilligVariable::SingleAddr(_modulus_id)], ) = (function_arguments, function_results) { - prepare_bigint_output(brillig_context, lhs_modulus, rhs_modulus, modulus_id); brillig_context.black_box_op_instruction(BlackBoxOp::BigIntSub { lhs: lhs.address, rhs: rhs.address, @@ -275,11 +273,10 @@ pub(crate) fn convert_black_box_call( } BlackBoxFunc::BigIntMul => { if let ( - [BrilligVariable::SingleAddr(lhs), BrilligVariable::SingleAddr(lhs_modulus), BrilligVariable::SingleAddr(rhs), BrilligVariable::SingleAddr(rhs_modulus)], - [BrilligVariable::SingleAddr(output), BrilligVariable::SingleAddr(modulus_id)], + [BrilligVariable::SingleAddr(lhs), BrilligVariable::SingleAddr(_lhs_modulus), BrilligVariable::SingleAddr(rhs), BrilligVariable::SingleAddr(_rhs_modulus)], + [BrilligVariable::SingleAddr(output), BrilligVariable::SingleAddr(_modulus_id)], ) = (function_arguments, function_results) { - prepare_bigint_output(brillig_context, lhs_modulus, rhs_modulus, modulus_id); brillig_context.black_box_op_instruction(BlackBoxOp::BigIntMul { lhs: lhs.address, rhs: rhs.address, @@ -293,11 +290,10 @@ pub(crate) fn convert_black_box_call( } BlackBoxFunc::BigIntDiv => { if let ( - [BrilligVariable::SingleAddr(lhs), BrilligVariable::SingleAddr(lhs_modulus), BrilligVariable::SingleAddr(rhs), BrilligVariable::SingleAddr(rhs_modulus)], - [BrilligVariable::SingleAddr(output), BrilligVariable::SingleAddr(modulus_id)], + [BrilligVariable::SingleAddr(lhs), BrilligVariable::SingleAddr(_lhs_modulus), BrilligVariable::SingleAddr(rhs), BrilligVariable::SingleAddr(_rhs_modulus)], + [BrilligVariable::SingleAddr(output), BrilligVariable::SingleAddr(_modulus_id)], ) = (function_arguments, function_results) { - prepare_bigint_output(brillig_context, lhs_modulus, rhs_modulus, modulus_id); brillig_context.black_box_op_instruction(BlackBoxOp::BigIntDiv { lhs: lhs.address, rhs: rhs.address, @@ -416,27 +412,3 @@ fn convert_array_or_vector( ), } } - -fn prepare_bigint_output( - brillig_context: &mut BrilligContext, - lhs_modulus: &SingleAddrVariable, - rhs_modulus: &SingleAddrVariable, - modulus_id: &SingleAddrVariable, -) { - // Check moduli - let condition = brillig_context.allocate_register(); - let condition_adr = SingleAddrVariable { address: condition, bit_size: 1 }; - brillig_context.binary_instruction( - *lhs_modulus, - *rhs_modulus, - condition_adr, - BrilligBinaryOp::Equals, - ); - brillig_context.codegen_constrain( - condition_adr, - Some("moduli should be identical in BigInt operation".to_string()), - ); - brillig_context.deallocate_register(condition); - - brillig_context.mov_instruction(modulus_id.address, lhs_modulus.address); -} diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/errors.rs b/noir/noir-repo/compiler/noirc_evaluator/src/errors.rs index 2b328898257..2c7ec0f8e1a 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/errors.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/errors.rs @@ -19,8 +19,6 @@ use serde::{Deserialize, Serialize}; pub enum RuntimeError { #[error(transparent)] InternalError(#[from] InternalError), - #[error("Index out of bounds, array has size {array_size}, but index was {index}")] - IndexOutOfBounds { index: usize, array_size: usize, call_stack: CallStack }, #[error("Range constraint of {num_bits} bits is too large for the Field size")] InvalidRangeConstraint { num_bits: u32, call_stack: CallStack }, #[error("The value `{value:?}` cannot fit into `{typ}` which has range `{range}`")] @@ -145,7 +143,6 @@ impl RuntimeError { | InternalError::UndeclaredAcirVar { call_stack } | InternalError::Unexpected { call_stack, .. }, ) - | RuntimeError::IndexOutOfBounds { call_stack, .. } | RuntimeError::InvalidRangeConstraint { call_stack, .. } | RuntimeError::TypeConversion { call_stack, .. } | RuntimeError::UnInitialized { call_stack, .. } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs index 820374df9c1..81327cec013 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs @@ -190,12 +190,19 @@ pub fn create_program( let recursive = program.recursive; let ArtifactsAndWarnings((generated_acirs, generated_brillig, error_types), ssa_level_warnings) = optimize_into_acir(program, options)?; - assert_eq!( - generated_acirs.len(), - func_sigs.len(), - "The generated ACIRs should match the supplied function signatures" - ); - + if options.force_brillig_output { + assert_eq!( + generated_acirs.len(), + 1, + "Only the main ACIR is expected when forcing Brillig output" + ); + } else { + assert_eq!( + generated_acirs.len(), + func_sigs.len(), + "The generated ACIRs should match the supplied function signatures" + ); + } let mut program_artifact = SsaProgramArtifact::new(generated_brillig, error_types); // Add warnings collected at the Ssa stage 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 9d29d1d24d6..44467677154 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 @@ -294,24 +294,7 @@ impl GeneratedAcir { outputs: (outputs[0], outputs[1], outputs[2]), }, BlackBoxFunc::Keccak256 => { - let var_message_size = match inputs.to_vec().pop() { - Some(var_message_size) => var_message_size[0], - None => { - return Err(InternalError::MissingArg { - name: "".to_string(), - arg: "message_size".to_string(), - call_stack: self.call_stack.clone(), - }); - } - }; - - BlackBoxFuncCall::Keccak256 { - inputs: inputs[0].clone(), - var_message_size, - outputs: outputs - .try_into() - .expect("Compiler should generate correct size outputs"), - } + unreachable!("unexpected BlackBox {}", func_name.to_string()) } BlackBoxFunc::Keccakf1600 => BlackBoxFuncCall::Keccakf1600 { inputs: inputs[0] diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index cfcc7a9a997..1bdc9aaf4eb 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -975,6 +975,8 @@ impl<'a> Context<'a> { .into()) } }; + // Ensure that array id is fully resolved. + let array = dfg.resolve(array); let array_id = dfg.resolve(array); let array_typ = dfg.type_of_value(array_id); @@ -992,7 +994,6 @@ impl<'a> Context<'a> { // If we find one, we will use it when computing the index under the enable_side_effect predicate // If not, array_get(..) will use a fallback costing one multiplication in the worst case. // cf. https://github.com/noir-lang/noir/pull/4971 - // For simplicity we compute the offset only for simple arrays let is_simple_array = dfg.instruction_results(instruction).len() == 1 && can_omit_element_sizes_array(&array_typ); @@ -1085,45 +1086,30 @@ impl<'a> Context<'a> { } }; - let side_effects_always_enabled = - self.acir_context.is_constant_one(&self.current_side_effects_enabled_var); - let index_out_of_bounds = index >= array_size; - - // Note that the value of `side_effects_always_enabled` doesn't affect the value which we return here for valid - // indices, just whether we return an error for invalid indices at compile time or defer until execution. - match (side_effects_always_enabled, index_out_of_bounds) { - (true, false) => { - let value = match store_value { - Some(store_value) => AcirValue::Array(array.update(index, store_value)), - None => array[index].clone(), - }; + if index >= array_size { + return Ok(false); + } + + if let Some(store_value) = store_value { + let side_effects_always_enabled = + self.acir_context.is_constant_one(&self.current_side_effects_enabled_var); + if side_effects_always_enabled { + // If we know that this write will always occur then we can perform it at compile time. + let value = AcirValue::Array(array.update(index, store_value)); self.define_result(dfg, instruction, value); Ok(true) + } else { + // If a predicate is applied however we must wait until runtime. + Ok(false) } - (false, false) => { - if store_value.is_none() { - // If there is a predicate and the index is not out of range, we can optimistically perform the - // read at compile time as if the predicate is true. - // - // This is as if the predicate is false, any side-effects will be disabled so the value returned - // will not affect the rest of execution. - self.define_result(dfg, instruction, array[index].clone()); - Ok(true) - } else { - // We do not do this for a array writes however. - Ok(false) - } - } - - // Report the error if side effects are enabled. - (true, true) => { - let call_stack = self.acir_context.get_call_stack(); - Err(RuntimeError::IndexOutOfBounds { index, array_size, call_stack }) - } - // Index is out of bounds but predicate may result in this array operation being skipped - // so we don't return an error now. - (false, true) => Ok(false), + } else { + // If the index is not out of range, we can optimistically perform the read at compile time + // as if the predicate were true. This is as if the predicate were to resolve to false then + // the result should not affect the rest of circuit execution. + let value = array[index].clone(); + self.define_result(dfg, instruction, value); + Ok(true) } } @@ -1138,13 +1124,14 @@ impl<'a> Context<'a> { /// It is a dummy value because in the case of a false predicate, the value stored at the requested index will be itself. fn convert_array_operation_inputs( &mut self, - array: ValueId, + array_id: ValueId, dfg: &DataFlowGraph, index: ValueId, store_value: Option, offset: usize, ) -> Result<(AcirVar, Option), RuntimeError> { - let (array_id, array_typ, block_id) = self.check_array_is_initialized(array, dfg)?; + let array_typ = dfg.type_of_value(array_id); + let block_id = self.ensure_array_is_initialized(array_id, dfg)?; let index_var = self.convert_numeric_value(index, dfg)?; let index_var = self.get_flattened_index(&array_typ, array_id, index_var, dfg)?; @@ -1263,22 +1250,22 @@ impl<'a> Context<'a> { dfg: &DataFlowGraph, mut index_side_effect: bool, ) -> Result { - let (array_id, _, block_id) = self.check_array_is_initialized(array, dfg)?; + let block_id = self.ensure_array_is_initialized(array, dfg)?; let results = dfg.instruction_results(instruction); let res_typ = dfg.type_of_value(results[0]); // Get operations to call-data parameters are replaced by a get to the call-data-bus array if let Some(call_data) = self.data_bus.call_data { - if self.data_bus.call_data_map.contains_key(&array_id) { + if self.data_bus.call_data_map.contains_key(&array) { // TODO: the block_id of call-data must be notified to the backend // TODO: should we do the same for return-data? let type_size = res_typ.flattened_size(); let type_size = self.acir_context.add_constant(FieldElement::from(type_size as i128)); let offset = self.acir_context.mul_var(var_index, type_size)?; - let bus_index = self.acir_context.add_constant(FieldElement::from( - self.data_bus.call_data_map[&array_id] as i128, - )); + let bus_index = self + .acir_context + .add_constant(FieldElement::from(self.data_bus.call_data_map[&array] as i128)); let new_index = self.acir_context.add_var(offset, bus_index)?; return self.array_get(instruction, call_data, new_index, dfg, index_side_effect); } @@ -1292,8 +1279,7 @@ impl<'a> Context<'a> { let mut value = self.array_get_value(&res_typ, block_id, &mut var_index)?; if let AcirValue::Var(value_var, typ) = &value { - let array_id = dfg.resolve(array_id); - let array_typ = dfg.type_of_value(array_id); + let array_typ = dfg.type_of_value(array); if let (Type::Numeric(numeric_type), AcirType::NumericType(num)) = (array_typ.first(), typ) { @@ -1377,7 +1363,7 @@ impl<'a> Context<'a> { } }; - let (array_id, array_typ, block_id) = self.check_array_is_initialized(array, dfg)?; + let block_id = self.ensure_array_is_initialized(array, dfg)?; // Every array has a length in its type, so we fetch that from // the SSA IR. @@ -1386,10 +1372,11 @@ impl<'a> Context<'a> { // However, this size is simply the capacity of a slice. The capacity is dependent upon the witness // and may contain data for which we want to restrict access. The true slice length is tracked in a // a separate SSA value and restrictions on slice indices should be generated elsewhere in the SSA. + let array_typ = dfg.type_of_value(array); let array_len = if !array_typ.contains_slice_element() { array_typ.flattened_size() } else { - self.flattened_slice_size(array_id, dfg) + self.flattened_slice_size(array, dfg) }; // Since array_set creates a new array, we create a new block ID for this @@ -1412,18 +1399,13 @@ impl<'a> Context<'a> { self.array_set_value(&store_value, result_block_id, &mut var_index)?; let element_type_sizes = if !can_omit_element_sizes_array(&array_typ) { - let acir_value = self.convert_value(array_id, dfg); - Some(self.init_element_type_sizes_array( - &array_typ, - array_id, - Some(&acir_value), - dfg, - )?) + let acir_value = self.convert_value(array, dfg); + Some(self.init_element_type_sizes_array(&array_typ, array, Some(&acir_value), dfg)?) } else { None }; - let value_types = self.convert_value(array_id, dfg).flat_numeric_types(); + let value_types = self.convert_value(array, dfg).flat_numeric_types(); // Compiler sanity check assert_eq!(value_types.len(), array_len, "ICE: The length of the flattened type array should match the length of the dynamic array"); @@ -1469,37 +1451,33 @@ impl<'a> Context<'a> { Ok(()) } - fn check_array_is_initialized( + fn ensure_array_is_initialized( &mut self, array: ValueId, dfg: &DataFlowGraph, - ) -> Result<(ValueId, Type, BlockId), RuntimeError> { - // Fetch the internal SSA ID for the array - let array_id = dfg.resolve(array); - - let array_typ = dfg.type_of_value(array_id); - + ) -> Result { // Use the SSA ID to get or create its block ID - let block_id = self.block_id(&array_id); + let block_id = self.block_id(&array); // Check if the array has already been initialized in ACIR gen // if not, we initialize it using the values from SSA let already_initialized = self.initialized_arrays.contains(&block_id); if !already_initialized { - let value = &dfg[array_id]; + let value = &dfg[array]; match value { Value::Array { .. } | Value::Instruction { .. } => { - let value = self.convert_value(array_id, dfg); + let value = self.convert_value(array, dfg); + let array_typ = dfg.type_of_value(array); let len = if !array_typ.contains_slice_element() { array_typ.flattened_size() } else { - self.flattened_slice_size(array_id, dfg) + self.flattened_slice_size(array, dfg) }; self.initialize_array(block_id, len, Some(value))?; } _ => { return Err(InternalError::General { - message: format!("Array {array_id} should be initialized"), + message: format!("Array {array} should be initialized"), call_stack: self.acir_context.get_call_stack(), } .into()); @@ -1507,7 +1485,7 @@ impl<'a> Context<'a> { } } - Ok((array_id, array_typ, block_id)) + Ok(block_id) } fn init_element_type_sizes_array( @@ -1761,7 +1739,7 @@ impl<'a> Context<'a> { /// Converts an SSA terminator's return values into their ACIR representations fn get_num_return_witnesses( - &mut self, + &self, terminator: &TerminatorInstruction, dfg: &DataFlowGraph, ) -> usize { @@ -1815,7 +1793,7 @@ impl<'a> Context<'a> { has_constant_return |= self.acir_context.is_constant(&acir_var); if is_databus { // We do not return value for the data bus. - self.check_array_is_initialized( + self.ensure_array_is_initialized( self.data_bus.return_data.expect( "`is_databus == true` implies `data_bus.return_data` is `Some`", ), @@ -2182,8 +2160,9 @@ impl<'a> Context<'a> { Ok(vec![AcirValue::Var(self.acir_context.add_constant(len), AcirType::field())]) } Intrinsic::AsSlice => { - let (slice_contents, slice_typ, block_id) = - self.check_array_is_initialized(arguments[0], dfg)?; + let slice_contents = arguments[0]; + let slice_typ = dfg.type_of_value(slice_contents); + let block_id = self.ensure_array_is_initialized(slice_contents, dfg)?; assert!(!slice_typ.is_nested_slice(), "ICE: Nested slice used in ACIR generation"); let result_block_id = self.block_id(&result_ids[1]); @@ -2227,8 +2206,9 @@ impl<'a> Context<'a> { Intrinsic::SlicePushBack => { // arguments = [slice_length, slice_contents, ...elements_to_push] let slice_length = self.convert_value(arguments[0], dfg).into_var()?; - let (slice_contents, slice_typ, _) = - self.check_array_is_initialized(arguments[1], dfg)?; + let slice_contents = arguments[1]; + let slice_typ = dfg.type_of_value(slice_contents); + assert!(!slice_typ.is_nested_slice(), "ICE: Nested slice used in ACIR generation"); let slice = self.convert_value(slice_contents, dfg); @@ -2294,9 +2274,8 @@ impl<'a> Context<'a> { Intrinsic::SlicePushFront => { // arguments = [slice_length, slice_contents, ...elements_to_push] let slice_length = self.convert_value(arguments[0], dfg).into_var()?; - - let (slice_contents, slice_typ, _) = - self.check_array_is_initialized(arguments[1], dfg)?; + let slice_contents = arguments[1]; + let slice_typ = dfg.type_of_value(slice_contents); assert!(!slice_typ.is_nested_slice(), "ICE: Nested slice used in ACIR generation"); let slice: AcirValue = self.convert_value(slice_contents, dfg); @@ -2359,6 +2338,7 @@ impl<'a> Context<'a> { Intrinsic::SlicePopBack => { // arguments = [slice_length, slice_contents] let slice_length = self.convert_value(arguments[0], dfg).into_var()?; + let slice_contents = arguments[1]; let one = self.acir_context.add_constant(FieldElement::one()); let new_slice_length = self.acir_context.sub_var(slice_length, one)?; @@ -2367,8 +2347,8 @@ impl<'a> Context<'a> { // the elements stored at that index will no longer be able to be accessed. let mut var_index = new_slice_length; - let (slice_contents, slice_typ, block_id) = - self.check_array_is_initialized(arguments[1], dfg)?; + let slice_typ = dfg.type_of_value(slice_contents); + let block_id = self.ensure_array_is_initialized(slice_contents, dfg)?; assert!(!slice_typ.is_nested_slice(), "ICE: Nested slice used in ACIR generation"); let mut popped_elements = Vec::new(); @@ -2393,9 +2373,11 @@ impl<'a> Context<'a> { Intrinsic::SlicePopFront => { // arguments = [slice_length, slice_contents] let slice_length = self.convert_value(arguments[0], dfg).into_var()?; + let slice_contents = arguments[1]; + + let slice_typ = dfg.type_of_value(slice_contents); + let block_id = self.ensure_array_is_initialized(slice_contents, dfg)?; - let (slice_contents, slice_typ, block_id) = - self.check_array_is_initialized(arguments[1], dfg)?; assert!(!slice_typ.is_nested_slice(), "ICE: Nested slice used in ACIR generation"); let one = self.acir_context.add_constant(FieldElement::one()); @@ -2434,9 +2416,11 @@ impl<'a> Context<'a> { Intrinsic::SliceInsert => { // arguments = [slice_length, slice_contents, insert_index, ...elements_to_insert] let slice_length = self.convert_value(arguments[0], dfg).into_var()?; + let slice_contents = arguments[1]; + + let slice_typ = dfg.type_of_value(slice_contents); + let block_id = self.ensure_array_is_initialized(slice_contents, dfg)?; - let (slice_contents, slice_typ, block_id) = - self.check_array_is_initialized(arguments[1], dfg)?; assert!(!slice_typ.is_nested_slice(), "ICE: Nested slice used in ACIR generation"); let slice = self.convert_value(slice_contents, dfg); @@ -2573,9 +2557,11 @@ impl<'a> Context<'a> { Intrinsic::SliceRemove => { // arguments = [slice_length, slice_contents, remove_index] let slice_length = self.convert_value(arguments[0], dfg).into_var()?; + let slice_contents = arguments[1]; + + let slice_typ = dfg.type_of_value(slice_contents); + let block_id = self.ensure_array_is_initialized(slice_contents, dfg)?; - let (slice_contents, slice_typ, block_id) = - self.check_array_is_initialized(arguments[1], dfg)?; assert!(!slice_typ.is_nested_slice(), "ICE: Nested slice used in ACIR generation"); let slice = self.convert_value(slice_contents, dfg); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index b08283a9ceb..8cbae732ef9 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -288,25 +288,33 @@ impl Instruction { } /// Indicates if the instruction can be safely replaced with the results of another instruction with the same inputs. - pub(crate) fn can_be_deduplicated(&self, dfg: &DataFlowGraph) -> bool { + /// If `deduplicate_with_predicate` is set, we assume we're deduplicating with the instruction + /// and its predicate, rather than just the instruction. Setting this means instructions that + /// rely on predicates can be deduplicated as well. + pub(crate) fn can_be_deduplicated( + &self, + dfg: &DataFlowGraph, + deduplicate_with_predicate: bool, + ) -> bool { use Instruction::*; match self { // These either have side-effects or interact with memory - Constrain(..) - | EnableSideEffects { .. } + EnableSideEffects { .. } | Allocate | Load { .. } | Store { .. } | IncrementRc { .. } - | DecrementRc { .. } - | RangeCheck { .. } => false, + | DecrementRc { .. } => false, Call { func, .. } => match dfg[*func] { Value::Intrinsic(intrinsic) => !intrinsic.has_side_effects(), _ => false, }, + // We can deduplicate these instructions if we know the predicate is also the same. + Constrain(..) | RangeCheck { .. } => deduplicate_with_predicate, + // These can have different behavior depending on the EnableSideEffectsIf context. // Replacing them with a similar instruction potentially enables replacing an instruction // with one that was disabled. See @@ -317,7 +325,9 @@ impl Instruction { | Truncate { .. } | IfElse { .. } | ArrayGet { .. } - | ArraySet { .. } => !self.requires_acir_gen_predicate(dfg), + | ArraySet { .. } => { + deduplicate_with_predicate || !self.requires_acir_gen_predicate(dfg) + } } } @@ -372,7 +382,7 @@ impl Instruction { } /// If true the instruction will depends on enable_side_effects context during acir-gen - fn requires_acir_gen_predicate(&self, dfg: &DataFlowGraph) -> bool { + pub(crate) fn requires_acir_gen_predicate(&self, dfg: &DataFlowGraph) -> bool { match self { Instruction::Binary(binary) if matches!(binary.operator, BinaryOp::Div | BinaryOp::Mod) => diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs index 281ab7c3057..ad01edbd0b2 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -461,28 +461,37 @@ fn simplify_black_box_func( BlackBoxFunc::SHA256 => simplify_hash(dfg, arguments, acvm::blackbox_solver::sha256), BlackBoxFunc::Blake2s => simplify_hash(dfg, arguments, acvm::blackbox_solver::blake2s), BlackBoxFunc::Blake3 => simplify_hash(dfg, arguments, acvm::blackbox_solver::blake3), - BlackBoxFunc::PedersenCommitment - | BlackBoxFunc::PedersenHash - | BlackBoxFunc::Keccakf1600 => SimplifyResult::None, //TODO(Guillaume) - BlackBoxFunc::Keccak256 => { - match (dfg.get_array_constant(arguments[0]), dfg.get_numeric_constant(arguments[1])) { - (Some((input, _)), Some(num_bytes)) if array_is_constant(dfg, &input) => { - let input_bytes: Vec = to_u8_vec(dfg, input); - - let num_bytes = num_bytes.to_u128() as usize; - let truncated_input_bytes = &input_bytes[0..num_bytes]; - let hash = acvm::blackbox_solver::keccak256(truncated_input_bytes) - .expect("Rust solvable black box function should not fail"); - - let hash_values = - vecmap(hash, |byte| FieldElement::from_be_bytes_reduce(&[byte])); - - let result_array = make_constant_array(dfg, hash_values, Type::unsigned(8)); + BlackBoxFunc::PedersenCommitment | BlackBoxFunc::PedersenHash => SimplifyResult::None, + BlackBoxFunc::Keccakf1600 => { + if let Some((array_input, _)) = dfg.get_array_constant(arguments[0]) { + if array_is_constant(dfg, &array_input) { + let const_input: Vec = array_input + .iter() + .map(|id| { + let field = dfg + .get_numeric_constant(*id) + .expect("value id from array should point at constant"); + field.to_u128() as u64 + }) + .collect(); + + let state = acvm::blackbox_solver::keccakf1600( + const_input.try_into().expect("Keccakf1600 input should have length of 25"), + ) + .expect("Rust solvable black box function should not fail"); + let state_values = vecmap(state, |x| FieldElement::from(x as u128)); + let result_array = make_constant_array(dfg, state_values, Type::unsigned(64)); SimplifyResult::SimplifiedTo(result_array) + } else { + SimplifyResult::None } - _ => SimplifyResult::None, + } else { + SimplifyResult::None } } + BlackBoxFunc::Keccak256 => { + unreachable!("Keccak256 should have been replaced by calls to Keccakf1600") + } BlackBoxFunc::Poseidon2Permutation => SimplifyResult::None, //TODO(Guillaume) BlackBoxFunc::EcdsaSecp256k1 => { simplify_signature(dfg, arguments, acvm::blackbox_solver::ecdsa_secp256k1_verify) diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs index d7cd366e9af..160105d27e6 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs @@ -87,12 +87,16 @@ struct Context { block_queue: Vec, } +/// HashMap from (Instruction, side_effects_enabled_var) to the results of the instruction. +/// Stored as a two-level map to avoid cloning Instructions during the `.get` call. +type InstructionResultCache = HashMap, Vec>>; + impl Context { fn fold_constants_in_block(&mut self, function: &mut Function, block: BasicBlockId) { let instructions = function.dfg[block].take_instructions(); // Cache of instructions without any side-effects along with their outputs. - let mut cached_instruction_results: HashMap> = HashMap::default(); + let mut cached_instruction_results = HashMap::default(); // Contains sets of values which are constrained to be equivalent to each other. // @@ -124,7 +128,7 @@ impl Context { dfg: &mut DataFlowGraph, block: BasicBlockId, id: InstructionId, - instruction_result_cache: &mut HashMap>, + instruction_result_cache: &mut InstructionResultCache, constraint_simplification_mappings: &mut HashMap>, side_effects_enabled_var: &mut ValueId, ) { @@ -134,7 +138,9 @@ impl Context { let old_results = dfg.instruction_results(id).to_vec(); // If a copy of this instruction exists earlier in the block, then reuse the previous results. - if let Some(cached_results) = instruction_result_cache.get(&instruction) { + if let Some(cached_results) = + Self::get_cached(dfg, instruction_result_cache, &instruction, *side_effects_enabled_var) + { Self::replace_result_ids(dfg, &old_results, cached_results); return; } @@ -150,6 +156,7 @@ impl Context { dfg, instruction_result_cache, constraint_simplification_mapping, + *side_effects_enabled_var, ); // If we just inserted an `Instruction::EnableSideEffects`, we need to update `side_effects_enabled_var` @@ -224,8 +231,9 @@ impl Context { instruction: Instruction, instruction_results: Vec, dfg: &DataFlowGraph, - instruction_result_cache: &mut HashMap>, + instruction_result_cache: &mut InstructionResultCache, constraint_simplification_mapping: &mut HashMap, + side_effects_enabled_var: ValueId, ) { if self.use_constraint_info { // If the instruction was a constraint, then create a link between the two `ValueId`s @@ -258,8 +266,15 @@ impl Context { // If the instruction doesn't have side-effects and if it won't interact with enable_side_effects during acir_gen, // we cache the results so we can reuse them if the same instruction appears again later in the block. - if instruction.can_be_deduplicated(dfg) { - instruction_result_cache.insert(instruction, instruction_results); + if instruction.can_be_deduplicated(dfg, self.use_constraint_info) { + let use_predicate = + self.use_constraint_info && instruction.requires_acir_gen_predicate(dfg); + let predicate = use_predicate.then_some(side_effects_enabled_var); + + instruction_result_cache + .entry(instruction) + .or_default() + .insert(predicate, instruction_results); } } @@ -273,6 +288,25 @@ impl Context { dfg.set_value_from_id(*old_result, *new_result); } } + + fn get_cached<'a>( + dfg: &DataFlowGraph, + instruction_result_cache: &'a mut InstructionResultCache, + instruction: &Instruction, + side_effects_enabled_var: ValueId, + ) -> Option<&'a Vec> { + let results_for_instruction = instruction_result_cache.get(instruction); + + // See if there's a cached version with no predicate first + if let Some(results) = results_for_instruction.and_then(|map| map.get(&None)) { + return Some(results); + } + + let predicate = + instruction.requires_acir_gen_predicate(dfg).then_some(side_effects_enabled_var); + + results_for_instruction.and_then(|map| map.get(&predicate)) + } } #[cfg(test)] @@ -725,4 +759,86 @@ mod test { let ending_instruction_count = instructions.len(); assert_eq!(starting_instruction_count, ending_instruction_count); } + + #[test] + fn deduplicate_instructions_with_predicates() { + // fn main f0 { + // b0(v0: bool, v1: bool, v2: [u32; 2]): + // enable_side_effects v0 + // v3 = array_get v2, index u32 0 + // v4 = array_set v2, index u32 1, value: u32 2 + // v5 = array_get v4, index u32 0 + // constrain_eq v3, v5 + // enable_side_effects v1 + // v6 = array_get v2, index u32 0 + // v7 = array_set v2, index u32 1, value: u32 2 + // v8 = array_get v7, index u32 0 + // constrain_eq v6, v8 + // enable_side_effects v0 + // v9 = array_get v2, index u32 0 + // v10 = array_set v2, index u32 1, value: u32 2 + // v11 = array_get v10, index u32 0 + // constrain_eq v9, v11 + // } + let main_id = Id::test_new(0); + + // Compiling main + let mut builder = FunctionBuilder::new("main".into(), main_id); + + let v0 = builder.add_parameter(Type::bool()); + let v1 = builder.add_parameter(Type::bool()); + let v2 = builder.add_parameter(Type::Array(Rc::new(vec![Type::field()]), 2)); + + let zero = builder.numeric_constant(0u128, Type::length_type()); + let one = builder.numeric_constant(1u128, Type::length_type()); + let two = builder.numeric_constant(2u128, Type::length_type()); + + builder.insert_enable_side_effects_if(v0); + let v3 = builder.insert_array_get(v2, zero, Type::length_type()); + let v4 = builder.insert_array_set(v2, one, two); + let v5 = builder.insert_array_get(v4, zero, Type::length_type()); + builder.insert_constrain(v3, v5, None); + + builder.insert_enable_side_effects_if(v1); + let v6 = builder.insert_array_get(v2, zero, Type::length_type()); + let v7 = builder.insert_array_set(v2, one, two); + let v8 = builder.insert_array_get(v7, zero, Type::length_type()); + builder.insert_constrain(v6, v8, None); + + // We expect all these instructions after the 'enable side effects' instruction to be removed. + builder.insert_enable_side_effects_if(v0); + let v9 = builder.insert_array_get(v2, zero, Type::length_type()); + let v10 = builder.insert_array_set(v2, one, two); + let v11 = builder.insert_array_get(v10, zero, Type::length_type()); + builder.insert_constrain(v9, v11, None); + + let ssa = builder.finish(); + println!("{ssa}"); + + let main = ssa.main(); + let instructions = main.dfg[main.entry_block()].instructions(); + assert_eq!(instructions.len(), 15); + + // Expected output: + // + // fn main f0 { + // b0(v0: bool, v1: bool, v2: [Field; 2]): + // enable_side_effects v0 + // v3 = array_get v2, index Field 0 + // v4 = array_set v2, index Field 1, value: Field 2 + // v5 = array_get v4, index Field 0 + // constrain_eq v3, v5 + // enable_side_effects v1 + // v7 = array_set v2, index Field 1, value: Field 2 + // v8 = array_get v7, index Field 0 + // constrain_eq v3, v8 + // enable_side_effects v0 + // } + let ssa = ssa.fold_constants_using_constraints(); + println!("{ssa}"); + + let main = ssa.main(); + let instructions = main.dfg[main.entry_block()].instructions(); + assert_eq!(instructions.len(), 10); + } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index c7ce3aaa155..4deb21ef712 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -752,27 +752,24 @@ impl<'f> Context<'f> { Instruction::Call { func, arguments } } Value::Intrinsic(Intrinsic::BlackBox(BlackBoxFunc::MultiScalarMul)) => { - let mut array_with_predicate = im::Vector::new(); - let array_typ; - if let Value::Array { array, typ } = - &self.inserter.function.dfg[arguments[0]] - { - array_typ = typ.clone(); - for (i, value) in array.clone().iter().enumerate() { - if i % 3 == 2 { - array_with_predicate.push_back(self.var_or_one( - *value, - condition, - call_stack.clone(), - )); - } else { - array_with_predicate.push_back(*value); - } - } + let points_array_idx = if matches!( + self.inserter.function.dfg[arguments[0]], + Value::Array { .. } + ) { + 0 } else { - unreachable!(); - } - arguments[0] = + // if the first argument is not an array, we assume it is a slice + // which means the array is the second argument + 1 + }; + let (array_with_predicate, array_typ) = self + .apply_predicate_to_msm_argument( + arguments[points_array_idx], + condition, + call_stack.clone(), + ); + + arguments[points_array_idx] = self.inserter.function.dfg.make_array(array_with_predicate, array_typ); Instruction::Call { func, arguments } } @@ -785,6 +782,40 @@ impl<'f> Context<'f> { } } + /// When a MSM is done under a predicate, we need to apply the predicate + /// to the is_infinity property of the input points in order to ensure + /// that the points will be on the curve no matter what. + fn apply_predicate_to_msm_argument( + &mut self, + argument: ValueId, + predicate: ValueId, + call_stack: CallStack, + ) -> (im::Vector, Type) { + let array_typ; + let mut array_with_predicate = im::Vector::new(); + if let Value::Array { array, typ } = &self.inserter.function.dfg[argument] { + array_typ = typ.clone(); + for (i, value) in array.clone().iter().enumerate() { + if i % 3 == 2 { + array_with_predicate.push_back(self.var_or_one( + *value, + predicate, + call_stack.clone(), + )); + } else { + array_with_predicate.push_back(*value); + } + } + } else { + unreachable!( + "Expected an array, got {}", + &self.inserter.function.dfg.type_of_value(argument) + ); + }; + + (array_with_predicate, array_typ) + } + // Computes: if condition { var } else { 1 } fn var_or_one(&mut self, var: ValueId, condition: ValueId, call_stack: CallStack) -> ValueId { let field = self.insert_instruction( @@ -1381,7 +1412,7 @@ mod test { // Tests that it does not simplify a true constraint an always-false constraint // acir(inline) fn main f1 { // b0(v0: [u8; 2]): - // v4 = call keccak256(v0, u8 2) + // v4 = call sha256(v0, u8 2) // v5 = array_get v4, index u8 0 // v6 = cast v5 as u32 // v8 = truncate v6 to 1 bits, max_bit_size: 32 @@ -1417,7 +1448,7 @@ mod test { let two = builder.numeric_constant(2_u128, Type::unsigned(8)); let keccak = - builder.import_intrinsic_id(Intrinsic::BlackBox(acvm::acir::BlackBoxFunc::Keccak256)); + builder.import_intrinsic_id(Intrinsic::BlackBox(acvm::acir::BlackBoxFunc::SHA256)); let v4 = builder.insert_call(keccak, vec![array, two], vec![Type::Array(element_type, 32)])[0]; let v5 = builder.insert_array_get(v4, zero, Type::unsigned(8)); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs index e013546f14a..8e55debec1d 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs @@ -5,7 +5,7 @@ use acvm::{acir::AcirField, FieldElement}; use iter_extended::vecmap; use noirc_errors::Location; use noirc_frontend::ast::{BinaryOpKind, Signedness}; -use noirc_frontend::monomorphization::ast::{self, LocalId, Parameters}; +use noirc_frontend::monomorphization::ast::{self, InlineType, LocalId, Parameters}; use noirc_frontend::monomorphization::ast::{FuncId, Program}; use crate::errors::RuntimeError; @@ -121,9 +121,14 @@ impl<'a> FunctionContext<'a> { /// /// Note that the previous function cannot be resumed after calling this. Developers should /// avoid calling new_function until the previous function is completely finished with ssa-gen. - pub(super) fn new_function(&mut self, id: IrFunctionId, func: &ast::Function) { + pub(super) fn new_function( + &mut self, + id: IrFunctionId, + func: &ast::Function, + force_brillig_runtime: bool, + ) { self.definitions.clear(); - if func.unconstrained { + if func.unconstrained || (force_brillig_runtime && func.inline_type != InlineType::Inline) { self.builder.new_brillig_function(func.name.clone(), id); } else { self.builder.new_function(func.name.clone(), id, func.inline_type); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs index afe44881830..abd251b008f 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs @@ -111,7 +111,7 @@ pub(crate) fn generate_ssa( // to generate SSA for each function used within the program. while let Some((src_function_id, dest_id)) = context.pop_next_function_in_queue() { let function = &context.program[src_function_id]; - function_context.new_function(dest_id, function); + function_context.new_function(dest_id, function, force_brillig_runtime); function_context.codegen_function_body(&function.body)?; } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs index 87cc7990753..057daa2bdde 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs @@ -800,12 +800,8 @@ impl FunctionDefinition { return_visibility: Visibility::Private, } } -} - -impl Display for FunctionDefinition { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!(f, "{:?}", self.attributes)?; + pub fn signature(&self) -> String { let parameters = vecmap(&self.parameters, |Param { visibility, pattern, typ, span: _ }| { if *visibility == Visibility::Public { format!("{pattern}: {visibility} {typ}") @@ -827,15 +823,14 @@ impl Display for FunctionDefinition { format!(" -> {}", self.return_type) }; - write!( - f, - "fn {}({}){}{} {}", - self.name, - parameters.join(", "), - return_type, - where_clause_str, - self.body - ) + format!("fn {}({}){}{}", self.name, parameters.join(", "), return_type, where_clause_str) + } +} + +impl Display for FunctionDefinition { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "{:?}", self.attributes)?; + write!(f, "fn {} {}", self.signature(), self.body) } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs index 3e6a140ff93..b41efebc905 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs @@ -10,7 +10,7 @@ use super::{ BlockExpression, Expression, ExpressionKind, IndexExpression, MemberAccessExpression, MethodCallExpression, UnresolvedType, }; -use crate::hir::resolution::resolver::SELF_TYPE_NAME; +use crate::elaborator::types::SELF_TYPE_NAME; use crate::lexer::token::SpannedToken; use crate::macros_api::SecondaryAttribute; use crate::parser::{ParserError, ParserErrorReason}; @@ -299,6 +299,7 @@ pub enum PathKind { Crate, Dep, Plain, + Super, } #[derive(Debug, PartialEq, Eq, Clone)] @@ -748,6 +749,7 @@ impl Display for PathKind { match self { PathKind::Crate => write!(f, "crate"), PathKind::Dep => write!(f, "dep"), + PathKind::Super => write!(f, "super"), PathKind::Plain => write!(f, "plain"), } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/structure.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/structure.rs index bb2d89841b9..112747e09fb 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/structure.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/structure.rs @@ -14,18 +14,7 @@ pub struct NoirStruct { pub generics: UnresolvedGenerics, pub fields: Vec<(Ident, UnresolvedType)>, pub span: Span, -} - -impl NoirStruct { - pub fn new( - name: Ident, - attributes: Vec, - generics: UnresolvedGenerics, - fields: Vec<(Ident, UnresolvedType)>, - span: Span, - ) -> NoirStruct { - NoirStruct { name, attributes, generics, fields, span } - } + pub is_comptime: bool, } impl Display for NoirStruct { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs index b1b14e3f657..b23fbaede61 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs @@ -7,6 +7,7 @@ use crate::ast::{ BlockExpression, Expression, FunctionReturnType, Ident, NoirFunction, Path, UnresolvedGenerics, UnresolvedType, }; +use crate::macros_api::SecondaryAttribute; use crate::node_interner::TraitId; /// AST node for trait definitions: @@ -18,6 +19,7 @@ pub struct NoirTrait { pub where_clause: Vec, pub span: Span, pub items: Vec, + pub attributes: Vec, } /// Any declaration inside the body of a trait that a user is required to @@ -51,6 +53,7 @@ pub struct TypeImpl { pub generics: UnresolvedGenerics, pub where_clause: Vec, pub methods: Vec<(NoirFunction, Span)>, + pub is_comptime: bool, } /// Ast node for an implementation of a trait for a particular type @@ -67,6 +70,8 @@ pub struct NoirTraitImpl { pub where_clause: Vec, pub items: Vec, + + pub is_comptime: bool, } /// Represents a simple trait constraint such as `where Foo: TraitY` @@ -82,7 +87,7 @@ pub struct UnresolvedTraitConstraint { } /// Represents a single trait bound, such as `TraitX` or `TraitY` -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct TraitBound { pub trait_path: Path, pub trait_id: Option, // initially None, gets assigned during DC diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs index ad79af4e63d..6eed2c16e6b 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -9,37 +9,37 @@ use crate::{ UnresolvedTypeExpression, }, hir::{ - comptime::{self, Interpreter, InterpreterError}, - resolution::{errors::ResolverError, resolver::LambdaContext}, + comptime::{self, InterpreterError}, + resolution::errors::ResolverError, type_check::TypeCheckError, }, hir_def::{ expr::{ HirArrayLiteral, HirBinaryOp, HirBlockExpression, HirCallExpression, HirCastExpression, - HirConstructorExpression, HirIfExpression, HirIndexExpression, HirInfixExpression, - HirLambda, HirMemberAccess, HirMethodCallExpression, HirMethodReference, - HirPrefixExpression, + HirConstructorExpression, HirExpression, HirIfExpression, HirIndexExpression, + HirInfixExpression, HirLambda, HirMemberAccess, HirMethodCallExpression, + HirMethodReference, HirPrefixExpression, }, traits::TraitConstraint, }, macros_api::{ - BlockExpression, CallExpression, CastExpression, Expression, ExpressionKind, HirExpression, - HirLiteral, HirStatement, Ident, IndexExpression, Literal, MemberAccessExpression, + BlockExpression, CallExpression, CastExpression, Expression, ExpressionKind, HirLiteral, + HirStatement, Ident, IndexExpression, Literal, MemberAccessExpression, MethodCallExpression, PrefixExpression, }, - node_interner::{DefinitionKind, ExprId, FuncId, ReferenceId}, + node_interner::{DefinitionKind, ExprId, FuncId, TraitMethodId}, token::Tokens, QuotedType, Shared, StructType, Type, }; -use super::Elaborator; +use super::{Elaborator, LambdaContext}; impl<'context> Elaborator<'context> { pub(super) fn elaborate_expression(&mut self, expr: Expression) -> (ExprId, Type) { let (hir_expr, typ) = match expr.kind { ExpressionKind::Literal(literal) => self.elaborate_literal(literal, expr.span), ExpressionKind::Block(block) => self.elaborate_block(block), - ExpressionKind::Prefix(prefix) => self.elaborate_prefix(*prefix), + ExpressionKind::Prefix(prefix) => return self.elaborate_prefix(*prefix), ExpressionKind::Index(index) => self.elaborate_index(*index), ExpressionKind::Call(call) => self.elaborate_call(*call, expr.span), ExpressionKind::MethodCall(call) => self.elaborate_method_call(*call, expr.span), @@ -227,11 +227,22 @@ impl<'context> Elaborator<'context> { (HirExpression::Literal(HirLiteral::FmtStr(str, fmt_str_idents)), typ) } - fn elaborate_prefix(&mut self, prefix: PrefixExpression) -> (HirExpression, Type) { + fn elaborate_prefix(&mut self, prefix: PrefixExpression) -> (ExprId, Type) { let span = prefix.rhs.span; let (rhs, rhs_type) = self.elaborate_expression(prefix.rhs); - let ret_type = self.type_check_prefix_operand(&prefix.operator, &rhs_type, span); - (HirExpression::Prefix(HirPrefixExpression { operator: prefix.operator, rhs }), ret_type) + let trait_id = self.interner.get_prefix_operator_trait_method(&prefix.operator); + + let operator = prefix.operator; + let expr = + HirExpression::Prefix(HirPrefixExpression { operator, rhs, trait_method_id: trait_id }); + let expr_id = self.interner.push_expr(expr); + self.interner.push_expr_location(expr_id, span, self.file); + + let result = self.prefix_operand_type_rules(&operator, &rhs_type, span); + let typ = self.handle_operand_type_rules_result(result, &rhs_type, trait_id, expr_id, span); + + self.interner.push_expr_type(expr_id, typ.clone()); + (expr_id, typ) } fn elaborate_index(&mut self, index_expr: IndexExpression) -> (HirExpression, Type) { @@ -309,6 +320,7 @@ impl<'context> Elaborator<'context> { let (mut object, mut object_type) = self.elaborate_expression(method_call.object); object_type = object_type.follow_bindings(); + let method_name_span = method_call.method_name.span(); let method_name = method_call.method_name.0.contents.as_str(); match self.lookup_method(&object_type, method_name, span) { Some(method_ref) => { @@ -374,6 +386,9 @@ impl<'context> Elaborator<'context> { self.interner.push_expr_type(function_id, func_type.clone()); + self.interner + .add_function_reference(func_id, Location::new(method_name_span, self.file)); + // Type check the new call now that it has been changed from a method call // to a function call. This way we avoid duplicating code. let typ = self.type_check_call(&function_call, func_type, function_args, span); @@ -388,7 +403,8 @@ impl<'context> Elaborator<'context> { constructor: ConstructorExpression, ) -> (HirExpression, Type) { let span = constructor.type_name.span(); - let is_self_type = constructor.type_name.last_segment().is_self_type_name(); + let last_segment = constructor.type_name.last_segment(); + let is_self_type = last_segment.is_self_type_name(); let (r#type, struct_generics) = if let Some(struct_id) = constructor.struct_type { let typ = self.interner.get_struct(struct_id); @@ -418,9 +434,9 @@ impl<'context> Elaborator<'context> { struct_generics, }); - let referenced = ReferenceId::Struct(struct_type.borrow().id); - let reference = ReferenceId::Reference(Location::new(span, self.file), is_self_type); - self.interner.add_reference(referenced, reference); + let struct_id = struct_type.borrow().id; + let reference_location = Location::new(last_segment.span(), self.file); + self.interner.add_struct_reference(struct_id, reference_location, is_self_type); (expr, Type::Struct(struct_type, generics)) } @@ -440,8 +456,13 @@ impl<'context> Elaborator<'context> { let mut unseen_fields = struct_type.borrow().field_names(); for (field_name, field) in fields { - let expected_type = field_types.iter().find(|(name, _)| name == &field_name.0.contents); - let expected_type = expected_type.map(|(_, typ)| typ).unwrap_or(&Type::Error); + let expected_field_with_index = field_types + .iter() + .enumerate() + .find(|(_, (name, _))| name == &field_name.0.contents); + let expected_index = expected_field_with_index.map(|(index, _)| index); + let expected_type = + expected_field_with_index.map(|(_, (_, typ))| typ).unwrap_or(&Type::Error); let field_span = field.span; let (resolved, field_type) = self.elaborate_expression(field); @@ -468,6 +489,14 @@ impl<'context> Elaborator<'context> { }); } + if let Some(expected_index) = expected_index { + self.interner.add_struct_member_reference( + struct_type.borrow().id, + expected_index, + Location::new(field_name.span(), self.file), + ); + } + ret.push((field_name, resolved)); } @@ -489,10 +518,11 @@ impl<'context> Elaborator<'context> { ) -> (ExprId, Type) { let (lhs, lhs_type) = self.elaborate_expression(access.lhs); let rhs = access.rhs; + let rhs_span = rhs.span(); // `is_offset` is only used when lhs is a reference and we want to return a reference to rhs let access = HirMemberAccess { lhs, rhs, is_offset: false }; let expr_id = self.intern_expr(HirExpression::MemberAccess(access.clone()), span); - let typ = self.type_check_member_access(access, expr_id, lhs_type, span); + let typ = self.type_check_member_access(access, expr_id, lhs_type, rhs_span); self.interner.push_expr_type(expr_id, typ.clone()); (expr_id, typ) } @@ -527,19 +557,38 @@ impl<'context> Elaborator<'context> { let expr_id = self.interner.push_expr(expr); self.interner.push_expr_location(expr_id, span, self.file); - let typ = match self.infix_operand_type_rules(&lhs_type, &operator, &rhs_type, span) { + let result = self.infix_operand_type_rules(&lhs_type, &operator, &rhs_type, span); + let typ = + self.handle_operand_type_rules_result(result, &lhs_type, Some(trait_id), expr_id, span); + + self.interner.push_expr_type(expr_id, typ.clone()); + (expr_id, typ) + } + + fn handle_operand_type_rules_result( + &mut self, + result: Result<(Type, bool), TypeCheckError>, + operand_type: &Type, + trait_id: Option, + expr_id: ExprId, + span: Span, + ) -> Type { + match result { Ok((typ, use_impl)) => { if use_impl { + let trait_id = + trait_id.expect("ice: expected some trait_id when use_impl is true"); + // Delay checking the trait constraint until the end of the function. // Checking it now could bind an unbound type variable to any type // that implements the trait. let constraint = TraitConstraint { - typ: lhs_type.clone(), + typ: operand_type.clone(), trait_id: trait_id.trait_id, trait_generics: Vec::new(), }; self.push_trait_constraint(constraint, expr_id); - self.type_check_operator_method(expr_id, trait_id, &lhs_type, span); + self.type_check_operator_method(expr_id, trait_id, operand_type, span); } typ } @@ -547,10 +596,7 @@ impl<'context> Elaborator<'context> { self.push_err(error); Type::Error } - }; - - self.interner.push_expr_type(expr_id, typ.clone()); - (expr_id, typ) + } } fn elaborate_if(&mut self, if_expr: IfExpression) -> (HirExpression, Type) { @@ -660,12 +706,20 @@ impl<'context> Elaborator<'context> { // call is not yet solved for. self.function_context.push(Default::default()); let (block, _typ) = self.elaborate_block_expression(block); - self.check_and_pop_function_context(); - let mut interpreter = - Interpreter::new(self.interner, &mut self.comptime_scopes, self.crate_id); + self.check_and_pop_function_context(); + let mut interpreter_errors = vec![]; + let mut interpreter = self.setup_interpreter(&mut interpreter_errors); let value = interpreter.evaluate_block(block); - self.inline_comptime_value(value, span) + self.include_interpreter_errors(interpreter_errors); + let (id, typ) = self.inline_comptime_value(value, span); + + let location = self.interner.id_location(id); + self.debug_comptime(location, |interner| { + interner.expression(&id).to_display_ast(interner, location.span).kind + }); + + (id, typ) } pub(super) fn inline_comptime_value( @@ -736,9 +790,9 @@ impl<'context> Elaborator<'context> { } }; - let mut interpreter = - Interpreter::new(self.interner, &mut self.comptime_scopes, self.crate_id); - + let file = self.file; + let mut interpreter_errors = vec![]; + let mut interpreter = self.setup_interpreter(&mut interpreter_errors); let mut comptime_args = Vec::new(); let mut errors = Vec::new(); @@ -748,17 +802,19 @@ impl<'context> Elaborator<'context> { let location = interpreter.interner.expr_location(&argument); comptime_args.push((arg, location)); } - Err(error) => errors.push((error.into(), self.file)), + Err(error) => errors.push((error.into(), file)), } } + let bindings = interpreter.interner.get_instantiation_bindings(func).clone(); + let result = interpreter.call_function(function, comptime_args, bindings, location); + self.include_interpreter_errors(interpreter_errors); + if !errors.is_empty() { self.errors.append(&mut errors); return None; } - let bindings = interpreter.interner.get_instantiation_bindings(func).clone(); - let result = interpreter.call_function(function, comptime_args, bindings, location); let (expr_id, typ) = self.inline_comptime_value(result, location.span); Some((self.interner.expression(&expr_id), typ)) } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs index 6104da582a7..e2e2e5fa1d5 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs @@ -1,5 +1,6 @@ use std::{ collections::{BTreeMap, BTreeSet}, + fmt::Display, rc::Rc, }; @@ -15,16 +16,17 @@ use crate::{ dc_mod, errors::DuplicateType, }, - resolution::{errors::ResolverError, path_resolver::PathResolver, resolver::LambdaContext}, + resolution::{errors::ResolverError, path_resolver::PathResolver}, scope::ScopeForest as GenericScopeForest, - type_check::{check_trait_impl_method_matches_declaration, TypeCheckError}, + type_check::TypeCheckError, }, hir_def::{ - expr::HirIdent, + expr::{HirCapturedVar, HirIdent}, function::{FunctionBody, Parameters}, traits::TraitConstraint, types::{Generics, Kind, ResolvedGeneric}, }, + lexer::Lexer, macros_api::{ BlockExpression, Ident, NodeInterner, NoirFunction, NoirStruct, Pattern, SecondaryAttribute, StructId, @@ -34,6 +36,7 @@ use crate::{ TypeAliasId, }, parser::TopLevelStatement, + token::Tokens, Shared, Type, TypeBindings, TypeVariable, }; use crate::{ @@ -64,7 +67,7 @@ mod patterns; mod scope; mod statements; mod traits; -mod types; +pub mod types; mod unquote; use fm::FileId; @@ -72,6 +75,8 @@ use iter_extended::vecmap; use noirc_errors::{Location, Span}; use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet}; +use self::traits::check_trait_impl_method_matches_declaration; + /// ResolverMetas are tagged onto each definition to track how many times they are used #[derive(Debug, PartialEq, Eq)] pub struct ResolverMeta { @@ -82,6 +87,13 @@ pub struct ResolverMeta { type ScopeForest = GenericScopeForest; +pub struct LambdaContext { + pub captures: Vec, + /// the index in the scope tree + /// (sometimes being filled by ScopeTree's find method) + pub scope_index: usize, +} + pub struct Elaborator<'context> { scopes: ScopeForest, @@ -157,6 +169,9 @@ pub struct Elaborator<'context> { /// up all currently visible definitions. The first scope is always the global scope. comptime_scopes: Vec>, + /// The scope of --debug-comptime, or None if unset + debug_comptime_in_file: Option, + /// These are the globals that have yet to be elaborated. /// This map is used to lazily evaluate these globals if they're encountered before /// they are elaborated (e.g. in a function's type or another global's RHS). @@ -178,7 +193,11 @@ struct FunctionContext { } impl<'context> Elaborator<'context> { - pub fn new(context: &'context mut Context, crate_id: CrateId) -> Self { + pub fn new( + context: &'context mut Context, + crate_id: CrateId, + debug_comptime_in_file: Option, + ) -> Self { Self { scopes: ScopeForest::default(), errors: Vec::new(), @@ -198,6 +217,7 @@ impl<'context> Elaborator<'context> { function_context: vec![FunctionContext::default()], current_trait_impl: None, comptime_scopes: vec![HashMap::default()], + debug_comptime_in_file, unresolved_globals: BTreeMap::new(), } } @@ -206,8 +226,9 @@ impl<'context> Elaborator<'context> { context: &'context mut Context, crate_id: CrateId, items: CollectedItems, + debug_comptime_in_file: Option, ) -> Vec<(CompilationError, FileId)> { - let mut this = Self::new(context, crate_id); + let mut this = Self::new(context, crate_id, debug_comptime_in_file); // Filter out comptime items to execute their functions first if needed. // This step is why comptime items can only refer to other comptime items @@ -240,11 +261,11 @@ impl<'context> Elaborator<'context> { } // Must resolve structs before we resolve globals. - let generated_items = self.collect_struct_definitions(items.types); + let mut generated_items = self.collect_struct_definitions(items.types); self.define_function_metas(&mut items.functions, &mut items.impls, &mut items.trait_impls); - self.collect_traits(items.traits); + self.collect_traits(items.traits, &mut generated_items); // Before we resolve any function symbols we must go through our impls and // re-collect the methods within into their proper module. This cannot be @@ -266,6 +287,10 @@ impl<'context> Elaborator<'context> { self.elaborate_global(global); } + // We have to run any comptime attributes on functions before the function is elaborated + // since the generated items are checked beforehand as well. + self.run_attributes_on_functions(&items.functions, &mut generated_items); + // After everything is collected, we can elaborate our generated items. // It may be better to inline these within `items` entirely since elaborating them // all here means any globals will not see these. Inlining them completely within `items` @@ -708,6 +733,12 @@ impl<'context> Elaborator<'context> { let statements = std::mem::take(&mut func.def.body.statements); let body = BlockExpression { statements }; + let struct_id = if let Some(Type::Struct(struct_type, _)) = &self.self_type { + Some(struct_type.borrow().id) + } else { + None + }; + let meta = FuncMeta { name: name_ident, kind: func.kind, @@ -715,6 +746,7 @@ impl<'context> Elaborator<'context> { typ, direct_generics, all_generics: self.generics.clone(), + struct_id, trait_impl: self.current_trait_impl, parameters: parameters.into(), parameter_idents, @@ -1191,6 +1223,7 @@ impl<'context> Elaborator<'context> { let span = typ.struct_def.span; let fields = self.resolve_struct_fields(typ.struct_def, type_id); + let fields_len = fields.len(); self.interner.update_struct(type_id, |struct_def| { struct_def.set_fields(fields); @@ -1217,7 +1250,13 @@ impl<'context> Elaborator<'context> { } }); - self.run_comptime_attributes_on_struct(attributes, type_id, span, &mut generated_items); + for field_index in 0..fields_len { + self.interner + .add_definition_location(ReferenceId::StructMember(type_id, field_index), None); + } + + let item = Value::StructDefinition(type_id); + self.run_comptime_attributes_on_item(&attributes, item, span, &mut generated_items); } // Check whether the struct fields have nested slices @@ -1243,17 +1282,17 @@ impl<'context> Elaborator<'context> { generated_items } - fn run_comptime_attributes_on_struct( + fn run_comptime_attributes_on_item( &mut self, - attributes: Vec, - struct_id: StructId, + attributes: &[SecondaryAttribute], + item: Value, span: Span, generated_items: &mut CollectedItems, ) { for attribute in attributes { if let SecondaryAttribute::Custom(name) = attribute { if let Err(error) = - self.run_comptime_attribute_on_struct(name, struct_id, span, generated_items) + self.run_comptime_attribute_on_item(name, item.clone(), span, generated_items) { self.errors.push(error); } @@ -1261,30 +1300,37 @@ impl<'context> Elaborator<'context> { } } - fn run_comptime_attribute_on_struct( + fn run_comptime_attribute_on_item( &mut self, - attribute: String, - struct_id: StructId, + attribute: &str, + item: Value, span: Span, generated_items: &mut CollectedItems, ) -> Result<(), (CompilationError, FileId)> { - let id = self - .lookup_global(Path::from_single(attribute, span)) - .map_err(|_| (ResolverError::UnknownAnnotation { span }.into(), self.file))?; + let location = Location::new(span, self.file); + let (function_name, mut arguments) = Self::parse_attribute(attribute, location) + .unwrap_or_else(|| (attribute.to_string(), Vec::new())); + + let Ok(id) = self.lookup_global(Path::from_single(function_name, span)) else { + // Do not issue an error if the attribute is unknown + return Ok(()); + }; let definition = self.interner.definition(id); let DefinitionKind::Function(function) = definition.kind else { return Err((ResolverError::NonFunctionInAnnotation { span }.into(), self.file)); }; - let mut interpreter = - Interpreter::new(self.interner, &mut self.comptime_scopes, self.crate_id); - let location = Location::new(span, self.file); - let arguments = vec![(Value::StructDefinition(struct_id), location)]; + self.handle_varargs_attribute(function, &mut arguments, location); + arguments.insert(0, (item, location)); + + let mut interpreter_errors = vec![]; + let mut interpreter = self.setup_interpreter(&mut interpreter_errors); let value = interpreter .call_function(function, arguments, TypeBindings::new(), location) .map_err(|error| error.into_compilation_error_pair())?; + self.include_interpreter_errors(interpreter_errors); if value != Value::Unit { let items = value @@ -1297,6 +1343,59 @@ impl<'context> Elaborator<'context> { Ok(()) } + /// Parses an attribute in the form of a function call (e.g. `#[foo(a b, c d)]`) into + /// the function and quoted arguments called (e.g. `("foo", vec![(a b, location), (c d, location)])`) + fn parse_attribute( + annotation: &str, + location: Location, + ) -> Option<(String, Vec<(Value, Location)>)> { + let (tokens, errors) = Lexer::lex(annotation); + if !errors.is_empty() { + return None; + } + + let mut tokens = tokens.0; + if tokens.len() >= 4 { + // Remove the outer `ident ( )` wrapping the function arguments + let first = tokens.remove(0).into_token(); + let second = tokens.remove(0).into_token(); + + // Last token is always an EndOfInput + let _ = tokens.pop().unwrap().into_token(); + let last = tokens.pop().unwrap().into_token(); + + use crate::lexer::token::Token::*; + if let (Ident(name), LeftParen, RightParen) = (first, second, last) { + let args = tokens.split(|token| *token.token() == Comma); + let args = + vecmap(args, |arg| (Value::Code(Rc::new(Tokens(arg.to_vec()))), location)); + return Some((name, args)); + } + } + + None + } + + /// Checks if the given attribute function is a varargs function. + /// If so, we should pass its arguments in one slice rather than as separate arguments. + fn handle_varargs_attribute( + &mut self, + function: FuncId, + arguments: &mut Vec<(Value, Location)>, + location: Location, + ) { + let meta = self.interner.function_meta(&function); + let parameters = &meta.parameters.0; + + // If the last parameter is a slice, this is a varargs function. + if parameters.last().map_or(false, |(_, typ, _)| matches!(typ, Type::Slice(_))) { + let typ = Type::Slice(Box::new(Type::Quoted(crate::QuotedType::Quoted))); + let slice_elements = arguments.drain(..).map(|(value, _)| value); + let slice = Value::Slice(slice_elements.collect(), typ); + arguments.push((slice, location)); + } + } + pub fn resolve_struct_fields( &mut self, unresolved: NoirStruct, @@ -1349,7 +1448,8 @@ impl<'context> Elaborator<'context> { self.elaborate_comptime_global(global_id); } - self.interner.add_definition_location(ReferenceId::Global(global_id)); + self.interner + .add_definition_location(ReferenceId::Global(global_id), Some(self.module_id())); self.local_module = old_module; self.file = old_file; @@ -1365,9 +1465,8 @@ impl<'context> Elaborator<'context> { let global = self.interner.get_global(global_id); let definition_id = global.definition_id; let location = global.location; - - let mut interpreter = - Interpreter::new(self.interner, &mut self.comptime_scopes, self.crate_id); + let mut interpreter_errors = vec![]; + let mut interpreter = self.setup_interpreter(&mut interpreter_errors); if let Err(error) = interpreter.evaluate_let(let_statement) { self.errors.push(error.into_compilation_error_pair()); @@ -1376,8 +1475,13 @@ impl<'context> Elaborator<'context> { .lookup_id(definition_id, location) .expect("The global should be defined since evaluate_let did not error"); + self.debug_comptime(location, |interner| { + interner.get_global(global_id).let_statement.to_display_ast(interner).kind + }); + self.interner.get_global_mut(global_id).value = Some(value); } + self.include_interpreter_errors(interpreter_errors); } fn define_function_metas( @@ -1451,13 +1555,11 @@ impl<'context> Elaborator<'context> { if let Some(trait_id) = trait_id { let trait_name = trait_impl.trait_path.last_segment(); - - let referenced = ReferenceId::Trait(trait_id); - let reference = ReferenceId::Reference( + self.interner.add_trait_reference( + trait_id, Location::new(trait_name.span(), trait_impl.file_id), trait_name.is_self_type_name(), ); - self.interner.add_reference(referenced, reference); } } } @@ -1473,6 +1575,10 @@ impl<'context> Elaborator<'context> { } } + fn include_interpreter_errors(&mut self, errors: Vec) { + self.errors.extend(errors.into_iter().map(InterpreterError::into_compilation_error_pair)); + } + /// True if we're currently within a `comptime` block, function, or global fn in_comptime_context(&self) -> bool { // The first context is the global context, followed by the function-specific context. @@ -1531,17 +1637,25 @@ impl<'context> Elaborator<'context> { function_sets.push(UnresolvedFunctions { functions, file_id, trait_id, self_type }); } + let (comptime_trait_impls, trait_impls) = + items.trait_impls.into_iter().partition(|trait_impl| trait_impl.is_comptime); + + let (comptime_structs, structs) = + items.types.into_iter().partition(|typ| typ.1.struct_def.is_comptime); + let comptime = CollectedItems { functions: comptime_function_sets, - types: BTreeMap::new(), + types: comptime_structs, type_aliases: BTreeMap::new(), traits: BTreeMap::new(), - trait_impls: Vec::new(), + trait_impls: comptime_trait_impls, globals: Vec::new(), impls: rustc_hash::FxHashMap::default(), }; items.functions = function_sets; + items.trait_impls = trait_impls; + items.types = structs; (comptime, items) } @@ -1552,75 +1666,131 @@ impl<'context> Elaborator<'context> { location: Location, ) { for item in items { - match item { - TopLevelStatement::Function(function) => { - let id = self.interner.push_empty_fn(); - let module = self.module_id(); - self.interner.push_function(id, &function.def, module, location); - let functions = vec![(self.local_module, id, function)]; - generated_items.functions.push(UnresolvedFunctions { - file_id: self.file, - functions, - trait_id: None, - self_type: None, - }); - } - TopLevelStatement::TraitImpl(mut trait_impl) => { - let methods = dc_mod::collect_trait_impl_functions( - self.interner, - &mut trait_impl, - self.crate_id, - self.file, - self.local_module, - ); + self.add_item(item, generated_items, location); + } + } - generated_items.trait_impls.push(UnresolvedTraitImpl { - file_id: self.file, - module_id: self.local_module, - trait_generics: trait_impl.trait_generics, - trait_path: trait_impl.trait_name, - object_type: trait_impl.object_type, - methods, - generics: trait_impl.impl_generics, - where_clause: trait_impl.where_clause, - - // These last fields are filled in later - trait_id: None, - impl_id: None, - resolved_object_type: None, - resolved_generics: Vec::new(), - resolved_trait_generics: Vec::new(), - }); - } - TopLevelStatement::Global(global) => { - let (global, error) = dc_mod::collect_global( - self.interner, - self.def_maps.get_mut(&self.crate_id).unwrap(), - global, - self.file, - self.local_module, - ); + fn add_item( + &mut self, + item: TopLevelStatement, + generated_items: &mut CollectedItems, + location: Location, + ) { + match item { + TopLevelStatement::Function(function) => { + let id = self.interner.push_empty_fn(); + let module = self.module_id(); + self.interner.push_function(id, &function.def, module, location); + let functions = vec![(self.local_module, id, function)]; + generated_items.functions.push(UnresolvedFunctions { + file_id: self.file, + functions, + trait_id: None, + self_type: None, + }); + } + TopLevelStatement::TraitImpl(mut trait_impl) => { + let methods = dc_mod::collect_trait_impl_functions( + self.interner, + &mut trait_impl, + self.crate_id, + self.file, + self.local_module, + ); - generated_items.globals.push(global); - if let Some(error) = error { - self.errors.push(error); - } - } - // Assume that an error has already been issued - TopLevelStatement::Error => (), - - TopLevelStatement::Module(_) - | TopLevelStatement::Import(_) - | TopLevelStatement::Struct(_) - | TopLevelStatement::Trait(_) - | TopLevelStatement::Impl(_) - | TopLevelStatement::TypeAlias(_) - | TopLevelStatement::SubModule(_) => { - let item = item.to_string(); - let error = InterpreterError::UnsupportedTopLevelItemUnquote { item, location }; - self.errors.push(error.into_compilation_error_pair()); + generated_items.trait_impls.push(UnresolvedTraitImpl { + file_id: self.file, + module_id: self.local_module, + trait_generics: trait_impl.trait_generics, + trait_path: trait_impl.trait_name, + object_type: trait_impl.object_type, + methods, + generics: trait_impl.impl_generics, + where_clause: trait_impl.where_clause, + is_comptime: trait_impl.is_comptime, + + // These last fields are filled in later + trait_id: None, + impl_id: None, + resolved_object_type: None, + resolved_generics: Vec::new(), + resolved_trait_generics: Vec::new(), + }); + } + TopLevelStatement::Global(global) => { + let (global, error) = dc_mod::collect_global( + self.interner, + self.def_maps.get_mut(&self.crate_id).unwrap(), + global, + self.file, + self.local_module, + ); + + generated_items.globals.push(global); + if let Some(error) = error { + self.errors.push(error); } } + // Assume that an error has already been issued + TopLevelStatement::Error => (), + + TopLevelStatement::Module(_) + | TopLevelStatement::Import(_) + | TopLevelStatement::Struct(_) + | TopLevelStatement::Trait(_) + | TopLevelStatement::Impl(_) + | TopLevelStatement::TypeAlias(_) + | TopLevelStatement::SubModule(_) => { + let item = item.to_string(); + let error = InterpreterError::UnsupportedTopLevelItemUnquote { item, location }; + self.errors.push(error.into_compilation_error_pair()); + } + } + } + + fn setup_interpreter<'a>( + &'a mut self, + interpreter_errors: &'a mut Vec, + ) -> Interpreter { + Interpreter::new( + self.interner, + &mut self.comptime_scopes, + self.crate_id, + self.debug_comptime_in_file, + interpreter_errors, + ) + } + + fn debug_comptime T>( + &mut self, + location: Location, + mut expr_f: F, + ) { + if Some(location.file) == self.debug_comptime_in_file { + let displayed_expr = expr_f(self.interner); + self.errors.push(( + InterpreterError::debug_evaluate_comptime(displayed_expr, location).into(), + location.file, + )); + } + } + + fn run_attributes_on_functions( + &mut self, + function_sets: &[UnresolvedFunctions], + generated_items: &mut CollectedItems, + ) { + for function_set in function_sets { + self.file = function_set.file_id; + self.self_type = function_set.self_type.clone(); + + for (local_module, function_id, function) in &function_set.functions { + self.local_module = *local_module; + let attributes = function.secondary_attributes(); + let item = Value::FunctionDefinition(*function_id); + let span = function.span(); + self.run_comptime_attributes_on_item(attributes, item, span, generated_items); + } } } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/patterns.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/patterns.rs index b1c9b1b37cc..d9576c77666 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -15,9 +15,7 @@ use crate::{ stmt::HirPattern, }, macros_api::{HirExpression, Ident, Path, Pattern}, - node_interner::{ - DefinitionId, DefinitionKind, ExprId, FuncId, GlobalId, ReferenceId, TraitImplKind, - }, + node_interner::{DefinitionId, DefinitionKind, ExprId, FuncId, GlobalId, TraitImplKind}, Shared, StructType, Type, TypeBindings, }; @@ -202,9 +200,15 @@ impl<'context> Elaborator<'context> { new_definitions, ); - let referenced = ReferenceId::Struct(struct_type.borrow().id); - let reference = ReferenceId::Reference(Location::new(name_span, self.file), is_self_type); - self.interner.add_reference(referenced, reference); + let struct_id = struct_type.borrow().id; + + let reference_location = Location::new(name_span, self.file); + self.interner.add_struct_reference(struct_id, reference_location, is_self_type); + + for (field_index, field) in fields.iter().enumerate() { + let reference_location = Location::new(field.0.span(), self.file); + self.interner.add_struct_member_reference(struct_id, field_index, reference_location); + } HirPattern::Struct(expected_type, fields, location) } @@ -456,9 +460,16 @@ impl<'context> Elaborator<'context> { // Comptime variables must be replaced with their values if let Some(definition) = self.interner.try_definition(definition_id) { if definition.comptime && !self.in_comptime_context() { - let mut interpreter = - Interpreter::new(self.interner, &mut self.comptime_scopes, self.crate_id); + let mut interpreter_errors = vec![]; + let mut interpreter = Interpreter::new( + self.interner, + &mut self.comptime_scopes, + self.crate_id, + self.debug_comptime_in_file, + &mut interpreter_errors, + ); let value = interpreter.evaluate(id); + self.include_interpreter_errors(interpreter_errors); return self.inline_comptime_value(value, span); } } @@ -479,7 +490,6 @@ impl<'context> Elaborator<'context> { // This lookup allows support of such statements: let x = foo::bar::SOME_GLOBAL + 10; // If the expression is a singular indent, we search the resolver's current scope as normal. let span = path.span(); - let is_self_type_name = path.last_segment().is_self_type_name(); let (hir_ident, var_scope_index) = self.get_ident_from_path(path); if hir_ident.id != DefinitionId::dummy_id() { @@ -489,10 +499,7 @@ impl<'context> Elaborator<'context> { self.interner.add_function_dependency(current_item, func_id); } - let variable = - ReferenceId::Reference(hir_ident.location, is_self_type_name); - let function = ReferenceId::Function(func_id); - self.interner.add_reference(function, variable); + self.interner.add_function_reference(func_id, hir_ident.location); } DefinitionKind::Global(global_id) => { if let Some(global) = self.unresolved_globals.remove(&global_id) { @@ -502,10 +509,7 @@ impl<'context> Elaborator<'context> { self.interner.add_global_dependency(current_item, global_id); } - let variable = - ReferenceId::Reference(hir_ident.location, is_self_type_name); - let global = ReferenceId::Global(global_id); - self.interner.add_reference(global, variable); + self.interner.add_global_reference(global_id, hir_ident.location); } DefinitionKind::GenericType(_) => { // Initialize numeric generics to a polymorphic integer type in case @@ -521,10 +525,8 @@ impl<'context> Elaborator<'context> { // only local variables can be captured by closures. self.resolve_local_variable(hir_ident.clone(), var_scope_index); - let referenced = ReferenceId::Local(hir_ident.id); - let reference = - ReferenceId::Reference(Location::new(span, self.file), false); - self.interner.add_reference(referenced, reference); + let reference_location = Location::new(span, self.file); + self.interner.add_local_reference(hir_ident.id, reference_location); } } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/scope.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/scope.rs index b7016280453..23638b03cf5 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/scope.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/scope.rs @@ -3,10 +3,8 @@ use noirc_errors::{Location, Spanned}; use crate::ast::ERROR_IDENT; use crate::hir::def_map::{LocalModuleId, ModuleId}; use crate::hir::resolution::path_resolver::{PathResolver, StandardPathResolver}; -use crate::hir::resolution::resolver::SELF_TYPE_NAME; use crate::hir::scope::{Scope as GenericScope, ScopeTree as GenericScopeTree}; use crate::macros_api::Ident; -use crate::node_interner::ReferenceId; use crate::{ hir::{ def_map::{ModuleDefId, TryFromModuleDefId}, @@ -22,6 +20,7 @@ use crate::{ }; use crate::{Type, TypeAlias}; +use super::types::SELF_TYPE_NAME; use super::{Elaborator, ResolverMeta}; type Scope = GenericScope; @@ -48,17 +47,30 @@ impl<'context> Elaborator<'context> { let path_resolution; if self.interner.track_references { - let mut references: Vec = Vec::new(); + let last_segment = path.last_segment(); + let location = Location::new(last_segment.span(), self.file); + let is_self_type_name = last_segment.is_self_type_name(); + + let mut references: Vec<_> = Vec::new(); path_resolution = resolver.resolve(self.def_maps, path.clone(), &mut Some(&mut references))?; for (referenced, ident) in references.iter().zip(path.segments) { - let reference = ReferenceId::Reference( + let Some(referenced) = referenced else { + continue; + }; + self.interner.add_reference( + *referenced, Location::new(ident.span(), self.file), ident.is_self_type_name(), ); - self.interner.add_reference(*referenced, reference); } + + self.interner.add_module_def_id_reference( + path_resolution.module_def_id, + location, + is_self_type_name, + ); } else { path_resolution = resolver.resolve(self.def_maps, path, &mut None)?; } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/statements.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/statements.rs index 1fc92ad28ba..ba3dcccca99 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/statements.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/statements.rs @@ -3,7 +3,6 @@ use noirc_errors::{Location, Span}; use crate::{ ast::{AssignStatement, ConstrainStatement, LValue}, hir::{ - comptime::Interpreter, resolution::errors::ResolverError, type_check::{Source, TypeCheckError}, }, @@ -16,7 +15,7 @@ use crate::{ macros_api::{ ForLoopStatement, ForRange, HirStatement, LetStatement, Path, Statement, StatementKind, }, - node_interner::{DefinitionId, DefinitionKind, GlobalId, ReferenceId, StmtId}, + node_interner::{DefinitionId, DefinitionKind, GlobalId, StmtId}, Type, }; @@ -256,9 +255,8 @@ impl<'context> Elaborator<'context> { typ.follow_bindings() }; - let referenced = ReferenceId::Local(ident.id); - let reference = ReferenceId::Reference(Location::new(span, self.file), false); - self.interner.add_reference(referenced, reference); + let reference_location = Location::new(span, self.file); + self.interner.add_local_reference(ident.id, reference_location); (HirLValue::Ident(ident.clone(), typ.clone()), typ, mutable) } @@ -381,6 +379,9 @@ impl<'context> Elaborator<'context> { Type::Struct(s, args) => { let s = s.borrow(); if let Some((field, index)) = s.get_field(field_name, args) { + let reference_location = Location::new(span, self.file); + self.interner.add_struct_member_reference(s.id, index, reference_location); + return Some((field, index)); } } @@ -444,11 +445,15 @@ impl<'context> Elaborator<'context> { let span = statement.span; let (hir_statement, _typ) = self.elaborate_statement(statement); self.check_and_pop_function_context(); - - let mut interpreter = - Interpreter::new(self.interner, &mut self.comptime_scopes, self.crate_id); + let mut interpreter_errors = vec![]; + let mut interpreter = self.setup_interpreter(&mut interpreter_errors); let value = interpreter.evaluate_statement(hir_statement); let (expr, typ) = self.inline_comptime_value(value, span); + self.include_interpreter_errors(interpreter_errors); + + let location = self.interner.id_location(hir_statement); + self.debug_comptime(location, |interner| expr.to_display_ast(interner).kind); + (HirStatement::Expression(expr), typ) } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/traits.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/traits.rs index 77ac8e476f8..9443791700f 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/traits.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/traits.rs @@ -1,27 +1,37 @@ use std::{collections::BTreeMap, rc::Rc}; use iter_extended::vecmap; -use noirc_errors::Location; +use noirc_errors::{Location, Span}; use crate::{ ast::{ FunctionKind, TraitItem, UnresolvedGeneric, UnresolvedGenerics, UnresolvedTraitConstraint, }, - hir::def_collector::dc_crate::UnresolvedTrait, - hir_def::traits::{TraitConstant, TraitFunction, TraitType}, + hir::{ + def_collector::dc_crate::{CollectedItems, UnresolvedTrait}, + type_check::TypeCheckError, + }, + hir_def::{ + function::Parameters, + traits::{TraitConstant, TraitFunction, TraitType}, + }, macros_api::{ BlockExpression, FunctionDefinition, FunctionReturnType, Ident, ItemVisibility, - NoirFunction, Param, Pattern, UnresolvedType, Visibility, + NodeInterner, NoirFunction, Param, Pattern, UnresolvedType, Visibility, }, node_interner::{FuncId, TraitId}, token::Attributes, - Kind, ResolvedGeneric, Type, TypeVariableKind, + Kind, ResolvedGeneric, Type, TypeBindings, TypeVariableKind, }; use super::Elaborator; impl<'context> Elaborator<'context> { - pub fn collect_traits(&mut self, traits: BTreeMap) { + pub fn collect_traits( + &mut self, + traits: BTreeMap, + generated_items: &mut CollectedItems, + ) { for (trait_id, unresolved_trait) in traits { self.recover_generics(|this| { let resolved_generics = this.interner.get_trait(trait_id).generics.clone(); @@ -41,13 +51,19 @@ impl<'context> Elaborator<'context> { this.interner.update_trait(trait_id, |trait_def| { trait_def.set_methods(methods); }); + + let attributes = &unresolved_trait.trait_def.attributes; + let item = crate::hir::comptime::Value::TraitDefinition(trait_id); + let span = unresolved_trait.trait_def.span; + this.run_comptime_attributes_on_item(attributes, item, span, generated_items); }); // This check needs to be after the trait's methods are set since // the interner may set `interner.ordering_type` based on the result type // of the Cmp trait, if this is it. if self.crate_id.is_stdlib() { - self.interner.try_add_operator_trait(trait_id); + self.interner.try_add_infix_operator_trait(trait_id); + self.interner.try_add_prefix_operator_trait(trait_id); } } } @@ -194,3 +210,159 @@ impl<'context> Elaborator<'context> { self.generics.truncate(old_generic_count); } } + +/// Checks that the type of a function in a trait impl matches the type +/// of the corresponding function declaration in the trait itself. +/// +/// To do this, given a trait such as: +/// `trait Foo { fn foo(...); }` +/// +/// And an impl such as: +/// `impl Foo for Bar { fn foo(...); } ` +/// +/// We have to substitute: +/// - Self for Bar +/// - A for D +/// - B for F +/// +/// Before we can type check. Finally, we must also check that the unification +/// result does not introduce any new bindings. This can happen if the impl +/// function's type is more general than that of the trait function. E.g. +/// `fn baz(a: A, b: B)` when the impl required `fn baz(a: A, b: A)`. +/// +/// This does not type check the body of the impl function. +pub(crate) fn check_trait_impl_method_matches_declaration( + interner: &mut NodeInterner, + function: FuncId, +) -> Vec { + let meta = interner.function_meta(&function); + let method_name = interner.function_name(&function); + let mut errors = Vec::new(); + + let definition_type = meta.typ.as_monotype(); + + let impl_ = + meta.trait_impl.expect("Trait impl function should have a corresponding trait impl"); + + // If the trait implementation is not defined in the interner then there was a previous + // error in resolving the trait path and there is likely no trait for this impl. + let Some(impl_) = interner.try_get_trait_implementation(impl_) else { + return errors; + }; + + let impl_ = impl_.borrow(); + let trait_info = interner.get_trait(impl_.trait_id); + + let mut bindings = TypeBindings::new(); + bindings.insert( + trait_info.self_type_typevar_id, + (trait_info.self_type_typevar.clone(), impl_.typ.clone()), + ); + + if trait_info.generics.len() != impl_.trait_generics.len() { + let expected = trait_info.generics.len(); + let found = impl_.trait_generics.len(); + let span = impl_.ident.span(); + let item = trait_info.name.to_string(); + errors.push(TypeCheckError::GenericCountMismatch { item, expected, found, span }); + } + + // Substitute each generic on the trait with the corresponding generic on the impl + for (generic, arg) in trait_info.generics.iter().zip(&impl_.trait_generics) { + bindings.insert(generic.type_var.id(), (generic.type_var.clone(), arg.clone())); + } + + // If this is None, the trait does not have the corresponding function. + // This error should have been caught in name resolution already so we don't + // issue an error for it here. + if let Some(trait_fn_id) = trait_info.method_ids.get(method_name) { + let trait_fn_meta = interner.function_meta(trait_fn_id); + + if trait_fn_meta.direct_generics.len() != meta.direct_generics.len() { + let expected = trait_fn_meta.direct_generics.len(); + let found = meta.direct_generics.len(); + let span = meta.name.location.span; + let item = method_name.to_string(); + errors.push(TypeCheckError::GenericCountMismatch { item, expected, found, span }); + } + + // Substitute each generic on the trait function with the corresponding generic on the impl function + for ( + ResolvedGeneric { type_var: trait_fn_generic, .. }, + ResolvedGeneric { name, type_var: impl_fn_generic, .. }, + ) in trait_fn_meta.direct_generics.iter().zip(&meta.direct_generics) + { + let arg = Type::NamedGeneric(impl_fn_generic.clone(), name.clone(), Kind::Normal); + bindings.insert(trait_fn_generic.id(), (trait_fn_generic.clone(), arg)); + } + + let (declaration_type, _) = trait_fn_meta.typ.instantiate_with_bindings(bindings, interner); + + check_function_type_matches_expected_type( + &declaration_type, + definition_type, + method_name, + &meta.parameters, + meta.name.location.span, + &trait_info.name.0.contents, + &mut errors, + ); + } + + errors +} + +fn check_function_type_matches_expected_type( + expected: &Type, + actual: &Type, + method_name: &str, + actual_parameters: &Parameters, + span: Span, + trait_name: &str, + errors: &mut Vec, +) { + let mut bindings = TypeBindings::new(); + // Shouldn't need to unify envs, they should always be equal since they're both free functions + if let (Type::Function(params_a, ret_a, _env_a), Type::Function(params_b, ret_b, _env_b)) = + (expected, actual) + { + if params_a.len() == params_b.len() { + for (i, (a, b)) in params_a.iter().zip(params_b.iter()).enumerate() { + if a.try_unify(b, &mut bindings).is_err() { + errors.push(TypeCheckError::TraitMethodParameterTypeMismatch { + method_name: method_name.to_string(), + expected_typ: a.to_string(), + actual_typ: b.to_string(), + parameter_span: actual_parameters.0[i].0.span(), + parameter_index: i + 1, + }); + } + } + + if ret_b.try_unify(ret_a, &mut bindings).is_err() { + errors.push(TypeCheckError::TypeMismatch { + expected_typ: ret_a.to_string(), + expr_typ: ret_b.to_string(), + expr_span: span, + }); + } + } else { + errors.push(TypeCheckError::MismatchTraitImplNumParameters { + actual_num_parameters: params_b.len(), + expected_num_parameters: params_a.len(), + trait_name: trait_name.to_string(), + method_name: method_name.to_string(), + span, + }); + } + } + + // If result bindings is not empty, a type variable was bound which means the two + // signatures were not a perfect match. Note that this relies on us already binding + // all the expected generics to each other prior to this check. + if !bindings.is_empty() { + let expected_typ = expected.to_string(); + let expr_typ = actual.to_string(); + errors.push(TypeCheckError::TypeMismatch { expected_typ, expr_typ, expr_span: span }); + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs index 698114cfb5e..a50b8949971 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs @@ -12,10 +12,7 @@ use crate::{ hir::{ comptime::{Interpreter, Value}, def_map::ModuleDefId, - resolution::{ - errors::ResolverError, - resolver::{verify_mutable_reference, SELF_TYPE_NAME, WILDCARD_TYPE}, - }, + resolution::errors::ResolverError, type_check::{NoMatchingImplFoundError, Source, TypeCheckError}, }, hir_def::{ @@ -27,18 +24,20 @@ use crate::{ traits::TraitConstraint, }, macros_api::{ - HirExpression, HirLiteral, HirStatement, Path, PathKind, SecondaryAttribute, Signedness, - UnaryOp, UnresolvedType, UnresolvedTypeData, + HirExpression, HirLiteral, HirStatement, NodeInterner, Path, PathKind, SecondaryAttribute, + Signedness, UnaryOp, UnresolvedType, UnresolvedTypeData, }, node_interner::{ - DefinitionKind, DependencyId, ExprId, GlobalId, ReferenceId, TraitId, TraitImplKind, - TraitMethodId, + DefinitionKind, DependencyId, ExprId, GlobalId, TraitId, TraitImplKind, TraitMethodId, }, Generics, Kind, ResolvedGeneric, Type, TypeBinding, TypeVariable, TypeVariableKind, }; use super::{lints, Elaborator}; +pub const SELF_TYPE_NAME: &str = "Self"; +pub const WILDCARD_TYPE: &str = "_"; + impl<'context> Elaborator<'context> { /// Translates an UnresolvedType to a Type with a `TypeKind::Normal` pub(super) fn resolve_type(&mut self, typ: UnresolvedType) -> Type { @@ -58,12 +57,16 @@ impl<'context> Elaborator<'context> { use crate::ast::UnresolvedTypeData::*; let span = typ.span; - let (is_self_type_name, is_synthetic) = if let Named(ref named_path, _, synthetic) = typ.typ - { - (named_path.last_segment().is_self_type_name(), synthetic) - } else { - (false, false) - }; + let (named_path_span, is_self_type_name, is_synthetic) = + if let Named(ref named_path, _, synthetic) = typ.typ { + ( + Some(named_path.last_segment().span()), + named_path.last_segment().is_self_type_name(), + synthetic, + ) + } else { + (None, false, false) + }; let resolved_type = match typ.typ { FieldElement => Type::FieldElement, @@ -154,30 +157,23 @@ impl<'context> Elaborator<'context> { }; if let Some(unresolved_span) = typ.span { + let location = Location::new(named_path_span.unwrap_or(unresolved_span), self.file); + match resolved_type { Type::Struct(ref struct_type, _) => { // Record the location of the type reference - self.interner.push_type_ref_location( - resolved_type.clone(), - Location::new(unresolved_span, self.file), - ); + self.interner.push_type_ref_location(resolved_type.clone(), location); if !is_synthetic { - let referenced = ReferenceId::Struct(struct_type.borrow().id); - let reference = ReferenceId::Reference( - Location::new(unresolved_span, self.file), + self.interner.add_struct_reference( + struct_type.borrow().id, + location, is_self_type_name, ); - self.interner.add_reference(referenced, reference); } } Type::Alias(ref alias_type, _) => { - let referenced = ReferenceId::Alias(alias_type.borrow().id); - let reference = ReferenceId::Reference( - Location::new(unresolved_span, self.file), - is_self_type_name, - ); - self.interner.add_reference(referenced, reference); + self.interner.add_alias_reference(alias_type.borrow().id, location); } _ => (), } @@ -369,10 +365,8 @@ impl<'context> Elaborator<'context> { self.interner.add_global_dependency(current_item, id); } - let referenced = ReferenceId::Global(id); - let reference = - ReferenceId::Reference(Location::new(path.span(), self.file), false); - self.interner.add_reference(referenced, reference); + let reference_location = Location::new(path.span(), self.file); + self.interner.add_global_reference(id, reference_location); Some(Type::Constant(self.eval_global_as_array_length(id, path))) } @@ -670,57 +664,6 @@ impl<'context> Elaborator<'context> { } } - pub(super) fn type_check_prefix_operand( - &mut self, - op: &crate::ast::UnaryOp, - rhs_type: &Type, - span: Span, - ) -> Type { - let unify = |this: &mut Self, expected| { - this.unify(rhs_type, &expected, || TypeCheckError::TypeMismatch { - expr_typ: rhs_type.to_string(), - expected_typ: expected.to_string(), - expr_span: span, - }); - expected - }; - - match op { - crate::ast::UnaryOp::Minus => { - if rhs_type.is_unsigned() { - self.push_err(TypeCheckError::InvalidUnaryOp { - kind: rhs_type.to_string(), - span, - }); - } - let expected = self.polymorphic_integer_or_field(); - self.unify(rhs_type, &expected, || TypeCheckError::InvalidUnaryOp { - kind: rhs_type.to_string(), - span, - }); - expected - } - crate::ast::UnaryOp::Not => { - let rhs_type = rhs_type.follow_bindings(); - - // `!` can work on booleans or integers - if matches!(rhs_type, Type::Integer(..)) { - return rhs_type; - } - - unify(self, Type::Bool) - } - crate::ast::UnaryOp::MutableReference => { - Type::MutableReference(Box::new(rhs_type.follow_bindings())) - } - crate::ast::UnaryOp::Dereference { implicitly_added: _ } => { - let element_type = self.interner.next_type_variable(); - unify(self, Type::MutableReference(Box::new(element_type.clone()))); - element_type - } - } - } - /// Insert as many dereference operations as necessary to automatically dereference a method /// call object to its base value type T. pub(super) fn insert_auto_dereferences(&mut self, object: ExprId, typ: Type) -> (ExprId, Type) { @@ -730,6 +673,7 @@ impl<'context> Elaborator<'context> { let object = self.interner.push_expr(HirExpression::Prefix(HirPrefixExpression { operator: UnaryOp::Dereference { implicitly_added: true }, rhs: object, + trait_method_id: None, })); self.interner.push_expr_type(object, element.as_ref().clone()); self.interner.push_expr_location(object, location.span, location.file); @@ -1073,6 +1017,84 @@ impl<'context> Elaborator<'context> { } } + // Given a unary operator and a type, this method will produce the output type + // and a boolean indicating whether to use the trait impl corresponding to the operator + // or not. A value of false indicates the caller to use a primitive operation for this + // operator, while a true value indicates a user-provided trait impl is required. + pub(super) fn prefix_operand_type_rules( + &mut self, + op: &UnaryOp, + rhs_type: &Type, + span: Span, + ) -> Result<(Type, bool), TypeCheckError> { + use Type::*; + + match op { + crate::ast::UnaryOp::Minus | crate::ast::UnaryOp::Not => { + match rhs_type { + // An error type will always return an error + Error => Ok((Error, false)), + Alias(alias, args) => { + let alias = alias.borrow().get_type(args); + self.prefix_operand_type_rules(op, &alias, span) + } + + // Matches on TypeVariable must be first so that we follow any type + // bindings. + TypeVariable(int, _) => { + if let TypeBinding::Bound(binding) = &*int.borrow() { + return self.prefix_operand_type_rules(op, binding, span); + } + + // The `!` prefix operator is not valid for Field, so if this is a numeric + // type we constrain it to just (non-Field) integer types. + if matches!(op, crate::ast::UnaryOp::Not) && rhs_type.is_numeric() { + let integer_type = Type::polymorphic_integer(self.interner); + self.unify(rhs_type, &integer_type, || { + TypeCheckError::InvalidUnaryOp { kind: rhs_type.to_string(), span } + }); + } + + Ok((rhs_type.clone(), !rhs_type.is_numeric())) + } + Integer(sign_x, bit_width_x) => { + if *op == UnaryOp::Minus && *sign_x == Signedness::Unsigned { + return Err(TypeCheckError::InvalidUnaryOp { + kind: rhs_type.to_string(), + span, + }); + } + Ok((Integer(*sign_x, *bit_width_x), false)) + } + // The result of a Field is always a witness + FieldElement => { + if *op == UnaryOp::Not { + return Err(TypeCheckError::FieldNot { span }); + } + Ok((FieldElement, false)) + } + + Bool => Ok((Bool, false)), + + _ => Ok((rhs_type.clone(), true)), + } + } + crate::ast::UnaryOp::MutableReference => { + Ok((Type::MutableReference(Box::new(rhs_type.follow_bindings())), false)) + } + crate::ast::UnaryOp::Dereference { implicitly_added: _ } => { + let element_type = self.interner.next_type_variable(); + let expected = Type::MutableReference(Box::new(element_type.clone())); + self.unify(rhs_type, &expected, || TypeCheckError::TypeMismatch { + expr_typ: rhs_type.to_string(), + expected_typ: expected.to_string(), + expr_span: span, + }); + Ok((element_type, false)) + } + } + } + /// Prerequisite: verify_trait_constraint of the operator's trait constraint. /// /// Although by this point the operator is expected to already have a trait impl, @@ -1140,6 +1162,7 @@ impl<'context> Elaborator<'context> { *access_lhs = this.interner.push_expr(HirExpression::Prefix(HirPrefixExpression { operator: crate::ast::UnaryOp::Dereference { implicitly_added: true }, rhs: old_lhs, + trait_method_id: None, })); this.interner.push_expr_type(old_lhs, lhs_type); this.interner.push_expr_type(*access_lhs, element); @@ -1362,6 +1385,7 @@ impl<'context> Elaborator<'context> { self.interner.push_expr(HirExpression::Prefix(HirPrefixExpression { operator: UnaryOp::MutableReference, rhs: *object, + trait_method_id: None, })); self.interner.push_expr_type(new_object, new_type); self.interner.push_expr_location(new_object, location.span, location.file); @@ -1593,3 +1617,29 @@ impl<'context> Elaborator<'context> { context.trait_constraints.push((constraint, expr_id)); } } + +/// Gives an error if a user tries to create a mutable reference +/// to an immutable variable. +fn verify_mutable_reference(interner: &NodeInterner, rhs: ExprId) -> Result<(), ResolverError> { + match interner.expression(&rhs) { + HirExpression::MemberAccess(member_access) => { + verify_mutable_reference(interner, member_access.lhs) + } + HirExpression::Index(_) => { + let span = interner.expr_span(&rhs); + Err(ResolverError::MutableReferenceToArrayElement { span }) + } + HirExpression::Ident(ident, _) => { + if let Some(definition) = interner.try_definition(ident.id) { + if !definition.mutable { + return Err(ResolverError::MutableReferenceToImmutableVariable { + span: interner.expr_span(&rhs), + variable: definition.name.clone(), + }); + } + } + Ok(()) + } + _ => Ok(()), + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/errors.rs index 25ff50085fe..0472b0040e5 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/errors.rs @@ -1,3 +1,4 @@ +use std::fmt::Display; use std::rc::Rc; use crate::{ @@ -45,6 +46,7 @@ pub enum InterpreterError { NonStructInConstructor { typ: Type, location: Location }, CannotInlineMacro { value: Value, location: Location }, UnquoteFoundDuringEvaluation { location: Location }, + DebugEvaluateComptime { diagnostic: CustomDiagnostic, location: Location }, FailedToParseMacro { error: ParserError, tokens: Rc, rule: &'static str, file: FileId }, UnsupportedTopLevelItemUnquote { item: String, location: Location }, NonComptimeFnCallInSameCrate { function: String, location: Location }, @@ -117,7 +119,8 @@ impl InterpreterError { | InterpreterError::NoImpl { location, .. } | InterpreterError::ImplMethodTypeMismatch { location, .. } | InterpreterError::BreakNotInLoop { location, .. } - | InterpreterError::ContinueNotInLoop { location, .. } => *location, + | InterpreterError::DebugEvaluateComptime { location, .. } => *location, + InterpreterError::ContinueNotInLoop { location, .. } => *location, InterpreterError::FailedToParseMacro { error, file, .. } => { Location::new(error.span(), *file) } @@ -129,6 +132,20 @@ impl InterpreterError { } } } + + pub(crate) fn debug_evaluate_comptime(expr: impl Display, location: Location) -> Self { + let mut formatted_result = format!("{}", expr); + // if multi-line, display on a separate line from the message + if formatted_result.contains('\n') { + formatted_result.insert(0, '\n'); + } + let diagnostic = CustomDiagnostic::simple_info( + "`comptime` expression ran:".to_string(), + format!("After evaluation: {}", formatted_result), + location.span, + ); + InterpreterError::DebugEvaluateComptime { diagnostic, location } + } } impl<'a> From<&'a InterpreterError> for CustomDiagnostic { @@ -291,6 +308,7 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { let secondary = "This is a bug".into(); CustomDiagnostic::simple_error(msg, secondary, location.span) } + InterpreterError::DebugEvaluateComptime { diagnostic, .. } => diagnostic.clone(), InterpreterError::FailedToParseMacro { error, tokens, rule, file: _ } => { let message = format!("Failed to parse macro's token stream into {rule}"); let tokens = vecmap(&tokens.0, ToString::to_string).join(" "); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs new file mode 100644 index 00000000000..22763c9cb64 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs @@ -0,0 +1,428 @@ +use iter_extended::vecmap; +use noirc_errors::{Span, Spanned}; + +use crate::ast::{ + ArrayLiteral, AssignStatement, BlockExpression, CallExpression, CastExpression, ConstrainKind, + ConstructorExpression, ExpressionKind, ForLoopStatement, ForRange, Ident, IfExpression, + IndexExpression, InfixExpression, LValue, Lambda, LetStatement, Literal, + MemberAccessExpression, MethodCallExpression, Path, Pattern, PrefixExpression, UnresolvedType, + UnresolvedTypeData, UnresolvedTypeExpression, +}; +use crate::ast::{ConstrainStatement, Expression, Statement, StatementKind}; +use crate::hir_def::expr::{HirArrayLiteral, HirBlockExpression, HirExpression, HirIdent}; +use crate::hir_def::stmt::{HirLValue, HirPattern, HirStatement}; +use crate::hir_def::types::{Type, TypeBinding, TypeVariableKind}; +use crate::macros_api::HirLiteral; +use crate::node_interner::{ExprId, NodeInterner, StmtId}; + +// TODO: +// - Full path for idents & types +// - Assert/AssertEq information lost +// - The type name span is lost in constructor patterns & expressions +// - All type spans are lost +// - Type::TypeVariable has no equivalent in the Ast + +impl HirStatement { + pub fn to_display_ast(&self, interner: &NodeInterner, span: Span) -> Statement { + let kind = match self { + HirStatement::Let(let_stmt) => { + let pattern = let_stmt.pattern.to_display_ast(interner); + let r#type = interner.id_type(let_stmt.expression).to_display_ast(); + let expression = let_stmt.expression.to_display_ast(interner); + StatementKind::Let(LetStatement { + pattern, + r#type, + expression, + comptime: false, + attributes: Vec::new(), + }) + } + HirStatement::Constrain(constrain) => { + let expr = constrain.0.to_display_ast(interner); + let message = constrain.2.map(|message| message.to_display_ast(interner)); + + // TODO: Find difference in usage between Assert & AssertEq + StatementKind::Constrain(ConstrainStatement(expr, message, ConstrainKind::Assert)) + } + HirStatement::Assign(assign) => StatementKind::Assign(AssignStatement { + lvalue: assign.lvalue.to_display_ast(interner), + expression: assign.expression.to_display_ast(interner), + }), + HirStatement::For(for_stmt) => StatementKind::For(ForLoopStatement { + identifier: for_stmt.identifier.to_display_ast(interner), + range: ForRange::Range( + for_stmt.start_range.to_display_ast(interner), + for_stmt.end_range.to_display_ast(interner), + ), + block: for_stmt.block.to_display_ast(interner), + span, + }), + HirStatement::Break => StatementKind::Break, + HirStatement::Continue => StatementKind::Continue, + HirStatement::Expression(expr) => { + StatementKind::Expression(expr.to_display_ast(interner)) + } + HirStatement::Semi(expr) => StatementKind::Semi(expr.to_display_ast(interner)), + HirStatement::Error => StatementKind::Error, + HirStatement::Comptime(statement) => { + StatementKind::Comptime(Box::new(statement.to_display_ast(interner))) + } + }; + + Statement { kind, span } + } +} + +impl StmtId { + /// Convert to AST for display (some details lost) + pub fn to_display_ast(self, interner: &NodeInterner) -> Statement { + let statement = interner.statement(&self); + let span = interner.statement_span(self); + + statement.to_display_ast(interner, span) + } +} + +impl HirExpression { + /// Convert to AST for display (some details lost) + pub fn to_display_ast(&self, interner: &NodeInterner, span: Span) -> Expression { + let kind = match self { + HirExpression::Ident(ident, generics) => { + let path = Path::from_ident(ident.to_display_ast(interner)); + ExpressionKind::Variable( + path, + generics.as_ref().map(|option| { + option.iter().map(|generic| generic.to_display_ast()).collect() + }), + ) + } + HirExpression::Literal(HirLiteral::Array(array)) => { + let array = array.to_display_ast(interner, span); + ExpressionKind::Literal(Literal::Array(array)) + } + HirExpression::Literal(HirLiteral::Slice(array)) => { + let array = array.to_display_ast(interner, span); + ExpressionKind::Literal(Literal::Slice(array)) + } + HirExpression::Literal(HirLiteral::Bool(value)) => { + ExpressionKind::Literal(Literal::Bool(*value)) + } + HirExpression::Literal(HirLiteral::Integer(value, sign)) => { + ExpressionKind::Literal(Literal::Integer(*value, *sign)) + } + HirExpression::Literal(HirLiteral::Str(string)) => { + ExpressionKind::Literal(Literal::Str(string.clone())) + } + HirExpression::Literal(HirLiteral::FmtStr(string, _exprs)) => { + // TODO: Is throwing away the exprs here valid? + ExpressionKind::Literal(Literal::FmtStr(string.clone())) + } + HirExpression::Literal(HirLiteral::Unit) => ExpressionKind::Literal(Literal::Unit), + HirExpression::Block(expr) => ExpressionKind::Block(expr.to_display_ast(interner)), + HirExpression::Prefix(prefix) => ExpressionKind::Prefix(Box::new(PrefixExpression { + operator: prefix.operator, + rhs: prefix.rhs.to_display_ast(interner), + })), + HirExpression::Infix(infix) => ExpressionKind::Infix(Box::new(InfixExpression { + lhs: infix.lhs.to_display_ast(interner), + operator: Spanned::from(infix.operator.location.span, infix.operator.kind), + rhs: infix.rhs.to_display_ast(interner), + })), + HirExpression::Index(index) => ExpressionKind::Index(Box::new(IndexExpression { + collection: index.collection.to_display_ast(interner), + index: index.index.to_display_ast(interner), + })), + HirExpression::Constructor(constructor) => { + let type_name = constructor.r#type.borrow().name.to_string(); + let type_name = Path::from_single(type_name, span); + let fields = vecmap(constructor.fields.clone(), |(name, expr): (Ident, ExprId)| { + (name, expr.to_display_ast(interner)) + }); + let struct_type = None; + + ExpressionKind::Constructor(Box::new(ConstructorExpression { + type_name, + fields, + struct_type, + })) + } + HirExpression::MemberAccess(access) => { + ExpressionKind::MemberAccess(Box::new(MemberAccessExpression { + lhs: access.lhs.to_display_ast(interner), + rhs: access.rhs.clone(), + })) + } + HirExpression::Call(call) => { + let func = Box::new(call.func.to_display_ast(interner)); + let arguments = vecmap(call.arguments.clone(), |arg| arg.to_display_ast(interner)); + let is_macro_call = false; + ExpressionKind::Call(Box::new(CallExpression { func, arguments, is_macro_call })) + } + HirExpression::MethodCall(method_call) => { + ExpressionKind::MethodCall(Box::new(MethodCallExpression { + object: method_call.object.to_display_ast(interner), + method_name: method_call.method.clone(), + arguments: vecmap(method_call.arguments.clone(), |arg| { + arg.to_display_ast(interner) + }), + generics: method_call.generics.clone().map(|option| { + option.iter().map(|generic| generic.to_display_ast()).collect() + }), + is_macro_call: false, + })) + } + HirExpression::Cast(cast) => { + let lhs = cast.lhs.to_display_ast(interner); + let r#type = cast.r#type.to_display_ast(); + ExpressionKind::Cast(Box::new(CastExpression { lhs, r#type })) + } + HirExpression::If(if_expr) => ExpressionKind::If(Box::new(IfExpression { + condition: if_expr.condition.to_display_ast(interner), + consequence: if_expr.consequence.to_display_ast(interner), + alternative: if_expr.alternative.map(|expr| expr.to_display_ast(interner)), + })), + HirExpression::Tuple(fields) => { + ExpressionKind::Tuple(vecmap(fields, |field| field.to_display_ast(interner))) + } + HirExpression::Lambda(lambda) => { + let parameters = vecmap(lambda.parameters.clone(), |(pattern, typ)| { + (pattern.to_display_ast(interner), typ.to_display_ast()) + }); + let return_type = lambda.return_type.to_display_ast(); + let body = lambda.body.to_display_ast(interner); + ExpressionKind::Lambda(Box::new(Lambda { parameters, return_type, body })) + } + HirExpression::Error => ExpressionKind::Error, + HirExpression::Comptime(block) => { + ExpressionKind::Comptime(block.to_display_ast(interner), span) + } + HirExpression::Quote(block) => ExpressionKind::Quote(block.clone()), + + // A macro was evaluated here: return the quoted result + HirExpression::Unquote(block) => ExpressionKind::Quote(block.clone()), + }; + + Expression::new(kind, span) + } +} + +impl ExprId { + /// Convert to AST for display (some details lost) + pub fn to_display_ast(self, interner: &NodeInterner) -> Expression { + let expression = interner.expression(&self); + // TODO: empty 0 span + let span = interner.try_expr_span(&self).unwrap_or_else(|| Span::empty(0)); + expression.to_display_ast(interner, span) + } +} + +impl HirPattern { + /// Convert to AST for display (some details lost) + fn to_display_ast(&self, interner: &NodeInterner) -> Pattern { + match self { + HirPattern::Identifier(ident) => Pattern::Identifier(ident.to_display_ast(interner)), + HirPattern::Mutable(pattern, location) => { + let pattern = Box::new(pattern.to_display_ast(interner)); + Pattern::Mutable(pattern, location.span, false) + } + HirPattern::Tuple(patterns, location) => { + let patterns = vecmap(patterns, |pattern| pattern.to_display_ast(interner)); + Pattern::Tuple(patterns, location.span) + } + HirPattern::Struct(typ, patterns, location) => { + let patterns = vecmap(patterns, |(name, pattern)| { + (name.clone(), pattern.to_display_ast(interner)) + }); + let name = match typ.follow_bindings() { + Type::Struct(struct_def, _) => { + let struct_def = struct_def.borrow(); + struct_def.name.0.contents.clone() + } + // This pass shouldn't error so if the type isn't a struct we just get a string + // representation of any other type and use that. We're relying on name + // resolution to fail later when this Ast is re-converted to Hir. + other => other.to_string(), + }; + // The name span is lost here + let path = Path::from_single(name, location.span); + Pattern::Struct(path, patterns, location.span) + } + } + } +} + +impl HirIdent { + /// Convert to AST for display (some details lost) + fn to_display_ast(&self, interner: &NodeInterner) -> Ident { + let name = interner.definition_name(self.id).to_owned(); + Ident(Spanned::from(self.location.span, name)) + } +} + +impl Type { + /// Convert to AST for display (some details lost) + fn to_display_ast(&self) -> UnresolvedType { + let typ = match self { + Type::FieldElement => UnresolvedTypeData::FieldElement, + Type::Array(length, element) => { + let length = length.to_type_expression(); + let element = Box::new(element.to_display_ast()); + UnresolvedTypeData::Array(length, element) + } + Type::Slice(element) => { + let element = Box::new(element.to_display_ast()); + UnresolvedTypeData::Slice(element) + } + Type::Integer(sign, bit_size) => UnresolvedTypeData::Integer(*sign, *bit_size), + Type::Bool => UnresolvedTypeData::Bool, + Type::String(length) => { + let length = length.to_type_expression(); + UnresolvedTypeData::String(length) + } + Type::FmtString(length, element) => { + let length = length.to_type_expression(); + let element = Box::new(element.to_display_ast()); + UnresolvedTypeData::FormatString(length, element) + } + Type::Unit => UnresolvedTypeData::Unit, + Type::Tuple(fields) => { + let fields = vecmap(fields, |field| field.to_display_ast()); + UnresolvedTypeData::Tuple(fields) + } + Type::Struct(def, generics) => { + let struct_def = def.borrow(); + let generics = vecmap(generics, |generic| generic.to_display_ast()); + let name = Path::from_ident(struct_def.name.clone()); + UnresolvedTypeData::Named(name, generics, false) + } + Type::Alias(type_def, generics) => { + // Keep the alias name instead of expanding this in case the + // alias' definition was changed + let type_def = type_def.borrow(); + let generics = vecmap(generics, |generic| generic.to_display_ast()); + let name = Path::from_ident(type_def.name.clone()); + UnresolvedTypeData::Named(name, generics, false) + } + Type::TypeVariable(binding, kind) => { + match &*binding.borrow() { + TypeBinding::Bound(typ) => return typ.to_display_ast(), + TypeBinding::Unbound(id) => { + let expression = match kind { + // TODO: fix span or make Option + TypeVariableKind::Constant(value) => { + UnresolvedTypeExpression::Constant(*value, Span::empty(0)) + } + other_kind => { + let name = format!("var_{:?}_{}", other_kind, id); + + // TODO: fix span or make Option + let path = Path::from_single(name, Span::empty(0)); + UnresolvedTypeExpression::Variable(path) + } + }; + + UnresolvedTypeData::Expression(expression) + } + } + } + Type::TraitAsType(_, name, generics) => { + let generics = vecmap(generics, |generic| generic.to_display_ast()); + let name = Path::from_single(name.as_ref().clone(), Span::default()); + UnresolvedTypeData::TraitAsType(name, generics) + } + Type::NamedGeneric(_var, name, _kind) => { + let name = Path::from_single(name.as_ref().clone(), Span::default()); + UnresolvedTypeData::TraitAsType(name, Vec::new()) + } + Type::Function(args, ret, env) => { + let args = vecmap(args, |arg| arg.to_display_ast()); + let ret = Box::new(ret.to_display_ast()); + let env = Box::new(env.to_display_ast()); + UnresolvedTypeData::Function(args, ret, env) + } + Type::MutableReference(element) => { + let element = Box::new(element.to_display_ast()); + UnresolvedTypeData::MutableReference(element) + } + // Type::Forall is only for generic functions which don't store a type + // in their Ast so they don't need to call to_display_ast for their Forall type. + // Since there is no UnresolvedTypeData equivalent for Type::Forall, we use + // this to ignore this case since it shouldn't be needed anyway. + Type::Forall(_, typ) => return typ.to_display_ast(), + Type::Constant(_) => panic!("Type::Constant where a type was expected: {self:?}"), + Type::Quoted(quoted_type) => UnresolvedTypeData::Quoted(*quoted_type), + Type::Error => UnresolvedTypeData::Error, + }; + + UnresolvedType { typ, span: None } + } + + /// Convert to AST for display (some details lost) + fn to_type_expression(&self) -> UnresolvedTypeExpression { + let span = Span::default(); + + match self.follow_bindings() { + Type::Constant(length) => UnresolvedTypeExpression::Constant(length, span), + Type::NamedGeneric(_var, name, _kind) => { + let path = Path::from_single(name.as_ref().clone(), span); + UnresolvedTypeExpression::Variable(path) + } + // TODO: This should be turned into a proper error. + other => panic!("Cannot represent {other:?} as type expression"), + } + } +} + +impl HirLValue { + /// Convert to AST for display (some details lost) + fn to_display_ast(&self, interner: &NodeInterner) -> LValue { + match self { + HirLValue::Ident(ident, _) => LValue::Ident(ident.to_display_ast(interner)), + HirLValue::MemberAccess { object, field_name, field_index: _, typ: _, location } => { + let object = Box::new(object.to_display_ast(interner)); + LValue::MemberAccess { object, field_name: field_name.clone(), span: location.span } + } + HirLValue::Index { array, index, typ: _, location } => { + let array = Box::new(array.to_display_ast(interner)); + let index = index.to_display_ast(interner); + LValue::Index { array, index, span: location.span } + } + HirLValue::Dereference { lvalue, element_type: _, location } => { + let lvalue = Box::new(lvalue.to_display_ast(interner)); + LValue::Dereference(lvalue, location.span) + } + } + } +} + +impl HirArrayLiteral { + /// Convert to AST for display (some details lost) + fn to_display_ast(&self, interner: &NodeInterner, span: Span) -> ArrayLiteral { + match self { + HirArrayLiteral::Standard(elements) => { + ArrayLiteral::Standard(vecmap(elements, |element| element.to_display_ast(interner))) + } + HirArrayLiteral::Repeated { repeated_element, length } => { + let repeated_element = Box::new(repeated_element.to_display_ast(interner)); + let length = match length { + Type::Constant(length) => { + let literal = Literal::Integer((*length as u128).into(), false); + let kind = ExpressionKind::Literal(literal); + Box::new(Expression::new(kind, span)) + } + other => panic!("Cannot convert non-constant type for repeated array literal from Hir -> Ast: {other:?}"), + }; + ArrayLiteral::Repeated { repeated_element, length } + } + } + } +} + +impl HirBlockExpression { + /// Convert to AST for display (some details lost) + fn to_display_ast(&self, interner: &NodeInterner) -> BlockExpression { + let statements = + vecmap(self.statements.clone(), |statement| statement.to_display_ast(interner)); + BlockExpression { statements } + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter.rs index 605a35780ed..cbea5cd158e 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter.rs @@ -1,6 +1,7 @@ use std::{collections::hash_map::Entry, rc::Rc}; use acvm::{acir::AcirField, FieldElement}; +use fm::FileId; use im::Vector; use iter_extended::try_vecmap; use noirc_errors::Location; @@ -19,7 +20,7 @@ use crate::{ hir_def::{ expr::{ HirArrayLiteral, HirBlockExpression, HirCallExpression, HirCastExpression, - HirConstructorExpression, HirIdent, HirIfExpression, HirIndexExpression, + HirConstructorExpression, HirExpression, HirIdent, HirIfExpression, HirIndexExpression, HirInfixExpression, HirLambda, HirMemberAccess, HirMethodCallExpression, HirPrefixExpression, }, @@ -28,7 +29,7 @@ use crate::{ HirPattern, }, }, - macros_api::{HirExpression, HirLiteral, HirStatement, NodeInterner}, + macros_api::{HirLiteral, HirStatement, NodeInterner}, node_interner::{DefinitionId, DefinitionKind, ExprId, FuncId, StmtId}, Shared, Type, TypeBinding, TypeBindings, TypeVariableKind, }; @@ -51,6 +52,10 @@ pub struct Interpreter<'interner> { crate_id: CrateId, + /// The scope of --debug-comptime, or None if unset + pub(super) debug_comptime_in_file: Option, + pub(super) debug_comptime_evaluations: &'interner mut Vec, + in_loop: bool, } @@ -60,8 +65,17 @@ impl<'a> Interpreter<'a> { interner: &'a mut NodeInterner, scopes: &'a mut Vec>, crate_id: CrateId, + debug_comptime_in_file: Option, + debug_comptime_evaluations: &'a mut Vec, ) -> Self { - Self { interner, scopes, crate_id, in_loop: false } + Self { + interner, + scopes, + crate_id, + debug_comptime_in_file, + debug_comptime_evaluations, + in_loop: false, + } } pub(crate) fn call_function( @@ -115,7 +129,11 @@ impl<'a> Interpreter<'a> { self.define_pattern(parameter, typ, argument, arg_location)?; } - let function_body = self.interner.function(&function).as_expr(); + let function_body = self.interner.function(&function).try_as_expr().ok_or_else(|| { + let function = self.interner.function_name(&function).to_owned(); + InterpreterError::NonComptimeFnCallInSameCrate { function, location } + })?; + let result = self.evaluate(function_body)?; self.exit_function(previous_state); @@ -227,6 +245,8 @@ impl<'a> Interpreter<'a> { Ok(()) } HirPattern::Mutable(pattern, _) => { + // Create a mutable reference to store to + let argument = Value::Pointer(Shared::new(argument), true); self.define_pattern(pattern, typ, argument, location) } HirPattern::Tuple(pattern_fields, _) => match (argument, typ) { @@ -320,8 +340,19 @@ impl<'a> Interpreter<'a> { } } - /// Evaluate an expression and return the result + /// Evaluate an expression and return the result. + /// This will automatically dereference a mutable variable if used. pub fn evaluate(&mut self, id: ExprId) -> IResult { + match self.evaluate_no_dereference(id)? { + Value::Pointer(elem, true) => Ok(elem.borrow().clone()), + other => Ok(other), + } + } + + /// Evaluating a mutable variable will dereference it automatically. + /// This function should be used when that is not desired - e.g. when + /// compiling a `&mut var` expression to grab the original reference. + fn evaluate_no_dereference(&mut self, id: ExprId) -> IResult { match self.interner.expression(&id) { HirExpression::Ident(ident, _) => self.evaluate_ident(ident, id), HirExpression::Literal(literal) => self.evaluate_literal(literal, id), @@ -578,7 +609,10 @@ impl<'a> Interpreter<'a> { } fn evaluate_prefix(&mut self, prefix: HirPrefixExpression, id: ExprId) -> IResult { - let rhs = self.evaluate(prefix.rhs)?; + let rhs = match prefix.operator { + UnaryOp::MutableReference => self.evaluate_no_dereference(prefix.rhs)?, + _ => self.evaluate(prefix.rhs)?, + }; self.evaluate_prefix_with_value(rhs, prefix.operator, id) } @@ -620,9 +654,17 @@ impl<'a> Interpreter<'a> { Err(InterpreterError::InvalidValueForUnary { value, location, operator: "not" }) } }, - UnaryOp::MutableReference => Ok(Value::Pointer(Shared::new(rhs))), + UnaryOp::MutableReference => { + // If this is a mutable variable (auto_deref = true), turn this into an explicit + // mutable reference just by switching the value of `auto_deref`. Otherwise, wrap + // the value in a fresh reference. + match rhs { + Value::Pointer(elem, true) => Ok(Value::Pointer(elem, false)), + other => Ok(Value::Pointer(Shared::new(other), false)), + } + } UnaryOp::Dereference { implicitly_added: _ } => match rhs { - Value::Pointer(element) => Ok(element.borrow().clone()), + Value::Pointer(element, _) => Ok(element.borrow().clone()), value => { let location = self.interner.expr_location(&id); Err(InterpreterError::NonPointerDereferenced { value, location }) @@ -1289,7 +1331,7 @@ impl<'a> Interpreter<'a> { HirLValue::Ident(ident, typ) => self.mutate(ident.id, rhs, ident.location), HirLValue::Dereference { lvalue, element_type: _, location } => { match self.evaluate_lvalue(&lvalue)? { - Value::Pointer(value) => { + Value::Pointer(value, _) => { *value.borrow_mut() = rhs; Ok(()) } @@ -1339,10 +1381,13 @@ impl<'a> Interpreter<'a> { fn evaluate_lvalue(&mut self, lvalue: &HirLValue) -> IResult { match lvalue { - HirLValue::Ident(ident, _) => self.lookup(ident), + HirLValue::Ident(ident, _) => match self.lookup(ident)? { + Value::Pointer(elem, true) => Ok(elem.borrow().clone()), + other => Ok(other), + }, HirLValue::Dereference { lvalue, element_type: _, location } => { match self.evaluate_lvalue(lvalue)? { - Value::Pointer(value) => Ok(value.borrow().clone()), + Value::Pointer(value, _) => Ok(value.borrow().clone()), value => { Err(InterpreterError::NonPointerDereferenced { value, location: *location }) } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index 399d9905269..017ea360562 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -1,11 +1,17 @@ -use std::rc::Rc; +use std::{ + hash::{Hash, Hasher}, + rc::Rc, +}; +use acvm::{AcirField, FieldElement}; +use chumsky::Parser; use noirc_errors::Location; use crate::{ - ast::IntegerBitSize, + ast::{IntegerBitSize, TraitBound}, hir::comptime::{errors::IResult, InterpreterError, Value}, macros_api::{NodeInterner, Signedness}, + parser, token::{SpannedToken, Token, Tokens}, QuotedType, Type, }; @@ -20,6 +26,11 @@ pub(super) fn call_builtin( "array_len" => array_len(interner, arguments, location), "as_slice" => as_slice(interner, arguments, location), "is_unconstrained" => Ok(Value::Bool(true)), + "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), "slice_insert" => slice_insert(interner, arguments, location), "slice_pop_back" => slice_pop_back(interner, arguments, location), "slice_pop_front" => slice_pop_front(interner, arguments, location), @@ -29,6 +40,9 @@ pub(super) fn call_builtin( "struct_def_as_type" => struct_def_as_type(interner, arguments, location), "struct_def_fields" => struct_def_fields(interner, arguments, location), "struct_def_generics" => struct_def_generics(interner, arguments, location), + "trait_constraint_eq" => trait_constraint_eq(interner, arguments, location), + "trait_constraint_hash" => trait_constraint_hash(interner, arguments, location), + "quoted_as_trait_constraint" => quoted_as_trait_constraint(interner, arguments, location), _ => { let item = format!("Comptime evaluation for builtin function {name}"); Err(InterpreterError::Unimplemented { item, location }) @@ -79,6 +93,26 @@ fn get_u32(value: Value, location: Location) -> IResult { } } +fn get_trait_constraint(value: Value, location: Location) -> IResult { + match value { + Value::TraitConstraint(bound) => Ok(bound), + value => { + let expected = Type::Quoted(QuotedType::TraitConstraint); + Err(InterpreterError::TypeMismatch { expected, value, location }) + } + } +} + +fn get_quoted(value: Value, location: Location) -> IResult> { + match value { + Value::Code(tokens) => Ok(tokens), + value => { + let expected = Type::Quoted(QuotedType::Quoted); + Err(InterpreterError::TypeMismatch { expected, value, location }) + } + } +} + fn array_len( interner: &NodeInterner, mut arguments: Vec<(Value, Location)>, @@ -231,7 +265,7 @@ fn slice_remove( interner: &mut NodeInterner, mut arguments: Vec<(Value, Location)>, location: Location, -) -> Result { +) -> IResult { check_argument_count(2, &arguments, location)?; let index = get_u32(arguments.pop().unwrap().0, location)? as usize; @@ -257,7 +291,7 @@ fn slice_push_front( interner: &mut NodeInterner, mut arguments: Vec<(Value, Location)>, location: Location, -) -> Result { +) -> IResult { check_argument_count(2, &arguments, location)?; let (element, _) = arguments.pop().unwrap(); @@ -270,7 +304,7 @@ fn slice_pop_front( interner: &mut NodeInterner, mut arguments: Vec<(Value, Location)>, location: Location, -) -> Result { +) -> IResult { check_argument_count(1, &arguments, location)?; let (mut values, typ) = get_slice(interner, arguments.pop().unwrap().0, location)?; @@ -284,7 +318,7 @@ fn slice_pop_back( interner: &mut NodeInterner, mut arguments: Vec<(Value, Location)>, location: Location, -) -> Result { +) -> IResult { check_argument_count(1, &arguments, location)?; let (mut values, typ) = get_slice(interner, arguments.pop().unwrap().0, location)?; @@ -298,7 +332,7 @@ fn slice_insert( interner: &mut NodeInterner, mut arguments: Vec<(Value, Location)>, location: Location, -) -> Result { +) -> IResult { check_argument_count(3, &arguments, location)?; let (element, _) = arguments.pop().unwrap(); @@ -307,3 +341,118 @@ fn slice_insert( values.insert(index as usize, element); Ok(Value::Slice(values, typ)) } + +// fn as_trait_constraint(quoted: Quoted) -> TraitConstraint +fn quoted_as_trait_constraint( + _interner: &mut NodeInterner, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + check_argument_count(1, &arguments, location)?; + + let tokens = get_quoted(arguments.pop().unwrap().0, location)?; + let quoted = tokens.as_ref().clone(); + + let trait_bound = parser::trait_bound().parse(quoted).map_err(|mut errors| { + let error = errors.swap_remove(0); + let rule = "a trait constraint"; + InterpreterError::FailedToParseMacro { error, tokens, rule, file: location.file } + })?; + + Ok(Value::TraitConstraint(trait_bound)) +} + +// fn constraint_hash(constraint: TraitConstraint) -> Field +fn trait_constraint_hash( + _interner: &mut NodeInterner, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + check_argument_count(1, &arguments, location)?; + + let bound = get_trait_constraint(arguments.pop().unwrap().0, location)?; + + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + bound.hash(&mut hasher); + let hash = hasher.finish(); + + Ok(Value::Field((hash as u128).into())) +} + +// fn constraint_eq(constraint_a: TraitConstraint, constraint_b: TraitConstraint) -> bool +fn trait_constraint_eq( + _interner: &mut NodeInterner, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + check_argument_count(2, &arguments, location)?; + + let constraint_b = get_trait_constraint(arguments.pop().unwrap().0, location)?; + let constraint_a = get_trait_constraint(arguments.pop().unwrap().0, location)?; + + Ok(Value::Bool(constraint_a == constraint_b)) +} + +fn modulus_be_bits( + _interner: &mut NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + check_argument_count(0, &arguments, location)?; + + let bits = FieldElement::modulus().to_radix_be(2); + let bits_vector = bits.into_iter().map(|bit| Value::U1(bit != 0)).collect(); + + let int_type = Type::Integer(crate::ast::Signedness::Unsigned, IntegerBitSize::One); + let typ = Type::Slice(Box::new(int_type)); + Ok(Value::Slice(bits_vector, typ)) +} + +fn modulus_be_bytes( + _interner: &mut NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + check_argument_count(0, &arguments, location)?; + + let bytes = FieldElement::modulus().to_bytes_be(); + let bytes_vector = bytes.into_iter().map(Value::U8).collect(); + + let int_type = Type::Integer(crate::ast::Signedness::Unsigned, IntegerBitSize::Eight); + let typ = Type::Slice(Box::new(int_type)); + Ok(Value::Slice(bytes_vector, typ)) +} + +fn modulus_le_bits( + interner: &mut NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let Value::Slice(bits, typ) = modulus_be_bits(interner, arguments, location)? else { + unreachable!("modulus_be_bits must return slice") + }; + let reversed_bits = bits.into_iter().rev().collect(); + Ok(Value::Slice(reversed_bits, typ)) +} + +fn modulus_le_bytes( + interner: &mut NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let Value::Slice(bytes, typ) = modulus_be_bytes(interner, arguments, location)? else { + unreachable!("modulus_be_bytes must return slice") + }; + let reversed_bytes = bytes.into_iter().rev().collect(); + Ok(Value::Slice(reversed_bytes, typ)) +} + +fn modulus_num_bits( + _interner: &mut NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + check_argument_count(0, &arguments, location)?; + let bits = FieldElement::max_num_bits().into(); + Ok(Value::U64(bits)) +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/mod.rs index ab984d2f2be..3cc7b5f7e98 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/mod.rs @@ -1,4 +1,5 @@ mod errors; +mod hir_to_display_ast; mod interpreter; mod scan; mod tests; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/scan.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/scan.rs index 770c523bd7f..2ce22ab51e3 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/scan.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/scan.rs @@ -29,6 +29,8 @@ use super::{ Value, }; +use noirc_errors::Location; + #[allow(dead_code)] impl<'interner> Interpreter<'interner> { /// Scan through a function, evaluating any Comptime nodes found. @@ -41,11 +43,12 @@ impl<'interner> Interpreter<'interner> { return Ok(()); } - let function = self.interner.function(&function); + if let Some(function) = self.interner.function(&function).try_as_expr() { + let state = self.enter_function(); + self.scan_expression(function)?; + self.exit_function(state); + } - let state = self.enter_function(); - self.scan_expression(function.as_expr())?; - self.exit_function(state); Ok(()) } @@ -80,9 +83,10 @@ impl<'interner> Interpreter<'interner> { HirExpression::Lambda(lambda) => self.scan_lambda(lambda), HirExpression::Comptime(block) => { let location = self.interner.expr_location(&expr); - let new_expr = + let new_expr_id = self.evaluate_block(block)?.into_hir_expression(self.interner, location)?; - let new_expr = self.interner.expression(&new_expr); + let new_expr = self.interner.expression(&new_expr_id); + self.debug_comptime(new_expr_id, location); self.interner.replace_expr(&expr, new_expr); Ok(()) } @@ -118,7 +122,9 @@ impl<'interner> Interpreter<'interner> { if let Ok(value) = self.evaluate_ident(ident, id) { // TODO(#4922): Inlining closures is currently unimplemented if !matches!(value, Value::Closure(..)) { - self.inline_expression(value, id)?; + let new_expr = self.inline_expression(value, id)?; + let location = self.interner.id_location(id); + self.debug_comptime(new_expr, location); } } Ok(()) @@ -231,6 +237,7 @@ impl<'interner> Interpreter<'interner> { let new_expr = self .evaluate_comptime(comptime)? .into_hir_expression(self.interner, location)?; + self.debug_comptime(new_expr, location); self.interner.replace_statement(statement, HirStatement::Expression(new_expr)); Ok(()) } @@ -247,11 +254,19 @@ impl<'interner> Interpreter<'interner> { Ok(()) } - fn inline_expression(&mut self, value: Value, expr: ExprId) -> IResult<()> { + fn inline_expression(&mut self, value: Value, expr: ExprId) -> IResult { let location = self.interner.expr_location(&expr); - let new_expr = value.into_hir_expression(self.interner, location)?; - let new_expr = self.interner.expression(&new_expr); + let new_expr_id = value.into_hir_expression(self.interner, location)?; + let new_expr = self.interner.expression(&new_expr_id); self.interner.replace_expr(&expr, new_expr); - Ok(()) + Ok(new_expr_id) + } + + fn debug_comptime(&mut self, expr: ExprId, location: Location) { + if Some(location.file) == self.debug_comptime_in_file { + let expr = expr.to_display_ast(self.interner); + self.debug_comptime_evaluations + .push(InterpreterError::debug_evaluate_comptime(expr, location)); + } } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/tests.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/tests.rs index 870f2bc458a..84430bdfa30 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/tests.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/tests.rs @@ -1,38 +1,91 @@ #![cfg(test)] -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; +use std::path::PathBuf; +use fm::{FileId, FileManager}; +use noirc_arena::Index; use noirc_errors::Location; use super::errors::InterpreterError; use super::interpreter::Interpreter; use super::value::Value; +use crate::elaborator::Elaborator; use crate::graph::CrateId; -use crate::hir::type_check::test::type_check_src_code; +use crate::hir::def_collector::dc_crate::DefCollector; +use crate::hir::def_collector::dc_mod::collect_defs; +use crate::hir::def_map::{CrateDefMap, LocalModuleId, ModuleData}; +use crate::hir::{Context, ParsedFiles}; +use crate::macros_api::NodeInterner; +use crate::node_interner::FuncId; +use crate::parser::parse_program; -fn interpret_helper(src: &str, func_namespace: Vec) -> Result { - let (mut interner, main_id) = type_check_src_code(src, func_namespace); +fn elaborate_src_code(src: &str) -> (NodeInterner, FuncId) { + let file = FileId::default(); + + // Can't use Index::test_new here for some reason, even with #[cfg(test)]. + let module_id = LocalModuleId(Index::unsafe_zeroed()); + let mut modules = noirc_arena::Arena::default(); + let location = Location::new(Default::default(), file); + let root = LocalModuleId(modules.insert(ModuleData::new(None, location, false))); + assert_eq!(root, module_id); + + let file_manager = FileManager::new(&PathBuf::new()); + let parsed_files = ParsedFiles::new(); + let mut context = Context::new(file_manager, parsed_files); + context.def_interner.populate_dummy_operator_traits(); + + let krate = context.crate_graph.add_crate_root(FileId::dummy()); + + let (module, errors) = parse_program(src); + assert_eq!(errors.len(), 0); + let ast = module.into_sorted(); + + let def_map = CrateDefMap { root: module_id, modules, krate, extern_prelude: BTreeMap::new() }; + let mut collector = DefCollector::new(def_map); + + collect_defs(&mut collector, ast, FileId::dummy(), module_id, krate, &mut context, &[]); + context.def_maps.insert(krate, collector.def_map); + + let errors = Elaborator::elaborate(&mut context, krate, collector.items, None); + assert_eq!(errors.len(), 0); + + let main = context.get_main_function(&krate).expect("Expected 'main' function"); + + (context.def_interner, main) +} + +fn interpret_helper(src: &str) -> Result { + let (mut interner, main_id) = elaborate_src_code(src); let mut scopes = vec![HashMap::default()]; - let mut interpreter = Interpreter::new(&mut interner, &mut scopes, CrateId::Root(0)); + let no_debug_evaluate_comptime = None; + let mut interpreter_errors = vec![]; + let mut interpreter = Interpreter::new( + &mut interner, + &mut scopes, + CrateId::Root(0), + no_debug_evaluate_comptime, + &mut interpreter_errors, + ); let no_location = Location::dummy(); interpreter.call_function(main_id, Vec::new(), HashMap::new(), no_location) } -fn interpret(src: &str, func_namespace: Vec) -> Value { - interpret_helper(src, func_namespace).unwrap_or_else(|error| { +fn interpret(src: &str) -> Value { + interpret_helper(src).unwrap_or_else(|error| { panic!("Expected interpreter to exit successfully, but found {error:?}") }) } -fn interpret_expect_error(src: &str, func_namespace: Vec) -> InterpreterError { - interpret_helper(src, func_namespace).expect_err("Expected interpreter to error") +fn interpret_expect_error(src: &str) -> InterpreterError { + interpret_helper(src).expect_err("Expected interpreter to error") } #[test] fn interpreter_works() { let program = "comptime fn main() -> pub Field { 3 }"; - let result = interpret(program, vec!["main".into()]); + let result = interpret(program); assert_eq!(result, Value::Field(3u128.into())); } @@ -43,7 +96,7 @@ fn mutation_works() { x = 4; x }"; - let result = interpret(program, vec!["main".into()]); + let result = interpret(program); assert_eq!(result, Value::I8(4)); } @@ -54,7 +107,7 @@ fn mutating_references() { *x = 4; *x }"; - let result = interpret(program, vec!["main".into()]); + let result = interpret(program); assert_eq!(result, Value::I32(4)); } @@ -65,10 +118,22 @@ fn mutating_mutable_references() { *x = 4; *x }"; - let result = interpret(program, vec!["main".into()]); + let result = interpret(program); assert_eq!(result, Value::I64(4)); } +#[test] +fn mutation_leaks() { + let program = "comptime fn main() -> pub i8 { + let mut x = 3; + let y = &mut x; + *y = 5; + x + }"; + let result = interpret(program); + assert_eq!(result, Value::I8(5)); +} + #[test] fn mutating_arrays() { let program = "comptime fn main() -> pub u8 { @@ -76,7 +141,7 @@ fn mutating_arrays() { a1[1] = 22; a1[1] }"; - let result = interpret(program, vec!["main".into()]); + let result = interpret(program); assert_eq!(result, Value::U8(22)); } @@ -90,7 +155,7 @@ fn mutate_in_new_scope() { } x }"; - let result = interpret(program, vec!["main".into()]); + let result = interpret(program); assert_eq!(result, Value::U8(2)); } @@ -103,7 +168,7 @@ fn for_loop() { } x }"; - let result = interpret(program, vec!["main".into()]); + let result = interpret(program); assert_eq!(result, Value::U8(15)); } @@ -116,7 +181,7 @@ fn for_loop_u16() { } x }"; - let result = interpret(program, vec!["main".into()]); + let result = interpret(program); assert_eq!(result, Value::U16(15)); } @@ -132,7 +197,7 @@ fn for_loop_with_break() { } x }"; - let result = interpret(program, vec!["main".into()]); + let result = interpret(program); assert_eq!(result, Value::U32(6)); } @@ -148,7 +213,7 @@ fn for_loop_with_continue() { } x }"; - let result = interpret(program, vec!["main".into()]); + let result = interpret(program); assert_eq!(result, Value::U64(11)); } @@ -157,7 +222,7 @@ fn assert() { let program = "comptime fn main() { assert(1 == 1); }"; - let result = interpret(program, vec!["main".into()]); + let result = interpret(program); assert_eq!(result, Value::Unit); } @@ -166,7 +231,7 @@ fn assert_fail() { let program = "comptime fn main() { assert(1 == 2); }"; - let result = interpret_expect_error(program, vec!["main".into()]); + let result = interpret_expect_error(program); assert!(matches!(result, InterpreterError::FailingConstraint { .. })); } @@ -176,7 +241,7 @@ fn lambda() { let f = |x: u8| x + 1; f(1) }"; - let result = interpret(program, vec!["main".into()]); + let result = interpret(program); assert!(matches!(result, Value::U8(2))); } @@ -194,21 +259,21 @@ fn non_deterministic_recursion() { fib(x - 1) + fib(x - 2) } }"; - let result = interpret(program, vec!["main".into(), "fib".into()]); + let result = interpret(program); assert_eq!(result, Value::U64(55)); } #[test] fn generic_functions() { let program = " - fn main() -> pub u8 { + comptime fn main() -> pub u8 { apply(1, |x| x + 1) } - fn apply(x: T, f: fn[Env](T) -> U) -> U { + comptime fn apply(x: T, f: fn[Env](T) -> U) -> U { f(x) } "; - let result = interpret(program, vec!["main".into(), "apply".into()]); + let result = interpret(program); assert!(matches!(result, Value::U8(2))); } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/value.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/value.rs index 9e15b73324f..0cbf4662f6a 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/value.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/value.rs @@ -7,13 +7,14 @@ use iter_extended::{try_vecmap, vecmap}; use noirc_errors::Location; use crate::{ - ast::{ArrayLiteral, ConstructorExpression, Ident, IntegerBitSize, Signedness}, + ast::{ArrayLiteral, ConstructorExpression, Ident, IntegerBitSize, Signedness, TraitBound}, + hir::def_map::ModuleId, hir_def::expr::{HirArrayLiteral, HirConstructorExpression, HirIdent, HirLambda, ImplKind}, macros_api::{ Expression, ExpressionKind, HirExpression, HirLiteral, Literal, NodeInterner, Path, StructId, }, - node_interner::{ExprId, FuncId}, + node_interner::{ExprId, FuncId, TraitId}, parser::{self, NoirParser, TopLevelStatement}, token::{SpannedToken, Token, Tokens}, QuotedType, Shared, Type, TypeBindings, @@ -31,6 +32,7 @@ pub enum Value { I16(i16), I32(i32), I64(i64), + U1(bool), U8(u8), U16(u16), U32(u32), @@ -40,11 +42,15 @@ pub enum Value { Closure(HirLambda, Vec, Type), Tuple(Vec), Struct(HashMap, Value>, Type), - Pointer(Shared), + Pointer(Shared, /* auto_deref */ bool), Array(Vector, Type), Slice(Vector, Type), Code(Rc), StructDefinition(StructId), + TraitConstraint(TraitBound), + TraitDefinition(TraitId), + FunctionDefinition(FuncId), + ModuleDefinition(ModuleId), } impl Value { @@ -57,6 +63,7 @@ impl Value { Value::I16(_) => Type::Integer(Signedness::Signed, IntegerBitSize::Sixteen), Value::I32(_) => Type::Integer(Signedness::Signed, IntegerBitSize::ThirtyTwo), Value::I64(_) => Type::Integer(Signedness::Signed, IntegerBitSize::SixtyFour), + Value::U1(_) => Type::Integer(Signedness::Unsigned, IntegerBitSize::One), Value::U8(_) => Type::Integer(Signedness::Unsigned, IntegerBitSize::Eight), Value::U16(_) => Type::Integer(Signedness::Unsigned, IntegerBitSize::Sixteen), Value::U32(_) => Type::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo), @@ -75,10 +82,14 @@ impl Value { Value::Slice(_, typ) => return Cow::Borrowed(typ), Value::Code(_) => Type::Quoted(QuotedType::Quoted), Value::StructDefinition(_) => Type::Quoted(QuotedType::StructDefinition), - Value::Pointer(element) => { + Value::Pointer(element, _) => { let element = element.borrow().get_type().into_owned(); Type::MutableReference(Box::new(element)) } + Value::TraitConstraint { .. } => Type::Quoted(QuotedType::TraitConstraint), + Value::TraitDefinition(_) => Type::Quoted(QuotedType::TraitDefinition), + Value::FunctionDefinition(_) => Type::Quoted(QuotedType::FunctionDefinition), + Value::ModuleDefinition(_) => Type::Quoted(QuotedType::Module), }) } @@ -115,6 +126,9 @@ impl Value { let value = (value as u128).into(); ExpressionKind::Literal(Literal::Integer(value, negative)) } + Value::U1(value) => { + ExpressionKind::Literal(Literal::Integer((value as u128).into(), false)) + } Value::U8(value) => { ExpressionKind::Literal(Literal::Integer((value as u128).into(), false)) } @@ -192,7 +206,12 @@ impl Value { } }; } - Value::Pointer(_) | Value::StructDefinition(_) => { + Value::Pointer(..) + | Value::StructDefinition(_) + | Value::TraitConstraint(_) + | Value::TraitDefinition(_) + | Value::FunctionDefinition(_) + | Value::ModuleDefinition(_) => { return Err(InterpreterError::CannotInlineMacro { value: self, location }) } }; @@ -235,6 +254,9 @@ impl Value { let value = (value as u128).into(); HirExpression::Literal(HirLiteral::Integer(value, negative)) } + Value::U1(value) => { + HirExpression::Literal(HirLiteral::Integer((value as u128).into(), false)) + } Value::U8(value) => { HirExpression::Literal(HirLiteral::Integer((value as u128).into(), false)) } @@ -298,7 +320,12 @@ impl Value { HirExpression::Literal(HirLiteral::Slice(HirArrayLiteral::Standard(elements))) } Value::Code(block) => HirExpression::Unquote(unwrap_rc(block)), - Value::Pointer(_) | Value::StructDefinition(_) => { + Value::Pointer(..) + | Value::StructDefinition(_) + | Value::TraitConstraint(_) + | Value::TraitDefinition(_) + | Value::FunctionDefinition(_) + | Value::ModuleDefinition(_) => { return Err(InterpreterError::CannotInlineMacro { value: self, location }) } }; @@ -366,6 +393,7 @@ impl Display for Value { Value::I16(value) => write!(f, "{value}"), Value::I32(value) => write!(f, "{value}"), Value::I64(value) => write!(f, "{value}"), + Value::U1(value) => write!(f, "{value}"), Value::U8(value) => write!(f, "{value}"), Value::U16(value) => write!(f, "{value}"), Value::U32(value) => write!(f, "{value}"), @@ -385,7 +413,7 @@ impl Display for Value { let fields = vecmap(fields, |(name, value)| format!("{}: {}", name, value)); write!(f, "{typename} {{ {} }}", fields.join(", ")) } - Value::Pointer(value) => write!(f, "&mut {}", value.borrow()), + Value::Pointer(value, _) => write!(f, "&mut {}", value.borrow()), Value::Array(values, _) => { let values = vecmap(values, ToString::to_string); write!(f, "[{}]", values.join(", ")) @@ -402,6 +430,10 @@ impl Display for Value { write!(f, " }}") } Value::StructDefinition(_) => write!(f, "(struct definition)"), + Value::TraitConstraint { .. } => write!(f, "(trait constraint)"), + Value::TraitDefinition(_) => write!(f, "(trait definition)"), + Value::FunctionDefinition(_) => write!(f, "(function definition)"), + Value::ModuleDefinition(_) => write!(f, "(module)"), } } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index 43ab6224ea7..ad8832b3f68 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -2,20 +2,14 @@ use super::dc_mod::collect_defs; use super::errors::{DefCollectorErrorKind, DuplicateType}; use crate::elaborator::Elaborator; use crate::graph::CrateId; -use crate::hir::comptime::{Interpreter, InterpreterError}; +use crate::hir::comptime::InterpreterError; use crate::hir::def_map::{CrateDefMap, LocalModuleId, ModuleId}; use crate::hir::resolution::errors::ResolverError; +use crate::hir::resolution::path_resolver; +use crate::hir::type_check::TypeCheckError; use crate::{ResolvedGeneric, Type}; use crate::hir::resolution::import::{resolve_import, ImportDirective, PathResolution}; -use crate::hir::resolution::{ - collect_impls, collect_trait_impls, path_resolver, resolve_free_functions, resolve_globals, - resolve_impls, resolve_structs, resolve_trait_by_path, resolve_trait_impls, resolve_traits, - resolve_type_aliases, -}; -use crate::hir::type_check::{ - check_trait_impl_method_matches_declaration, type_check_func, TypeCheckError, TypeChecker, -}; use crate::hir::Context; use crate::macros_api::{MacroError, MacroProcessor}; @@ -29,23 +23,16 @@ use crate::ast::{ }; use crate::parser::{ParserError, SortedModule}; +use noirc_errors::{CustomDiagnostic, Location, Span}; + use fm::FileId; use iter_extended::vecmap; -use noirc_errors::{CustomDiagnostic, Location, Span}; use rustc_hash::FxHashMap as HashMap; use std::collections::BTreeMap; - +use std::fmt::Write; +use std::path::PathBuf; use std::vec; -#[derive(Default)] -pub struct ResolvedModule { - pub globals: Vec<(FileId, GlobalId)>, - pub functions: Vec<(FileId, FuncId)>, - pub trait_impl_functions: Vec<(FileId, FuncId)>, - - pub errors: Vec<(CompilationError, FileId)>, -} - /// Stores all of the unresolved functions in a particular file/mod #[derive(Clone)] pub struct UnresolvedFunctions { @@ -65,35 +52,6 @@ impl UnresolvedFunctions { pub fn function_ids(&self) -> Vec { vecmap(&self.functions, |(_, id, _)| *id) } - - pub fn resolve_trait_bounds_trait_ids( - &mut self, - def_maps: &BTreeMap, - crate_id: CrateId, - ) -> Vec { - let mut errors = Vec::new(); - - for (local_id, _, func) in &mut self.functions { - let module = ModuleId { krate: crate_id, local_id: *local_id }; - - for bound in &mut func.def.where_clause { - match resolve_trait_by_path(def_maps, module, bound.trait_bound.trait_path.clone()) - { - Ok((trait_id, warning)) => { - bound.trait_bound.trait_id = Some(trait_id); - if let Some(warning) = warning { - errors.push(DefCollectorErrorKind::PathResolutionError(warning)); - } - } - Err(err) => { - errors.push(err); - } - } - } - } - - errors - } } pub struct UnresolvedStruct { @@ -121,6 +79,7 @@ pub struct UnresolvedTraitImpl { pub methods: UnresolvedFunctions, pub generics: UnresolvedGenerics, pub where_clause: Vec, + pub is_comptime: bool, // Every field after this line is filled in later in the elaborator pub trait_id: Option, @@ -157,11 +116,11 @@ pub struct DefCollector { #[derive(Default)] pub struct CollectedItems { - pub(crate) functions: Vec, + pub functions: Vec, pub(crate) types: BTreeMap, pub(crate) type_aliases: BTreeMap, pub(crate) traits: BTreeMap, - pub(crate) globals: Vec, + pub globals: Vec, pub(crate) impls: ImplMap, pub(crate) trait_impls: Vec, } @@ -195,12 +154,19 @@ pub enum CompilationError { ResolverError(ResolverError), TypeError(TypeCheckError), InterpreterError(InterpreterError), + DebugComptimeScopeNotFound(Vec), } -impl CompilationError { - fn is_error(&self) -> bool { - let diagnostic = CustomDiagnostic::from(self); - diagnostic.is_error() +impl std::fmt::Display for CompilationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CompilationError::ParseError(error) => write!(f, "{}", error), + CompilationError::DefinitionError(error) => write!(f, "{}", error), + CompilationError::ResolverError(error) => write!(f, "{}", error), + CompilationError::TypeError(error) => write!(f, "{}", error), + CompilationError::InterpreterError(error) => write!(f, "{:?}", error), + CompilationError::DebugComptimeScopeNotFound(error) => write!(f, "{:?}", error), + } } } @@ -212,6 +178,16 @@ impl<'a> From<&'a CompilationError> for CustomDiagnostic { CompilationError::ResolverError(error) => error.into(), CompilationError::TypeError(error) => error.into(), CompilationError::InterpreterError(error) => error.into(), + CompilationError::DebugComptimeScopeNotFound(error) => { + let msg = "multiple files found matching --debug-comptime path".into(); + let secondary = error.iter().fold(String::new(), |mut output, path| { + let _ = writeln!(output, " {}", path.display()); + output + }); + // NOTE: this span is empty as it is not expected to be displayed + let dummy_span = Span::default(); + CustomDiagnostic::simple_error(msg, secondary, dummy_span) + } } } } @@ -246,7 +222,7 @@ impl From for CompilationError { } impl DefCollector { - fn new(def_map: CrateDefMap) -> DefCollector { + pub fn new(def_map: CrateDefMap) -> DefCollector { DefCollector { def_map, imports: vec![], @@ -265,12 +241,12 @@ impl DefCollector { /// Collect all of the definitions in a given crate into a CrateDefMap /// Modules which are not a part of the module hierarchy starting with /// the root module, will be ignored. - pub fn collect( + pub fn collect_crate_and_dependencies( mut def_map: CrateDefMap, context: &mut Context, ast: SortedModule, root_file_id: FileId, - use_legacy: bool, + debug_comptime_in_file: Option<&str>, macro_processors: &[&dyn MacroProcessor], ) -> Vec<(CompilationError, FileId)> { let mut errors: Vec<(CompilationError, FileId)> = vec![]; @@ -287,7 +263,7 @@ impl DefCollector { errors.extend(CrateDefMap::collect_defs( dep.crate_id, context, - use_legacy, + debug_comptime_in_file, macro_processors, )); @@ -331,7 +307,7 @@ impl DefCollector { for collected_import in std::mem::take(&mut def_collector.imports) { let module_id = collected_import.module_id; let resolved_import = if context.def_interner.track_references { - let mut references: Vec = Vec::new(); + let mut references: Vec> = Vec::new(); let resolved_import = resolve_import( crate_id, &collected_import, @@ -343,9 +319,14 @@ impl DefCollector { let file_id = current_def_map.file_id(module_id); for (referenced, ident) in references.iter().zip(&collected_import.path.segments) { - let reference = - ReferenceId::Reference(Location::new(ident.span(), file_id), false); - context.def_interner.add_reference(*referenced, reference); + let Some(referenced) = referenced else { + continue; + }; + context.def_interner.add_reference( + *referenced, + Location::new(ident.span(), file_id), + false, + ); } resolved_import @@ -396,107 +377,27 @@ impl DefCollector { } } - if !use_legacy { - let mut more_errors = Elaborator::elaborate(context, crate_id, def_collector.items); - errors.append(&mut more_errors); - return errors; - } - - let mut resolved_module = ResolvedModule { errors, ..Default::default() }; - - // We must first resolve and intern the globals before we can resolve any stmts inside each function. - // Each function uses its own resolver with a newly created ScopeForest, and must be resolved again to be within a function's scope - // - // Additionally, we must resolve integer globals before structs since structs may refer to - // the values of integer globals as numeric generics. - let (literal_globals, other_globals) = filter_literal_globals(def_collector.items.globals); - - resolved_module.resolve_globals(context, literal_globals, crate_id); - - resolved_module.errors.extend(resolve_type_aliases( - context, - def_collector.items.type_aliases, - crate_id, - )); + let debug_comptime_in_file = debug_comptime_in_file.and_then(|debug_comptime_in_file| { + let file = context.file_manager.find_by_path_suffix(debug_comptime_in_file); + file.unwrap_or_else(|error| { + errors.push((CompilationError::DebugComptimeScopeNotFound(error), root_file_id)); + None + }) + }); - resolved_module.errors.extend(resolve_traits( - context, - def_collector.items.traits, - crate_id, - )); - - // Must resolve structs before we resolve globals. - resolved_module.errors.extend(resolve_structs( - context, - def_collector.items.types, - crate_id, - )); - - // Bind trait impls to their trait. Collect trait functions, that have a - // default implementation, which hasn't been overridden. - resolved_module.errors.extend(collect_trait_impls( - context, - crate_id, - &mut def_collector.items.trait_impls, - )); - - // Before we resolve any function symbols we must go through our impls and - // re-collect the methods within into their proper module. This cannot be - // done before resolution since we need to be able to resolve the type of the - // impl since that determines the module we should collect into. - // - // These are resolved after trait impls so that struct methods are chosen - // over trait methods if there are name conflicts. - resolved_module.errors.extend(collect_impls(context, crate_id, &def_collector.items.impls)); - - // We must wait to resolve non-integer globals until after we resolve structs since struct - // globals will need to reference the struct type they're initialized to ensure they are valid. - resolved_module.resolve_globals(context, other_globals, crate_id); - - // Resolve each function in the crate. This is now possible since imports have been resolved - resolved_module.functions = resolve_free_functions( - &mut context.def_interner, - crate_id, - &context.def_maps, - def_collector.items.functions, - None, - &mut resolved_module.errors, - ); - - resolved_module.functions.extend(resolve_impls( - &mut context.def_interner, - crate_id, - &context.def_maps, - def_collector.items.impls, - &mut resolved_module.errors, - )); - - resolved_module.trait_impl_functions = resolve_trait_impls( - context, - def_collector.items.trait_impls, - crate_id, - &mut resolved_module.errors, - ); + let mut more_errors = + Elaborator::elaborate(context, crate_id, def_collector.items, debug_comptime_in_file); + errors.append(&mut more_errors); for macro_processor in macro_processors { macro_processor.process_typed_ast(&crate_id, context).unwrap_or_else( |(macro_err, file_id)| { - resolved_module.errors.push((macro_err.into(), file_id)); + errors.push((macro_err.into(), file_id)); }, ); } - let cycle_errors = context.def_interner.check_for_dependency_cycles(); - let cycles_present = !cycle_errors.is_empty(); - resolved_module.errors.extend(cycle_errors); - - resolved_module.type_check(context); - - if !cycles_present { - resolved_module.evaluate_comptime(&mut context.def_interner, crate_id); - } - - resolved_module.errors + errors } } @@ -511,18 +412,8 @@ fn add_import_reference( return; } - let referenced = match def_id { - crate::macros_api::ModuleDefId::ModuleId(module_id) => ReferenceId::Module(module_id), - crate::macros_api::ModuleDefId::FunctionId(func_id) => ReferenceId::Function(func_id), - crate::macros_api::ModuleDefId::TypeId(struct_id) => ReferenceId::Struct(struct_id), - crate::macros_api::ModuleDefId::TraitId(trait_id) => ReferenceId::Trait(trait_id), - crate::macros_api::ModuleDefId::TypeAliasId(type_alias_id) => { - ReferenceId::Alias(type_alias_id) - } - crate::macros_api::ModuleDefId::GlobalId(global_id) => ReferenceId::Global(global_id), - }; - let reference = ReferenceId::Reference(Location::new(name.span(), file_id), false); - interner.add_reference(referenced, reference); + let location = Location::new(name.span(), file_id); + interner.add_module_def_id_reference(def_id, location, false); } fn inject_prelude( @@ -582,76 +473,3 @@ pub fn filter_literal_globals( _ => false, }) } - -impl ResolvedModule { - fn type_check(&mut self, context: &mut Context) { - self.type_check_globals(&mut context.def_interner); - self.type_check_functions(&mut context.def_interner); - self.type_check_trait_impl_function(&mut context.def_interner); - } - - fn type_check_globals(&mut self, interner: &mut NodeInterner) { - for (file_id, global_id) in self.globals.iter() { - for error in TypeChecker::check_global(*global_id, interner) { - self.errors.push((error.into(), *file_id)); - } - } - } - - fn type_check_functions(&mut self, interner: &mut NodeInterner) { - for (file, func) in self.functions.iter() { - for error in type_check_func(interner, *func) { - self.errors.push((error.into(), *file)); - } - } - } - - fn type_check_trait_impl_function(&mut self, interner: &mut NodeInterner) { - for (file, func) in self.trait_impl_functions.iter() { - for error in check_trait_impl_method_matches_declaration(interner, *func) { - self.errors.push((error.into(), *file)); - } - for error in type_check_func(interner, *func) { - self.errors.push((error.into(), *file)); - } - } - } - - /// Evaluate all `comptime` expressions in this module - fn evaluate_comptime(&mut self, interner: &mut NodeInterner, crate_id: CrateId) { - if self.count_errors() == 0 { - let mut scopes = vec![HashMap::default()]; - let mut interpreter = Interpreter::new(interner, &mut scopes, crate_id); - - for (_file, global) in &self.globals { - if let Err(error) = interpreter.scan_global(*global) { - self.errors.push(error.into_compilation_error_pair()); - } - } - - for (_file, function) in &self.functions { - // The file returned by the error may be different than the file the - // function is in so only use the error's file id. - if let Err(error) = interpreter.scan_function(*function) { - self.errors.push(error.into_compilation_error_pair()); - } - } - } - } - - fn resolve_globals( - &mut self, - context: &mut Context, - literal_globals: Vec, - crate_id: CrateId, - ) { - let globals = resolve_globals(context, literal_globals, crate_id); - self.globals.extend(globals.globals); - self.errors.extend(globals.errors); - } - - /// Counts the number of errors (minus warnings) this program currently has - fn count_errors(&self) -> usize { - self.errors.iter().filter(|(error, _)| error.is_error()).count() - } -} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs index 48985116f4f..a9213a8c09a 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -14,7 +14,7 @@ use crate::ast::{ TypeImpl, }; use crate::macros_api::NodeInterner; -use crate::node_interner::ReferenceId; +use crate::node_interner::{ModuleAttributes, ReferenceId}; use crate::{ graph::CrateId, hir::def_collector::dc_crate::{UnresolvedStruct, UnresolvedTrait}, @@ -90,7 +90,7 @@ pub fn collect_defs( errors.extend(collector.collect_structs(context, ast.types, crate_id)); - errors.extend(collector.collect_type_aliases(context, ast.type_aliases)); + errors.extend(collector.collect_type_aliases(context, ast.type_aliases, crate_id)); errors.extend(collector.collect_functions(context, ast.functions, crate_id)); @@ -185,6 +185,7 @@ impl<'a> ModCollector<'a> { generics: trait_impl.impl_generics, where_clause: trait_impl.where_clause, trait_generics: trait_impl.trait_generics, + is_comptime: trait_impl.is_comptime, // These last fields are filled later on trait_id: None, @@ -318,7 +319,10 @@ impl<'a> ModCollector<'a> { // And store the TypeId -> StructType mapping somewhere it is reachable self.def_collector.items.types.insert(id, unresolved); - context.def_interner.add_definition_location(ReferenceId::Struct(id)); + context.def_interner.add_definition_location( + ReferenceId::Struct(id), + Some(ModuleId { krate, local_id: self.module_id }), + ); } definition_errors } @@ -329,6 +333,7 @@ impl<'a> ModCollector<'a> { &mut self, context: &mut Context, type_aliases: Vec, + krate: CrateId, ) -> Vec<(CompilationError, FileId)> { let mut errors: Vec<(CompilationError, FileId)> = vec![]; for type_alias in type_aliases { @@ -365,7 +370,10 @@ impl<'a> ModCollector<'a> { self.def_collector.items.type_aliases.insert(type_alias_id, unresolved); - context.def_interner.add_definition_location(ReferenceId::Alias(type_alias_id)); + context.def_interner.add_definition_location( + ReferenceId::Alias(type_alias_id), + Some(ModuleId { krate, local_id: self.module_id }), + ); } errors } @@ -532,7 +540,10 @@ impl<'a> ModCollector<'a> { }; context.def_interner.push_empty_trait(trait_id, &unresolved, resolved_generics); - context.def_interner.add_definition_location(ReferenceId::Trait(trait_id)); + context.def_interner.add_definition_location( + ReferenceId::Trait(trait_id), + Some(ModuleId { krate, local_id: self.module_id }), + ); self.def_collector.items.traits.insert(trait_id, unresolved); } @@ -649,9 +660,7 @@ impl<'a> ModCollector<'a> { ) { Ok(child_mod_id) => { // Track that the "foo" in `mod foo;` points to the module "foo" - let referenced = ReferenceId::Module(child_mod_id); - let reference = ReferenceId::Reference(location, false); - context.def_interner.add_reference(referenced, reference); + context.def_interner.add_module_reference(child_mod_id, location); errors.extend(collect_defs( self.def_collector, @@ -722,7 +731,14 @@ impl<'a> ModCollector<'a> { return Err(err); } - context.def_interner.add_module_location(mod_id, mod_location); + context.def_interner.add_module_attributes( + mod_id, + ModuleAttributes { + name: mod_name.0.contents.clone(), + location: mod_location, + parent: self.module_id, + }, + ); } Ok(mod_id) diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs index 59205f74d89..9de96ab06e8 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs @@ -73,7 +73,7 @@ impl CrateDefMap { pub fn collect_defs( crate_id: CrateId, context: &mut Context, - use_legacy: bool, + debug_comptime_in_file: Option<&str>, macro_processors: &[&dyn MacroProcessor], ) -> Vec<(CompilationError, FileId)> { // Check if this Crate has already been compiled @@ -117,12 +117,12 @@ impl CrateDefMap { }; // Now we want to populate the CrateDefMap using the DefCollector - errors.extend(DefCollector::collect( + errors.extend(DefCollector::collect_crate_and_dependencies( def_map, context, ast, root_file_id, - use_legacy, + debug_comptime_in_file, macro_processors, )); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/functions.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/functions.rs deleted file mode 100644 index fe46796ed24..00000000000 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/functions.rs +++ /dev/null @@ -1,84 +0,0 @@ -use std::collections::BTreeMap; - -use fm::FileId; -use iter_extended::vecmap; - -use crate::{ - graph::CrateId, - hir::{ - def_collector::dc_crate::{CompilationError, UnresolvedFunctions}, - def_map::{CrateDefMap, ModuleId}, - }, - node_interner::{FuncId, NodeInterner, TraitImplId}, - ResolvedGeneric, Type, -}; - -use super::{path_resolver::StandardPathResolver, Resolver}; - -#[allow(clippy::too_many_arguments)] -pub(crate) fn resolve_function_set( - interner: &mut NodeInterner, - crate_id: CrateId, - def_maps: &BTreeMap, - mut unresolved_functions: UnresolvedFunctions, - self_type: Option, - trait_impl_id: Option, - impl_generics: Vec, - errors: &mut Vec<(CompilationError, FileId)>, -) -> Vec<(FileId, FuncId)> { - let file_id = unresolved_functions.file_id; - - let where_clause_errors = - unresolved_functions.resolve_trait_bounds_trait_ids(def_maps, crate_id); - errors.extend(where_clause_errors.iter().cloned().map(|e| (e.into(), file_id))); - - vecmap(unresolved_functions.functions, |(mod_id, func_id, func)| { - let module_id = ModuleId { krate: crate_id, local_id: mod_id }; - let path_resolver = StandardPathResolver::new(module_id); - - let mut resolver = Resolver::new(interner, &path_resolver, def_maps, file_id); - // Must use set_generics here to ensure we re-use the same generics from when - // the impl was originally collected. Otherwise the function will be using different - // TypeVariables for the same generic, causing it to instantiate incorrectly. - resolver.set_generics(impl_generics.clone()); - resolver.set_self_type(self_type.clone()); - resolver.set_trait_id(unresolved_functions.trait_id); - resolver.set_trait_impl_id(trait_impl_id); - - // Without this, impl methods can accidentally be placed in contracts. See #3254 - if self_type.is_some() { - resolver.set_in_contract(false); - } - - let (hir_func, func_meta, errs) = resolver.resolve_function(func, func_id); - interner.push_fn_meta(func_meta, func_id); - interner.update_fn(func_id, hir_func); - errors.extend(errs.iter().cloned().map(|e| (e.into(), file_id))); - (file_id, func_id) - }) -} - -pub(crate) fn resolve_free_functions( - interner: &mut NodeInterner, - crate_id: CrateId, - def_maps: &BTreeMap, - collected_functions: Vec, - self_type: Option, - errors: &mut Vec<(CompilationError, FileId)>, -) -> Vec<(FileId, FuncId)> { - collected_functions - .into_iter() - .flat_map(|unresolved_functions| { - resolve_function_set( - interner, - crate_id, - def_maps, - unresolved_functions, - self_type.clone(), - None, - vec![], // no impl generics - errors, - ) - }) - .collect() -} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/globals.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/globals.rs deleted file mode 100644 index bcda4e75d3d..00000000000 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/globals.rs +++ /dev/null @@ -1,46 +0,0 @@ -use super::{path_resolver::StandardPathResolver, resolver::Resolver, take_errors}; -use crate::{ - graph::CrateId, - hir::{ - def_collector::dc_crate::{CompilationError, UnresolvedGlobal}, - def_map::ModuleId, - Context, - }, - node_interner::GlobalId, -}; -use fm::FileId; -use iter_extended::vecmap; - -#[derive(Default)] -pub(crate) struct ResolvedGlobals { - pub(crate) globals: Vec<(FileId, GlobalId)>, - pub(crate) errors: Vec<(CompilationError, FileId)>, -} - -pub(crate) fn resolve_globals( - context: &mut Context, - globals: Vec, - crate_id: CrateId, -) -> ResolvedGlobals { - let mut errors: Vec<(CompilationError, FileId)> = vec![]; - let globals = vecmap(globals, |global| { - let module_id = ModuleId { local_id: global.module_id, krate: crate_id }; - let path_resolver = StandardPathResolver::new(module_id); - - let mut resolver = Resolver::new( - &mut context.def_interner, - &path_resolver, - &context.def_maps, - global.file_id, - ); - - let hir_stmt = resolver.resolve_global_let(global.stmt_def, global.global_id); - errors.extend(take_errors(global.file_id, resolver)); - - let statement_id = context.def_interner.get_global(global.global_id).let_statement; - context.def_interner.replace_statement(statement_id, hir_stmt); - - (global.file_id, global.global_id) - }); - ResolvedGlobals { globals, errors } -} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/impls.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/impls.rs deleted file mode 100644 index 7efd1eed86e..00000000000 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/impls.rs +++ /dev/null @@ -1,145 +0,0 @@ -use std::collections::BTreeMap; - -use fm::FileId; - -use crate::ast::ItemVisibility; -use crate::{ - graph::CrateId, - hir::{ - def_collector::{ - dc_crate::{CompilationError, ImplMap}, - errors::DefCollectorErrorKind, - }, - def_map::{CrateDefMap, ModuleId}, - Context, - }, - node_interner::{FuncId, NodeInterner}, - Type, -}; - -use super::{ - errors::ResolverError, functions, get_module_mut, get_struct_type, - path_resolver::StandardPathResolver, resolver::Resolver, take_errors, -}; - -/// Go through the list of impls and add each function within to the scope -/// of the module defined by its type. -pub(crate) fn collect_impls( - context: &mut Context, - crate_id: CrateId, - collected_impls: &ImplMap, -) -> Vec<(CompilationError, FileId)> { - let interner = &mut context.def_interner; - let def_maps = &mut context.def_maps; - let mut errors: Vec<(CompilationError, FileId)> = vec![]; - - for ((unresolved_type, module_id), methods) in collected_impls { - let path_resolver = - StandardPathResolver::new(ModuleId { local_id: *module_id, krate: crate_id }); - - let file = def_maps[&crate_id].file_id(*module_id); - - for (generics, span, unresolved) in methods { - let mut resolver = Resolver::new(interner, &path_resolver, def_maps, file); - resolver.add_generics(generics); - let typ = resolver.resolve_type(unresolved_type.clone()); - - errors.extend(take_errors(unresolved.file_id, resolver)); - - if let Some(struct_type) = get_struct_type(&typ) { - let struct_type = struct_type.borrow(); - - // `impl`s are only allowed on types defined within the current crate - if struct_type.id.krate() != crate_id { - let span = *span; - let type_name = struct_type.name.to_string(); - let error = DefCollectorErrorKind::ForeignImpl { span, type_name }; - errors.push((error.into(), unresolved.file_id)); - continue; - } - - // Grab the module defined by the struct type. Note that impls are a case - // where the module the methods are added to is not the same as the module - // they are resolved in. - let module = get_module_mut(def_maps, struct_type.id.module_id()); - - for (_, method_id, method) in &unresolved.functions { - // If this method was already declared, remove it from the module so it cannot - // be accessed with the `TypeName::method` syntax. We'll check later whether the - // object types in each method overlap or not. If they do, we issue an error. - // If not, that is specialization which is allowed. - if module - .declare_function( - method.name_ident().clone(), - ItemVisibility::Public, - *method_id, - ) - .is_err() - { - module.remove_function(method.name_ident()); - } - } - // Prohibit defining impls for primitive types if we're not in the stdlib - } else if typ != Type::Error && !crate_id.is_stdlib() { - let span = *span; - let error = DefCollectorErrorKind::NonStructTypeInImpl { span }; - errors.push((error.into(), unresolved.file_id)); - } - } - } - errors -} - -pub(crate) fn resolve_impls( - interner: &mut NodeInterner, - crate_id: CrateId, - def_maps: &BTreeMap, - collected_impls: ImplMap, - errors: &mut Vec<(CompilationError, FileId)>, -) -> Vec<(FileId, FuncId)> { - let mut file_method_ids = Vec::new(); - - for ((unresolved_type, module_id), methods) in collected_impls { - let path_resolver = - StandardPathResolver::new(ModuleId { local_id: module_id, krate: crate_id }); - - let file = def_maps[&crate_id].file_id(module_id); - - for (generics, _, functions) in methods { - let mut resolver = Resolver::new(interner, &path_resolver, def_maps, file); - resolver.add_generics(&generics); - let generics = resolver.get_generics().to_vec(); - let self_type = resolver.resolve_type(unresolved_type.clone()); - - let mut file_func_ids = functions::resolve_function_set( - interner, - crate_id, - def_maps, - functions, - Some(self_type.clone()), - None, - generics, - errors, - ); - if self_type != Type::Error { - for (file_id, method_id) in &file_func_ids { - let method_name = interner.function_name(method_id).to_owned(); - - if let Some(first_fn) = - interner.add_method(&self_type, method_name.clone(), *method_id, false) - { - let error = ResolverError::DuplicateDefinition { - name: method_name, - first_span: interner.function_ident(&first_fn).span(), - second_span: interner.function_ident(method_id).span(), - }; - errors.push((error.into(), *file_id)); - } - } - } - file_method_ids.append(&mut file_func_ids); - } - } - - file_method_ids -} 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 710c12a91bf..10e18248dec 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 @@ -41,6 +41,8 @@ pub enum PathResolutionError { Unresolved(Ident), #[error("{0} is private and not visible from the current module")] Private(Ident), + #[error("There is no super module")] + NoSuper(Span), } #[derive(Debug)] @@ -73,6 +75,9 @@ impl<'a> From<&'a PathResolutionError> for CustomDiagnostic { format!("{ident} is private"), ident.span(), ), + PathResolutionError::NoSuper(span) => { + CustomDiagnostic::simple_error(error.to_string(), String::new(), *span) + } } } } @@ -81,7 +86,7 @@ pub fn resolve_import( crate_id: CrateId, import_directive: &ImportDirective, def_maps: &BTreeMap, - path_references: &mut Option<&mut Vec>, + path_references: &mut Option<&mut Vec>>, ) -> Result { let module_scope = import_directive.module_id; let NamespaceResolution { @@ -126,7 +131,7 @@ fn resolve_path_to_ns( crate_id: CrateId, importing_crate: CrateId, def_maps: &BTreeMap, - path_references: &mut Option<&mut Vec>, + path_references: &mut Option<&mut Vec>>, ) -> NamespaceResolutionResult { let import_path = &import_directive.path.segments; let def_map = &def_maps[&crate_id]; @@ -187,6 +192,25 @@ fn resolve_path_to_ns( path_references, importing_crate, ), + + crate::ast::PathKind::Super => { + if let Some(parent_module_id) = + def_maps[&crate_id].modules[import_directive.module_id.0].parent + { + resolve_name_in_module( + crate_id, + importing_crate, + import_path, + parent_module_id, + def_maps, + path_references, + ) + } else { + let span_start = import_directive.path.span().start(); + let span = Span::from(span_start..span_start + 5); // 5 == "super".len() + Err(PathResolutionError::NoSuper(span)) + } + } } } @@ -196,7 +220,7 @@ fn resolve_path_from_crate_root( import_path: &[Ident], def_maps: &BTreeMap, - path_references: &mut Option<&mut Vec>, + path_references: &mut Option<&mut Vec>>, ) -> NamespaceResolutionResult { resolve_name_in_module( crate_id, @@ -214,7 +238,7 @@ fn resolve_name_in_module( import_path: &[Ident], starting_mod: LocalModuleId, def_maps: &BTreeMap, - path_references: &mut Option<&mut Vec>, + path_references: &mut Option<&mut Vec>>, ) -> NamespaceResolutionResult { let def_map = &def_maps[&krate]; let mut current_mod_id = ModuleId { krate, local_id: starting_mod }; @@ -247,7 +271,7 @@ fn resolve_name_in_module( current_mod_id = match typ { ModuleDefId::ModuleId(id) => { if let Some(path_references) = path_references { - path_references.push(ReferenceId::Module(id)); + path_references.push(Some(ReferenceId::Module(id))); } id } @@ -255,14 +279,14 @@ fn resolve_name_in_module( // TODO: If impls are ever implemented, types can be used in a path ModuleDefId::TypeId(id) => { if let Some(path_references) = path_references { - path_references.push(ReferenceId::Struct(id)); + path_references.push(Some(ReferenceId::Struct(id))); } id.module_id() } ModuleDefId::TypeAliasId(_) => panic!("type aliases cannot be used in type namespace"), ModuleDefId::TraitId(id) => { if let Some(path_references) = path_references { - path_references.push(ReferenceId::Trait(id)); + path_references.push(Some(ReferenceId::Trait(id))); } id.0 } @@ -309,7 +333,7 @@ fn resolve_external_dep( current_def_map: &CrateDefMap, directive: &ImportDirective, def_maps: &BTreeMap, - path_references: &mut Option<&mut Vec>, + path_references: &mut Option<&mut Vec>>, importing_crate: CrateId, ) -> NamespaceResolutionResult { // Use extern_prelude to get the dep @@ -327,6 +351,11 @@ fn resolve_external_dep( // See `singleton_import.nr` test case for a check that such cases are handled elsewhere. let path_without_crate_name = &path[1..]; + // Given that we skipped the first segment, record that it doesn't refer to any module or type. + if let Some(path_references) = path_references { + path_references.push(None); + } + let path = Path { segments: path_without_crate_name.to_vec(), kind: PathKind::Plain, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/mod.rs index 8c16a9cca80..01a3fe856e5 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/mod.rs @@ -8,51 +8,3 @@ pub mod errors; pub mod import; pub mod path_resolver; -pub mod resolver; - -mod functions; -mod globals; -mod impls; -mod structs; -mod traits; -mod type_aliases; - -pub(crate) use functions::resolve_free_functions; -pub(crate) use globals::resolve_globals; -pub(crate) use impls::{collect_impls, resolve_impls}; -pub(crate) use structs::resolve_structs; -pub(crate) use traits::{ - collect_trait_impls, resolve_trait_by_path, resolve_trait_impls, resolve_traits, -}; -pub(crate) use type_aliases::resolve_type_aliases; - -use crate::{ - graph::CrateId, - hir::{ - def_collector::dc_crate::CompilationError, - def_map::{CrateDefMap, ModuleData, ModuleId}, - }, - Shared, StructType, Type, -}; -use fm::FileId; -use iter_extended::vecmap; -use resolver::Resolver; -use std::collections::BTreeMap; - -fn take_errors(file_id: FileId, resolver: Resolver<'_>) -> Vec<(CompilationError, FileId)> { - vecmap(resolver.take_errors(), |e| (e.into(), file_id)) -} - -fn get_module_mut( - def_maps: &mut BTreeMap, - module: ModuleId, -) -> &mut ModuleData { - &mut def_maps.get_mut(&module.krate).unwrap().modules[module.local_id.0] -} - -fn get_struct_type(typ: &Type) -> Option<&Shared> { - match typ { - Type::Struct(definition, _) => Some(definition), - _ => None, - } -} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/path_resolver.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/path_resolver.rs index c3dc76b635f..7cd44a84018 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/path_resolver.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/path_resolver.rs @@ -9,12 +9,13 @@ use crate::hir::def_map::{CrateDefMap, LocalModuleId, ModuleId}; pub trait PathResolver { /// Resolve the given path returning the resolved ModuleDefId. /// If `path_references` is `Some`, a `ReferenceId` for each segment in `path` - /// will be resolved and pushed. + /// will be resolved and pushed (some entries will be None if they don't refer to + /// a module or type). fn resolve( &self, def_maps: &BTreeMap, path: Path, - path_references: &mut Option<&mut Vec>, + path_references: &mut Option<&mut Vec>>, ) -> PathResolutionResult; fn local_module_id(&self) -> LocalModuleId; @@ -38,7 +39,7 @@ impl PathResolver for StandardPathResolver { &self, def_maps: &BTreeMap, path: Path, - path_references: &mut Option<&mut Vec>, + path_references: &mut Option<&mut Vec>>, ) -> PathResolutionResult { resolve_path(def_maps, self.module_id, path, path_references) } @@ -58,7 +59,7 @@ pub fn resolve_path( def_maps: &BTreeMap, module_id: ModuleId, path: Path, - path_references: &mut Option<&mut Vec>, + path_references: &mut Option<&mut Vec>>, ) -> PathResolutionResult { // lets package up the path into an ImportDirective and resolve it using that let import = diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/resolver.rs deleted file mode 100644 index 793362bb3d6..00000000000 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ /dev/null @@ -1,2203 +0,0 @@ -// Fix usage of intern and resolve -// In some places, we do intern, however in others we are resolving and interning -// Ideally, I want to separate the interning and resolving abstractly -// so separate functions, but combine them naturally -// This could be possible, if lowering, is given a mutable map/scope as a parameter. -// So that it can match Idents to Ids. This is close to what the Scope map looks like -// Except for the num_times_used parameter. -// We can instead have a map from Ident to Into and implement that trait on ResolverMeta -// -// -// XXX: Change mentions of intern to resolve. In regards to the above comment -// -// XXX: Resolver does not check for unused functions -use acvm::acir::AcirField; - -use crate::hir_def::expr::{ - HirArrayLiteral, HirBinaryOp, HirBlockExpression, HirCallExpression, HirCapturedVar, - HirCastExpression, HirConstructorExpression, HirExpression, HirIdent, HirIfExpression, - HirIndexExpression, HirInfixExpression, HirLambda, HirLiteral, HirMemberAccess, - HirMethodCallExpression, HirPrefixExpression, ImplKind, -}; - -use crate::hir_def::function::FunctionBody; -use crate::hir_def::traits::{Trait, TraitConstraint}; -use crate::macros_api::SecondaryAttribute; -use crate::token::Attributes; -use regex::Regex; -use std::collections::{BTreeMap, BTreeSet, HashSet}; -use std::rc::Rc; - -use crate::ast::{ - ArrayLiteral, BinaryOpKind, BlockExpression, Expression, ExpressionKind, ForRange, - FunctionDefinition, FunctionKind, FunctionReturnType, Ident, ItemVisibility, LValue, - LetStatement, Literal, NoirFunction, NoirStruct, NoirTypeAlias, Param, Path, PathKind, Pattern, - Statement, StatementKind, TraitBound, UnaryOp, UnresolvedGeneric, UnresolvedGenerics, - UnresolvedTraitConstraint, UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression, - Visibility, ERROR_IDENT, -}; -use crate::graph::CrateId; -use crate::hir::def_map::{ModuleDefId, TryFromModuleDefId, MAIN_FUNCTION}; -use crate::hir::{ - comptime::{Interpreter, Value}, - def_map::CrateDefMap, - resolution::path_resolver::PathResolver, -}; -use crate::hir_def::stmt::{HirAssignStatement, HirForStatement, HirLValue, HirPattern}; -use crate::node_interner::{ - DefinitionId, DefinitionKind, DependencyId, ExprId, FuncId, GlobalId, NodeInterner, StmtId, - StructId, TraitId, TraitImplId, TraitMethodId, TypeAliasId, -}; -use crate::{ - GenericTypeVars, Generics, Kind, ResolvedGeneric, Shared, StructType, Type, TypeAlias, - TypeVariable, TypeVariableKind, -}; -use fm::FileId; -use iter_extended::vecmap; -use noirc_errors::{Location, Span, Spanned}; - -use crate::hir::scope::{ - Scope as GenericScope, ScopeForest as GenericScopeForest, ScopeTree as GenericScopeTree, -}; -use crate::hir_def::{ - function::{FuncMeta, HirFunction}, - stmt::{HirConstrainStatement, HirLetStatement, HirStatement}, -}; - -use super::errors::{PubPosition, ResolverError}; -use super::import::PathResolution; - -pub const SELF_TYPE_NAME: &str = "Self"; -pub const WILDCARD_TYPE: &str = "_"; - -type Scope = GenericScope; -type ScopeTree = GenericScopeTree; -type ScopeForest = GenericScopeForest; - -pub struct LambdaContext { - pub captures: Vec, - /// the index in the scope tree - /// (sometimes being filled by ScopeTree's find method) - pub scope_index: usize, -} - -/// The primary jobs of the Resolver are to validate that every variable found refers to exactly 1 -/// definition in scope, and to convert the AST into the HIR. -/// -/// A Resolver is a short-lived struct created to resolve a top-level definition. -/// One of these is created for each function definition and struct definition. -/// This isn't strictly necessary to its function, it could be refactored out in the future. -pub struct Resolver<'a> { - scopes: ScopeForest, - path_resolver: &'a dyn PathResolver, - def_maps: &'a BTreeMap, - trait_id: Option, - trait_bounds: Vec, - pub interner: &'a mut NodeInterner, - errors: Vec, - file: FileId, - - /// Set to the current type if we're resolving an impl - self_type: Option, - - /// If we're currently resolving methods within a trait impl, this will be set - /// to the corresponding trait impl ID. - current_trait_impl: Option, - - /// The current dependency item we're resolving. - /// Used to link items to their dependencies in the dependency graph - current_item: Option, - - /// In-resolution names - /// - /// This needs to be a set because we can have multiple in-resolution - /// names when resolving structs that are declared in reverse order of their - /// dependencies, such as in the following case: - /// - /// ``` - /// struct Wrapper { - /// value: Wrapped - /// } - /// struct Wrapped { - /// } - /// ``` - resolving_ids: BTreeSet, - - /// True if the current module is a contract. - /// This is usually determined by self.path_resolver.module_id(), but it can - /// be overridden for impls. Impls are an odd case since the methods within resolve - /// as if they're in the parent module, but should be placed in a child module. - /// Since they should be within a child module, in_contract is manually set to false - /// for these so we can still resolve them in the parent module without them being in a contract. - in_contract: bool, - - /// Contains a mapping of the current struct or functions's generics to - /// unique type variables if we're resolving a struct. Empty otherwise. - /// This is a Vec rather than a map to preserve the order a functions generics - /// were declared in. - generics: Vec, - - /// When resolving lambda expressions, we need to keep track of the variables - /// that are captured. We do this in order to create the hidden environment - /// parameter for the lambda function. - lambda_stack: Vec, - - /// True if we're currently resolving an unconstrained function - in_unconstrained_fn: bool, - - /// How many loops we're currently within. - /// This increases by 1 at the start of a loop, and decreases by 1 when it ends. - nested_loops: u32, -} - -/// ResolverMetas are tagged onto each definition to track how many times they are used -#[derive(Debug, PartialEq, Eq)] -struct ResolverMeta { - num_times_used: usize, - ident: HirIdent, - warn_if_unused: bool, -} - -pub enum ResolvePathError { - WrongKind, - NotFound, -} - -impl<'a> Resolver<'a> { - pub fn new( - interner: &'a mut NodeInterner, - path_resolver: &'a dyn PathResolver, - def_maps: &'a BTreeMap, - file: FileId, - ) -> Resolver<'a> { - let module_id = path_resolver.module_id(); - let in_contract = module_id.module(def_maps).is_contract; - - Self { - path_resolver, - def_maps, - trait_id: None, - trait_bounds: Vec::new(), - scopes: ScopeForest::default(), - interner, - self_type: None, - generics: Vec::new(), - errors: Vec::new(), - lambda_stack: Vec::new(), - current_trait_impl: None, - current_item: None, - resolving_ids: BTreeSet::new(), - file, - in_contract, - in_unconstrained_fn: false, - nested_loops: 0, - } - } - - pub fn set_self_type(&mut self, self_type: Option) { - self.self_type = self_type; - } - - pub fn set_trait_id(&mut self, trait_id: Option) { - self.trait_id = trait_id; - } - - pub fn set_trait_impl_id(&mut self, impl_id: Option) { - self.current_trait_impl = impl_id; - } - - pub fn get_self_type(&mut self) -> Option<&Type> { - self.self_type.as_ref() - } - - fn push_err(&mut self, err: ResolverError) { - self.errors.push(err); - } - - /// This turns function parameters of the form: - /// fn foo(x: impl Bar) - /// - /// into - /// fn foo(x: T0_impl_Bar) where T0_impl_Bar: Bar - fn desugar_impl_trait_args(&mut self, func: &mut NoirFunction, func_id: FuncId) { - let mut impl_trait_generics = HashSet::new(); - let mut counter: usize = 0; - for parameter in func.def.parameters.iter_mut() { - if let UnresolvedTypeData::TraitAsType(path, args) = ¶meter.typ.typ { - let mut new_generic_ident: Ident = - format!("T{}_impl_{}", func_id, path.as_string()).into(); - let mut new_generic_path = Path::from_ident(new_generic_ident.clone()); - let new_generic = UnresolvedGeneric::from(new_generic_ident.clone()); - while impl_trait_generics.contains(&new_generic) - || self.lookup_generic_or_global_type(&new_generic_path).is_some() - { - new_generic_ident = - format!("T{}_impl_{}_{}", func_id, path.as_string(), counter).into(); - new_generic_path = Path::from_ident(new_generic_ident.clone()); - counter += 1; - } - impl_trait_generics.insert(UnresolvedGeneric::from(new_generic_ident.clone())); - - let is_synthesized = true; - let new_generic_type_data = - UnresolvedTypeData::Named(new_generic_path, vec![], is_synthesized); - let new_generic_type = - UnresolvedType { typ: new_generic_type_data.clone(), span: None }; - let new_trait_bound = TraitBound { - trait_path: path.clone(), - trait_id: None, - trait_generics: args.to_vec(), - }; - let new_trait_constraint = UnresolvedTraitConstraint { - typ: new_generic_type, - trait_bound: new_trait_bound, - }; - - parameter.typ.typ = new_generic_type_data; - func.def.generics.push(new_generic_ident.into()); - func.def.where_clause.push(new_trait_constraint); - } - } - self.add_generics(&impl_trait_generics.into_iter().collect()); - } - - /// Resolving a function involves interning the metadata - /// interning any statements inside of the function - /// and interning the function itself - /// We resolve and lower the function at the same time - /// Since lowering would require scope data, unless we add an extra resolution field to the AST - pub fn resolve_function( - mut self, - mut func: NoirFunction, - func_id: FuncId, - ) -> (HirFunction, FuncMeta, Vec) { - self.scopes.start_function(); - self.current_item = Some(DependencyId::Function(func_id)); - - // Check whether the function has globals in the local module and add them to the scope - self.resolve_local_globals(); - self.add_generics(&func.def.generics); - - self.desugar_impl_trait_args(&mut func, func_id); - self.trait_bounds = func.def.where_clause.clone(); - - let is_low_level_or_oracle = func - .attributes() - .function - .as_ref() - .map_or(false, |func| func.is_low_level() || func.is_oracle()); - let (hir_func, func_meta) = self.intern_function(func, func_id); - let func_scope_tree = self.scopes.end_function(); - - // The arguments to low-level and oracle functions are always unused so we do not produce warnings for them. - if !is_low_level_or_oracle { - self.check_for_unused_variables_in_scope_tree(func_scope_tree); - } - - self.trait_bounds.clear(); - (hir_func, func_meta, self.errors) - } - - pub fn resolve_trait_function( - &mut self, - name: &Ident, - generics: &UnresolvedGenerics, - parameters: &[(Ident, UnresolvedType)], - return_type: &FunctionReturnType, - where_clause: &[UnresolvedTraitConstraint], - func_id: FuncId, - ) -> (HirFunction, FuncMeta) { - self.scopes.start_function(); - - // Check whether the function has globals in the local module and add them to the scope - self.resolve_local_globals(); - - self.trait_bounds = where_clause.to_vec(); - - let kind = FunctionKind::Normal; - let def = FunctionDefinition { - name: name.clone(), - attributes: Attributes::empty(), - is_unconstrained: false, - is_comptime: false, - visibility: ItemVisibility::Public, // Trait functions are always public - generics: generics.clone(), - parameters: vecmap(parameters, |(name, typ)| Param { - visibility: Visibility::Private, - pattern: Pattern::Identifier(name.clone()), - typ: typ.clone(), - span: name.span(), - }), - body: BlockExpression { statements: Vec::new() }, - span: name.span(), - where_clause: where_clause.to_vec(), - return_type: return_type.clone(), - return_visibility: Visibility::Private, - }; - - let (hir_func, func_meta) = self.intern_function(NoirFunction { kind, def }, func_id); - let _ = self.scopes.end_function(); - // Don't check the scope tree for unused variables, they can't be used in a declaration anyway. - self.trait_bounds.clear(); - (hir_func, func_meta) - } - - fn check_for_unused_variables_in_scope_tree(&mut self, scope_decls: ScopeTree) { - let mut unused_vars = Vec::new(); - for scope in scope_decls.0.into_iter() { - Resolver::check_for_unused_variables_in_local_scope(scope, &mut unused_vars); - } - - for unused_var in unused_vars.iter() { - if let Some(definition_info) = self.interner.try_definition(unused_var.id) { - let name = &definition_info.name; - if name != ERROR_IDENT && !definition_info.is_global() { - let ident = Ident(Spanned::from(unused_var.location.span, name.to_owned())); - self.push_err(ResolverError::UnusedVariable { ident }); - } - } - } - } - - fn check_for_unused_variables_in_local_scope(decl_map: Scope, unused_vars: &mut Vec) { - let unused_variables = decl_map.filter(|(variable_name, metadata)| { - let has_underscore_prefix = variable_name.starts_with('_'); // XXX: This is used for development mode, and will be removed - metadata.warn_if_unused && metadata.num_times_used == 0 && !has_underscore_prefix - }); - unused_vars.extend(unused_variables.map(|(_, meta)| meta.ident.clone())); - } - - /// Run the given function in a new scope. - fn in_new_scope T>(&mut self, f: F) -> T { - self.scopes.start_scope(); - let ret = f(self); - let scope = self.scopes.end_scope(); - self.check_for_unused_variables_in_scope_tree(scope.into()); - ret - } - - fn add_variable_decl( - &mut self, - name: Ident, - mutable: bool, - allow_shadowing: bool, - definition: DefinitionKind, - ) -> HirIdent { - self.add_variable_decl_inner(name, mutable, allow_shadowing, true, definition) - } - - fn add_variable_decl_inner( - &mut self, - name: Ident, - mutable: bool, - allow_shadowing: bool, - warn_if_unused: bool, - definition: DefinitionKind, - ) -> HirIdent { - if definition.is_global() { - return self.add_global_variable_decl(name, definition); - } - - let location = Location::new(name.span(), self.file); - let var_name = name.0.contents.clone(); - let id = self.interner.push_definition(var_name, mutable, false, definition, location); - let ident = HirIdent::non_trait_method(id, location); - let resolver_meta = - ResolverMeta { num_times_used: 0, ident: ident.clone(), warn_if_unused }; - - let scope = self.scopes.get_mut_scope(); - let old_value = scope.add_key_value(name.0.contents.clone(), resolver_meta); - - if !allow_shadowing { - if let Some(old_value) = old_value { - self.push_err(ResolverError::DuplicateDefinition { - name: name.0.contents, - first_span: old_value.ident.location.span, - second_span: location.span, - }); - } - } - - ident - } - - fn add_global_variable_decl(&mut self, name: Ident, definition: DefinitionKind) -> HirIdent { - let scope = self.scopes.get_mut_scope(); - - // This check is necessary to maintain the same definition ids in the interner. Currently, each function uses a new resolver that has its own ScopeForest and thus global scope. - // We must first check whether an existing definition ID has been inserted as otherwise there will be multiple definitions for the same global statement. - // This leads to an error in evaluation where the wrong definition ID is selected when evaluating a statement using the global. The check below prevents this error. - let mut global_id = None; - let global = self.interner.get_all_globals(); - for global_info in global { - if global_info.ident == name - && global_info.local_id == self.path_resolver.local_module_id() - { - global_id = Some(global_info.id); - } - } - - let (ident, resolver_meta) = if let Some(id) = global_id { - let global = self.interner.get_global(id); - let hir_ident = HirIdent::non_trait_method(global.definition_id, global.location); - let ident = hir_ident.clone(); - let resolver_meta = ResolverMeta { num_times_used: 0, ident, warn_if_unused: true }; - (hir_ident, resolver_meta) - } else { - let location = Location::new(name.span(), self.file); - let var_name = name.0.contents.clone(); - let id = self.interner.push_definition(var_name, false, false, definition, location); - let ident = HirIdent::non_trait_method(id, location); - let resolver_meta = - ResolverMeta { num_times_used: 0, ident: ident.clone(), warn_if_unused: true }; - (ident, resolver_meta) - }; - - let old_global_value = scope.add_key_value(name.0.contents.clone(), resolver_meta); - if let Some(old_global_value) = old_global_value { - self.push_err(ResolverError::DuplicateDefinition { - name: name.0.contents.clone(), - first_span: old_global_value.ident.location.span, - second_span: name.span(), - }); - } - ident - } - - // Checks for a variable having been declared before - // variable declaration and definition cannot be separate in Noir - // Once the variable has been found, intern and link `name` to this definition - // return the IdentId of `name` - // - // If a variable is not found, then an error is logged and a dummy id - // is returned, for better error reporting UX - fn find_variable_or_default(&mut self, name: &Ident) -> (HirIdent, usize) { - self.find_variable(name).unwrap_or_else(|error| { - self.push_err(error); - let id = DefinitionId::dummy_id(); - let location = Location::new(name.span(), self.file); - (HirIdent::non_trait_method(id, location), 0) - }) - } - - fn find_variable(&mut self, name: &Ident) -> Result<(HirIdent, usize), ResolverError> { - // Find the definition for this Ident - let scope_tree = self.scopes.current_scope_tree(); - let variable = scope_tree.find(&name.0.contents); - - let location = Location::new(name.span(), self.file); - if let Some((variable_found, scope)) = variable { - variable_found.num_times_used += 1; - let id = variable_found.ident.id; - Ok((HirIdent::non_trait_method(id, location), scope)) - } else { - Err(ResolverError::VariableNotDeclared { - name: name.0.contents.clone(), - span: name.0.span(), - }) - } - } - - fn intern_function(&mut self, func: NoirFunction, id: FuncId) -> (HirFunction, FuncMeta) { - let func_meta = self.extract_meta(&func, id); - - if func.def.is_unconstrained { - self.in_unconstrained_fn = true; - } - - let hir_func = match func.kind { - FunctionKind::Builtin | FunctionKind::LowLevel | FunctionKind::Oracle => { - HirFunction::empty() - } - FunctionKind::Normal | FunctionKind::Recursive => { - let expr_id = self.intern_block(func.def.body); - self.interner.push_expr_location(expr_id, func.def.span, self.file); - HirFunction::unchecked_from_expr(expr_id) - } - }; - - (hir_func, func_meta) - } - - pub fn resolve_trait_constraint( - &mut self, - constraint: UnresolvedTraitConstraint, - ) -> Option { - let typ = self.resolve_type(constraint.typ); - let trait_generics = - vecmap(constraint.trait_bound.trait_generics, |typ| self.resolve_type(typ)); - - let span = constraint.trait_bound.trait_path.span(); - let the_trait = self.lookup_trait_or_error(constraint.trait_bound.trait_path)?; - let trait_id = the_trait.id; - - let expected_generics = the_trait.generics.len(); - let actual_generics = trait_generics.len(); - - if actual_generics != expected_generics { - let item_name = the_trait.name.to_string(); - self.push_err(ResolverError::IncorrectGenericCount { - span, - item_name, - actual: actual_generics, - expected: expected_generics, - }); - } - - Some(TraitConstraint { typ, trait_id, trait_generics }) - } - - /// Translates an UnresolvedType into a Type and appends any - /// freshly created TypeVariables created to new_variables. - fn resolve_type_inner(&mut self, typ: UnresolvedType) -> Type { - use crate::ast::UnresolvedTypeData::*; - - let resolved_type = match typ.typ { - FieldElement => Type::FieldElement, - Array(size, elem) => { - let elem = Box::new(self.resolve_type_inner(*elem)); - let size = self.convert_expression_type(size); - Type::Array(Box::new(size), elem) - } - Slice(elem) => { - let elem = Box::new(self.resolve_type_inner(*elem)); - Type::Slice(elem) - } - Expression(expr) => self.convert_expression_type(expr), - Integer(sign, bits) => Type::Integer(sign, bits), - Bool => Type::Bool, - String(size) => { - let resolved_size = self.convert_expression_type(size); - Type::String(Box::new(resolved_size)) - } - FormatString(size, fields) => { - let resolved_size = self.convert_expression_type(size); - let fields = self.resolve_type_inner(*fields); - Type::FmtString(Box::new(resolved_size), Box::new(fields)) - } - Quoted(quoted) => Type::Quoted(quoted), - Unit => Type::Unit, - Unspecified => Type::Error, - Error => Type::Error, - Named(path, args, _) => self.resolve_named_type(path, args), - TraitAsType(path, args) => self.resolve_trait_as_type(path, args), - - Tuple(fields) => Type::Tuple(vecmap(fields, |field| self.resolve_type_inner(field))), - Function(args, ret, env) => { - let args = vecmap(args, |arg| self.resolve_type_inner(arg)); - let ret = Box::new(self.resolve_type_inner(*ret)); - - // expect() here is valid, because the only places we don't have a span are omitted types - // e.g. a function without return type implicitly has a spanless UnresolvedType::Unit return type - // To get an invalid env type, the user must explicitly specify the type, which will have a span - let env_span = - env.span.expect("Unexpected missing span for closure environment type"); - - let env = Box::new(self.resolve_type_inner(*env)); - - match *env { - Type::Unit | Type::Tuple(_) | Type::NamedGeneric(_, _, _) => { - Type::Function(args, ret, env) - } - _ => { - self.push_err(ResolverError::InvalidClosureEnvironment { - typ: *env, - span: env_span, - }); - Type::Error - } - } - } - MutableReference(element) => { - Type::MutableReference(Box::new(self.resolve_type_inner(*element))) - } - Parenthesized(typ) => self.resolve_type_inner(*typ), - Resolved(id) => self.interner.get_quoted_type(id).clone(), - }; - - if let Type::Struct(_, _) = resolved_type { - if let Some(unresolved_span) = typ.span { - // Record the location of the type reference - self.interner.push_type_ref_location( - resolved_type.clone(), - Location::new(unresolved_span, self.file), - ); - } - } - resolved_type - } - - fn find_generic(&self, target_name: &str) -> Option<&ResolvedGeneric> { - self.generics.iter().find(|generic| generic.name.as_ref() == target_name) - } - - fn resolve_named_type(&mut self, path: Path, args: Vec) -> Type { - if args.is_empty() { - if let Some(typ) = self.lookup_generic_or_global_type(&path) { - return typ; - } - } - - // Check if the path is a type variable first. We currently disallow generics on type - // variables since we do not support higher-kinded types. - if path.segments.len() == 1 { - let name = &path.last_segment().0.contents; - - if name == SELF_TYPE_NAME { - if let Some(self_type) = self.self_type.clone() { - if !args.is_empty() { - self.push_err(ResolverError::GenericsOnSelfType { span: path.span() }); - } - return self_type; - } - } - } - - let span = path.span(); - let mut args = vecmap(args, |arg| self.resolve_type_inner(arg)); - - if let Some(type_alias) = self.lookup_type_alias(path.clone()) { - let type_alias = type_alias.borrow(); - let expected_generic_count = type_alias.generics.len(); - let type_alias_string = type_alias.to_string(); - let id = type_alias.id; - - self.verify_generics_count(expected_generic_count, &mut args, span, || { - type_alias_string - }); - - if let Some(item) = self.current_item { - self.interner.add_type_alias_dependency(item, id); - } - - // Collecting Type Alias references [Location]s to be used by LSP in order - // to resolve the definition of the type alias - self.interner.add_type_alias_ref(id, Location::new(span, self.file)); - - // Because there is no ordering to when type aliases (and other globals) are resolved, - // it is possible for one to refer to an Error type and issue no error if it is set - // equal to another type alias. Fixing this fully requires an analysis to create a DFG - // of definition ordering, but for now we have an explicit check here so that we at - // least issue an error that the type was not found instead of silently passing. - let alias = self.interner.get_type_alias(id); - return Type::Alias(alias, args); - } - - match self.lookup_struct_or_error(path) { - Some(struct_type) => { - if self.resolving_ids.contains(&struct_type.borrow().id) { - self.push_err(ResolverError::SelfReferentialStruct { - span: struct_type.borrow().name.span(), - }); - - return Type::Error; - } - - let expected_generic_count = struct_type.borrow().generics.len(); - if !self.in_contract - && self - .interner - .struct_attributes(&struct_type.borrow().id) - .iter() - .any(|attr| matches!(attr, SecondaryAttribute::Abi(_))) - { - self.push_err(ResolverError::AbiAttributeOutsideContract { - span: struct_type.borrow().name.span(), - }); - } - self.verify_generics_count(expected_generic_count, &mut args, span, || { - struct_type.borrow().to_string() - }); - - if let Some(current_item) = self.current_item { - let dependency_id = struct_type.borrow().id; - self.interner.add_type_dependency(current_item, dependency_id); - } - - Type::Struct(struct_type, args) - } - None => Type::Error, - } - } - - fn resolve_trait_as_type(&mut self, path: Path, args: Vec) -> Type { - let args = vecmap(args, |arg| self.resolve_type_inner(arg)); - - if let Some(t) = self.lookup_trait_or_error(path) { - Type::TraitAsType(t.id, Rc::new(t.name.to_string()), args) - } else { - Type::Error - } - } - - fn verify_generics_count( - &mut self, - expected_count: usize, - args: &mut Vec, - span: Span, - type_name: impl FnOnce() -> String, - ) { - if args.len() != expected_count { - self.errors.push(ResolverError::IncorrectGenericCount { - span, - item_name: type_name(), - actual: args.len(), - expected: expected_count, - }); - - // Fix the generic count so we can continue typechecking - args.resize_with(expected_count, || Type::Error); - } - } - - fn lookup_generic_or_global_type(&mut self, path: &Path) -> Option { - if path.segments.len() == 1 { - let name = &path.last_segment().0.contents; - if let Some(generic) = self.find_generic(name) { - // We always insert a `TypeKind::Normal` as we do not support explicit numeric generics - // in the resolver - return Some(Type::NamedGeneric( - generic.type_var.clone(), - generic.name.clone(), - Kind::Normal, - )); - }; - } - - // If we cannot find a local generic of the same name, try to look up a global - match self.path_resolver.resolve(self.def_maps, path.clone(), &mut None) { - Ok(PathResolution { module_def_id: ModuleDefId::GlobalId(id), error }) => { - if let Some(current_item) = self.current_item { - self.interner.add_global_dependency(current_item, id); - } - - if let Some(error) = error { - self.push_err(error.into()); - } - Some(Type::Constant(self.eval_global_as_array_length(id, path))) - } - _ => None, - } - } - - fn convert_expression_type(&mut self, length: UnresolvedTypeExpression) -> Type { - match length { - UnresolvedTypeExpression::Variable(path) => { - self.lookup_generic_or_global_type(&path).unwrap_or_else(|| { - self.push_err(ResolverError::NoSuchNumericTypeVariable { path }); - Type::Constant(0) - }) - } - UnresolvedTypeExpression::Constant(int, _) => Type::Constant(int), - UnresolvedTypeExpression::BinaryOperation(lhs, op, rhs, _) => { - let (lhs_span, rhs_span) = (lhs.span(), rhs.span()); - let lhs = self.convert_expression_type(*lhs); - let rhs = self.convert_expression_type(*rhs); - - match (lhs, rhs) { - (Type::Constant(lhs), Type::Constant(rhs)) => { - Type::Constant(op.function()(lhs, rhs)) - } - (lhs, _) => { - let span = - if !matches!(lhs, Type::Constant(_)) { lhs_span } else { rhs_span }; - self.push_err(ResolverError::InvalidArrayLengthExpr { span }); - Type::Constant(0) - } - } - } - } - } - - fn get_ident_from_path(&mut self, path: Path) -> (HirIdent, usize) { - let location = Location::new(path.span(), self.file); - - let error = match path.as_ident().map(|ident| self.find_variable(ident)) { - Some(Ok(found)) => return found, - // Try to look it up as a global, but still issue the first error if we fail - Some(Err(error)) => match self.lookup_global(path) { - Ok(id) => return (HirIdent::non_trait_method(id, location), 0), - Err(_) => error, - }, - None => match self.lookup_global(path) { - Ok(id) => return (HirIdent::non_trait_method(id, location), 0), - Err(error) => error, - }, - }; - self.push_err(error); - let id = DefinitionId::dummy_id(); - (HirIdent::non_trait_method(id, location), 0) - } - - /// Translates an UnresolvedType to a Type - pub fn resolve_type(&mut self, typ: UnresolvedType) -> Type { - let span = typ.span; - let resolved_type = self.resolve_type_inner(typ); - if resolved_type.is_nested_slice() { - self.errors.push(ResolverError::NestedSlices { span: span.unwrap() }); - } - resolved_type - } - - pub fn resolve_type_alias( - mut self, - unresolved: NoirTypeAlias, - alias_id: TypeAliasId, - ) -> (Type, Generics, Vec) { - let generics = self.add_generics(&unresolved.generics); - self.resolve_local_globals(); - - self.current_item = Some(DependencyId::Alias(alias_id)); - let typ = self.resolve_type(unresolved.typ); - - (typ, generics, self.errors) - } - - pub fn take_errors(self) -> Vec { - self.errors - } - - /// Return the current generics. - /// Needed to keep referring to the same type variables across many - /// methods in a single impl. - pub fn get_generics(&self) -> &[ResolvedGeneric] { - &self.generics - } - - /// Set the current generics that are in scope. - /// Unlike add_generics, this function will not create any new type variables, - /// opting to reuse the existing ones it is directly given. - pub fn set_generics(&mut self, generics: Vec) { - self.generics = generics; - } - - /// Translates a (possibly Unspecified) UnresolvedType to a Type. - /// Any UnresolvedType::Unspecified encountered are replaced with fresh type variables. - fn resolve_inferred_type(&mut self, typ: UnresolvedType) -> Type { - match &typ.typ { - UnresolvedTypeData::Unspecified => self.interner.next_type_variable(), - _ => self.resolve_type(typ), - } - } - - /// Add the given generics to scope. - /// Each generic will have a fresh Shared associated with it. - pub fn add_generics(&mut self, generics: &UnresolvedGenerics) -> Generics { - vecmap(generics, |generic| { - // Map the generic to a fresh type variable - let id = self.interner.next_type_variable_id(); - let typevar = TypeVariable::unbound(id); - let ident = generic.ident(); - let span = ident.0.span(); - - // Check for name collisions of this generic - let name = Rc::new(ident.0.contents.clone()); - - let resolved_generic = ResolvedGeneric { - name: name.clone(), - type_var: typevar, - // We only support numeric generics in the elaborator - kind: Kind::Normal, - span, - }; - if let Some(generic) = self.find_generic(&name) { - self.errors.push(ResolverError::DuplicateDefinition { - name: ident.0.contents.clone(), - first_span: generic.span, - second_span: span, - }); - } else { - self.generics.push(resolved_generic.clone()); - } - - resolved_generic - }) - } - - /// Add the given existing generics to scope. - /// This is useful for adding the same generics to many items. E.g. apply impl generics - /// to each function in the impl or trait generics to each item in the trait. - pub fn add_existing_generics( - &mut self, - unresolved_generics: &UnresolvedGenerics, - generics: &GenericTypeVars, - ) { - assert_eq!(unresolved_generics.len(), generics.len()); - - for (unresolved_generic, typevar) in unresolved_generics.iter().zip(generics) { - self.add_existing_generic( - unresolved_generic, - unresolved_generic.span(), - typevar.clone(), - ); - } - } - - pub fn add_existing_generic( - &mut self, - unresolved_generic: &UnresolvedGeneric, - span: Span, - typevar: TypeVariable, - ) { - let name = &unresolved_generic.ident().0.contents; - - // Check for name collisions of this generic - let rc_name = Rc::new(name.clone()); - - if let Some(generic) = self.find_generic(&rc_name) { - self.errors.push(ResolverError::DuplicateDefinition { - name: name.clone(), - first_span: generic.span, - second_span: span, - }); - } else { - let resolved_generic = ResolvedGeneric { - name: rc_name, - type_var: typevar.clone(), - kind: unresolved_generic - .kind() - .expect("ICE: Deprecated code should only support normal kinds"), - span, - }; - self.generics.push(resolved_generic); - } - } - - pub fn resolve_struct_fields( - mut self, - unresolved: NoirStruct, - struct_id: StructId, - ) -> (Generics, Vec<(Ident, Type)>, Vec) { - let generics = self.add_generics(&unresolved.generics); - - // Check whether the struct definition has globals in the local module and add them to the scope - self.resolve_local_globals(); - - self.current_item = Some(DependencyId::Struct(struct_id)); - - self.resolving_ids.insert(struct_id); - let fields = vecmap(unresolved.fields, |(ident, typ)| (ident, self.resolve_type(typ))); - self.resolving_ids.remove(&struct_id); - - (generics, fields, self.errors) - } - - fn resolve_local_globals(&mut self) { - let globals = vecmap(self.interner.get_all_globals(), |global| { - (global.id, global.local_id, global.ident.clone()) - }); - for (id, local_module_id, name) in globals { - if local_module_id == self.path_resolver.local_module_id() { - let definition = DefinitionKind::Global(id); - self.add_global_variable_decl(name, definition); - } - } - } - - /// TODO: This is currently only respected for generic free functions - /// there's a bunch of other places where trait constraints can pop up - fn resolve_trait_constraints( - &mut self, - where_clause: &[UnresolvedTraitConstraint], - ) -> Vec { - where_clause - .iter() - .cloned() - .filter_map(|constraint| self.resolve_trait_constraint(constraint)) - .collect() - } - - /// Extract metadata from a NoirFunction - /// to be used in analysis and intern the function parameters - /// Prerequisite: self.add_generics() has already been called with the given - /// function's generics, including any generics from the impl, if any. - fn extract_meta(&mut self, func: &NoirFunction, func_id: FuncId) -> FuncMeta { - let location = Location::new(func.name_ident().span(), self.file); - let id = self.interner.function_definition_id(func_id); - let name_ident = HirIdent::non_trait_method(id, location); - - let attributes = func.attributes().clone(); - let has_no_predicates_attribute = attributes.is_no_predicates(); - let should_fold = attributes.is_foldable(); - if !self.inline_attribute_allowed(func) { - if has_no_predicates_attribute { - self.push_err(ResolverError::NoPredicatesAttributeOnUnconstrained { - ident: func.name_ident().clone(), - }); - } else if should_fold { - self.push_err(ResolverError::FoldAttributeOnUnconstrained { - ident: func.name_ident().clone(), - }); - } - } - // Both the #[fold] and #[no_predicates] alter a function's inline type and code generation in similar ways. - // In certain cases such as type checking (for which the following flag will be used) both attributes - // indicate we should code generate in the same way. Thus, we unify the attributes into one flag here. - let has_inline_attribute = has_no_predicates_attribute || should_fold; - - let generics = vecmap(&self.generics, |generic| generic.type_var.clone()); - let mut parameters = vec![]; - let mut parameter_types = vec![]; - - for Param { visibility, pattern, typ, span: _ } in func.parameters().iter().cloned() { - if visibility == Visibility::Public && !self.pub_allowed(func) { - self.push_err(ResolverError::UnnecessaryPub { - ident: func.name_ident().clone(), - position: PubPosition::Parameter, - }); - } - - let pattern = self.resolve_pattern(pattern, DefinitionKind::Local(None)); - let typ = self.resolve_type_inner(typ); - - parameters.push((pattern, typ.clone(), visibility)); - parameter_types.push(typ); - } - - let return_type = Box::new(self.resolve_type(func.return_type())); - - self.declare_numeric_generics(¶meter_types, &return_type); - - if !self.pub_allowed(func) && func.def.return_visibility == Visibility::Public { - self.push_err(ResolverError::UnnecessaryPub { - ident: func.name_ident().clone(), - position: PubPosition::ReturnType, - }); - } - let is_low_level_function = - attributes.function.as_ref().map_or(false, |func| func.is_low_level()); - if !self.path_resolver.module_id().krate.is_stdlib() && is_low_level_function { - let error = - ResolverError::LowLevelFunctionOutsideOfStdlib { ident: func.name_ident().clone() }; - self.push_err(error); - } - - // 'pub' is required on return types for entry point functions - if self.is_entry_point_function(func) - && return_type.as_ref() != &Type::Unit - && func.def.return_visibility == Visibility::Private - { - self.push_err(ResolverError::NecessaryPub { ident: func.name_ident().clone() }); - } - // '#[recursive]' attribute is only allowed for entry point functions - if !self.is_entry_point_function(func) && func.kind == FunctionKind::Recursive { - self.push_err(ResolverError::MisplacedRecursiveAttribute { - ident: func.name_ident().clone(), - }); - } - - let mut typ = Type::Function(parameter_types, return_type, Box::new(Type::Unit)); - - if !generics.is_empty() { - typ = Type::Forall(generics, Box::new(typ)); - } - - self.interner.push_definition_type(name_ident.id, typ.clone()); - - let direct_generics = func.def.generics.iter(); - let direct_generics = direct_generics - .filter_map(|generic| self.find_generic(&generic.ident().0.contents).cloned()) - .collect(); - - FuncMeta { - name: name_ident, - kind: func.kind, - location, - typ, - direct_generics, - trait_impl: self.current_trait_impl, - parameters: parameters.into(), - return_type: func.def.return_type.clone(), - return_visibility: func.def.return_visibility, - has_body: !func.def.body.is_empty(), - trait_constraints: self.resolve_trait_constraints(&func.def.where_clause), - is_entry_point: self.is_entry_point_function(func), - has_inline_attribute, - source_crate: self.path_resolver.module_id().krate, - - // These fields are only used by the elaborator - all_generics: Vec::new(), - is_trait_function: false, - parameter_idents: Vec::new(), - function_body: FunctionBody::Resolved, - } - } - - /// Override whether this name resolver is within a contract or not. - /// This will affect which types are allowed as parameters to methods as well - /// as which modifiers are allowed on a function. - pub(crate) fn set_in_contract(&mut self, in_contract: bool) { - self.in_contract = in_contract; - } - - /// True if the 'pub' keyword is allowed on parameters in this function - /// 'pub' on function parameters is only allowed for entry point functions - fn pub_allowed(&self, func: &NoirFunction) -> bool { - self.is_entry_point_function(func) || func.attributes().is_foldable() - } - - fn is_entry_point_function(&self, func: &NoirFunction) -> bool { - if self.in_contract { - func.attributes().is_contract_entry_point() - } else { - func.name() == MAIN_FUNCTION - } - } - - fn inline_attribute_allowed(&self, func: &NoirFunction) -> bool { - // Inline attributes are only relevant for constrained functions - // as all unconstrained functions are not inlined - !func.def.is_unconstrained - } - - // TODO(https://github.com/noir-lang/noir/issues/5156): Remove this method in favor of explicit numeric generics - fn declare_numeric_generics(&mut self, params: &[Type], return_type: &Type) { - if self.generics.is_empty() { - return; - } - - for (name_to_find, type_variable) in Self::find_numeric_generics(params, return_type) { - // Declare any generics to let users use numeric generics in scope. - // Don't issue a warning if these are unused - // - // We can fail to find the generic in self.generics if it is an implicit one created - // by the compiler. This can happen when, e.g. eliding array lengths using the slice - // syntax [T]. - if let Some(ResolvedGeneric { name, span, .. }) = - self.generics.iter().find(|generic| generic.name.as_ref() == &name_to_find) - { - let ident = Ident::new(name.to_string(), *span); - let definition = DefinitionKind::GenericType(type_variable); - self.add_variable_decl_inner(ident.clone(), false, false, false, definition); - } - } - } - - fn find_numeric_generics( - parameters: &[Type], - return_type: &Type, - ) -> Vec<(String, TypeVariable)> { - let mut found = BTreeMap::new(); - for parameter in parameters { - Self::find_numeric_generics_in_type(parameter, &mut found); - } - Self::find_numeric_generics_in_type(return_type, &mut found); - found.into_iter().collect() - } - - fn find_numeric_generics_in_type(typ: &Type, found: &mut BTreeMap) { - match typ { - Type::FieldElement - | Type::Integer(_, _) - | Type::Bool - | Type::Unit - | Type::Error - | Type::TypeVariable(_, _) - | Type::Constant(_) - | Type::NamedGeneric(_, _, _) - | Type::Quoted(_) - | Type::Forall(_, _) => (), - - Type::TraitAsType(_, _, args) => { - for arg in args { - Self::find_numeric_generics_in_type(arg, found); - } - } - - Type::Array(length, element_type) => { - if let Type::NamedGeneric(type_variable, name, _) = length.as_ref() { - found.insert(name.to_string(), type_variable.clone()); - } - Self::find_numeric_generics_in_type(element_type, found); - } - - Type::Slice(element_type) => { - Self::find_numeric_generics_in_type(element_type, found); - } - - Type::Tuple(fields) => { - for field in fields { - Self::find_numeric_generics_in_type(field, found); - } - } - - Type::Function(parameters, return_type, _env) => { - for parameter in parameters { - Self::find_numeric_generics_in_type(parameter, found); - } - Self::find_numeric_generics_in_type(return_type, found); - } - - Type::Struct(struct_type, generics) => { - for (i, generic) in generics.iter().enumerate() { - if let Type::NamedGeneric(type_variable, name, _) = generic { - if struct_type.borrow().generic_is_numeric(i) { - found.insert(name.to_string(), type_variable.clone()); - } - } else { - Self::find_numeric_generics_in_type(generic, found); - } - } - } - Type::Alias(alias, generics) => { - for (i, generic) in generics.iter().enumerate() { - if let Type::NamedGeneric(type_variable, name, _) = generic { - if alias.borrow().generic_is_numeric(i) { - found.insert(name.to_string(), type_variable.clone()); - } - } else { - Self::find_numeric_generics_in_type(generic, found); - } - } - } - Type::MutableReference(element) => Self::find_numeric_generics_in_type(element, found), - Type::String(length) => { - if let Type::NamedGeneric(type_variable, name, _) = length.as_ref() { - found.insert(name.to_string(), type_variable.clone()); - } - } - Type::FmtString(length, fields) => { - if let Type::NamedGeneric(type_variable, name, _) = length.as_ref() { - found.insert(name.to_string(), type_variable.clone()); - } - Self::find_numeric_generics_in_type(fields, found); - } - } - } - - pub fn resolve_global_let( - &mut self, - let_stmt: LetStatement, - global_id: GlobalId, - ) -> HirStatement { - self.current_item = Some(DependencyId::Global(global_id)); - let expression = self.resolve_expression(let_stmt.expression); - let definition = DefinitionKind::Global(global_id); - - if !self.in_contract - && let_stmt.attributes.iter().any(|attr| matches!(attr, SecondaryAttribute::Abi(_))) - { - let span = let_stmt.pattern.span(); - self.push_err(ResolverError::AbiAttributeOutsideContract { span }); - } - - if !let_stmt.comptime && matches!(let_stmt.pattern, Pattern::Mutable(..)) { - let span = let_stmt.pattern.span(); - self.push_err(ResolverError::MutableGlobal { span }); - } - - HirStatement::Let(HirLetStatement { - pattern: self.resolve_pattern(let_stmt.pattern, definition), - r#type: self.resolve_type(let_stmt.r#type), - expression, - attributes: let_stmt.attributes, - comptime: let_stmt.comptime, - }) - } - - pub fn resolve_stmt(&mut self, stmt: StatementKind, span: Span) -> HirStatement { - match stmt { - StatementKind::Let(let_stmt) => { - let expression = self.resolve_expression(let_stmt.expression); - let definition = DefinitionKind::Local(Some(expression)); - HirStatement::Let(HirLetStatement { - pattern: self.resolve_pattern(let_stmt.pattern, definition), - r#type: self.resolve_type(let_stmt.r#type), - expression, - attributes: let_stmt.attributes, - comptime: let_stmt.comptime, - }) - } - StatementKind::Constrain(constrain_stmt) => { - let expr_id = self.resolve_expression(constrain_stmt.0); - let assert_message_expr_id = - constrain_stmt.1.map(|assert_expr_id| self.resolve_expression(assert_expr_id)); - - HirStatement::Constrain(HirConstrainStatement( - expr_id, - self.file, - assert_message_expr_id, - )) - } - StatementKind::Expression(expr) => { - HirStatement::Expression(self.resolve_expression(expr)) - } - StatementKind::Semi(expr) => HirStatement::Semi(self.resolve_expression(expr)), - StatementKind::Assign(assign_stmt) => { - let identifier = self.resolve_lvalue(assign_stmt.lvalue); - let expression = self.resolve_expression(assign_stmt.expression); - let stmt = HirAssignStatement { lvalue: identifier, expression }; - HirStatement::Assign(stmt) - } - StatementKind::For(for_loop) => { - match for_loop.range { - ForRange::Range(start_range, end_range) => { - let start_range = self.resolve_expression(start_range); - let end_range = self.resolve_expression(end_range); - let (identifier, block) = (for_loop.identifier, for_loop.block); - - self.nested_loops += 1; - - // TODO: For loop variables are currently mutable by default since we haven't - // yet implemented syntax for them to be optionally mutable. - let (identifier, block) = self.in_new_scope(|this| { - let decl = this.add_variable_decl( - identifier, - false, - true, - DefinitionKind::Local(None), - ); - (decl, this.resolve_expression(block)) - }); - - self.nested_loops -= 1; - - HirStatement::For(HirForStatement { - start_range, - end_range, - block, - identifier, - }) - } - range @ ForRange::Array(_) => { - let for_stmt = - range.into_for(for_loop.identifier, for_loop.block, for_loop.span); - self.resolve_stmt(for_stmt.kind, for_loop.span) - } - } - } - StatementKind::Break => { - self.check_break_continue(true, span); - HirStatement::Break - } - StatementKind::Continue => { - self.check_break_continue(false, span); - HirStatement::Continue - } - StatementKind::Error => HirStatement::Error, - StatementKind::Comptime(statement) => { - let hir_statement = self.resolve_stmt(statement.kind, statement.span); - let statement_id = self.interner.push_stmt(hir_statement); - self.interner.push_stmt_location(statement_id, statement.span, self.file); - HirStatement::Comptime(statement_id) - } - } - } - - pub fn intern_stmt(&mut self, stmt: Statement) -> StmtId { - let hir_stmt = self.resolve_stmt(stmt.kind, stmt.span); - let id = self.interner.push_stmt(hir_stmt); - self.interner.push_stmt_location(id, stmt.span, self.file); - id - } - - fn resolve_lvalue(&mut self, lvalue: LValue) -> HirLValue { - match lvalue { - LValue::Ident(ident) => { - let ident = self.find_variable_or_default(&ident); - self.resolve_local_variable(ident.0.clone(), ident.1); - - HirLValue::Ident(ident.0, Type::Error) - } - LValue::MemberAccess { object, field_name, span } => HirLValue::MemberAccess { - object: Box::new(self.resolve_lvalue(*object)), - field_name, - location: Location::new(span, self.file), - field_index: None, - typ: Type::Error, - }, - LValue::Index { array, index, span } => { - let array = Box::new(self.resolve_lvalue(*array)); - let index = self.resolve_expression(index); - let location = Location::new(span, self.file); - HirLValue::Index { array, index, location, typ: Type::Error } - } - LValue::Dereference(lvalue, span) => { - let lvalue = Box::new(self.resolve_lvalue(*lvalue)); - let location = Location::new(span, self.file); - HirLValue::Dereference { lvalue, location, element_type: Type::Error } - } - } - } - - fn resolve_local_variable(&mut self, hir_ident: HirIdent, var_scope_index: usize) { - let mut transitive_capture_index: Option = None; - - for lambda_index in 0..self.lambda_stack.len() { - if self.lambda_stack[lambda_index].scope_index > var_scope_index { - // Beware: the same variable may be captured multiple times, so we check - // for its presence before adding the capture below. - let pos = self.lambda_stack[lambda_index] - .captures - .iter() - .position(|capture| capture.ident.id == hir_ident.id); - - if pos.is_none() { - self.lambda_stack[lambda_index].captures.push(HirCapturedVar { - ident: hir_ident.clone(), - transitive_capture_index, - }); - } - - if lambda_index + 1 < self.lambda_stack.len() { - // There is more than one closure between the current scope and - // the scope of the variable, so this is a propagated capture. - // We need to track the transitive capture index as we go up in - // the closure stack. - transitive_capture_index = Some(pos.unwrap_or( - // If this was a fresh capture, we added it to the end of - // the captures vector: - self.lambda_stack[lambda_index].captures.len() - 1, - )); - } - } - } - } - - fn resolve_array_literal(&mut self, array_literal: ArrayLiteral) -> HirArrayLiteral { - match array_literal { - ArrayLiteral::Standard(elements) => { - let elements = vecmap(elements, |elem| self.resolve_expression(elem)); - HirArrayLiteral::Standard(elements) - } - ArrayLiteral::Repeated { repeated_element, length } => { - let span = length.span; - let length = - UnresolvedTypeExpression::from_expr(*length, span).unwrap_or_else(|error| { - self.errors.push(ResolverError::ParserError(Box::new(error))); - UnresolvedTypeExpression::Constant(0, span) - }); - - let length = self.convert_expression_type(length); - let repeated_element = self.resolve_expression(*repeated_element); - - HirArrayLiteral::Repeated { repeated_element, length } - } - } - } - - pub fn resolve_expression(&mut self, expr: Expression) -> ExprId { - let hir_expr = match expr.kind { - ExpressionKind::Literal(literal) => HirExpression::Literal(match literal { - Literal::Bool(b) => HirLiteral::Bool(b), - Literal::Array(array_literal) => { - HirLiteral::Array(self.resolve_array_literal(array_literal)) - } - Literal::Slice(array_literal) => { - HirLiteral::Slice(self.resolve_array_literal(array_literal)) - } - Literal::Integer(integer, sign) => HirLiteral::Integer(integer, sign), - Literal::Str(str) => HirLiteral::Str(str), - Literal::RawStr(str, _) => HirLiteral::Str(str), - Literal::FmtStr(str) => self.resolve_fmt_str_literal(str, expr.span), - Literal::Unit => HirLiteral::Unit, - }), - ExpressionKind::Variable(path, generics) => { - let generics = - generics.map(|generics| vecmap(generics, |typ| self.resolve_type(typ))); - - if let Some((method, constraint, assumed)) = self.resolve_trait_generic_path(&path) - { - HirExpression::Ident( - HirIdent { - location: Location::new(expr.span, self.file), - id: self.interner.trait_method_id(method), - impl_kind: ImplKind::TraitMethod(method, constraint, assumed), - }, - generics, - ) - } else { - // If the Path is being used as an Expression, then it is referring to a global from a separate module - // Otherwise, then it is referring to an Identifier - // This lookup allows support of such statements: let x = foo::bar::SOME_GLOBAL + 10; - // If the expression is a singular indent, we search the resolver's current scope as normal. - let (hir_ident, var_scope_index) = self.get_ident_from_path(path.clone()); - - if hir_ident.id != DefinitionId::dummy_id() { - match self.interner.definition(hir_ident.id).kind { - DefinitionKind::Function(id) => { - if let Some(current_item) = self.current_item { - self.interner.add_function_dependency(current_item, id); - } - } - DefinitionKind::Global(global_id) => { - if let Some(current_item) = self.current_item { - self.interner.add_global_dependency(current_item, global_id); - } - } - DefinitionKind::GenericType(_) => { - // Initialize numeric generics to a polymorphic integer type in case - // they're used in expressions. We must do this here since the type - // checker does not check definition kinds and otherwise expects - // parameters to already be typed. - if self.interner.definition_type(hir_ident.id) == Type::Error { - let typ = Type::polymorphic_integer_or_field(self.interner); - self.interner.push_definition_type(hir_ident.id, typ); - } - } - DefinitionKind::Local(_) => { - // only local variables can be captured by closures. - self.resolve_local_variable(hir_ident.clone(), var_scope_index); - } - } - } - - HirExpression::Ident(hir_ident, generics) - } - } - ExpressionKind::Prefix(prefix) => { - let operator = prefix.operator; - let rhs = self.resolve_expression(prefix.rhs); - - if operator == UnaryOp::MutableReference { - if let Err(error) = verify_mutable_reference(self.interner, rhs) { - self.errors.push(error); - } - } - - HirExpression::Prefix(HirPrefixExpression { operator, rhs }) - } - ExpressionKind::Infix(infix) => { - let lhs = self.resolve_expression(infix.lhs); - let rhs = self.resolve_expression(infix.rhs); - let trait_id = self.interner.get_operator_trait_method(infix.operator.contents); - - HirExpression::Infix(HirInfixExpression { - lhs, - operator: HirBinaryOp::new(infix.operator, self.file), - trait_method_id: trait_id, - rhs, - }) - } - ExpressionKind::Call(call_expr) => { - // Get the span and name of path for error reporting - let func = self.resolve_expression(*call_expr.func); - - let arguments = vecmap(call_expr.arguments, |arg| self.resolve_expression(arg)); - let location = Location::new(expr.span, self.file); - HirExpression::Call(HirCallExpression { func, arguments, location }) - } - ExpressionKind::MethodCall(call_expr) => { - let method = call_expr.method_name; - let object = self.resolve_expression(call_expr.object); - - // Cannot verify the generic count here equals the expected count since we don't - // know which definition `method` refers to until it is resolved during type checking. - let generics = call_expr - .generics - .map(|generics| vecmap(generics, |typ| self.resolve_type(typ))); - - let arguments = vecmap(call_expr.arguments, |arg| self.resolve_expression(arg)); - let location = Location::new(expr.span, self.file); - HirExpression::MethodCall(HirMethodCallExpression { - method, - object, - generics, - arguments, - location, - }) - } - ExpressionKind::Cast(cast_expr) => HirExpression::Cast(HirCastExpression { - lhs: self.resolve_expression(cast_expr.lhs), - r#type: self.resolve_type(cast_expr.r#type), - }), - ExpressionKind::If(if_expr) => HirExpression::If(HirIfExpression { - condition: self.resolve_expression(if_expr.condition), - consequence: self.resolve_expression(if_expr.consequence), - alternative: if_expr.alternative.map(|e| self.resolve_expression(e)), - }), - ExpressionKind::Index(indexed_expr) => HirExpression::Index(HirIndexExpression { - collection: self.resolve_expression(indexed_expr.collection), - index: self.resolve_expression(indexed_expr.index), - }), - ExpressionKind::Block(block_expr) => { - HirExpression::Block(self.resolve_block(block_expr)) - } - ExpressionKind::Constructor(constructor) => { - let span = constructor.type_name.span(); - - match self.lookup_type_or_error(constructor.type_name) { - Some(Type::Struct(r#type, struct_generics)) => { - let typ = r#type.clone(); - let fields = constructor.fields; - let resolve_expr = Resolver::resolve_expression; - let fields = - self.resolve_constructor_fields(typ, fields, span, resolve_expr); - HirExpression::Constructor(HirConstructorExpression { - fields, - r#type, - struct_generics, - }) - } - Some(typ) => { - self.push_err(ResolverError::NonStructUsedInConstructor { typ, span }); - HirExpression::Error - } - None => HirExpression::Error, - } - } - ExpressionKind::MemberAccess(access) => { - // Validating whether the lhs actually has the rhs as a field - // needs to wait until type checking when we know the type of the lhs - HirExpression::MemberAccess(HirMemberAccess { - lhs: self.resolve_expression(access.lhs), - rhs: access.rhs, - // This is only used when lhs is a reference and we want to return a reference to rhs - is_offset: false, - }) - } - ExpressionKind::Error => HirExpression::Error, - ExpressionKind::Tuple(elements) => { - let elements = vecmap(elements, |elem| self.resolve_expression(elem)); - HirExpression::Tuple(elements) - } - // We must stay in the same function scope as the parent function to allow for closures - // to capture variables. This is currently limited to immutable variables. - ExpressionKind::Lambda(lambda) => self.in_new_scope(|this| { - let scope_index = this.scopes.current_scope_index(); - - this.lambda_stack.push(LambdaContext { captures: Vec::new(), scope_index }); - - let parameters = vecmap(lambda.parameters, |(pattern, typ)| { - let parameter = DefinitionKind::Local(None); - (this.resolve_pattern(pattern, parameter), this.resolve_inferred_type(typ)) - }); - - let return_type = this.resolve_inferred_type(lambda.return_type); - let body = this.resolve_expression(lambda.body); - - let lambda_context = this.lambda_stack.pop().unwrap(); - - HirExpression::Lambda(HirLambda { - parameters, - return_type, - body, - captures: lambda_context.captures, - }) - }), - ExpressionKind::Parenthesized(sub_expr) => return self.resolve_expression(*sub_expr), - - // The quoted expression isn't resolved since we don't want errors if variables aren't defined - ExpressionKind::Quote(block) => HirExpression::Quote(block), - ExpressionKind::Comptime(block, _) => { - HirExpression::Comptime(self.resolve_block(block)) - } - ExpressionKind::Resolved(_) => unreachable!( - "ExpressionKind::Resolved should only be emitted by the comptime interpreter" - ), - ExpressionKind::Unquote(_) => { - self.push_err(ResolverError::UnquoteUsedOutsideQuote { span: expr.span }); - HirExpression::Literal(HirLiteral::Unit) - } - }; - - // If these lines are ever changed, make sure to change the early return - // in the ExpressionKind::Variable case as well - let expr_id = self.interner.push_expr(hir_expr); - self.interner.push_expr_location(expr_id, expr.span, self.file); - expr_id - } - - fn resolve_pattern(&mut self, pattern: Pattern, definition: DefinitionKind) -> HirPattern { - self.resolve_pattern_mutable(pattern, None, definition) - } - - fn resolve_pattern_mutable( - &mut self, - pattern: Pattern, - mutable: Option, - definition: DefinitionKind, - ) -> HirPattern { - match pattern { - Pattern::Identifier(name) => { - // If this definition is mutable, do not store the rhs because it will - // not always refer to the correct value of the variable - let definition = match (mutable, definition) { - (Some(_), DefinitionKind::Local(_)) => DefinitionKind::Local(None), - (_, other) => other, - }; - let id = self.add_variable_decl(name, mutable.is_some(), true, definition); - HirPattern::Identifier(id) - } - Pattern::Mutable(pattern, span, _) => { - if let Some(first_mut) = mutable { - self.push_err(ResolverError::UnnecessaryMut { first_mut, second_mut: span }); - } - - let pattern = self.resolve_pattern_mutable(*pattern, Some(span), definition); - let location = Location::new(span, self.file); - HirPattern::Mutable(Box::new(pattern), location) - } - Pattern::Tuple(fields, span) => { - let fields = vecmap(fields, |field| { - self.resolve_pattern_mutable(field, mutable, definition.clone()) - }); - let location = Location::new(span, self.file); - HirPattern::Tuple(fields, location) - } - Pattern::Struct(name, fields, span) => { - let error_identifier = |this: &mut Self| { - // Must create a name here to return a HirPattern::Identifier. Allowing - // shadowing here lets us avoid further errors if we define ERROR_IDENT - // multiple times. - let name = ERROR_IDENT.into(); - let identifier = this.add_variable_decl(name, false, true, definition.clone()); - HirPattern::Identifier(identifier) - }; - - let (struct_type, generics) = match self.lookup_type_or_error(name) { - Some(Type::Struct(struct_type, generics)) => (struct_type, generics), - None => return error_identifier(self), - Some(typ) => { - self.push_err(ResolverError::NonStructUsedInConstructor { typ, span }); - return error_identifier(self); - } - }; - - let resolve_field = |this: &mut Self, pattern| { - this.resolve_pattern_mutable(pattern, mutable, definition.clone()) - }; - - let typ = struct_type.clone(); - let fields = self.resolve_constructor_fields(typ, fields, span, resolve_field); - - let typ = Type::Struct(struct_type, generics); - let location = Location::new(span, self.file); - HirPattern::Struct(typ, fields, location) - } - } - } - - /// Resolve all the fields of a struct constructor expression. - /// Ensures all fields are present, none are repeated, and all - /// are part of the struct. - /// - /// This is generic to allow it to work for constructor expressions - /// and constructor patterns. - fn resolve_constructor_fields( - &mut self, - struct_type: Shared, - fields: Vec<(Ident, T)>, - span: Span, - mut resolve_function: impl FnMut(&mut Self, T) -> U, - ) -> Vec<(Ident, U)> { - let mut ret = Vec::with_capacity(fields.len()); - let mut seen_fields = HashSet::new(); - let mut unseen_fields = struct_type.borrow().field_names(); - - for (field, expr) in fields { - let resolved = resolve_function(self, expr); - - if unseen_fields.contains(&field) { - unseen_fields.remove(&field); - seen_fields.insert(field.clone()); - } else if seen_fields.contains(&field) { - // duplicate field - self.push_err(ResolverError::DuplicateField { field: field.clone() }); - } else { - // field not required by struct - self.push_err(ResolverError::NoSuchField { - field: field.clone(), - struct_definition: struct_type.borrow().name.clone(), - }); - } - - ret.push((field, resolved)); - } - - if !unseen_fields.is_empty() { - self.push_err(ResolverError::MissingFields { - span, - missing_fields: unseen_fields.into_iter().map(|field| field.to_string()).collect(), - struct_definition: struct_type.borrow().name.clone(), - }); - } - - ret - } - - pub fn get_struct(&self, type_id: StructId) -> Shared { - self.interner.get_struct(type_id) - } - - pub fn get_trait_mut(&mut self, trait_id: TraitId) -> &mut Trait { - self.interner.get_trait_mut(trait_id) - } - - fn lookup(&mut self, path: Path) -> Result { - let span = path.span(); - let id = self.resolve_path(path)?; - T::try_from(id).ok_or_else(|| ResolverError::Expected { - expected: T::description(), - got: id.as_str().to_owned(), - span, - }) - } - - fn lookup_global(&mut self, path: Path) -> Result { - let span = path.span(); - let id = self.resolve_path(path)?; - - if let Some(function) = TryFromModuleDefId::try_from(id) { - return Ok(self.interner.function_definition_id(function)); - } - - if let Some(global) = TryFromModuleDefId::try_from(id) { - let global = self.interner.get_global(global); - return Ok(global.definition_id); - } - - let expected = "global variable".into(); - let got = "local variable".into(); - Err(ResolverError::Expected { span, expected, got }) - } - - /// Lookup a given struct type by name. - fn lookup_struct_or_error(&mut self, path: Path) -> Option> { - match self.lookup(path) { - Ok(struct_id) => Some(self.get_struct(struct_id)), - Err(error) => { - self.push_err(error); - None - } - } - } - - /// Lookup a given trait by name/path. - fn lookup_trait_or_error(&mut self, path: Path) -> Option<&mut Trait> { - match self.lookup(path) { - Ok(trait_id) => Some(self.get_trait_mut(trait_id)), - Err(error) => { - self.push_err(error); - None - } - } - } - - /// Looks up a given type by name. - /// This will also instantiate any struct types found. - fn lookup_type_or_error(&mut self, path: Path) -> Option { - let ident = path.as_ident(); - if ident.map_or(false, |i| i == SELF_TYPE_NAME) { - if let Some(typ) = &self.self_type { - return Some(typ.clone()); - } - } - - match self.lookup(path) { - Ok(struct_id) => { - let struct_type = self.get_struct(struct_id); - let generics = struct_type.borrow().instantiate(self.interner); - Some(Type::Struct(struct_type, generics)) - } - Err(error) => { - self.push_err(error); - None - } - } - } - - fn lookup_type_alias(&mut self, path: Path) -> Option> { - self.lookup(path).ok().map(|id| self.interner.get_type_alias(id)) - } - - // this resolves Self::some_static_method, inside an impl block (where we don't have a concrete self_type) - fn resolve_trait_static_method_by_self( - &mut self, - path: &Path, - ) -> Option<(TraitMethodId, TraitConstraint, bool)> { - let trait_id = self.trait_id?; - - if path.kind == PathKind::Plain && path.segments.len() == 2 { - let name = &path.segments[0].0.contents; - let method = &path.segments[1]; - - if name == SELF_TYPE_NAME { - let the_trait = self.interner.get_trait(trait_id); - let method = the_trait.find_method(method.0.contents.as_str())?; - - let constraint = TraitConstraint { - typ: self.self_type.clone()?, - trait_generics: Type::from_generics(&vecmap(&the_trait.generics, |generic| { - generic.type_var.clone() - })), - trait_id, - }; - return Some((method, constraint, false)); - } - } - None - } - - // this resolves TraitName::some_static_method - fn resolve_trait_static_method( - &mut self, - path: &Path, - ) -> Option<(TraitMethodId, TraitConstraint, bool)> { - if path.kind == PathKind::Plain && path.segments.len() == 2 { - let method = &path.segments[1]; - - let mut trait_path = path.clone(); - trait_path.pop(); - let trait_id = self.lookup(trait_path).ok()?; - let the_trait = self.interner.get_trait(trait_id); - - let method = the_trait.find_method(method.0.contents.as_str())?; - let constraint = TraitConstraint { - typ: Type::TypeVariable( - the_trait.self_type_typevar.clone(), - TypeVariableKind::Normal, - ), - trait_generics: Type::from_generics(&vecmap(&the_trait.generics, |generic| { - generic.type_var.clone() - })), - trait_id, - }; - return Some((method, constraint, false)); - } - None - } - - // This resolves a static trait method T::trait_method by iterating over the where clause - // - // Returns the trait method, trait constraint, and whether the impl is assumed from a where - // clause. This is always true since this helper searches where clauses for a generic constraint. - // E.g. `t.method()` with `where T: Foo` in scope will return `(Foo::method, T, vec![Bar])` - fn resolve_trait_method_by_named_generic( - &mut self, - path: &Path, - ) -> Option<(TraitMethodId, TraitConstraint, bool)> { - if path.segments.len() != 2 { - return None; - } - - for UnresolvedTraitConstraint { typ, trait_bound } in self.trait_bounds.clone() { - if let UnresolvedTypeData::Named(constraint_path, _, _) = &typ.typ { - // if `path` is `T::method_name`, we're looking for constraint of the form `T: SomeTrait` - if constraint_path.segments.len() == 1 - && path.segments[0] != constraint_path.last_segment() - { - continue; - } - - if let Ok(ModuleDefId::TraitId(trait_id)) = - self.resolve_path(trait_bound.trait_path.clone()) - { - let the_trait = self.interner.get_trait(trait_id); - if let Some(method) = - the_trait.find_method(path.segments.last().unwrap().0.contents.as_str()) - { - let constraint = TraitConstraint { - trait_id, - typ: self.resolve_type(typ.clone()), - trait_generics: vecmap(trait_bound.trait_generics, |typ| { - self.resolve_type(typ) - }), - }; - return Some((method, constraint, true)); - } - } - } - } - None - } - - // Try to resolve the given trait method path. - // - // Returns the trait method, trait constraint, and whether the impl is assumed to exist by a where clause or not - // E.g. `t.method()` with `where T: Foo` in scope will return `(Foo::method, T, vec![Bar])` - fn resolve_trait_generic_path( - &mut self, - path: &Path, - ) -> Option<(TraitMethodId, TraitConstraint, bool)> { - self.resolve_trait_static_method_by_self(path) - .or_else(|| self.resolve_trait_static_method(path)) - .or_else(|| self.resolve_trait_method_by_named_generic(path)) - } - - fn resolve_path(&mut self, path: Path) -> Result { - let path_resolution = self.path_resolver.resolve(self.def_maps, path, &mut None)?; - - if let Some(error) = path_resolution.error { - self.push_err(error.into()); - } - - Ok(path_resolution.module_def_id) - } - - fn resolve_block(&mut self, block_expr: BlockExpression) -> HirBlockExpression { - let statements = - self.in_new_scope(|this| vecmap(block_expr.statements, |stmt| this.intern_stmt(stmt))); - HirBlockExpression { statements } - } - - pub fn intern_block(&mut self, block: BlockExpression) -> ExprId { - let hir_block = HirExpression::Block(self.resolve_block(block)); - self.interner.push_expr(hir_block) - } - - fn eval_global_as_array_length(&mut self, global: GlobalId, path: &Path) -> u32 { - let Some(stmt) = self.interner.get_global_let_statement(global) else { - let path = path.clone(); - self.push_err(ResolverError::NoSuchNumericTypeVariable { path }); - return 0; - }; - - let length = stmt.expression; - let span = self.interner.expr_span(&length); - let result = self.try_eval_array_length_id(length, span); - - match result.map(|length| length.try_into()) { - Ok(Ok(length_value)) => return length_value, - Ok(Err(_cast_err)) => self.push_err(ResolverError::IntegerTooLarge { span }), - Err(Some(error)) => self.push_err(error), - Err(None) => (), - } - 0 - } - - fn try_eval_array_length_id( - &self, - rhs: ExprId, - span: Span, - ) -> Result> { - // Arbitrary amount of recursive calls to try before giving up - let fuel = 100; - self.try_eval_array_length_id_with_fuel(rhs, span, fuel) - } - - fn try_eval_array_length_id_with_fuel( - &self, - rhs: ExprId, - span: Span, - fuel: u32, - ) -> Result> { - if fuel == 0 { - // If we reach here, it is likely from evaluating cyclic globals. We expect an error to - // be issued for them after name resolution so issue no error now. - return Err(None); - } - - match self.interner.expression(&rhs) { - HirExpression::Literal(HirLiteral::Integer(int, false)) => { - int.try_into_u128().ok_or(Some(ResolverError::IntegerTooLarge { span })) - } - HirExpression::Ident(ident, _) => { - let definition = self.interner.definition(ident.id); - match definition.kind { - DefinitionKind::Global(global_id) => { - let let_statement = self.interner.get_global_let_statement(global_id); - if let Some(let_statement) = let_statement { - let expression = let_statement.expression; - self.try_eval_array_length_id_with_fuel(expression, span, fuel - 1) - } else { - Err(Some(ResolverError::InvalidArrayLengthExpr { span })) - } - } - _ => Err(Some(ResolverError::InvalidArrayLengthExpr { span })), - } - } - HirExpression::Infix(infix) => { - let lhs = self.try_eval_array_length_id_with_fuel(infix.lhs, span, fuel - 1)?; - let rhs = self.try_eval_array_length_id_with_fuel(infix.rhs, span, fuel - 1)?; - - match infix.operator.kind { - BinaryOpKind::Add => Ok(lhs + rhs), - BinaryOpKind::Subtract => Ok(lhs - rhs), - BinaryOpKind::Multiply => Ok(lhs * rhs), - BinaryOpKind::Divide => Ok(lhs / rhs), - BinaryOpKind::Equal => Ok((lhs == rhs) as u128), - BinaryOpKind::NotEqual => Ok((lhs != rhs) as u128), - BinaryOpKind::Less => Ok((lhs < rhs) as u128), - BinaryOpKind::LessEqual => Ok((lhs <= rhs) as u128), - BinaryOpKind::Greater => Ok((lhs > rhs) as u128), - BinaryOpKind::GreaterEqual => Ok((lhs >= rhs) as u128), - BinaryOpKind::And => Ok(lhs & rhs), - BinaryOpKind::Or => Ok(lhs | rhs), - BinaryOpKind::Xor => Ok(lhs ^ rhs), - BinaryOpKind::ShiftRight => Ok(lhs >> rhs), - BinaryOpKind::ShiftLeft => Ok(lhs << rhs), - BinaryOpKind::Modulo => Ok(lhs % rhs), - } - } - HirExpression::Cast(cast) => { - let lhs = self.try_eval_array_length_id_with_fuel(cast.lhs, span, fuel - 1)?; - let lhs_value = Value::Field(lhs.into()); - let evaluated_value = - Interpreter::evaluate_cast_one_step(&cast, rhs, lhs_value, self.interner) - .map_err(|error| Some(ResolverError::ArrayLengthInterpreter { error }))?; - - evaluated_value - .to_u128() - .ok_or_else(|| Some(ResolverError::InvalidArrayLengthExpr { span })) - } - _other => Err(Some(ResolverError::InvalidArrayLengthExpr { span })), - } - } - - fn resolve_fmt_str_literal(&mut self, str: String, call_expr_span: Span) -> HirLiteral { - let re = Regex::new(r"\{([a-zA-Z0-9_]+)\}") - .expect("ICE: an invalid regex pattern was used for checking format strings"); - let mut fmt_str_idents = Vec::new(); - for field in re.find_iter(&str) { - let matched_str = field.as_str(); - let ident_name = &matched_str[1..(matched_str.len() - 1)]; - - let scope_tree = self.scopes.current_scope_tree(); - let variable = scope_tree.find(ident_name); - if let Some((old_value, _)) = variable { - old_value.num_times_used += 1; - let ident = HirExpression::Ident(old_value.ident.clone(), None); - let expr_id = self.interner.push_expr(ident); - self.interner.push_expr_location(expr_id, call_expr_span, self.file); - fmt_str_idents.push(expr_id); - } else if ident_name.parse::().is_ok() { - self.errors.push(ResolverError::NumericConstantInFormatString { - name: ident_name.to_owned(), - span: call_expr_span, - }); - } else { - self.errors.push(ResolverError::VariableNotDeclared { - name: ident_name.to_owned(), - span: call_expr_span, - }); - } - } - HirLiteral::FmtStr(str, fmt_str_idents) - } - - fn check_break_continue(&mut self, is_break: bool, span: Span) { - if !self.in_unconstrained_fn { - self.push_err(ResolverError::JumpInConstrainedFn { is_break, span }); - } - if self.nested_loops == 0 { - self.push_err(ResolverError::JumpOutsideLoop { is_break, span }); - } - } -} - -/// Gives an error if a user tries to create a mutable reference -/// to an immutable variable. -pub fn verify_mutable_reference(interner: &NodeInterner, rhs: ExprId) -> Result<(), ResolverError> { - match interner.expression(&rhs) { - HirExpression::MemberAccess(member_access) => { - verify_mutable_reference(interner, member_access.lhs) - } - HirExpression::Index(_) => { - let span = interner.expr_span(&rhs); - Err(ResolverError::MutableReferenceToArrayElement { span }) - } - HirExpression::Ident(ident, _) => { - if let Some(definition) = interner.try_definition(ident.id) { - if !definition.mutable { - return Err(ResolverError::MutableReferenceToImmutableVariable { - span: interner.expr_span(&rhs), - variable: definition.name.clone(), - }); - } - } - Ok(()) - } - _ => Ok(()), - } -} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/structs.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/structs.rs deleted file mode 100644 index f62e5589d74..00000000000 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/structs.rs +++ /dev/null @@ -1,83 +0,0 @@ -use std::collections::BTreeMap; - -use fm::FileId; -use iter_extended::vecmap; - -use crate::ast::Ident; -use crate::{ - graph::CrateId, - hir::{ - def_collector::dc_crate::{CompilationError, UnresolvedStruct}, - def_map::ModuleId, - Context, - }, - node_interner::StructId, - Generics, Type, -}; - -use super::{errors::ResolverError, path_resolver::StandardPathResolver, resolver::Resolver}; - -/// Create the mappings from TypeId -> StructType -/// so that expressions can access the fields of structs -pub(crate) fn resolve_structs( - context: &mut Context, - structs: BTreeMap, - crate_id: CrateId, -) -> Vec<(CompilationError, FileId)> { - let mut errors: Vec<(CompilationError, FileId)> = vec![]; - // This is necessary to avoid cloning the entire struct map - // when adding checks after each struct field is resolved. - let struct_ids = structs.keys().copied().collect::>(); - - // Resolve each field in each struct. - // Each struct should already be present in the NodeInterner after def collection. - for (type_id, typ) in structs { - let file_id = typ.file_id; - let (generics, fields, resolver_errors) = - resolve_struct_fields(context, crate_id, type_id, typ); - errors.extend(vecmap(resolver_errors, |err| (err.into(), file_id))); - context.def_interner.update_struct(type_id, |struct_def| { - struct_def.set_fields(fields); - struct_def.generics = generics; - }); - } - - // Check whether the struct fields have nested slices - // We need to check after all structs are resolved to - // make sure every struct's fields is accurately set. - for id in struct_ids { - let struct_type = context.def_interner.get_struct(id); - // Only handle structs without generics as any generics args will be checked - // after monomorphization when performing SSA codegen - if struct_type.borrow().generics.is_empty() { - let fields = struct_type.borrow().get_fields(&[]); - for field in fields.iter() { - if field.1.is_nested_slice() { - errors.push(( - ResolverError::NestedSlices { span: struct_type.borrow().location.span } - .into(), - struct_type.borrow().location.file, - )); - } - } - } - } - - errors -} - -fn resolve_struct_fields( - context: &mut Context, - krate: CrateId, - type_id: StructId, - unresolved: UnresolvedStruct, -) -> (Generics, Vec<(Ident, Type)>, Vec) { - let path_resolver = - StandardPathResolver::new(ModuleId { local_id: unresolved.module_id, krate }); - let file_id = unresolved.file_id; - let (generics, fields, errors) = - Resolver::new(&mut context.def_interner, &path_resolver, &context.def_maps, file_id) - .resolve_struct_fields(unresolved.struct_def, type_id); - - (generics, fields, errors) -} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/traits.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/traits.rs deleted file mode 100644 index 6781c2833c4..00000000000 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/traits.rs +++ /dev/null @@ -1,505 +0,0 @@ -use std::collections::{BTreeMap, HashSet}; - -use fm::FileId; -use iter_extended::vecmap; -use noirc_errors::Location; - -use crate::ast::{Ident, ItemVisibility, Path, TraitItem, UnresolvedGeneric}; -use crate::{ - graph::CrateId, - hir::{ - def_collector::{ - dc_crate::{CompilationError, UnresolvedTrait, UnresolvedTraitImpl}, - errors::{DefCollectorErrorKind, DuplicateType}, - }, - def_map::{CrateDefMap, ModuleDefId, ModuleId}, - Context, - }, - hir_def::traits::{TraitConstant, TraitFunction, TraitImpl, TraitType}, - node_interner::{FuncId, NodeInterner, TraitId}, - GenericTypeVars, Shared, Type, TypeVariableKind, -}; - -use super::{ - functions, get_module_mut, get_struct_type, - import::{PathResolution, PathResolutionError}, - path_resolver::{PathResolver, StandardPathResolver}, - resolver::Resolver, - take_errors, -}; - -/// Create the mappings from TypeId -> TraitType -/// so that expressions can access the elements of traits -pub(crate) fn resolve_traits( - context: &mut Context, - traits: BTreeMap, - crate_id: CrateId, -) -> Vec<(CompilationError, FileId)> { - let mut all_errors = Vec::new(); - - for (trait_id, unresolved_trait) in traits { - let file_id = context.def_maps[&crate_id].file_id(unresolved_trait.module_id); - let generics = context.resolve_generics( - &unresolved_trait.trait_def.generics, - &mut all_errors, - file_id, - ); - let generic_type_vars = generics.iter().map(|generic| generic.type_var.clone()).collect(); - - context.def_interner.push_empty_trait(trait_id, &unresolved_trait, generics); - - // Resolve order - // 1. Trait Types ( Trait constants can have a trait type, therefore types before constants) - let _ = resolve_trait_types(context, crate_id, &unresolved_trait); - // 2. Trait Constants ( Trait's methods can use trait types & constants, therefore they should be after) - let _ = resolve_trait_constants(context, crate_id, &unresolved_trait); - // 3. Trait Methods - let (methods, errors) = resolve_trait_methods( - context, - trait_id, - crate_id, - &unresolved_trait, - &generic_type_vars, - ); - - all_errors.extend(errors); - - context.def_interner.update_trait(trait_id, |trait_def| { - trait_def.set_methods(methods); - }); - - // This check needs to be after the trait's methods are set since - // the interner may set `interner.ordering_type` based on the result type - // of the Cmp trait, if this is it. - if crate_id.is_stdlib() { - context.def_interner.try_add_operator_trait(trait_id); - } - } - all_errors -} - -fn resolve_trait_types( - _context: &mut Context, - _crate_id: CrateId, - _unresolved_trait: &UnresolvedTrait, -) -> (Vec, Vec<(CompilationError, FileId)>) { - // TODO - (vec![], vec![]) -} -fn resolve_trait_constants( - _context: &mut Context, - _crate_id: CrateId, - _unresolved_trait: &UnresolvedTrait, -) -> (Vec, Vec<(CompilationError, FileId)>) { - // TODO - (vec![], vec![]) -} - -fn resolve_trait_methods( - context: &mut Context, - trait_id: TraitId, - crate_id: CrateId, - unresolved_trait: &UnresolvedTrait, - trait_generics: &GenericTypeVars, -) -> (Vec, Vec<(CompilationError, FileId)>) { - let interner = &mut context.def_interner; - let def_maps = &mut context.def_maps; - - let path_resolver = StandardPathResolver::new(ModuleId { - local_id: unresolved_trait.module_id, - krate: crate_id, - }); - let file = def_maps[&crate_id].file_id(unresolved_trait.module_id); - - let mut functions = vec![]; - let mut resolver_errors = vec![]; - - for item in &unresolved_trait.trait_def.items { - if let TraitItem::Function { - name, - generics, - parameters, - return_type, - where_clause, - body: _, - } = item - { - let the_trait = interner.get_trait(trait_id); - let self_typevar = the_trait.self_type_typevar.clone(); - let self_type = Type::TypeVariable(self_typevar.clone(), TypeVariableKind::Normal); - let name_span = the_trait.name.span(); - - let mut resolver = Resolver::new(interner, &path_resolver, def_maps, file); - resolver.add_generics(generics); - - resolver.add_existing_generics(&unresolved_trait.trait_def.generics, trait_generics); - resolver.add_existing_generic( - &UnresolvedGeneric::Variable(Ident::from("Self")), - name_span, - self_typevar, - ); - resolver.set_self_type(Some(self_type.clone())); - - let func_id = unresolved_trait.method_ids[&name.0.contents]; - let (_, func_meta) = resolver.resolve_trait_function( - name, - generics, - parameters, - return_type, - where_clause, - func_id, - ); - resolver.interner.push_fn_meta(func_meta, func_id); - - let arguments = vecmap(parameters, |param| resolver.resolve_type(param.1.clone())); - let return_type = resolver.resolve_type(return_type.get_type().into_owned()); - - let generics = vecmap(resolver.get_generics(), |generic| generic.type_var.clone()); - - let default_impl_list: Vec<_> = unresolved_trait - .fns_with_default_impl - .functions - .iter() - .filter(|(_, _, q)| q.name() == name.0.contents) - .collect(); - - let default_impl = if default_impl_list.len() == 1 { - Some(Box::new(default_impl_list[0].2.clone())) - } else { - None - }; - - let no_environment = Box::new(Type::Unit); - let function_type = Type::Function(arguments, Box::new(return_type), no_environment); - - functions.push(TraitFunction { - name: name.clone(), - typ: Type::Forall(generics, Box::new(function_type)), - location: Location::new(name.span(), unresolved_trait.file_id), - default_impl, - default_impl_module_id: unresolved_trait.module_id, - }); - - let errors = resolver.take_errors().into_iter(); - resolver_errors.extend(errors.map(|resolution_error| (resolution_error.into(), file))); - } - } - (functions, resolver_errors) -} - -fn collect_trait_impl_methods( - interner: &mut NodeInterner, - def_maps: &BTreeMap, - crate_id: CrateId, - trait_id: TraitId, - trait_impl: &mut UnresolvedTraitImpl, -) -> Vec<(CompilationError, FileId)> { - // In this Vec methods[i] corresponds to trait.methods[i]. If the impl has no implementation - // for a particular method, the default implementation will be added at that slot. - let mut ordered_methods = Vec::new(); - - // check whether the trait implementation is in the same crate as either the trait or the type - let mut errors = - check_trait_impl_crate_coherence(interner, trait_id, trait_impl, crate_id, def_maps); - // set of function ids that have a corresponding method in the trait - let mut func_ids_in_trait = HashSet::new(); - - // Temporarily take ownership of the trait's methods so we can iterate over them - // while also mutating the interner - let the_trait = interner.get_trait_mut(trait_id); - let methods = std::mem::take(&mut the_trait.methods); - - for method in &methods { - let overrides: Vec<_> = trait_impl - .methods - .functions - .iter() - .filter(|(_, _, f)| f.name() == method.name.0.contents) - .collect(); - - if overrides.is_empty() { - if let Some(default_impl) = &method.default_impl { - // copy 'where' clause from unresolved trait impl - let mut default_impl_clone = default_impl.clone(); - default_impl_clone.def.where_clause.extend(trait_impl.where_clause.clone()); - - let func_id = interner.push_empty_fn(); - let module = ModuleId { local_id: trait_impl.module_id, krate: crate_id }; - let location = Location::new(default_impl.def.span, trait_impl.file_id); - interner.push_function(func_id, &default_impl.def, module, location); - func_ids_in_trait.insert(func_id); - ordered_methods.push((method.default_impl_module_id, func_id, *default_impl_clone)); - } else { - let error = DefCollectorErrorKind::TraitMissingMethod { - trait_name: interner.get_trait(trait_id).name.clone(), - method_name: method.name.clone(), - trait_impl_span: trait_impl.object_type.span.expect("type must have a span"), - }; - errors.push((error.into(), trait_impl.file_id)); - } - } else { - for (_, func_id, _) in &overrides { - func_ids_in_trait.insert(*func_id); - } - - if overrides.len() > 1 { - let error = DefCollectorErrorKind::Duplicate { - typ: DuplicateType::TraitAssociatedFunction, - first_def: overrides[0].2.name_ident().clone(), - second_def: overrides[1].2.name_ident().clone(), - }; - errors.push((error.into(), trait_impl.file_id)); - } - - ordered_methods.push(overrides[0].clone()); - } - } - - // Restore the methods that were taken before the for loop - let the_trait = interner.get_trait_mut(trait_id); - the_trait.set_methods(methods); - - // Emit MethodNotInTrait error for methods in the impl block that - // don't have a corresponding method signature defined in the trait - for (_, func_id, func) in &trait_impl.methods.functions { - if !func_ids_in_trait.contains(func_id) { - let error = DefCollectorErrorKind::MethodNotInTrait { - trait_name: the_trait.name.clone(), - impl_method: func.name_ident().clone(), - }; - errors.push((error.into(), trait_impl.file_id)); - } - } - - trait_impl.methods.functions = ordered_methods; - trait_impl.methods.trait_id = Some(trait_id); - errors -} - -fn collect_trait_impl( - context: &mut Context, - crate_id: CrateId, - trait_impl: &mut UnresolvedTraitImpl, -) -> Vec<(CompilationError, FileId)> { - let interner = &mut context.def_interner; - let def_maps = &mut context.def_maps; - let mut errors: Vec<(CompilationError, FileId)> = vec![]; - let unresolved_type = trait_impl.object_type.clone(); - let module = ModuleId { local_id: trait_impl.module_id, krate: crate_id }; - trait_impl.trait_id = - match resolve_trait_by_path(def_maps, module, trait_impl.trait_path.clone()) { - Ok((trait_id, warning)) => { - if let Some(warning) = warning { - errors.push(( - DefCollectorErrorKind::PathResolutionError(warning).into(), - trait_impl.file_id, - )); - } - Some(trait_id) - } - Err(error) => { - errors.push((error.into(), trait_impl.file_id)); - None - } - }; - - if let Some(trait_id) = trait_impl.trait_id { - errors - .extend(collect_trait_impl_methods(interner, def_maps, crate_id, trait_id, trait_impl)); - - let path_resolver = StandardPathResolver::new(module); - let file = def_maps[&crate_id].file_id(trait_impl.module_id); - let mut resolver = Resolver::new(interner, &path_resolver, def_maps, file); - resolver.add_generics(&trait_impl.generics); - - let typ = resolver.resolve_type(unresolved_type); - errors.extend(take_errors(trait_impl.file_id, resolver)); - - if let Some(struct_type) = get_struct_type(&typ) { - let struct_type = struct_type.borrow(); - let module = get_module_mut(def_maps, struct_type.id.module_id()); - - for (_, method_id, method) in &trait_impl.methods.functions { - // If this method was already declared, remove it from the module so it cannot - // be accessed with the `TypeName::method` syntax. We'll check later whether the - // object types in each method overlap or not. If they do, we issue an error. - // If not, that is specialization which is allowed. - if module - .declare_function( - method.name_ident().clone(), - ItemVisibility::Public, - *method_id, - ) - .is_err() - { - module.remove_function(method.name_ident()); - } - } - } - } - errors -} - -pub(crate) fn collect_trait_impls( - context: &mut Context, - crate_id: CrateId, - collected_impls: &mut [UnresolvedTraitImpl], -) -> Vec<(CompilationError, FileId)> { - collected_impls - .iter_mut() - .flat_map(|trait_impl| collect_trait_impl(context, crate_id, trait_impl)) - .collect() -} - -fn check_trait_impl_crate_coherence( - interner: &mut NodeInterner, - trait_id: TraitId, - trait_impl: &UnresolvedTraitImpl, - current_crate: CrateId, - def_maps: &BTreeMap, -) -> Vec<(CompilationError, FileId)> { - let mut errors: Vec<(CompilationError, FileId)> = vec![]; - - let module = ModuleId { krate: current_crate, local_id: trait_impl.module_id }; - let file = def_maps[¤t_crate].file_id(trait_impl.module_id); - let path_resolver = StandardPathResolver::new(module); - let mut resolver = Resolver::new(interner, &path_resolver, def_maps, file); - - let object_crate = match resolver.resolve_type(trait_impl.object_type.clone()) { - Type::Struct(struct_type, _) => struct_type.borrow().id.krate(), - _ => CrateId::Dummy, - }; - - let the_trait = interner.get_trait(trait_id); - if current_crate != the_trait.crate_id && current_crate != object_crate { - let error = DefCollectorErrorKind::TraitImplOrphaned { - span: trait_impl.object_type.span.expect("object type must have a span"), - }; - errors.push((error.into(), trait_impl.file_id)); - } - - errors -} - -pub(crate) fn resolve_trait_by_path( - def_maps: &BTreeMap, - module: ModuleId, - path: Path, -) -> Result<(TraitId, Option), DefCollectorErrorKind> { - let path_resolver = StandardPathResolver::new(module); - - match path_resolver.resolve(def_maps, path.clone(), &mut None) { - Ok(PathResolution { module_def_id: ModuleDefId::TraitId(trait_id), error }) => { - Ok((trait_id, error)) - } - Ok(_) => Err(DefCollectorErrorKind::NotATrait { not_a_trait_name: path }), - Err(_) => Err(DefCollectorErrorKind::TraitNotFound { trait_path: path }), - } -} - -pub(crate) fn resolve_trait_impls( - context: &mut Context, - traits: Vec, - crate_id: CrateId, - errors: &mut Vec<(CompilationError, FileId)>, -) -> Vec<(FileId, FuncId)> { - let interner = &mut context.def_interner; - let mut methods = Vec::<(FileId, FuncId)>::new(); - - for trait_impl in traits { - let unresolved_type = trait_impl.object_type; - let local_mod_id = trait_impl.module_id; - let module_id = ModuleId { krate: crate_id, local_id: local_mod_id }; - let path_resolver = StandardPathResolver::new(module_id); - - let self_type_span = unresolved_type.span; - - let mut resolver = - Resolver::new(interner, &path_resolver, &context.def_maps, trait_impl.file_id); - resolver.add_generics(&trait_impl.generics); - - let trait_generics = - vecmap(&trait_impl.trait_generics, |generic| resolver.resolve_type(generic.clone())); - - let self_type = resolver.resolve_type(unresolved_type.clone()); - let impl_generics = resolver.get_generics().to_vec(); - let impl_id = interner.next_trait_impl_id(); - - let mut impl_methods = functions::resolve_function_set( - interner, - crate_id, - &context.def_maps, - trait_impl.methods.clone(), - Some(self_type.clone()), - Some(impl_id), - impl_generics.clone(), - errors, - ); - - let maybe_trait_id = trait_impl.trait_id; - if let Some(trait_id) = maybe_trait_id { - for (_, func) in &impl_methods { - interner.set_function_trait(*func, self_type.clone(), trait_id); - } - } - - if matches!(self_type, Type::MutableReference(_)) { - let span = self_type_span.unwrap_or_else(|| trait_impl.trait_path.span()); - let error = DefCollectorErrorKind::MutableReferenceInTraitImpl { span }; - errors.push((error.into(), trait_impl.file_id)); - } - - let mut new_resolver = - Resolver::new(interner, &path_resolver, &context.def_maps, trait_impl.file_id); - - new_resolver.set_generics(impl_generics.clone()); - new_resolver.set_self_type(Some(self_type.clone())); - - if let Some(trait_id) = maybe_trait_id { - let where_clause = trait_impl - .where_clause - .into_iter() - .flat_map(|item| new_resolver.resolve_trait_constraint(item)) - .collect(); - - let resolver_errors = new_resolver.take_errors().into_iter(); - errors.extend(resolver_errors.map(|error| (error.into(), trait_impl.file_id))); - - let resolved_trait_impl = Shared::new(TraitImpl { - ident: trait_impl.trait_path.last_segment().clone(), - typ: self_type.clone(), - trait_id, - trait_generics: trait_generics.clone(), - file: trait_impl.file_id, - where_clause, - methods: vecmap(&impl_methods, |(_, func_id)| *func_id), - }); - - let impl_generics = vecmap(impl_generics, |generic| generic.type_var); - - if let Err((prev_span, prev_file)) = interner.add_trait_implementation( - self_type.clone(), - trait_id, - trait_generics, - impl_id, - impl_generics, - resolved_trait_impl, - ) { - let error = DefCollectorErrorKind::OverlappingImpl { - typ: self_type.clone(), - span: self_type_span.unwrap_or_else(|| trait_impl.trait_path.span()), - }; - errors.push((error.into(), trait_impl.file_id)); - - // The 'previous impl defined here' note must be a separate error currently - // since it may be in a different file and all errors have the same file id. - let error = DefCollectorErrorKind::OverlappingImplNote { span: prev_span }; - errors.push((error.into(), prev_file)); - } - - methods.append(&mut impl_methods); - } - } - - methods -} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/type_aliases.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/type_aliases.rs deleted file mode 100644 index 2e5ce611a7f..00000000000 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/type_aliases.rs +++ /dev/null @@ -1,33 +0,0 @@ -use super::{path_resolver::StandardPathResolver, resolver::Resolver}; -use crate::{ - graph::CrateId, - hir::{ - def_collector::dc_crate::{CompilationError, UnresolvedTypeAlias}, - def_map::ModuleId, - Context, - }, - node_interner::TypeAliasId, -}; -use fm::FileId; -use std::collections::BTreeMap; - -pub(crate) fn resolve_type_aliases( - context: &mut Context, - type_aliases: BTreeMap, - crate_id: CrateId, -) -> Vec<(CompilationError, FileId)> { - let mut errors: Vec<(CompilationError, FileId)> = vec![]; - for (alias_id, unresolved_typ) in type_aliases { - let path_resolver = StandardPathResolver::new(ModuleId { - local_id: unresolved_typ.module_id, - krate: crate_id, - }); - let file = unresolved_typ.file_id; - let (typ, generics, resolver_errors) = - Resolver::new(&mut context.def_interner, &path_resolver, &context.def_maps, file) - .resolve_type_alias(unresolved_typ.type_alias_def, alias_id); - errors.extend(resolver_errors.iter().cloned().map(|e| (e.into(), file))); - context.def_interner.set_type_alias(alias_id, typ, generics); - } - errors -} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/errors.rs index e5c52213056..af168a10df9 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/errors.rs @@ -83,6 +83,8 @@ pub enum TypeCheckError { IntegerAndFieldBinaryOperation { span: Span }, #[error("Cannot do modulo on Fields, try casting to an integer first")] FieldModulo { span: Span }, + #[error("Cannot do not (`!`) on Fields, try casting to an integer first")] + FieldNot { span: Span }, #[error("Fields cannot be compared, try casting to an integer first")] FieldComparison { span: Span }, #[error("The bit count in a bit-shift operation must fit in a u8, try casting the right hand side into a u8 first")] @@ -256,6 +258,7 @@ impl<'a> From<&'a TypeCheckError> for Diagnostic { | TypeCheckError::IntegerAndFieldBinaryOperation { span } | TypeCheckError::OverflowingAssignment { span, .. } | TypeCheckError::FieldModulo { span } + | TypeCheckError::FieldNot { span } | TypeCheckError::ConstrainedReferenceToUnconstrained { span } | TypeCheckError::UnconstrainedReferenceToConstrained { span } | TypeCheckError::UnconstrainedSliceReturnToConstrained { span } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/expr.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/expr.rs deleted file mode 100644 index 1f3e9103cde..00000000000 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/expr.rs +++ /dev/null @@ -1,1391 +0,0 @@ -use iter_extended::vecmap; -use noirc_errors::Span; - -use crate::ast::{BinaryOpKind, IntegerBitSize, UnaryOp}; -use crate::hir_def::expr::HirCallExpression; -use crate::macros_api::Signedness; -use crate::{ - hir::{resolution::resolver::verify_mutable_reference, type_check::errors::Source}, - hir_def::{ - expr::{ - self, HirArrayLiteral, HirBinaryOp, HirBlockExpression, HirExpression, HirIdent, - HirLiteral, HirMethodCallExpression, HirMethodReference, HirPrefixExpression, ImplKind, - }, - types::Type, - }, - node_interner::{DefinitionKind, ExprId, FuncId, TraitId, TraitImplKind, TraitMethodId}, - TypeBinding, TypeBindings, TypeVariableKind, -}; - -use super::NoMatchingImplFoundError; -use super::{errors::TypeCheckError, TypeChecker}; - -impl<'interner> TypeChecker<'interner> { - fn check_if_deprecated(&mut self, expr: &ExprId) { - if let HirExpression::Ident(expr::HirIdent { location, id, impl_kind: _ }, _) = - self.interner.expression(expr) - { - if let Some(DefinitionKind::Function(func_id)) = - self.interner.try_definition(id).map(|def| &def.kind) - { - let attributes = self.interner.function_attributes(func_id); - if let Some(note) = attributes.get_deprecated_note() { - self.errors.push(TypeCheckError::CallDeprecated { - name: self.interner.definition_name(id).to_string(), - note, - span: location.span, - }); - } - } - } - } - - fn is_unconstrained_call(&self, expr: &ExprId) -> bool { - if let HirExpression::Ident(expr::HirIdent { id, .. }, _) = self.interner.expression(expr) { - if let Some(DefinitionKind::Function(func_id)) = - self.interner.try_definition(id).map(|def| &def.kind) - { - let modifiers = self.interner.function_modifiers(func_id); - return modifiers.is_unconstrained; - } - } - false - } - - fn check_hir_array_literal( - &mut self, - hir_array_literal: HirArrayLiteral, - ) -> (Result>, Box) { - match hir_array_literal { - HirArrayLiteral::Standard(arr) => { - let elem_types = vecmap(&arr, |arg| self.check_expression(arg)); - - let first_elem_type = elem_types - .first() - .cloned() - .unwrap_or_else(|| self.interner.next_type_variable()); - - // Check if the array is homogeneous - for (index, elem_type) in elem_types.iter().enumerate().skip(1) { - let location = self.interner.expr_location(&arr[index]); - - elem_type.unify(&first_elem_type, &mut self.errors, || { - TypeCheckError::NonHomogeneousArray { - first_span: self.interner.expr_location(&arr[0]).span, - first_type: first_elem_type.to_string(), - first_index: index, - second_span: location.span, - second_type: elem_type.to_string(), - second_index: index + 1, - } - .add_context("elements in an array must have the same type") - }); - } - - (Ok(arr.len() as u32), Box::new(first_elem_type.clone())) - } - HirArrayLiteral::Repeated { repeated_element, length } => { - let elem_type = self.check_expression(&repeated_element); - let length = match length { - Type::Constant(length) => Ok(length), - other => Err(Box::new(other)), - }; - (length, Box::new(elem_type)) - } - } - } - - /// Infers a type for a given expression, and return this type. - /// As a side-effect, this function will also remember this type in the NodeInterner - /// for the given expr_id key. - /// - /// This function also converts any HirExpression::MethodCalls `a.foo(b, c)` into - /// an equivalent HirExpression::Call in the form `foo(a, b, c)`. This cannot - /// be done earlier since we need to know the type of the object `a` to resolve which - /// function `foo` to refer to. - pub(crate) fn check_expression(&mut self, expr_id: &ExprId) -> Type { - let typ = match self.interner.expression(expr_id) { - HirExpression::Ident(ident, generics) => self.check_ident(ident, expr_id, generics), - HirExpression::Literal(literal) => match literal { - HirLiteral::Array(hir_array_literal) => { - let (length, elem_type) = self.check_hir_array_literal(hir_array_literal); - Type::Array( - length.map_or_else( - |typ| typ, - |constant| Box::new(Type::constant_variable(constant, self.interner)), - ), - elem_type, - ) - } - HirLiteral::Slice(hir_array_literal) => { - let (length_type, elem_type) = self.check_hir_array_literal(hir_array_literal); - match length_type { - Ok(_length) => Type::Slice(elem_type), - Err(_non_constant) => { - self.errors.push(TypeCheckError::NonConstantSliceLength { - span: self.interner.expr_span(expr_id), - }); - Type::Error - } - } - } - HirLiteral::Bool(_) => Type::Bool, - HirLiteral::Integer(_, _) => self.polymorphic_integer_or_field(), - HirLiteral::Str(string) => { - let len = Type::Constant(string.len() as u32); - Type::String(Box::new(len)) - } - HirLiteral::FmtStr(string, idents) => { - let len = Type::Constant(string.len() as u32); - let types = vecmap(&idents, |elem| self.check_expression(elem)); - Type::FmtString(Box::new(len), Box::new(Type::Tuple(types))) - } - HirLiteral::Unit => Type::Unit, - }, - HirExpression::Infix(infix_expr) => { - // The type of the infix expression must be looked up from a type table - let lhs_type = self.check_expression(&infix_expr.lhs); - let rhs_type = self.check_expression(&infix_expr.rhs); - - let lhs_span = self.interner.expr_span(&infix_expr.lhs); - let rhs_span = self.interner.expr_span(&infix_expr.rhs); - let span = lhs_span.merge(rhs_span); - - let operator = &infix_expr.operator; - match self.infix_operand_type_rules(&lhs_type, operator, &rhs_type, span) { - Ok((typ, use_impl)) => { - if use_impl { - let id = infix_expr.trait_method_id; - - // Delay checking the trait constraint until the end of the function. - // Checking it now could bind an unbound type variable to any type - // that implements the trait. - let constraint = crate::hir_def::traits::TraitConstraint { - typ: lhs_type.clone(), - trait_id: id.trait_id, - trait_generics: Vec::new(), - }; - self.trait_constraints.push((constraint, *expr_id)); - self.typecheck_operator_method(*expr_id, id, &lhs_type, span); - } - typ - } - Err(error) => { - self.errors.push(error); - Type::Error - } - } - } - HirExpression::Index(index_expr) => self.check_index_expression(expr_id, index_expr), - HirExpression::Call(call_expr) => { - let function = self.check_expression(&call_expr.func); - - let args = vecmap(&call_expr.arguments, |arg| { - let typ = self.check_expression(arg); - (typ, *arg, self.interner.expr_span(arg)) - }); - - let span = self.interner.expr_span(expr_id); - self.check_call(&call_expr, function, args, span) - } - HirExpression::MethodCall(mut method_call) => { - let method_call_span = self.interner.expr_span(expr_id); - let object = method_call.object; - let object_span = self.interner.expr_span(&method_call.object); - let mut object_type = self.check_expression(&method_call.object).follow_bindings(); - let method_name = method_call.method.0.contents.as_str(); - match self.lookup_method(&object_type, method_name, expr_id) { - Some(method_ref) => { - // Desugar the method call into a normal, resolved function call - // so that the backend doesn't need to worry about methods - let location = method_call.location; - - // Automatically add `&mut` if the method expects a mutable reference and - // the object is not already one. - let func_id = match &method_ref { - HirMethodReference::FuncId(func_id) => *func_id, - HirMethodReference::TraitMethodId(method_id, _) => { - let id = self.interner.trait_method_id(*method_id); - let definition = self.interner.definition(id); - let DefinitionKind::Function(func_id) = definition.kind else { - unreachable!( - "Expected trait function to be a DefinitionKind::Function" - ) - }; - func_id - } - }; - - if func_id != FuncId::dummy_id() { - let function_type = self.interner.function_meta(&func_id).typ.clone(); - self.try_add_mutable_reference_to_object( - &mut method_call, - &function_type, - &mut object_type, - ); - } - - // These arguments will be given to the desugared function call. - // Compared to the method arguments, they also contain the object. - let mut function_args = Vec::with_capacity(method_call.arguments.len() + 1); - - function_args.push((object_type.clone(), object, object_span)); - - for arg in method_call.arguments.iter() { - let span = self.interner.expr_span(arg); - let typ = self.check_expression(arg); - function_args.push((typ, *arg, span)); - } - - // TODO: update object_type here? - let ((function_id, _), function_call) = method_call.into_function_call( - &method_ref, - object_type, - location, - self.interner, - ); - - let func_type = self.check_expression(&function_id); - - // Type check the new call now that it has been changed from a method call - // to a function call. This way we avoid duplicating code. - // We call `check_call` rather than `check_expression` directly as we want to avoid - // resolving the object type again once it is part of the arguments. - let typ = self.check_call( - &function_call, - func_type, - function_args, - method_call_span, - ); - - self.interner.replace_expr(expr_id, HirExpression::Call(function_call)); - - typ - } - None => Type::Error, - } - } - HirExpression::Cast(cast_expr) => { - // Evaluate the LHS - let lhs_type = self.check_expression(&cast_expr.lhs); - let span = self.interner.expr_span(expr_id); - self.check_cast(lhs_type, cast_expr.r#type, span) - } - HirExpression::Block(block_expr) => self.check_block(block_expr), - HirExpression::Prefix(prefix_expr) => { - let rhs_type = self.check_expression(&prefix_expr.rhs); - let span = self.interner.expr_span(&prefix_expr.rhs); - self.type_check_prefix_operand(&prefix_expr.operator, &rhs_type, span) - } - HirExpression::If(if_expr) => self.check_if_expr(&if_expr, expr_id), - HirExpression::Constructor(constructor) => self.check_constructor(constructor, expr_id), - HirExpression::MemberAccess(access) => self.check_member_access(access, *expr_id), - HirExpression::Error => Type::Error, - HirExpression::Tuple(elements) => { - Type::Tuple(vecmap(&elements, |elem| self.check_expression(elem))) - } - HirExpression::Lambda(lambda) => { - let captured_vars = vecmap(lambda.captures, |capture| { - self.interner.definition_type(capture.ident.id) - }); - - let env_type: Type = - if captured_vars.is_empty() { Type::Unit } else { Type::Tuple(captured_vars) }; - - let params = vecmap(lambda.parameters, |(pattern, typ)| { - self.bind_pattern(&pattern, typ.clone()); - typ - }); - - let actual_return = self.check_expression(&lambda.body); - - let span = self.interner.expr_span(&lambda.body); - self.unify(&actual_return, &lambda.return_type, || TypeCheckError::TypeMismatch { - expected_typ: lambda.return_type.to_string(), - expr_typ: actual_return.to_string(), - expr_span: span, - }); - - Type::Function(params, Box::new(lambda.return_type), Box::new(env_type)) - } - HirExpression::Quote(_) => Type::Quoted(crate::QuotedType::Quoted), - HirExpression::Comptime(block) => self.check_block(block), - - // Unquote should be inserted & removed by the comptime interpreter. - // Even if we allowed it here, we wouldn't know what type to give to the result. - HirExpression::Unquote(block) => { - unreachable!("Unquote remaining during type checking {block:?}") - } - }; - - self.interner.push_expr_type(*expr_id, typ.clone()); - typ - } - - fn check_call( - &mut self, - call: &HirCallExpression, - func_type: Type, - args: Vec<(Type, ExprId, Span)>, - span: Span, - ) -> Type { - // Need to setup these flags here as `self` is borrowed mutably to type check the rest of the call expression - // These flags are later used to type check calls to unconstrained functions from constrained functions - let func_mod = self.current_function.map(|func| self.interner.function_modifiers(&func)); - let is_current_func_constrained = - func_mod.map_or(true, |func_mod| !func_mod.is_unconstrained); - - let is_unconstrained_call = self.is_unconstrained_call(&call.func); - self.check_if_deprecated(&call.func); - - // Check that we are not passing a mutable reference from a constrained runtime to an unconstrained runtime - if is_current_func_constrained && is_unconstrained_call { - for (typ, _, _) in args.iter() { - if !typ.is_valid_for_unconstrained_boundary() { - self.errors.push(TypeCheckError::ConstrainedReferenceToUnconstrained { span }); - } - } - } - - let return_type = self.bind_function_type(func_type, args, span); - - // Check that we are not passing a slice from an unconstrained runtime to a constrained runtime - if is_current_func_constrained && is_unconstrained_call { - if return_type.contains_slice() { - self.errors.push(TypeCheckError::UnconstrainedSliceReturnToConstrained { span }); - } else if matches!(&return_type.follow_bindings(), Type::MutableReference(_)) { - self.errors.push(TypeCheckError::UnconstrainedReferenceToConstrained { span }); - } - }; - - return_type - } - - fn check_block(&mut self, block: HirBlockExpression) -> Type { - let mut block_type = Type::Unit; - - let statements = block.statements(); - for (i, stmt) in statements.iter().enumerate() { - let expr_type = self.check_statement(stmt); - - if let crate::hir_def::stmt::HirStatement::Semi(expr) = self.interner.statement(stmt) { - let inner_expr_type = self.interner.id_type(expr); - let span = self.interner.expr_span(&expr); - - self.unify(&inner_expr_type, &Type::Unit, || TypeCheckError::UnusedResultError { - expr_type: inner_expr_type.clone(), - expr_span: span, - }); - } - - if i + 1 == statements.len() { - block_type = expr_type; - } - } - - block_type - } - - /// Returns the type of the given identifier - fn check_ident( - &mut self, - ident: HirIdent, - expr_id: &ExprId, - generics: Option>, - ) -> Type { - let mut bindings = TypeBindings::new(); - - // Add type bindings from any constraints that were used. - // We need to do this first since otherwise instantiating the type below - // will replace each trait generic with a fresh type variable, rather than - // the type used in the trait constraint (if it exists). See #4088. - if let ImplKind::TraitMethod(_, constraint, assumed) = &ident.impl_kind { - let the_trait = self.interner.get_trait(constraint.trait_id); - assert_eq!(the_trait.generics.len(), constraint.trait_generics.len()); - - for (param, arg) in the_trait.generics.iter().zip(&constraint.trait_generics) { - // Avoid binding t = t - if !arg.occurs(param.type_var.id()) { - bindings.insert(param.type_var.id(), (param.type_var.clone(), arg.clone())); - } - } - - // If the trait impl is already assumed to exist we should add any type bindings for `Self`. - // Otherwise `self` will be replaced with a fresh type variable, which will require the user - // to specify a redundant type annotation. - if *assumed { - bindings.insert( - the_trait.self_type_typevar_id, - (the_trait.self_type_typevar.clone(), constraint.typ.clone()), - ); - } - } - - // An identifiers type may be forall-quantified in the case of generic functions. - // E.g. `fn foo(t: T, field: Field) -> T` has type `forall T. fn(T, Field) -> T`. - // We must instantiate identifiers at every call site to replace this T with a new type - // variable to handle generic functions. - let t = self.interner.id_type_substitute_trait_as_type(ident.id); - - let definition = self.interner.try_definition(ident.id); - let function_generic_count = definition.map_or(0, |definition| match &definition.kind { - DefinitionKind::Function(function) => { - self.interner.function_modifiers(function).generic_count - } - _ => 0, - }); - - let span = self.interner.expr_span(expr_id); - // This instantiates a trait's generics as well which need to be set - // when the constraint below is later solved for when the function is - // finished. How to link the two? - let (typ, bindings) = self.instantiate(t, bindings, generics, function_generic_count, span); - - // Push any trait constraints required by this definition to the context - // to be checked later when the type of this variable is further constrained. - if let Some(definition) = self.interner.try_definition(ident.id) { - if let DefinitionKind::Function(func_id) = definition.kind { - let function = self.interner.function_meta(&func_id); - for mut constraint in function.trait_constraints.clone() { - constraint.apply_bindings(&bindings); - self.trait_constraints.push((constraint, *expr_id)); - } - } - } - - if let ImplKind::TraitMethod(_, mut constraint, assumed) = ident.impl_kind { - constraint.apply_bindings(&bindings); - if assumed { - let trait_impl = TraitImplKind::Assumed { - object_type: constraint.typ, - trait_generics: constraint.trait_generics, - }; - self.interner.select_impl_for_expression(*expr_id, trait_impl); - } else { - // Currently only one impl can be selected per expr_id, so this - // constraint needs to be pushed after any other constraints so - // that monomorphization can resolve this trait method to the correct impl. - self.trait_constraints.push((constraint, *expr_id)); - } - } - - self.interner.store_instantiation_bindings(*expr_id, bindings); - typ - } - - fn instantiate( - &mut self, - typ: Type, - bindings: TypeBindings, - turbofish_generics: Option>, - function_generic_count: usize, - span: Span, - ) -> (Type, TypeBindings) { - match turbofish_generics { - Some(turbofish_generics) => { - if turbofish_generics.len() != function_generic_count { - self.errors.push(TypeCheckError::IncorrectTurbofishGenericCount { - expected_count: function_generic_count, - actual_count: turbofish_generics.len(), - span, - }); - typ.instantiate_with_bindings(bindings, self.interner) - } else { - // Fetch the count of any implicit generics on the function, such as - // for a method within a generic impl. - let implicit_generic_count = match &typ { - Type::Forall(generics, _) => generics.len() - function_generic_count, - _ => 0, - }; - typ.instantiate_with(turbofish_generics, self.interner, implicit_generic_count) - } - } - None => typ.instantiate_with_bindings(bindings, self.interner), - } - } - - pub fn verify_trait_constraint( - &mut self, - object_type: &Type, - trait_id: TraitId, - trait_generics: &[Type], - function_ident_id: ExprId, - span: Span, - ) { - match self.interner.lookup_trait_implementation(object_type, trait_id, trait_generics) { - Ok(impl_kind) => { - self.interner.select_impl_for_expression(function_ident_id, impl_kind); - } - Err(erroring_constraints) => { - if erroring_constraints.is_empty() { - self.errors.push(TypeCheckError::TypeAnnotationsNeeded { span }); - } else if let Some(error) = - NoMatchingImplFoundError::new(self.interner, erroring_constraints, span) - { - self.errors.push(TypeCheckError::NoMatchingImplFound(error)); - } - } - } - } - - /// Check if the given method type requires a mutable reference to the object type, and check - /// if the given object type is already a mutable reference. If not, add one. - /// This is used to automatically transform a method call: `foo.bar()` into a function - /// call: `bar(&mut foo)`. - /// - /// A notable corner case of this function is where it interacts with auto-deref of `.`. - /// If a field is being mutated e.g. `foo.bar.mutate_bar()` where `foo: &mut Foo`, the compiler - /// will insert a dereference before bar `(*foo).bar.mutate_bar()` which would cause us to - /// mutate a copy of bar rather than a reference to it. We must check for this corner case here - /// and remove the implicitly added dereference operator if we find one. - fn try_add_mutable_reference_to_object( - &mut self, - method_call: &mut HirMethodCallExpression, - function_type: &Type, - object_type: &mut Type, - ) { - let expected_object_type = match function_type { - Type::Function(args, _, _) => args.first(), - Type::Forall(_, typ) => match typ.as_ref() { - Type::Function(args, _, _) => args.first(), - typ => unreachable!("Unexpected type for function: {typ}"), - }, - typ => unreachable!("Unexpected type for function: {typ}"), - }; - - if let Some(expected_object_type) = expected_object_type { - let actual_type = object_type.follow_bindings(); - - if matches!(expected_object_type.follow_bindings(), Type::MutableReference(_)) { - if !matches!(actual_type, Type::MutableReference(_)) { - if let Err(error) = verify_mutable_reference(self.interner, method_call.object) - { - self.errors.push(TypeCheckError::ResolverError(error)); - } - - let new_type = Type::MutableReference(Box::new(actual_type)); - *object_type = new_type.clone(); - - // First try to remove a dereference operator that may have been implicitly - // inserted by a field access expression `foo.bar` on a mutable reference `foo`. - let new_object = self.try_remove_implicit_dereference(method_call.object); - - // If that didn't work, then wrap the whole expression in an `&mut` - method_call.object = new_object.unwrap_or_else(|| { - let location = self.interner.id_location(method_call.object); - - let new_object = - self.interner.push_expr(HirExpression::Prefix(HirPrefixExpression { - operator: UnaryOp::MutableReference, - rhs: method_call.object, - })); - self.interner.push_expr_type(new_object, new_type); - self.interner.push_expr_location(new_object, location.span, location.file); - new_object - }); - } - // Otherwise if the object type is a mutable reference and the method is not, insert as - // many dereferences as needed. - } else if matches!(actual_type, Type::MutableReference(_)) { - let (object, new_type) = - self.insert_auto_dereferences(method_call.object, actual_type); - *object_type = new_type; - method_call.object = object; - } - } - } - - /// Insert as many dereference operations as necessary to automatically dereference a method - /// call object to its base value type T. - pub(crate) fn insert_auto_dereferences(&mut self, object: ExprId, typ: Type) -> (ExprId, Type) { - if let Type::MutableReference(element) = typ { - let location = self.interner.id_location(object); - - let object = self.interner.push_expr(HirExpression::Prefix(HirPrefixExpression { - operator: UnaryOp::Dereference { implicitly_added: true }, - rhs: object, - })); - self.interner.push_expr_type(object, element.as_ref().clone()); - self.interner.push_expr_location(object, location.span, location.file); - - // Recursively dereference to allow for converting &mut &mut T to T - self.insert_auto_dereferences(object, *element) - } else { - (object, typ) - } - } - - /// Given a method object: `(*foo).bar` of a method call `(*foo).bar.baz()`, remove the - /// implicitly added dereference operator if one is found. - /// - /// Returns Some(new_expr_id) if a dereference was removed and None otherwise. - fn try_remove_implicit_dereference(&mut self, object: ExprId) -> Option { - match self.interner.expression(&object) { - HirExpression::MemberAccess(mut access) => { - let new_lhs = self.try_remove_implicit_dereference(access.lhs)?; - access.lhs = new_lhs; - access.is_offset = true; - - // `object` will have a different type now, which will be filled in - // later when type checking the method call as a function call. - self.interner.replace_expr(&object, HirExpression::MemberAccess(access)); - Some(object) - } - HirExpression::Prefix(prefix) => match prefix.operator { - // Found a dereference we can remove. Now just replace it with its rhs to remove it. - UnaryOp::Dereference { implicitly_added: true } => Some(prefix.rhs), - _ => None, - }, - _ => None, - } - } - - fn check_index_expression( - &mut self, - id: &ExprId, - mut index_expr: expr::HirIndexExpression, - ) -> Type { - let index_type = self.check_expression(&index_expr.index); - let span = self.interner.expr_span(&index_expr.index); - - index_type.unify(&self.polymorphic_integer_or_field(), &mut self.errors, || { - TypeCheckError::TypeMismatch { - expected_typ: "an integer".to_owned(), - expr_typ: index_type.to_string(), - expr_span: span, - } - }); - - // When writing `a[i]`, if `a : &mut ...` then automatically dereference `a` as many - // times as needed to get the underlying array. - let lhs_type = self.check_expression(&index_expr.collection); - let (new_lhs, lhs_type) = self.insert_auto_dereferences(index_expr.collection, lhs_type); - index_expr.collection = new_lhs; - self.interner.replace_expr(id, HirExpression::Index(index_expr)); - - match lhs_type.follow_bindings() { - // XXX: We can check the array bounds here also, but it may be better to constant fold first - // and have ConstId instead of ExprId for constants - Type::Array(_, base_type) => *base_type, - Type::Slice(base_type) => *base_type, - Type::Error => Type::Error, - typ => { - let span = self.interner.expr_span(&new_lhs); - self.errors.push(TypeCheckError::TypeMismatch { - expected_typ: "Array".to_owned(), - expr_typ: typ.to_string(), - expr_span: span, - }); - Type::Error - } - } - } - - fn check_cast(&mut self, from: Type, to: Type, span: Span) -> Type { - match from.follow_bindings() { - Type::Integer(..) - | Type::FieldElement - | Type::TypeVariable(_, TypeVariableKind::IntegerOrField) - | Type::TypeVariable(_, TypeVariableKind::Integer) - | Type::Bool => (), - - Type::TypeVariable(_, _) => { - self.errors.push(TypeCheckError::TypeAnnotationsNeeded { span }); - return Type::Error; - } - Type::Error => return Type::Error, - from => { - self.errors.push(TypeCheckError::InvalidCast { from, span }); - return Type::Error; - } - } - - match to { - Type::Integer(sign, bits) => Type::Integer(sign, bits), - Type::FieldElement => Type::FieldElement, - Type::Bool => Type::Bool, - Type::Error => Type::Error, - _ => { - self.errors.push(TypeCheckError::UnsupportedCast { span }); - Type::Error - } - } - } - - fn check_if_expr(&mut self, if_expr: &expr::HirIfExpression, expr_id: &ExprId) -> Type { - let cond_type = self.check_expression(&if_expr.condition); - let then_type = self.check_expression(&if_expr.consequence); - - let expr_span = self.interner.expr_span(&if_expr.condition); - - self.unify(&cond_type, &Type::Bool, || TypeCheckError::TypeMismatch { - expected_typ: Type::Bool.to_string(), - expr_typ: cond_type.to_string(), - expr_span, - }); - - match if_expr.alternative { - None => Type::Unit, - Some(alternative) => { - let else_type = self.check_expression(&alternative); - - let expr_span = self.interner.expr_span(expr_id); - self.unify(&then_type, &else_type, || { - let err = TypeCheckError::TypeMismatch { - expected_typ: then_type.to_string(), - expr_typ: else_type.to_string(), - expr_span, - }; - - let context = if then_type == Type::Unit { - "Are you missing a semicolon at the end of your 'else' branch?" - } else if else_type == Type::Unit { - "Are you missing a semicolon at the end of the first block of this 'if'?" - } else { - "Expected the types of both if branches to be equal" - }; - - err.add_context(context) - }); - - then_type - } - } - } - - fn check_constructor( - &mut self, - constructor: expr::HirConstructorExpression, - expr_id: &ExprId, - ) -> Type { - let typ = constructor.r#type; - let generics = constructor.struct_generics; - - // Sort argument types by name so we can zip with the struct type in the same ordering. - // Note that we use a Vec to store the original arguments (rather than a BTreeMap) to - // preserve the evaluation order of the source code. - let mut args = constructor.fields; - sort_by_key_ref(&mut args, |(name, _)| name); - - let mut fields = typ.borrow().get_fields(&generics); - sort_by_key_ref(&mut fields, |(name, _)| name); - - for ((param_name, param_type), (arg_ident, arg)) in fields.into_iter().zip(args) { - // This can be false if the user provided an incorrect field count. That error should - // be caught during name resolution so it is fine to skip typechecking if there is a - // mismatch here as long as we continue typechecking the rest of the program to the best - // of our ability. - if param_name == arg_ident.0.contents { - let arg_type = self.check_expression(&arg); - - let span = self.interner.expr_span(expr_id); - self.unify_with_coercions(&arg_type, ¶m_type, arg, || { - TypeCheckError::TypeMismatch { - expected_typ: param_type.to_string(), - expr_typ: arg_type.to_string(), - expr_span: span, - } - }); - } - } - - Type::Struct(typ, generics) - } - - fn check_member_access(&mut self, mut access: expr::HirMemberAccess, expr_id: ExprId) -> Type { - let lhs_type = self.check_expression(&access.lhs).follow_bindings(); - let span = self.interner.expr_span(&expr_id); - let access_lhs = &mut access.lhs; - - let dereference_lhs = |this: &mut Self, lhs_type, element| { - let old_lhs = *access_lhs; - *access_lhs = this.interner.push_expr(HirExpression::Prefix(HirPrefixExpression { - operator: crate::ast::UnaryOp::Dereference { implicitly_added: true }, - rhs: old_lhs, - })); - this.interner.push_expr_type(old_lhs, lhs_type); - this.interner.push_expr_type(*access_lhs, element); - - let old_location = this.interner.id_location(old_lhs); - this.interner.push_expr_location(*access_lhs, span, old_location.file); - }; - - // If this access is just a field offset, we want to avoid dereferencing - let dereference_lhs = (!access.is_offset).then_some(dereference_lhs); - - match self.check_field_access(&lhs_type, &access.rhs.0.contents, span, dereference_lhs) { - Some((element_type, index)) => { - self.interner.set_field_index(expr_id, index); - // We must update `access` in case we added any dereferences to it - self.interner.replace_expr(&expr_id, HirExpression::MemberAccess(access)); - element_type - } - None => Type::Error, - } - } - - /// This will verify that an expression in the form `lhs.rhs_name` has the given field and will push - /// a type error if it does not. If there is no error, the type of the struct/tuple field is returned - /// along with the index of the field in question. - /// - /// This function is abstracted from check_member_access so that it can be shared between - /// there and the HirLValue::MemberAccess case of check_lvalue. - /// - /// `dereference_lhs` is called when the lhs type is a Type::MutableReference that should be - /// automatically dereferenced so its field can be extracted. This function is expected to - /// perform any mutations necessary to wrap the lhs in a UnaryOp::Dereference prefix - /// expression. The second parameter of this function represents the lhs_type (which should - /// always be a Type::MutableReference if `dereference_lhs` is called) and the third - /// represents the element type. - /// - /// If `dereference_lhs` is None, this will assume we're taking the offset of a struct field - /// rather than dereferencing it. So the result of `foo.bar` with a `foo : &mut Foo` will - /// be a `&mut Bar` rather than just a `Bar`. - pub(super) fn check_field_access( - &mut self, - lhs_type: &Type, - field_name: &str, - span: Span, - dereference_lhs: Option, - ) -> Option<(Type, usize)> { - let lhs_type = lhs_type.follow_bindings(); - - match &lhs_type { - Type::Struct(s, args) => { - let s = s.borrow(); - if let Some((field, index)) = s.get_field(field_name, args) { - return Some((field, index)); - } - } - Type::Tuple(elements) => { - if let Ok(index) = field_name.parse::() { - let length = elements.len(); - if index < length { - return Some((elements[index].clone(), index)); - } else { - self.errors.push(TypeCheckError::TupleIndexOutOfBounds { - index, - lhs_type, - length, - span, - }); - return None; - } - } - } - // If the lhs is a mutable reference we automatically transform - // lhs.field into (*lhs).field - Type::MutableReference(element) => { - if let Some(mut dereference_lhs) = dereference_lhs { - dereference_lhs(self, lhs_type.clone(), element.as_ref().clone()); - return self.check_field_access( - element, - field_name, - span, - Some(dereference_lhs), - ); - } else { - let (element, index) = - self.check_field_access(element, field_name, span, dereference_lhs)?; - return Some((Type::MutableReference(Box::new(element)), index)); - } - } - _ => (), - } - - // If we get here the type has no field named 'access.rhs'. - // Now we specialize the error message based on whether we know the object type in question yet. - if let Type::TypeVariable(..) = &lhs_type { - self.errors.push(TypeCheckError::TypeAnnotationsNeeded { span }); - } else if lhs_type != Type::Error { - self.errors.push(TypeCheckError::AccessUnknownMember { - lhs_type, - field_name: field_name.to_string(), - span, - }); - } - - None - } - - // Given a binary comparison operator and another type. This method will produce the output type - // and a boolean indicating whether to use the trait impl corresponding to the operator - // or not. A value of false indicates the caller to use a primitive operation for this - // operator, while a true value indicates a user-provided trait impl is required. - fn comparator_operand_type_rules( - &mut self, - lhs_type: &Type, - rhs_type: &Type, - op: &HirBinaryOp, - span: Span, - ) -> Result<(Type, bool), TypeCheckError> { - use Type::*; - - match (lhs_type, rhs_type) { - // Avoid reporting errors multiple times - (Error, _) | (_, Error) => Ok((Bool, false)), - (Alias(alias, args), other) | (other, Alias(alias, args)) => { - let alias = alias.borrow().get_type(args); - self.comparator_operand_type_rules(&alias, other, op, span) - } - - // Matches on TypeVariable must be first to follow any type - // bindings. - (TypeVariable(var, _), other) | (other, TypeVariable(var, _)) => { - if let TypeBinding::Bound(binding) = &*var.borrow() { - return self.comparator_operand_type_rules(other, binding, op, span); - } - - let use_impl = self.bind_type_variables_for_infix(lhs_type, op, rhs_type, span); - Ok((Bool, use_impl)) - } - (Integer(sign_x, bit_width_x), Integer(sign_y, bit_width_y)) => { - if sign_x != sign_y { - return Err(TypeCheckError::IntegerSignedness { - sign_x: *sign_x, - sign_y: *sign_y, - span, - }); - } - if bit_width_x != bit_width_y { - return Err(TypeCheckError::IntegerBitWidth { - bit_width_x: *bit_width_x, - bit_width_y: *bit_width_y, - span, - }); - } - Ok((Bool, false)) - } - (FieldElement, FieldElement) => { - if op.kind.is_valid_for_field_type() { - Ok((Bool, false)) - } else { - Err(TypeCheckError::FieldComparison { span }) - } - } - - // <= and friends are technically valid for booleans, just not very useful - (Bool, Bool) => Ok((Bool, false)), - - (lhs, rhs) => { - self.unify(lhs, rhs, || TypeCheckError::TypeMismatchWithSource { - expected: lhs.clone(), - actual: rhs.clone(), - span: op.location.span, - source: Source::Binary, - }); - Ok((Bool, true)) - } - } - } - - fn lookup_method( - &mut self, - object_type: &Type, - method_name: &str, - expr_id: &ExprId, - ) -> Option { - match object_type.follow_bindings() { - Type::Struct(typ, _args) => { - let id = typ.borrow().id; - match self.interner.lookup_method(object_type, id, method_name, false) { - Some(method_id) => Some(HirMethodReference::FuncId(method_id)), - None => { - self.errors.push(TypeCheckError::UnresolvedMethodCall { - method_name: method_name.to_string(), - object_type: object_type.clone(), - span: self.interner.expr_span(expr_id), - }); - None - } - } - } - // TODO: We should allow method calls on `impl Trait`s eventually. - // For now it is fine since they are only allowed on return types. - Type::TraitAsType(..) => { - self.errors.push(TypeCheckError::UnresolvedMethodCall { - method_name: method_name.to_string(), - object_type: object_type.clone(), - span: self.interner.expr_span(expr_id), - }); - None - } - Type::NamedGeneric(_, _, _) => { - let func_meta = self.interner.function_meta( - &self.current_function.expect("unexpected method outside a function"), - ); - - for constraint in &func_meta.trait_constraints { - if *object_type == constraint.typ { - if let Some(the_trait) = self.interner.try_get_trait(constraint.trait_id) { - for (method_index, method) in the_trait.methods.iter().enumerate() { - if method.name.0.contents == method_name { - let trait_method = TraitMethodId { - trait_id: constraint.trait_id, - method_index, - }; - return Some(HirMethodReference::TraitMethodId( - trait_method, - constraint.trait_generics.clone(), - )); - } - } - } - } - } - - self.errors.push(TypeCheckError::UnresolvedMethodCall { - method_name: method_name.to_string(), - object_type: object_type.clone(), - span: self.interner.expr_span(expr_id), - }); - None - } - // Mutable references to another type should resolve to methods of their element type. - // This may be a struct or a primitive type. - Type::MutableReference(element) => self - .interner - .lookup_primitive_trait_method_mut(element.as_ref(), method_name) - .map(HirMethodReference::FuncId) - .or_else(|| self.lookup_method(&element, method_name, expr_id)), - - // If we fail to resolve the object to a struct type, we have no way of type - // checking its arguments as we can't even resolve the name of the function - Type::Error => None, - - // The type variable must be unbound at this point since follow_bindings was called - Type::TypeVariable(_, TypeVariableKind::Normal) => { - let span = self.interner.expr_span(expr_id); - self.errors.push(TypeCheckError::TypeAnnotationsNeeded { span }); - None - } - - other => match self.interner.lookup_primitive_method(&other, method_name) { - Some(method_id) => Some(HirMethodReference::FuncId(method_id)), - None => { - self.errors.push(TypeCheckError::UnresolvedMethodCall { - method_name: method_name.to_string(), - object_type: object_type.clone(), - span: self.interner.expr_span(expr_id), - }); - None - } - }, - } - } - - fn bind_function_type_impl( - &mut self, - fn_params: &[Type], - fn_ret: &Type, - callsite_args: &[(Type, ExprId, Span)], - span: Span, - ) -> Type { - if fn_params.len() != callsite_args.len() { - self.errors.push(TypeCheckError::ParameterCountMismatch { - expected: fn_params.len(), - found: callsite_args.len(), - span, - }); - return Type::Error; - } - - for (param, (arg, _, arg_span)) in fn_params.iter().zip(callsite_args) { - self.unify(arg, param, || TypeCheckError::TypeMismatch { - expected_typ: param.to_string(), - expr_typ: arg.to_string(), - expr_span: *arg_span, - }); - } - - fn_ret.clone() - } - - fn bind_function_type( - &mut self, - function: Type, - args: Vec<(Type, ExprId, Span)>, - span: Span, - ) -> Type { - // Could do a single unification for the entire function type, but matching beforehand - // lets us issue a more precise error on the individual argument that fails to type check. - match function { - Type::TypeVariable(binding, TypeVariableKind::Normal) => { - if let TypeBinding::Bound(typ) = &*binding.borrow() { - return self.bind_function_type(typ.clone(), args, span); - } - - let ret = self.interner.next_type_variable(); - let args = vecmap(args, |(arg, _, _)| arg); - let env_type = self.interner.next_type_variable(); - let expected = Type::Function(args, Box::new(ret.clone()), Box::new(env_type)); - - if let Err(error) = binding.try_bind(expected, span) { - self.errors.push(error); - } - ret - } - // ignoring env for subtype on purpose - Type::Function(parameters, ret, _env) => { - self.bind_function_type_impl(¶meters, &ret, &args, span) - } - Type::Error => Type::Error, - found => { - self.errors.push(TypeCheckError::ExpectedFunction { found, span }); - Type::Error - } - } - } - - /// Handles the TypeVariable case for checking binary operators. - /// Returns true if we should use the impl for the operator instead of the primitive - /// version of it. - fn bind_type_variables_for_infix( - &mut self, - lhs_type: &Type, - op: &HirBinaryOp, - rhs_type: &Type, - span: Span, - ) -> bool { - self.unify(lhs_type, rhs_type, || TypeCheckError::TypeMismatchWithSource { - expected: lhs_type.clone(), - actual: rhs_type.clone(), - source: Source::Binary, - span, - }); - - let use_impl = !lhs_type.is_numeric(); - - // If this operator isn't valid for fields we have to possibly narrow - // TypeVariableKind::IntegerOrField to TypeVariableKind::Integer. - // Doing so also ensures a type error if Field is used. - // The is_numeric check is to allow impls for custom types to bypass this. - if !op.kind.is_valid_for_field_type() && lhs_type.is_numeric() { - let target = Type::polymorphic_integer(self.interner); - - use crate::ast::BinaryOpKind::*; - use TypeCheckError::*; - self.unify(lhs_type, &target, || match op.kind { - Less | LessEqual | Greater | GreaterEqual => FieldComparison { span }, - And | Or | Xor | ShiftRight | ShiftLeft => FieldBitwiseOp { span }, - Modulo => FieldModulo { span }, - other => unreachable!("Operator {other:?} should be valid for Field"), - }); - } - - use_impl - } - - // Given a binary operator and another type. This method will produce the output type - // and a boolean indicating whether to use the trait impl corresponding to the operator - // or not. A value of false indicates the caller to use a primitive operation for this - // operator, while a true value indicates a user-provided trait impl is required. - fn infix_operand_type_rules( - &mut self, - lhs_type: &Type, - op: &HirBinaryOp, - rhs_type: &Type, - span: Span, - ) -> Result<(Type, bool), TypeCheckError> { - if op.kind.is_comparator() { - return self.comparator_operand_type_rules(lhs_type, rhs_type, op, span); - } - - use Type::*; - match (lhs_type, rhs_type) { - // An error type on either side will always return an error - (Error, _) | (_, Error) => Ok((Error, false)), - (Alias(alias, args), other) | (other, Alias(alias, args)) => { - let alias = alias.borrow().get_type(args); - self.infix_operand_type_rules(&alias, op, other, span) - } - - // Matches on TypeVariable must be first so that we follow any type - // bindings. - (TypeVariable(int, _), other) | (other, TypeVariable(int, _)) => { - if let TypeBinding::Bound(binding) = &*int.borrow() { - return self.infix_operand_type_rules(binding, op, other, span); - } - if op.kind == BinaryOpKind::ShiftLeft || op.kind == BinaryOpKind::ShiftRight { - self.unify( - rhs_type, - &Type::Integer(Signedness::Unsigned, IntegerBitSize::Eight), - || TypeCheckError::InvalidShiftSize { span }, - ); - let use_impl = if lhs_type.is_numeric() { - let integer_type = Type::polymorphic_integer(self.interner); - self.bind_type_variables_for_infix(lhs_type, op, &integer_type, span) - } else { - true - }; - return Ok((lhs_type.clone(), use_impl)); - } - let use_impl = self.bind_type_variables_for_infix(lhs_type, op, rhs_type, span); - Ok((other.clone(), use_impl)) - } - (Integer(sign_x, bit_width_x), Integer(sign_y, bit_width_y)) => { - if op.kind == BinaryOpKind::ShiftLeft || op.kind == BinaryOpKind::ShiftRight { - if *sign_y != Signedness::Unsigned || *bit_width_y != IntegerBitSize::Eight { - return Err(TypeCheckError::InvalidShiftSize { span }); - } - return Ok((Integer(*sign_x, *bit_width_x), false)); - } - if sign_x != sign_y { - return Err(TypeCheckError::IntegerSignedness { - sign_x: *sign_x, - sign_y: *sign_y, - span, - }); - } - if bit_width_x != bit_width_y { - return Err(TypeCheckError::IntegerBitWidth { - bit_width_x: *bit_width_x, - bit_width_y: *bit_width_y, - span, - }); - } - Ok((Integer(*sign_x, *bit_width_x), false)) - } - // The result of two Fields is always a witness - (FieldElement, FieldElement) => { - if !op.kind.is_valid_for_field_type() { - if op.kind == BinaryOpKind::Modulo { - return Err(TypeCheckError::FieldModulo { span }); - } else { - return Err(TypeCheckError::FieldBitwiseOp { span }); - } - } - Ok((FieldElement, false)) - } - - (Bool, Bool) => Ok((Bool, false)), - - (lhs, rhs) => { - if op.kind == BinaryOpKind::ShiftLeft || op.kind == BinaryOpKind::ShiftRight { - if rhs == &Type::Integer(Signedness::Unsigned, IntegerBitSize::Eight) { - return Ok((lhs.clone(), true)); - } - return Err(TypeCheckError::InvalidShiftSize { span }); - } - self.unify(lhs, rhs, || TypeCheckError::TypeMismatchWithSource { - expected: lhs.clone(), - actual: rhs.clone(), - span: op.location.span, - source: Source::Binary, - }); - Ok((lhs.clone(), true)) - } - } - } - - fn type_check_prefix_operand( - &mut self, - op: &crate::ast::UnaryOp, - rhs_type: &Type, - span: Span, - ) -> Type { - let mut unify = |expected| { - rhs_type.unify(&expected, &mut self.errors, || TypeCheckError::TypeMismatch { - expr_typ: rhs_type.to_string(), - expected_typ: expected.to_string(), - expr_span: span, - }); - expected - }; - - match op { - crate::ast::UnaryOp::Minus => { - if rhs_type.is_unsigned() { - self.errors - .push(TypeCheckError::InvalidUnaryOp { kind: rhs_type.to_string(), span }); - } - let expected = self.polymorphic_integer_or_field(); - rhs_type.unify(&expected, &mut self.errors, || TypeCheckError::InvalidUnaryOp { - kind: rhs_type.to_string(), - span, - }); - expected - } - crate::ast::UnaryOp::Not => { - let rhs_type = rhs_type.follow_bindings(); - - // `!` can work on booleans or integers - if matches!(rhs_type, Type::Integer(..)) { - return rhs_type; - } - - unify(Type::Bool) - } - crate::ast::UnaryOp::MutableReference => { - Type::MutableReference(Box::new(rhs_type.follow_bindings())) - } - crate::ast::UnaryOp::Dereference { implicitly_added: _ } => { - let element_type = self.interner.next_type_variable(); - unify(Type::MutableReference(Box::new(element_type.clone()))); - element_type - } - } - } - - /// Prerequisite: verify_trait_constraint of the operator's trait constraint. - /// - /// Although by this point the operator is expected to already have a trait impl, - /// we still need to match the operator's type against the method's instantiated type - /// to ensure the instantiation bindings are correct and the monomorphizer can - /// re-apply the needed bindings. - fn typecheck_operator_method( - &mut self, - expr_id: ExprId, - trait_method_id: TraitMethodId, - object_type: &Type, - span: Span, - ) { - let the_trait = self.interner.get_trait(trait_method_id.trait_id); - - let method = &the_trait.methods[trait_method_id.method_index]; - let (method_type, mut bindings) = method.typ.instantiate(self.interner); - - match method_type { - Type::Function(args, _, _) => { - // We can cheat a bit and match against only the object type here since no operator - // overload uses other generic parameters or return types aside from the object type. - let expected_object_type = &args[0]; - self.unify(object_type, expected_object_type, || TypeCheckError::TypeMismatch { - expected_typ: expected_object_type.to_string(), - expr_typ: object_type.to_string(), - expr_span: span, - }); - } - other => { - unreachable!("Expected operator method to have a function type, but found {other}") - } - } - - // We must also remember to apply these substitutions to the object_type - // referenced by the selected trait impl, if one has yet to be selected. - let impl_kind = self.interner.get_selected_impl_for_expression(expr_id); - if let Some(TraitImplKind::Assumed { object_type, trait_generics }) = impl_kind { - let the_trait = self.interner.get_trait(trait_method_id.trait_id); - let object_type = object_type.substitute(&bindings); - bindings.insert( - the_trait.self_type_typevar_id, - (the_trait.self_type_typevar.clone(), object_type.clone()), - ); - self.interner.select_impl_for_expression( - expr_id, - TraitImplKind::Assumed { object_type, trait_generics }, - ); - } - - self.interner.store_instantiation_bindings(expr_id, bindings); - } -} - -/// Taken from: https://stackoverflow.com/a/47127500 -fn sort_by_key_ref(xs: &mut [T], key: F) -where - F: Fn(&T) -> &K, - K: ?Sized + Ord, -{ - xs.sort_by(|x, y| key(x).cmp(key(y))); -} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/mod.rs index 1a70bade863..b6efa17a529 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/mod.rs @@ -8,801 +8,5 @@ //! as all functions are required to give their full signatures. Closures are inferred but are //! never generalized and thus cannot be used polymorphically. mod errors; -mod expr; -mod stmt; - -pub use errors::{NoMatchingImplFoundError, TypeCheckError}; -use noirc_errors::Span; - -use crate::{ - hir_def::{ - expr::HirExpression, - function::{Param, Parameters}, - stmt::HirStatement, - traits::TraitConstraint, - }, - node_interner::{ExprId, FuncId, GlobalId, NodeInterner}, - Kind, ResolvedGeneric, Type, TypeBindings, -}; - pub use self::errors::Source; - -pub struct TypeChecker<'interner> { - interner: &'interner mut NodeInterner, - errors: Vec, - current_function: Option, - - /// Trait constraints are collected during type checking until they are - /// verified at the end of a function. This is because constraints arise - /// on each variable, but it is only until function calls when the types - /// needed for the trait constraint may become known. - trait_constraints: Vec<(TraitConstraint, ExprId)>, - - /// All type variables created in the current function. - /// This map is used to default any integer type variables at the end of - /// a function (before checking trait constraints) if a type wasn't already chosen. - type_variables: Vec, -} - -/// Type checks a function and assigns the -/// appropriate types to expressions in a side table -pub fn type_check_func(interner: &mut NodeInterner, func_id: FuncId) -> Vec { - let meta = interner.function_meta(&func_id); - let declared_return_type = meta.return_type().clone(); - let can_ignore_ret = meta.is_stub(); - - let function_body_id = &interner.function(&func_id).as_expr(); - - let mut type_checker = TypeChecker::new(interner); - type_checker.current_function = Some(func_id); - - let meta = type_checker.interner.function_meta(&func_id); - let parameters = meta.parameters.clone(); - let expected_return_type = meta.return_type.clone(); - let expected_trait_constraints = meta.trait_constraints.clone(); - let name_span = meta.name.location.span; - - let mut errors = Vec::new(); - - // Temporarily add any impls in this function's `where` clause to scope - for constraint in &expected_trait_constraints { - let object = constraint.typ.clone(); - let trait_id = constraint.trait_id; - let generics = constraint.trait_generics.clone(); - - if !type_checker.interner.add_assumed_trait_implementation(object, trait_id, generics) { - if let Some(the_trait) = type_checker.interner.try_get_trait(trait_id) { - let trait_name = the_trait.name.to_string(); - let typ = constraint.typ.clone(); - let span = name_span; - errors.push(TypeCheckError::UnneededTraitConstraint { trait_name, typ, span }); - } - } - } - - // Bind each parameter to its annotated type. - // This is locally obvious, but it must be bound here so that the - // Definition object of the parameter in the NodeInterner is given the correct type. - for param in parameters { - check_if_type_is_valid_for_program_input(&type_checker, func_id, ¶m, &mut errors); - type_checker.bind_pattern(¶m.0, param.1); - } - - let function_last_type = type_checker.check_function_body(function_body_id); - // Check declared return type and actual return type - if !can_ignore_ret { - let (expr_span, empty_function) = function_info(type_checker.interner, function_body_id); - let func_span = type_checker.interner.expr_span(function_body_id); // XXX: We could be more specific and return the span of the last stmt, however stmts do not have spans yet - if let Type::TraitAsType(trait_id, _, generics) = &declared_return_type { - if type_checker - .interner - .lookup_trait_implementation(&function_last_type, *trait_id, generics) - .is_err() - { - let error = TypeCheckError::TypeMismatchWithSource { - expected: declared_return_type.clone(), - actual: function_last_type, - span: func_span, - source: Source::Return(expected_return_type, expr_span), - }; - errors.push(error); - } - } else { - function_last_type.unify_with_coercions( - &declared_return_type, - *function_body_id, - type_checker.interner, - &mut errors, - || { - let mut error = TypeCheckError::TypeMismatchWithSource { - expected: declared_return_type.clone(), - actual: function_last_type.clone(), - span: func_span, - source: Source::Return(expected_return_type, expr_span), - }; - - if empty_function { - error = error.add_context("implicitly returns `()` as its body has no tail or `return` expression"); - } - error - }, - ); - } - } - - // Default any type variables that still need defaulting. - // This is done before trait impl search since leaving them bindable can lead to errors - // when multiple impls are available. Instead we default first to choose the Field or u64 impl. - for typ in &type_checker.type_variables { - if let Type::TypeVariable(variable, kind) = typ.follow_bindings() { - let msg = "TypeChecker should only track defaultable type vars"; - variable.bind(kind.default_type().expect(msg)); - } - } - - // Verify any remaining trait constraints arising from the function body - for (mut constraint, expr_id) in std::mem::take(&mut type_checker.trait_constraints) { - let span = type_checker.interner.expr_span(&expr_id); - - if matches!(&constraint.typ, Type::MutableReference(_)) { - let (_, dereferenced_typ) = - type_checker.insert_auto_dereferences(expr_id, constraint.typ.clone()); - constraint.typ = dereferenced_typ; - } - - type_checker.verify_trait_constraint( - &constraint.typ, - constraint.trait_id, - &constraint.trait_generics, - expr_id, - span, - ); - } - - // Now remove all the `where` clause constraints we added - for constraint in &expected_trait_constraints { - type_checker.interner.remove_assumed_trait_implementations_for_trait(constraint.trait_id); - } - - errors.append(&mut type_checker.errors); - errors -} - -/// Only sized types are valid to be used as main's parameters or the parameters to a contract -/// function. If the given type is not sized (e.g. contains a slice or NamedGeneric type), an -/// error is issued. -fn check_if_type_is_valid_for_program_input( - type_checker: &TypeChecker<'_>, - func_id: FuncId, - param: &Param, - errors: &mut Vec, -) { - let meta = type_checker.interner.function_meta(&func_id); - if (meta.is_entry_point && !param.1.is_valid_for_program_input()) - || (meta.has_inline_attribute && !param.1.is_valid_non_inlined_function_input()) - { - let span = param.0.span(); - errors.push(TypeCheckError::InvalidTypeForEntryPoint { span }); - } -} - -fn function_info(interner: &NodeInterner, function_body_id: &ExprId) -> (noirc_errors::Span, bool) { - let (expr_span, empty_function) = - if let HirExpression::Block(block) = interner.expression(function_body_id) { - let last_stmt = block.statements().last(); - let mut span = interner.expr_span(function_body_id); - - if let Some(last_stmt) = last_stmt { - if let HirStatement::Expression(expr) = interner.statement(last_stmt) { - span = interner.expr_span(&expr); - } - } - - (span, last_stmt.is_none()) - } else { - (interner.expr_span(function_body_id), false) - }; - (expr_span, empty_function) -} - -/// Checks that the type of a function in a trait impl matches the type -/// of the corresponding function declaration in the trait itself. -/// -/// To do this, given a trait such as: -/// `trait Foo { fn foo(...); }` -/// -/// And an impl such as: -/// `impl Foo for Bar { fn foo(...); } ` -/// -/// We have to substitute: -/// - Self for Bar -/// - A for D -/// - B for F -/// -/// Before we can type check. Finally, we must also check that the unification -/// result does not introduce any new bindings. This can happen if the impl -/// function's type is more general than that of the trait function. E.g. -/// `fn baz(a: A, b: B)` when the impl required `fn baz(a: A, b: A)`. -/// -/// This does not type check the body of the impl function. -pub(crate) fn check_trait_impl_method_matches_declaration( - interner: &mut NodeInterner, - function: FuncId, -) -> Vec { - let meta = interner.function_meta(&function); - let method_name = interner.function_name(&function); - let mut errors = Vec::new(); - - let definition_type = meta.typ.as_monotype(); - - let impl_ = - meta.trait_impl.expect("Trait impl function should have a corresponding trait impl"); - - // If the trait implementation is not defined in the interner then there was a previous - // error in resolving the trait path and there is likely no trait for this impl. - let Some(impl_) = interner.try_get_trait_implementation(impl_) else { - return errors; - }; - - let impl_ = impl_.borrow(); - let trait_info = interner.get_trait(impl_.trait_id); - - let mut bindings = TypeBindings::new(); - bindings.insert( - trait_info.self_type_typevar_id, - (trait_info.self_type_typevar.clone(), impl_.typ.clone()), - ); - - if trait_info.generics.len() != impl_.trait_generics.len() { - let expected = trait_info.generics.len(); - let found = impl_.trait_generics.len(); - let span = impl_.ident.span(); - let item = trait_info.name.to_string(); - errors.push(TypeCheckError::GenericCountMismatch { item, expected, found, span }); - } - - // Substitute each generic on the trait with the corresponding generic on the impl - for (generic, arg) in trait_info.generics.iter().zip(&impl_.trait_generics) { - bindings.insert(generic.type_var.id(), (generic.type_var.clone(), arg.clone())); - } - - // If this is None, the trait does not have the corresponding function. - // This error should have been caught in name resolution already so we don't - // issue an error for it here. - if let Some(trait_fn_id) = trait_info.method_ids.get(method_name) { - let trait_fn_meta = interner.function_meta(trait_fn_id); - - if trait_fn_meta.direct_generics.len() != meta.direct_generics.len() { - let expected = trait_fn_meta.direct_generics.len(); - let found = meta.direct_generics.len(); - let span = meta.name.location.span; - let item = method_name.to_string(); - errors.push(TypeCheckError::GenericCountMismatch { item, expected, found, span }); - } - - // Substitute each generic on the trait function with the corresponding generic on the impl function - for ( - ResolvedGeneric { type_var: trait_fn_generic, .. }, - ResolvedGeneric { name, type_var: impl_fn_generic, .. }, - ) in trait_fn_meta.direct_generics.iter().zip(&meta.direct_generics) - { - let arg = Type::NamedGeneric(impl_fn_generic.clone(), name.clone(), Kind::Normal); - bindings.insert(trait_fn_generic.id(), (trait_fn_generic.clone(), arg)); - } - - let (declaration_type, _) = trait_fn_meta.typ.instantiate_with_bindings(bindings, interner); - - check_function_type_matches_expected_type( - &declaration_type, - definition_type, - method_name, - &meta.parameters, - meta.name.location.span, - &trait_info.name.0.contents, - &mut errors, - ); - } - - errors -} - -fn check_function_type_matches_expected_type( - expected: &Type, - actual: &Type, - method_name: &str, - actual_parameters: &Parameters, - span: Span, - trait_name: &str, - errors: &mut Vec, -) { - let mut bindings = TypeBindings::new(); - // Shouldn't need to unify envs, they should always be equal since they're both free functions - if let (Type::Function(params_a, ret_a, _env_a), Type::Function(params_b, ret_b, _env_b)) = - (expected, actual) - { - if params_a.len() == params_b.len() { - for (i, (a, b)) in params_a.iter().zip(params_b.iter()).enumerate() { - if a.try_unify(b, &mut bindings).is_err() { - errors.push(TypeCheckError::TraitMethodParameterTypeMismatch { - method_name: method_name.to_string(), - expected_typ: a.to_string(), - actual_typ: b.to_string(), - parameter_span: actual_parameters.0[i].0.span(), - parameter_index: i + 1, - }); - } - } - - if ret_b.try_unify(ret_a, &mut bindings).is_err() { - errors.push(TypeCheckError::TypeMismatch { - expected_typ: ret_a.to_string(), - expr_typ: ret_b.to_string(), - expr_span: span, - }); - } - } else { - errors.push(TypeCheckError::MismatchTraitImplNumParameters { - actual_num_parameters: params_b.len(), - expected_num_parameters: params_a.len(), - trait_name: trait_name.to_string(), - method_name: method_name.to_string(), - span, - }); - } - } - - // If result bindings is not empty, a type variable was bound which means the two - // signatures were not a perfect match. Note that this relies on us already binding - // all the expected generics to each other prior to this check. - if !bindings.is_empty() { - let expected_typ = expected.to_string(); - let expr_typ = actual.to_string(); - errors.push(TypeCheckError::TypeMismatch { expected_typ, expr_typ, expr_span: span }); - } -} - -impl<'interner> TypeChecker<'interner> { - fn new(interner: &'interner mut NodeInterner) -> Self { - Self { - interner, - errors: Vec::new(), - trait_constraints: Vec::new(), - type_variables: Vec::new(), - current_function: None, - } - } - - fn check_function_body(&mut self, body: &ExprId) -> Type { - self.check_expression(body) - } - - pub fn check_global( - id: GlobalId, - interner: &'interner mut NodeInterner, - ) -> Vec { - let mut this = Self { - interner, - errors: Vec::new(), - trait_constraints: Vec::new(), - type_variables: Vec::new(), - current_function: None, - }; - let statement = this.interner.get_global(id).let_statement; - this.check_statement(&statement); - this.errors - } - - /// Wrapper of Type::unify using self.errors - fn unify( - &mut self, - actual: &Type, - expected: &Type, - make_error: impl FnOnce() -> TypeCheckError, - ) { - actual.unify(expected, &mut self.errors, make_error); - } - - /// Wrapper of Type::unify_with_coercions using self.errors - fn unify_with_coercions( - &mut self, - actual: &Type, - expected: &Type, - expression: ExprId, - make_error: impl FnOnce() -> TypeCheckError, - ) { - actual.unify_with_coercions( - expected, - expression, - self.interner, - &mut self.errors, - make_error, - ); - } - - /// Return a fresh integer or field type variable and log it - /// in self.type_variables to default it later. - fn polymorphic_integer_or_field(&mut self) -> Type { - let typ = Type::polymorphic_integer_or_field(self.interner); - self.type_variables.push(typ.clone()); - typ - } - - /// Return a fresh integer type variable and log it - /// in self.type_variables to default it later. - fn polymorphic_integer(&mut self) -> Type { - let typ = Type::polymorphic_integer(self.interner); - self.type_variables.push(typ.clone()); - typ - } -} - -// XXX: These tests are all manual currently. -/// We can either build a test apparatus or pass raw code through the resolver -#[cfg(test)] -pub mod test { - use std::collections::{BTreeMap, HashMap}; - use std::vec; - - use fm::FileId; - use iter_extended::btree_map; - use noirc_errors::{Location, Span}; - - use crate::ast::{BinaryOpKind, FunctionKind, FunctionReturnType, Path, Visibility}; - use crate::graph::CrateId; - use crate::hir::def_map::{ModuleData, ModuleId}; - use crate::hir::resolution::import::{ - PathResolution, PathResolutionError, PathResolutionResult, - }; - use crate::hir_def::expr::HirIdent; - use crate::hir_def::function::FunctionBody; - use crate::hir_def::stmt::HirLetStatement; - use crate::hir_def::stmt::HirPattern::Identifier; - use crate::hir_def::types::Type; - use crate::hir_def::{ - expr::{HirBinaryOp, HirBlockExpression, HirExpression, HirInfixExpression}, - function::{FuncMeta, HirFunction}, - stmt::HirStatement, - }; - use crate::node_interner::{ - DefinitionKind, FuncId, NodeInterner, ReferenceId, TraitId, TraitMethodId, - }; - use crate::{ - hir::{ - def_map::{CrateDefMap, LocalModuleId, ModuleDefId}, - resolution::{path_resolver::PathResolver, resolver::Resolver}, - }, - parse_program, - }; - - #[test] - fn basic_let() { - let mut interner = NodeInterner::default(); - interner.populate_dummy_operator_traits(); - - // Safety: The FileId in a location isn't used for tests - let file = FileId::default(); - let location = Location::new(Span::default(), file); - - // Add a simple let Statement into the interner - // let z = x + y; - // - // Push x variable - let x_id = interner.push_definition( - "x".into(), - false, - false, - DefinitionKind::Local(None), - location, - ); - - let x = HirIdent::non_trait_method(x_id, location); - - // Push y variable - let y_id = interner.push_definition( - "y".into(), - false, - false, - DefinitionKind::Local(None), - location, - ); - let y = HirIdent::non_trait_method(y_id, location); - - // Push z variable - let z_id = interner.push_definition( - "z".into(), - false, - false, - DefinitionKind::Local(None), - location, - ); - let z = HirIdent::non_trait_method(z_id, location); - - // Push x and y as expressions - let x_expr_id = interner.push_expr(HirExpression::Ident(x.clone(), None)); - let y_expr_id = interner.push_expr(HirExpression::Ident(y.clone(), None)); - - // Create Infix - let operator = HirBinaryOp { location, kind: BinaryOpKind::Add }; - let trait_id = TraitId(ModuleId::dummy_id()); - let trait_method_id = TraitMethodId { trait_id, method_index: 0 }; - let expr = HirInfixExpression { lhs: x_expr_id, operator, rhs: y_expr_id, trait_method_id }; - let expr_id = interner.push_expr(HirExpression::Infix(expr)); - interner.push_expr_location(expr_id, Span::single_char(0), file); - - interner.push_expr_location(x_expr_id, Span::single_char(0), file); - interner.push_expr_location(y_expr_id, Span::single_char(0), file); - - // Create let statement - let let_stmt = HirLetStatement { - pattern: Identifier(z), - r#type: Type::FieldElement, - expression: expr_id, - attributes: vec![], - comptime: false, - }; - let stmt_id = interner.push_stmt(HirStatement::Let(let_stmt)); - let expr_id = interner - .push_expr(HirExpression::Block(HirBlockExpression { statements: vec![stmt_id] })); - interner.push_expr_location(expr_id, Span::single_char(0), file); - - // Create function to enclose the let statement - let func = HirFunction::unchecked_from_expr(expr_id); - let func_id = interner.push_fn(func); - - let definition = DefinitionKind::Local(None); - let id = interner.push_definition("test_func".into(), false, false, definition, location); - let name = HirIdent::non_trait_method(id, location); - - // Add function meta - let func_meta = FuncMeta { - name, - kind: FunctionKind::Normal, - location, - typ: Type::Function( - vec![Type::FieldElement, Type::FieldElement], - Box::new(Type::Unit), - Box::new(Type::Unit), - ), - parameters: vec![ - (Identifier(x), Type::FieldElement, Visibility::Private), - (Identifier(y), Type::FieldElement, Visibility::Private), - ] - .into(), - return_visibility: Visibility::Private, - has_body: true, - trait_impl: None, - return_type: FunctionReturnType::Default(Span::default()), - trait_constraints: Vec::new(), - direct_generics: Vec::new(), - is_entry_point: true, - is_trait_function: false, - has_inline_attribute: false, - all_generics: Vec::new(), - parameter_idents: Vec::new(), - function_body: FunctionBody::Resolved, - source_crate: CrateId::dummy_id(), - }; - interner.push_fn_meta(func_meta, func_id); - - let errors = super::type_check_func(&mut interner, func_id); - assert!(errors.is_empty()); - } - - #[test] - #[should_panic] - fn basic_let_stmt() { - let src = r#" - fn main(x : Field) { - let k = [x,x]; - let _z = x + k; - } - "#; - - type_check_src_code(src, vec![String::from("main")]); - } - - #[test] - fn basic_index_expr() { - let src = r#" - fn main(x : Field) { - let k = [x,x]; - let _z = x + k[0]; - } - "#; - - type_check_src_code(src, vec![String::from("main")]); - } - #[test] - fn basic_call_expr() { - let src = r#" - fn main(x : Field) { - let _z = x + foo(x); - } - - fn foo(x : Field) -> Field { - x - } - "#; - - type_check_src_code(src, vec![String::from("main"), String::from("foo")]); - } - #[test] - fn basic_for_expr() { - let src = r#" - fn main(_x : Field) { - for _i in 0..10 { - for _k in 0..100 { - - } - } - } - - "#; - - type_check_src_code(src, vec![String::from("main")]); - } - #[test] - fn basic_closure() { - let src = r#" - fn main(x : Field) -> pub Field { - let closure = |y| y + x; - closure(x) - } - "#; - - type_check_src_code(src, vec![String::from("main")]); - } - - #[test] - fn closure_with_no_args() { - let src = r#" - fn main(x : Field) -> pub Field { - let closure = || x; - closure() - } - "#; - - type_check_src_code(src, vec![String::from("main")]); - } - - #[test] - fn fold_entry_point() { - let src = r#" - #[fold] - fn fold(x: &mut Field) -> Field { - *x - } - "#; - - type_check_src_code_errors_expected(src, vec![String::from("fold")], 1); - } - - #[test] - fn fold_numeric_generic() { - let src = r#" - #[fold] - fn fold(x: T) -> T { - x - } - "#; - - type_check_src_code(src, vec![String::from("fold")]); - } - // This is the same Stub that is in the resolver, maybe we can pull this out into a test module and re-use? - struct TestPathResolver(HashMap); - - impl PathResolver for TestPathResolver { - fn resolve( - &self, - _def_maps: &BTreeMap, - path: Path, - _path_references: &mut Option<&mut Vec>, - ) -> PathResolutionResult { - // Not here that foo::bar and hello::foo::bar would fetch the same thing - let name = path.segments.last().unwrap(); - self.0 - .get(&name.0.contents) - .cloned() - .map(|module_def_id| PathResolution { module_def_id, error: None }) - .ok_or_else(move || PathResolutionError::Unresolved(name.clone())) - } - - fn local_module_id(&self) -> LocalModuleId { - LocalModuleId(noirc_arena::Index::unsafe_zeroed()) - } - - fn module_id(&self) -> ModuleId { - ModuleId { krate: CrateId::dummy_id(), local_id: self.local_module_id() } - } - } - - impl TestPathResolver { - fn insert_func(&mut self, name: String, func_id: FuncId) { - self.0.insert(name, func_id.into()); - } - } - - pub fn type_check_src_code(src: &str, func_namespace: Vec) -> (NodeInterner, FuncId) { - type_check_src_code_errors_expected(src, func_namespace, 0) - } - - // This function assumes that there is only one function and this is the - // func id that is returned - fn type_check_src_code_errors_expected( - src: &str, - func_namespace: Vec, - expected_num_type_check_errs: usize, - ) -> (NodeInterner, FuncId) { - let (program, errors) = parse_program(src); - let mut interner = NodeInterner::default(); - interner.populate_dummy_operator_traits(); - - if !errors.iter().all(|error| error.is_warning()) { - assert_eq!( - errors.len(), - 0, - "expected 0 parser errors, but got {}, errors: {:?}", - errors.len(), - errors - ); - } - - let func_ids = btree_map(&func_namespace, |name| { - (name.to_string(), interner.push_test_function_definition(name.into())) - }); - - let main_id = - *func_ids.get("main").unwrap_or_else(|| func_ids.first_key_value().unwrap().1); - - let mut path_resolver = TestPathResolver(HashMap::new()); - for (name, id) in func_ids.iter() { - path_resolver.insert_func(name.to_owned(), *id); - } - - let mut def_maps = BTreeMap::new(); - let file = FileId::default(); - - let mut modules = noirc_arena::Arena::default(); - let location = Location::new(Default::default(), file); - modules.insert(ModuleData::new(None, location, false)); - - def_maps.insert( - CrateId::dummy_id(), - CrateDefMap { - root: path_resolver.local_module_id(), - modules, - krate: CrateId::dummy_id(), - extern_prelude: BTreeMap::new(), - }, - ); - - for nf in program.into_sorted().functions { - let resolver = Resolver::new(&mut interner, &path_resolver, &def_maps, file); - - let function_id = *func_ids.get(nf.name()).unwrap(); - let (hir_func, func_meta, resolver_errors) = resolver.resolve_function(nf, function_id); - - interner.push_fn_meta(func_meta, function_id); - interner.update_fn(function_id, hir_func); - assert_eq!(resolver_errors, vec![]); - } - - // Type check section - let mut errors = Vec::new(); - - for function in func_ids.values() { - errors.extend(super::type_check_func(&mut interner, *function)); - } - - assert_eq!( - errors.len(), - expected_num_type_check_errs, - "expected {} type check errors, but got {}, errors: {:?}", - expected_num_type_check_errs, - errors.len(), - errors - ); - - (interner, main_id) - } -} +pub use errors::{NoMatchingImplFoundError, TypeCheckError}; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/stmt.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/stmt.rs deleted file mode 100644 index 9abd1b34690..00000000000 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/stmt.rs +++ /dev/null @@ -1,395 +0,0 @@ -use acvm::acir::AcirField; -use iter_extended::vecmap; -use noirc_errors::Span; - -use crate::ast::UnaryOp; -use crate::hir_def::expr::{HirExpression, HirIdent, HirLiteral}; -use crate::hir_def::stmt::{ - HirAssignStatement, HirConstrainStatement, HirForStatement, HirLValue, HirLetStatement, - HirPattern, HirStatement, -}; -use crate::hir_def::types::Type; -use crate::node_interner::{DefinitionId, ExprId, StmtId}; - -use super::errors::{Source, TypeCheckError}; -use super::TypeChecker; - -impl<'interner> TypeChecker<'interner> { - /// Type checks a statement and all expressions/statements contained within. - /// - /// All statements have a unit type `()` as their type so the type of the statement - /// is not interesting. Type checking must still be done on statements to ensure any - /// expressions used within them are typed correctly. - pub(crate) fn check_statement(&mut self, stmt_id: &StmtId) -> Type { - match self.interner.statement(stmt_id) { - // Lets lay out a convincing argument that the handling of - // SemiExpressions and Expressions below is correct. - // - // The only time you will get a Semi expression is if - // you have an expression by itself - // - // Example: - // - // 5; or x; or x+a; - // - // In these cases, you cannot even get the expr_id because - // it is not bound to anything. We could therefore. - // - // However since TypeChecking checks the return type of the last statement - // the type checker could in the future incorrectly return the type. - // - // As it stands, this is also impossible because the ret_type function - // does not use the interner to get the type. It returns Unit. - // - // The reason why we still modify the database, is to make sure it is future-proof - HirStatement::Expression(expr_id) => { - return self.check_expression(&expr_id); - } - HirStatement::Semi(expr_id) => { - self.check_expression(&expr_id); - } - HirStatement::Let(let_stmt) => self.check_let_stmt(let_stmt), - HirStatement::Constrain(constrain_stmt) => self.check_constrain_stmt(constrain_stmt), - HirStatement::Assign(assign_stmt) => self.check_assign_stmt(assign_stmt, stmt_id), - HirStatement::For(for_loop) => self.check_for_loop(for_loop), - HirStatement::Comptime(statement) => return self.check_statement(&statement), - HirStatement::Break | HirStatement::Continue | HirStatement::Error => (), - } - Type::Unit - } - - fn check_for_loop(&mut self, for_loop: HirForStatement) { - let start_range_type = self.check_expression(&for_loop.start_range); - let end_range_type = self.check_expression(&for_loop.end_range); - - let start_span = self.interner.expr_span(&for_loop.start_range); - let end_span = self.interner.expr_span(&for_loop.end_range); - - // Check that start range and end range have the same types - let range_span = start_span.merge(end_span); - self.unify(&start_range_type, &end_range_type, || TypeCheckError::TypeMismatch { - expected_typ: start_range_type.to_string(), - expr_typ: end_range_type.to_string(), - expr_span: range_span, - }); - - let expected_type = self.polymorphic_integer(); - - self.unify(&start_range_type, &expected_type, || TypeCheckError::TypeCannotBeUsed { - typ: start_range_type.clone(), - place: "for loop", - span: range_span, - }); - - self.interner.push_definition_type(for_loop.identifier.id, start_range_type); - - self.check_expression(&for_loop.block); - } - - /// Associate a given HirPattern with the given Type, and remember - /// this association in the NodeInterner. - pub(crate) fn bind_pattern(&mut self, pattern: &HirPattern, typ: Type) { - match pattern { - HirPattern::Identifier(ident) => self.interner.push_definition_type(ident.id, typ), - HirPattern::Mutable(pattern, _) => self.bind_pattern(pattern, typ), - HirPattern::Tuple(fields, location) => match typ.follow_bindings() { - Type::Tuple(field_types) if field_types.len() == fields.len() => { - for (field, field_type) in fields.iter().zip(field_types) { - self.bind_pattern(field, field_type); - } - } - Type::Error => (), - other => { - let expected = - Type::Tuple(vecmap(fields, |_| self.interner.next_type_variable())); - - self.errors.push(TypeCheckError::TypeMismatchWithSource { - expected, - actual: other, - span: location.span, - source: Source::Assignment, - }); - } - }, - HirPattern::Struct(struct_type, fields, location) => { - self.unify(struct_type, &typ, || TypeCheckError::TypeMismatchWithSource { - expected: struct_type.clone(), - actual: typ.clone(), - span: location.span, - source: Source::Assignment, - }); - - if let Type::Struct(struct_type, generics) = struct_type.follow_bindings() { - let struct_type = struct_type.borrow(); - - for (field_name, field_pattern) in fields { - if let Some((type_field, _)) = - struct_type.get_field(&field_name.0.contents, &generics) - { - self.bind_pattern(field_pattern, type_field); - } - } - } - } - } - } - - fn check_assign_stmt(&mut self, assign_stmt: HirAssignStatement, stmt_id: &StmtId) { - let expr_type = self.check_expression(&assign_stmt.expression); - let span = self.interner.expr_span(&assign_stmt.expression); - let (lvalue_type, new_lvalue, mutable) = self.check_lvalue(&assign_stmt.lvalue, span); - - if !mutable { - let (name, span) = self.get_lvalue_name_and_span(&assign_stmt.lvalue); - self.errors.push(TypeCheckError::VariableMustBeMutable { name, span }); - } - - // Must push new lvalue to the interner, we've resolved any field indices - self.interner.update_statement(stmt_id, |stmt| match stmt { - HirStatement::Assign(assign) => assign.lvalue = new_lvalue, - _ => unreachable!("statement is known to be assignment"), - }); - - let span = self.interner.expr_span(&assign_stmt.expression); - self.unify_with_coercions(&expr_type, &lvalue_type, assign_stmt.expression, || { - TypeCheckError::TypeMismatchWithSource { - actual: expr_type.clone(), - expected: lvalue_type.clone(), - span, - source: Source::Assignment, - } - }); - } - - fn get_lvalue_name_and_span(&self, lvalue: &HirLValue) -> (String, Span) { - match lvalue { - HirLValue::Ident(name, _) => { - let span = name.location.span; - - if let Some(definition) = self.interner.try_definition(name.id) { - (definition.name.clone(), span) - } else { - ("(undeclared variable)".into(), span) - } - } - HirLValue::MemberAccess { object, .. } => self.get_lvalue_name_and_span(object), - HirLValue::Index { array, .. } => self.get_lvalue_name_and_span(array), - HirLValue::Dereference { lvalue, .. } => self.get_lvalue_name_and_span(lvalue), - } - } - - /// Type check an lvalue - the left hand side of an assignment statement. - fn check_lvalue(&mut self, lvalue: &HirLValue, assign_span: Span) -> (Type, HirLValue, bool) { - match lvalue { - HirLValue::Ident(ident, _) => { - let mut mutable = true; - - let typ = if ident.id == DefinitionId::dummy_id() { - Type::Error - } else { - if let Some(definition) = self.interner.try_definition(ident.id) { - mutable = definition.mutable; - } - - let typ = self.interner.definition_type(ident.id).instantiate(self.interner).0; - typ.follow_bindings() - }; - - (typ.clone(), HirLValue::Ident(ident.clone(), typ), mutable) - } - HirLValue::MemberAccess { object, field_name, location, .. } => { - let (lhs_type, object, mut mutable) = self.check_lvalue(object, assign_span); - let mut object = Box::new(object); - let field_name = field_name.clone(); - - let object_ref = &mut object; - let mutable_ref = &mut mutable; - let location = *location; - - let dereference_lhs = move |_: &mut Self, _, element_type| { - // We must create a temporary value first to move out of object_ref before - // we eventually reassign to it. - let id = DefinitionId::dummy_id(); - let ident = HirIdent::non_trait_method(id, location); - let tmp_value = HirLValue::Ident(ident, Type::Error); - - let lvalue = std::mem::replace(object_ref, Box::new(tmp_value)); - *object_ref = - Box::new(HirLValue::Dereference { lvalue, element_type, location }); - *mutable_ref = true; - }; - - let name = &field_name.0.contents; - let (object_type, field_index) = self - .check_field_access(&lhs_type, name, field_name.span(), Some(dereference_lhs)) - .unwrap_or((Type::Error, 0)); - - let field_index = Some(field_index); - let typ = object_type.clone(); - let lvalue = - HirLValue::MemberAccess { object, field_name, field_index, typ, location }; - (object_type, lvalue, mutable) - } - HirLValue::Index { array, index, location, .. } => { - let index_type = self.check_expression(index); - let expr_span = self.interner.expr_span(index); - let location = *location; - - index_type.unify(&self.polymorphic_integer_or_field(), &mut self.errors, || { - TypeCheckError::TypeMismatch { - expected_typ: "an integer".to_owned(), - expr_typ: index_type.to_string(), - expr_span, - } - }); - - let (mut lvalue_type, mut lvalue, mut mutable) = - self.check_lvalue(array, assign_span); - - // Before we check that the lvalue is an array, try to dereference it as many times - // as needed to unwrap any &mut wrappers. - while let Type::MutableReference(element) = lvalue_type.follow_bindings() { - let element_type = element.as_ref().clone(); - lvalue = - HirLValue::Dereference { lvalue: Box::new(lvalue), element_type, location }; - lvalue_type = *element; - // We know this value to be mutable now since we found an `&mut` - mutable = true; - } - - let typ = match lvalue_type.follow_bindings() { - Type::Array(_, elem_type) => *elem_type, - Type::Slice(elem_type) => *elem_type, - Type::Error => Type::Error, - Type::String(_) => { - let (_lvalue_name, lvalue_span) = self.get_lvalue_name_and_span(&lvalue); - self.errors.push(TypeCheckError::StringIndexAssign { span: lvalue_span }); - Type::Error - } - other => { - // TODO: Need a better span here - self.errors.push(TypeCheckError::TypeMismatch { - expected_typ: "array".to_string(), - expr_typ: other.to_string(), - expr_span: assign_span, - }); - Type::Error - } - }; - - let array = Box::new(lvalue); - (typ.clone(), HirLValue::Index { array, index: *index, typ, location }, mutable) - } - HirLValue::Dereference { lvalue, element_type: _, location } => { - let (reference_type, lvalue, _) = self.check_lvalue(lvalue, assign_span); - let lvalue = Box::new(lvalue); - let location = *location; - - let element_type = Type::type_variable(self.interner.next_type_variable_id()); - let expected_type = Type::MutableReference(Box::new(element_type.clone())); - - self.unify(&reference_type, &expected_type, || TypeCheckError::TypeMismatch { - expected_typ: expected_type.to_string(), - expr_typ: reference_type.to_string(), - expr_span: assign_span, - }); - - // Dereferences are always mutable since we already type checked against a &mut T - ( - element_type.clone(), - HirLValue::Dereference { lvalue, element_type, location }, - true, - ) - } - } - } - - fn check_let_stmt(&mut self, let_stmt: HirLetStatement) { - let resolved_type = self.check_declaration(let_stmt.expression, let_stmt.r#type); - - // Set the type of the pattern to be equal to the annotated type - self.bind_pattern(&let_stmt.pattern, resolved_type); - } - - fn check_constrain_stmt(&mut self, stmt: HirConstrainStatement) { - let expr_type = self.check_expression(&stmt.0); - let expr_span = self.interner.expr_span(&stmt.0); - - // Must type check the assertion message expression so that we instantiate bindings - stmt.2.map(|assert_msg_expr| self.check_expression(&assert_msg_expr)); - - self.unify(&expr_type, &Type::Bool, || TypeCheckError::TypeMismatch { - expr_typ: expr_type.to_string(), - expected_typ: Type::Bool.to_string(), - expr_span, - }); - } - - /// All declaration statements check that the user specified type(UST) is equal to the - /// expression on the RHS, unless the UST is unspecified in which case - /// the type of the declaration is inferred to match the RHS. - fn check_declaration(&mut self, rhs_expr: ExprId, annotated_type: Type) -> Type { - // Type check the expression on the RHS - let expr_type = self.check_expression(&rhs_expr); - - // First check if the LHS is unspecified - // If so, then we give it the same type as the expression - if annotated_type != Type::Error { - // Now check if LHS is the same type as the RHS - // Importantly, we do not coerce any types implicitly - let expr_span = self.interner.expr_span(&rhs_expr); - - self.unify_with_coercions(&expr_type, &annotated_type, rhs_expr, || { - TypeCheckError::TypeMismatch { - expected_typ: annotated_type.to_string(), - expr_typ: expr_type.to_string(), - expr_span, - } - }); - if annotated_type.is_unsigned() { - self.lint_overflowing_uint(&rhs_expr, &annotated_type); - } - annotated_type - } else { - expr_type - } - } - - /// Check if an assignment is overflowing with respect to `annotated_type` - /// in a declaration statement where `annotated_type` is an unsigned integer - fn lint_overflowing_uint(&mut self, rhs_expr: &ExprId, annotated_type: &Type) { - let expr = self.interner.expression(rhs_expr); - let span = self.interner.expr_span(rhs_expr); - match expr { - HirExpression::Literal(HirLiteral::Integer(value, false)) => { - let v = value.to_u128(); - if let Type::Integer(_, bit_count) = annotated_type { - let bit_count: u32 = (*bit_count).into(); - let max = 1 << bit_count; - if v >= max { - self.errors.push(TypeCheckError::OverflowingAssignment { - expr: -value, - ty: annotated_type.clone(), - range: format!("0..={}", max - 1), - span, - }); - }; - }; - } - HirExpression::Prefix(expr) => { - self.lint_overflowing_uint(&expr.rhs, annotated_type); - if matches!(expr.operator, UnaryOp::Minus) { - self.errors.push(TypeCheckError::InvalidUnaryOp { - kind: "annotated_type".to_string(), - span, - }); - } - } - HirExpression::Infix(expr) => { - self.lint_overflowing_uint(&expr.lhs, annotated_type); - self.lint_overflowing_uint(&expr.rhs, annotated_type); - } - _ => {} - } - } -} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/expr.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/expr.rs index 8de4f118774..d498f2e1cfc 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/expr.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/expr.rs @@ -40,13 +40,6 @@ pub enum HirExpression { Error, } -impl HirExpression { - /// Returns an empty block expression - pub const fn empty_block() -> HirExpression { - HirExpression::Block(HirBlockExpression { statements: vec![] }) - } -} - /// Corresponds to a variable in the source code #[derive(Debug, Clone)] pub struct HirIdent { @@ -124,6 +117,10 @@ pub enum HirArrayLiteral { pub struct HirPrefixExpression { pub operator: UnaryOp, pub rhs: ExprId, + + /// The trait method id for the operator trait method that corresponds to this operator, + /// if such a trait exists (for example, there's no trait for the dereference operator). + pub trait_method_id: Option, } #[derive(Debug, Clone)] diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/function.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/function.rs index fa8bb55abee..dc563a5f65f 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/function.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/function.rs @@ -6,30 +6,34 @@ use super::stmt::HirPattern; use super::traits::TraitConstraint; use crate::ast::{FunctionKind, FunctionReturnType, Visibility}; use crate::graph::CrateId; -use crate::macros_api::BlockExpression; +use crate::macros_api::{BlockExpression, StructId}; use crate::node_interner::{ExprId, NodeInterner, TraitImplId}; use crate::{ResolvedGeneric, Type}; -/// A Hir function is a block expression -/// with a list of statements +/// A Hir function is a block expression with a list of statements. +/// If the function has yet to be resolved, the body starts off empty (None). #[derive(Debug, Clone)] -pub struct HirFunction(ExprId); +pub struct HirFunction(Option); impl HirFunction { pub fn empty() -> HirFunction { - HirFunction(ExprId::empty_block_id()) + HirFunction(None) } pub const fn unchecked_from_expr(expr_id: ExprId) -> HirFunction { - HirFunction(expr_id) + HirFunction(Some(expr_id)) } - pub const fn as_expr(&self) -> ExprId { + pub fn as_expr(&self) -> ExprId { + self.0.expect("Function has yet to be elaborated, cannot get an ExprId of its body!") + } + + pub fn try_as_expr(&self) -> Option { self.0 } pub fn block(&self, interner: &NodeInterner) -> HirBlockExpression { - match interner.expression(&self.0) { + match interner.expression(&self.as_expr()) { HirExpression::Block(block_expr) => block_expr, _ => unreachable!("ice: functions can only be block expressions"), } @@ -126,6 +130,9 @@ pub struct FuncMeta { pub trait_constraints: Vec, + /// The struct this function belongs to, if any + pub struct_id: Option, + /// The trait impl this function belongs to, if any pub trait_impl: Option, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types.rs index 6777c0d1c79..677915776e9 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types.rs @@ -114,79 +114,6 @@ pub enum Type { Error, } -impl Type { - /// Returns the number of field elements required to represent the type once encoded. - pub fn field_count(&self) -> u32 { - match self { - Type::FieldElement | Type::Integer { .. } | Type::Bool => 1, - Type::Array(size, typ) => { - let length = size - .evaluate_to_u32() - .expect("Cannot have variable sized arrays as a parameter to main"); - let typ = typ.as_ref(); - length * typ.field_count() - } - Type::Struct(def, args) => { - let struct_type = def.borrow(); - let fields = struct_type.get_fields(args); - fields.iter().fold(0, |acc, (_, field_type)| acc + field_type.field_count()) - } - Type::Alias(def, generics) => def.borrow().get_type(generics).field_count(), - Type::Tuple(fields) => { - fields.iter().fold(0, |acc, field_typ| acc + field_typ.field_count()) - } - Type::String(size) => size - .evaluate_to_u32() - .expect("Cannot have variable sized strings as a parameter to main"), - Type::FmtString(_, _) - | Type::Unit - | Type::TypeVariable(_, _) - | Type::TraitAsType(..) - | Type::NamedGeneric(_, _, _) - | Type::Function(_, _, _) - | Type::MutableReference(_) - | Type::Forall(_, _) - | Type::Constant(_) - | Type::Quoted(_) - | Type::Slice(_) - | Type::Error => unreachable!("This type cannot exist as a parameter to main"), - } - } - - pub(crate) fn is_nested_slice(&self) -> bool { - match self { - Type::Slice(elem) => elem.as_ref().contains_slice(), - Type::Array(_, elem) => elem.as_ref().contains_slice(), - Type::Alias(alias, generics) => alias.borrow().get_type(generics).is_nested_slice(), - _ => false, - } - } - - pub(crate) fn contains_slice(&self) -> bool { - match self { - Type::Slice(_) => true, - Type::Struct(struct_typ, generics) => { - let fields = struct_typ.borrow().get_fields(generics); - for field in fields.iter() { - if field.1.contains_slice() { - return true; - } - } - false - } - Type::Tuple(types) => { - for typ in types.iter() { - if typ.contains_slice() { - return true; - } - } - false - } - _ => false, - } - } -} - /// A Kind is the type of a Type. These are used since only certain kinds of types are allowed in /// certain positions. /// @@ -215,6 +142,10 @@ pub enum QuotedType { TopLevelItem, Type, StructDefinition, + TraitConstraint, + TraitDefinition, + FunctionDefinition, + Module, } /// A list of TypeVariableIds to bind to a type. Storing the @@ -342,6 +273,11 @@ impl StructType { vecmap(&self.fields, |(name, typ)| (name.0.contents.clone(), typ.clone())) } + /// Returns the field at the given index. Panics if no field exists at the given index. + pub fn field_at(&self, index: usize) -> &(Ident, Type) { + &self.fields[index] + } + pub fn field_names(&self) -> BTreeSet { self.fields.iter().map(|(name, _)| name.clone()).collect() } @@ -589,7 +525,7 @@ impl TypeVariable { /// TypeBindings are the mutable insides of a TypeVariable. /// They are either bound to some type, or are unbound. -#[derive(Clone, PartialEq, Eq, Hash)] +#[derive(Clone, PartialEq, Eq, Hash, Debug)] pub enum TypeBinding { Bound(Type), Unbound(TypeVariableId), @@ -605,6 +541,156 @@ impl TypeBinding { #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct TypeVariableId(pub usize); +impl std::fmt::Display for Type { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Type::FieldElement => { + write!(f, "Field") + } + Type::Array(len, typ) => { + write!(f, "[{typ}; {len}]") + } + Type::Slice(typ) => { + write!(f, "[{typ}]") + } + Type::Integer(sign, num_bits) => match sign { + Signedness::Signed => write!(f, "i{num_bits}"), + Signedness::Unsigned => write!(f, "u{num_bits}"), + }, + Type::TypeVariable(var, TypeVariableKind::Normal) => write!(f, "{}", var.borrow()), + Type::TypeVariable(binding, TypeVariableKind::Integer) => { + if let TypeBinding::Unbound(_) = &*binding.borrow() { + write!(f, "{}", Type::default_int_type()) + } else { + write!(f, "{}", binding.borrow()) + } + } + Type::TypeVariable(binding, TypeVariableKind::IntegerOrField) => { + if let TypeBinding::Unbound(_) = &*binding.borrow() { + // Show a Field by default if this TypeVariableKind::IntegerOrField is unbound, since that is + // what they bind to by default anyway. It is less confusing than displaying it + // as a generic. + write!(f, "Field") + } else { + write!(f, "{}", binding.borrow()) + } + } + Type::TypeVariable(binding, TypeVariableKind::Constant(n)) => { + if let TypeBinding::Unbound(_) = &*binding.borrow() { + // TypeVariableKind::Constant(n) binds to Type::Constant(n) by default, so just show that. + write!(f, "{n}") + } else { + write!(f, "{}", binding.borrow()) + } + } + Type::Struct(s, args) => { + let args = vecmap(args, |arg| arg.to_string()); + if args.is_empty() { + write!(f, "{}", s.borrow()) + } else { + write!(f, "{}<{}>", s.borrow(), args.join(", ")) + } + } + Type::Alias(alias, args) => { + let args = vecmap(args, |arg| arg.to_string()); + if args.is_empty() { + write!(f, "{}", alias.borrow()) + } else { + write!(f, "{}<{}>", alias.borrow(), args.join(", ")) + } + } + Type::TraitAsType(_id, name, generics) => { + write!(f, "impl {}", name)?; + if !generics.is_empty() { + let generics = vecmap(generics, ToString::to_string).join(", "); + write!(f, "<{generics}>")?; + } + Ok(()) + } + Type::Tuple(elements) => { + let elements = vecmap(elements, ToString::to_string); + write!(f, "({})", elements.join(", ")) + } + Type::Bool => write!(f, "bool"), + Type::String(len) => write!(f, "str<{len}>"), + Type::FmtString(len, elements) => { + write!(f, "fmtstr<{len}, {elements}>") + } + Type::Unit => write!(f, "()"), + Type::Error => write!(f, "error"), + Type::NamedGeneric(binding, name, _) => match &*binding.borrow() { + TypeBinding::Bound(binding) => binding.fmt(f), + TypeBinding::Unbound(_) if name.is_empty() => write!(f, "_"), + TypeBinding::Unbound(_) => write!(f, "{name}"), + }, + Type::Constant(x) => x.fmt(f), + Type::Forall(typevars, typ) => { + let typevars = vecmap(typevars, |var| var.id().to_string()); + write!(f, "forall {}. {}", typevars.join(" "), typ) + } + Type::Function(args, ret, env) => { + let closure_env_text = match **env { + Type::Unit => "".to_string(), + _ => format!(" with env {env}"), + }; + + let args = vecmap(args.iter(), ToString::to_string); + + write!(f, "fn({}) -> {ret}{closure_env_text}", args.join(", ")) + } + Type::MutableReference(element) => { + write!(f, "&mut {element}") + } + Type::Quoted(quoted) => write!(f, "{}", quoted), + } + } +} + +impl std::fmt::Display for BinaryTypeOperator { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BinaryTypeOperator::Addition => write!(f, "+"), + BinaryTypeOperator::Subtraction => write!(f, "-"), + BinaryTypeOperator::Multiplication => write!(f, "*"), + BinaryTypeOperator::Division => write!(f, "/"), + BinaryTypeOperator::Modulo => write!(f, "%"), + } + } +} + +impl std::fmt::Display for TypeVariableId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "_") + } +} + +impl std::fmt::Display for TypeBinding { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TypeBinding::Bound(typ) => typ.fmt(f), + TypeBinding::Unbound(id) => id.fmt(f), + } + } +} + +impl std::fmt::Display for QuotedType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + QuotedType::Expr => write!(f, "Expr"), + QuotedType::Quoted => write!(f, "Quoted"), + QuotedType::TopLevelItem => write!(f, "TopLevelItem"), + QuotedType::Type => write!(f, "Type"), + QuotedType::StructDefinition => write!(f, "StructDefinition"), + QuotedType::TraitDefinition => write!(f, "TraitDefinition"), + QuotedType::TraitConstraint => write!(f, "TraitConstraint"), + QuotedType::FunctionDefinition => write!(f, "FunctionDefinition"), + QuotedType::Module => write!(f, "Module"), + } + } +} + +pub struct UnificationError; + impl Type { pub fn default_int_or_field_type() -> Type { Type::FieldElement @@ -1039,155 +1125,78 @@ impl Type { // | Type::Error => Kind::Normal, // } // } -} -impl std::fmt::Display for Type { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + /// Returns the number of field elements required to represent the type once encoded. + pub fn field_count(&self) -> u32 { match self { - Type::FieldElement => { - write!(f, "Field") - } - Type::Array(len, typ) => { - write!(f, "[{typ}; {len}]") - } - Type::Slice(typ) => { - write!(f, "[{typ}]") - } - Type::Integer(sign, num_bits) => match sign { - Signedness::Signed => write!(f, "i{num_bits}"), - Signedness::Unsigned => write!(f, "u{num_bits}"), - }, - Type::TypeVariable(var, TypeVariableKind::Normal) => write!(f, "{}", var.borrow()), - Type::TypeVariable(binding, TypeVariableKind::Integer) => { - if let TypeBinding::Unbound(_) = &*binding.borrow() { - write!(f, "{}", Type::default_int_type()) - } else { - write!(f, "{}", binding.borrow()) - } - } - Type::TypeVariable(binding, TypeVariableKind::IntegerOrField) => { - if let TypeBinding::Unbound(_) = &*binding.borrow() { - // Show a Field by default if this TypeVariableKind::IntegerOrField is unbound, since that is - // what they bind to by default anyway. It is less confusing than displaying it - // as a generic. - write!(f, "Field") - } else { - write!(f, "{}", binding.borrow()) - } - } - Type::TypeVariable(binding, TypeVariableKind::Constant(n)) => { - if let TypeBinding::Unbound(_) = &*binding.borrow() { - // TypeVariableKind::Constant(n) binds to Type::Constant(n) by default, so just show that. - write!(f, "{n}") - } else { - write!(f, "{}", binding.borrow()) - } - } - Type::Struct(s, args) => { - let args = vecmap(args, |arg| arg.to_string()); - if args.is_empty() { - write!(f, "{}", s.borrow()) - } else { - write!(f, "{}<{}>", s.borrow(), args.join(", ")) - } - } - Type::Alias(alias, args) => { - let args = vecmap(args, |arg| arg.to_string()); - if args.is_empty() { - write!(f, "{}", alias.borrow()) - } else { - write!(f, "{}<{}>", alias.borrow(), args.join(", ")) - } - } - Type::TraitAsType(_id, name, generics) => { - write!(f, "impl {}", name)?; - if !generics.is_empty() { - let generics = vecmap(generics, ToString::to_string).join(", "); - write!(f, "<{generics}>")?; - } - Ok(()) - } - Type::Tuple(elements) => { - let elements = vecmap(elements, ToString::to_string); - write!(f, "({})", elements.join(", ")) - } - Type::Bool => write!(f, "bool"), - Type::String(len) => write!(f, "str<{len}>"), - Type::FmtString(len, elements) => { - write!(f, "fmtstr<{len}, {elements}>") - } - Type::Unit => write!(f, "()"), - Type::Error => write!(f, "error"), - Type::NamedGeneric(binding, name, _) => match &*binding.borrow() { - TypeBinding::Bound(binding) => binding.fmt(f), - TypeBinding::Unbound(_) if name.is_empty() => write!(f, "_"), - TypeBinding::Unbound(_) => write!(f, "{name}"), - }, - Type::Constant(x) => x.fmt(f), - Type::Forall(typevars, typ) => { - let typevars = vecmap(typevars, |var| var.id().to_string()); - write!(f, "forall {}. {}", typevars.join(" "), typ) + Type::FieldElement | Type::Integer { .. } | Type::Bool => 1, + Type::Array(size, typ) => { + let length = size + .evaluate_to_u32() + .expect("Cannot have variable sized arrays as a parameter to main"); + let typ = typ.as_ref(); + length * typ.field_count() } - Type::Function(args, ret, env) => { - let closure_env_text = match **env { - Type::Unit => "".to_string(), - _ => format!(" with env {env}"), - }; - - let args = vecmap(args.iter(), ToString::to_string); - - write!(f, "fn({}) -> {ret}{closure_env_text}", args.join(", ")) + Type::Struct(def, args) => { + let struct_type = def.borrow(); + let fields = struct_type.get_fields(args); + fields.iter().fold(0, |acc, (_, field_type)| acc + field_type.field_count()) } - Type::MutableReference(element) => { - write!(f, "&mut {element}") + Type::Alias(def, generics) => def.borrow().get_type(generics).field_count(), + Type::Tuple(fields) => { + fields.iter().fold(0, |acc, field_typ| acc + field_typ.field_count()) } - Type::Quoted(quoted) => write!(f, "{}", quoted), - } - } -} - -impl std::fmt::Display for BinaryTypeOperator { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - BinaryTypeOperator::Addition => write!(f, "+"), - BinaryTypeOperator::Subtraction => write!(f, "-"), - BinaryTypeOperator::Multiplication => write!(f, "*"), - BinaryTypeOperator::Division => write!(f, "/"), - BinaryTypeOperator::Modulo => write!(f, "%"), + Type::String(size) => size + .evaluate_to_u32() + .expect("Cannot have variable sized strings as a parameter to main"), + Type::FmtString(_, _) + | Type::Unit + | Type::TypeVariable(_, _) + | Type::TraitAsType(..) + | Type::NamedGeneric(_, _, _) + | Type::Function(_, _, _) + | Type::MutableReference(_) + | Type::Forall(_, _) + | Type::Constant(_) + | Type::Quoted(_) + | Type::Slice(_) + | Type::Error => unreachable!("This type cannot exist as a parameter to main"), } } -} - -impl std::fmt::Display for TypeVariableId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "_") - } -} -impl std::fmt::Display for TypeBinding { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + pub(crate) fn is_nested_slice(&self) -> bool { match self { - TypeBinding::Bound(typ) => typ.fmt(f), - TypeBinding::Unbound(id) => id.fmt(f), + Type::Slice(elem) => elem.as_ref().contains_slice(), + Type::Array(_, elem) => elem.as_ref().contains_slice(), + Type::Alias(alias, generics) => alias.borrow().get_type(generics).is_nested_slice(), + _ => false, } } -} -impl std::fmt::Display for QuotedType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + pub(crate) fn contains_slice(&self) -> bool { match self { - QuotedType::Expr => write!(f, "Expr"), - QuotedType::Quoted => write!(f, "Quoted"), - QuotedType::TopLevelItem => write!(f, "TopLevelItem"), - QuotedType::Type => write!(f, "Type"), - QuotedType::StructDefinition => write!(f, "StructDefinition"), + Type::Slice(_) => true, + Type::Struct(struct_typ, generics) => { + let fields = struct_typ.borrow().get_fields(generics); + for field in fields.iter() { + if field.1.contains_slice() { + return true; + } + } + false + } + Type::Tuple(types) => { + for typ in types.iter() { + if typ.contains_slice() { + return true; + } + } + false + } + _ => false, } } -} -pub struct UnificationError; - -impl Type { /// Try to bind a MaybeConstant variable to self, succeeding if self is a Constant, /// MaybeConstant, or type variable. If successful, the binding is placed in the /// given TypeBindings map rather than linked immediately. @@ -2002,6 +2011,83 @@ impl Type { pub fn from_generics(generics: &GenericTypeVars) -> Vec { vecmap(generics, |var| Type::TypeVariable(var.clone(), TypeVariableKind::Normal)) } + + /// Replace any `Type::NamedGeneric` in this type with a `Type::TypeVariable` + /// using to the same inner `TypeVariable`. This is used during monomorphization + /// to bind to named generics since they are unbindable during type checking. + pub fn replace_named_generics_with_type_variables(&mut self) { + match self { + Type::FieldElement + | Type::Constant(_) + | Type::Integer(_, _) + | Type::Bool + | Type::Unit + | Type::Error + | Type::Quoted(_) => (), + + Type::Array(len, elem) => { + len.replace_named_generics_with_type_variables(); + elem.replace_named_generics_with_type_variables(); + } + + Type::Slice(elem) => elem.replace_named_generics_with_type_variables(), + Type::String(len) => len.replace_named_generics_with_type_variables(), + Type::FmtString(len, captures) => { + len.replace_named_generics_with_type_variables(); + captures.replace_named_generics_with_type_variables(); + } + Type::Tuple(fields) => { + for field in fields { + field.replace_named_generics_with_type_variables(); + } + } + Type::Struct(_, generics) => { + for generic in generics { + generic.replace_named_generics_with_type_variables(); + } + } + Type::Alias(alias, generics) => { + let mut typ = alias.borrow().get_type(generics); + typ.replace_named_generics_with_type_variables(); + *self = typ; + } + Type::TypeVariable(var, _) => { + let var = var.borrow(); + if let TypeBinding::Bound(binding) = &*var { + let mut binding = binding.clone(); + drop(var); + binding.replace_named_generics_with_type_variables(); + *self = binding; + } + } + Type::TraitAsType(_, _, generics) => { + for generic in generics { + generic.replace_named_generics_with_type_variables(); + } + } + Type::NamedGeneric(var, _, _) => { + let type_binding = var.borrow(); + if let TypeBinding::Bound(binding) = &*type_binding { + let mut binding = binding.clone(); + drop(type_binding); + binding.replace_named_generics_with_type_variables(); + *self = binding; + } else { + drop(type_binding); + *self = Type::TypeVariable(var.clone(), TypeVariableKind::Normal); + } + } + Type::Function(args, ret, env) => { + for arg in args { + arg.replace_named_generics_with_type_variables(); + } + ret.replace_named_generics_with_type_variables(); + env.replace_named_generics_with_type_variables(); + } + Type::MutableReference(elem) => elem.replace_named_generics_with_type_variables(), + Type::Forall(_, typ) => typ.replace_named_generics_with_type_variables(), + } + } } /// Wraps a given `expression` in `expression.as_slice()` @@ -2198,10 +2284,10 @@ impl std::fmt::Debug for Type { Type::Error => write!(f, "error"), Type::NamedGeneric(binding, name, kind) => match kind { Kind::Normal => { - write!(f, "{} -> {:?}", name, binding) + write!(f, "{}{:?}", name, binding) } Kind::Numeric(typ) => { - write!(f, "({} : {}) -> {:?}", name, typ, binding) + write!(f, "({} : {}){:?}", name, typ, binding) } }, Type::Constant(x) => x.fmt(f), diff --git a/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs b/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs index 41de13fb17e..c6a1d44f26b 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs @@ -895,12 +895,14 @@ pub enum Keyword { Fn, For, FormatString, + FunctionDefinition, Global, If, Impl, In, Let, Mod, + Module, Mut, Pub, Quoted, @@ -908,12 +910,14 @@ pub enum Keyword { ReturnData, String, Struct, + StructDefinition, Super, TopLevelItem, Trait, + TraitConstraint, + TraitDefinition, Type, TypeType, - StructDefinition, Unchecked, Unconstrained, Use, @@ -943,12 +947,14 @@ impl fmt::Display for Keyword { Keyword::Fn => write!(f, "fn"), Keyword::For => write!(f, "for"), Keyword::FormatString => write!(f, "fmtstr"), + Keyword::FunctionDefinition => write!(f, "FunctionDefinition"), Keyword::Global => write!(f, "global"), Keyword::If => write!(f, "if"), Keyword::Impl => write!(f, "impl"), Keyword::In => write!(f, "in"), Keyword::Let => write!(f, "let"), Keyword::Mod => write!(f, "mod"), + Keyword::Module => write!(f, "Module"), Keyword::Mut => write!(f, "mut"), Keyword::Pub => write!(f, "pub"), Keyword::Quoted => write!(f, "Quoted"), @@ -956,12 +962,14 @@ impl fmt::Display for Keyword { Keyword::ReturnData => write!(f, "return_data"), Keyword::String => write!(f, "str"), Keyword::Struct => write!(f, "struct"), + Keyword::StructDefinition => write!(f, "StructDefinition"), Keyword::Super => write!(f, "super"), Keyword::TopLevelItem => write!(f, "TopLevelItem"), Keyword::Trait => write!(f, "trait"), + Keyword::TraitConstraint => write!(f, "TraitConstraint"), + Keyword::TraitDefinition => write!(f, "TraitDefinition"), Keyword::Type => write!(f, "type"), Keyword::TypeType => write!(f, "Type"), - Keyword::StructDefinition => write!(f, "StructDefinition"), Keyword::Unchecked => write!(f, "unchecked"), Keyword::Unconstrained => write!(f, "unconstrained"), Keyword::Use => write!(f, "use"), @@ -994,12 +1002,14 @@ impl Keyword { "fn" => Keyword::Fn, "for" => Keyword::For, "fmtstr" => Keyword::FormatString, + "FunctionDefinition" => Keyword::FunctionDefinition, "global" => Keyword::Global, "if" => Keyword::If, "impl" => Keyword::Impl, "in" => Keyword::In, "let" => Keyword::Let, "mod" => Keyword::Mod, + "Module" => Keyword::Module, "mut" => Keyword::Mut, "pub" => Keyword::Pub, "Quoted" => Keyword::Quoted, @@ -1010,6 +1020,8 @@ impl Keyword { "super" => Keyword::Super, "TopLevelItem" => Keyword::TopLevelItem, "trait" => Keyword::Trait, + "TraitConstraint" => Keyword::TraitConstraint, + "TraitDefinition" => Keyword::TraitDefinition, "type" => Keyword::Type, "Type" => Keyword::TypeType, "StructDefinition" => Keyword::StructDefinition, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/locations.rs b/noir/noir-repo/compiler/noirc_frontend/src/locations.rs index fcaef0a8dd6..0ba74e22781 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/locations.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/locations.rs @@ -3,7 +3,11 @@ use noirc_errors::Location; use rangemap::RangeMap; use rustc_hash::FxHashMap; -use crate::{macros_api::NodeInterner, node_interner::ReferenceId}; +use crate::{ + hir::def_map::{ModuleDefId, ModuleId}, + macros_api::{NodeInterner, StructId}, + node_interner::{DefinitionId, FuncId, GlobalId, ReferenceId, TraitId, TypeAliasId}, +}; use petgraph::prelude::NodeIndex as PetGraphIndex; #[derive(Debug, Default)] @@ -13,7 +17,7 @@ pub(crate) struct LocationIndices { impl LocationIndices { pub(crate) fn add_location(&mut self, location: Location, node_index: PetGraphIndex) { - // Some location spans are empty: maybe they are from ficticious nodes? + // Some location spans are empty: maybe they are from fictitious nodes? if location.span.start() == location.span.end() { return; } @@ -31,13 +35,18 @@ impl LocationIndices { impl NodeInterner { pub fn reference_location(&self, reference: ReferenceId) -> Location { match reference { - ReferenceId::Module(id) => self.module_location(&id), + ReferenceId::Module(id) => self.module_attributes(&id).location, ReferenceId::Function(id) => self.function_modifiers(&id).name_location, ReferenceId::Struct(id) => { let struct_type = self.get_struct(id); let struct_type = struct_type.borrow(); Location::new(struct_type.name.span(), struct_type.location.file) } + ReferenceId::StructMember(id, field_index) => { + let struct_type = self.get_struct(id); + let struct_type = struct_type.borrow(); + Location::new(struct_type.field_at(field_index).0.span(), struct_type.location.file) + } ReferenceId::Trait(id) => { let trait_type = self.get_trait(id); Location::new(trait_type.name.span(), trait_type.location.file) @@ -53,11 +62,97 @@ impl NodeInterner { } } - pub(crate) fn add_reference(&mut self, referenced: ReferenceId, reference: ReferenceId) { + pub fn reference_module(&self, reference: ReferenceId) -> Option<&ModuleId> { + self.reference_modules.get(&reference) + } + + pub(crate) fn add_module_def_id_reference( + &mut self, + def_id: ModuleDefId, + location: Location, + is_self_type: bool, + ) { + match def_id { + ModuleDefId::ModuleId(module_id) => { + self.add_module_reference(module_id, location); + } + ModuleDefId::FunctionId(func_id) => { + self.add_function_reference(func_id, location); + } + ModuleDefId::TypeId(struct_id) => { + self.add_struct_reference(struct_id, location, is_self_type); + } + ModuleDefId::TraitId(trait_id) => { + self.add_trait_reference(trait_id, location, is_self_type); + } + ModuleDefId::TypeAliasId(type_alias_id) => { + self.add_alias_reference(type_alias_id, location); + } + ModuleDefId::GlobalId(global_id) => { + self.add_global_reference(global_id, location); + } + }; + } + + pub(crate) fn add_module_reference(&mut self, id: ModuleId, location: Location) { + self.add_reference(ReferenceId::Module(id), location, false); + } + + pub(crate) fn add_struct_reference( + &mut self, + id: StructId, + location: Location, + is_self_type: bool, + ) { + self.add_reference(ReferenceId::Struct(id), location, is_self_type); + } + + pub(crate) fn add_struct_member_reference( + &mut self, + id: StructId, + member_index: usize, + location: Location, + ) { + self.add_reference(ReferenceId::StructMember(id, member_index), location, false); + } + + pub(crate) fn add_trait_reference( + &mut self, + id: TraitId, + location: Location, + is_self_type: bool, + ) { + self.add_reference(ReferenceId::Trait(id), location, is_self_type); + } + + pub(crate) fn add_alias_reference(&mut self, id: TypeAliasId, location: Location) { + self.add_reference(ReferenceId::Alias(id), location, false); + } + + pub(crate) fn add_function_reference(&mut self, id: FuncId, location: Location) { + self.add_reference(ReferenceId::Function(id), location, false); + } + + pub(crate) fn add_global_reference(&mut self, id: GlobalId, location: Location) { + self.add_reference(ReferenceId::Global(id), location, false); + } + + pub(crate) fn add_local_reference(&mut self, id: DefinitionId, location: Location) { + self.add_reference(ReferenceId::Local(id), location, false); + } + + pub(crate) fn add_reference( + &mut self, + referenced: ReferenceId, + location: Location, + is_self_type: bool, + ) { if !self.track_references { return; } + let reference = ReferenceId::Reference(location, is_self_type); + let referenced_index = self.get_or_insert_reference(referenced); let reference_location = self.reference_location(reference); let reference_index = self.reference_graph.add_node(reference); @@ -66,7 +161,11 @@ impl NodeInterner { self.location_indices.add_location(reference_location, reference_index); } - pub(crate) fn add_definition_location(&mut self, referenced: ReferenceId) { + pub(crate) fn add_definition_location( + &mut self, + referenced: ReferenceId, + module_id: Option, + ) { if !self.track_references { return; } @@ -74,6 +173,9 @@ impl NodeInterner { let referenced_index = self.get_or_insert_reference(referenced); let referenced_location = self.reference_location(referenced); self.location_indices.add_location(referenced_location, referenced_index); + if let Some(module_id) = module_id { + self.reference_modules.insert(referenced, module_id); + } } #[tracing::instrument(skip(self), ret)] @@ -105,7 +207,7 @@ impl NodeInterner { // Starting at the given location, find the node referenced by it. Then, gather // all locations that reference that node, and return all of them - // (the references and optionally the referenced node if `include_referencedd` is true). + // (the references and optionally the referenced node if `include_referenced` is true). // If `include_self_type_name` is true, references where "Self" is written are returned, // otherwise they are not. // Returns `None` if the location is not known to this interner. @@ -115,14 +217,8 @@ impl NodeInterner { include_referenced: bool, include_self_type_name: bool, ) -> Option> { - let node_index = self.location_indices.get_node_from_location(location)?; - - let reference_node = self.reference_graph[node_index]; - let referenced_node_index = if let ReferenceId::Reference(_, _) = reference_node { - self.referenced_index(node_index)? - } else { - node_index - }; + let referenced_node = self.find_referenced(location)?; + let referenced_node_index = self.reference_graph_indices[&referenced_node]; let found_locations = self.find_all_references_for_index( referenced_node_index, @@ -133,6 +229,19 @@ impl NodeInterner { Some(found_locations) } + // Returns the `ReferenceId` that is referenced by the given location, if any. + pub fn find_referenced(&self, location: Location) -> Option { + let node_index = self.location_indices.get_node_from_location(location)?; + + let reference_node = self.reference_graph[node_index]; + if let ReferenceId::Reference(_, _) = reference_node { + let node_index = self.referenced_index(node_index)?; + Some(self.reference_graph[node_index]) + } else { + Some(reference_node) + } + } + // Given a referenced node index, find all references to it and return their locations, optionally together // with the reference node's location if `include_referenced` is true. // If `include_self_type_name` is true, references where "Self" is written are returned, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs index fabfc74b901..a46f32e3094 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -21,7 +21,7 @@ use crate::{ types, }, node_interner::{self, DefinitionKind, NodeInterner, StmtId, TraitImplKind, TraitMethodId}, - Type, TypeBinding, TypeBindings, TypeVariable, TypeVariableKind, + Type, TypeBinding, TypeBindings, }; use acvm::{acir::AcirField, FieldElement}; use iter_extended::{btree_map, try_vecmap, vecmap}; @@ -444,13 +444,26 @@ impl<'interner> Monomorphizer<'interner> { HirExpression::Block(block) => self.block(block.statements)?, HirExpression::Prefix(prefix) => { + let rhs = self.expr(prefix.rhs)?; let location = self.interner.expr_location(&expr); - ast::Expression::Unary(ast::Unary { - operator: prefix.operator, - rhs: Box::new(self.expr(prefix.rhs)?), - result_type: Self::convert_type(&self.interner.id_type(expr), location)?, - location, - }) + + if self.interner.get_selected_impl_for_expression(expr).is_some() { + // If an impl was selected for this prefix operator, replace it + // with a method call to the appropriate trait impl method. + let (function_type, ret) = + self.interner.get_prefix_operator_type(expr, prefix.rhs); + + let method = prefix + .trait_method_id + .expect("ice: missing trait method if when impl was found"); + let func = self.resolve_trait_method_expr(expr, function_type, method)?; + self.create_prefix_operator_impl_call(func, rhs, ret, location)? + } else { + let operator = prefix.operator; + let rhs = Box::new(rhs); + let result_type = Self::convert_type(&self.interner.id_type(expr), location)?; + ast::Expression::Unary(ast::Unary { operator, rhs, result_type, location }) + } } HirExpression::Infix(infix) => { @@ -462,11 +475,12 @@ impl<'interner> Monomorphizer<'interner> { // If an impl was selected for this infix operator, replace it // with a method call to the appropriate trait impl method. let (function_type, ret) = - self.interner.get_operator_type(infix.lhs, operator, expr); + self.interner.get_infix_operator_type(infix.lhs, operator, expr); let method = infix.trait_method_id; let func = self.resolve_trait_method_expr(expr, function_type, method)?; - self.create_operator_impl_call(func, lhs, infix.operator, rhs, ret, location)? + let operator = infix.operator; + self.create_infix_operator_impl_call(func, lhs, operator, rhs, ret, location)? } else { let lhs = Box::new(lhs); let rhs = Box::new(rhs); @@ -1651,12 +1665,12 @@ impl<'interner> Monomorphizer<'interner> { }) } - /// Call an operator overloading method for the given operator. + /// Call an infix operator overloading method for the given operator. /// This function handles the special cases some operators have which don't map /// 1 to 1 onto their operator function. For example: != requires a negation on /// the result of its `eq` method, and the comparison operators each require a /// conversion from the `Ordering` result to a boolean. - fn create_operator_impl_call( + fn create_infix_operator_impl_call( &self, func: ast::Expression, lhs: ast::Expression, @@ -1715,6 +1729,21 @@ impl<'interner> Monomorphizer<'interner> { Ok(result) } + + /// Call an operator overloading method for the given prefix operator. + fn create_prefix_operator_impl_call( + &self, + func: ast::Expression, + rhs: ast::Expression, + ret: Type, + location: Location, + ) -> Result { + let arguments = vec![rhs]; + let func = Box::new(func); + let return_type = Self::convert_type(&ret, location)?; + + Ok(ast::Expression::Call(ast::Call { func, arguments, return_type, location })) + } } fn unwrap_tuple_type(typ: &HirType) -> Vec { @@ -1759,24 +1788,21 @@ pub fn perform_impl_bindings( if let Some(trait_method) = trait_method { let the_trait = interner.get_trait(trait_method.trait_id); - let trait_method_type = the_trait.methods[trait_method.method_index].typ.as_monotype(); + let mut trait_method_type = + the_trait.methods[trait_method.method_index].typ.as_monotype().clone(); + + let mut impl_method_type = + interner.function_meta(&impl_method).typ.unwrap_forall().1.clone(); // Make each NamedGeneric in this type bindable by replacing it with a TypeVariable // with the same internal id and binding. - let (generics, impl_method_type) = interner.function_meta(&impl_method).typ.unwrap_forall(); - - let replace_type_variable = |var: &TypeVariable| { - (var.id(), (var.clone(), Type::TypeVariable(var.clone(), TypeVariableKind::Normal))) - }; - - // Replace each NamedGeneric with a TypeVariable containing the same internal type variable - let type_bindings = generics.iter().map(replace_type_variable).collect(); - let impl_method_type = impl_method_type.force_substitute(&type_bindings); + trait_method_type.replace_named_generics_with_type_variables(); + impl_method_type.replace_named_generics_with_type_variables(); trait_method_type.try_unify(&impl_method_type, &mut bindings).map_err(|_| { InterpreterError::ImplMethodTypeMismatch { - expected: trait_method_type.clone(), - actual: impl_method_type, + expected: trait_method_type.follow_bindings(), + actual: impl_method_type.follow_bindings(), location, } })?; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs b/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs index 588b56afa1a..1c7d0984b2f 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; use std::collections::HashMap; use std::fmt; use std::hash::Hash; +use std::marker::Copy; use std::ops::Deref; use fm::FileId; @@ -18,6 +19,7 @@ use crate::hir::comptime; use crate::hir::def_collector::dc_crate::CompilationError; use crate::hir::def_collector::dc_crate::{UnresolvedStruct, UnresolvedTrait, UnresolvedTypeAlias}; use crate::hir::def_map::{LocalModuleId, ModuleId}; +use crate::macros_api::UnaryOp; use crate::QuotedType; use crate::ast::{BinaryOpKind, FunctionDefinition, ItemVisibility}; @@ -42,6 +44,13 @@ use crate::{Shared, TypeAlias, TypeBindings, TypeVariable, TypeVariableId, TypeV /// This is needed to stop recursing for cases such as `impl Foo for T where T: Eq` const IMPL_SEARCH_RECURSION_LIMIT: u32 = 10; +#[derive(Debug)] +pub struct ModuleAttributes { + pub name: String, + pub location: Location, + pub parent: LocalModuleId, +} + type StructAttributes = Vec; /// The node interner is the central storage location of all nodes in Noir's Hir (the @@ -66,7 +75,7 @@ pub struct NodeInterner { function_modules: HashMap, // The location of each module - module_locations: HashMap, + module_attributes: HashMap, /// This graph tracks dependencies between different global definitions. /// This is used to ensure the absence of dependency cycles for globals and types. @@ -139,8 +148,11 @@ pub struct NodeInterner { /// the context to get the concrete type of the object and select the correct impl itself. selected_trait_implementations: HashMap, - /// Holds the trait ids of the traits used for operator overloading - operator_traits: HashMap, + /// Holds the trait ids of the traits used for infix operator overloading + infix_operator_traits: HashMap, + + /// Holds the trait ids of the traits used for prefix operator overloading + prefix_operator_traits: HashMap, /// The `Ordering` type is a semi-builtin type that is the result of the comparison traits. ordering_type: Option, @@ -195,7 +207,7 @@ pub struct NodeInterner { /// Edges are directed from reference nodes to referenced nodes. /// For example: /// - /// ``` + /// ```text /// let foo = 3; /// // referenced /// // ^ @@ -214,6 +226,10 @@ pub struct NodeInterner { /// Store the location of the references in the graph pub(crate) location_indices: LocationIndices, + + // The module where each reference is + // (ReferenceId::Reference and ReferenceId::Local aren't included here) + pub(crate) reference_modules: HashMap, } /// A dependency in the dependency graph may be a type or a definition. @@ -241,6 +257,7 @@ pub enum DependencyId { pub enum ReferenceId { Module(ModuleId), Struct(StructId), + StructMember(StructId, usize), Trait(TraitId), Global(GlobalId), Function(FuncId), @@ -369,11 +386,6 @@ impl StmtId { #[derive(Debug, Eq, PartialEq, Hash, Copy, Clone, PartialOrd, Ord)] pub struct ExprId(Index); -impl ExprId { - pub fn empty_block_id() -> ExprId { - ExprId(Index::unsafe_zeroed()) - } -} #[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] pub struct FuncId(Index); @@ -541,13 +553,13 @@ pub struct QuotedTypeId(noirc_arena::Index); impl Default for NodeInterner { fn default() -> Self { - let mut interner = NodeInterner { + NodeInterner { nodes: Arena::default(), func_meta: HashMap::new(), function_definition_ids: HashMap::new(), function_modifiers: HashMap::new(), function_modules: HashMap::new(), - module_locations: HashMap::new(), + module_attributes: HashMap::new(), func_id_to_trait: HashMap::new(), dependency_graph: petgraph::graph::DiGraph::new(), dependency_graph_indices: HashMap::new(), @@ -563,7 +575,8 @@ impl Default for NodeInterner { next_trait_implementation_id: 0, trait_implementation_map: HashMap::new(), selected_trait_implementations: HashMap::new(), - operator_traits: HashMap::new(), + infix_operator_traits: HashMap::new(), + prefix_operator_traits: HashMap::new(), ordering_type: None, instantiation_bindings: HashMap::new(), field_indices: HashMap::new(), @@ -579,12 +592,8 @@ impl Default for NodeInterner { location_indices: LocationIndices::default(), reference_graph: petgraph::graph::DiGraph::new(), reference_graph_indices: HashMap::new(), - }; - - // An empty block expression is used often, we add this into the `node` on startup - let expr_id = interner.push_expr(HirExpression::empty_block()); - assert_eq!(expr_id, ExprId::empty_block_id()); - interner + reference_modules: HashMap::new(), + } } } @@ -847,7 +856,7 @@ impl NodeInterner { self.definitions.push(DefinitionInfo { name, mutable, comptime, kind, location }); if is_local { - self.add_definition_location(ReferenceId::Local(id)); + self.add_definition_location(ReferenceId::Local(id), None); } id @@ -885,7 +894,7 @@ impl NodeInterner { // This needs to be done after pushing the definition since it will reference the // location that was stored - self.add_definition_location(ReferenceId::Function(id)); + self.add_definition_location(ReferenceId::Function(id), Some(module)); definition_id } @@ -986,12 +995,20 @@ impl NodeInterner { &self.struct_attributes[struct_id] } - pub fn add_module_location(&mut self, module_id: ModuleId, location: Location) { - self.module_locations.insert(module_id, location); + pub fn add_module_attributes(&mut self, module_id: ModuleId, attributes: ModuleAttributes) { + self.module_attributes.insert(module_id, attributes); + } + + pub fn module_attributes(&self, module_id: &ModuleId) -> &ModuleAttributes { + &self.module_attributes[module_id] } - pub fn module_location(&self, module_id: &ModuleId) -> Location { - self.module_locations[module_id] + pub fn try_module_attributes(&self, module_id: &ModuleId) -> Option<&ModuleAttributes> { + self.module_attributes.get(module_id) + } + + pub fn try_module_parent(&self, module_id: &ModuleId) -> Option { + self.try_module_attributes(module_id).map(|attrs| attrs.parent) } pub fn global_attributes(&self, global_id: &GlobalId) -> &[SecondaryAttribute] { @@ -1071,6 +1088,10 @@ impl NodeInterner { self.id_location(expr_id).span } + pub fn try_expr_span(&self, expr_id: &ExprId) -> Option { + self.try_id_location(expr_id).map(|location| location.span) + } + pub fn expr_location(&self, expr_id: &ExprId) -> Location { self.id_location(expr_id) } @@ -1175,8 +1196,14 @@ impl NodeInterner { } /// Returns the span of an item stored in the Interner - pub fn id_location(&self, index: impl Into) -> Location { - self.id_to_location.get(&index.into()).copied().unwrap() + pub fn id_location(&self, index: impl Into + Copy) -> Location { + self.try_id_location(index) + .unwrap_or_else(|| panic!("ID is missing a source location: {:?}", index.into())) + } + + /// Returns the span of an item stored in the Interner, if present + pub fn try_id_location(&self, index: impl Into) -> Option { + self.id_to_location.get(&index.into()).copied() } /// Replaces the HirExpression at the given ExprId with a new HirExpression @@ -1665,18 +1692,29 @@ impl NodeInterner { /// Retrieves the trait id for a given binary operator. /// All binary operators correspond to a trait - although multiple may correspond /// to the same trait (such as `==` and `!=`). - /// `self.operator_traits` is expected to be filled before name resolution, + /// `self.infix_operator_traits` is expected to be filled before name resolution, /// during definition collection. pub fn get_operator_trait_method(&self, operator: BinaryOpKind) -> TraitMethodId { - let trait_id = self.operator_traits[&operator]; + let trait_id = self.infix_operator_traits[&operator]; // Assume that the operator's method to be overloaded is the first method of the trait. TraitMethodId { trait_id, method_index: 0 } } + /// Retrieves the trait id for a given unary operator. + /// Only some unary operators correspond to a trait: `-` and `!`, but for example `*` does not. + /// `self.prefix_operator_traits` is expected to be filled before name resolution, + /// during definition collection. + pub fn get_prefix_operator_trait_method(&self, operator: &UnaryOp) -> Option { + let trait_id = self.prefix_operator_traits.get(operator)?; + + // Assume that the operator's method to be overloaded is the first method of the trait. + Some(TraitMethodId { trait_id: *trait_id, method_index: 0 }) + } + /// Add the given trait as an operator trait if its name matches one of the /// operator trait names (Add, Sub, ...). - pub fn try_add_operator_trait(&mut self, trait_id: TraitId) { + pub fn try_add_infix_operator_trait(&mut self, trait_id: TraitId) { let the_trait = self.get_trait(trait_id); let operator = match the_trait.name.0.contents.as_str() { @@ -1695,17 +1733,17 @@ impl NodeInterner { _ => return, }; - self.operator_traits.insert(operator, trait_id); + self.infix_operator_traits.insert(operator, trait_id); // Some operators also require we insert a matching entry for related operators match operator { BinaryOpKind::Equal => { - self.operator_traits.insert(BinaryOpKind::NotEqual, trait_id); + self.infix_operator_traits.insert(BinaryOpKind::NotEqual, trait_id); } BinaryOpKind::Less => { - self.operator_traits.insert(BinaryOpKind::LessEqual, trait_id); - self.operator_traits.insert(BinaryOpKind::Greater, trait_id); - self.operator_traits.insert(BinaryOpKind::GreaterEqual, trait_id); + self.infix_operator_traits.insert(BinaryOpKind::LessEqual, trait_id); + self.infix_operator_traits.insert(BinaryOpKind::Greater, trait_id); + self.infix_operator_traits.insert(BinaryOpKind::GreaterEqual, trait_id); let the_trait = self.get_trait(trait_id); self.ordering_type = match &the_trait.methods[0].typ { @@ -1720,27 +1758,43 @@ impl NodeInterner { } } + /// Add the given trait as an operator trait if its name matches one of the + /// prefix operator trait names (Not or Neg). + pub fn try_add_prefix_operator_trait(&mut self, trait_id: TraitId) { + let the_trait = self.get_trait(trait_id); + + let operator = match the_trait.name.0.contents.as_str() { + "Neg" => UnaryOp::Minus, + "Not" => UnaryOp::Not, + _ => return, + }; + + self.prefix_operator_traits.insert(operator, trait_id); + } + /// This function is needed when creating a NodeInterner for testing so that calls /// to `get_operator_trait` do not panic when the stdlib isn't present. #[cfg(test)] pub fn populate_dummy_operator_traits(&mut self) { let dummy_trait = TraitId(ModuleId::dummy_id()); - self.operator_traits.insert(BinaryOpKind::Add, dummy_trait); - self.operator_traits.insert(BinaryOpKind::Subtract, dummy_trait); - self.operator_traits.insert(BinaryOpKind::Multiply, dummy_trait); - self.operator_traits.insert(BinaryOpKind::Divide, dummy_trait); - self.operator_traits.insert(BinaryOpKind::Modulo, dummy_trait); - self.operator_traits.insert(BinaryOpKind::Equal, dummy_trait); - self.operator_traits.insert(BinaryOpKind::NotEqual, dummy_trait); - self.operator_traits.insert(BinaryOpKind::Less, dummy_trait); - self.operator_traits.insert(BinaryOpKind::LessEqual, dummy_trait); - self.operator_traits.insert(BinaryOpKind::Greater, dummy_trait); - self.operator_traits.insert(BinaryOpKind::GreaterEqual, dummy_trait); - self.operator_traits.insert(BinaryOpKind::And, dummy_trait); - self.operator_traits.insert(BinaryOpKind::Or, dummy_trait); - self.operator_traits.insert(BinaryOpKind::Xor, dummy_trait); - self.operator_traits.insert(BinaryOpKind::ShiftLeft, dummy_trait); - self.operator_traits.insert(BinaryOpKind::ShiftRight, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::Add, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::Subtract, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::Multiply, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::Divide, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::Modulo, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::Equal, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::NotEqual, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::Less, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::LessEqual, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::Greater, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::GreaterEqual, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::And, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::Or, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::Xor, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::ShiftLeft, dummy_trait); + self.infix_operator_traits.insert(BinaryOpKind::ShiftRight, dummy_trait); + self.prefix_operator_traits.insert(UnaryOp::Minus, dummy_trait); + self.prefix_operator_traits.insert(UnaryOp::Not, dummy_trait); } pub(crate) fn ordering_type(&self) -> Type { @@ -1873,7 +1927,7 @@ impl NodeInterner { } /// Returns the type of an operator (which is always a function), along with its return type. - pub fn get_operator_type( + pub fn get_infix_operator_type( &self, lhs: ExprId, operator: BinaryOpKind, @@ -1894,6 +1948,15 @@ impl NodeInterner { let env = Box::new(Type::Unit); (Type::Function(args, Box::new(ret.clone()), env), ret) } + + /// Returns the type of a prefix operator (which is always a function), along with its return type. + pub fn get_prefix_operator_type(&self, operator_expr: ExprId, rhs: ExprId) -> (Type, Type) { + let rhs_type = self.id_type(rhs); + let args = vec![rhs_type]; + let ret = self.id_type(operator_expr); + let env = Box::new(Type::Unit); + (Type::Function(args, Box::new(ret.clone()), env), ret) + } } impl Methods { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs index c4aa0654ecd..c62d66769ac 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs @@ -22,7 +22,7 @@ use chumsky::primitive::Container; pub use errors::ParserError; pub use errors::ParserErrorReason; use noirc_errors::Span; -pub use parser::{expression, parse_program, top_level_items}; +pub use parser::{expression, parse_program, top_level_items, trait_bound}; #[derive(Debug, Clone)] pub enum TopLevelStatement { @@ -39,6 +39,24 @@ pub enum TopLevelStatement { Error, } +impl TopLevelStatement { + pub fn into_item_kind(self) -> Option { + match self { + TopLevelStatement::Function(f) => Some(ItemKind::Function(f)), + TopLevelStatement::Module(m) => Some(ItemKind::ModuleDecl(m)), + TopLevelStatement::Import(i) => Some(ItemKind::Import(i)), + TopLevelStatement::Struct(s) => Some(ItemKind::Struct(s)), + TopLevelStatement::Trait(t) => Some(ItemKind::Trait(t)), + TopLevelStatement::TraitImpl(t) => Some(ItemKind::TraitImpl(t)), + TopLevelStatement::Impl(i) => Some(ItemKind::Impl(i)), + TopLevelStatement::TypeAlias(t) => Some(ItemKind::TypeAlias(t)), + TopLevelStatement::SubModule(s) => Some(ItemKind::Submodules(s)), + TopLevelStatement::Global(c) => Some(ItemKind::Global(c)), + TopLevelStatement::Error => None, + } + } +} + // Helper trait that gives us simpler type signatures for return types: // e.g. impl Parser versus impl Parser> pub trait NoirParser: Parser + Sized + Clone {} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs index de9095aaff2..7f3e0e68bbc 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs @@ -171,20 +171,8 @@ fn module() -> impl NoirParser { .to(ParsedModule::default()) .then(spanned(top_level_statement(module_parser)).repeated()) .foldl(|mut program, (statement, span)| { - let mut push_item = |kind| program.items.push(Item { kind, span }); - - match statement { - TopLevelStatement::Function(f) => push_item(ItemKind::Function(f)), - TopLevelStatement::Module(m) => push_item(ItemKind::ModuleDecl(m)), - TopLevelStatement::Import(i) => push_item(ItemKind::Import(i)), - TopLevelStatement::Struct(s) => push_item(ItemKind::Struct(s)), - TopLevelStatement::Trait(t) => push_item(ItemKind::Trait(t)), - TopLevelStatement::TraitImpl(t) => push_item(ItemKind::TraitImpl(t)), - TopLevelStatement::Impl(i) => push_item(ItemKind::Impl(i)), - TopLevelStatement::TypeAlias(t) => push_item(ItemKind::TypeAlias(t)), - TopLevelStatement::SubModule(s) => push_item(ItemKind::Submodules(s)), - TopLevelStatement::Global(c) => push_item(ItemKind::Global(c)), - TopLevelStatement::Error => (), + if let Some(kind) = statement.into_item_kind() { + program.items.push(Item { kind, span }); } program }) @@ -204,9 +192,9 @@ pub fn top_level_items() -> impl NoirParser> { /// | module_declaration /// | use_statement /// | global_declaration -fn top_level_statement( - module_parser: impl NoirParser, -) -> impl NoirParser { +fn top_level_statement<'a>( + module_parser: impl NoirParser + 'a, +) -> impl NoirParser + 'a { choice(( function::function_definition(false).map(TopLevelStatement::Function), structs::struct_definition(), @@ -227,8 +215,9 @@ fn top_level_statement( /// /// implementation: 'impl' generics type '{' function_definition ... '}' fn implementation() -> impl NoirParser { - keyword(Keyword::Impl) - .ignore_then(function::generics()) + maybe_comp_time() + .then_ignore(keyword(Keyword::Impl)) + .then(function::generics()) .then(parse_type().map_with_span(|typ, span| (typ, span))) .then(where_clause()) .then_ignore(just(Token::LeftBrace)) @@ -236,13 +225,14 @@ fn implementation() -> impl NoirParser { .then_ignore(just(Token::RightBrace)) .map(|args| { let ((other_args, where_clause), methods) = args; - let (generics, (object_type, type_span)) = other_args; + let ((is_comptime, generics), (object_type, type_span)) = other_args; TopLevelStatement::Impl(TypeImpl { generics, object_type, type_span, where_clause, methods, + is_comptime, }) }) } @@ -408,7 +398,7 @@ fn trait_bounds() -> impl NoirParser> { trait_bound().separated_by(just(Token::Plus)).at_least(1).allow_trailing() } -fn trait_bound() -> impl NoirParser { +pub fn trait_bound() -> impl NoirParser { path().then(generic_type_args(parse_type())).map(|(trait_path, trait_generics)| TraitBound { trait_path, trait_generics, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/path.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/path.rs index e40268af410..8957fb7c40b 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/path.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/path.rs @@ -17,6 +17,7 @@ pub(super) fn path() -> impl NoirParser { choice(( path_kind(Keyword::Crate, PathKind::Crate), path_kind(Keyword::Dep, PathKind::Dep), + path_kind(Keyword::Super, PathKind::Super), idents().map_with_span(make_path(PathKind::Plain)), )) } @@ -64,6 +65,7 @@ mod test { ("std", PathKind::Plain), ("hash::collections", PathKind::Plain), ("crate::std::hash", PathKind::Crate), + ("super::foo", PathKind::Super), ]; for (src, expected_path_kind) in cases { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/structs.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/structs.rs index 7da956bdfea..9a3adf74d7f 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/structs.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/structs.rs @@ -1,6 +1,7 @@ use chumsky::prelude::*; use crate::ast::{Ident, NoirStruct, UnresolvedType}; +use crate::parser::parser::types::maybe_comp_time; use crate::{ parser::{ parser::{ @@ -28,13 +29,21 @@ pub(super) fn struct_definition() -> impl NoirParser { .or(just(Semicolon).to(Vec::new())); attributes() + .then(maybe_comp_time()) .then_ignore(keyword(Struct)) .then(ident()) .then(function::generics()) .then(fields) - .validate(|(((raw_attributes, name), generics), fields), span, emit| { - let attributes = validate_secondary_attributes(raw_attributes, span, emit); - TopLevelStatement::Struct(NoirStruct { name, attributes, generics, fields, span }) + .validate(|((((attributes, is_comptime), name), generics), fields), span, emit| { + let attributes = validate_secondary_attributes(attributes, span, emit); + TopLevelStatement::Struct(NoirStruct { + name, + attributes, + generics, + fields, + span, + is_comptime, + }) }) } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/traits.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/traits.rs index 1aec57c8e41..4e4c9d5c0db 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/traits.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/traits.rs @@ -1,6 +1,8 @@ use chumsky::prelude::*; +use super::attributes::{attributes, validate_secondary_attributes}; use super::function::function_return_type; +use super::types::maybe_comp_time; use super::{block, expression, fresh_statement, function, function_declaration_parameters}; use crate::ast::{ @@ -18,15 +20,24 @@ use crate::{ use super::{generic_type_args, parse_type, path, primitives::ident}; pub(super) fn trait_definition() -> impl NoirParser { - keyword(Keyword::Trait) - .ignore_then(ident()) + attributes() + .then_ignore(keyword(Keyword::Trait)) + .then(ident()) .then(function::generics()) .then(where_clause()) .then_ignore(just(Token::LeftBrace)) .then(trait_body()) .then_ignore(just(Token::RightBrace)) - .map_with_span(|(((name, generics), where_clause), items), span| { - TopLevelStatement::Trait(NoirTrait { name, generics, where_clause, span, items }) + .validate(|((((attributes, name), generics), where_clause), items), span, emit| { + let attributes = validate_secondary_attributes(attributes, span, emit); + TopLevelStatement::Trait(NoirTrait { + name, + generics, + where_clause, + span, + items, + attributes, + }) }) } @@ -93,8 +104,9 @@ fn trait_type_declaration() -> impl NoirParser { /// /// trait_implementation: 'impl' generics ident generic_args for type '{' trait_implementation_body '}' pub(super) fn trait_implementation() -> impl NoirParser { - keyword(Keyword::Impl) - .ignore_then(function::generics()) + maybe_comp_time() + .then_ignore(keyword(Keyword::Impl)) + .then(function::generics()) .then(path()) .then(generic_type_args(parse_type())) .then_ignore(keyword(Keyword::For)) @@ -104,8 +116,8 @@ pub(super) fn trait_implementation() -> impl NoirParser { .then(trait_implementation_body()) .then_ignore(just(Token::RightBrace)) .map(|args| { - let ((other_args, where_clause), items) = args; - let (((impl_generics, trait_name), trait_generics), object_type) = other_args; + let (((other_args, object_type), where_clause), items) = args; + let (((is_comptime, impl_generics), trait_name), trait_generics) = other_args; TopLevelStatement::TraitImpl(NoirTraitImpl { impl_generics, @@ -114,6 +126,7 @@ pub(super) fn trait_implementation() -> impl NoirParser { object_type, items, where_clause, + is_comptime, }) }) } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/types.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/types.rs index 32929312d54..3bd59608b12 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/types.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/types.rs @@ -26,6 +26,10 @@ pub(super) fn parse_type_inner<'a>( string_type(), expr_type(), struct_definition_type(), + trait_constraint_type(), + trait_definition_type(), + function_definition_type(), + module_type(), top_level_item_type(), type_of_quoted_types(), quoted_type(), @@ -54,15 +58,7 @@ pub(super) fn parenthesized_type( } pub(super) fn maybe_comp_time() -> impl NoirParser { - keyword(Keyword::Comptime).or_not().validate(|opt, span, emit| { - if opt.is_some() { - emit(ParserError::with_reason( - ParserErrorReason::ExperimentalFeature("Comptime values"), - span, - )); - } - opt.is_some() - }) + keyword(Keyword::Comptime).or_not().map(|opt| opt.is_some()) } pub(super) fn field_type() -> impl NoirParser { @@ -87,6 +83,30 @@ pub(super) fn struct_definition_type() -> impl NoirParser { }) } +/// This is the type `TraitConstraint` - the type of a quoted trait constraint +pub(super) fn trait_constraint_type() -> impl NoirParser { + keyword(Keyword::TraitConstraint).map_with_span(|_, span| { + UnresolvedTypeData::Quoted(QuotedType::TraitConstraint).with_span(span) + }) +} + +pub(super) fn trait_definition_type() -> impl NoirParser { + keyword(Keyword::TraitDefinition).map_with_span(|_, span| { + UnresolvedTypeData::Quoted(QuotedType::TraitDefinition).with_span(span) + }) +} + +pub(super) fn function_definition_type() -> impl NoirParser { + keyword(Keyword::FunctionDefinition).map_with_span(|_, span| { + UnresolvedTypeData::Quoted(QuotedType::FunctionDefinition).with_span(span) + }) +} + +pub(super) fn module_type() -> impl NoirParser { + keyword(Keyword::Module) + .map_with_span(|_, span| UnresolvedTypeData::Quoted(QuotedType::Module).with_span(span)) +} + /// This is the type `TopLevelItem` - the type of a quoted statement in the top level. /// E.g. a type definition, trait definition, trait impl, function, etc. fn top_level_item_type() -> impl NoirParser { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs index 70f8f785d68..eb4631385ed 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs @@ -50,10 +50,7 @@ pub(crate) fn remove_experimental_warnings(errors: &mut Vec<(CompilationError, F }); } -pub(crate) fn get_program( - src: &str, - use_legacy: bool, -) -> (ParsedModule, Context, Vec<(CompilationError, FileId)>) { +pub(crate) fn get_program(src: &str) -> (ParsedModule, Context, Vec<(CompilationError, FileId)>) { let root = std::path::Path::new("/"); let fm = FileManager::new(root); @@ -80,20 +77,27 @@ pub(crate) fn get_program( }; // Now we want to populate the CrateDefMap using the DefCollector - errors.extend(DefCollector::collect( + errors.extend(DefCollector::collect_crate_and_dependencies( def_map, &mut context, program.clone().into_sorted(), root_file_id, - use_legacy, - &[], // No macro processors + None, // No debug_comptime_in_file + &[], // No macro processors )); } (program, context, errors) } pub(crate) fn get_program_errors(src: &str) -> Vec<(CompilationError, FileId)> { - get_program(src, false).2 + get_program(src).2 +} + +fn assert_no_errors(src: &str) { + let errors = get_program_errors(src); + if !errors.is_empty() { + panic!("Expected no errors, got: {:?}", errors); + } } #[test] @@ -140,10 +144,7 @@ fn check_trait_implemented_for_all_t() { fn main(a: Foo) -> pub bool { a.is_default() }"; - - let errors = get_program_errors(src); - errors.iter().for_each(|err| println!("{:?}", err)); - assert!(errors.is_empty()); + assert_no_errors(src); } #[test] @@ -766,9 +767,7 @@ fn test_impl_self_within_default_def() { self } }"; - let errors = get_program_errors(src); - errors.iter().for_each(|err| println!("{:?}", err)); - assert!(errors.is_empty()); + assert_no_errors(src); } #[test] @@ -793,10 +792,7 @@ fn check_trait_as_type_as_fn_parameter() { fn main(a: Foo) -> pub bool { test_eq(a) }"; - - let errors = get_program_errors(src); - errors.iter().for_each(|err| println!("{:?}", err)); - assert!(errors.is_empty()); + assert_no_errors(src); } #[test] @@ -829,14 +825,11 @@ fn check_trait_as_type_as_two_fn_parameters() { fn main(a: Foo, b: u64) -> pub bool { test_eq(a, b) }"; - - let errors = get_program_errors(src); - errors.iter().for_each(|err| println!("{:?}", err)); - assert!(errors.is_empty()); + assert_no_errors(src); } fn get_program_captures(src: &str) -> Vec> { - let (program, context, _errors) = get_program(src, false); + let (program, context, _errors) = get_program(src); let interner = context.def_interner; let mut all_captures: Vec> = Vec::new(); for func in program.into_sorted().functions { @@ -897,7 +890,7 @@ fn resolve_empty_function() { } "; - assert!(get_program_errors(src).is_empty()); + assert_no_errors(src); } #[test] fn resolve_basic_function() { @@ -907,7 +900,7 @@ fn resolve_basic_function() { assert(y == x); } "#; - assert!(get_program_errors(src).is_empty()); + assert_no_errors(src); } #[test] fn resolve_unused_var() { @@ -980,7 +973,7 @@ fn resolve_literal_expr() { assert(y == x); } "#; - assert!(get_program_errors(src).is_empty()); + assert_no_errors(src); } #[test] @@ -1027,7 +1020,7 @@ fn resolve_prefix_expr() { let _y = -x; } "#; - assert!(get_program_errors(src).is_empty()); + assert_no_errors(src); } #[test] @@ -1039,7 +1032,7 @@ fn resolve_for_expr() { }; } "#; - assert!(get_program_errors(src).is_empty()); + assert_no_errors(src); } #[test] @@ -1053,7 +1046,7 @@ fn resolve_call_expr() { x } "#; - assert!(get_program_errors(src).is_empty()); + assert_no_errors(src); } #[test] @@ -1070,7 +1063,7 @@ fn resolve_shadowing() { x } "#; - assert!(get_program_errors(src).is_empty()); + assert_no_errors(src); } #[test] @@ -1081,7 +1074,7 @@ fn resolve_basic_closure() { closure(x) } "#; - assert!(get_program_errors(src).is_empty()); + assert_no_errors(src); } #[test] @@ -1132,7 +1125,7 @@ fn resolve_complex_closures() { a + b + c + closure_with_transitive_captures(6) } "#; - assert!(get_program_errors(src).is_empty(), "there should be no errors"); + assert_no_errors(src); let expected_captures = vec![ vec![], @@ -1198,7 +1191,7 @@ fn resolve_fmt_strings() { } fn check_rewrite(src: &str, expected: &str) { - let (_program, mut context, _errors) = get_program(src, false); + let (_program, mut context, _errors) = get_program(src); let main_func_id = context.def_interner.find_function("main").unwrap(); let program = monomorphize(main_func_id, &mut context.def_interner).unwrap(); assert!(format!("{}", program) == expected); @@ -1635,8 +1628,7 @@ fn numeric_generic_in_function_signature() { let src = r#" fn foo(arr: [Field; N]) -> [Field; N] { arr } "#; - let errors = get_program_errors(src); - assert!(errors.is_empty()); + assert_no_errors(src); } #[test] @@ -1748,12 +1740,14 @@ fn numeric_generic_used_in_nested_type_pass() { inner: [u64; N], } "#; - let errors = get_program_errors(src); - assert!(errors.is_empty()); + assert_no_errors(src); } #[test] fn numeric_generic_used_in_trait() { + // We want to make sure that `N` in `impl Deserialize` does + // not trigger `expected type, found numeric generic parameter N` as the trait + // does in fact expect a numeric generic. let src = r#" struct MyType { a: Field, @@ -1772,11 +1766,7 @@ fn numeric_generic_used_in_trait() { fn deserialize(fields: [Field; N], other: T) -> Self; } "#; - let errors = get_program_errors(src); - // We want to make sure that `N` in `impl Deserialize` does - // not trigger `expected type, found numeric generic parameter N` as the trait - // does in fact expect a numeric generic. - assert!(errors.is_empty()); + assert_no_errors(src); } #[test] @@ -1807,8 +1797,7 @@ fn numeric_generic_in_trait_impl_with_extra_impl_generics() { fn deserialize(fields: [Field; N]) -> Self; } "#; - let errors = get_program_errors(src); - assert!(errors.is_empty()); + assert_no_errors(src); } #[test] @@ -1826,8 +1815,7 @@ fn numeric_generic_used_in_where_clause() { T::deserialize(fields) } "#; - let errors = get_program_errors(src); - assert!(errors.is_empty()); + assert_no_errors(src); } #[test] @@ -1844,8 +1832,7 @@ fn numeric_generic_used_in_turbofish() { assert(double::<7 + 8>() == 30); } "#; - let errors = get_program_errors(src); - assert!(errors.is_empty()); + assert_no_errors(src); } #[test] @@ -1865,8 +1852,7 @@ fn constant_used_with_numeric_generic() { } } "#; - let errors = get_program_errors(src); - assert!(errors.is_empty()); + assert_no_errors(src); } #[test] @@ -2075,8 +2061,7 @@ fn turbofish_numeric_generic_nested_call() { let _ = bar::(); } "#; - let errors = get_program_errors(src); - assert!(errors.is_empty()); + assert_no_errors(src); // Check for turbofish numeric generics used with method calls let src = r#" @@ -2106,6 +2091,48 @@ fn turbofish_numeric_generic_nested_call() { let _ = bar::(); } "#; + assert_no_errors(src); +} + +#[test] +fn use_super() { + let src = r#" + fn some_func() {} + + mod foo { + use super::some_func; + } + "#; + assert_no_errors(src); +} + +#[test] +fn use_super_in_path() { + let src = r#" + fn some_func() {} + + mod foo { + fn func() { + super::some_func(); + } + } + "#; + assert_no_errors(src); +} + +#[test] +fn no_super() { + let src = "use super::some_func;"; let errors = get_program_errors(src); - assert!(errors.is_empty()); + assert_eq!(errors.len(), 1); + + let CompilationError::DefinitionError(DefCollectorErrorKind::PathResolutionError( + PathResolutionError::NoSuper(span), + )) = &errors[0].0 + else { + panic!("Expected a 'no super' error, got {:?}", errors[0].0); + }; + + assert_eq!(span.start(), 4); + assert_eq!(span.end(), 9); } diff --git a/noir/noir-repo/compiler/wasm/src/compile.rs b/noir/noir-repo/compiler/wasm/src/compile.rs index 59b0e00e49f..05f42bc91a1 100644 --- a/noir/noir-repo/compiler/wasm/src/compile.rs +++ b/noir/noir-repo/compiler/wasm/src/compile.rs @@ -164,10 +164,9 @@ pub fn compile_program( console_error_panic_hook::set_once(); let (crate_id, mut context) = prepare_context(entry_point, dependency_graph, file_source_map)?; - let compile_options = CompileOptions { - expression_width: ExpressionWidth::Bounded { width: 4 }, - ..CompileOptions::default() - }; + let expression_width = ExpressionWidth::Bounded { width: 4 }; + let compile_options = + CompileOptions { expression_width: Some(expression_width), ..CompileOptions::default() }; let compiled_program = noirc_driver::compile_main(&mut context, crate_id, &compile_options, None) @@ -180,8 +179,7 @@ pub fn compile_program( })? .0; - let optimized_program = - nargo::ops::transform_program(compiled_program, compile_options.expression_width); + let optimized_program = nargo::ops::transform_program(compiled_program, expression_width); let warnings = optimized_program.warnings.clone(); Ok(JsCompileProgramResult::new(optimized_program.into(), warnings)) @@ -196,10 +194,9 @@ pub fn compile_contract( console_error_panic_hook::set_once(); let (crate_id, mut context) = prepare_context(entry_point, dependency_graph, file_source_map)?; - let compile_options = CompileOptions { - expression_width: ExpressionWidth::Bounded { width: 4 }, - ..CompileOptions::default() - }; + let expression_width = ExpressionWidth::Bounded { width: 4 }; + let compile_options = + CompileOptions { expression_width: Some(expression_width), ..CompileOptions::default() }; let compiled_contract = noirc_driver::compile_contract(&mut context, crate_id, &compile_options) @@ -212,8 +209,7 @@ pub fn compile_contract( })? .0; - let optimized_contract = - nargo::ops::transform_contract(compiled_contract, compile_options.expression_width); + let optimized_contract = nargo::ops::transform_contract(compiled_contract, expression_width); let warnings = optimized_contract.warnings.clone(); Ok(JsCompileContractResult::new(optimized_contract.into(), warnings)) diff --git a/noir/noir-repo/compiler/wasm/src/compile_new.rs b/noir/noir-repo/compiler/wasm/src/compile_new.rs index d5f02833521..ef2af1dd654 100644 --- a/noir/noir-repo/compiler/wasm/src/compile_new.rs +++ b/noir/noir-repo/compiler/wasm/src/compile_new.rs @@ -100,7 +100,10 @@ impl CompilerContext { } else { ExpressionWidth::Bounded { width: 4 } }; - let compile_options = CompileOptions { expression_width, ..CompileOptions::default() }; + let compile_options = CompileOptions { + expression_width: Some(expression_width), + ..CompileOptions::default() + }; let root_crate_id = *self.context.root_crate_id(); let compiled_program = @@ -114,8 +117,7 @@ impl CompilerContext { })? .0; - let optimized_program = - nargo::ops::transform_program(compiled_program, compile_options.expression_width); + let optimized_program = nargo::ops::transform_program(compiled_program, expression_width); let warnings = optimized_program.warnings.clone(); Ok(JsCompileProgramResult::new(optimized_program.into(), warnings)) @@ -130,7 +132,10 @@ impl CompilerContext { } else { ExpressionWidth::Bounded { width: 4 } }; - let compile_options = CompileOptions { expression_width, ..CompileOptions::default() }; + let compile_options = CompileOptions { + expression_width: Some(expression_width), + ..CompileOptions::default() + }; let root_crate_id = *self.context.root_crate_id(); let compiled_contract = @@ -145,7 +150,7 @@ impl CompilerContext { .0; let optimized_contract = - nargo::ops::transform_contract(compiled_contract, compile_options.expression_width); + nargo::ops::transform_contract(compiled_contract, expression_width); let warnings = optimized_contract.warnings.clone(); Ok(JsCompileContractResult::new(optimized_contract.into(), warnings)) diff --git a/noir/noir-repo/cspell.json b/noir/noir-repo/cspell.json index 2a9bfb4b544..689b72435ef 100644 --- a/noir/noir-repo/cspell.json +++ b/noir/noir-repo/cspell.json @@ -206,6 +206,8 @@ "unoptimized", "urem", "USERPROFILE", + "vararg", + "varargs", "vecmap", "vitkov", "wasi", diff --git a/noir/noir-repo/docs/docs/explainers/explainer-oracle.md b/noir/noir-repo/docs/docs/explainers/explainer-oracle.md index b84ca5dd986..821e1f95c04 100644 --- a/noir/noir-repo/docs/docs/explainers/explainer-oracle.md +++ b/noir/noir-repo/docs/docs/explainers/explainer-oracle.md @@ -31,7 +31,7 @@ In short, anything that can be constrained in a Noir program but needs to be fet Just like in The Matrix, Oracles are powerful. But with great power, comes great responsibility. Just because you're using them in a Noir program doesn't mean they're true. Noir has no superpowers. If you want to prove that Portugal won the Euro Cup 2016, you're still relying on potentially untrusted information. -To give a concrete example, Alice wants to login to the [NounsDAO](https://nouns.wtf/) forum with her username "noir_nouner" by proving she owns a noun without revealing her ethereum address. Her Noir program could have a oracle call like this: +To give a concrete example, Alice wants to login to the [NounsDAO](https://nouns.wtf/) forum with her username "noir_nouner" by proving she owns a noun without revealing her ethereum address. Her Noir program could have an oracle call like this: ```rust #[oracle(getNoun)] @@ -52,6 +52,6 @@ If you don't constrain the return of your oracle, you could be clearly opening a On CLI, Nargo resolves oracles by making JSON RPC calls, which means it would require an RPC node to be running. -In JavaScript, NoirJS accepts and resolves arbitrary call handlers (that is, not limited to JSON) as long as they matches the expected types the developer defines. Refer to [Foreign Call Handler](../reference/NoirJS/noir_js/type-aliases/ForeignCallHandler.md) to learn more about NoirJS's call handling. +In JavaScript, NoirJS accepts and resolves arbitrary call handlers (that is, not limited to JSON) as long as they match the expected types the developer defines. Refer to [Foreign Call Handler](../reference/NoirJS/noir_js/type-aliases/ForeignCallHandler.md) to learn more about NoirJS's call handling. If you want to build using oracles, follow through to the [oracle guide](../how_to/how-to-oracles.md) for a simple example on how to do that. diff --git a/noir/noir-repo/docs/docs/getting_started/hello_noir/project_breakdown.md b/noir/noir-repo/docs/docs/getting_started/hello_noir/project_breakdown.md index 29688df148f..525b8dabdd8 100644 --- a/noir/noir-repo/docs/docs/getting_started/hello_noir/project_breakdown.md +++ b/noir/noir-repo/docs/docs/getting_started/hello_noir/project_breakdown.md @@ -67,6 +67,7 @@ The package section defines a number of fields including: - `entry` (optional) - a relative filepath to use as the entry point into your package (overrides the default of `src/lib.nr` or `src/main.nr`) - `backend` (optional) - `license` (optional) +- `expression_width` (optional) - Sets the default backend expression width. This field will override the default backend expression width specified by the Noir compiler (currently set to width 4). #### Dependencies section diff --git a/noir/noir-repo/docs/docs/how_to/how-to-oracles.md b/noir/noir-repo/docs/docs/how_to/how-to-oracles.md index df41276cfe1..2f69902062c 100644 --- a/noir/noir-repo/docs/docs/how_to/how-to-oracles.md +++ b/noir/noir-repo/docs/docs/how_to/how-to-oracles.md @@ -46,7 +46,7 @@ unconstrained fn get_sqrt(number: Field) -> Field { } ``` -In this example, we're wrapping our oracle function in a unconstrained method, and decorating it with `oracle(getSqrt)`. We can then call the unconstrained function as we would call any other function: +In this example, we're wrapping our oracle function in an unconstrained method, and decorating it with `oracle(getSqrt)`. We can then call the unconstrained function as we would call any other function: ```rust fn main(input: Field) { @@ -234,7 +234,7 @@ const client = new JSONRPCClient((jsonRPCRequest) => { // declaring a function that takes the name of the foreign call (getSqrt) and the inputs const foreignCallHandler = async (name, input) => { // notice that the "inputs" parameter contains *all* the inputs - // in this case we to make the RPC request with the first parameter "numbers", which would be input[0] + // in this case we make the RPC request with the first parameter "numbers", which would be input[0] const oracleReturn = await client.request(name, [ input[0].map((i) => i.toString("hex")), ]); diff --git a/noir/noir-repo/docs/docs/how_to/how-to-recursion.md b/noir/noir-repo/docs/docs/how_to/how-to-recursion.md index aac84e29fac..71f02fa5435 100644 --- a/noir/noir-repo/docs/docs/how_to/how-to-recursion.md +++ b/noir/noir-repo/docs/docs/how_to/how-to-recursion.md @@ -47,7 +47,7 @@ In a standard recursive app, you're also dealing with at least two circuits. For - `main`: a circuit of type `assert(x != y)`, where `main` is marked with a `#[recursive]` attribute. This attribute states that the backend should generate proofs that are friendly for verification within another circuit. - `recursive`: a circuit that verifies `main` -For a full example on how recursive proofs work, please refer to the [noir-examples](https://github.com/noir-lang/noir-examples) repository. We will *not* be using it as a reference for this guide. +For a full example of how recursive proofs work, please refer to the [noir-examples](https://github.com/noir-lang/noir-examples) repository. We will *not* be using it as a reference for this guide. ## Step 1: Setup diff --git a/noir/noir-repo/docs/docs/how_to/how-to-solidity-verifier.md b/noir/noir-repo/docs/docs/how_to/how-to-solidity-verifier.md index e6ed9abaec6..c800d91ac69 100644 --- a/noir/noir-repo/docs/docs/how_to/how-to-solidity-verifier.md +++ b/noir/noir-repo/docs/docs/how_to/how-to-solidity-verifier.md @@ -40,7 +40,7 @@ Generating a Solidity Verifier contract is actually a one-command process. Howev ## Step 1 - Generate a contract -This is by far the most straight-forward step. Just run: +This is by far the most straightforward step. Just run: ```sh nargo compile @@ -99,7 +99,7 @@ This time we will see a warning about an unused function parameter. This is expe ## Step 3 - Deploying -At this point we should have a compiled contract read to deploy. If we navigate to the deploy section in Remix, we will see many different environments we can deploy to. The steps to deploy on each environment would be out-of-scope for this guide, so we will just use the default Remix VM. +At this point we should have a compiled contract ready to deploy. If we navigate to the deploy section in Remix, we will see many different environments we can deploy to. The steps to deploy on each environment would be out-of-scope for this guide, so we will just use the default Remix VM. Looking closely, we will notice that our "Solidity Verifier" is actually three contracts working together: @@ -111,7 +111,7 @@ Remix will take care of the dependencies for us so we can simply deploy the Ultr ![Deploying UltraVerifier](@site/static/img/how-tos/solidity_verifier_5.png) -A contract will show up in the "Deployed Contracts" section, where we can retrieve the Verification Key Hash. This is particularly useful for double-checking the deployer contract is the correct one. +A contract will show up in the "Deployed Contracts" section, where we can retrieve the Verification Key Hash. This is particularly useful for double-checking that the deployer contract is the correct one. :::note diff --git a/noir/noir-repo/docs/docs/noir/concepts/data_bus.md b/noir/noir-repo/docs/docs/noir/concepts/data_bus.mdx similarity index 88% rename from noir/noir-repo/docs/docs/noir/concepts/data_bus.md rename to noir/noir-repo/docs/docs/noir/concepts/data_bus.mdx index e54fc861257..e55e58622ce 100644 --- a/noir/noir-repo/docs/docs/noir/concepts/data_bus.md +++ b/noir/noir-repo/docs/docs/noir/concepts/data_bus.mdx @@ -2,7 +2,9 @@ title: Data Bus sidebar_position: 13 --- -**Disclaimer** this feature is experimental, do not use it! +import Experimental from '@site/src/components/Notes/_experimental.mdx'; + + The data bus is an optimization that the backend can use to make recursion more efficient. In order to use it, you must define some inputs of the program entry points (usually the `main()` diff --git a/noir/noir-repo/docs/docs/noir/concepts/oracles.md b/noir/noir-repo/docs/docs/noir/concepts/oracles.mdx similarity index 78% rename from noir/noir-repo/docs/docs/noir/concepts/oracles.md rename to noir/noir-repo/docs/docs/noir/concepts/oracles.mdx index aa380b5f7b8..77a2ac1550a 100644 --- a/noir/noir-repo/docs/docs/noir/concepts/oracles.md +++ b/noir/noir-repo/docs/docs/noir/concepts/oracles.mdx @@ -11,11 +11,9 @@ keywords: sidebar_position: 6 --- -:::note +import Experimental from '@site/src/components/Notes/_experimental.mdx'; -This is an experimental feature that is not fully documented. If you notice any outdated information or potential improvements to this page, pull request contributions are very welcome: https://github.com/noir-lang/noir - -::: + Noir has support for Oracles via RPC calls. This means Noir will make an RPC call and use the return value for proof generation. diff --git a/noir/noir-repo/docs/docs/noir/modules_packages_crates/modules.md b/noir/noir-repo/docs/docs/noir/modules_packages_crates/modules.md index 9fffd925b7b..16b6307d2fd 100644 --- a/noir/noir-repo/docs/docs/noir/modules_packages_crates/modules.md +++ b/noir/noir-repo/docs/docs/noir/modules_packages_crates/modules.md @@ -148,4 +148,38 @@ Filename : `src/foo/bar/mod.nr` ```rust fn from_bar() {} +``` + +### Referencing a parent module + +Given a submodule, you can refer to its parent module using the `super` keyword. + +Filename : `src/main.nr` + +```rust +mod foo; + +fn main() { + foo::from_foo(); +} +``` + +Filename : `src/foo.nr` + +```rust +mod bar; + +fn from_foo() {} +``` + +Filename : `src/foo/bar.nr` + +```rust +// Same as bar::from_foo +use super::from_foo; + +fn from_bar() { + from_foo(); // invokes super::from_foo(), which is bar::from_foo() + super::from_foo(); // also invokes bar::from_foo() +} ``` \ No newline at end of file diff --git a/noir/noir-repo/docs/src/components/Notes/_experimental.mdx b/noir/noir-repo/docs/src/components/Notes/_experimental.mdx index da1b0826aa1..1c0b2448ad3 100644 --- a/noir/noir-repo/docs/src/components/Notes/_experimental.mdx +++ b/noir/noir-repo/docs/src/components/Notes/_experimental.mdx @@ -1,6 +1,7 @@ -:::caution +:::caution Experimental Feature -This feature is experimental. You should expect it to change in future versions, -cause unexpected behavior, or simply not work at all. +This feature is experimental. The documentation may be incomplete or out of date, which means it could change in future versions, potentially causing unexpected behavior or not working as expected. + +**Contributions Welcome:** If you notice any inaccuracies or potential improvements, please consider contributing. Visit our GitHub repository to make your contributions: [Contribute Here](https://github.com/noir-lang/noir). ::: diff --git a/noir/noir-repo/noir_stdlib/src/collections/bounded_vec.nr b/noir/noir-repo/noir_stdlib/src/collections/bounded_vec.nr index c218ecd2348..a56d6f60390 100644 --- a/noir/noir-repo/noir_stdlib/src/collections/bounded_vec.nr +++ b/noir/noir-repo/noir_stdlib/src/collections/bounded_vec.nr @@ -14,7 +14,7 @@ impl BoundedVec { /// Get an element from the vector at the given index. /// Panics if the given index points beyond the end of the vector (`self.len()`). pub fn get(self, index: u32) -> T { - assert(index < self.len); + assert(index < self.len, "Attempted to read past end of BoundedVec"); self.get_unchecked(index) } @@ -152,25 +152,16 @@ impl From<[T; Len]> for BoundedVec } mod bounded_vec_tests { - // TODO: Allow imports from "super" - use crate::collections::bounded_vec::BoundedVec; - #[test] - fn empty_equality() { - let mut bounded_vec1: BoundedVec = BoundedVec::new(); - let mut bounded_vec2: BoundedVec = BoundedVec::new(); - - assert_eq(bounded_vec1, bounded_vec2); - } + mod get { + use crate::collections::bounded_vec::BoundedVec; - #[test] - fn inequality() { - let mut bounded_vec1: BoundedVec = BoundedVec::new(); - let mut bounded_vec2: BoundedVec = BoundedVec::new(); - bounded_vec1.push(1); - bounded_vec2.push(2); + #[test(should_fail_with = "Attempted to read past end of BoundedVec")] + fn panics_when_reading_elements_past_end_of_vec() { + let vec: BoundedVec = BoundedVec::new(); - assert(bounded_vec1 != bounded_vec2); + crate::println(vec.get(0)); + } } mod set { @@ -295,4 +286,26 @@ mod bounded_vec_tests { assert_eq(bounded_vec.storage()[1], 2); } } + + mod trait_eq { + use crate::collections::bounded_vec::BoundedVec; + + #[test] + fn empty_equality() { + let mut bounded_vec1: BoundedVec = BoundedVec::new(); + let mut bounded_vec2: BoundedVec = BoundedVec::new(); + + assert_eq(bounded_vec1, bounded_vec2); + } + + #[test] + fn inequality() { + let mut bounded_vec1: BoundedVec = BoundedVec::new(); + let mut bounded_vec2: BoundedVec = BoundedVec::new(); + bounded_vec1.push(1); + bounded_vec2.push(2); + + assert(bounded_vec1 != bounded_vec2); + } + } } diff --git a/noir/noir-repo/noir_stdlib/src/compat.nr b/noir/noir-repo/noir_stdlib/src/compat.nr index 06da8150767..92e15bae30e 100644 --- a/noir/noir-repo/noir_stdlib/src/compat.nr +++ b/noir/noir-repo/noir_stdlib/src/compat.nr @@ -1,7 +1,21 @@ -global BN254_MODULUS_BE_BYTES: [u8] = &[ +comptime global BN254_MODULUS_BE_BYTES: [u8] = &[ 48, 100, 78, 114, 225, 49, 160, 41, 184, 80, 69, 182, 129, 129, 88, 93, 40, 51, 232, 72, 121, 185, 112, 145, 67, 225, 245, 147, 240, 0, 0, 1 ]; pub fn is_bn254() -> bool { - crate::field::modulus_be_bytes() == BN254_MODULUS_BE_BYTES + comptime + { + // We can't use the `Eq` trait here due to limitations on calling non-comptime functions + // defined within the same crate. + let mut eq = true; + + let modulus_be_bytes = crate::field::modulus_be_bytes(); + // We can't do `BN254_MODULUS_BE_BYTES.len()` due to limitations on calling non-comptime functions. + assert_eq(crate::field::modulus_num_bits(), 254); + for i in 0..32 { + eq &= modulus_be_bytes[i] == BN254_MODULUS_BE_BYTES[i]; + } + + eq + } } diff --git a/noir/noir-repo/noir_stdlib/src/embedded_curve_ops.nr b/noir/noir-repo/noir_stdlib/src/embedded_curve_ops.nr index c791ac88404..9c1a3097217 100644 --- a/noir/noir-repo/noir_stdlib/src/embedded_curve_ops.nr +++ b/noir/noir-repo/noir_stdlib/src/embedded_curve_ops.nr @@ -1,6 +1,9 @@ use crate::ops::arith::{Add, Sub, Neg}; use crate::cmp::Eq; +/// A point on the embedded elliptic curve +/// By definition, the base field of the embedded curve is the scalar field of the proof system curve, i.e the Noir Field. +/// x and y denotes the Weierstrass coordinates of the point, if is_infinite is false. struct EmbeddedCurvePoint { x: Field, y: Field, @@ -8,32 +11,35 @@ struct EmbeddedCurvePoint { } impl EmbeddedCurvePoint { - fn new(x: Field, y: Field, is_infinite: bool) -> Self { - Self { x, y, is_infinite } - } - + /// Elliptic curve point doubling operation + /// returns the doubled point of a point P, i.e P+P fn double(self) -> EmbeddedCurvePoint { embedded_curve_add(self, self) } + /// Returns the null element of the curve; 'the point at infinity' fn point_at_infinity() -> EmbeddedCurvePoint { EmbeddedCurvePoint { x: 0, y: 0, is_infinite: true } } } impl Add for EmbeddedCurvePoint { + /// Adds two points P+Q, using the curve addition formula, and also handles point at infinity fn add(self, other: EmbeddedCurvePoint) -> EmbeddedCurvePoint { embedded_curve_add(self, other) } } impl Sub for EmbeddedCurvePoint { + /// Points subtraction operation, using addition and negation fn sub(self, other: EmbeddedCurvePoint) -> EmbeddedCurvePoint { self + other.neg() } } impl Neg for EmbeddedCurvePoint { + /// Negates a point P, i.e returns -P, by negating the y coordinate. + /// If the point is at infinity, then the result is also at infinity. fn neg(self) -> EmbeddedCurvePoint { EmbeddedCurvePoint { x: self.x, @@ -44,12 +50,15 @@ impl Neg for EmbeddedCurvePoint { } impl Eq for EmbeddedCurvePoint { + /// Checks whether two points are equal fn eq(self: Self, b: EmbeddedCurvePoint) -> bool { (self.is_infinite & b.is_infinite) | ((self.is_infinite == b.is_infinite) & (self.x == b.x) & (self.y == b.y)) } } -// Scalar represented as low and high limbs +/// Scalar for the embedded curve represented as low and high limbs +/// By definition, the scalar field of the embedded curve is base field of the proving system curve. +/// It may not fit into a Field element, so it is represented with two Field elements; its low and high limbs. struct EmbeddedCurveScalar { lo: Field, hi: Field, @@ -60,11 +69,6 @@ impl EmbeddedCurveScalar { EmbeddedCurveScalar { lo, hi } } - pub fn derive_public_key(self) -> EmbeddedCurvePoint { - let public_key = fixed_base_scalar_mul(self.lo, self.hi); - EmbeddedCurvePoint { x: public_key[0], y: public_key[1], is_infinite: false } - } - #[field(bn254)] fn from_field(scalar: Field) -> EmbeddedCurveScalar { let (a,b) = crate::field::bn254::decompose(scalar); @@ -73,8 +77,8 @@ impl EmbeddedCurveScalar { } impl Eq for EmbeddedCurveScalar { - fn eq(self, key: EmbeddedCurveScalar) -> bool { - (key.hi == self.hi) & (key.lo == self.lo) + fn eq(self, other: Self) -> bool { + (other.hi == self.hi) & (other.lo == self.lo) } } @@ -93,6 +97,9 @@ pub fn multi_scalar_mul( // docs:end:multi_scalar_mul {} +#[foreign(multi_scalar_mul)] +pub(crate) fn multi_scalar_mul_slice(points: [EmbeddedCurvePoint], scalars: [EmbeddedCurveScalar]) -> [Field; 3] {} + // docs:start:fixed_base_scalar_mul pub fn fixed_base_scalar_mul( scalar_low: Field, diff --git a/noir/noir-repo/noir_stdlib/src/field/bn254.nr b/noir/noir-repo/noir_stdlib/src/field/bn254.nr index bcdc23f80dc..e8db0a30c38 100644 --- a/noir/noir-repo/noir_stdlib/src/field/bn254.nr +++ b/noir/noir-repo/noir_stdlib/src/field/bn254.nr @@ -23,7 +23,7 @@ fn compute_decomposition(x: Field) -> (Field, Field) { (low, high) } -unconstrained fn decompose_hint(x: Field) -> (Field, Field) { +unconstrained pub(crate) fn decompose_hint(x: Field) -> (Field, Field) { compute_decomposition(x) } diff --git a/noir/noir-repo/noir_stdlib/src/field/mod.nr b/noir/noir-repo/noir_stdlib/src/field/mod.nr index b876bcc967b..4b6deaa1106 100644 --- a/noir/noir-repo/noir_stdlib/src/field/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/field/mod.nr @@ -84,19 +84,20 @@ impl Field { } #[builtin(modulus_num_bits)] -pub fn modulus_num_bits() -> u64 {} +pub comptime fn modulus_num_bits() -> u64 {} #[builtin(modulus_be_bits)] -pub fn modulus_be_bits() -> [u1] {} +pub comptime fn modulus_be_bits() -> [u1] {} #[builtin(modulus_le_bits)] -pub fn modulus_le_bits() -> [u1] {} +pub comptime fn modulus_le_bits() -> [u1] {} #[builtin(modulus_be_bytes)] -pub fn modulus_be_bytes() -> [u8] {} +pub comptime fn modulus_be_bytes() -> [u8] {} #[builtin(modulus_le_bytes)] -pub fn modulus_le_bytes() -> [u8] {} +pub comptime fn modulus_le_bytes() -> [u8] {} + // Convert a 32 byte array to a field element by modding pub fn bytes32_to_field(bytes32: [u8; 32]) -> Field { // Convert it to a field element diff --git a/noir/noir-repo/noir_stdlib/src/hash/keccak.nr b/noir/noir-repo/noir_stdlib/src/hash/keccak.nr new file mode 100644 index 00000000000..a747676731a --- /dev/null +++ b/noir/noir-repo/noir_stdlib/src/hash/keccak.nr @@ -0,0 +1,141 @@ +global LIMBS_PER_BLOCK = 17; //BLOCK_SIZE / 8; +global NUM_KECCAK_LANES = 25; +global BLOCK_SIZE = 136; //(1600 - BITS * 2) / WORD_SIZE; +global WORD_SIZE = 8; + +use crate::collections::vec::Vec; + +#[foreign(keccakf1600)] +fn keccakf1600(input: [u64; 25]) -> [u64; 25] {} + +#[no_predicates] +pub(crate) fn keccak256(mut input: [u8; N], message_size: u32) -> [u8; 32] { + assert(N >= message_size); + for i in 0..N { + if i >= message_size { + input[i] = 0; + } + } + + //1. format_input_lanes + let max_blocks = (N + BLOCK_SIZE) / BLOCK_SIZE; + //maximum number of bytes to hash + let max_blocks_length = (BLOCK_SIZE * (max_blocks)); + let real_max_blocks = (message_size + BLOCK_SIZE) / BLOCK_SIZE; + let real_blocks_bytes = real_max_blocks * BLOCK_SIZE; + + let mut block_bytes = Vec::from_slice(input.as_slice()); + for _i in N..N + BLOCK_SIZE { + block_bytes.push(0); + } + block_bytes.set(message_size, 1); + block_bytes.set(real_blocks_bytes - 1, 0x80); + + // keccak lanes interpret memory as little-endian integers, + // means we need to swap our byte ordering + let num_limbs = max_blocks * LIMBS_PER_BLOCK; //max_blocks_length / WORD_SIZE; + for i in 0..num_limbs { + let mut temp = [0; 8]; + for j in 0..8 { + temp[j] = block_bytes.get(8*i+j); + } + for j in 0..8 { + block_bytes.set(8 * i + j, temp[7 - j]); + } + } + let byte_size = max_blocks_length; + let mut sliced_buffer = Vec::new(); + for _i in 0..num_limbs { + sliced_buffer.push(0); + } + // populate a vector of 64-bit limbs from our byte array + for i in 0..num_limbs { + let mut sliced = 0; + if (i * WORD_SIZE + WORD_SIZE > byte_size) { + let slice_size = byte_size - (i * WORD_SIZE); + let byte_shift = (WORD_SIZE - slice_size) * 8; + let mut v = 1; + for k in 0..slice_size { + sliced += v * (block_bytes.get(i * WORD_SIZE+7-k) as Field); + v *= 256; + } + let w = 1 << (byte_shift as u8); + sliced *= w as Field; + } else { + let mut v = 1; + for k in 0..WORD_SIZE { + sliced += v * (block_bytes.get(i * WORD_SIZE+7-k) as Field); + v *= 256; + } + } + sliced_buffer.set(i, sliced as u64); + } + + //2. sponge_absorb + let num_blocks = max_blocks; + let mut state : [u64;NUM_KECCAK_LANES]= [0; NUM_KECCAK_LANES]; + let mut under_block = true; + for i in 0..num_blocks { + if i == real_max_blocks { + under_block = false; + } + if under_block { + if (i == 0) { + for j in 0..LIMBS_PER_BLOCK { + state[j] = sliced_buffer.get(j); + } + } else { + for j in 0..LIMBS_PER_BLOCK { + state[j] = state[j] ^ sliced_buffer.get(i * LIMBS_PER_BLOCK + j); + } + } + state = keccakf1600(state); + } + } + + //3. sponge_squeeze + let mut result = [0; 32]; + for i in 0..4 { + let lane = state[i] as Field; + let lane_le = lane.to_le_bytes(8); + for j in 0..8 { + result[8*i+j] = lane_le[j]; + } + } + result +} + +mod tests { + use crate::hash::keccak::keccak256; + + #[test] + fn smoke_test() { + let input = [0xbd]; + let result = [ + 0x5a, 0x50, 0x2f, 0x9f, 0xca, 0x46, 0x7b, 0x26, 0x6d, 0x5b, 0x78, 0x33, 0x65, 0x19, 0x37, 0xe8, 0x05, 0x27, 0x0c, 0xa3, 0xf3, 0xaf, 0x1c, 0x0d, 0xd2, 0x46, 0x2d, 0xca, 0x4b, 0x3b, 0x1a, 0xbf + ]; + assert_eq(keccak256(input, input.len()), result); + } + + #[test] + fn hash_hello_world() { + // "hello world" + let input = [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33]; + let result = [ + 0xec, 0xd0, 0xe1, 0x8, 0xa9, 0x8e, 0x19, 0x2a, 0xf1, 0xd2, 0xc2, 0x50, 0x55, 0xf4, 0xe3, 0xbe, 0xd7, 0x84, 0xb5, 0xc8, 0x77, 0x20, 0x4e, 0x73, 0x21, 0x9a, 0x52, 0x3, 0x25, 0x1f, 0xea, 0xab + ]; + assert_eq(keccak256(input, input.len()), result); + } + + #[test] + fn var_size_hash() { + let input = [ + 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223 + ]; + let result = [ + 226, 37, 115, 94, 94, 196, 72, 116, 194, 105, 79, 233, 65, 12, 30, 94, 181, 131, 170, 219, 171, 166, 236, 88, 143, 67, 255, 160, 248, 214, 39, 129 + ]; + assert_eq(keccak256(input, 13), result); + } +} + diff --git a/noir/noir-repo/noir_stdlib/src/hash/mod.nr b/noir/noir-repo/noir_stdlib/src/hash/mod.nr index 65f3b9419ff..40d50abc9e5 100644 --- a/noir/noir-repo/noir_stdlib/src/hash/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/hash/mod.nr @@ -1,11 +1,13 @@ mod poseidon; mod mimc; mod poseidon2; +mod keccak; use crate::default::Default; use crate::uint128::U128; use crate::sha256::{digest, sha256_var}; -use crate::embedded_curve_ops::{EmbeddedCurvePoint, EmbeddedCurveScalar, multi_scalar_mul}; +use crate::collections::vec::Vec; +use crate::embedded_curve_ops::{EmbeddedCurvePoint, EmbeddedCurveScalar, multi_scalar_mul, multi_scalar_mul_slice}; #[foreign(sha256)] // docs:start:sha256 @@ -25,32 +27,35 @@ pub fn blake3(input: [u8; N]) -> [u8; 32] // docs:end:blake3 {} -#[no_predicates] // docs:start:pedersen_commitment pub fn pedersen_commitment(input: [Field; N]) -> EmbeddedCurvePoint { // docs:end:pedersen_commitment - let value = pedersen_commitment_with_separator(input, 0); - if (value.x == 0) & (value.y == 0) { - EmbeddedCurvePoint { x: 0, y: 0, is_infinite: true } - } else { - EmbeddedCurvePoint { x: value.x, y: value.y, is_infinite: false } - } + pedersen_commitment_with_separator(input, 0) } -fn pedersen_commitment_with_separator_noir(input: [Field; N], separator: u32) -> EmbeddedCurvePoint { +fn pedersen_commitment_with_separator(input: [Field; N], separator: u32) -> EmbeddedCurvePoint { let mut points = [EmbeddedCurveScalar { lo: 0, hi: 0 }; N]; for i in 0..N { - points[i] = EmbeddedCurveScalar::from_field(input[i]); + // we use the unsafe version because the multi_scalar_mul will constraint the scalars. + points[i] = from_field_unsafe(input[i]); } let generators = derive_generators("DEFAULT_DOMAIN_SEPARATOR".as_bytes(), separator); let values = multi_scalar_mul(generators, points); EmbeddedCurvePoint { x: values[0], y: values[1], is_infinite: values[2] as bool } } -#[no_predicates] -pub fn pedersen_commitment_with_separator(input: [Field; N], separator: u32) -> EmbeddedCurvePoint { - let values = __pedersen_commitment_with_separator(input, separator); - EmbeddedCurvePoint { x: values[0], y: values[1], is_infinite: false } +fn pedersen_hash_with_separator(input: [Field; N], separator: u32) -> Field { + let mut scalars: Vec = Vec::from_slice([EmbeddedCurveScalar { lo: 0, hi: 0 }; N].as_slice()); //Vec::new(); + + for i in 0..N { + scalars.set(i, from_field_unsafe(input[i])); + } + scalars.push(EmbeddedCurveScalar { lo: N as Field, hi: 0 }); + let domain_generators :[EmbeddedCurvePoint; N]= derive_generators("DEFAULT_DOMAIN_SEPARATOR".as_bytes(), separator); + let mut vec_generators = Vec::from_slice(domain_generators.as_slice()); + let length_generator : [EmbeddedCurvePoint; 1] = derive_generators("pedersen_hash_length".as_bytes(), 0); + vec_generators.push(length_generator[0]); + multi_scalar_mul_slice(vec_generators.slice, scalars.slice)[0] } // docs:start:pedersen_hash @@ -61,10 +66,7 @@ pub fn pedersen_hash(input: [Field; N]) -> Field } #[field(bn254)] -fn derive_generators( - domain_separator_bytes: [u8; M], - starting_index: u32 -) -> [EmbeddedCurvePoint; N] { +fn derive_generators(domain_separator_bytes: [u8; M], starting_index: u32) -> [EmbeddedCurvePoint; N] { crate::assert_constant(domain_separator_bytes); crate::assert_constant(starting_index); __derive_generators(domain_separator_bytes, starting_index) @@ -72,23 +74,22 @@ fn derive_generators( #[builtin(derive_pedersen_generators)] #[field(bn254)] -fn __derive_generators(domain_separator_bytes: [u8; M], starting_index: u32) -> [EmbeddedCurvePoint; N] {} +fn __derive_generators( + domain_separator_bytes: [u8; M], + starting_index: u32 +) -> [EmbeddedCurvePoint; N] {} -fn pedersen_hash_with_separator_noir(input: [Field; N], separator: u32) -> Field { - let v1 = pedersen_commitment_with_separator(input, separator); - let length_generator : [EmbeddedCurvePoint; 1] = derive_generators("pedersen_hash_length".as_bytes(), 0); - multi_scalar_mul( - [length_generator[0], v1], - [EmbeddedCurveScalar { lo: N as Field, hi: 0 }, EmbeddedCurveScalar { lo: 1, hi: 0 }] - )[0] +#[field(bn254)] + // Same as from_field but: + // does not assert the limbs are 128 bits + // does not assert the decomposition does not overflow the EmbeddedCurveScalar + fn from_field_unsafe(scalar: Field) -> EmbeddedCurveScalar { + let (xlo, xhi) = crate::field::bn254::decompose_hint(scalar); + // Check that the decomposition is correct + assert_eq(scalar, xlo + crate::field::bn254::TWO_POW_128 * xhi); + EmbeddedCurveScalar { lo: xlo, hi: xhi } } -#[foreign(pedersen_hash)] -pub fn pedersen_hash_with_separator(input: [Field; N], separator: u32) -> Field {} - -#[foreign(pedersen_commitment)] -fn __pedersen_commitment_with_separator(input: [Field; N], separator: u32) -> [Field; 2] {} - pub fn hash_to_field(inputs: [Field]) -> Field { let mut sum = 0; @@ -100,11 +101,12 @@ pub fn hash_to_field(inputs: [Field]) -> Field { sum } -#[foreign(keccak256)] // docs:start:keccak256 pub fn keccak256(input: [u8; N], message_size: u32) -> [u8; 32] // docs:end:keccak256 -{} +{ + crate::hash::keccak::keccak256(input, message_size) +} #[foreign(poseidon2_permutation)] pub fn poseidon2_permutation(_input: [Field; N], _state_length: u32) -> [Field; N] {} @@ -263,10 +265,111 @@ impl Hash for (A, B, C, D, E) where A: Hash, B: Hash, C: Hash, D: } } +// Some test vectors for Pedersen hash and Pedersen Commitment. +// They have been generated using the same functions so the tests are for now useless +// but they will be useful when we switch to Noir implementation. #[test] -fn assert_pedersen_noir() { - // TODO: make this a fuzzer test once fuzzer supports curve-specific blackbox functions. - let input = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - assert_eq(pedersen_hash_with_separator(input, 4), pedersen_hash_with_separator_noir(input, 4)); - assert_eq(pedersen_commitment_with_separator(input, 4), pedersen_commitment_with_separator_noir(input, 4)); +fn assert_pedersen() { + assert_eq( + pedersen_hash_with_separator([1], 1), 0x1b3f4b1a83092a13d8d1a59f7acb62aba15e7002f4440f2275edb99ebbc2305f + ); + assert_eq( + pedersen_commitment_with_separator([1], 1), EmbeddedCurvePoint { + x: 0x054aa86a73cb8a34525e5bbed6e43ba1198e860f5f3950268f71df4591bde402, + y: 0x209dcfbf2cfb57f9f6046f44d71ac6faf87254afc7407c04eb621a6287cac126, + is_infinite: false + } + ); + + assert_eq( + pedersen_hash_with_separator([1, 2], 2), 0x26691c129448e9ace0c66d11f0a16d9014a9e8498ee78f4d69f0083168188255 + ); + assert_eq( + pedersen_commitment_with_separator([1, 2], 2), EmbeddedCurvePoint { + x: 0x2e2b3b191e49541fe468ec6877721d445dcaffe41728df0a0eafeb15e87b0753, + y: 0x2ff4482400ad3a6228be17a2af33e2bcdf41be04795f9782bd96efe7e24f8778, + is_infinite: false + } + ); + assert_eq( + pedersen_hash_with_separator([1, 2, 3], 3), 0x0bc694b7a1f8d10d2d8987d07433f26bd616a2d351bc79a3c540d85b6206dbe4 + ); + assert_eq( + pedersen_commitment_with_separator([1, 2, 3], 3), EmbeddedCurvePoint { + x: 0x1fee4e8cf8d2f527caa2684236b07c4b1bad7342c01b0f75e9a877a71827dc85, + y: 0x2f9fedb9a090697ab69bf04c8bc15f7385b3e4b68c849c1536e5ae15ff138fd1, + is_infinite: false + } + ); + assert_eq( + pedersen_hash_with_separator([1, 2, 3, 4], 4), 0xdae10fb32a8408521803905981a2b300d6a35e40e798743e9322b223a5eddc + ); + assert_eq( + pedersen_commitment_with_separator([1, 2, 3, 4], 4), EmbeddedCurvePoint { + x: 0x07ae3e202811e1fca39c2d81eabe6f79183978e6f12be0d3b8eda095b79bdbc9, + y: 0x0afc6f892593db6fbba60f2da558517e279e0ae04f95758587760ba193145014, + is_infinite: false + } + ); + assert_eq( + pedersen_hash_with_separator([1, 2, 3, 4, 5], 5), 0xfc375b062c4f4f0150f7100dfb8d9b72a6d28582dd9512390b0497cdad9c22 + ); + assert_eq( + pedersen_commitment_with_separator([1, 2, 3, 4, 5], 5), EmbeddedCurvePoint { + x: 0x1754b12bd475a6984a1094b5109eeca9838f4f81ac89c5f0a41dbce53189bb29, + y: 0x2da030e3cfcdc7ddad80eaf2599df6692cae0717d4e9f7bfbee8d073d5d278f7, + is_infinite: false + } + ); + assert_eq( + pedersen_hash_with_separator([1, 2, 3, 4, 5, 6], 6), 0x1696ed13dc2730062a98ac9d8f9de0661bb98829c7582f699d0273b18c86a572 + ); + assert_eq( + pedersen_commitment_with_separator([1, 2, 3, 4, 5, 6], 6), EmbeddedCurvePoint { + x: 0x190f6c0e97ad83e1e28da22a98aae156da083c5a4100e929b77e750d3106a697, + y: 0x1f4b60f34ef91221a0b49756fa0705da93311a61af73d37a0c458877706616fb, + is_infinite: false + } + ); + assert_eq( + pedersen_hash_with_separator([1, 2, 3, 4, 5, 6, 7], 7), 0x128c0ff144fc66b6cb60eeac8a38e23da52992fc427b92397a7dffd71c45ede3 + ); + assert_eq( + pedersen_commitment_with_separator([1, 2, 3, 4, 5, 6, 7], 7), EmbeddedCurvePoint { + x: 0x015441e9d29491b06563fac16fc76abf7a9534c715421d0de85d20dbe2965939, + y: 0x1d2575b0276f4e9087e6e07c2cb75aa1baafad127af4be5918ef8a2ef2fea8fc, + is_infinite: false + } + ); + assert_eq( + pedersen_hash_with_separator([1, 2, 3, 4, 5, 6, 7, 8], 8), 0x2f960e117482044dfc99d12fece2ef6862fba9242be4846c7c9a3e854325a55c + ); + assert_eq( + pedersen_commitment_with_separator([1, 2, 3, 4, 5, 6, 7, 8], 8), EmbeddedCurvePoint { + x: 0x1657737676968887fceb6dd516382ea13b3a2c557f509811cd86d5d1199bc443, + y: 0x1f39f0cb569040105fa1e2f156521e8b8e08261e635a2b210bdc94e8d6d65f77, + is_infinite: false + } + ); + assert_eq( + pedersen_hash_with_separator([1, 2, 3, 4, 5, 6, 7, 8, 9], 9), 0x0c96db0790602dcb166cc4699e2d306c479a76926b81c2cb2aaa92d249ec7be7 + ); + assert_eq( + pedersen_commitment_with_separator([1, 2, 3, 4, 5, 6, 7, 8, 9], 9), EmbeddedCurvePoint { + x: 0x0a3ceae42d14914a432aa60ec7fded4af7dad7dd4acdbf2908452675ec67e06d, + y: 0xfc19761eaaf621ad4aec9a8b2e84a4eceffdba78f60f8b9391b0bd9345a2f2, + is_infinite: false + } + ); + assert_eq( + pedersen_hash_with_separator([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 10), 0x2cd37505871bc460a62ea1e63c7fe51149df5d0801302cf1cbc48beb8dff7e94 + ); + assert_eq( + pedersen_commitment_with_separator([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 10), EmbeddedCurvePoint { + x: 0x2fb3f8b3d41ddde007c8c3c62550f9a9380ee546fcc639ffbb3fd30c8d8de30c, + y: 0x300783be23c446b11a4c0fabf6c91af148937cea15fcf5fb054abf7f752ee245, + is_infinite: false + } + ); } + diff --git a/noir/noir-repo/noir_stdlib/src/meta/mod.nr b/noir/noir-repo/noir_stdlib/src/meta/mod.nr index 1825888130b..ed3365d755c 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/mod.nr @@ -1 +1,10 @@ mod type_def; +mod trait_constraint; +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. +pub comptime fn unquote(code: Quoted) -> Quoted { + code +} diff --git a/noir/noir-repo/noir_stdlib/src/meta/quoted.nr b/noir/noir-repo/noir_stdlib/src/meta/quoted.nr new file mode 100644 index 00000000000..6273d64b10c --- /dev/null +++ b/noir/noir-repo/noir_stdlib/src/meta/quoted.nr @@ -0,0 +1,4 @@ +impl Quoted { + #[builtin(quoted_as_trait_constraint)] + fn as_trait_constraint(self) -> TraitConstraint {} +} diff --git a/noir/noir-repo/noir_stdlib/src/meta/trait_constraint.nr b/noir/noir-repo/noir_stdlib/src/meta/trait_constraint.nr new file mode 100644 index 00000000000..f0276608974 --- /dev/null +++ b/noir/noir-repo/noir_stdlib/src/meta/trait_constraint.nr @@ -0,0 +1,20 @@ +use crate::hash::{Hash, Hasher}; +use crate::cmp::Eq; + +impl Eq for TraitConstraint { + fn eq(self, other: Self) -> bool { + constraint_eq(self, other) + } +} + +impl Hash for TraitConstraint { + fn hash(self, state: &mut H) where H: Hasher { + state.write(constraint_hash(self)); + } +} + +#[builtin(trait_constraint_eq)] +fn constraint_eq(_first: TraitConstraint, _second: TraitConstraint) -> bool {} + +#[builtin(trait_constraint_hash)] +fn constraint_hash(_constraint: TraitConstraint) -> Field {} diff --git a/noir/noir-repo/scripts/bump-bb.sh b/noir/noir-repo/scripts/bump-bb.sh new file mode 100755 index 00000000000..36c1f78ca05 --- /dev/null +++ b/noir/noir-repo/scripts/bump-bb.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +BB_VERSION=$1 + +sed -i.bak "s/^VERSION=.*/VERSION=\"$BB_VERSION\"/" ./scripts/install_bb.sh && rm ./scripts/install_bb.sh.bak + +tmp=$(mktemp) +BACKEND_BARRETENBERG_PACKAGE_JSON=./tooling/noir_js_backend_barretenberg/package.json +jq --arg v $BB_VERSION '.dependencies."@aztec/bb.js" = $v' $BACKEND_BARRETENBERG_PACKAGE_JSON > $tmp && mv $tmp $BACKEND_BARRETENBERG_PACKAGE_JSON + +YARN_ENABLE_IMMUTABLE_INSTALLS=false yarn install diff --git a/noir/noir-repo/scripts/install_bb.sh b/noir/noir-repo/scripts/install_bb.sh index b0d55b6ff1d..95dcfdda880 100755 --- a/noir/noir-repo/scripts/install_bb.sh +++ b/noir/noir-repo/scripts/install_bb.sh @@ -1,6 +1,6 @@ #!/bin/bash -VERSION="0.43.0" +VERSION="0.46.1" BBUP_PATH=~/.bb/bbup diff --git a/noir/noir-repo/scripts/redo-typo-pr.sh b/noir/noir-repo/scripts/redo-typo-pr.sh index 416be65a449..4b3b93b48ed 100755 --- a/noir/noir-repo/scripts/redo-typo-pr.sh +++ b/noir/noir-repo/scripts/redo-typo-pr.sh @@ -16,16 +16,21 @@ gh pr checkout $ORIGINAL_PR_NUMBER echo "Creating new local branch $NEW_BRANCH" git checkout -b $NEW_BRANCH -# Step 3: Push the new branch to GitHub +# Step 3: Squash commits +echo "Squashing new local branch $NEW_BRANCH" +git reset --soft master +git add . +git commit -m "chore: typo fixes" + +# Step 4: Push the new branch to GitHub echo "Pushing new branch $NEW_BRANCH to GitHub" -git commit --amend --no-edit git push origin $NEW_BRANCH -# Step 4: create a new pull request +# Step 5: create a new pull request echo "Creating a new pull request for $NEW_BRANCH" gh pr create --base master --head $NEW_BRANCH --title "chore: redo typo PR by $AUTHOR" --body "Thanks $AUTHOR for https://github.com/$REPO/pull/$ORIGINAL_PR_NUMBER. Our policy is to redo typo changes to dissuade metric farming. This is an automated script." -# Step 5: Close the original PR +# Step 6: Close the original PR echo "Closing original PR #$ORIGINAL_PR_NUMBER" gh pr close $ORIGINAL_PR_NUMBER diff --git a/noir/noir-repo/test_programs/compile_failure/unary_not_on_field_type_variable/Nargo.toml b/noir/noir-repo/test_programs/compile_failure/unary_not_on_field_type_variable/Nargo.toml new file mode 100644 index 00000000000..ae5c9ecb9c9 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_failure/unary_not_on_field_type_variable/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "unary_not_on_field_type_variable" +type = "bin" +authors = [""] +compiler_version = "" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/compile_failure/unary_not_on_field_type_variable/src/main.nr b/noir/noir-repo/test_programs/compile_failure/unary_not_on_field_type_variable/src/main.nr new file mode 100644 index 00000000000..1023f753c32 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_failure/unary_not_on_field_type_variable/src/main.nr @@ -0,0 +1,4 @@ +fn main() { + let num: Field = 0; + assert_eq(!0, num); +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/attribute_args/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/attribute_args/Nargo.toml new file mode 100644 index 00000000000..8efe5d203d1 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/attribute_args/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "attribute_args" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] diff --git a/noir/noir-repo/test_programs/compile_success_empty/attribute_args/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/attribute_args/src/main.nr new file mode 100644 index 00000000000..44b9c20460f --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/attribute_args/src/main.nr @@ -0,0 +1,20 @@ +#[attr_with_args(a b, c d)] +#[varargs(one, two)] +#[varargs(one, two, three, four)] +struct Foo {} + +comptime fn attr_with_args(s: StructDefinition, a: Quoted, b: Quoted) { + // Ensure all variables are in scope. + // We can't print them since that breaks the test runner. + let _ = s; + let _ = a; + let _ = b; +} + +comptime fn varargs(s: StructDefinition, t: [Quoted]) { + let _ = s; + for _ in t {} + assert(t.len() < 5); +} + +fn main() {} diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_trait_constraint/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/comptime_trait_constraint/Nargo.toml new file mode 100644 index 00000000000..c7e28c053a7 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_trait_constraint/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_trait_constraint" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_trait_constraint/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_trait_constraint/src/main.nr new file mode 100644 index 00000000000..5c99f8c587e --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_trait_constraint/src/main.nr @@ -0,0 +1,39 @@ +use std::hash::{Hash, Hasher}; + +trait TraitWithGenerics { + fn foo(self) -> (A, B); +} + +fn main() { + comptime + { + let constraint1 = quote { Default }.as_trait_constraint(); + let constraint2 = quote { TraitWithGenerics }.as_trait_constraint(); + + assert(constraint1 != constraint2); + + let mut hasher = TestHasher { result: 0 }; + constraint1.hash(&mut hasher); + let hash1 = hasher.finish(); + + let mut hasher = TestHasher { result: 0 }; + constraint2.hash(&mut hasher); + let hash2 = hasher.finish(); + + assert(hash1 != hash2); + } +} + +comptime struct TestHasher { + result: Field, +} + +comptime impl Hasher for TestHasher { + comptime fn finish(self) -> Field { + self.result + } + + comptime fn write(&mut self, input: Field) { + self.result += input; + } +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/function_attribute/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/function_attribute/Nargo.toml new file mode 100644 index 00000000000..94b5c5da6a8 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/function_attribute/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "function_attribute" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/compile_success_empty/function_attribute/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/function_attribute/src/main.nr new file mode 100644 index 00000000000..ec22b730d3f --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/function_attribute/src/main.nr @@ -0,0 +1,18 @@ +#[function_attr] +fn foo() {} + +struct Foo {} + +comptime fn function_attr(_f: FunctionDefinition) -> Quoted { + quote { + impl Default for Foo { + fn default() -> Foo { + Foo {} + } + } + } +} + +fn main() { + let _ = Foo::default(); +} diff --git a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/regression_5428/Nargo.toml similarity index 66% rename from noir/noir-repo/test_programs/execution_success/verify_honk_proof/Nargo.toml rename to noir/noir-repo/test_programs/compile_success_empty/regression_5428/Nargo.toml index 8fce1bf44b6..7507b934d66 100644 --- a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/Nargo.toml +++ b/noir/noir-repo/test_programs/compile_success_empty/regression_5428/Nargo.toml @@ -1,5 +1,5 @@ [package] -name = "verify_honk_proof" +name = "regression_5428" type = "bin" authors = [""] diff --git a/noir/noir-repo/test_programs/compile_success_empty/regression_5428/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/regression_5428/src/main.nr new file mode 100644 index 00000000000..f01b89cbea4 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/regression_5428/src/main.nr @@ -0,0 +1,9 @@ +fn main() { + assert_true!(); +} + +comptime fn assert_true() -> Quoted { + let first = quote { assert( }; + let second = quote { true); }; + first.append(second) +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/trait_attribute/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/trait_attribute/Nargo.toml new file mode 100644 index 00000000000..c72fe5e3e89 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/trait_attribute/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "trait_attribute" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] diff --git a/noir/noir-repo/test_programs/compile_success_empty/trait_attribute/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/trait_attribute/src/main.nr new file mode 100644 index 00000000000..87f4893e3e5 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/trait_attribute/src/main.nr @@ -0,0 +1,19 @@ +#[trait_attr] +trait Foo { + fn foo(self) -> Self; +} + +comptime fn trait_attr(_t: TraitDefinition) -> Quoted { + quote { + impl Foo for Field { + fn foo(self) -> Self { + self + 1 + } + } + } +} + +fn main() { + assert_eq(1.foo(), 2); + assert_eq(10.foo(), 11); +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/unquote/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/unquote/Nargo.toml new file mode 100644 index 00000000000..68b2890e37a --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/unquote/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "unquote" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/compile_success_empty/unquote/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/unquote/src/main.nr new file mode 100644 index 00000000000..2717286b810 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/unquote/src/main.nr @@ -0,0 +1,4 @@ +fn main() { + std::meta::unquote!(quote { assert(true); }); + assert(std::meta::unquote!(quote { true })); +} diff --git a/noir/noir-repo/test_programs/compile_success_no_bug/check_uncostrained_regression/Nargo.toml b/noir/noir-repo/test_programs/compile_success_no_bug/check_uncostrained_regression/Nargo.toml new file mode 100644 index 00000000000..3c6b5d9688c --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_no_bug/check_uncostrained_regression/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "check_unconstrained_regression" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/compile_success_no_bug/check_uncostrained_regression/src/main.nr b/noir/noir-repo/test_programs/compile_success_no_bug/check_uncostrained_regression/src/main.nr new file mode 100644 index 00000000000..e93e068f432 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_no_bug/check_uncostrained_regression/src/main.nr @@ -0,0 +1,27 @@ +struct Trigger{ + x: u32, + y: Field, + z: [Field;3], +} +struct ResultType{ + a: u32, + b: Field, + c: [Field;3], +} + +unconstrained fn convert(trigger: Trigger) -> ResultType { + let result= ResultType { a: trigger.x + 1, b: trigger.y - 1 + trigger.z[2], c: [trigger.z[0], 0, trigger.z[1]] }; + result +} +impl Trigger { + fn execute(self) -> ResultType { + let result = convert(self); + assert(result.a == self.x + 1); + assert(result.b == self.y - 1 + self.z[2]); + assert(result.c[1] == 0); + result + } +} +fn main(x: Trigger) -> pub ResultType { + x.execute() +} diff --git a/noir/noir-repo/test_programs/execution_success/bench_ecdsa_secp256k1/Nargo.toml b/noir/noir-repo/test_programs/execution_success/bench_ecdsa_secp256k1/Nargo.toml new file mode 100644 index 00000000000..7e83251cc5a --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/bench_ecdsa_secp256k1/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "bench_ecdsa_secp256k1" +description = "ECDSA secp256k1 verification" +type = "bin" +authors = [""] + +[dependencies] diff --git a/noir/noir-repo/test_programs/execution_success/bench_ecdsa_secp256k1/Prover.toml b/noir/noir-repo/test_programs/execution_success/bench_ecdsa_secp256k1/Prover.toml new file mode 100644 index 00000000000..e78fc19cb71 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/bench_ecdsa_secp256k1/Prover.toml @@ -0,0 +1,169 @@ + +hashed_message = [ + 0x3a, + 0x73, + 0xf4, + 0x12, + 0x3a, + 0x5c, + 0xd2, + 0x12, + 0x1f, + 0x21, + 0xcd, + 0x7e, + 0x8d, + 0x35, + 0x88, + 0x35, + 0x47, + 0x69, + 0x49, + 0xd0, + 0x35, + 0xd9, + 0xc2, + 0xda, + 0x68, + 0x06, + 0xb4, + 0x63, + 0x3a, + 0xc8, + 0xc1, + 0xe2, +] +pub_key_x = [ + 0xa0, + 0x43, + 0x4d, + 0x9e, + 0x47, + 0xf3, + 0xc8, + 0x62, + 0x35, + 0x47, + 0x7c, + 0x7b, + 0x1a, + 0xe6, + 0xae, + 0x5d, + 0x34, + 0x42, + 0xd4, + 0x9b, + 0x19, + 0x43, + 0xc2, + 0xb7, + 0x52, + 0xa6, + 0x8e, + 0x2a, + 0x47, + 0xe2, + 0x47, + 0xc7, +] +pub_key_y = [ + 0x89, + 0x3a, + 0xba, + 0x42, + 0x54, + 0x19, + 0xbc, + 0x27, + 0xa3, + 0xb6, + 0xc7, + 0xe6, + 0x93, + 0xa2, + 0x4c, + 0x69, + 0x6f, + 0x79, + 0x4c, + 0x2e, + 0xd8, + 0x77, + 0xa1, + 0x59, + 0x3c, + 0xbe, + 0xe5, + 0x3b, + 0x03, + 0x73, + 0x68, + 0xd7, +] +signature = [ + 0xe5, + 0x08, + 0x1c, + 0x80, + 0xab, + 0x42, + 0x7d, + 0xc3, + 0x70, + 0x34, + 0x6f, + 0x4a, + 0x0e, + 0x31, + 0xaa, + 0x2b, + 0xad, + 0x8d, + 0x97, + 0x98, + 0xc3, + 0x80, + 0x61, + 0xdb, + 0x9a, + 0xe5, + 0x5a, + 0x4e, + 0x8d, + 0xf4, + 0x54, + 0xfd, + 0x28, + 0x11, + 0x98, + 0x94, + 0x34, + 0x4e, + 0x71, + 0xb7, + 0x87, + 0x70, + 0xcc, + 0x93, + 0x1d, + 0x61, + 0xf4, + 0x80, + 0xec, + 0xbb, + 0x0b, + 0x89, + 0xd6, + 0xeb, + 0x69, + 0x69, + 0x01, + 0x61, + 0xe4, + 0x9a, + 0x71, + 0x5f, + 0xcd, + 0x55, +] diff --git a/noir/noir-repo/test_programs/execution_success/bench_ecdsa_secp256k1/src/main.nr b/noir/noir-repo/test_programs/execution_success/bench_ecdsa_secp256k1/src/main.nr new file mode 100644 index 00000000000..60f182c7836 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/bench_ecdsa_secp256k1/src/main.nr @@ -0,0 +1,6 @@ +use dep::std; + +fn main(hashed_message: [u8; 32], pub_key_x: [u8; 32], pub_key_y: [u8; 32], signature: [u8; 64]) { + let valid_signature = std::ecdsa_secp256k1::verify_signature(pub_key_x, pub_key_y, signature, hashed_message); + assert(valid_signature); +} diff --git a/noir/noir-repo/test_programs/execution_success/binary_operator_overloading/Nargo.toml b/noir/noir-repo/test_programs/execution_success/binary_operator_overloading/Nargo.toml new file mode 100644 index 00000000000..a43f38bdf30 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/binary_operator_overloading/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "binary_operator_overloading" +type = "bin" +authors = [""] +compiler_version = ">=0.20.0" + +[dependencies] diff --git a/noir/noir-repo/test_programs/execution_success/operator_overloading/Prover.toml b/noir/noir-repo/test_programs/execution_success/binary_operator_overloading/Prover.toml similarity index 100% rename from noir/noir-repo/test_programs/execution_success/operator_overloading/Prover.toml rename to noir/noir-repo/test_programs/execution_success/binary_operator_overloading/Prover.toml diff --git a/noir/noir-repo/test_programs/execution_success/operator_overloading/src/main.nr b/noir/noir-repo/test_programs/execution_success/binary_operator_overloading/src/main.nr similarity index 100% rename from noir/noir-repo/test_programs/execution_success/operator_overloading/src/main.nr rename to noir/noir-repo/test_programs/execution_success/binary_operator_overloading/src/main.nr diff --git a/noir/noir-repo/test_programs/execution_success/comptime_slice_equality/Nargo.toml b/noir/noir-repo/test_programs/execution_success/comptime_slice_equality/Nargo.toml new file mode 100644 index 00000000000..72700d87d8b --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/comptime_slice_equality/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_slice_equality" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/comptime_slice_equality/src/main.nr b/noir/noir-repo/test_programs/execution_success/comptime_slice_equality/src/main.nr new file mode 100644 index 00000000000..83f82fca06f --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/comptime_slice_equality/src/main.nr @@ -0,0 +1,6 @@ +fn main() { + comptime + { + assert_eq(&[1], &[1]); + } +} diff --git a/noir/noir-repo/test_programs/execution_success/poseidon_bn254_hash_width_3/Nargo.toml b/noir/noir-repo/test_programs/execution_success/poseidon_bn254_hash_width_3/Nargo.toml new file mode 100644 index 00000000000..7047f0aeef2 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/poseidon_bn254_hash_width_3/Nargo.toml @@ -0,0 +1,9 @@ +[package] +name = "poseidon_bn254_hash_width_3" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" +# Test usage of `expression_width` field +expression_width = "3" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/poseidon_bn254_hash_width_3/Prover.toml b/noir/noir-repo/test_programs/execution_success/poseidon_bn254_hash_width_3/Prover.toml new file mode 100644 index 00000000000..8eecf9a3db2 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/poseidon_bn254_hash_width_3/Prover.toml @@ -0,0 +1,4 @@ +x1 = [1,2] +y1 = "0x115cc0f5e7d690413df64c6b9662e9cf2a3617f2743245519e19607a4417189a" +x2 = [1,2,3,4] +y2 = "0x299c867db6c1fdd79dcefa40e4510b9837e60ebb1ce0663dbaa525df65250465" diff --git a/noir/noir-repo/test_programs/execution_success/poseidon_bn254_hash_width_3/src/main.nr b/noir/noir-repo/test_programs/execution_success/poseidon_bn254_hash_width_3/src/main.nr new file mode 100644 index 00000000000..bb441a1ace3 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/poseidon_bn254_hash_width_3/src/main.nr @@ -0,0 +1,9 @@ +use std::hash::poseidon; + +fn main(x1: [Field; 2], y1: pub Field, x2: [Field; 4], y2: pub Field) { + let hash1 = poseidon::bn254::hash_2(x1); + assert(hash1 == y1); + + let hash2 = poseidon::bn254::hash_4(x2); + assert(hash2 == y2); +} diff --git a/noir/noir-repo/test_programs/execution_success/operator_overloading/Nargo.toml b/noir/noir-repo/test_programs/execution_success/unary_operator_overloading/Nargo.toml similarity index 70% rename from noir/noir-repo/test_programs/execution_success/operator_overloading/Nargo.toml rename to noir/noir-repo/test_programs/execution_success/unary_operator_overloading/Nargo.toml index 7f9f18ff567..ebc88faaaf4 100644 --- a/noir/noir-repo/test_programs/execution_success/operator_overloading/Nargo.toml +++ b/noir/noir-repo/test_programs/execution_success/unary_operator_overloading/Nargo.toml @@ -1,5 +1,5 @@ [package] -name = "operator_overloading" +name = "unary_operator_overloading" type = "bin" authors = [""] compiler_version = ">=0.20.0" diff --git a/noir/noir-repo/test_programs/execution_success/unary_operator_overloading/Prover.toml b/noir/noir-repo/test_programs/execution_success/unary_operator_overloading/Prover.toml new file mode 100644 index 00000000000..b95c23a4d31 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/unary_operator_overloading/Prover.toml @@ -0,0 +1 @@ +x = 3 diff --git a/noir/noir-repo/test_programs/execution_success/unary_operator_overloading/src/main.nr b/noir/noir-repo/test_programs/execution_success/unary_operator_overloading/src/main.nr new file mode 100644 index 00000000000..20cdac51434 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/unary_operator_overloading/src/main.nr @@ -0,0 +1,36 @@ +use std::ops::{Neg, Not}; + +// x = 3 +fn main(x: u32) { + let wx = Wrapper::new(x as i32); + let ex: i32 = 3; + + assert((-wx).inner == -ex); + assert((!wx).inner == !ex); + + // Check that it works with type variables (x's type isn't immediately known) + let x = 3; + assert(-3 == -x); +} + +struct Wrapper { + inner: i32 +} + +impl Wrapper { + fn new(inner: i32) -> Self { + Wrapper { inner } + } +} + +impl Neg for Wrapper { + fn neg(self) -> Wrapper { + Wrapper::new(-self.inner) + } +} + +impl Not for Wrapper { + fn not(self) -> Wrapper { + Wrapper::new(!self.inner) + } +} diff --git a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/Prover.toml b/noir/noir-repo/test_programs/execution_success/verify_honk_proof/Prover.toml deleted file mode 100644 index 921b69e100a..00000000000 --- a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/Prover.toml +++ /dev/null @@ -1,4 +0,0 @@ -key_hash = "0x096129b1c6e108252fc5c829c4cc9b7e8f0d1fd9f29c2532b563d6396645e08f" -proof = ["0x0000000000000000000000000000000000000000000000000000000000000020","0x0000000000000000000000000000000000000000000000000000000000000011","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000042ab5d6d1986846cf","0x00000000000000000000000000000000000000000000000b75c020998797da78","0x0000000000000000000000000000000000000000000000005a107acb64952eca","0x000000000000000000000000000000000000000000000000000031e97a575e9d","0x00000000000000000000000000000000000000000000000b5666547acf8bd5a4","0x00000000000000000000000000000000000000000000000c410db10a01750aeb","0x00000000000000000000000000000000000000000000000d722669117f9758a4","0x000000000000000000000000000000000000000000000000000178cbf4206471","0x000000000000000000000000000000000000000000000000e91b8a11e7842c38","0x000000000000000000000000000000000000000000000007fd51009034b3357f","0x000000000000000000000000000000000000000000000009889939f81e9c7402","0x0000000000000000000000000000000000000000000000000000f94656a2ca48","0x000000000000000000000000000000000000000000000006fb128b46c1ddb67f","0x0000000000000000000000000000000000000000000000093fe27776f50224bd","0x000000000000000000000000000000000000000000000004a0c80c0da527a081","0x0000000000000000000000000000000000000000000000000001b52c2020d746","0x0000000000000000000000000000005a9bae947e1e91af9e4033d8d6aa6ed632","0x000000000000000000000000000000000025e485e013446d4ac7981c88ba6ecc","0x000000000000000000000000000000ff1e0496e30ab24a63b32b2d1120b76e62","0x00000000000000000000000000000000001afe0a8a685d7cd85d1010e55d9d7c","0x000000000000000000000000000000b0804efd6573805f991458295f510a2004","0x00000000000000000000000000000000000c81a178016e2fe18605022d5a8b0e","0x000000000000000000000000000000eba51e76eb1cfff60a53a0092a3c3dea47","0x000000000000000000000000000000000022e7466247b533282f5936ac4e6c15","0x00000000000000000000000000000071b1d76edf770edff98f00ff4deec264cd","0x00000000000000000000000000000000001e48128e68794d8861fcbb2986a383","0x000000000000000000000000000000d3a2af4915ae6d86b097adc377fafda2d4","0x000000000000000000000000000000000006359de9ca452dab3a4f1f8d9c9d98","0x0000000000000000000000000000000d9d719a8b9f020ad3642d60fe704e696f","0x00000000000000000000000000000000000ddfdbbdefc4ac1580ed38e12cfa49","0x0000000000000000000000000000008289fe9754ce48cd01b7be96a861b5e157","0x00000000000000000000000000000000000ff3e0896bdea021253b3d360fa678","0x0000000000000000000000000000000d9d719a8b9f020ad3642d60fe704e696f","0x00000000000000000000000000000000000ddfdbbdefc4ac1580ed38e12cfa49","0x0000000000000000000000000000008289fe9754ce48cd01b7be96a861b5e157","0x00000000000000000000000000000000000ff3e0896bdea021253b3d360fa678","0x000000000000000000000000000000f968b227a358a305607f3efc933823d288","0x00000000000000000000000000000000000eaf8adb390375a76d95e918b65e08","0x000000000000000000000000000000bb34b4b447aae56f5e24f81c3acd6d547f","0x00000000000000000000000000000000002175d012746260ebcfe339a91a81e1","0x0000000000000000000000000000005b739ed2075f2b046062b8fc6a2d1e9863","0x00000000000000000000000000000000001285cd1030d338c0e1603b4da2c838","0x00000000000000000000000000000027447d6c281eb38b2b937af4a516d60c04","0x000000000000000000000000000000000019bc3d980465fbb4a656a74296fc58","0x000000000000000000000000000000b484788ace8f7df86dd5e325d2e9b12599","0x00000000000000000000000000000000000a2ca0d10eb7b767114ae230b728d3","0x000000000000000000000000000000c6dfc7092f16f95795e437664498b88d53","0x0000000000000000000000000000000000131067b4e4d95a4f6f8cf5c9b5450a","0x0f413f22eec51f2a02800e0cafaeec1d92d744fbbaef213c687b9edabd6985f5","0x21230f4ff26c80ffb5d037a9d1d26c3f955ca34cbeca4f54db6656b932967a0c","0x0521f877fe35535767f99597cc50effbd283dcae6812ee0a7620d796ccbfd642","0x202b01350a9cc5c20ec0f3eaada338c0a3b793811bd539418ffa3cc4302615e2","0x2d1214d9b0d41058ad4a172d9c0aecc5bdabe95e687c3465050c6b5396509be4","0x1113b344a151b0af091cb28d728b752ebb4865da6cd7ee68471b961ca5cf69b9","0x2aa66d0954bb83e17bd5c9928d3aa7a7df75d741d409f7c15ba596804ba643fb","0x2e26bc7a530771ef7a95d5360d537e41cf94d8a0942764ff09881c107f91a106","0x0f14f32b921bb63ad1df00adab7c82af58ea8aa7f353f14b281208d8c5fab504","0x13429515c0c53b6502bbcdf545defb3cb69a986c9263e070fcbb397391aae1a3","0x1f21cac5e2f262afc1006a21454cc6bcb018c44e53ad8ab61cebbac99e539176","0x2a9886a6ddc8a61b097c668cd362fc8acdee8dde74f7b1af192c3e060bb2948f","0x2d718181e408ead2e9bcd30a84ad1fccbaf8d48ab6d1820bad4933d284b503c4","0x2634c1aafc902f14508f34d3d7e9d485f42d1a4c95b5a1ef73711ed0d3c68d77","0x092ede9777e6472ce5ffd8c963d466006189e960e2c591d338dc8d4af1a057fb","0x1cba45b17fd24f1cb1b4ab7b83eee741f6c77ba70a497dc4de259eceb7d5ea26","0x246e887c7bf2e17f919b2393b6e9b00b33e8822d862544a775aac05cb7bff710","0x04c3f539fe8689971948afcb437f1ecbd444a5bddaca1c8a450348dcd8480047","0x20c6a423ae4fd58e8951aa378d02d77baf90508ceb48856db2319d70938b186e","0x1bcf8786b554b3316d8ebdbc9d006a4e5d4865aad512ffd404b7f83550d3d030","0x09ab038260518f0970564afcd6bf22e2abf6b1fa5e12a327bbf195b6ca5edd78","0x1024e32554746f89c195286ba6ccfc9765e5d14bbe8064bc6fdf22d16ec6b495","0x17706656f8dbd7e47bb257a6428f0cb7278ea02fa9e6ce431d7bcc9133fba9c7","0x25a3e8a33c15ef2a4dd16313a6049bf1d468b4cdc141f238f2d51a1e8e1c22b3","0x1198863f08006edb27aee23164fb117a4ddec1bf1ed89807aa907e5cd24bf068","0x1862b4856b5b4d4a064f873e221703e4e2cd1ebfca1337dedca56485c38ed5a0","0x062214af1ea6dd6bf8895b92d394571c43970b6f967e1c794624d96071b25ad3","0x1e5be9428ddcf1f9b0cbafc28101e792ec5cf73852b0cd0b84fbff71b4490e09","0x2d4189bea5b1e30f63c64bd26df82f18bcaf885ec8887b54634b2557869ce87f","0x0f2e5d9a908850e9d44925e17d8b12d1adb1ed029799c9b5858598504242bbc0","0x3050dc85746a57931d99f3f35e77c2ba561fba0baa018b79ff1fd544026833ae","0x2a591a32437e5e0b875a137fd868bd1b6dbc003ff1b661f26e00627cc7c5cf47","0x27946841e1670ad9c65717016d0cedf524724217236e81b9fd0a264a36ebfb0e","0x0fc396e9d19d6e68e289602e292ee345542d0d28bf6de34fa62cc577cbdfb1df","0x08e7433a07a44c0c9c4dd4b273a2685bbd1a91fd5cf2b43409458fab42a23e1b","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x12bd9bfb029c3503a5c6deea87b0a0f11bb9f7ea584af2d48f3e48d7e09247ae","0x2ccc4810748c0a82dfc0f063d0b8c7999ffe9474653080e6ef92b3cb7a428784","0x08eb574d7fecadadb508c8bd35fdad06b99110609d679763c2e3645229b1b95a","0x0f1a65e747c8021ed7c454a4be1e89b1bce66ead9ed980fa98a7a050eafe98a1","0x1c8ff9e36684ec71614dee4c17859b06c742089f6029d3694a16e00dac9b57f1","0x0303101a8ba712aeca4da85b767ab8d3ecf489ec7d746f8ee20041717cc000e9","0x0aaf64c65e7088e5596108c9601467911fea809ca6540d79af77e6e66e36cd99","0x17caf164ce74ea7edfb1390e07763d2197797ec26661b92cde18a98d61d2fddc","0x18cb055c7ad6d01437725bb457681d81f3ecadc4f35d838a3c13daf25a44456a","0x2d78602b8bbcd32b36a99a6e2d248e7fe044ef1b50813133370412f9ef5299f0","0x2b139276ea86d426a115479e4154f72a6bd83a6253bf13e9670dc6b4664378f0","0x127c7837b384902c39a104036c09546728571c46c8166b1b9b13b3a615ebb781","0x05faa4816f83cf0189a482ad943c94b9ec6474002f2b327f8698763ad0ea0985","0x2f90359cc30ee693fb3aced96523cf7aebd152c22329eee56a398d9a4ac0628e","0x0a71beaf17a59c5a238f04c1f203848d87502c5057a78c13f0cfb0f9876e7714","0x2696c1e6d089556adaeb95c8a5e3065b00a393a38c2d69e9bd6ce8cdc49d87da","0x1f3d165a7dc6564a036e451eb9cb7f1e1cb1e6d29daa75e3f135ea3e58a79ccd","0x1473a660819bdd838d56122b72b32b267211e9f1103239480ec50fa85c9e1035","0x0a8ccaeb22451f391b3fc3467c8e6e900270a7afb7b510e8acf5a4f06f1c0888","0x03b3080afc0658cc87e307758cebc171921f43eca159b9dedf7f72aa8dd926bd","0x2dd7d6663fa0e1755dfafac352c361fcd64c7f4d53627e3646870ac169cc4a07","0x1ec54b883f5f35ccad0e75695af20790d9860104095bab34c9bf01628dd40cb9","0x193dff50f83c241f7a9e087a29ce72ecf3f6d8563593f786dcd04c32bcfd4ced","0x135122c0dae26cda8ca1c09de8225064ad86d10423ab0aaa53b481aa4626e1d6","0x08d5a56cbfab5aeed56d3cdd7fb6b30fc26b0c1a5b63fccd7fa44c53ba6fd35a","0x0d12f126dfa2daad3726d00ca339284cc22e36c6d81bb7a4b95c6f9598b60e7c","0x2e8b24bbdf2fd839d3c7cae1f0eeb96bfcfaeef30b27476f2fafcb17da78cd5e","0x2364acfe0cea39b7f749c5f303b99504977357925f810f684c60f35d16315211","0x06ca062eb70b8c51cfac35345e7b6b51f33a8ec9ebe204fb9b4911200bf508b7","0x266c0aa1ccb97186815bf69084f600d06ddd934e59a38dfe602ee5d6b9487f22","0x1d817537a49c6d0e3b4b65c6665334b91d7593142e60065048be9e55ceb5e7ab","0x05e9b7256a368df053c691952b59e9327a7c12ed322bbd6f72c669b9b9c26d49","0x05e9b7256a368df053c691952b59e9327a7c12ed322bbd6f72c669b9b9c26d49","0x25b77026673a1e613e50df0e88fb510973739d5f9064bd364079a9f884209632","0x25c9bc7a3f6aae3d43ff68b5614b34b5eaceff37157b37347995d231784ac1fd","0x085f69baef22680ae15f4801ef4361ebe9c7fc24a94b5bc2527dce8fb705439e","0x0d7c6b9ce31bfc32238a205455baf5ffe99cd30eb0f7bb5b504e1d4501e01382","0x1001a8cc4bc1221c814fba0eddcf3c40619b133373640c600de5bed0a0a05b10","0x20f5894be90e52977cb70f4f4cbd5101693db0360848939750db7e91109d54b6","0x22c09cb26db43f0599408b4daed0f4f496c66424e6affa41c14387d8e0af851b","0x24e5f41357798432426a9549d71e8cc681eaebacbe87f6e3bf38e85de5aa2f3d","0x06eb90100c736fbf2b87432d7821ecdc0b365024739bc36363d48b905973f5b9","0x000000000000000000000000000000ece6d09ed58e9f5661c01140b10558a8c2","0x000000000000000000000000000000000012b6e4f37adcb34b8e88ff8b6eebce","0x000000000000000000000000000000b226a2bb93593fa1fab19a44767828a3f5","0x00000000000000000000000000000000002b5b518342030543092e1428a7e33c","0x00000000000000000000000000000022ba33857034a0574c216eb3c1ddff3025","0x00000000000000000000000000000000001918e58df857985a7cf9eae7802165","0x00000000000000000000000000000045c2d840b96fb6106cc14dcad89dd5f675","0x00000000000000000000000000000000000afdfac1e3a1febdd0208867d44f98","0x00000000000000000000000000000042ebed6c5ec45d794f119aef24c192af0f","0x00000000000000000000000000000000002d05ef250900bbcc5751bbeb210d6a","0x00000000000000000000000000000060d604bdda48eecc90ed065bd9770e1323","0x00000000000000000000000000000000001fed91c63d0041660c1cbc84c2ffbb","0x00000000000000000000000000000054196b549cde36092e8184c7f4f7d878de","0x00000000000000000000000000000000000153f26a01294329922b492485cc31","0x00000000000000000000000000000056ebea579d10dbb440f0222931df2c0059","0x00000000000000000000000000000000000d2cbc61ce5b7cdd7fce398da4637b","0x000000000000000000000000000000e2b9512360b9797d96675d8a2fd2f7aa5d","0x000000000000000000000000000000000025742905f105ff895f74e7c3daa34a","0x000000000000000000000000000000a2dd7df55db59bd41b83518d4403fbc382","0x00000000000000000000000000000000002c1d9c3cbb9371d4cc4e9f900b9a46","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x000000000000000000000000000000bcf12ae40c9425c3e67654b84181f90502","0x00000000000000000000000000000000000b6d3faa8a71ff6ef1aa887b7307cf","0x0000000000000000000000000000001f6f719acc23b8f84808c0275d61cfb456","0x0000000000000000000000000000000000296030933ed0c134457ae71c393dfe","0x000000000000000000000000000000ebe1a57cdd7d3d763289b40ef5ed9a7ae0","0x000000000000000000000000000000000010f30483e7df51fca2316d3367603c","0x0000000000000000000000000000000149b7b283ab18060618c8e051864c03cd","0x00000000000000000000000000000000001ef7763235a3a25e241a5f06704dc3"] -public_inputs = ["0x0000000000000000000000000000000000000000000000000000000000000003"] -verification_key = ["0x0000000000000000000000000000000000000000000000000000000000000020","0x0000000000000000000000000000000000000000000000000000000000000011","0x0000000000000000000000000000000000000000000000000000000000000001","0x00000000000000000000000000000060e430ad1c23bfcf3514323aae3f206e84","0x00000000000000000000000000000000001b5c3ff4c2458d8f481b1c068f27ae","0x000000000000000000000000000000bb510ab2112def34980e4fc6998ad9dd16","0x00000000000000000000000000000000000576e7c105b43e061e13cb877fefe1","0x000000000000000000000000000000ced074785d11857b065d8199e6669a601c","0x00000000000000000000000000000000000053b48a4098c1c0ae268f273952f7","0x000000000000000000000000000000d1d4b26e941db8168cee8f6de548ae0fd8","0x00000000000000000000000000000000001a9adf5a6dadc3d948bb61dfd63f4c","0x0000000000000000000000000000009ce1faac6f8de6ebb18f1db17372c82ad5","0x00000000000000000000000000000000002002681bb417184b2df070a16a3858","0x000000000000000000000000000000161baa651a8092e0e84725594de5aba511","0x00000000000000000000000000000000000be0064399c2a1efff9eb0cdcb2223","0x0000000000000000000000000000008673be6fd1bdbe980a29d8c1ded54381e7","0x000000000000000000000000000000000008a5158a7d9648cf1d234524c9fa0c","0x0000000000000000000000000000002b4fce6e4b1c72062b296d49bca2aa4130","0x00000000000000000000000000000000002e45a9eff4b6769e55fb710cded44f","0x00000000000000000000000000000072b85bf733758b76bcf97333efb85a23e3","0x000000000000000000000000000000000017da0ea508994fc82862715e4b5592","0x00000000000000000000000000000094fa74695cf058dba8ff35aec95456c6c3","0x0000000000000000000000000000000000211acddb851061c24b8f159e832bd1","0x000000000000000000000000000000303b5e5c531384b9a792e11702ad3bcab0","0x00000000000000000000000000000000000d336dff51a60b8833d5d7f6d4314c","0x0000000000000000000000000000009f825dde88092070747180d581c342444a","0x0000000000000000000000000000000000237fbd6511a03cca8cac01b555fe01","0x0000000000000000000000000000007c313205159495df6d8de292079a4844ff","0x000000000000000000000000000000000018facdfc468530dd45e8f7a1d38ce9","0x0000000000000000000000000000000d1ce33446fc3dc4ab40ca38d92dac74e1","0x00000000000000000000000000000000000852d8e3e0e8f4435af3e94222688b","0x0000000000000000000000000000006c04ee19ec1dfec87ed47d6d04aa158de2","0x000000000000000000000000000000000013240f97a584b45184c8ec31319b5f","0x000000000000000000000000000000cefb5d240b07ceb4be26ea429b6dc9d9e0","0x00000000000000000000000000000000002dad22022121d689f57fb38ca21349","0x000000000000000000000000000000c9f189f2a91aeb664ce376d8b157ba98f8","0x00000000000000000000000000000000002531a51ad54f124d58094b219818d2","0x000000000000000000000000000000ef1e6db71809307f677677e62b4163f556","0x0000000000000000000000000000000000272da4396fb2a7ee0638b9140e523d","0x0000000000000000000000000000002e54c0244a7732c87bc4712a76dd8c83fb","0x000000000000000000000000000000000007db77b3e04b7eba9643da57cbbe4d","0x000000000000000000000000000000e0dfe1ddd7f74ae0d636c910c3e85830d8","0x00000000000000000000000000000000000466fa9b57ec4664abd1505b490862","0x0000000000000000000000000000009ee55ae8a32fe5384c79907067cc27192e","0x00000000000000000000000000000000000799d0e465cec07ecb5238c854e830","0x0000000000000000000000000000001d5910ad361e76e1c241247a823733c39f","0x00000000000000000000000000000000002b03f2ccf7507564da2e6678bef8fe","0x000000000000000000000000000000231147211b3c75e1f47d150e4bbd2fb22e","0x00000000000000000000000000000000000d19ee104a10d3c701cfd87473cbbe","0x0000000000000000000000000000006705f3f382637d00f698e2c5c94ed05ae9","0x00000000000000000000000000000000000b9c792da28bb60601dd7ce4b74e68","0x000000000000000000000000000000ac5acc8cc21e4ddb225c510670f80c80b3","0x00000000000000000000000000000000002da9d3fa57343e6998aba19429b9fa","0x0000000000000000000000000000004bacbf54b7c17a560df0af18b6d0d527be","0x00000000000000000000000000000000000faea33aeca2025b22c288964b21eb","0x000000000000000000000000000000492e756298d68d6e95de096055cc0336c3","0x00000000000000000000000000000000001a12a12f004859e5a3675c7315121b","0x000000000000000000000000000000893d521d512f30e6d32afbbc0cecd8ee00","0x00000000000000000000000000000000001674b3c1ef12c6da690631e0d86c04","0x000000000000000000000000000000aa6cb02a52e7a613873d4ac9b411349945","0x00000000000000000000000000000000001ecb1fe9c493add46751f9940f73e1","0x00000000000000000000000000000045b3d362ca82cba69fb2b9c733a5b8c351","0x000000000000000000000000000000000019a683586af466e331945b732d2f8c","0x000000000000000000000000000000fc79b052dfdfe67c0ecfc06b4267ffd694","0x00000000000000000000000000000000001336a70c396393038d5e9913744ac2","0x0000000000000000000000000000005450d29af1e9438e91cd33ddeb2548226e","0x000000000000000000000000000000000000993a602891cfd0e6f6ecf7404933","0x000000000000000000000000000000498efddab90a32e9b2db729ed6e9b40192","0x00000000000000000000000000000000002425efebe9628c63ca6fc28bdb5901","0x000000000000000000000000000000d8488157f875a21ab5f93f1c2b641f3de9","0x0000000000000000000000000000000000290f95ada3936604dc4b14df7504e3","0x0000000000000000000000000000005d6902187f3ed60dcce06fca211b40329a","0x00000000000000000000000000000000002b5870a6ba0b20aaa0178e5adfbc36","0x000000000000000000000000000000e5c2519171fa0e548fc3c4966ffc1ce570","0x00000000000000000000000000000000001cb8d8f4793b7debbdc429389dbf2d","0x000000000000000000000000000000a3ee22dd60456277b86c32a18982dcb185","0x00000000000000000000000000000000002493c99a3d068b03f8f2b8d28b57ce","0x000000000000000000000000000000f6c3731486320082c20ec71bbdc92196c1","0x00000000000000000000000000000000001ded39c4c8366469843cd63f09ecac","0x000000000000000000000000000000494997477ab161763e46601d95844837ef","0x00000000000000000000000000000000002e0cddbc5712d79b59cb3b41ebbcdd","0x000000000000000000000000000000426db4c64531d350750df62dbbc41a1bd9","0x0000000000000000000000000000000000303126892f664d8d505964d14315ec","0x00000000000000000000000000000076a6b2c6040c0c62bd59acfe3e3e125672","0x000000000000000000000000000000000000874a5ad262eecc6b565e0b085074","0x000000000000000000000000000000ef082fb517183c9c6841c2b8ef2ca1df04","0x0000000000000000000000000000000000127b2a745a1b74968c3edc18982b9b","0x000000000000000000000000000000c9efd4f8c3d56e1eb23d789a8f710d5be6","0x000000000000000000000000000000000015a18748490ff4c2b1871081954e86","0x000000000000000000000000000000a0011ef987dc016ab110eacd554a1d8bbf","0x00000000000000000000000000000000002097c84955059442a95df075833071","0x000000000000000000000000000000d38e9426ad3085b68b00a93c17897c2877","0x00000000000000000000000000000000002aecd48089890ea0798eb952c66824","0x00000000000000000000000000000078d8a9ce405ce559f441f2e71477ff3ddb","0x00000000000000000000000000000000001216bdb2f0d961bb8a7a23331d2150","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x000000000000000000000000000000ee40d90bea71fba7a412dd61fcf34e8ceb","0x0000000000000000000000000000000000140b0936c323fd2471155617b6af56","0x0000000000000000000000000000002b90071823185c5ff8e440fd3d73b6fefc","0x00000000000000000000000000000000002b6c10790a5f6631c87d652e059df4"] diff --git a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/src/main.nr b/noir/noir-repo/test_programs/execution_success/verify_honk_proof/src/main.nr deleted file mode 100644 index ecfd18f3837..00000000000 --- a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/src/main.nr +++ /dev/null @@ -1,21 +0,0 @@ - -// This circuit aggregates a single Honk proof from `assert_statement_recursive`. -global SIZE_OF_PROOF_IF_LOGN_IS_28 : u32 = 409; -fn main( - verification_key: [Field; 103], - // This is the proof without public inputs attached. - // - // This means: the size of this does not change with the number of public inputs. - proof: [Field; SIZE_OF_PROOF_IF_LOGN_IS_28], - public_inputs: pub [Field; 1], - // This is currently not public. It is fine given that the vk is a part of the circuit definition. - // I believe we want to eventually make it public too though. - key_hash: Field -) { - std::verify_proof( - verification_key.as_slice(), - proof.as_slice(), - public_inputs.as_slice(), - key_hash - ); -} diff --git a/noir/noir-repo/test_programs/gates_report.sh b/noir/noir-repo/test_programs/gates_report.sh index b33e81b037c..6c901ff24bc 100755 --- a/noir/noir-repo/test_programs/gates_report.sh +++ b/noir/noir-repo/test_programs/gates_report.sh @@ -4,7 +4,7 @@ set -e BACKEND=${BACKEND:-bb} # These tests are incompatible with gas reporting -excluded_dirs=("workspace" "workspace_default_member" "double_verify_nested_proof") +excluded_dirs=("workspace" "workspace_default_member" "databus") current_dir=$(pwd) artifacts_path="$current_dir/acir_artifacts" @@ -18,6 +18,10 @@ NUM_ARTIFACTS=$(ls -1q "$artifacts_path" | wc -l) ITER="1" for pathname in $test_dirs; do ARTIFACT_NAME=$(basename "$pathname") + if [[ " ${excluded_dirs[@]} " =~ "$ARTIFACT_NAME" ]]; then + ITER=$(( $ITER + 1 )) + continue + fi GATES_INFO=$($BACKEND gates -b "$artifacts_path/$ARTIFACT_NAME/target/program.json") MAIN_FUNCTION_INFO=$(echo $GATES_INFO | jq -r '.functions[0] | .name = "main"') diff --git a/noir/noir-repo/tooling/debugger/Cargo.toml b/noir/noir-repo/tooling/debugger/Cargo.toml index 05b28f9d95a..540d6d11bc0 100644 --- a/noir/noir-repo/tooling/debugger/Cargo.toml +++ b/noir/noir-repo/tooling/debugger/Cargo.toml @@ -14,7 +14,7 @@ build-data.workspace = true acvm.workspace = true fm.workspace = true nargo.workspace = true -noirc_frontend.workspace = true +noirc_frontend = { workspace = true, features = ["bn254"] } noirc_printable_type.workspace = true noirc_errors.workspace = true noirc_driver.workspace = true diff --git a/noir/noir-repo/tooling/debugger/ignored-tests.txt b/noir/noir-repo/tooling/debugger/ignored-tests.txt index a3971d437fb..745971d9b28 100644 --- a/noir/noir-repo/tooling/debugger/ignored-tests.txt +++ b/noir/noir-repo/tooling/debugger/ignored-tests.txt @@ -1,18 +1,6 @@ -bigint brillig_references -brillig_to_bytes_integration debug_logs -fold_after_inlined_calls -fold_basic -fold_basic_nested_call -fold_call_witness_condition -fold_complex_outputs -fold_distinct_return -fold_fibonacci -fold_numeric_generic_poseidon is_unconstrained macros -modulus references -regression_4709 -to_bytes_integration +regression_4709 \ No newline at end of file diff --git a/noir/noir-repo/tooling/debugger/src/context.rs b/noir/noir-repo/tooling/debugger/src/context.rs index cb36988bf0b..d18ec5f0786 100644 --- a/noir/noir-repo/tooling/debugger/src/context.rs +++ b/noir/noir-repo/tooling/debugger/src/context.rs @@ -1,10 +1,11 @@ use crate::foreign_calls::DebugForeignCallExecutor; use acvm::acir::circuit::brillig::BrilligBytecode; use acvm::acir::circuit::{Circuit, Opcode, OpcodeLocation}; -use acvm::acir::native_types::{Witness, WitnessMap}; +use acvm::acir::native_types::{Witness, WitnessMap, WitnessStack}; use acvm::brillig_vm::MemoryValue; use acvm::pwg::{ - ACVMStatus, BrilligSolver, BrilligSolverStatus, ForeignCallWaitInfo, StepResult, ACVM, + ACVMStatus, AcirCallWaitInfo, BrilligSolver, BrilligSolverStatus, ForeignCallWaitInfo, + OpcodeNotSolvable, StepResult, ACVM, }; use acvm::{BlackBoxFunctionSolver, FieldElement}; @@ -15,56 +16,235 @@ use nargo::NargoError; use noirc_artifacts::debug::{DebugArtifact, StackFrame}; use noirc_driver::DebugFile; +use thiserror::Error; + use std::collections::BTreeMap; use std::collections::{hash_set::Iter, HashSet}; +/// A Noir program is composed by +/// `n` ACIR circuits +/// |_ `m` ACIR opcodes +/// |_ Acir call +/// |_ Acir Brillig function invocation +/// |_ `p` Brillig opcodes +/// +/// The purpose of this structure is to map the opcode locations in ACIR circuits into +/// a flat contiguous address space to be able to expose them to the DAP interface. +/// In this address space, the ACIR circuits are laid out one after the other, and +/// Brillig functions called from such circuits are expanded inline, replacing +/// the `BrilligCall` ACIR opcode. +/// +/// `addresses: Vec>` +/// * The outer vec is `n` sized - one element per ACIR circuit +/// * Each nested vec is `m` sized - one element per ACIR opcode in circuit +/// * Each element is the "virtual address" of such opcode +/// +/// For flattening we map each ACIR circuit and ACIR opcode with a sequential address number +/// We start by assigning 0 to the very first ACIR opcode and then start accumulating by +/// traversing by depth-first +/// +/// Even if the address space is continuous, the `addresses` tree only +/// keeps track of the ACIR opcodes, since the Brillig opcode addresses can be +/// calculated from the initial opcode address. +/// As a result the flattened indexed addresses list may have "holes". +/// +/// If between two consequent `addresses` nodes there is a "hole" (an address jump), +/// this means that the first one is actually a ACIR Brillig call +/// which has as many brillig opcodes as `second_address - first_address` +/// +#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub struct AddressMap { + addresses: Vec>, + + // virtual address of the last opcode of the program + last_valid_address: usize, +} + +impl AddressMap { + pub(super) fn new( + circuits: &[Circuit], + unconstrained_functions: &[BrilligBytecode], + ) -> Self { + let opcode_address_size = |opcode: &Opcode| { + if let Opcode::BrilligCall { id, .. } = opcode { + unconstrained_functions[*id as usize].bytecode.len() + } else { + 1 + } + }; + + let mut addresses = Vec::with_capacity(circuits.len()); + let mut next_address = 0usize; + + for circuit in circuits { + let mut circuit_addresses = Vec::with_capacity(circuit.opcodes.len()); + for opcode in &circuit.opcodes { + circuit_addresses.push(next_address); + next_address += opcode_address_size(opcode); + } + addresses.push(circuit_addresses); + } + + Self { addresses, last_valid_address: next_address - 1 } + } + + /// Returns the absolute address of the opcode at the given location. + /// Absolute here means accounting for nested Brillig opcodes in BrilligCall + /// opcodes. + pub fn debug_location_to_address(&self, location: &DebugLocation) -> usize { + let circuit_addresses = &self.addresses[location.circuit_id as usize]; + match &location.opcode_location { + OpcodeLocation::Acir(acir_index) => circuit_addresses[*acir_index], + OpcodeLocation::Brillig { acir_index, brillig_index } => { + circuit_addresses[*acir_index] + *brillig_index + } + } + } + + pub fn address_to_debug_location(&self, address: usize) -> Option { + if address > self.last_valid_address { + return None; + } + // We binary search if the given address is the first opcode address of each circuit id + // if is not, this means that the address itself is "contained" in the previous + // circuit indicated by `Err(insert_index)` + let circuit_id = + match self.addresses.binary_search_by(|addresses| addresses[0].cmp(&address)) { + Ok(found_index) => found_index, + // This means that the address is not in `insert_index` circuit + // because is an `Err`, so it must be included in previous circuit vec of opcodes + Err(insert_index) => insert_index - 1, + }; + + // We binary search among the selected `circuit_id`` list of opcodes + // If Err(insert_index) this means that the given address + // is a Brillig addresses that's contained in previous index ACIR opcode index + let opcode_location = match self.addresses[circuit_id].binary_search(&address) { + Ok(found_index) => OpcodeLocation::Acir(found_index), + Err(insert_index) => { + let acir_index = insert_index - 1; + let base_offset = self.addresses[circuit_id][acir_index]; + let brillig_index = address - base_offset; + OpcodeLocation::Brillig { acir_index, brillig_index } + } + }; + Some(DebugLocation { circuit_id: circuit_id as u32, opcode_location }) + } +} + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub struct DebugLocation { + pub circuit_id: u32, + pub opcode_location: OpcodeLocation, +} + +impl std::fmt::Display for DebugLocation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let circuit_id = self.circuit_id; + match self.opcode_location { + OpcodeLocation::Acir(index) => write!(f, "{circuit_id}:{index}"), + OpcodeLocation::Brillig { acir_index, brillig_index } => { + write!(f, "{circuit_id}:{acir_index}.{brillig_index}") + } + } + } +} + +#[derive(Error, Debug)] +pub enum DebugLocationFromStrError { + #[error("Invalid debug location string: {0}")] + InvalidDebugLocationString(String), +} + +impl std::str::FromStr for DebugLocation { + type Err = DebugLocationFromStrError; + fn from_str(s: &str) -> Result { + let parts: Vec<_> = s.split(':').collect(); + let error = Err(DebugLocationFromStrError::InvalidDebugLocationString(s.to_string())); + + match parts.len() { + 1 => OpcodeLocation::from_str(parts[0]).map_or(error, |opcode_location| { + Ok(DebugLocation { circuit_id: 0, opcode_location }) + }), + 2 => { + let first_part = parts[0].parse().ok(); + let second_part = OpcodeLocation::from_str(parts[1]).ok(); + if let (Some(circuit_id), Some(opcode_location)) = (first_part, second_part) { + Ok(DebugLocation { circuit_id, opcode_location }) + } else { + error + } + } + _ => error, + } + } +} + #[derive(Debug)] pub(super) enum DebugCommandResult { Done, Ok, - BreakpointReached(OpcodeLocation), + BreakpointReached(DebugLocation), Error(NargoError), } +pub struct ExecutionFrame<'a, B: BlackBoxFunctionSolver> { + circuit_id: u32, + acvm: ACVM<'a, FieldElement, B>, +} + pub(super) struct DebugContext<'a, B: BlackBoxFunctionSolver> { acvm: ACVM<'a, FieldElement, B>, + current_circuit_id: u32, brillig_solver: Option>, + + witness_stack: WitnessStack, + acvm_stack: Vec>, + + backend: &'a B, foreign_call_executor: Box, + debug_artifact: &'a DebugArtifact, - breakpoints: HashSet, - source_to_opcodes: BTreeMap>, + breakpoints: HashSet, + source_to_locations: BTreeMap>, + + circuits: &'a [Circuit], unconstrained_functions: &'a [BrilligBytecode], - // Absolute (in terms of all the opcodes ACIR+Brillig) addresses of the ACIR - // opcodes with one additional entry for to indicate the last valid address. - acir_opcode_addresses: Vec, + acir_opcode_addresses: AddressMap, } impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { pub(super) fn new( blackbox_solver: &'a B, - circuit: &'a Circuit, + circuits: &'a [Circuit], debug_artifact: &'a DebugArtifact, initial_witness: WitnessMap, foreign_call_executor: Box, unconstrained_functions: &'a [BrilligBytecode], ) -> Self { let source_to_opcodes = build_source_to_opcode_debug_mappings(debug_artifact); - let acir_opcode_addresses = build_acir_opcode_offsets(circuit, unconstrained_functions); + let current_circuit_id: u32 = 0; + let initial_circuit = &circuits[current_circuit_id as usize]; + let acir_opcode_addresses = AddressMap::new(circuits, unconstrained_functions); Self { - // TODO: need to handle brillig pointer in the debugger acvm: ACVM::new( blackbox_solver, - &circuit.opcodes, + &initial_circuit.opcodes, initial_witness, unconstrained_functions, - &circuit.assert_messages, + &initial_circuit.assert_messages, ), + current_circuit_id, brillig_solver: None, + witness_stack: WitnessStack::default(), + acvm_stack: vec![], + backend: blackbox_solver, foreign_call_executor, debug_artifact, breakpoints: HashSet::new(), - source_to_opcodes, + source_to_locations: source_to_opcodes, + circuits, unconstrained_functions, acir_opcode_addresses, } @@ -74,6 +254,10 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { self.acvm.opcodes() } + pub(super) fn get_opcodes_of_circuit(&self, circuit_id: u32) -> &[Opcode] { + &self.circuits[circuit_id as usize].opcodes + } + pub(super) fn get_witness_map(&self) -> &WitnessMap { self.acvm.witness_map() } @@ -86,36 +270,49 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { self.acvm.overwrite_witness(witness, value) } - pub(super) fn get_current_opcode_location(&self) -> Option { + pub(super) fn get_current_debug_location(&self) -> Option { let ip = self.acvm.instruction_pointer(); if ip >= self.get_opcodes().len() { None - } else if let Some(ref solver) = self.brillig_solver { - Some(OpcodeLocation::Brillig { - acir_index: ip, - brillig_index: solver.program_counter(), - }) } else { - Some(OpcodeLocation::Acir(ip)) + let opcode_location = if let Some(ref solver) = self.brillig_solver { + OpcodeLocation::Brillig { acir_index: ip, brillig_index: solver.program_counter() } + } else { + OpcodeLocation::Acir(ip) + }; + Some(DebugLocation { circuit_id: self.current_circuit_id, opcode_location }) } } - pub(super) fn get_call_stack(&self) -> Vec { + pub(super) fn get_call_stack(&self) -> Vec { + // Build the frames from parent ACIR calls + let mut frames: Vec<_> = self + .acvm_stack + .iter() + .map(|ExecutionFrame { circuit_id, acvm }| DebugLocation { + circuit_id: *circuit_id, + opcode_location: OpcodeLocation::Acir(acvm.instruction_pointer()), + }) + .collect(); + + // Now add the frame(s) for the currently executing ACVM let instruction_pointer = self.acvm.instruction_pointer(); - if instruction_pointer >= self.get_opcodes().len() { - vec![] - } else if let Some(ref solver) = self.brillig_solver { - solver - .get_call_stack() - .iter() - .map(|program_counter| OpcodeLocation::Brillig { + let circuit_id = self.current_circuit_id; + if let Some(ref solver) = self.brillig_solver { + frames.extend(solver.get_call_stack().iter().map(|program_counter| DebugLocation { + circuit_id, + opcode_location: OpcodeLocation::Brillig { acir_index: instruction_pointer, brillig_index: *program_counter, - }) - .collect() - } else { - vec![OpcodeLocation::Acir(instruction_pointer)] + }, + })); + } else if instruction_pointer < self.get_opcodes().len() { + frames.push(DebugLocation { + circuit_id, + opcode_location: OpcodeLocation::Acir(instruction_pointer), + }); } + frames } pub(super) fn is_source_location_in_debug_module(&self, location: &Location) -> bool { @@ -142,10 +339,10 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { &self, file_id: &FileId, line: i64, - ) -> Option { + ) -> Option { let line = line as usize; - let line_to_opcodes = self.source_to_opcodes.get(file_id)?; - let found_index = match line_to_opcodes.binary_search_by(|x| x.0.cmp(&line)) { + let line_to_opcodes = self.source_to_locations.get(file_id)?; + let found_location = match line_to_opcodes.binary_search_by(|x| x.0.cmp(&line)) { Ok(index) => { // move backwards to find the first opcode which matches the line let mut index = index; @@ -161,7 +358,7 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { line_to_opcodes[index].1 } }; - Some(found_index) + Some(found_location) } /// Returns the callstack in source code locations for the currently @@ -172,9 +369,9 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { /// This function also filters source locations that are determined to be in /// the internal debug module. pub(super) fn get_current_source_location(&self) -> Option> { - self.get_current_opcode_location() + self.get_current_debug_location() .as_ref() - .map(|opcode_location| self.get_source_location_for_opcode_location(opcode_location)) + .map(|debug_location| self.get_source_location_for_debug_location(debug_location)) .filter(|v: &Vec| !v.is_empty()) } @@ -184,15 +381,12 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { /// the given opcode location cannot be mapped back to a source location /// (eg. it may be pure debug instrumentation code or other synthetically /// produced opcode by the compiler) - pub(super) fn get_source_location_for_opcode_location( + pub(super) fn get_source_location_for_debug_location( &self, - opcode_location: &OpcodeLocation, + debug_location: &DebugLocation, ) -> Vec { - // TODO: this assumes we're debugging a program (ie. the DebugArtifact - // will contain a single DebugInfo), but this assumption doesn't hold - // for contracts - self.debug_artifact.debug_symbols[0] - .opcode_location(opcode_location) + self.debug_artifact.debug_symbols[debug_location.circuit_id as usize] + .opcode_location(&debug_location.opcode_location) .map(|source_locations| { source_locations .into_iter() @@ -208,48 +402,30 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { /// general, the matching between opcode location and source location is 1 /// to 1, but due to the compiler inlining functions a single opcode /// location may expand to multiple source locations. - pub(super) fn get_source_call_stack(&self) -> Vec<(OpcodeLocation, Location)> { + pub(super) fn get_source_call_stack(&self) -> Vec<(DebugLocation, Location)> { self.get_call_stack() .iter() - .flat_map(|opcode_location| { - self.get_source_location_for_opcode_location(opcode_location) + .flat_map(|debug_location| { + self.get_source_location_for_debug_location(debug_location) .into_iter() - .map(|source_location| (*opcode_location, source_location)) + .map(|source_location| (*debug_location, source_location)) }) .collect() } /// Returns the absolute address of the opcode at the given location. - /// Absolute here means accounting for nested Brillig opcodes in BrilligCall - /// opcodes. - pub fn opcode_location_to_address(&self, location: &OpcodeLocation) -> usize { - match location { - OpcodeLocation::Acir(acir_index) => self.acir_opcode_addresses[*acir_index], - OpcodeLocation::Brillig { acir_index, brillig_index } => { - self.acir_opcode_addresses[*acir_index] + *brillig_index - } - } + pub fn debug_location_to_address(&self, location: &DebugLocation) -> usize { + self.acir_opcode_addresses.debug_location_to_address(location) } - pub fn address_to_opcode_location(&self, address: usize) -> Option { - if address >= *self.acir_opcode_addresses.last().unwrap_or(&0) { - return None; - } - let location = match self.acir_opcode_addresses.binary_search(&address) { - Ok(found_index) => OpcodeLocation::Acir(found_index), - Err(insert_index) => { - let acir_index = insert_index - 1; - let base_offset = self.acir_opcode_addresses[acir_index]; - let brillig_index = address - base_offset; - OpcodeLocation::Brillig { acir_index, brillig_index } - } - }; - Some(location) + // Returns the DebugLocation associated to the given address + pub fn address_to_debug_location(&self, address: usize) -> Option { + self.acir_opcode_addresses.address_to_debug_location(address) } - pub(super) fn render_opcode_at_location(&self, location: &OpcodeLocation) -> String { - let opcodes = self.get_opcodes(); - match location { + pub(super) fn render_opcode_at_location(&self, location: &DebugLocation) -> String { + let opcodes = self.get_opcodes_of_circuit(location.circuit_id); + match &location.opcode_location { OpcodeLocation::Acir(acir_index) => { let opcode = &opcodes[*acir_index]; match opcode { @@ -280,7 +456,7 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { self.brillig_solver = Some(solver); if self.breakpoint_reached() { DebugCommandResult::BreakpointReached( - self.get_current_opcode_location() + self.get_current_debug_location() .expect("Breakpoint reached but we have no location"), ) } else { @@ -296,7 +472,6 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { self.handle_foreign_call(foreign_call) } Err(err) => DebugCommandResult::Error(NargoError::ExecutionError( - // TODO: debugger does not handle multiple acir calls ExecutionError::SolvingError(err, None), )), } @@ -315,24 +490,82 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { } else { self.acvm.resolve_pending_foreign_call(foreign_call_result); } - // TODO: should we retry executing the opcode somehow in this case? + // TODO: should we retry executing the opcode somehow in this + // case? Otherwise, executing a foreign call takes two debugging + // steps. DebugCommandResult::Ok } Err(error) => DebugCommandResult::Error(error.into()), } } - fn handle_acvm_status(&mut self, status: ACVMStatus) -> DebugCommandResult { - if let ACVMStatus::RequiresForeignCall(foreign_call) = status { - return self.handle_foreign_call(foreign_call); + fn handle_acir_call( + &mut self, + call_info: AcirCallWaitInfo, + ) -> DebugCommandResult { + let callee_circuit = &self.circuits[call_info.id as usize]; + let callee_witness_map = call_info.initial_witness; + let callee_acvm = ACVM::new( + self.backend, + &callee_circuit.opcodes, + callee_witness_map, + self.unconstrained_functions, + &callee_circuit.assert_messages, + ); + let caller_acvm = std::mem::replace(&mut self.acvm, callee_acvm); + self.acvm_stack + .push(ExecutionFrame { circuit_id: self.current_circuit_id, acvm: caller_acvm }); + self.current_circuit_id = call_info.id; + + // Explicitly handling the new ACVM status here handles two edge cases: + // 1. there is a breakpoint set at the beginning of a circuit + // 2. the called circuit has no opcodes + self.handle_acvm_status(self.acvm.get_status().clone()) + } + + fn handle_acir_call_finished(&mut self) -> DebugCommandResult { + let caller_frame = self.acvm_stack.pop().expect("Execution stack should not be empty"); + let caller_acvm = caller_frame.acvm; + let callee_acvm = std::mem::replace(&mut self.acvm, caller_acvm); + self.current_circuit_id = caller_frame.circuit_id; + let call_solved_witness = callee_acvm.finalize(); + + let ACVMStatus::RequiresAcirCall(call_info) = self.acvm.get_status() else { + unreachable!("Resolving an ACIR call, the caller is in an invalid state"); + }; + let acir_to_call = &self.circuits[call_info.id as usize]; + + let mut call_resolved_outputs = Vec::new(); + for return_witness_index in acir_to_call.return_values.indices() { + if let Some(return_value) = call_solved_witness.get_index(return_witness_index) { + call_resolved_outputs.push(*return_value); + } else { + return DebugCommandResult::Error( + ExecutionError::SolvingError( + OpcodeNotSolvable::MissingAssignment(return_witness_index).into(), + None, // Missing assignment errors do not supply user-facing diagnostics so we do not need to attach a call stack + ) + .into(), + ); + } } + self.acvm.resolve_pending_acir_call(call_resolved_outputs); + + DebugCommandResult::Ok + } + fn handle_acvm_status(&mut self, status: ACVMStatus) -> DebugCommandResult { match status { - ACVMStatus::Solved => DebugCommandResult::Done, + ACVMStatus::Solved => { + if self.acvm_stack.is_empty() { + return DebugCommandResult::Done; + } + self.handle_acir_call_finished() + } ACVMStatus::InProgress => { if self.breakpoint_reached() { DebugCommandResult::BreakpointReached( - self.get_current_opcode_location() + self.get_current_debug_location() .expect("Breakpoint reached but we have no location"), ) } else { @@ -340,15 +573,10 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { } } ACVMStatus::Failure(error) => DebugCommandResult::Error(NargoError::ExecutionError( - // TODO: debugger does not handle multiple acir calls ExecutionError::SolvingError(error, None), )), - ACVMStatus::RequiresForeignCall(_) => { - unreachable!("Unexpected pending foreign call resolution"); - } - ACVMStatus::RequiresAcirCall(_) => { - todo!("Multiple ACIR calls are not supported"); - } + ACVMStatus::RequiresForeignCall(foreign_call) => self.handle_foreign_call(foreign_call), + ACVMStatus::RequiresAcirCall(call_info) => self.handle_acir_call(call_info), } } @@ -367,9 +595,11 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { } fn get_current_acir_index(&self) -> Option { - self.get_current_opcode_location().map(|opcode_location| match opcode_location { - OpcodeLocation::Acir(acir_index) => acir_index, - OpcodeLocation::Brillig { acir_index, .. } => acir_index, + 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, + } }) } @@ -394,10 +624,16 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { return true; } - match self.get_current_opcode_location() { - Some(OpcodeLocation::Brillig { .. }) => true, - Some(OpcodeLocation::Acir(acir_index)) => { - matches!(self.get_opcodes()[acir_index], Opcode::BrilligCall { .. }) + match self.get_current_debug_location() { + Some(DebugLocation { opcode_location: OpcodeLocation::Brillig { .. }, .. }) => true, + Some(DebugLocation { + circuit_id, + opcode_location: OpcodeLocation::Acir(acir_index), + }) => { + matches!( + self.get_opcodes_of_circuit(circuit_id)[acir_index], + Opcode::BrilligCall { .. } + ) } _ => false, } @@ -491,16 +727,19 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { } fn breakpoint_reached(&self) -> bool { - if let Some(location) = self.get_current_opcode_location() { + if let Some(location) = self.get_current_debug_location() { self.breakpoints.contains(&location) } else { false } } - pub(super) fn is_valid_opcode_location(&self, location: &OpcodeLocation) -> bool { - let opcodes = self.get_opcodes(); - match *location { + pub(super) fn is_valid_debug_location(&self, location: &DebugLocation) -> bool { + if location.circuit_id as usize >= self.circuits.len() { + return false; + } + let opcodes = self.get_opcodes_of_circuit(location.circuit_id); + match location.opcode_location { OpcodeLocation::Acir(acir_index) => acir_index < opcodes.len(), OpcodeLocation::Brillig { acir_index, brillig_index } => { if acir_index < opcodes.len() { @@ -518,19 +757,19 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { } } - pub(super) fn is_breakpoint_set(&self, location: &OpcodeLocation) -> bool { + pub(super) fn is_breakpoint_set(&self, location: &DebugLocation) -> bool { self.breakpoints.contains(location) } - pub(super) fn add_breakpoint(&mut self, location: OpcodeLocation) -> bool { + pub(super) fn add_breakpoint(&mut self, location: DebugLocation) -> bool { self.breakpoints.insert(location) } - pub(super) fn delete_breakpoint(&mut self, location: &OpcodeLocation) -> bool { + pub(super) fn delete_breakpoint(&mut self, location: &DebugLocation) -> bool { self.breakpoints.remove(location) } - pub(super) fn iterate_breakpoints(&self) -> Iter<'_, OpcodeLocation> { + pub(super) fn iterate_breakpoints(&self) -> Iter<'_, DebugLocation> { self.breakpoints.iter() } @@ -542,8 +781,10 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { matches!(self.acvm.get_status(), ACVMStatus::Solved) } - pub fn finalize(self) -> WitnessMap { - self.acvm.finalize() + pub fn finalize(mut self) -> WitnessStack { + let last_witness_map = self.acvm.finalize(); + self.witness_stack.push(0, last_witness_map); + self.witness_stack } } @@ -555,11 +796,10 @@ fn is_debug_file_in_debug_crate(debug_file: &DebugFile) -> bool { /// numbers and opcode locations corresponding to those line numbers fn build_source_to_opcode_debug_mappings( debug_artifact: &DebugArtifact, -) -> BTreeMap> { +) -> BTreeMap> { if debug_artifact.debug_symbols.is_empty() { return BTreeMap::new(); } - let locations = &debug_artifact.debug_symbols[0].locations; let simple_files: BTreeMap<_, _> = debug_artifact .file_map .iter() @@ -572,50 +812,34 @@ fn build_source_to_opcode_debug_mappings( }) .collect(); - let mut result: BTreeMap> = BTreeMap::new(); - locations.iter().for_each(|(opcode_location, source_locations)| { - source_locations.iter().for_each(|source_location| { - let span = source_location.span; - let file_id = source_location.file; - let Some(file) = simple_files.get(&file_id) else { - return; - }; - let Ok(line_index) = file.line_index((), span.start() as usize) else { - return; - }; - let line_number = line_index + 1; - - result.entry(file_id).or_default().push((line_number, *opcode_location)); - }); - }); + let mut result: BTreeMap> = BTreeMap::new(); + + for (circuit_id, debug_symbols) in debug_artifact.debug_symbols.iter().enumerate() { + for (opcode_location, source_locations) in &debug_symbols.locations { + source_locations.iter().for_each(|source_location| { + let span = source_location.span; + let file_id = source_location.file; + let Some(file) = simple_files.get(&file_id) else { + return; + }; + let Ok(line_index) = file.line_index((), span.start() as usize) else { + return; + }; + let line_number = line_index + 1; + + let debug_location = DebugLocation { + circuit_id: circuit_id as u32, + opcode_location: *opcode_location, + }; + result.entry(file_id).or_default().push((line_number, debug_location)); + }); + } + } result.iter_mut().for_each(|(_, file_locations)| file_locations.sort_by_key(|x| (x.0, x.1))); result } -fn build_acir_opcode_offsets( - circuit: &Circuit, - unconstrained_functions: &[BrilligBytecode], -) -> Vec { - let mut result = Vec::with_capacity(circuit.opcodes.len() + 1); - // address of the first opcode is always 0 - result.push(0); - circuit.opcodes.iter().fold(0, |acc, opcode| { - let acc = acc - + match opcode { - Opcode::BrilligCall { id, .. } => { - unconstrained_functions[*id as usize].bytecode.len() - } - _ => 1, - }; - // push the starting address of the next opcode - result.push(acc); - acc - }); - result -} - -// TODO: update all debugger tests to use unconstrained brillig pointers #[cfg(test)] mod tests { use super::*; @@ -625,7 +849,7 @@ mod tests { acir::{ circuit::{ brillig::{BrilligInputs, BrilligOutputs}, - opcodes::BlockId, + opcodes::{BlockId, BlockType}, }, native_types::Expression, AcirField, @@ -675,7 +899,8 @@ mod tests { }]; let brillig_funcs = &vec![brillig_bytecode]; let current_witness_index = 2; - let circuit = &Circuit { current_witness_index, opcodes, ..Circuit::default() }; + let circuit = Circuit { current_witness_index, opcodes, ..Circuit::default() }; + let circuits = &vec![circuit]; let debug_symbols = vec![]; let file_map = BTreeMap::new(); @@ -687,51 +912,66 @@ mod tests { Box::new(DefaultDebugForeignCallExecutor::from_artifact(true, debug_artifact)); let mut context = DebugContext::new( &StubbedBlackBoxSolver, - circuit, + circuits, debug_artifact, initial_witness, foreign_call_executor, brillig_funcs, ); - assert_eq!(context.get_current_opcode_location(), Some(OpcodeLocation::Acir(0))); + assert_eq!( + context.get_current_debug_location(), + Some(DebugLocation { circuit_id: 0, opcode_location: OpcodeLocation::Acir(0) }) + ); // Execute the first Brillig opcode (calldata copy) let result = context.step_into_opcode(); assert!(matches!(result, DebugCommandResult::Ok)); assert_eq!( - context.get_current_opcode_location(), - Some(OpcodeLocation::Brillig { acir_index: 0, brillig_index: 1 }) + context.get_current_debug_location(), + Some(DebugLocation { + circuit_id: 0, + opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 1 } + }) ); // execute the second Brillig opcode (const) let result = context.step_into_opcode(); assert!(matches!(result, DebugCommandResult::Ok)); assert_eq!( - context.get_current_opcode_location(), - Some(OpcodeLocation::Brillig { acir_index: 0, brillig_index: 2 }) + context.get_current_debug_location(), + Some(DebugLocation { + circuit_id: 0, + opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 2 } + }) ); // try to execute the third Brillig opcode (and resolve the foreign call) let result = context.step_into_opcode(); assert!(matches!(result, DebugCommandResult::Ok)); assert_eq!( - context.get_current_opcode_location(), - Some(OpcodeLocation::Brillig { acir_index: 0, brillig_index: 2 }) + context.get_current_debug_location(), + Some(DebugLocation { + circuit_id: 0, + opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 2 } + }) ); // retry the third Brillig opcode (foreign call should be finished) let result = context.step_into_opcode(); assert!(matches!(result, DebugCommandResult::Ok)); assert_eq!( - context.get_current_opcode_location(), - Some(OpcodeLocation::Brillig { acir_index: 0, brillig_index: 3 }) + context.get_current_debug_location(), + Some(DebugLocation { + circuit_id: 0, + opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 3 } + }) ); // last Brillig opcode let result = context.step_into_opcode(); assert!(matches!(result, DebugCommandResult::Done)); - assert_eq!(context.get_current_opcode_location(), None); + assert_eq!(context.get_current_debug_location(), None); } #[test] @@ -784,7 +1024,8 @@ mod tests { }), ]; let current_witness_index = 3; - let circuit = &Circuit { current_witness_index, opcodes, ..Circuit::default() }; + let circuit = Circuit { current_witness_index, opcodes, ..Circuit::default() }; + let circuits = &vec![circuit]; let debug_symbols = vec![]; let file_map = BTreeMap::new(); @@ -797,7 +1038,7 @@ mod tests { let brillig_funcs = &vec![brillig_bytecode]; let mut context = DebugContext::new( &StubbedBlackBoxSolver, - circuit, + circuits, debug_artifact, initial_witness, foreign_call_executor, @@ -805,28 +1046,40 @@ mod tests { ); // set breakpoint - let breakpoint_location = OpcodeLocation::Brillig { acir_index: 0, brillig_index: 1 }; + let breakpoint_location = DebugLocation { + circuit_id: 0, + opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 1 }, + }; assert!(context.add_breakpoint(breakpoint_location)); // execute the first ACIR opcode (Brillig block) -> should reach the breakpoint instead let result = context.step_acir_opcode(); assert!(matches!(result, DebugCommandResult::BreakpointReached(_))); - assert_eq!(context.get_current_opcode_location(), Some(breakpoint_location)); + assert_eq!(context.get_current_debug_location(), Some(breakpoint_location)); // continue execution to the next ACIR opcode let result = context.step_acir_opcode(); assert!(matches!(result, DebugCommandResult::Ok)); - assert_eq!(context.get_current_opcode_location(), Some(OpcodeLocation::Acir(1))); + assert_eq!( + context.get_current_debug_location(), + Some(DebugLocation { circuit_id: 0, opcode_location: OpcodeLocation::Acir(1) }) + ); // last ACIR opcode let result = context.step_acir_opcode(); assert!(matches!(result, DebugCommandResult::Done)); - assert_eq!(context.get_current_opcode_location(), None); + assert_eq!(context.get_current_debug_location(), None); } #[test] - fn test_address_opcode_location_mapping() { - let brillig_bytecode = BrilligBytecode { + fn test_address_debug_location_mapping() { + let brillig_one = BrilligBytecode { + bytecode: vec![ + BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 0 }, + BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 0 }, + ], + }; + let brillig_two = BrilligBytecode { bytecode: vec![ BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 0 }, BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 0 }, @@ -834,22 +1087,33 @@ mod tests { ], }; - let opcodes = vec![ - Opcode::BrilligCall { id: 0, inputs: vec![], outputs: vec![], predicate: None }, - Opcode::MemoryInit { - block_id: BlockId(0), - init: vec![], - block_type: acvm::acir::circuit::opcodes::BlockType::Memory, - }, - Opcode::BrilligCall { id: 0, inputs: vec![], outputs: vec![], predicate: None }, - Opcode::AssertZero(Expression::default()), - ]; - let circuit = Circuit { opcodes, ..Circuit::default() }; + let circuit_one = Circuit { + opcodes: vec![ + Opcode::MemoryInit { + block_id: BlockId(0), + init: vec![], + block_type: BlockType::Memory, + }, + Opcode::BrilligCall { id: 0, inputs: vec![], outputs: vec![], predicate: None }, + Opcode::Call { id: 1, inputs: vec![], outputs: vec![], predicate: None }, + Opcode::AssertZero(Expression::default()), + ], + ..Circuit::default() + }; + let circuit_two = Circuit { + opcodes: vec![ + Opcode::BrilligCall { id: 1, inputs: vec![], outputs: vec![], predicate: None }, + Opcode::AssertZero(Expression::default()), + ], + ..Circuit::default() + }; + let circuits = vec![circuit_one, circuit_two]; let debug_artifact = DebugArtifact { debug_symbols: vec![], file_map: BTreeMap::new() }; - let brillig_funcs = &vec![brillig_bytecode]; + let brillig_funcs = &vec![brillig_one, brillig_two]; + let context = DebugContext::new( &StubbedBlackBoxSolver, - &circuit, + &circuits, &debug_artifact, WitnessMap::new(), Box::new(DefaultDebugForeignCallExecutor::new(true)), @@ -857,46 +1121,56 @@ mod tests { ); let locations = - (0..=7).map(|address| context.address_to_opcode_location(address)).collect::>(); + (0..=8).map(|address| context.address_to_debug_location(address)).collect::>(); // mapping from addresses to opcode locations assert_eq!( locations, vec![ - Some(OpcodeLocation::Acir(0)), - Some(OpcodeLocation::Brillig { acir_index: 0, brillig_index: 1 }), - Some(OpcodeLocation::Brillig { acir_index: 0, brillig_index: 2 }), - Some(OpcodeLocation::Acir(1)), - Some(OpcodeLocation::Acir(2)), - Some(OpcodeLocation::Brillig { acir_index: 2, brillig_index: 1 }), - Some(OpcodeLocation::Brillig { acir_index: 2, brillig_index: 2 }), - Some(OpcodeLocation::Acir(3)), + Some(DebugLocation { circuit_id: 0, opcode_location: OpcodeLocation::Acir(0) }), + Some(DebugLocation { circuit_id: 0, opcode_location: OpcodeLocation::Acir(1) }), + Some(DebugLocation { + circuit_id: 0, + opcode_location: OpcodeLocation::Brillig { acir_index: 1, brillig_index: 1 } + }), + Some(DebugLocation { circuit_id: 0, opcode_location: OpcodeLocation::Acir(2) }), + Some(DebugLocation { circuit_id: 0, opcode_location: OpcodeLocation::Acir(3) }), + Some(DebugLocation { circuit_id: 1, opcode_location: OpcodeLocation::Acir(0) }), + Some(DebugLocation { + circuit_id: 1, + opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 1 } + }), + Some(DebugLocation { + circuit_id: 1, + opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 2 } + }), + Some(DebugLocation { circuit_id: 1, opcode_location: OpcodeLocation::Acir(1) }), ] ); let addresses = locations .iter() .flatten() - .map(|location| context.opcode_location_to_address(location)) + .map(|location| context.debug_location_to_address(location)) .collect::>(); // and vice-versa - assert_eq!(addresses, (0..=7).collect::>()); + assert_eq!(addresses, (0..=8).collect::>()); // check edge cases - assert_eq!(None, context.address_to_opcode_location(8)); + assert_eq!(None, context.address_to_debug_location(9)); assert_eq!( - 0, - context.opcode_location_to_address(&OpcodeLocation::Brillig { - acir_index: 0, - brillig_index: 0 + 1, + context.debug_location_to_address(&DebugLocation { + circuit_id: 0, + opcode_location: OpcodeLocation::Brillig { acir_index: 1, brillig_index: 0 } }) ); assert_eq!( - 4, - context.opcode_location_to_address(&OpcodeLocation::Brillig { - acir_index: 2, - brillig_index: 0 + 5, + context.debug_location_to_address(&DebugLocation { + circuit_id: 1, + opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 0 } }) ); } diff --git a/noir/noir-repo/tooling/debugger/src/dap.rs b/noir/noir-repo/tooling/debugger/src/dap.rs index 77abf3093cd..cfe33a61cb5 100644 --- a/noir/noir-repo/tooling/debugger/src/dap.rs +++ b/noir/noir-repo/tooling/debugger/src/dap.rs @@ -2,12 +2,12 @@ use std::collections::BTreeMap; use std::io::{Read, Write}; use acvm::acir::circuit::brillig::BrilligBytecode; -use acvm::acir::circuit::{Circuit, OpcodeLocation}; +use acvm::acir::circuit::Circuit; use acvm::acir::native_types::WitnessMap; use acvm::{BlackBoxFunctionSolver, FieldElement}; -use crate::context::DebugCommandResult; use crate::context::DebugContext; +use crate::context::{DebugCommandResult, DebugLocation}; use crate::foreign_calls::DefaultDebugForeignCallExecutor; use dap::errors::ServerError; @@ -37,8 +37,8 @@ pub struct DapSession<'a, R: Read, W: Write, B: BlackBoxFunctionSolver, - source_breakpoints: BTreeMap>, + instruction_breakpoints: Vec<(DebugLocation, BreakpointId)>, + source_breakpoints: BTreeMap>, } enum ScopeReferences { @@ -61,14 +61,14 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession< pub fn new( server: Server, solver: &'a B, - circuit: &'a Circuit, + circuits: &'a [Circuit], debug_artifact: &'a DebugArtifact, initial_witness: WitnessMap, unconstrained_functions: &'a [BrilligBytecode], ) -> Self { let context = DebugContext::new( solver, - circuit, + circuits, debug_artifact, initial_witness, Box::new(DefaultDebugForeignCallExecutor::from_artifact(true, debug_artifact)), @@ -100,7 +100,7 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession< } pub fn run_loop(&mut self) -> Result<(), ServerError> { - self.running = self.context.get_current_opcode_location().is_some(); + self.running = self.context.get_current_debug_location().is_some(); if self.running && self.context.get_current_source_location().is_none() { // TODO: remove this? This is to ensure that the tool has a proper @@ -194,7 +194,7 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession< .get_source_call_stack() .iter() .enumerate() - .map(|(index, (opcode_location, source_location))| { + .map(|(index, (debug_location, source_location))| { let line_number = self.debug_artifact.location_line_number(*source_location).unwrap(); let column_number = @@ -204,7 +204,7 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession< Some(frame) => format!("{} {}", frame.function_name, index), None => format!("frame #{index}"), }; - let address = self.context.opcode_location_to_address(opcode_location); + let address = self.context.debug_location_to_address(debug_location); StackFrame { id: index as i64, @@ -251,18 +251,18 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession< let mut instructions: Vec = vec![]; while count > 0 { - let opcode_location = if address >= 0 { - self.context.address_to_opcode_location(address as usize) + let debug_location = if address >= 0 { + self.context.address_to_debug_location(address as usize) } else { None }; - if let Some(opcode_location) = opcode_location { + if let Some(debug_location) = debug_location { instructions.push(DisassembledInstruction { address: address.to_string(), // we'll use the instruction_bytes field to render the OpcodeLocation - instruction_bytes: Some(opcode_location.to_string()), - instruction: self.context.render_opcode_at_location(&opcode_location), + instruction_bytes: Some(debug_location.to_string()), + instruction: self.context.render_opcode_at_location(&debug_location), ..DisassembledInstruction::default() }); } else { @@ -320,16 +320,16 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession< self.handle_execution_result(result) } - fn find_breakpoints_at_location(&self, opcode_location: &OpcodeLocation) -> Vec { + fn find_breakpoints_at_location(&self, debug_location: &DebugLocation) -> Vec { let mut result = vec![]; for (location, id) in &self.instruction_breakpoints { - if opcode_location == location { + if debug_location == location { result.push(*id); } } for breakpoints in self.source_breakpoints.values() { for (location, id) in breakpoints { - if opcode_location == location { + if debug_location == location { result.push(*id); } } @@ -404,7 +404,7 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession< }; // compute breakpoints to set and return - let mut breakpoints_to_set: Vec<(OpcodeLocation, i64)> = vec![]; + let mut breakpoints_to_set: Vec<(DebugLocation, i64)> = vec![]; let breakpoints: Vec = args .breakpoints .iter() @@ -420,8 +420,8 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession< }; let Some(location) = self .context - .address_to_opcode_location(address) - .filter(|location| self.context.is_valid_opcode_location(location)) + .address_to_debug_location(address) + .filter(|location| self.context.is_valid_debug_location(location)) else { return Breakpoint { verified: false, @@ -472,7 +472,7 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession< let Some(ref breakpoints) = &args.breakpoints else { return vec![]; }; - let mut breakpoints_to_set: Vec<(OpcodeLocation, i64)> = vec![]; + let mut breakpoints_to_set: Vec<(DebugLocation, i64)> = vec![]; let breakpoints = breakpoints .iter() .map(|breakpoint| { @@ -490,14 +490,14 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession< // TODO: line will not necessarily be the one requested; we // should do the reverse mapping and retrieve the actual source // code line number - if !self.context.is_valid_opcode_location(&location) { + if !self.context.is_valid_debug_location(&location) { return Breakpoint { verified: false, message: Some(String::from("Invalid opcode location")), ..Breakpoint::default() }; } - let breakpoint_address = self.context.opcode_location_to_address(&location); + let breakpoint_address = self.context.debug_location_to_address(&location); let instruction_reference = format!("{}", breakpoint_address); let breakpoint_id = self.get_next_breakpoint_id(); breakpoints_to_set.push((location, breakpoint_id)); @@ -612,7 +612,7 @@ pub fn run_session>( let mut session = DapSession::new( server, solver, - &program.program.functions[0], + &program.program.functions, &debug_artifact, initial_witness, &program.program.unconstrained_functions, diff --git a/noir/noir-repo/tooling/debugger/src/lib.rs b/noir/noir-repo/tooling/debugger/src/lib.rs index 9d0059ee495..37ac088ca35 100644 --- a/noir/noir-repo/tooling/debugger/src/lib.rs +++ b/noir/noir-repo/tooling/debugger/src/lib.rs @@ -9,23 +9,18 @@ use std::io::{Read, Write}; use ::dap::errors::ServerError; use ::dap::server::Server; -use acvm::acir::circuit::brillig::BrilligBytecode; -use acvm::{acir::circuit::Circuit, acir::native_types::WitnessMap}; +use acvm::acir::native_types::{WitnessMap, WitnessStack}; use acvm::{BlackBoxFunctionSolver, FieldElement}; -use noirc_artifacts::debug::DebugArtifact; - use nargo::NargoError; use noirc_driver::CompiledProgram; -pub fn debug_circuit>( - blackbox_solver: &B, - circuit: &Circuit, - debug_artifact: DebugArtifact, +pub fn run_repl_session>( + solver: &B, + program: CompiledProgram, initial_witness: WitnessMap, - unconstrained_functions: &[BrilligBytecode], -) -> Result>, NargoError> { - repl::run(blackbox_solver, circuit, &debug_artifact, initial_witness, unconstrained_functions) +) -> Result>, NargoError> { + repl::run(solver, program, initial_witness) } pub fn run_dap_loop>( diff --git a/noir/noir-repo/tooling/debugger/src/repl.rs b/noir/noir-repo/tooling/debugger/src/repl.rs index 7d8c6e0947d..d3462985642 100644 --- a/noir/noir-repo/tooling/debugger/src/repl.rs +++ b/noir/noir-repo/tooling/debugger/src/repl.rs @@ -1,11 +1,12 @@ -use crate::context::{DebugCommandResult, DebugContext}; +use crate::context::{DebugCommandResult, DebugContext, DebugLocation}; use acvm::acir::circuit::brillig::BrilligBytecode; use acvm::acir::circuit::{Circuit, Opcode, OpcodeLocation}; -use acvm::acir::native_types::{Witness, WitnessMap}; +use acvm::acir::native_types::{Witness, WitnessMap, WitnessStack}; use acvm::brillig_vm::brillig::Opcode as BrilligOpcode; use acvm::{BlackBoxFunctionSolver, FieldElement}; use nargo::NargoError; +use noirc_driver::CompiledProgram; use crate::foreign_calls::DefaultDebugForeignCallExecutor; use noirc_artifacts::debug::DebugArtifact; @@ -19,17 +20,21 @@ use crate::source_code_printer::print_source_code_location; pub struct ReplDebugger<'a, B: BlackBoxFunctionSolver> { context: DebugContext<'a, B>, blackbox_solver: &'a B, - circuit: &'a Circuit, debug_artifact: &'a DebugArtifact, initial_witness: WitnessMap, last_result: DebugCommandResult, + + // ACIR functions to debug + circuits: &'a [Circuit], + + // Brillig functions referenced from the ACIR circuits above unconstrained_functions: &'a [BrilligBytecode], } impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> { pub fn new( blackbox_solver: &'a B, - circuit: &'a Circuit, + circuits: &'a [Circuit], debug_artifact: &'a DebugArtifact, initial_witness: WitnessMap, unconstrained_functions: &'a [BrilligBytecode], @@ -38,13 +43,13 @@ impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> { Box::new(DefaultDebugForeignCallExecutor::from_artifact(true, debug_artifact)); let context = DebugContext::new( blackbox_solver, - circuit, + circuits, debug_artifact, initial_witness.clone(), foreign_call_executor, unconstrained_functions, ); - let last_result = if context.get_current_opcode_location().is_none() { + let last_result = if context.get_current_debug_location().is_none() { // handle circuit with no opcodes DebugCommandResult::Done } else { @@ -53,7 +58,7 @@ impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> { Self { context, blackbox_solver, - circuit, + circuits, debug_artifact, initial_witness, last_result, @@ -62,42 +67,43 @@ impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> { } pub fn show_current_vm_status(&self) { - let location = self.context.get_current_opcode_location(); - let opcodes = self.context.get_opcodes(); + let location = self.context.get_current_debug_location(); match location { None => println!("Finished execution"), Some(location) => { - match location { + let circuit_id = location.circuit_id; + let opcodes = self.context.get_opcodes_of_circuit(circuit_id); + match &location.opcode_location { OpcodeLocation::Acir(ip) => { - println!("At opcode {}: {}", ip, opcodes[ip]); + println!("At opcode {} :: {}", location, opcodes[*ip]); } OpcodeLocation::Brillig { acir_index, brillig_index } => { let brillig_bytecode = - if let Opcode::BrilligCall { id, .. } = opcodes[acir_index] { + if let Opcode::BrilligCall { id, .. } = opcodes[*acir_index] { &self.unconstrained_functions[id as usize].bytecode } else { unreachable!("Brillig location does not contain Brillig opcodes"); }; println!( - "At opcode {}.{}: {:?}", - acir_index, brillig_index, brillig_bytecode[brillig_index] + "At opcode {} :: {:?}", + location, brillig_bytecode[*brillig_index] ); } } - let locations = self.context.get_source_location_for_opcode_location(&location); + let locations = self.context.get_source_location_for_debug_location(&location); print_source_code_location(self.debug_artifact, &locations); } } } - fn show_stack_frame(&self, index: usize, location: &OpcodeLocation) { + fn show_stack_frame(&self, index: usize, debug_location: &DebugLocation) { let opcodes = self.context.get_opcodes(); - match location { + match &debug_location.opcode_location { OpcodeLocation::Acir(instruction_pointer) => { println!( - "Frame #{index}, opcode {}: {}", - instruction_pointer, opcodes[*instruction_pointer] + "Frame #{index}, opcode {} :: {}", + debug_location, opcodes[*instruction_pointer] ) } OpcodeLocation::Brillig { acir_index, brillig_index } => { @@ -108,12 +114,12 @@ impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> { unreachable!("Brillig location does not contain Brillig opcodes"); }; println!( - "Frame #{index}, opcode {}.{}: {:?}", - acir_index, brillig_index, brillig_bytecode[*brillig_index] + "Frame #{index}, opcode {} :: {:?}", + debug_location, brillig_bytecode[*brillig_index] ); } } - let locations = self.context.get_source_location_for_opcode_location(location); + let locations = self.context.get_source_location_for_debug_location(debug_location); print_source_code_location(self.debug_artifact, &locations); } @@ -130,8 +136,21 @@ impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> { } fn display_opcodes(&self) { - let opcodes = self.context.get_opcodes(); - let current_opcode_location = self.context.get_current_opcode_location(); + for i in 0..self.circuits.len() { + self.display_opcodes_of_circuit(i as u32); + } + } + + fn display_opcodes_of_circuit(&self, circuit_id: u32) { + let current_opcode_location = + self.context.get_current_debug_location().and_then(|debug_location| { + if debug_location.circuit_id == circuit_id { + Some(debug_location.opcode_location) + } else { + None + } + }); + let opcodes = self.context.get_opcodes_of_circuit(circuit_id); let current_acir_index = match current_opcode_location { Some(OpcodeLocation::Acir(ip)) => Some(ip), Some(OpcodeLocation::Brillig { acir_index, .. }) => Some(acir_index), @@ -144,7 +163,10 @@ impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> { let outer_marker = |acir_index| { if current_acir_index == Some(acir_index) { "->" - } else if self.context.is_breakpoint_set(&OpcodeLocation::Acir(acir_index)) { + } else if self.context.is_breakpoint_set(&DebugLocation { + circuit_id, + opcode_location: OpcodeLocation::Acir(acir_index), + }) { " *" } else { "" @@ -153,10 +175,10 @@ impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> { let brillig_marker = |acir_index, brillig_index| { if current_acir_index == Some(acir_index) && brillig_index == current_brillig_index { "->" - } else if self - .context - .is_breakpoint_set(&OpcodeLocation::Brillig { acir_index, brillig_index }) - { + } else if self.context.is_breakpoint_set(&DebugLocation { + circuit_id, + opcode_location: OpcodeLocation::Brillig { acir_index, brillig_index }, + }) { " *" } else { "" @@ -165,7 +187,8 @@ impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> { let print_brillig_bytecode = |acir_index, bytecode: &[BrilligOpcode]| { for (brillig_index, brillig_opcode) in bytecode.iter().enumerate() { println!( - "{:>3}.{:<2} |{:2} {:?}", + "{:>2}:{:>3}.{:<2} |{:2} {:?}", + circuit_id, acir_index, brillig_index, brillig_marker(acir_index, brillig_index), @@ -178,33 +201,33 @@ impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> { match &opcode { Opcode::BrilligCall { id, inputs, outputs, .. } => { println!( - "{:>3} {:2} BRILLIG CALL id={} inputs={:?}", - acir_index, marker, id, inputs + "{:>2}:{:>3} {:2} BRILLIG CALL id={} inputs={:?}", + circuit_id, acir_index, marker, id, inputs ); - println!(" | outputs={:?}", outputs); + println!(" | outputs={:?}", outputs); let bytecode = &self.unconstrained_functions[*id as usize].bytecode; print_brillig_bytecode(acir_index, bytecode); } - _ => println!("{:>3} {:2} {:?}", acir_index, marker, opcode), + _ => println!("{:>2}:{:>3} {:2} {:?}", circuit_id, acir_index, marker, opcode), } } } - fn add_breakpoint_at(&mut self, location: OpcodeLocation) { - if !self.context.is_valid_opcode_location(&location) { - println!("Invalid opcode location {location}"); + fn add_breakpoint_at(&mut self, location: DebugLocation) { + if !self.context.is_valid_debug_location(&location) { + println!("Invalid location {location}"); } else if self.context.add_breakpoint(location) { - println!("Added breakpoint at opcode {location}"); + println!("Added breakpoint at {location}"); } else { - println!("Breakpoint at opcode {location} already set"); + println!("Breakpoint at {location} already set"); } } - fn delete_breakpoint_at(&mut self, location: OpcodeLocation) { + fn delete_breakpoint_at(&mut self, location: DebugLocation) { if self.context.delete_breakpoint(&location) { - println!("Breakpoint at opcode {location} deleted"); + println!("Breakpoint at {location} deleted"); } else { - println!("Breakpoint at opcode {location} not set"); + println!("Breakpoint at {location} not set"); } } @@ -281,20 +304,19 @@ impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> { } fn restart_session(&mut self) { - let breakpoints: Vec = - self.context.iterate_breakpoints().copied().collect(); + let breakpoints: Vec = self.context.iterate_breakpoints().copied().collect(); let foreign_call_executor = Box::new(DefaultDebugForeignCallExecutor::from_artifact(true, self.debug_artifact)); self.context = DebugContext::new( self.blackbox_solver, - self.circuit, + self.circuits, self.debug_artifact, self.initial_witness.clone(), foreign_call_executor, self.unconstrained_functions, ); - for opcode_location in breakpoints { - self.context.add_breakpoint(opcode_location); + for debug_location in breakpoints { + self.context.add_breakpoint(debug_location); } self.last_result = DebugCommandResult::Ok; println!("Restarted debugging session."); @@ -372,21 +394,23 @@ impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> { self.context.is_solved() } - fn finalize(self) -> WitnessMap { + fn finalize(self) -> WitnessStack { self.context.finalize() } } pub fn run>( blackbox_solver: &B, - circuit: &Circuit, - debug_artifact: &DebugArtifact, + program: CompiledProgram, initial_witness: WitnessMap, - unconstrained_functions: &[BrilligBytecode], -) -> Result>, NargoError> { +) -> Result>, NargoError> { + let circuits = &program.program.functions; + let debug_artifact = + &DebugArtifact { debug_symbols: program.debug, file_map: program.file_map }; + let unconstrained_functions = &program.program.unconstrained_functions; let context = RefCell::new(ReplDebugger::new( blackbox_solver, - circuit, + circuits, debug_artifact, initial_witness, unconstrained_functions, @@ -480,7 +504,7 @@ pub fn run>( "break", command! { "add a breakpoint at an opcode location", - (LOCATION:OpcodeLocation) => |location| { + (LOCATION:DebugLocation) => |location| { ref_context.borrow_mut().add_breakpoint_at(location); Ok(CommandStatus::Done) } @@ -490,7 +514,7 @@ pub fn run>( "delete", command! { "delete breakpoint at an opcode location", - (LOCATION:OpcodeLocation) => |location| { + (LOCATION:DebugLocation) => |location| { ref_context.borrow_mut().delete_breakpoint_at(location); Ok(CommandStatus::Done) } @@ -576,8 +600,8 @@ pub fn run>( drop(repl); if context.borrow().is_solved() { - let solved_witness = context.into_inner().finalize(); - Ok(Some(solved_witness)) + let solved_witness_stack = context.into_inner().finalize(); + Ok(Some(solved_witness_stack)) } else { Ok(None) } diff --git a/noir/noir-repo/tooling/debugger/tests/debug.rs b/noir/noir-repo/tooling/debugger/tests/debug.rs index 313b6b30591..2dca6b95f0e 100644 --- a/noir/noir-repo/tooling/debugger/tests/debug.rs +++ b/noir/noir-repo/tooling/debugger/tests/debug.rs @@ -12,7 +12,7 @@ mod tests { let nargo_bin = cargo_bin("nargo").into_os_string().into_string().expect("Cannot parse nargo path"); - let timeout_seconds = 20; + let timeout_seconds = 25; let mut dbg_session = spawn_bash(Some(timeout_seconds * 1000)).expect("Could not start bash session"); diff --git a/noir/noir-repo/tooling/lsp/src/lib.rs b/noir/noir-repo/tooling/lsp/src/lib.rs index e47dfff714a..80c4573138c 100644 --- a/noir/noir-repo/tooling/lsp/src/lib.rs +++ b/noir/noir-repo/tooling/lsp/src/lib.rs @@ -21,7 +21,10 @@ use async_lsp::{ use fm::{codespan_files as files, FileManager}; use fxhash::FxHashSet; use lsp_types::{ - request::{PrepareRenameRequest, References, Rename}, + request::{ + DocumentSymbolRequest, HoverRequest, InlayHintRequest, PrepareRenameRequest, References, + Rename, + }, CodeLens, }; use nargo::{ @@ -45,10 +48,10 @@ use notifications::{ on_did_open_text_document, on_did_save_text_document, on_exit, on_initialized, }; use requests::{ - on_code_lens_request, on_formatting, on_goto_declaration_request, on_goto_definition_request, - on_goto_type_definition_request, on_initialize, on_prepare_rename_request, - on_profile_run_request, on_references_request, on_rename_request, on_shutdown, - on_test_run_request, on_tests_request, + on_code_lens_request, on_document_symbol_request, on_formatting, on_goto_declaration_request, + on_goto_definition_request, on_goto_type_definition_request, on_hover_request, on_initialize, + on_inlay_hint_request, on_prepare_rename_request, on_profile_run_request, + on_references_request, on_rename_request, on_shutdown, on_test_run_request, on_tests_request, }; use serde_json::Value as JsonValue; use thiserror::Error; @@ -126,9 +129,12 @@ impl NargoLspService { .request::(on_goto_definition_request) .request::(on_goto_declaration_request) .request::(on_goto_type_definition_request) + .request::(on_document_symbol_request) .request::(on_references_request) .request::(on_prepare_rename_request) .request::(on_rename_request) + .request::(on_hover_request) + .request::(on_inlay_hint_request) .notification::(on_initialized) .notification::(on_did_change_configuration) .notification::(on_did_open_text_document) @@ -229,43 +235,75 @@ fn byte_span_to_range<'a, F: files::Files<'a> + ?Sized>( } } -pub(crate) fn resolve_workspace_for_source_path(file_path: &Path) -> Result { - if let Some(toml_path) = find_file_manifest(file_path) { - resolve_workspace_from_toml( - &toml_path, - PackageSelection::All, - Some(NOIR_ARTIFACT_VERSION_STRING.to_string()), - ) - .map_err(|err| LspError::WorkspaceResolutionError(err.to_string())) - } else { - let Some(parent_folder) = file_path - .parent() - .and_then(|f| f.file_name()) - .and_then(|file_name_os_str| file_name_os_str.to_str()) - else { - return Err(LspError::WorkspaceResolutionError(format!( - "Could not resolve parent folder for file: {:?}", - file_path - ))); - }; - let assumed_package = Package { - version: None, - compiler_required_version: Some(NOIR_ARTIFACT_VERSION_STRING.to_string()), - root_dir: PathBuf::from(parent_folder), - package_type: PackageType::Binary, - entry_path: PathBuf::from(file_path), - name: CrateName::from_str(parent_folder) - .map_err(|err| LspError::WorkspaceResolutionError(err.to_string()))?, - dependencies: BTreeMap::new(), - }; - let workspace = Workspace { - root_dir: PathBuf::from(parent_folder), - members: vec![assumed_package], - selected_package_index: Some(0), - is_assumed: true, - }; - Ok(workspace) +pub(crate) fn resolve_workspace_for_source_path( + file_path: &Path, + root_path: &Option, +) -> Result { + // If there's a LSP root path, starting from file_path go up the directory tree + // searching for Nargo.toml files. The last one we find is the one we'll use + // (we'll assume Noir workspaces aren't nested) + if let Some(root_path) = root_path { + let mut current_path = file_path; + let mut current_toml_path = None; + while current_path.starts_with(root_path) { + if let Some(toml_path) = find_file_manifest(current_path) { + current_toml_path = Some(toml_path); + + if let Some(next_path) = current_path.parent() { + current_path = next_path; + } else { + break; + } + } else { + break; + } + } + + if let Some(toml_path) = current_toml_path { + return resolve_workspace_from_toml( + &toml_path, + PackageSelection::All, + Some(NOIR_ARTIFACT_VERSION_STRING.to_string()), + ) + .map_err(|err| LspError::WorkspaceResolutionError(err.to_string())); + } } + + let Some(parent_folder) = file_path + .parent() + .and_then(|f| f.file_name()) + .and_then(|file_name_os_str| file_name_os_str.to_str()) + else { + return Err(LspError::WorkspaceResolutionError(format!( + "Could not resolve parent folder for file: {:?}", + file_path + ))); + }; + let assumed_package = Package { + version: None, + compiler_required_version: Some(NOIR_ARTIFACT_VERSION_STRING.to_string()), + root_dir: PathBuf::from(parent_folder), + package_type: PackageType::Binary, + entry_path: PathBuf::from(file_path), + name: CrateName::from_str(parent_folder) + .map_err(|err| LspError::WorkspaceResolutionError(err.to_string()))?, + dependencies: BTreeMap::new(), + expression_width: None, + }; + let workspace = Workspace { + root_dir: PathBuf::from(parent_folder), + members: vec![assumed_package], + selected_package_index: Some(0), + is_assumed: true, + }; + Ok(workspace) +} + +pub(crate) fn workspace_package_for_file<'a>( + workspace: &'a Workspace, + file_path: &Path, +) -> Option<&'a Package> { + workspace.members.iter().find(|package| file_path.starts_with(&package.root_dir)) } pub(crate) fn prepare_package<'file_manager, 'parsed_files>( @@ -373,7 +411,7 @@ fn prepare_package_from_source_string() { let mut state = LspState::new(&client, acvm::blackbox_solver::StubbedBlackBoxSolver); let (mut context, crate_id) = crate::prepare_source(source.to_string(), &mut state); - let _check_result = noirc_driver::check_crate(&mut context, crate_id, false, false, false); + let _check_result = noirc_driver::check_crate(&mut context, crate_id, false, false, None); let main_func_id = context.get_main_function(&crate_id); assert!(main_func_id.is_some()); } diff --git a/noir/noir-repo/tooling/lsp/src/notifications/mod.rs b/noir/noir-repo/tooling/lsp/src/notifications/mod.rs index ed0ac13fae5..b6c28d37b91 100644 --- a/noir/noir-repo/tooling/lsp/src/notifications/mod.rs +++ b/noir/noir-repo/tooling/lsp/src/notifications/mod.rs @@ -14,7 +14,7 @@ use crate::types::{ use crate::{ byte_span_to_range, get_package_tests_in_crate, parse_diff, prepare_source, - resolve_workspace_for_source_path, LspState, + resolve_workspace_for_source_path, workspace_package_for_file, LspState, }; pub(super) fn on_initialized( @@ -39,7 +39,7 @@ pub(super) fn on_did_open_text_document( let document_uri = params.text_document.uri; - match process_noir_document(document_uri, state) { + match process_workspace_for_noir_document(document_uri, state) { Ok(_) => { state.open_documents_count += 1; ControlFlow::Continue(()) @@ -55,11 +55,14 @@ pub(super) fn on_did_change_text_document( let text = params.content_changes.into_iter().next().unwrap().text; state.input_files.insert(params.text_document.uri.to_string(), text.clone()); + let file_path = params.text_document.uri.to_file_path().unwrap(); + let (mut context, crate_id) = prepare_source(text, state); - let _ = check_crate(&mut context, crate_id, false, false, false); + let _ = check_crate(&mut context, crate_id, false, false, None); let workspace = match resolve_workspace_for_source_path( params.text_document.uri.to_file_path().unwrap().as_path(), + &state.root_path, ) { Ok(workspace) => workspace, Err(lsp_error) => { @@ -70,7 +73,8 @@ pub(super) fn on_did_change_text_document( .into())) } }; - let package = match workspace.members.first() { + + let package = match workspace_package_for_file(&workspace, &file_path) { Some(package) => package, None => { return ControlFlow::Break(Err(ResponseError::new( @@ -110,13 +114,16 @@ pub(super) fn on_did_save_text_document( ) -> ControlFlow> { let document_uri = params.text_document.uri; - match process_noir_document(document_uri, state) { + match process_workspace_for_noir_document(document_uri, state) { Ok(_) => ControlFlow::Continue(()), Err(err) => ControlFlow::Break(Err(err)), } } -fn process_noir_document( +// Given a Noir document, find the workspace it's contained in (an assumed workspace is created if +// it's only contained in a package), then type-checks the workspace's packages, +// caching code lenses and type definitions, and notifying about compilation errors. +pub(crate) fn process_workspace_for_noir_document( document_uri: lsp_types::Url, state: &mut LspState, ) -> Result<(), async_lsp::Error> { @@ -124,9 +131,10 @@ fn process_noir_document( ResponseError::new(ErrorCode::REQUEST_FAILED, "URI is not a valid file path") })?; - let workspace = resolve_workspace_for_source_path(&file_path).map_err(|lsp_error| { - ResponseError::new(ErrorCode::REQUEST_FAILED, lsp_error.to_string()) - })?; + let workspace = + resolve_workspace_for_source_path(&file_path, &state.root_path).map_err(|lsp_error| { + ResponseError::new(ErrorCode::REQUEST_FAILED, lsp_error.to_string()) + })?; let mut workspace_file_manager = file_manager_with_stdlib(&workspace.root_dir); insert_all_files_for_workspace_into_file_manager(&workspace, &mut workspace_file_manager); @@ -139,7 +147,7 @@ fn process_noir_document( let (mut context, crate_id) = crate::prepare_package(&workspace_file_manager, &parsed_files, package); - let file_diagnostics = match check_crate(&mut context, crate_id, false, false, false) { + let file_diagnostics = match check_crate(&mut context, crate_id, false, false, None) { Ok(((), warnings)) => warnings, Err(errors_and_warnings) => errors_and_warnings, }; @@ -190,6 +198,7 @@ fn process_noir_document( let severity = match diagnostic.kind { DiagnosticKind::Error => DiagnosticSeverity::ERROR, DiagnosticKind::Warning => DiagnosticSeverity::WARNING, + DiagnosticKind::Info => DiagnosticSeverity::INFORMATION, DiagnosticKind::Bug => DiagnosticSeverity::WARNING, }; Some(Diagnostic { diff --git a/noir/noir-repo/tooling/lsp/src/requests/code_lens_request.rs b/noir/noir-repo/tooling/lsp/src/requests/code_lens_request.rs index 744bddedd9d..51336a324da 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/code_lens_request.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/code_lens_request.rs @@ -21,6 +21,8 @@ const INFO_COMMAND: &str = "nargo.info"; const INFO_CODELENS_TITLE: &str = "Info"; const EXECUTE_COMMAND: &str = "nargo.execute"; const EXECUTE_CODELENS_TITLE: &str = "Execute"; +const DEBUG_COMMAND: &str = "nargo.debug.dap"; +const DEBUG_CODELENS_TITLE: &str = "Debug"; const PROFILE_COMMAND: &str = "nargo.profile"; const PROFILE_CODELENS_TITLE: &str = "Profile"; @@ -61,13 +63,17 @@ fn on_code_lens_request_inner( ResponseError::new(ErrorCode::REQUEST_FAILED, "Could not read file from disk") })?; - let workspace = resolve_workspace_for_source_path(file_path.as_path()).unwrap(); - let package = workspace.members.first().unwrap(); + let workspace = + resolve_workspace_for_source_path(file_path.as_path(), &state.root_path).unwrap(); + + let package = crate::workspace_package_for_file(&workspace, &file_path).ok_or_else(|| { + ResponseError::new(ErrorCode::REQUEST_FAILED, "Could not find package for file") + })?; let (mut context, crate_id) = prepare_source(source_string, state); // We ignore the warnings and errors produced by compilation for producing code lenses // because we can still get the test functions even if compilation fails - let _ = check_crate(&mut context, crate_id, false, false, false); + let _ = check_crate(&mut context, crate_id, false, false, None); let collected_lenses = collect_lenses_for_package(&context, crate_id, &workspace, package, None); @@ -154,35 +160,22 @@ pub(crate) fn collect_lenses_for_package( lenses.push(compile_lens); - let info_command = Command { - title: INFO_CODELENS_TITLE.to_string(), - command: INFO_COMMAND.into(), - arguments: Some(package_selection_args(workspace, package)), - }; - - let info_lens = CodeLens { range, command: Some(info_command), data: None }; - - lenses.push(info_lens); - - let execute_command = Command { - title: EXECUTE_CODELENS_TITLE.to_string(), - command: EXECUTE_COMMAND.into(), - arguments: Some(package_selection_args(workspace, package)), - }; - - let execute_lens = CodeLens { range, command: Some(execute_command), data: None }; - - lenses.push(execute_lens); - - let profile_command = Command { - title: PROFILE_CODELENS_TITLE.to_string(), - command: PROFILE_COMMAND.into(), - arguments: Some(package_selection_args(workspace, package)), - }; - - let profile_lens = CodeLens { range, command: Some(profile_command), data: None }; - - lenses.push(profile_lens); + let internal_command_lenses = [ + (INFO_CODELENS_TITLE, INFO_COMMAND), + (EXECUTE_CODELENS_TITLE, EXECUTE_COMMAND), + (PROFILE_CODELENS_TITLE, PROFILE_COMMAND), + (DEBUG_CODELENS_TITLE, DEBUG_COMMAND), + ] + .map(|(title, command)| { + let command = Command { + title: title.to_string(), + command: command.into(), + arguments: Some(package_selection_args(workspace, package)), + }; + CodeLens { range, command: Some(command), data: None } + }); + + lenses.append(&mut Vec::from(internal_command_lenses)); } } diff --git a/noir/noir-repo/tooling/lsp/src/requests/document_symbol.rs b/noir/noir-repo/tooling/lsp/src/requests/document_symbol.rs new file mode 100644 index 00000000000..67e2505d8fd --- /dev/null +++ b/noir/noir-repo/tooling/lsp/src/requests/document_symbol.rs @@ -0,0 +1,746 @@ +use std::future::{self, Future}; + +use async_lsp::ResponseError; +use fm::{FileId, FileMap, PathString}; +use lsp_types::{ + DocumentSymbol, DocumentSymbolParams, DocumentSymbolResponse, Location, Position, SymbolKind, + TextDocumentPositionParams, +}; +use noirc_errors::Span; +use noirc_frontend::{ + ast::{ + Expression, FunctionReturnType, Ident, LetStatement, NoirFunction, NoirStruct, NoirTrait, + NoirTraitImpl, TraitImplItem, TraitItem, TypeImpl, UnresolvedType, UnresolvedTypeData, + }, + parser::{Item, ItemKind, ParsedSubModule}, + ParsedModule, +}; + +use crate::LspState; + +use super::process_request; + +pub(crate) fn on_document_symbol_request( + state: &mut LspState, + params: DocumentSymbolParams, +) -> impl Future, ResponseError>> { + let Ok(file_path) = params.text_document.uri.to_file_path() else { + return future::ready(Ok(None)); + }; + + let text_document_position_params = TextDocumentPositionParams { + text_document: params.text_document.clone(), + position: Position { line: 0, character: 0 }, + }; + + let result = process_request(state, text_document_position_params, |args| { + args.files.get_file_id(&PathString::from_path(file_path)).map(|file_id| { + let file = args.files.get_file(file_id).unwrap(); + let source = file.source(); + let (parsed_module, _errors) = noirc_frontend::parse_program(source); + + let mut collector = DocumentSymbolCollector::new(file_id, args.files); + let mut symbols = Vec::new(); + collector.collect_in_parsed_module(&parsed_module, &mut symbols); + DocumentSymbolResponse::Nested(symbols) + }) + }); + + future::ready(result) +} + +struct DocumentSymbolCollector<'a> { + file_id: FileId, + files: &'a FileMap, +} + +impl<'a> DocumentSymbolCollector<'a> { + fn new(file_id: FileId, files: &'a FileMap) -> Self { + Self { file_id, files } + } + + fn collect_in_parsed_module( + &mut self, + parsed_module: &ParsedModule, + symbols: &mut Vec, + ) { + for item in &parsed_module.items { + self.collect_in_item(item, symbols); + } + } + + fn collect_in_item(&mut self, item: &Item, symbols: &mut Vec) { + match &item.kind { + ItemKind::Function(noir_function) => { + self.collect_in_noir_function(noir_function, item.span, symbols); + } + ItemKind::Struct(noir_struct) => { + self.collect_in_noir_struct(noir_struct, item.span, symbols); + } + ItemKind::Trait(noir_trait) => { + self.collect_in_noir_trait(noir_trait, item.span, symbols); + } + ItemKind::TraitImpl(noir_trait_impl) => { + self.collect_in_noir_trait_impl(noir_trait_impl, item.span, symbols); + } + ItemKind::Impl(type_impl) => { + self.collect_in_type_impl(type_impl, item.span, symbols); + } + ItemKind::Submodules(parsed_sub_module) => { + self.collect_in_parsed_sub_module(parsed_sub_module, item.span, symbols); + } + ItemKind::Global(let_statement) => { + self.collect_in_global(let_statement, item.span, symbols); + } + ItemKind::Import(..) | ItemKind::TypeAlias(..) | ItemKind::ModuleDecl(..) => (), + } + } + + fn collect_in_noir_function( + &mut self, + noir_function: &NoirFunction, + span: Span, + symbols: &mut Vec, + ) { + let Some(location) = self.to_lsp_location(span) else { + return; + }; + + let Some(selection_location) = self.to_lsp_location(noir_function.name_ident().span()) + else { + return; + }; + + #[allow(deprecated)] + symbols.push(DocumentSymbol { + name: noir_function.name().to_string(), + detail: Some(noir_function.def.signature()), + kind: SymbolKind::FUNCTION, + tags: None, + deprecated: None, + range: location.range, + selection_range: selection_location.range, + children: None, + }); + } + + fn collect_in_noir_struct( + &mut self, + noir_struct: &NoirStruct, + span: Span, + symbols: &mut Vec, + ) { + let Some(location) = self.to_lsp_location(span) else { + return; + }; + + let Some(selection_location) = self.to_lsp_location(noir_struct.name.span()) else { + return; + }; + + let mut children = Vec::new(); + for (field_name, typ) in &noir_struct.fields { + let span = if let Some(typ) = typ.span { + Span::from(field_name.span().start()..typ.end()) + } else { + field_name.span() + }; + + let Some(field_location) = self.to_lsp_location(span) else { + continue; + }; + + let Some(field_name_location) = self.to_lsp_location(field_name.span()) else { + continue; + }; + + #[allow(deprecated)] + children.push(DocumentSymbol { + name: field_name.to_string(), + detail: None, + kind: SymbolKind::FIELD, + tags: None, + deprecated: None, + range: field_location.range, + selection_range: field_name_location.range, + children: None, + }); + } + + #[allow(deprecated)] + symbols.push(DocumentSymbol { + name: noir_struct.name.to_string(), + detail: None, + kind: SymbolKind::STRUCT, + tags: None, + deprecated: None, + range: location.range, + selection_range: selection_location.range, + children: Some(children), + }); + } + + fn collect_in_noir_trait( + &mut self, + noir_trait: &NoirTrait, + span: Span, + symbols: &mut Vec, + ) { + let Some(location) = self.to_lsp_location(span) else { + return; + }; + + let Some(selection_location) = self.to_lsp_location(noir_trait.name.span()) else { + return; + }; + + let mut children = Vec::new(); + for item in &noir_trait.items { + self.collect_in_noir_trait_item(item, &mut children); + } + + #[allow(deprecated)] + symbols.push(DocumentSymbol { + name: noir_trait.name.to_string(), + detail: None, + kind: SymbolKind::INTERFACE, + tags: None, + deprecated: None, + range: location.range, + selection_range: selection_location.range, + children: Some(children), + }); + } + + fn collect_in_noir_trait_item( + &mut self, + trait_item: &TraitItem, + symbols: &mut Vec, + ) { + // Ideally `TraitItem` has a `span` for the entire definition, and we'd use that + // for the `range` property. For now we do our best to find a reasonable span. + match trait_item { + TraitItem::Function { name, parameters, return_type, body, .. } => { + let Some(name_location) = self.to_lsp_location(name.span()) else { + return; + }; + + let mut span = name.span(); + + // If there are parameters, extend the span to include the last parameter. + if let Some((param_name, _param_type)) = parameters.last() { + span = Span::from(span.start()..param_name.span().end()); + } + + // If there's a return type, extend the span to include it + match return_type { + FunctionReturnType::Default(return_type_span) => { + span = Span::from(span.start()..return_type_span.end()); + } + FunctionReturnType::Ty(typ) => { + if let Some(type_span) = typ.span { + span = Span::from(span.start()..type_span.end()); + } + } + } + + // If there's a body, extend the span to include it + if let Some(body) = body { + if let Some(statement) = body.statements.last() { + span = Span::from(span.start()..statement.span.end()); + } + } + + let Some(location) = self.to_lsp_location(span) else { + return; + }; + + #[allow(deprecated)] + symbols.push(DocumentSymbol { + name: name.to_string(), + detail: None, + kind: SymbolKind::METHOD, + tags: None, + deprecated: None, + range: location.range, + selection_range: name_location.range, + children: None, + }); + } + TraitItem::Constant { name, typ, default_value } => { + self.collect_in_constant(name, typ, default_value.as_ref(), symbols); + } + TraitItem::Type { name } => { + self.collect_in_type(name, None, symbols); + } + } + } + + fn collect_in_constant( + &mut self, + name: &Ident, + typ: &UnresolvedType, + default_value: Option<&Expression>, + symbols: &mut Vec, + ) { + let Some(name_location) = self.to_lsp_location(name.span()) else { + return; + }; + + let mut span = name.span(); + + // If there's a type span, extend the span to include it + if let Some(type_span) = typ.span { + span = Span::from(span.start()..type_span.end()); + } + + // If there's a default value, extend the span to include it + if let Some(default_value) = default_value { + span = Span::from(span.start()..default_value.span.end()); + } + + let Some(location) = self.to_lsp_location(span) else { + return; + }; + + #[allow(deprecated)] + symbols.push(DocumentSymbol { + name: name.to_string(), + detail: None, + kind: SymbolKind::CONSTANT, + tags: None, + deprecated: None, + range: location.range, + selection_range: name_location.range, + children: None, + }); + } + + fn collect_in_type( + &mut self, + name: &Ident, + typ: Option<&UnresolvedType>, + symbols: &mut Vec, + ) { + let Some(name_location) = self.to_lsp_location(name.span()) else { + return; + }; + + let span = if let Some(type_span) = typ.and_then(|typ| typ.span) { + Span::from(name.span().start()..type_span.end()) + } else { + name.span() + }; + + let Some(location) = self.to_lsp_location(span) else { + return; + }; + + #[allow(deprecated)] + symbols.push(DocumentSymbol { + name: name.to_string(), + detail: None, + kind: SymbolKind::TYPE_PARAMETER, + tags: None, + deprecated: None, + range: location.range, + selection_range: name_location.range, + children: None, + }); + } + + fn collect_in_noir_trait_impl( + &mut self, + noir_trait_impl: &NoirTraitImpl, + span: Span, + symbols: &mut Vec, + ) { + let Some(location) = self.to_lsp_location(span) else { + return; + }; + + let Some(name_location) = self.to_lsp_location(noir_trait_impl.trait_name.span) else { + return; + }; + + let mut trait_name = String::new(); + trait_name.push_str(&noir_trait_impl.trait_name.to_string()); + if !noir_trait_impl.trait_generics.is_empty() { + trait_name.push('<'); + for (index, generic) in noir_trait_impl.trait_generics.iter().enumerate() { + if index > 0 { + trait_name.push_str(", "); + } + trait_name.push_str(&generic.to_string()); + } + trait_name.push('>'); + } + + let mut children = Vec::new(); + for trait_impl_item in &noir_trait_impl.items { + self.collect_in_trait_impl_item(trait_impl_item, &mut children); + } + + #[allow(deprecated)] + symbols.push(DocumentSymbol { + name: format!("impl {} for {}", trait_name, noir_trait_impl.object_type), + detail: None, + kind: SymbolKind::NAMESPACE, + tags: None, + deprecated: None, + range: location.range, + selection_range: name_location.range, + children: Some(children), + }); + } + + fn collect_in_trait_impl_item( + &mut self, + trait_impl_item: &TraitImplItem, + symbols: &mut Vec, + ) { + match trait_impl_item { + TraitImplItem::Function(noir_function) => { + let span = Span::from( + noir_function.name_ident().span().start()..noir_function.span().end(), + ); + self.collect_in_noir_function(noir_function, span, symbols); + } + TraitImplItem::Constant(name, typ, default_value) => { + self.collect_in_constant(name, typ, Some(default_value), symbols); + } + TraitImplItem::Type { name, alias } => self.collect_in_type(name, Some(alias), symbols), + } + } + + fn collect_in_type_impl( + &mut self, + type_impl: &TypeImpl, + span: Span, + symbols: &mut Vec, + ) { + let Some(location) = self.to_lsp_location(span) else { + return; + }; + + let UnresolvedTypeData::Named(name_path, ..) = &type_impl.object_type.typ else { + return; + }; + + let name = name_path.last_segment(); + + let Some(name_location) = self.to_lsp_location(name.span()) else { + return; + }; + + let mut children = Vec::new(); + for (noir_function, noir_function_span) in &type_impl.methods { + self.collect_in_noir_function(noir_function, *noir_function_span, &mut children); + } + + #[allow(deprecated)] + symbols.push(DocumentSymbol { + name: name.to_string(), + detail: None, + kind: SymbolKind::NAMESPACE, + tags: None, + deprecated: None, + range: location.range, + selection_range: name_location.range, + children: Some(children), + }); + } + + fn collect_in_parsed_sub_module( + &mut self, + parsed_sub_module: &ParsedSubModule, + span: Span, + symbols: &mut Vec, + ) { + let Some(name_location) = self.to_lsp_location(parsed_sub_module.name.span()) else { + return; + }; + + let Some(location) = self.to_lsp_location(span) else { + return; + }; + + let mut children = Vec::new(); + for item in &parsed_sub_module.contents.items { + self.collect_in_item(item, &mut children); + } + + #[allow(deprecated)] + symbols.push(DocumentSymbol { + name: parsed_sub_module.name.to_string(), + detail: None, + kind: SymbolKind::MODULE, + tags: None, + deprecated: None, + range: location.range, + selection_range: name_location.range, + children: Some(children), + }); + } + + fn collect_in_global( + &mut self, + global: &LetStatement, + span: Span, + symbols: &mut Vec, + ) { + let Some(name_location) = self.to_lsp_location(global.pattern.span()) else { + return; + }; + + let Some(location) = self.to_lsp_location(span) else { + return; + }; + + #[allow(deprecated)] + symbols.push(DocumentSymbol { + name: global.pattern.to_string(), + detail: None, + kind: SymbolKind::CONSTANT, + tags: None, + deprecated: None, + range: location.range, + selection_range: name_location.range, + children: None, + }); + } + + fn to_lsp_location(&self, span: Span) -> Option { + super::to_lsp_location(self.files, self.file_id, span) + } +} + +#[cfg(test)] +mod document_symbol_tests { + use crate::test_utils; + + use super::*; + use lsp_types::{ + PartialResultParams, Range, SymbolKind, TextDocumentIdentifier, WorkDoneProgressParams, + }; + use tokio::test; + + #[test] + async fn test_document_symbol() { + let (mut state, noir_text_document) = test_utils::init_lsp_server("document_symbol").await; + + let response = on_document_symbol_request( + &mut state, + DocumentSymbolParams { + text_document: TextDocumentIdentifier { uri: noir_text_document }, + work_done_progress_params: WorkDoneProgressParams { work_done_token: None }, + partial_result_params: PartialResultParams { partial_result_token: None }, + }, + ) + .await + .expect("Could not execute on_document_symbol_request") + .unwrap(); + + let DocumentSymbolResponse::Nested(symbols) = response else { + panic!("Expected response to be nested"); + }; + + assert_eq!( + symbols, + vec![ + #[allow(deprecated)] + DocumentSymbol { + name: "foo".to_string(), + detail: Some("fn foo(_x: i32)".to_string()), + kind: SymbolKind::FUNCTION, + tags: None, + deprecated: None, + range: Range { + start: Position { line: 0, character: 0 }, + end: Position { line: 2, character: 1 }, + }, + selection_range: Range { + start: Position { line: 0, character: 3 }, + end: Position { line: 0, character: 6 }, + }, + children: None, + }, + #[allow(deprecated)] + DocumentSymbol { + name: "SomeStruct".to_string(), + detail: None, + kind: SymbolKind::STRUCT, + tags: None, + deprecated: None, + range: Range { + start: Position { line: 4, character: 0 }, + end: Position { line: 6, character: 1 }, + }, + selection_range: Range { + start: Position { line: 4, character: 7 }, + end: Position { line: 4, character: 17 }, + }, + children: Some(vec![ + #[allow(deprecated)] + DocumentSymbol { + name: "field".to_string(), + detail: None, + kind: SymbolKind::FIELD, + tags: None, + deprecated: None, + range: Range { + start: Position { line: 5, character: 4 }, + end: Position { line: 5, character: 14 }, + }, + selection_range: Range { + start: Position { line: 5, character: 4 }, + end: Position { line: 5, character: 9 }, + }, + children: None, + }, + ],), + }, + #[allow(deprecated)] + DocumentSymbol { + name: "SomeStruct".to_string(), + detail: None, + kind: SymbolKind::NAMESPACE, + tags: None, + deprecated: None, + range: Range { + start: Position { line: 8, character: 0 }, + end: Position { line: 12, character: 1 }, + }, + selection_range: Range { + start: Position { line: 8, character: 5 }, + end: Position { line: 8, character: 15 }, + }, + children: Some(vec![ + #[allow(deprecated)] + DocumentSymbol { + name: "new".to_string(), + detail: Some("fn new() -> SomeStruct".to_string()), + kind: SymbolKind::FUNCTION, + tags: None, + deprecated: None, + range: Range { + start: Position { line: 9, character: 4 }, + end: Position { line: 11, character: 5 }, + }, + selection_range: Range { + start: Position { line: 9, character: 7 }, + end: Position { line: 9, character: 10 }, + }, + children: None, + }, + ],), + }, + #[allow(deprecated)] + DocumentSymbol { + name: "SomeTrait".to_string(), + detail: None, + kind: SymbolKind::INTERFACE, + tags: None, + deprecated: None, + range: Range { + start: Position { line: 14, character: 0 }, + end: Position { line: 16, character: 1 }, + }, + selection_range: Range { + start: Position { line: 14, character: 6 }, + end: Position { line: 14, character: 15 }, + }, + children: Some(vec![ + #[allow(deprecated)] + DocumentSymbol { + name: "some_method".to_string(), + detail: None, + kind: SymbolKind::METHOD, + tags: None, + deprecated: None, + range: Range { + start: Position { line: 15, character: 7 }, + end: Position { line: 15, character: 25 }, + }, + selection_range: Range { + start: Position { line: 15, character: 7 }, + end: Position { line: 15, character: 18 }, + }, + children: None, + }, + ],), + }, + #[allow(deprecated)] + DocumentSymbol { + name: "impl SomeTrait for SomeStruct".to_string(), + detail: None, + kind: SymbolKind::NAMESPACE, + tags: None, + deprecated: None, + range: Range { + start: Position { line: 18, character: 0 }, + end: Position { line: 21, character: 1 }, + }, + selection_range: Range { + start: Position { line: 18, character: 5 }, + end: Position { line: 18, character: 14 }, + }, + children: Some(vec![ + #[allow(deprecated)] + DocumentSymbol { + name: "some_method".to_string(), + detail: Some("fn some_method(_x: i32)".to_string()), + kind: SymbolKind::FUNCTION, + tags: None, + deprecated: None, + range: Range { + start: Position { line: 19, character: 7 }, + end: Position { line: 20, character: 5 }, + }, + selection_range: Range { + start: Position { line: 19, character: 7 }, + end: Position { line: 19, character: 18 }, + }, + children: None, + }, + ],), + }, + #[allow(deprecated)] + DocumentSymbol { + name: "submodule".to_string(), + detail: None, + kind: SymbolKind::MODULE, + tags: None, + deprecated: None, + range: Range { + start: Position { line: 23, character: 0 }, + end: Position { line: 25, character: 1 }, + }, + selection_range: Range { + start: Position { line: 23, character: 4 }, + end: Position { line: 23, character: 13 }, + }, + children: Some(vec![ + #[allow(deprecated)] + DocumentSymbol { + name: "SOME_GLOBAL".to_string(), + detail: None, + kind: SymbolKind::CONSTANT, + tags: None, + deprecated: None, + range: Range { + start: Position { line: 24, character: 4 }, + end: Position { line: 24, character: 27 } + }, + selection_range: Range { + start: Position { line: 24, character: 11 }, + end: Position { line: 24, character: 22 } + }, + children: None + } + ]), + }, + ] + ); + } +} diff --git a/noir/noir-repo/tooling/lsp/src/requests/goto_declaration.rs b/noir/noir-repo/tooling/lsp/src/requests/goto_declaration.rs index 627cd8d203e..bd0f0afb827 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/goto_declaration.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/goto_declaration.rs @@ -20,10 +20,10 @@ fn on_goto_definition_inner( state: &mut LspState, params: GotoDeclarationParams, ) -> Result { - process_request(state, params.text_document_position_params, |location, interner, files| { - interner.get_declaration_location_from(location).and_then(|found_location| { + process_request(state, params.text_document_position_params, |args| { + args.interner.get_declaration_location_from(args.location).and_then(|found_location| { let file_id = found_location.file; - let definition_position = to_lsp_location(files, file_id, found_location.span)?; + let definition_position = to_lsp_location(args.files, file_id, found_location.span)?; let response = GotoDeclarationResponse::from(definition_position).to_owned(); Some(response) }) diff --git a/noir/noir-repo/tooling/lsp/src/requests/goto_definition.rs b/noir/noir-repo/tooling/lsp/src/requests/goto_definition.rs index 3713e8b646a..5e655766024 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/goto_definition.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/goto_definition.rs @@ -29,15 +29,21 @@ fn on_goto_definition_inner( params: GotoDefinitionParams, return_type_location_instead: bool, ) -> Result { - process_request(state, params.text_document_position_params, |location, interner, files| { - interner.get_definition_location_from(location, return_type_location_instead).and_then( - |found_location| { + process_request(state, params.text_document_position_params, |args| { + args.interner + .get_definition_location_from(args.location, return_type_location_instead) + .or_else(|| { + args.interner + .reference_at_location(args.location) + .map(|reference| args.interner.reference_location(reference)) + }) + .and_then(|found_location| { let file_id = found_location.file; - let definition_position = to_lsp_location(files, file_id, found_location.span)?; + let definition_position = + to_lsp_location(args.files, file_id, found_location.span)?; let response = GotoDefinitionResponse::from(definition_position).to_owned(); Some(response) - }, - ) + }) }) } @@ -201,4 +207,32 @@ mod goto_definition_tests { async fn goto_for_local_variable() { expect_goto_for_all_references("local_variable", "some_var", 0).await; } + + #[test] + async fn goto_at_struct_definition_finds_same_struct() { + expect_goto( + "go_to_definition", + Position { line: 21, character: 7 }, // "Foo" in "struct Foo" + "src/main.nr", + Range { + start: Position { line: 21, character: 7 }, + end: Position { line: 21, character: 10 }, + }, + ) + .await; + } + + #[test] + async fn goto_at_trait_definition_finds_same_trait() { + expect_goto( + "go_to_definition", + Position { line: 25, character: 6 }, // "Trait" in "trait Trait" + "src/main.nr", + Range { + start: Position { line: 25, character: 6 }, + end: Position { line: 25, character: 11 }, + }, + ) + .await; + } } diff --git a/noir/noir-repo/tooling/lsp/src/requests/hover.rs b/noir/noir-repo/tooling/lsp/src/requests/hover.rs new file mode 100644 index 00000000000..161fd20f555 --- /dev/null +++ b/noir/noir-repo/tooling/lsp/src/requests/hover.rs @@ -0,0 +1,641 @@ +use std::future::{self, Future}; + +use async_lsp::ResponseError; +use lsp_types::{Hover, HoverContents, HoverParams, MarkupContent, MarkupKind}; +use noirc_frontend::{ + ast::Visibility, + graph::CrateId, + hir::def_map::ModuleId, + hir_def::stmt::HirPattern, + macros_api::{NodeInterner, StructId}, + node_interner::{ + DefinitionId, DefinitionKind, FuncId, GlobalId, ReferenceId, TraitId, TypeAliasId, + }, + Generics, Type, +}; + +use crate::LspState; + +use super::{process_request, to_lsp_location, ProcessRequestCallbackArgs}; + +pub(crate) fn on_hover_request( + state: &mut LspState, + params: HoverParams, +) -> impl Future, ResponseError>> { + let result = process_request(state, params.text_document_position_params, |args| { + args.interner.reference_at_location(args.location).map(|reference| { + let location = args.interner.reference_location(reference); + let lsp_location = to_lsp_location(args.files, location.file, location.span); + Hover { + range: lsp_location.map(|location| location.range), + contents: HoverContents::Markup(MarkupContent { + kind: MarkupKind::Markdown, + value: format_reference(reference, &args), + }), + } + }) + }); + + future::ready(result) +} + +fn format_reference(reference: ReferenceId, args: &ProcessRequestCallbackArgs) -> String { + match reference { + ReferenceId::Module(id) => format_module(id, args), + ReferenceId::Struct(id) => format_struct(id, args), + ReferenceId::StructMember(id, field_index) => format_struct_member(id, field_index, args), + ReferenceId::Trait(id) => format_trait(id, args), + ReferenceId::Global(id) => format_global(id, args), + ReferenceId::Function(id) => format_function(id, args), + ReferenceId::Alias(id) => format_alias(id, args), + ReferenceId::Local(id) => format_local(id, args), + ReferenceId::Reference(location, _) => { + format_reference(args.interner.find_referenced(location).unwrap(), args) + } + } +} +fn format_module(id: ModuleId, args: &ProcessRequestCallbackArgs) -> String { + let module_attributes = args.interner.module_attributes(&id); + + let mut string = String::new(); + if format_parent_module_from_module_id( + &ModuleId { krate: id.krate, local_id: module_attributes.parent }, + args, + &mut string, + ) { + string.push('\n'); + } + string.push_str(" "); + string.push_str("mod "); + string.push_str(&module_attributes.name); + string +} + +fn format_struct(id: StructId, args: &ProcessRequestCallbackArgs) -> String { + let struct_type = args.interner.get_struct(id); + let struct_type = struct_type.borrow(); + + let mut string = String::new(); + if format_parent_module(ReferenceId::Struct(id), args, &mut string) { + string.push('\n'); + } + string.push_str(" "); + string.push_str("struct "); + string.push_str(&struct_type.name.0.contents); + format_generics(&struct_type.generics, &mut string); + string.push_str(" {\n"); + for (field_name, field_type) in struct_type.get_fields_as_written() { + string.push_str(" "); + string.push_str(&field_name); + string.push_str(": "); + string.push_str(&format!("{}", field_type)); + string.push_str(",\n"); + } + string.push_str(" }"); + string +} + +fn format_struct_member( + id: StructId, + field_index: usize, + args: &ProcessRequestCallbackArgs, +) -> String { + let struct_type = args.interner.get_struct(id); + let struct_type = struct_type.borrow(); + let (field_name, field_type) = struct_type.field_at(field_index); + + let mut string = String::new(); + if format_parent_module(ReferenceId::Struct(id), args, &mut string) { + string.push_str("::"); + } + string.push_str(&struct_type.name.0.contents); + string.push('\n'); + string.push_str(" "); + string.push_str(&field_name.0.contents); + string.push_str(": "); + string.push_str(&format!("{}", field_type)); + string +} + +fn format_trait(id: TraitId, args: &ProcessRequestCallbackArgs) -> String { + let a_trait = args.interner.get_trait(id); + + let mut string = String::new(); + if format_parent_module(ReferenceId::Trait(id), args, &mut string) { + string.push('\n'); + } + string.push_str(" "); + string.push_str("trait "); + string.push_str(&a_trait.name.0.contents); + format_generics(&a_trait.generics, &mut string); + string +} + +fn format_global(id: GlobalId, args: &ProcessRequestCallbackArgs) -> String { + let global_info = args.interner.get_global(id); + let definition_id = global_info.definition_id; + let typ = args.interner.definition_type(definition_id); + + let mut string = String::new(); + if format_parent_module(ReferenceId::Global(id), args, &mut string) { + string.push('\n'); + } + string.push_str(" "); + string.push_str("global "); + string.push_str(&global_info.ident.0.contents); + string.push_str(": "); + string.push_str(&format!("{}", typ)); + string +} + +fn format_function(id: FuncId, args: &ProcessRequestCallbackArgs) -> String { + let func_meta = args.interner.function_meta(&id); + let func_name_definition_id = args.interner.definition(func_meta.name.id); + + let mut string = String::new(); + let formatted_parent_module = + format_parent_module(ReferenceId::Function(id), args, &mut string); + let formatted_parent_struct = if let Some(struct_id) = func_meta.struct_id { + let struct_type = args.interner.get_struct(struct_id); + let struct_type = struct_type.borrow(); + if formatted_parent_module { + string.push_str("::"); + } + string.push_str(&struct_type.name.0.contents); + true + } else { + false + }; + if formatted_parent_module || formatted_parent_struct { + string.push('\n'); + } + string.push_str(" "); + string.push_str("fn "); + string.push_str(&func_name_definition_id.name); + format_generics(&func_meta.direct_generics, &mut string); + string.push('('); + let parameters = &func_meta.parameters; + for (index, (pattern, typ, visibility)) in parameters.iter().enumerate() { + format_pattern(pattern, args.interner, &mut string); + if !pattern_is_self(pattern, args.interner) { + string.push_str(": "); + if matches!(visibility, Visibility::Public) { + string.push_str("pub "); + } + string.push_str(&format!("{}", typ)); + } + if index != parameters.len() - 1 { + string.push_str(", "); + } + } + + string.push(')'); + + let return_type = func_meta.return_type(); + match return_type { + Type::Unit => (), + _ => { + string.push_str(" -> "); + string.push_str(&format!("{}", return_type)); + } + } + + string +} + +fn format_alias(id: TypeAliasId, args: &ProcessRequestCallbackArgs) -> String { + let type_alias = args.interner.get_type_alias(id); + let type_alias = type_alias.borrow(); + + let mut string = String::new(); + format_parent_module(ReferenceId::Alias(id), args, &mut string); + string.push('\n'); + string.push_str(" "); + string.push_str("type "); + string.push_str(&type_alias.name.0.contents); + string.push_str(" = "); + string.push_str(&format!("{}", &type_alias.typ)); + string +} + +fn format_local(id: DefinitionId, args: &ProcessRequestCallbackArgs) -> String { + let definition_info = args.interner.definition(id); + let DefinitionKind::Local(expr_id) = definition_info.kind else { + panic!("Expected a local reference to reference a local definition") + }; + let typ = args.interner.definition_type(id); + + let mut string = String::new(); + string.push_str(" "); + if definition_info.comptime { + string.push_str("comptime "); + } + if expr_id.is_some() { + string.push_str("let "); + } + if definition_info.mutable { + if expr_id.is_none() { + string.push_str("let "); + } + string.push_str("mut "); + } + string.push_str(&definition_info.name); + if !matches!(typ, Type::Error) { + string.push_str(": "); + string.push_str(&format!("{}", typ)); + } + string +} + +fn format_generics(generics: &Generics, string: &mut String) { + if generics.is_empty() { + return; + } + + string.push('<'); + for (index, generic) in generics.iter().enumerate() { + string.push_str(&generic.name); + if index != generics.len() - 1 { + string.push_str(", "); + } + } + string.push('>'); +} +fn format_pattern(pattern: &HirPattern, interner: &NodeInterner, string: &mut String) { + match pattern { + HirPattern::Identifier(ident) => { + let definition = interner.definition(ident.id); + string.push_str(&definition.name); + } + HirPattern::Mutable(pattern, _) => { + string.push_str("mut "); + format_pattern(pattern, interner, string); + } + HirPattern::Tuple(..) | HirPattern::Struct(..) => { + string.push('_'); + } + } +} + +fn pattern_is_self(pattern: &HirPattern, interner: &NodeInterner) -> bool { + match pattern { + HirPattern::Identifier(ident) => { + let definition = interner.definition(ident.id); + definition.name == "self" + } + HirPattern::Mutable(pattern, _) => pattern_is_self(pattern, interner), + HirPattern::Tuple(..) | HirPattern::Struct(..) => false, + } +} + +fn format_parent_module( + referenced: ReferenceId, + args: &ProcessRequestCallbackArgs, + string: &mut String, +) -> bool { + let Some(module) = args.interner.reference_module(referenced) else { + return false; + }; + + format_parent_module_from_module_id(module, args, string) +} + +fn format_parent_module_from_module_id( + module: &ModuleId, + args: &ProcessRequestCallbackArgs, + string: &mut String, +) -> bool { + let crate_id = module.krate; + let crate_name = match crate_id { + CrateId::Root(_) => Some(args.root_crate_name.clone()), + CrateId::Crate(_) => args + .root_crate_dependencies + .iter() + .find(|dep| dep.crate_id == crate_id) + .map(|dep| format!("{}", dep.name)), + CrateId::Stdlib(_) => Some("std".to_string()), + CrateId::Dummy => None, + }; + + let wrote_crate = if let Some(crate_name) = crate_name { + string.push_str(" "); + string.push_str(&crate_name); + true + } else { + false + }; + + let Some(module_attributes) = args.interner.try_module_attributes(module) else { + return wrote_crate; + }; + + if wrote_crate { + string.push_str("::"); + } else { + string.push_str(" "); + } + + let mut segments = Vec::new(); + let mut current_attributes = module_attributes; + while let Some(parent_attributes) = args.interner.try_module_attributes(&ModuleId { + krate: module.krate, + local_id: current_attributes.parent, + }) { + segments.push(&parent_attributes.name); + current_attributes = parent_attributes; + } + + for segment in segments.iter().rev() { + string.push_str(segment); + string.push_str("::"); + } + + string.push_str(&module_attributes.name); + + true +} + +#[cfg(test)] +mod hover_tests { + use crate::test_utils; + + use super::*; + use lsp_types::{ + Position, TextDocumentIdentifier, TextDocumentPositionParams, Url, WorkDoneProgressParams, + }; + use tokio::test; + + async fn assert_hover(directory: &str, file: &str, position: Position, expected_text: &str) { + let (mut state, noir_text_document) = test_utils::init_lsp_server(directory).await; + + // noir_text_document is always `src/main.nr` in the workspace directory, so let's go to the workspace dir + let noir_text_document = noir_text_document.to_file_path().unwrap(); + let workspace_dir = noir_text_document.parent().unwrap().parent().unwrap(); + + let file_uri = Url::from_file_path(workspace_dir.join(file)).unwrap(); + + let hover = on_hover_request( + &mut state, + HoverParams { + text_document_position_params: TextDocumentPositionParams { + text_document: TextDocumentIdentifier { uri: file_uri }, + position, + }, + work_done_progress_params: WorkDoneProgressParams { work_done_token: None }, + }, + ) + .await + .expect("Could not execute hover") + .unwrap(); + + let HoverContents::Markup(markup) = hover.contents else { + panic!("Expected hover contents to be Markup"); + }; + + assert_eq!(markup.value, expected_text); + } + + #[test] + async fn hover_on_module() { + assert_hover( + "workspace", + "two/src/lib.nr", + Position { line: 6, character: 9 }, + r#" one + mod subone"#, + ) + .await; + } + + #[test] + async fn hover_on_struct() { + assert_hover( + "workspace", + "two/src/lib.nr", + Position { line: 9, character: 20 }, + r#" one::subone + struct SubOneStruct { + some_field: i32, + some_other_field: Field, + }"#, + ) + .await; + } + + #[test] + async fn hover_on_generic_struct() { + assert_hover( + "workspace", + "two/src/lib.nr", + Position { line: 46, character: 17 }, + r#" one::subone + struct GenericStruct { + }"#, + ) + .await; + } + + #[test] + async fn hover_on_struct_member() { + assert_hover( + "workspace", + "two/src/lib.nr", + Position { line: 9, character: 35 }, + r#" one::subone::SubOneStruct + some_field: i32"#, + ) + .await; + } + + #[test] + async fn hover_on_trait() { + assert_hover( + "workspace", + "two/src/lib.nr", + Position { line: 12, character: 17 }, + r#" one::subone + trait SomeTrait"#, + ) + .await; + } + + #[test] + async fn hover_on_global() { + assert_hover( + "workspace", + "two/src/lib.nr", + Position { line: 15, character: 25 }, + r#" one::subone + global some_global: Field"#, + ) + .await; + } + + #[test] + async fn hover_on_function() { + assert_hover( + "workspace", + "two/src/lib.nr", + Position { line: 3, character: 4 }, + r#" one + fn function_one()"#, + ) + .await; + } + + #[test] + async fn hover_on_local_function() { + assert_hover( + "workspace", + "two/src/lib.nr", + Position { line: 2, character: 7 }, + r#" two + fn function_two()"#, + ) + .await; + } + + #[test] + async fn hover_on_struct_method() { + assert_hover( + "workspace", + "two/src/lib.nr", + Position { line: 20, character: 6 }, + r#" one::subone::SubOneStruct + fn foo(self, x: i32, y: i32) -> Field"#, + ) + .await; + } + + #[test] + async fn hover_on_local_var() { + assert_hover( + "workspace", + "two/src/lib.nr", + Position { line: 25, character: 12 }, + " let regular_var: Field", + ) + .await; + } + + #[test] + async fn hover_on_local_mut_var() { + assert_hover( + "workspace", + "two/src/lib.nr", + Position { line: 27, character: 4 }, + " let mut mutable_var: Field", + ) + .await; + } + + #[test] + async fn hover_on_parameter() { + assert_hover( + "workspace", + "two/src/lib.nr", + Position { line: 31, character: 12 }, + " some_param: i32", + ) + .await; + } + + #[test] + async fn hover_on_alias() { + assert_hover( + "workspace", + "two/src/lib.nr", + Position { line: 34, character: 17 }, + r#" one::subone + type SomeAlias = i32"#, + ) + .await; + } + + #[test] + async fn hover_on_trait_on_call() { + assert_hover( + "workspace", + "two/src/lib.nr", + Position { line: 39, character: 17 }, + r#" std::default + trait Default"#, + ) + .await; + } + + #[test] + async fn hover_on_std_module_in_use() { + assert_hover( + "workspace", + "two/src/lib.nr", + Position { line: 36, character: 9 }, + r#" std + mod default"#, + ) + .await; + } + + #[test] + async fn hover_on_crate_module_in_call() { + assert_hover( + "workspace", + "two/src/lib.nr", + Position { line: 15, character: 17 }, + r#" one + mod subone"#, + ) + .await; + } + + #[test] + async fn hover_on_module_without_crate_or_std_prefix() { + assert_hover( + "workspace", + "two/src/lib.nr", + Position { line: 43, character: 4 }, + r#" two + mod other"#, + ) + .await; + } + + #[test] + async fn hover_on_module_with_crate_prefix() { + assert_hover( + "workspace", + "two/src/lib.nr", + Position { line: 44, character: 11 }, + r#" two + mod other"#, + ) + .await; + } + + #[test] + async fn hover_on_module_on_struct_constructor() { + assert_hover( + "workspace", + "two/src/lib.nr", + Position { line: 19, character: 12 }, + r#" one + mod subone"#, + ) + .await; + } + + #[test] + async fn hover_on_type_inside_generic_arguments() { + assert_hover( + "workspace", + "two/src/lib.nr", + Position { line: 51, character: 30 }, + r#" one::subone + struct SubOneStruct { + some_field: i32, + some_other_field: Field, + }"#, + ) + .await; + } +} diff --git a/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs b/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs new file mode 100644 index 00000000000..c47e59b0c2b --- /dev/null +++ b/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs @@ -0,0 +1,647 @@ +use fm::codespan_files::Files; +use std::future::{self, Future}; + +use async_lsp::ResponseError; +use fm::{FileId, FileMap, PathString}; +use lsp_types::{ + InlayHint, InlayHintKind, InlayHintLabel, InlayHintLabelPart, InlayHintParams, Position, + TextDocumentPositionParams, +}; +use noirc_errors::{Location, Span}; +use noirc_frontend::{ + self, + ast::{ + BlockExpression, Expression, ExpressionKind, Ident, LetStatement, NoirFunction, Pattern, + Statement, StatementKind, TraitImplItem, TraitItem, UnresolvedTypeData, + }, + macros_api::NodeInterner, + node_interner::ReferenceId, + parser::{Item, ItemKind}, + ParsedModule, Type, TypeBinding, TypeVariable, TypeVariableKind, +}; + +use crate::LspState; + +use super::{process_request, to_lsp_location}; + +pub(crate) fn on_inlay_hint_request( + state: &mut LspState, + params: InlayHintParams, +) -> impl Future>, ResponseError>> { + let text_document_position_params = TextDocumentPositionParams { + text_document: params.text_document.clone(), + position: Position { line: 0, character: 0 }, + }; + + let result = process_request(state, text_document_position_params, |args| { + let path = PathString::from_path(params.text_document.uri.to_file_path().unwrap()); + args.files.get_file_id(&path).map(|file_id| { + let file = args.files.get_file(file_id).unwrap(); + let source = file.source(); + let (parsed_moduled, _errors) = noirc_frontend::parse_program(source); + + let span = range_to_byte_span(args.files, file_id, ¶ms.range) + .map(|range| Span::from(range.start as u32..range.end as u32)); + + let mut collector = InlayHintCollector::new(args.files, file_id, args.interner, span); + collector.collect_in_parsed_module(&parsed_moduled); + collector.inlay_hints + }) + }); + future::ready(result) +} + +pub(crate) struct InlayHintCollector<'a> { + files: &'a FileMap, + file_id: FileId, + interner: &'a NodeInterner, + span: Option, + inlay_hints: Vec, +} + +impl<'a> InlayHintCollector<'a> { + fn new( + files: &'a FileMap, + file_id: FileId, + interner: &'a NodeInterner, + span: Option, + ) -> InlayHintCollector<'a> { + InlayHintCollector { files, file_id, interner, span, inlay_hints: Vec::new() } + } + fn collect_in_parsed_module(&mut self, parsed_module: &ParsedModule) { + for item in &parsed_module.items { + self.collect_in_item(item); + } + } + + fn collect_in_item(&mut self, item: &Item) { + if !self.intersects_span(item.span) { + return; + } + + match &item.kind { + ItemKind::Function(noir_function) => self.collect_in_noir_function(noir_function), + ItemKind::Trait(noir_trait) => { + for item in &noir_trait.items { + self.collect_in_trait_item(item); + } + } + ItemKind::TraitImpl(noir_trait_impl) => { + for item in &noir_trait_impl.items { + self.collect_in_trait_impl_item(item); + } + } + ItemKind::Impl(type_impl) => { + for (noir_function, _) in &type_impl.methods { + self.collect_in_noir_function(noir_function); + } + } + ItemKind::Global(let_statement) => self.collect_in_let_statement(let_statement), + ItemKind::Submodules(parsed_submodule) => { + self.collect_in_parsed_module(&parsed_submodule.contents); + } + ItemKind::ModuleDecl(_) => (), + ItemKind::Import(_) => (), + ItemKind::Struct(_) => (), + ItemKind::TypeAlias(_) => (), + } + } + + fn collect_in_trait_item(&mut self, item: &TraitItem) { + match item { + TraitItem::Function { body, .. } => { + if let Some(body) = body { + self.collect_in_block_expression(body); + } + } + TraitItem::Constant { name: _, typ: _, default_value } => { + if let Some(default_value) = default_value { + self.collect_in_expression(default_value); + } + } + TraitItem::Type { .. } => (), + } + } + + fn collect_in_trait_impl_item(&mut self, item: &TraitImplItem) { + match item { + TraitImplItem::Function(noir_function) => self.collect_in_noir_function(noir_function), + TraitImplItem::Constant(_name, _typ, default_value) => { + self.collect_in_expression(default_value); + } + TraitImplItem::Type { .. } => (), + } + } + + fn collect_in_noir_function(&mut self, noir_function: &NoirFunction) { + self.collect_in_block_expression(&noir_function.def.body); + } + + fn collect_in_let_statement(&mut self, let_statement: &LetStatement) { + // Only show inlay hints for let variables that don't have an explicit type annotation + if let UnresolvedTypeData::Unspecified = let_statement.r#type.typ { + self.collect_in_pattern(&let_statement.pattern); + }; + + self.collect_in_expression(&let_statement.expression); + } + + fn collect_in_block_expression(&mut self, block_expression: &BlockExpression) { + for statement in &block_expression.statements { + self.collect_in_statement(statement); + } + } + + fn collect_in_statement(&mut self, statement: &Statement) { + if !self.intersects_span(statement.span) { + return; + } + + match &statement.kind { + StatementKind::Let(let_statement) => self.collect_in_let_statement(let_statement), + StatementKind::Constrain(constrain_statement) => { + self.collect_in_expression(&constrain_statement.0); + } + StatementKind::Expression(expression) => self.collect_in_expression(expression), + StatementKind::Assign(assign_statement) => { + self.collect_in_expression(&assign_statement.expression); + } + StatementKind::For(for_loop_statement) => { + self.collect_in_ident(&for_loop_statement.identifier); + self.collect_in_expression(&for_loop_statement.block); + } + StatementKind::Comptime(statement) => self.collect_in_statement(statement), + StatementKind::Semi(expression) => self.collect_in_expression(expression), + StatementKind::Break => (), + StatementKind::Continue => (), + StatementKind::Error => (), + } + } + + fn collect_in_expression(&mut self, expression: &Expression) { + if !self.intersects_span(expression.span) { + return; + } + + match &expression.kind { + ExpressionKind::Block(block_expression) => { + self.collect_in_block_expression(block_expression); + } + ExpressionKind::Prefix(prefix_expression) => { + self.collect_in_expression(&prefix_expression.rhs); + } + ExpressionKind::Index(index_expression) => { + self.collect_in_expression(&index_expression.collection); + self.collect_in_expression(&index_expression.index); + } + ExpressionKind::Call(call_expression) => { + self.collect_in_expression(&call_expression.func); + for arg in &call_expression.arguments { + self.collect_in_expression(arg); + } + } + ExpressionKind::MethodCall(method_call_expression) => { + self.collect_in_expression(&method_call_expression.object); + for arg in &method_call_expression.arguments { + self.collect_in_expression(arg); + } + } + ExpressionKind::Constructor(constructor_expression) => { + for (_name, expr) in &constructor_expression.fields { + self.collect_in_expression(expr); + } + } + ExpressionKind::MemberAccess(member_access_expression) => { + self.collect_in_expression(&member_access_expression.lhs); + } + ExpressionKind::Cast(cast_expression) => { + self.collect_in_expression(&cast_expression.lhs); + } + ExpressionKind::Infix(infix_expression) => { + self.collect_in_expression(&infix_expression.lhs); + self.collect_in_expression(&infix_expression.rhs); + } + ExpressionKind::If(if_expression) => { + self.collect_in_expression(&if_expression.condition); + self.collect_in_expression(&if_expression.consequence); + if let Some(alternative) = &if_expression.alternative { + self.collect_in_expression(alternative); + } + } + ExpressionKind::Tuple(expressions) => { + for expression in expressions { + self.collect_in_expression(expression); + } + } + ExpressionKind::Lambda(lambda) => self.collect_in_expression(&lambda.body), + ExpressionKind::Parenthesized(parenthesized) => { + self.collect_in_expression(parenthesized); + } + ExpressionKind::Unquote(expression) => { + self.collect_in_expression(expression); + } + ExpressionKind::Comptime(block_expression, _span) => { + self.collect_in_block_expression(block_expression); + } + ExpressionKind::Literal(..) + | ExpressionKind::Variable(..) + | ExpressionKind::Quote(..) + | ExpressionKind::Resolved(..) + | ExpressionKind::Error => (), + } + } + + fn collect_in_pattern(&mut self, pattern: &Pattern) { + match pattern { + Pattern::Identifier(ident) => { + self.collect_in_ident(ident); + } + Pattern::Mutable(pattern, _span, _is_synthesized) => { + self.collect_in_pattern(pattern); + } + Pattern::Tuple(patterns, _span) => { + for pattern in patterns { + self.collect_in_pattern(pattern); + } + } + Pattern::Struct(_path, patterns, _span) => { + for (_ident, pattern) in patterns { + self.collect_in_pattern(pattern); + } + } + } + } + + fn collect_in_ident(&mut self, ident: &Ident) { + let span = ident.span(); + let location = Location::new(ident.span(), self.file_id); + if let Some(lsp_location) = to_lsp_location(self.files, self.file_id, span) { + if let Some(referenced) = self.interner.find_referenced(location) { + match referenced { + ReferenceId::Global(global_id) => { + let global_info = self.interner.get_global(global_id); + let definition_id = global_info.definition_id; + let typ = self.interner.definition_type(definition_id); + self.push_type_hint(lsp_location, &typ); + } + ReferenceId::Local(definition_id) => { + let typ = self.interner.definition_type(definition_id); + self.push_type_hint(lsp_location, &typ); + } + ReferenceId::StructMember(struct_id, field_index) => { + let struct_type = self.interner.get_struct(struct_id); + let struct_type = struct_type.borrow(); + let (_field_name, field_type) = struct_type.field_at(field_index); + self.push_type_hint(lsp_location, field_type); + } + ReferenceId::Module(_) + | ReferenceId::Struct(_) + | ReferenceId::Trait(_) + | ReferenceId::Function(_) + | ReferenceId::Alias(_) + | ReferenceId::Reference(..) => (), + } + } + } + } + + fn push_type_hint(&mut self, location: lsp_types::Location, typ: &Type) { + let position = location.range.end; + + let mut parts = Vec::new(); + parts.push(string_part(": ")); + push_type_parts(typ, &mut parts, self.files); + + self.inlay_hints.push(InlayHint { + position, + label: InlayHintLabel::LabelParts(parts), + kind: Some(InlayHintKind::TYPE), + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }); + } + + fn intersects_span(&self, other_span: Span) -> bool { + self.span.map_or(true, |span| span.intersects(&other_span)) + } +} + +fn string_part(str: impl Into) -> InlayHintLabelPart { + InlayHintLabelPart { value: str.into(), location: None, tooltip: None, command: None } +} + +fn text_part_with_location(str: String, location: Location, files: &FileMap) -> InlayHintLabelPart { + InlayHintLabelPart { + value: str, + location: to_lsp_location(files, location.file, location.span), + tooltip: None, + command: None, + } +} + +fn push_type_parts(typ: &Type, parts: &mut Vec, files: &FileMap) { + match typ { + Type::Array(size, typ) => { + parts.push(string_part("[")); + push_type_parts(typ, parts, files); + parts.push(string_part("; ")); + push_type_parts(size, parts, files); + parts.push(string_part("]")); + } + Type::Slice(typ) => { + parts.push(string_part("[")); + push_type_parts(typ, parts, files); + parts.push(string_part("]")); + } + Type::Tuple(types) => { + parts.push(string_part("(")); + for (index, typ) in types.iter().enumerate() { + push_type_parts(typ, parts, files); + if index != types.len() - 1 { + parts.push(string_part(", ")); + } + } + parts.push(string_part(")")); + } + Type::Struct(struct_type, generics) => { + let struct_type = struct_type.borrow(); + let location = Location::new(struct_type.name.span(), struct_type.location.file); + parts.push(text_part_with_location(struct_type.name.to_string(), location, files)); + if !generics.is_empty() { + parts.push(string_part("<")); + for (index, generic) in generics.iter().enumerate() { + push_type_parts(generic, parts, files); + if index != generics.len() - 1 { + parts.push(string_part(", ")); + } + } + parts.push(string_part(">")); + } + } + Type::Alias(type_alias, generics) => { + let type_alias = type_alias.borrow(); + let location = Location::new(type_alias.name.span(), type_alias.location.file); + parts.push(text_part_with_location(type_alias.name.to_string(), location, files)); + if !generics.is_empty() { + parts.push(string_part("<")); + for (index, generic) in generics.iter().enumerate() { + push_type_parts(generic, parts, files); + if index != generics.len() - 1 { + parts.push(string_part(", ")); + } + } + parts.push(string_part(">")); + } + } + Type::Function(args, return_type, _env) => { + parts.push(string_part("fn(")); + for (index, arg) in args.iter().enumerate() { + push_type_parts(arg, parts, files); + if index != args.len() - 1 { + parts.push(string_part(", ")); + } + } + parts.push(string_part(") -> ")); + push_type_parts(return_type, parts, files); + } + Type::MutableReference(typ) => { + parts.push(string_part("&mut ")); + push_type_parts(typ, parts, files); + } + Type::TypeVariable(var, TypeVariableKind::Normal) => { + push_type_variable_parts(var, parts, files); + } + Type::TypeVariable(binding, TypeVariableKind::Integer) => { + if let TypeBinding::Unbound(_) = &*binding.borrow() { + push_type_parts(&Type::default_int_type(), parts, files); + } else { + push_type_variable_parts(binding, parts, files); + } + } + Type::TypeVariable(binding, TypeVariableKind::IntegerOrField) => { + if let TypeBinding::Unbound(_) = &*binding.borrow() { + parts.push(string_part("Field")); + } else { + push_type_variable_parts(binding, parts, files); + } + } + Type::TypeVariable(binding, TypeVariableKind::Constant(n)) => { + if let TypeBinding::Unbound(_) = &*binding.borrow() { + // TypeVariableKind::Constant(n) binds to Type::Constant(n) by default, so just show that. + parts.push(string_part(n.to_string())); + } else { + push_type_variable_parts(binding, parts, files); + } + } + + Type::FieldElement + | Type::Integer(..) + | Type::Bool + | Type::String(..) + | Type::FmtString(..) + | Type::Unit + | Type::TraitAsType(..) + | Type::NamedGeneric(..) + | Type::Forall(..) + | Type::Constant(..) + | Type::Quoted(..) + | Type::Error => { + parts.push(string_part(typ.to_string())); + } + } +} + +fn push_type_variable_parts( + var: &TypeVariable, + parts: &mut Vec, + files: &FileMap, +) { + let var = &*var.borrow(); + match var { + TypeBinding::Bound(typ) => { + push_type_parts(typ, parts, files); + } + TypeBinding::Unbound(..) => { + parts.push(string_part(var.to_string())); + } + } +} + +#[cfg(test)] +mod inlay_hints_tests { + use crate::test_utils; + + use super::*; + use lsp_types::{Range, TextDocumentIdentifier, WorkDoneProgressParams}; + use tokio::test; + + async fn get_inlay_hints(start_line: u32, end_line: u32) -> Vec { + let (mut state, noir_text_document) = test_utils::init_lsp_server("inlay_hints").await; + + on_inlay_hint_request( + &mut state, + InlayHintParams { + work_done_progress_params: WorkDoneProgressParams { work_done_token: None }, + text_document: TextDocumentIdentifier { uri: noir_text_document }, + range: lsp_types::Range { + start: lsp_types::Position { line: start_line, character: 0 }, + end: lsp_types::Position { line: end_line, character: 0 }, + }, + }, + ) + .await + .expect("Could not execute on_inlay_hint_request") + .unwrap() + } + + #[test] + async fn test_type_inlay_hints_without_location() { + let inlay_hints = get_inlay_hints(0, 3).await; + assert_eq!(inlay_hints.len(), 1); + + let inlay_hint = &inlay_hints[0]; + assert_eq!(inlay_hint.position, Position { line: 1, character: 11 }); + + if let InlayHintLabel::LabelParts(labels) = &inlay_hint.label { + assert_eq!(labels.len(), 2); + assert_eq!(labels[0].value, ": "); + assert_eq!(labels[0].location, None); + assert_eq!(labels[1].value, "Field"); + + // Field can't be reached (there's no source code for it) + assert_eq!(labels[1].location, None); + } else { + panic!("Expected InlayHintLabel::LabelParts, got {:?}", inlay_hint.label); + } + } + + #[test] + async fn test_type_inlay_hints_with_location() { + let inlay_hints = get_inlay_hints(12, 15).await; + assert_eq!(inlay_hints.len(), 1); + + let inlay_hint = &inlay_hints[0]; + assert_eq!(inlay_hint.position, Position { line: 13, character: 11 }); + + if let InlayHintLabel::LabelParts(labels) = &inlay_hint.label { + assert_eq!(labels.len(), 2); + assert_eq!(labels[0].value, ": "); + assert_eq!(labels[0].location, None); + assert_eq!(labels[1].value, "Foo"); + + // Check that it points to "Foo" in `struct Foo` + let location = labels[1].location.clone().expect("Expected a location"); + assert_eq!( + location.range, + Range { + start: Position { line: 4, character: 7 }, + end: Position { line: 4, character: 10 } + } + ); + } else { + panic!("Expected InlayHintLabel::LabelParts, got {:?}", inlay_hint.label); + } + } + + #[test] + async fn test_type_inlay_hints_in_for() { + let inlay_hints = get_inlay_hints(16, 18).await; + assert_eq!(inlay_hints.len(), 1); + + let inlay_hint = &inlay_hints[0]; + assert_eq!(inlay_hint.position, Position { line: 17, character: 9 }); + + if let InlayHintLabel::LabelParts(labels) = &inlay_hint.label { + assert_eq!(labels.len(), 2); + assert_eq!(labels[0].value, ": "); + assert_eq!(labels[0].location, None); + assert_eq!(labels[1].value, "u32"); + } else { + panic!("Expected InlayHintLabel::LabelParts, got {:?}", inlay_hint.label); + } + } + + #[test] + async fn test_type_inlay_hints_in_global() { + let inlay_hints = get_inlay_hints(19, 21).await; + assert_eq!(inlay_hints.len(), 1); + + let inlay_hint = &inlay_hints[0]; + assert_eq!(inlay_hint.position, Position { line: 20, character: 10 }); + + if let InlayHintLabel::LabelParts(labels) = &inlay_hint.label { + assert_eq!(labels.len(), 2); + assert_eq!(labels[0].value, ": "); + assert_eq!(labels[0].location, None); + assert_eq!(labels[1].value, "Field"); + } else { + panic!("Expected InlayHintLabel::LabelParts, got {:?}", inlay_hint.label); + } + } + + #[test] + async fn test_do_not_panic_when_given_line_is_too_big() { + let inlay_hints = get_inlay_hints(0, 100000).await; + assert!(!inlay_hints.is_empty()); + } +} + +// These functions are copied from the codespan_lsp crate, except that they never panic +// (the library will sometimes panic, so functions returning Result are not always accurate) + +fn range_to_byte_span( + files: &FileMap, + file_id: FileId, + range: &lsp_types::Range, +) -> Option> { + Some( + position_to_byte_index(files, file_id, &range.start)? + ..position_to_byte_index(files, file_id, &range.end)?, + ) +} + +fn position_to_byte_index( + files: &FileMap, + file_id: FileId, + position: &lsp_types::Position, +) -> Option { + let Ok(source) = files.source(file_id) else { + return None; + }; + + let Ok(line_span) = files.line_range(file_id, position.line as usize) else { + return None; + }; + let line_str = source.get(line_span.clone())?; + + let byte_offset = character_to_line_offset(line_str, position.character)?; + + Some(line_span.start + byte_offset) +} + +fn character_to_line_offset(line: &str, character: u32) -> Option { + let line_len = line.len(); + let mut character_offset = 0; + + let mut chars = line.chars(); + while let Some(ch) = chars.next() { + if character_offset == character { + let chars_off = chars.as_str().len(); + let ch_off = ch.len_utf8(); + + return Some(line_len - chars_off - ch_off); + } + + character_offset += ch.len_utf16() as u32; + } + + // Handle positions after the last character on the line + if character_offset == character { + Some(line_len) + } else { + None + } +} diff --git a/noir/noir-repo/tooling/lsp/src/requests/mod.rs b/noir/noir-repo/tooling/lsp/src/requests/mod.rs index 266b5b124ac..5e2250ff248 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/mod.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/mod.rs @@ -1,4 +1,4 @@ -use std::future::Future; +use std::{collections::HashMap, future::Future}; use crate::{ parse_diff, resolve_workspace_for_source_path, @@ -14,7 +14,7 @@ use lsp_types::{ use nargo::insert_all_files_for_workspace_into_file_manager; use nargo_fmt::Config; use noirc_driver::file_manager_with_stdlib; -use noirc_frontend::macros_api::NodeInterner; +use noirc_frontend::{graph::Dependency, macros_api::NodeInterner}; use serde::{Deserialize, Serialize}; use crate::{ @@ -33,8 +33,11 @@ use crate::{ // and params passed in. mod code_lens_request; +mod document_symbol; mod goto_declaration; mod goto_definition; +mod hover; +mod inlay_hint; mod profile_run; mod references; mod rename; @@ -43,10 +46,12 @@ mod tests; pub(crate) use { code_lens_request::collect_lenses_for_package, code_lens_request::on_code_lens_request, - goto_declaration::on_goto_declaration_request, goto_definition::on_goto_definition_request, - goto_definition::on_goto_type_definition_request, profile_run::on_profile_run_request, - references::on_references_request, rename::on_prepare_rename_request, - rename::on_rename_request, test_run::on_test_run_request, tests::on_tests_request, + document_symbol::on_document_symbol_request, goto_declaration::on_goto_declaration_request, + goto_definition::on_goto_definition_request, goto_definition::on_goto_type_definition_request, + hover::on_hover_request, inlay_hint::on_inlay_hint_request, + profile_run::on_profile_run_request, references::on_references_request, + rename::on_prepare_rename_request, rename::on_rename_request, test_run::on_test_run_request, + tests::on_tests_request, }; /// LSP client will send initialization request after the server has started. @@ -127,6 +132,25 @@ pub(crate) fn on_initialize( work_done_progress: None, }, })), + hover_provider: Some(lsp_types::OneOf::Right(lsp_types::HoverOptions { + work_done_progress_options: WorkDoneProgressOptions { + work_done_progress: None, + }, + })), + inlay_hint_provider: Some(lsp_types::OneOf::Right(lsp_types::InlayHintOptions { + work_done_progress_options: WorkDoneProgressOptions { + work_done_progress: None, + }, + resolve_provider: None, + })), + document_symbol_provider: Some(lsp_types::OneOf::Right( + lsp_types::DocumentSymbolOptions { + work_done_progress_options: WorkDoneProgressOptions { + work_done_progress: None, + }, + label: Some("Noir".to_string()), + }, + )), }, server_info: None, }) @@ -264,21 +288,33 @@ pub(crate) fn on_shutdown( async { Ok(()) } } +pub(crate) struct ProcessRequestCallbackArgs<'a> { + location: noirc_errors::Location, + files: &'a FileMap, + interner: &'a NodeInterner, + interners: &'a HashMap, + root_crate_name: String, + root_crate_dependencies: &'a Vec, +} + pub(crate) fn process_request( state: &mut LspState, text_document_position_params: TextDocumentPositionParams, callback: F, ) -> Result where - F: FnOnce(noirc_errors::Location, &NodeInterner, &FileMap) -> T, + F: FnOnce(ProcessRequestCallbackArgs) -> T, { let file_path = text_document_position_params.text_document.uri.to_file_path().map_err(|_| { ResponseError::new(ErrorCode::REQUEST_FAILED, "URI is not a valid file path") })?; - let workspace = resolve_workspace_for_source_path(file_path.as_path()).unwrap(); - let package = workspace.members.first().unwrap(); + let workspace = + resolve_workspace_for_source_path(file_path.as_path(), &state.root_path).unwrap(); + let package = crate::workspace_package_for_file(&workspace, &file_path).ok_or_else(|| { + ResponseError::new(ErrorCode::REQUEST_FAILED, "Could not find package for file") + })?; let package_root_path: String = package.root_dir.as_os_str().to_string_lossy().into(); @@ -294,7 +330,7 @@ where interner = def_interner; } else { // We ignore the warnings and errors produced by compilation while resolving the definition - let _ = noirc_driver::check_crate(&mut context, crate_id, false, false, false); + let _ = noirc_driver::check_crate(&mut context, crate_id, false, false, None); interner = &context.def_interner; } @@ -306,7 +342,88 @@ where &text_document_position_params.position, )?; - Ok(callback(location, interner, files)) + Ok(callback(ProcessRequestCallbackArgs { + location, + files, + interner, + interners: &state.cached_definitions, + root_crate_name: package.name.to_string(), + root_crate_dependencies: &context.crate_graph[context.root_crate_id()].dependencies, + })) +} +pub(crate) fn find_all_references_in_workspace( + location: noirc_errors::Location, + interner: &NodeInterner, + cached_interners: &HashMap, + files: &FileMap, + include_declaration: bool, + include_self_type_name: bool, +) -> Option> { + // First find the node that's referenced by the given location, if any + let referenced = interner.find_referenced(location); + + if let Some(referenced) = referenced { + // If we found the referenced node, find its location + let referenced_location = interner.reference_location(referenced); + + // Now we find all references that point to this location, in all interners + // (there's one interner per package, and all interners in a workspace rely on the + // same FileManager so a Location/FileId in one package is the same as in another package) + let mut locations = find_all_references( + referenced_location, + interner, + files, + include_declaration, + include_self_type_name, + ); + for interner in cached_interners.values() { + locations.extend(find_all_references( + referenced_location, + interner, + files, + include_declaration, + include_self_type_name, + )); + } + + // The LSP client usually removes duplicate loctions, but we do it here just in case they don't + locations.sort_by_key(|location| { + ( + location.uri.to_string(), + location.range.start.line, + location.range.start.character, + location.range.end.line, + location.range.end.character, + ) + }); + locations.dedup(); + + if locations.is_empty() { + None + } else { + Some(locations) + } + } else { + None + } +} + +pub(crate) fn find_all_references( + referenced_location: noirc_errors::Location, + interner: &NodeInterner, + files: &FileMap, + include_declaration: bool, + include_self_type_name: bool, +) -> Vec { + interner + .find_all_references(referenced_location, include_declaration, include_self_type_name) + .map(|locations| { + locations + .iter() + .filter_map(|location| to_lsp_location(files, location.file, location.span)) + .collect() + }) + .unwrap_or_default() } #[cfg(test)] diff --git a/noir/noir-repo/tooling/lsp/src/requests/references.rs b/noir/noir-repo/tooling/lsp/src/requests/references.rs index f8c23632936..badea8921b2 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/references.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/references.rs @@ -5,32 +5,34 @@ use lsp_types::{Location, ReferenceParams}; use crate::LspState; -use super::{process_request, to_lsp_location}; +use super::{find_all_references_in_workspace, process_request}; pub(crate) fn on_references_request( state: &mut LspState, params: ReferenceParams, ) -> impl Future>, ResponseError>> { - let result = - process_request(state, params.text_document_position, |location, interner, files| { - interner.find_all_references(location, params.context.include_declaration, true).map( - |locations| { - locations - .iter() - .filter_map(|location| to_lsp_location(files, location.file, location.span)) - .collect() - }, - ) - }); + let include_declaration = params.context.include_declaration; + let result = process_request(state, params.text_document_position, |args| { + find_all_references_in_workspace( + args.location, + args.interner, + args.interners, + args.files, + include_declaration, + true, + ) + }); future::ready(result) } #[cfg(test)] mod references_tests { use super::*; + use crate::notifications; use crate::test_utils::{self, search_in_file}; use lsp_types::{ - PartialResultParams, ReferenceContext, TextDocumentPositionParams, WorkDoneProgressParams, + PartialResultParams, Position, Range, ReferenceContext, TextDocumentPositionParams, Url, + WorkDoneProgressParams, }; use tokio::test; @@ -91,4 +93,70 @@ mod references_tests { async fn test_on_references_request_without_including_declaration() { check_references_succeeds("rename_function", "another_function", 0, false).await; } + + #[test] + async fn test_on_references_request_works_accross_workspace_packages() { + let (mut state, noir_text_document) = test_utils::init_lsp_server("workspace").await; + + // noir_text_document is always `src/main.nr` in the workspace directory, so let's go to the workspace dir + let noir_text_document = noir_text_document.to_file_path().unwrap(); + let workspace_dir = noir_text_document.parent().unwrap().parent().unwrap(); + + // Let's check that we can find references to `function_one` by doing that in the package "one" + // and getting results in the package "two" too. + let one_lib = Url::from_file_path(workspace_dir.join("one/src/lib.nr")).unwrap(); + let two_lib = Url::from_file_path(workspace_dir.join("two/src/lib.nr")).unwrap(); + + // We call this to open the document, so that the entire workspace is analyzed + notifications::process_workspace_for_noir_document(one_lib.clone(), &mut state).unwrap(); + + let params = ReferenceParams { + text_document_position: TextDocumentPositionParams { + text_document: lsp_types::TextDocumentIdentifier { uri: one_lib.clone() }, + position: Position { line: 0, character: 7 }, + }, + work_done_progress_params: WorkDoneProgressParams { work_done_token: None }, + partial_result_params: PartialResultParams { partial_result_token: None }, + context: ReferenceContext { include_declaration: true }, + }; + + let mut locations = on_references_request(&mut state, params) + .await + .expect("Could not execute on_references_request") + .unwrap(); + + // The definition, a use in "two", and a call in "two" + assert_eq!(locations.len(), 3); + + locations.sort_by_cached_key(|location| { + (location.uri.to_file_path().unwrap(), location.range.start.line) + }); + + assert_eq!(locations[0].uri, one_lib); + assert_eq!( + locations[0].range, + Range { + start: Position { line: 0, character: 7 }, + end: Position { line: 0, character: 19 }, + } + ); + + assert_eq!(locations[1].uri, two_lib); + assert_eq!( + locations[1].range, + Range { + start: Position { line: 0, character: 9 }, + end: Position { line: 0, character: 21 }, + } + ); + + assert_eq!(locations[2].uri, two_lib); + assert_eq!( + locations[2].range, + Range { + start: Position { line: 3, character: 4 }, + end: Position { line: 3, character: 16 }, + } + ); + } } diff --git a/noir/noir-repo/tooling/lsp/src/requests/rename.rs b/noir/noir-repo/tooling/lsp/src/requests/rename.rs index ac6c6792e15..84956681167 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/rename.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/rename.rs @@ -11,14 +11,14 @@ use noirc_frontend::node_interner::ReferenceId; use crate::LspState; -use super::{process_request, to_lsp_location}; +use super::{find_all_references_in_workspace, process_request}; pub(crate) fn on_prepare_rename_request( state: &mut LspState, params: TextDocumentPositionParams, ) -> impl Future, ResponseError>> { - let result = process_request(state, params, |location, interner, _| { - let reference_id = interner.reference_at_location(location); + let result = process_request(state, params, |args| { + let reference_id = args.interner.reference_at_location(args.location); let rename_possible = match reference_id { // Rename shouldn't be possible when triggered on top of "Self" Some(ReferenceId::Reference(_, true /* is self type name */)) => false, @@ -34,41 +34,36 @@ pub(crate) fn on_rename_request( state: &mut LspState, params: RenameParams, ) -> impl Future, ResponseError>> { - let result = - process_request(state, params.text_document_position, |location, interner, files| { - let rename_changes = - interner.find_all_references(location, true, false).map(|locations| { - let rs = locations.iter().fold( - HashMap::new(), - |mut acc: HashMap>, location| { - let file_id = location.file; - let span = location.span; - - let Some(lsp_location) = to_lsp_location(files, file_id, span) else { - return acc; - }; - - let edit = TextEdit { - range: lsp_location.range, - new_text: params.new_name.clone(), - }; - - acc.entry(lsp_location.uri).or_default().push(edit); - - acc - }, - ); - rs - }); - - let response = WorkspaceEdit { - changes: rename_changes, - document_changes: None, - change_annotations: None, - }; - - Some(response) + let result = process_request(state, params.text_document_position, |args| { + let rename_changes = find_all_references_in_workspace( + args.location, + args.interner, + args.interners, + args.files, + true, + false, + ) + .map(|locations| { + let rs = locations.iter().fold( + HashMap::new(), + |mut acc: HashMap>, location| { + let edit = + TextEdit { range: location.range, new_text: params.new_name.clone() }; + acc.entry(location.uri.clone()).or_default().push(edit); + acc + }, + ); + rs }); + + let response = WorkspaceEdit { + changes: rename_changes, + document_changes: None, + change_annotations: None, + }; + + Some(response) + }); future::ready(result) } @@ -175,6 +170,11 @@ mod rename_tests { check_rename_succeeds("rename_function_use", "some_function").await; } + #[test] + async fn test_rename_method() { + check_rename_succeeds("rename_function", "some_method").await; + } + #[test] async fn test_rename_struct() { check_rename_succeeds("rename_struct", "Foo").await; @@ -199,4 +199,9 @@ mod rename_tests { async fn test_rename_local_variable() { check_rename_succeeds("local_variable", "some_var").await; } + + #[test] + async fn test_rename_struct_member() { + check_rename_succeeds("struct_member", "some_member").await; + } } diff --git a/noir/noir-repo/tooling/lsp/src/requests/test_run.rs b/noir/noir-repo/tooling/lsp/src/requests/test_run.rs index acd4f5800f3..a4d507161c2 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/test_run.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/test_run.rs @@ -59,7 +59,7 @@ fn on_test_run_request_inner( Some(package) => { let (mut context, crate_id) = crate::prepare_package(&workspace_file_manager, &parsed_files, package); - if check_crate(&mut context, crate_id, false, false, false).is_err() { + if check_crate(&mut context, crate_id, false, false, None).is_err() { let result = NargoTestRunResult { id: params.id.clone(), result: "error".to_string(), diff --git a/noir/noir-repo/tooling/lsp/src/requests/tests.rs b/noir/noir-repo/tooling/lsp/src/requests/tests.rs index a2aa3ebc0bf..f0d2388504c 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/tests.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/tests.rs @@ -61,7 +61,7 @@ fn on_tests_request_inner( crate::prepare_package(&workspace_file_manager, &parsed_files, package); // We ignore the warnings and errors produced by compilation for producing tests // because we can still get the test functions even if compilation fails - let _ = check_crate(&mut context, crate_id, false, false, false); + let _ = check_crate(&mut context, crate_id, false, false, None); // We don't add test headings for a package if it contains no `#[test]` functions get_package_tests_in_crate(&context, &crate_id, &package.name) diff --git a/noir/noir-repo/tooling/lsp/src/types.rs b/noir/noir-repo/tooling/lsp/src/types.rs index 57eb2dd3618..fa3234cf3bb 100644 --- a/noir/noir-repo/tooling/lsp/src/types.rs +++ b/noir/noir-repo/tooling/lsp/src/types.rs @@ -1,7 +1,7 @@ use fm::FileId; use lsp_types::{ - DeclarationCapability, DefinitionOptions, OneOf, ReferencesOptions, RenameOptions, - TypeDefinitionProviderCapability, + DeclarationCapability, DefinitionOptions, DocumentSymbolOptions, HoverOptions, + InlayHintOptions, OneOf, ReferencesOptions, RenameOptions, TypeDefinitionProviderCapability, }; use noirc_driver::DebugFile; use noirc_errors::{debug_info::OpCodesCount, Location}; @@ -144,6 +144,18 @@ pub(crate) struct ServerCapabilities { /// The server provides references support. #[serde(skip_serializing_if = "Option::is_none")] pub(crate) references_provider: Option>, + + /// The server provides hover support. + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) hover_provider: Option>, + + /// The server provides inlay hints support. + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) inlay_hint_provider: Option>, + + /// The server provides document symbol support. + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) document_symbol_provider: Option>, } #[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)] diff --git a/noir/noir-repo/tooling/lsp/test_programs/document_symbol/Nargo.toml b/noir/noir-repo/tooling/lsp/test_programs/document_symbol/Nargo.toml new file mode 100644 index 00000000000..367b145f045 --- /dev/null +++ b/noir/noir-repo/tooling/lsp/test_programs/document_symbol/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "document_symbol" +type = "bin" +authors = [""] + +[dependencies] diff --git a/noir/noir-repo/tooling/lsp/test_programs/document_symbol/src/main.nr b/noir/noir-repo/tooling/lsp/test_programs/document_symbol/src/main.nr new file mode 100644 index 00000000000..39b2c7fff12 --- /dev/null +++ b/noir/noir-repo/tooling/lsp/test_programs/document_symbol/src/main.nr @@ -0,0 +1,26 @@ +fn foo(_x: i32) { + let _ = 1; +} + +struct SomeStruct { + field: i32, +} + +impl SomeStruct { + fn new() -> SomeStruct { + SomeStruct { field: 0 } + } +} + +trait SomeTrait { + fn some_method(x: U); +} + +impl SomeTrait for SomeStruct { + fn some_method(_x: i32) { + } +} + +mod submodule { + global SOME_GLOBAL = 1; +} diff --git a/noir/noir-repo/tooling/lsp/test_programs/go_to_definition/src/main.nr b/noir/noir-repo/tooling/lsp/test_programs/go_to_definition/src/main.nr index 76a367259b5..9223fdc0bd3 100644 --- a/noir/noir-repo/tooling/lsp/test_programs/go_to_definition/src/main.nr +++ b/noir/noir-repo/tooling/lsp/test_programs/go_to_definition/src/main.nr @@ -18,3 +18,12 @@ fn main() { bar::baz(); bar::inline::qux(); } + +struct Foo { + +} + +trait Trait { + +} + diff --git a/noir/noir-repo/tooling/lsp/test_programs/inlay_hints/Nargo.toml b/noir/noir-repo/tooling/lsp/test_programs/inlay_hints/Nargo.toml new file mode 100644 index 00000000000..89f8eda14be --- /dev/null +++ b/noir/noir-repo/tooling/lsp/test_programs/inlay_hints/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "inlay_hints" +type = "bin" +authors = [""] + +[dependencies] diff --git a/noir/noir-repo/tooling/lsp/test_programs/inlay_hints/src/main.nr b/noir/noir-repo/tooling/lsp/test_programs/inlay_hints/src/main.nr new file mode 100644 index 00000000000..005444ec0ae --- /dev/null +++ b/noir/noir-repo/tooling/lsp/test_programs/inlay_hints/src/main.nr @@ -0,0 +1,22 @@ +fn main() { + let var = 0; +} + +struct Foo { + +} + +fn make_foo() -> Foo { + Foo {} +} + +fn foo() { + let foo = make_foo(); +} + +fn test_for() { + for i in 0..10 {} +} + +global var = 0; + diff --git a/noir/noir-repo/tooling/lsp/test_programs/rename_function/src/main.nr b/noir/noir-repo/tooling/lsp/test_programs/rename_function/src/main.nr index 7a70084276e..e77b50c0b26 100644 --- a/noir/noir-repo/tooling/lsp/test_programs/rename_function/src/main.nr +++ b/noir/noir-repo/tooling/lsp/test_programs/rename_function/src/main.nr @@ -25,3 +25,16 @@ use foo::some_other_function as bar; fn x() { bar(); } + +struct SomeStruct { + +} + +impl SomeStruct { + fn some_method(self) {} +} + +fn y() { + let some_struct = SomeStruct {}; + some_struct.some_method(); +} diff --git a/noir/noir-repo/tooling/lsp/test_programs/struct_member/Nargo.toml b/noir/noir-repo/tooling/lsp/test_programs/struct_member/Nargo.toml new file mode 100644 index 00000000000..5272b9abb68 --- /dev/null +++ b/noir/noir-repo/tooling/lsp/test_programs/struct_member/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "struct_member" +type = "bin" +authors = [""] + +[dependencies] diff --git a/noir/noir-repo/tooling/lsp/test_programs/struct_member/src/main.nr b/noir/noir-repo/tooling/lsp/test_programs/struct_member/src/main.nr new file mode 100644 index 00000000000..3f1bac9df66 --- /dev/null +++ b/noir/noir-repo/tooling/lsp/test_programs/struct_member/src/main.nr @@ -0,0 +1,12 @@ +struct Foo { + some_member: Field +} + +fn main() { + let mut foo = Foo { some_member: 1 }; + foo.some_member = 2; + let _ = foo.some_member; + + let Foo { some_member } = foo; + let Foo { some_member: some_var } = foo; +} diff --git a/noir/noir-repo/tooling/lsp/test_programs/workspace/Nargo.toml b/noir/noir-repo/tooling/lsp/test_programs/workspace/Nargo.toml new file mode 100644 index 00000000000..d0a0badc295 --- /dev/null +++ b/noir/noir-repo/tooling/lsp/test_programs/workspace/Nargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["one", "two"] diff --git a/noir/noir-repo/tooling/lsp/test_programs/workspace/one/Nargo.toml b/noir/noir-repo/tooling/lsp/test_programs/workspace/one/Nargo.toml new file mode 100644 index 00000000000..39838d73362 --- /dev/null +++ b/noir/noir-repo/tooling/lsp/test_programs/workspace/one/Nargo.toml @@ -0,0 +1,4 @@ +[package] +name = "one" +authors = [] +type = "lib" diff --git a/noir/noir-repo/tooling/lsp/test_programs/workspace/one/src/lib.nr b/noir/noir-repo/tooling/lsp/test_programs/workspace/one/src/lib.nr new file mode 100644 index 00000000000..61f282fa2a7 --- /dev/null +++ b/noir/noir-repo/tooling/lsp/test_programs/workspace/one/src/lib.nr @@ -0,0 +1,25 @@ +pub fn function_one() {} + +mod subone { + struct SubOneStruct { + some_field: i32, + some_other_field: Field, + } + + impl SubOneStruct { + fn foo(self, x: i32, y: i32) -> Field { + 0 + } + } + + trait SomeTrait { + } + + global some_global = 2; + + type SomeAlias = i32; + + struct GenericStruct { + + } +} diff --git a/noir/noir-repo/tooling/lsp/test_programs/workspace/two/Nargo.toml b/noir/noir-repo/tooling/lsp/test_programs/workspace/two/Nargo.toml new file mode 100644 index 00000000000..26d99b65df1 --- /dev/null +++ b/noir/noir-repo/tooling/lsp/test_programs/workspace/two/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "two" +authors = [] +type = "lib" + +[dependencies] +one = { path = "../one" } \ No newline at end of file diff --git a/noir/noir-repo/tooling/lsp/test_programs/workspace/two/src/lib.nr b/noir/noir-repo/tooling/lsp/test_programs/workspace/two/src/lib.nr new file mode 100644 index 00000000000..3f0f0f117b7 --- /dev/null +++ b/noir/noir-repo/tooling/lsp/test_programs/workspace/two/src/lib.nr @@ -0,0 +1,53 @@ +use one::function_one; + +pub fn function_two() { + function_one() +} + +use one::subone; + +fn use_struct() { + let _ = subone::SubOneStruct { some_field: 0, some_other_field: 2 }; +} + +use one::subone::SomeTrait; + +fn use_global() { + let _ = one::subone::some_global; +} + +fn use_struct_method() { + let s = subone::SubOneStruct { some_field: 0, some_other_field: 2 }; + s.foo(0, 1); +} + +fn use_local_var() { + let regular_var = 0; + let _ = regular_var; + let mut mutable_var = 0; + mutable_var = 1; +} + +fn use_parameter(some_param: i32) { + let _ = some_param; +} + +use one::subone::SomeAlias; + +use std::default::Default; + +fn use_impl_method() { + let _: i32 = Default::default(); +} + +mod other; +use other::another_function; +use crate::other::other_function; + +use one::subone::GenericStruct; + +use std::collections::bounded_vec::BoundedVec; + +fn instantiate_generic() { + let x: BoundedVec = BoundedVec::new(); +} diff --git a/noir/noir-repo/tooling/lsp/test_programs/workspace/two/src/other.nr b/noir/noir-repo/tooling/lsp/test_programs/workspace/two/src/other.nr new file mode 100644 index 00000000000..4d2fffcee80 --- /dev/null +++ b/noir/noir-repo/tooling/lsp/test_programs/workspace/two/src/other.nr @@ -0,0 +1,2 @@ +fn other_function() {} +fn another_function() {} diff --git a/noir/noir-repo/tooling/nargo/src/package.rs b/noir/noir-repo/tooling/nargo/src/package.rs index 44f0a3504f7..f55ca5550a3 100644 --- a/noir/noir-repo/tooling/nargo/src/package.rs +++ b/noir/noir-repo/tooling/nargo/src/package.rs @@ -1,5 +1,6 @@ use std::{collections::BTreeMap, fmt::Display, path::PathBuf}; +use acvm::acir::circuit::ExpressionWidth; use noirc_frontend::graph::CrateName; use crate::constants::PROVER_INPUT_FILE; @@ -51,6 +52,7 @@ pub struct Package { pub entry_path: PathBuf, pub name: CrateName, pub dependencies: BTreeMap, + pub expression_width: Option, } impl Package { diff --git a/noir/noir-repo/tooling/nargo_cli/build.rs b/noir/noir-repo/tooling/nargo_cli/build.rs index 3789595aa26..74e07efb5c1 100644 --- a/noir/noir-repo/tooling/nargo_cli/build.rs +++ b/noir/noir-repo/tooling/nargo_cli/build.rs @@ -35,6 +35,7 @@ fn main() { generate_noir_test_failure_tests(&mut test_file, &test_dir); generate_compile_success_empty_tests(&mut test_file, &test_dir); generate_compile_success_contract_tests(&mut test_file, &test_dir); + generate_compile_success_no_bug_tests(&mut test_file, &test_dir); generate_compile_failure_tests(&mut test_file, &test_dir); } @@ -58,20 +59,6 @@ const IGNORED_BRILLIG_TESTS: [&str; 11] = [ "is_unconstrained", ]; -/// Certain features are only available in the elaborator. -/// We skip these tests for non-elaborator code since they are not -/// expected to work there. This can be removed once the old code is removed. -const IGNORED_NEW_FEATURE_TESTS: [&str; 8] = [ - "macros", - "wildcard_type", - "type_definition_annotation", - "numeric_generics_explicit", - "derive_impl", - "comptime_traits", - "comptime_slice_methods", - "unquote_multiple_items_from_annotation", -]; - fn read_test_cases( test_data_dir: &Path, test_sub_dir: &str, @@ -132,19 +119,6 @@ fn generate_execution_success_tests(test_file: &mut File, test_data_dir: &Path) nargo.assert().success();"#, ); - if !IGNORED_NEW_FEATURE_TESTS.contains(&test_name.as_str()) { - generate_test_case( - test_file, - test_type, - &format!("legacy_{test_name}"), - &test_dir, - r#" - nargo.arg("execute").arg("--force").arg("--use-legacy"); - - nargo.assert().success();"#, - ); - } - if !IGNORED_BRILLIG_TESTS.contains(&test_name.as_str()) { generate_test_case( test_file, @@ -176,17 +150,6 @@ fn generate_execution_failure_tests(test_file: &mut File, test_data_dir: &Path) nargo.assert().failure().stderr(predicate::str::contains("The application panicked (crashed).").not());"#, ); - - generate_test_case( - test_file, - test_type, - &format!("legacy_{test_name}"), - &test_dir, - r#" - nargo.arg("execute").arg("--force").arg("--use-legacy"); - - nargo.assert().failure().stderr(predicate::str::contains("The application panicked (crashed).").not());"#, - ); } } @@ -206,17 +169,6 @@ fn generate_noir_test_success_tests(test_file: &mut File, test_data_dir: &Path) nargo.assert().success();"#, ); - - generate_test_case( - test_file, - test_type, - &format!("legacy_{test_name}"), - &test_dir, - r#" - nargo.arg("test").arg("--use-legacy"); - - nargo.assert().success();"#, - ); } } @@ -235,17 +187,6 @@ fn generate_noir_test_failure_tests(test_file: &mut File, test_data_dir: &Path) nargo.assert().failure();"#, ); - - generate_test_case( - test_file, - test_type, - &format!("legacy_{test_name}"), - &test_dir, - r#" - nargo.arg("test").arg("--use-legacy"); - - nargo.assert().failure();"#, - ); } } @@ -282,21 +223,6 @@ fn generate_compile_success_empty_tests(test_file: &mut File, test_data_dir: &Pa {assert_zero_opcodes}"#, ), ); - - if !IGNORED_NEW_FEATURE_TESTS.contains(&test_name.as_str()) { - generate_test_case( - test_file, - test_type, - &format!("legacy_{test_name}"), - &test_dir, - &format!( - r#" - nargo.arg("info").arg("--json").arg("--force").arg("--use-legacy"); - - {assert_zero_opcodes}"#, - ), - ); - } } } @@ -313,19 +239,26 @@ fn generate_compile_success_contract_tests(test_file: &mut File, test_data_dir: &test_dir, r#" nargo.arg("compile").arg("--force"); - nargo.assert().success();"#, ); + } +} + +/// Generate tests for checking that the contract compiles and there are no "bugs" in stderr +fn generate_compile_success_no_bug_tests(test_file: &mut File, test_data_dir: &Path) { + let test_type = "compile_success_no_bug"; + let test_cases = read_test_cases(test_data_dir, test_type); + for (test_name, test_dir) in test_cases { + let test_dir = test_dir.display(); generate_test_case( test_file, test_type, - &format!("legacy_{test_name}"), + &test_name, &test_dir, r#" - nargo.arg("compile").arg("--force").arg("--use-legacy"); - - nargo.assert().success();"#, + nargo.arg("compile").arg("--force"); + nargo.assert().success().stderr(predicate::str::contains("bug:").not());"#, ); } } @@ -345,18 +278,5 @@ fn generate_compile_failure_tests(test_file: &mut File, test_data_dir: &Path) { nargo.assert().failure().stderr(predicate::str::contains("The application panicked (crashed).").not());"#, ); - - if !IGNORED_NEW_FEATURE_TESTS.contains(&test_name.as_str()) { - generate_test_case( - test_file, - test_type, - &format!("legacy_{test_name}"), - &test_dir, - r#" - nargo.arg("compile").arg("--force").arg("--use-legacy"); - - nargo.assert().failure().stderr(predicate::str::contains("The application panicked (crashed).").not());"#, - ); - } } } diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/check_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/check_cmd.rs index 2db3b10429a..d40bae1ecfd 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/check_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/check_cmd.rs @@ -87,7 +87,7 @@ fn check_package( compile_options.deny_warnings, compile_options.disable_macros, compile_options.silence_warnings, - compile_options.use_legacy, + compile_options.debug_comptime_in_file.as_deref(), )?; if package.is_library() || package.is_contract() { @@ -160,9 +160,10 @@ pub(crate) fn check_crate_and_report_errors( deny_warnings: bool, disable_macros: bool, silence_warnings: bool, - use_legacy: bool, + debug_comptime_in_file: Option<&str>, ) -> Result<(), CompileError> { - let result = check_crate(context, crate_id, deny_warnings, disable_macros, use_legacy); + let result = + check_crate(context, crate_id, deny_warnings, disable_macros, debug_comptime_in_file); report_errors(result, &context.file_manager, deny_warnings, silence_warnings) } diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/compile_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/compile_cmd.rs index e83b1728c93..a2877ebdeac 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/compile_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/compile_cmd.rs @@ -2,6 +2,7 @@ use std::io::Write; use std::path::Path; use std::time::Duration; +use acvm::acir::circuit::ExpressionWidth; use fm::FileManager; use nargo::ops::{collect_errors, compile_contract, compile_program, report_errors}; use nargo::package::Package; @@ -190,7 +191,11 @@ fn compile_programs( compile_options, load_cached_program(package), )?; - let program = nargo::ops::transform_program(program, compile_options.expression_width); + + let target_width = + get_target_width(package.expression_width, compile_options.expression_width); + let program = nargo::ops::transform_program(program, target_width); + save_program_to_file( &program.clone().into(), &package.name, @@ -216,8 +221,9 @@ fn compiled_contracts( .map(|package| { let (contract, warnings) = compile_contract(file_manager, parsed_files, package, compile_options)?; - let contract = - nargo::ops::transform_contract(contract, compile_options.expression_width); + let target_width = + get_target_width(package.expression_width, compile_options.expression_width); + let contract = nargo::ops::transform_contract(contract, target_width); save_contract(contract, package, target_dir, compile_options.show_artifact_paths); Ok(((), warnings)) }) @@ -243,3 +249,21 @@ fn save_contract( println!("Saved contract artifact to: {}", artifact_path.display()); } } + +/// Default expression width used for Noir compilation. +/// The ACVM native type `ExpressionWidth` has its own default which should always be unbounded, +/// while we can sometimes expect the compilation target width to change. +/// Thus, we set it separately here rather than trying to alter the default derivation of the type. +const DEFAULT_EXPRESSION_WIDTH: ExpressionWidth = ExpressionWidth::Bounded { width: 4 }; + +/// If a target width was not specified in the CLI we can safely override the default. +pub(crate) fn get_target_width( + package_default_width: Option, + compile_options_width: Option, +) -> ExpressionWidth { + if let (Some(manifest_default_width), None) = (package_default_width, compile_options_width) { + manifest_default_width + } else { + compile_options_width.unwrap_or(DEFAULT_EXPRESSION_WIDTH) + } +} diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/debug_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/debug_cmd.rs index 778009bf791..311af9b9db0 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/debug_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/debug_cmd.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use acvm::acir::native_types::{WitnessMap, WitnessStack}; +use acvm::acir::native_types::WitnessStack; use acvm::FieldElement; use bn254_blackbox_solver::Bn254BlackBoxSolver; use clap::Args; @@ -15,7 +15,6 @@ use nargo::{insert_all_files_for_workspace_into_file_manager, parse_all}; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_abi::input_parser::{Format, InputValue}; use noirc_abi::InputMap; -use noirc_artifacts::debug::DebugArtifact; use noirc_driver::{ file_manager_with_stdlib, CompileOptions, CompiledProgram, NOIR_ARTIFACT_VERSION_STRING, }; @@ -23,6 +22,7 @@ use noirc_frontend::debug::DebugInstrumenter; use noirc_frontend::graph::CrateName; use noirc_frontend::hir::ParsedFiles; +use super::compile_cmd::get_target_width; use super::fs::{inputs::read_inputs_from_file, witness::save_witness_to_dir}; use super::NargoConfig; use crate::errors::CliError; @@ -81,8 +81,10 @@ pub(crate) fn run(args: DebugCommand, config: NargoConfig) -> Result<(), CliErro args.compile_options.clone(), )?; - let compiled_program = - nargo::ops::transform_program(compiled_program, args.compile_options.expression_width); + let target_width = + get_target_width(package.expression_width, args.compile_options.expression_width); + + let compiled_program = nargo::ops::transform_program(compiled_program, target_width); run_async(package, compiled_program, &args.prover_name, &args.witness_name, target_dir) } @@ -169,10 +171,10 @@ fn run_async( runtime.block_on(async { println!("[{}] Starting debugger", package.name); - let (return_value, solved_witness) = + let (return_value, witness_stack) = debug_program_and_decode(program, package, prover_name)?; - if let Some(solved_witness) = solved_witness { + if let Some(solved_witness_stack) = witness_stack { println!("[{}] Circuit witness successfully solved", package.name); if let Some(return_value) = return_value { @@ -180,11 +182,8 @@ fn run_async( } if let Some(witness_name) = witness_name { - let witness_path = save_witness_to_dir( - WitnessStack::from(solved_witness), - witness_name, - target_dir, - )?; + let witness_path = + save_witness_to_dir(solved_witness_stack, witness_name, target_dir)?; println!("[{}] Witness saved to {}", package.name, witness_path.display()); } @@ -200,38 +199,32 @@ fn debug_program_and_decode( program: CompiledProgram, package: &Package, prover_name: &str, -) -> Result<(Option, Option>), CliError> { +) -> Result<(Option, Option>), CliError> { // Parse the initial witness values from Prover.toml let (inputs_map, _) = read_inputs_from_file(&package.root_dir, prover_name, Format::Toml, &program.abi)?; - let solved_witness = debug_program(&program, &inputs_map)?; - - match solved_witness { - Some(witness) => { - let (_, return_value) = program.abi.decode(&witness)?; - Ok((return_value, Some(witness))) + let program_abi = program.abi.clone(); + let witness_stack = debug_program(program, &inputs_map)?; + + match witness_stack { + Some(witness_stack) => { + let main_witness = &witness_stack + .peek() + .expect("Should have at least one witness on the stack") + .witness; + let (_, return_value) = program_abi.decode(main_witness)?; + Ok((return_value, Some(witness_stack))) } None => Ok((None, None)), } } pub(crate) fn debug_program( - compiled_program: &CompiledProgram, + compiled_program: CompiledProgram, inputs_map: &InputMap, -) -> Result>, CliError> { +) -> Result>, CliError> { let initial_witness = compiled_program.abi.encode(inputs_map, None)?; - let debug_artifact = DebugArtifact { - debug_symbols: compiled_program.debug.clone(), - file_map: compiled_program.file_map.clone(), - }; - - noir_debugger::debug_circuit( - &Bn254BlackBoxSolver, - &compiled_program.program.functions[0], - debug_artifact, - initial_witness, - &compiled_program.program.unconstrained_functions, - ) - .map_err(CliError::from) + noir_debugger::run_repl_session(&Bn254BlackBoxSolver, compiled_program, initial_witness) + .map_err(CliError::from) } diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/export_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/export_cmd.rs index ee30b29b0f0..1b7ba97d68d 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/export_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/export_cmd.rs @@ -89,7 +89,7 @@ fn compile_exported_functions( compile_options.deny_warnings, compile_options.disable_macros, compile_options.silence_warnings, - compile_options.use_legacy, + compile_options.debug_comptime_in_file.as_deref(), )?; let exported_functions = context.get_all_exported_functions_in_crate(&crate_id); diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/info_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/info_cmd.rs index 3759fb31c76..a6395d1c8c9 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/info_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/info_cmd.rs @@ -16,7 +16,9 @@ use serde::Serialize; use crate::errors::CliError; use super::{ - compile_cmd::compile_workspace_full, fs::program::read_program_from_file, NargoConfig, + compile_cmd::{compile_workspace_full, get_target_width}, + fs::program::read_program_from_file, + NargoConfig, }; /// Provides detailed information on each of a program's function (represented by a single circuit) @@ -84,11 +86,9 @@ pub(crate) fn run(args: InfoCommand, config: NargoConfig) -> Result<(), CliError .into_iter() .par_bridge() .map(|(package, program)| { - count_opcodes_and_gates_in_program( - program, - &package, - args.compile_options.expression_width, - ) + let target_width = + get_target_width(package.expression_width, args.compile_options.expression_width); + count_opcodes_and_gates_in_program(program, &package, target_width) }) .collect(); diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/mod.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/mod.rs index 485ccc7abaf..10ec38ad1d5 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/mod.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/mod.rs @@ -63,7 +63,6 @@ enum NargoCommand { Execute(execute_cmd::ExecuteCommand), #[command(hide = true)] // Hidden while the feature is being built out Export(export_cmd::ExportCommand), - #[command(hide = true)] // Hidden while the feature is being built out Debug(debug_cmd::DebugCommand), Test(test_cmd::TestCommand), Info(info_cmd::InfoCommand), diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs index de9e8dc5d7c..c8848e2e304 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs @@ -176,7 +176,7 @@ fn run_test + Default>( crate_id, compile_options.deny_warnings, compile_options.disable_macros, - compile_options.use_legacy, + compile_options.debug_comptime_in_file.as_deref(), ) .expect("Any errors should have occurred when collecting test functions"); @@ -243,7 +243,7 @@ fn get_tests_in_package( compile_options.deny_warnings, compile_options.disable_macros, compile_options.silence_warnings, - compile_options.use_legacy, + compile_options.debug_comptime_in_file.as_deref(), )?; Ok(context diff --git a/noir/noir-repo/tooling/nargo_cli/tests/stdlib-tests.rs b/noir/noir-repo/tooling/nargo_cli/tests/stdlib-tests.rs index bf6614860e2..c4cc792438e 100644 --- a/noir/noir-repo/tooling/nargo_cli/tests/stdlib-tests.rs +++ b/noir/noir-repo/tooling/nargo_cli/tests/stdlib-tests.rs @@ -27,12 +27,13 @@ fn run_stdlib_tests() { entry_path: PathBuf::from("main.nr"), name: "stdlib".parse().unwrap(), dependencies: BTreeMap::new(), + expression_width: None, }; let (mut context, dummy_crate_id) = prepare_package(&file_manager, &parsed_files, &dummy_package); - let result = check_crate(&mut context, dummy_crate_id, false, false, false); + let result = check_crate(&mut context, dummy_crate_id, false, false, None); report_errors(result, &context.file_manager, true, false) .expect("Error encountered while compiling standard library"); diff --git a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/imports.rs b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/imports.rs index 564ef3fa370..025d354259e 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/imports.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/imports.rs @@ -14,6 +14,7 @@ pub(crate) enum UseSegment { List(Vec), Dep, Crate, + Super, } impl UseSegment { @@ -50,6 +51,7 @@ impl UseSegment { } UseSegment::Dep => "dep".into(), UseSegment::Crate => "crate".into(), + UseSegment::Super => "super".into(), } } } @@ -66,6 +68,7 @@ impl UseTree { match use_tree.prefix.kind { ast::PathKind::Crate => result.path.push(UseSegment::Crate), ast::PathKind::Dep => result.path.push(UseSegment::Dep), + ast::PathKind::Super => result.path.push(UseSegment::Super), ast::PathKind::Plain => {} }; diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/expected/use_super.nr b/noir/noir-repo/tooling/nargo_fmt/tests/expected/use_super.nr new file mode 100644 index 00000000000..91fbe7a9df1 --- /dev/null +++ b/noir/noir-repo/tooling/nargo_fmt/tests/expected/use_super.nr @@ -0,0 +1,5 @@ +fn some_func() {} + +mod foo { + use super::some_func; +} diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/input/use_super.nr b/noir/noir-repo/tooling/nargo_fmt/tests/input/use_super.nr new file mode 100644 index 00000000000..a3b7d4cb4e2 --- /dev/null +++ b/noir/noir-repo/tooling/nargo_fmt/tests/input/use_super.nr @@ -0,0 +1,7 @@ +fn some_func() { + +} + +mod foo { + use super::some_func; +} diff --git a/noir/noir-repo/tooling/nargo_toml/Cargo.toml b/noir/noir-repo/tooling/nargo_toml/Cargo.toml index 574972d99e7..7c9faa4562a 100644 --- a/noir/noir-repo/tooling/nargo_toml/Cargo.toml +++ b/noir/noir-repo/tooling/nargo_toml/Cargo.toml @@ -18,6 +18,7 @@ serde.workspace = true thiserror.workspace = true toml.workspace = true url.workspace = true +noirc_driver.workspace = true semver = "1.0.20" [dev-dependencies] diff --git a/noir/noir-repo/tooling/nargo_toml/src/errors.rs b/noir/noir-repo/tooling/nargo_toml/src/errors.rs index 77fe77bcdbb..1ee8e90c8e5 100644 --- a/noir/noir-repo/tooling/nargo_toml/src/errors.rs +++ b/noir/noir-repo/tooling/nargo_toml/src/errors.rs @@ -72,6 +72,9 @@ pub enum ManifestError { #[error("Cyclic package dependency found when processing {cycle}")] CyclicDependency { cycle: String }, + + #[error("Failed to parse expression width with the following error: {0}")] + ParseExpressionWidth(String), } #[allow(clippy::enum_variant_names)] diff --git a/noir/noir-repo/tooling/nargo_toml/src/lib.rs b/noir/noir-repo/tooling/nargo_toml/src/lib.rs index 985cb30dc24..c0d8c7997fd 100644 --- a/noir/noir-repo/tooling/nargo_toml/src/lib.rs +++ b/noir/noir-repo/tooling/nargo_toml/src/lib.rs @@ -14,6 +14,7 @@ use nargo::{ package::{Dependency, Package, PackageType}, workspace::Workspace, }; +use noirc_driver::parse_expression_width; use noirc_frontend::graph::CrateName; use serde::Deserialize; @@ -199,6 +200,16 @@ impl PackageConfig { })?; } + let expression_width = self + .package + .expression_width + .as_ref() + .map(|expression_width| { + parse_expression_width(expression_width) + .map_err(|err| ManifestError::ParseExpressionWidth(err.to_string())) + }) + .map_or(Ok(None), |res| res.map(Some))?; + Ok(Package { version: self.package.version.clone(), compiler_required_version: self.package.compiler_version.clone(), @@ -207,6 +218,7 @@ impl PackageConfig { package_type, name, dependencies, + expression_width, }) } } @@ -275,6 +287,7 @@ struct PackageMetadata { // so you will not need to supply an ACIR and compiler version compiler_version: Option, license: Option, + expression_width: Option, } #[derive(Debug, Deserialize, Clone)] @@ -531,3 +544,18 @@ fn parse_workspace_default_member_toml() { assert!(Config::try_from(String::from(src)).is_ok()); assert!(Config::try_from(src).is_ok()); } + +#[test] +fn parse_package_expression_width_toml() { + let src = r#" + [package] + name = "test" + version = "0.1.0" + type = "bin" + authors = [""] + expression_width = "3" + "#; + + assert!(Config::try_from(String::from(src)).is_ok()); + assert!(Config::try_from(src).is_ok()); +} diff --git a/noir/noir-repo/tooling/nargo_toml/src/semver.rs b/noir/noir-repo/tooling/nargo_toml/src/semver.rs index 7c6e2a18b31..253ac82aa34 100644 --- a/noir/noir-repo/tooling/nargo_toml/src/semver.rs +++ b/noir/noir-repo/tooling/nargo_toml/src/semver.rs @@ -89,6 +89,7 @@ mod tests { name: CrateName::from_str("test").unwrap(), dependencies: BTreeMap::new(), version: Some("1.0".to_string()), + expression_width: None, }; if let Err(err) = semver_check_package(&package, &compiler_version) { panic!("semver check should have passed. compiler version is 0.1.0 and required version from the package is 0.1.0\n error: {err:?}") @@ -120,6 +121,7 @@ mod tests { name: CrateName::from_str("test").unwrap(), dependencies: BTreeMap::new(), version: Some("1.0".to_string()), + expression_width: None, }; let valid_dependency = Package { @@ -130,6 +132,7 @@ mod tests { name: CrateName::from_str("good_dependency").unwrap(), dependencies: BTreeMap::new(), version: Some("1.0".to_string()), + expression_width: None, }; let invalid_dependency = Package { compiler_required_version: Some("0.2.0".to_string()), @@ -139,6 +142,7 @@ mod tests { name: CrateName::from_str("bad_dependency").unwrap(), dependencies: BTreeMap::new(), version: Some("1.0".to_string()), + expression_width: None, }; package.dependencies.insert( @@ -179,6 +183,7 @@ mod tests { name: CrateName::from_str("test").unwrap(), dependencies: BTreeMap::new(), version: Some("1.0".to_string()), + expression_width: None, }; if let Err(err) = semver_check_package(&package, &compiler_version) { @@ -198,6 +203,7 @@ mod tests { name: CrateName::from_str("test").unwrap(), dependencies: BTreeMap::new(), version: Some("1.0".to_string()), + expression_width: None, }; if let Err(err) = semver_check_package(&package, &compiler_version) { diff --git a/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json b/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json index 3ce97f89ec2..12793d70545 100644 --- a/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json +++ b/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json @@ -41,7 +41,7 @@ "lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0" }, "dependencies": { - "@aztec/bb.js": "portal:../../../../barretenberg/ts", + "@aztec/bb.js": "0.46.1", "@noir-lang/types": "workspace:*", "fflate": "^0.8.0" }, diff --git a/noir/noir-repo/tooling/noirc_abi_wasm/build.sh b/noir/noir-repo/tooling/noirc_abi_wasm/build.sh index c07d2d8a4c1..16fb26e55db 100755 --- a/noir/noir-repo/tooling/noirc_abi_wasm/build.sh +++ b/noir/noir-repo/tooling/noirc_abi_wasm/build.sh @@ -25,7 +25,7 @@ function run_if_available { require_command jq require_command cargo require_command wasm-bindgen -#require_command wasm-opt +require_command wasm-opt self_path=$(dirname "$(readlink -f "$0")") pname=$(cargo read-manifest | jq -r '.name') diff --git a/noir/noir-repo/yarn.lock b/noir/noir-repo/yarn.lock index 181b6b3b206..5a442d77b30 100644 --- a/noir/noir-repo/yarn.lock +++ b/noir/noir-repo/yarn.lock @@ -221,18 +221,19 @@ __metadata: languageName: node linkType: hard -"@aztec/bb.js@portal:../../../../barretenberg/ts::locator=%40noir-lang%2Fbackend_barretenberg%40workspace%3Atooling%2Fnoir_js_backend_barretenberg": - version: 0.0.0-use.local - resolution: "@aztec/bb.js@portal:../../../../barretenberg/ts::locator=%40noir-lang%2Fbackend_barretenberg%40workspace%3Atooling%2Fnoir_js_backend_barretenberg" +"@aztec/bb.js@npm:0.46.1": + version: 0.46.1 + resolution: "@aztec/bb.js@npm:0.46.1" dependencies: comlink: ^4.4.1 commander: ^10.0.1 debug: ^4.3.4 tslib: ^2.4.0 bin: - bb.js: ./dest/node/main.js + bb.js: dest/node/main.js + checksum: 9475388f994e430ab3282a2c9769cd116f334358049955ed520f467d1abec8237bfeae7fa2fed9cd292f24c09c466e32e2af2d0a5cec2d10cc0c727728d96b0d languageName: node - linkType: soft + linkType: hard "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.11, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.22.13, @babel/code-frame@npm:^7.23.5, @babel/code-frame@npm:^7.8.3": version: 7.23.5 @@ -1746,52 +1747,6 @@ __metadata: languageName: node linkType: hard -"@chainsafe/as-sha256@npm:^0.3.1": - version: 0.3.1 - resolution: "@chainsafe/as-sha256@npm:0.3.1" - checksum: 58ea733be1657b0e31dbf48b0dba862da0833df34a81c1460c7352f04ce90874f70003cbf34d0afb9e5e53a33ee2d63a261a8b12462be85b2ba0a6f7f13d6150 - languageName: node - linkType: hard - -"@chainsafe/persistent-merkle-tree@npm:^0.4.2": - version: 0.4.2 - resolution: "@chainsafe/persistent-merkle-tree@npm:0.4.2" - dependencies: - "@chainsafe/as-sha256": ^0.3.1 - checksum: f9cfcb2132a243992709715dbd28186ab48c7c0c696f29d30857693cca5526bf753974a505ef68ffd5623bbdbcaa10f9083f4dd40bf99eb6408e451cc26a1a9e - languageName: node - linkType: hard - -"@chainsafe/persistent-merkle-tree@npm:^0.5.0": - version: 0.5.0 - resolution: "@chainsafe/persistent-merkle-tree@npm:0.5.0" - dependencies: - "@chainsafe/as-sha256": ^0.3.1 - checksum: 2c67203da776c79cd3a6132e2d672fe132393b2e63dc71604e3134acc8c0ec25cc5e431051545939ea0f7c5ff2066fb806b9e5cab974ca085d046226a1671f7d - languageName: node - linkType: hard - -"@chainsafe/ssz@npm:^0.10.0": - version: 0.10.2 - resolution: "@chainsafe/ssz@npm:0.10.2" - dependencies: - "@chainsafe/as-sha256": ^0.3.1 - "@chainsafe/persistent-merkle-tree": ^0.5.0 - checksum: 6bb70cf741d0a19dd0b28b3f6f067b96fa39f556e2eefa6ac745b21db9c3b3a8393dc3cca8ff4a6ce065ed71ddc3fb1b2b390a92004b9d01067c26e2558e5503 - languageName: node - linkType: hard - -"@chainsafe/ssz@npm:^0.9.2": - version: 0.9.4 - resolution: "@chainsafe/ssz@npm:0.9.4" - dependencies: - "@chainsafe/as-sha256": ^0.3.1 - "@chainsafe/persistent-merkle-tree": ^0.4.2 - case: ^1.6.3 - checksum: c6eaedeae9e5618b3c666ff4507a27647f665a8dcf17d5ca86da4ed4788c5a93868f256d0005467d184fdf35ec03f323517ec2e55ec42492d769540a2ec396bc - languageName: node - linkType: hard - "@colors/colors@npm:1.5.0": version: 1.5.0 resolution: "@colors/colors@npm:1.5.0" @@ -3500,7 +3455,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/abi@npm:5.7.0, @ethersproject/abi@npm:^5.1.2, @ethersproject/abi@npm:^5.7.0": +"@ethersproject/abi@npm:^5.1.2": version: 5.7.0 resolution: "@ethersproject/abi@npm:5.7.0" dependencies: @@ -3517,7 +3472,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/abstract-provider@npm:5.7.0, @ethersproject/abstract-provider@npm:^5.7.0": +"@ethersproject/abstract-provider@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/abstract-provider@npm:5.7.0" dependencies: @@ -3532,7 +3487,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/abstract-signer@npm:5.7.0, @ethersproject/abstract-signer@npm:^5.7.0": +"@ethersproject/abstract-signer@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/abstract-signer@npm:5.7.0" dependencies: @@ -3545,7 +3500,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/address@npm:5.7.0, @ethersproject/address@npm:^5.7.0": +"@ethersproject/address@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/address@npm:5.7.0" dependencies: @@ -3558,7 +3513,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/base64@npm:5.7.0, @ethersproject/base64@npm:^5.7.0": +"@ethersproject/base64@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/base64@npm:5.7.0" dependencies: @@ -3567,17 +3522,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/basex@npm:5.7.0, @ethersproject/basex@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/basex@npm:5.7.0" - dependencies: - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/properties": ^5.7.0 - checksum: 326087b7e1f3787b5fe6cd1cf2b4b5abfafbc355a45e88e22e5e9d6c845b613ffc5301d629b28d5c4d5e2bfe9ec424e6782c804956dff79be05f0098cb5817de - languageName: node - linkType: hard - -"@ethersproject/bignumber@npm:5.7.0, @ethersproject/bignumber@npm:^5.7.0": +"@ethersproject/bignumber@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/bignumber@npm:5.7.0" dependencies: @@ -3588,7 +3533,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/bytes@npm:5.7.0, @ethersproject/bytes@npm:^5.7.0": +"@ethersproject/bytes@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/bytes@npm:5.7.0" dependencies: @@ -3597,7 +3542,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/constants@npm:5.7.0, @ethersproject/constants@npm:^5.7.0": +"@ethersproject/constants@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/constants@npm:5.7.0" dependencies: @@ -3606,25 +3551,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/contracts@npm:5.7.0": - version: 5.7.0 - resolution: "@ethersproject/contracts@npm:5.7.0" - dependencies: - "@ethersproject/abi": ^5.7.0 - "@ethersproject/abstract-provider": ^5.7.0 - "@ethersproject/abstract-signer": ^5.7.0 - "@ethersproject/address": ^5.7.0 - "@ethersproject/bignumber": ^5.7.0 - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/constants": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - "@ethersproject/properties": ^5.7.0 - "@ethersproject/transactions": ^5.7.0 - checksum: 6ccf1121cba01b31e02f8c507cb971ab6bfed85706484a9ec09878ef1594a62215f43c4fdef8f4a4875b99c4a800bc95e3be69b1803f8ce479e07634b5a740c0 - languageName: node - linkType: hard - -"@ethersproject/hash@npm:5.7.0, @ethersproject/hash@npm:^5.7.0": +"@ethersproject/hash@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/hash@npm:5.7.0" dependencies: @@ -3641,48 +3568,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/hdnode@npm:5.7.0, @ethersproject/hdnode@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/hdnode@npm:5.7.0" - dependencies: - "@ethersproject/abstract-signer": ^5.7.0 - "@ethersproject/basex": ^5.7.0 - "@ethersproject/bignumber": ^5.7.0 - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - "@ethersproject/pbkdf2": ^5.7.0 - "@ethersproject/properties": ^5.7.0 - "@ethersproject/sha2": ^5.7.0 - "@ethersproject/signing-key": ^5.7.0 - "@ethersproject/strings": ^5.7.0 - "@ethersproject/transactions": ^5.7.0 - "@ethersproject/wordlists": ^5.7.0 - checksum: bfe5ca2d89a42de73655f853170ef4766b933c5f481cddad709b3aca18823275b096e572f92d1602a052f80b426edde44ad6b9d028799775a7dad4a5bbed2133 - languageName: node - linkType: hard - -"@ethersproject/json-wallets@npm:5.7.0, @ethersproject/json-wallets@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/json-wallets@npm:5.7.0" - dependencies: - "@ethersproject/abstract-signer": ^5.7.0 - "@ethersproject/address": ^5.7.0 - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/hdnode": ^5.7.0 - "@ethersproject/keccak256": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - "@ethersproject/pbkdf2": ^5.7.0 - "@ethersproject/properties": ^5.7.0 - "@ethersproject/random": ^5.7.0 - "@ethersproject/strings": ^5.7.0 - "@ethersproject/transactions": ^5.7.0 - aes-js: 3.0.0 - scrypt-js: 3.0.1 - checksum: f583458d22db62efaaf94d38dd243482776a45bf90f9f3882fbad5aa0b8fd288b41eb7c1ff8ec0b99c9b751088e43d6173530db64dd33c59f9d8daa8d7ad5aa2 - languageName: node - linkType: hard - -"@ethersproject/keccak256@npm:5.7.0, @ethersproject/keccak256@npm:^5.7.0": +"@ethersproject/keccak256@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/keccak256@npm:5.7.0" dependencies: @@ -3692,14 +3578,14 @@ __metadata: languageName: node linkType: hard -"@ethersproject/logger@npm:5.7.0, @ethersproject/logger@npm:^5.7.0": +"@ethersproject/logger@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/logger@npm:5.7.0" checksum: 075ab2f605f1fd0813f2e39c3308f77b44a67732b36e712d9bc085f22a84aac4da4f71b39bee50fe78da3e1c812673fadc41180c9970fe5e486e91ea17befe0d languageName: node linkType: hard -"@ethersproject/networks@npm:5.7.1, @ethersproject/networks@npm:^5.7.0": +"@ethersproject/networks@npm:^5.7.0": version: 5.7.1 resolution: "@ethersproject/networks@npm:5.7.1" dependencies: @@ -3708,17 +3594,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/pbkdf2@npm:5.7.0, @ethersproject/pbkdf2@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/pbkdf2@npm:5.7.0" - dependencies: - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/sha2": ^5.7.0 - checksum: b895adb9e35a8a127e794f7aadc31a2424ef355a70e51cde10d457e3e888bb8102373199a540cf61f2d6b9a32e47358f9c65b47d559f42bf8e596b5fd67901e9 - languageName: node - linkType: hard - -"@ethersproject/properties@npm:5.7.0, @ethersproject/properties@npm:^5.7.0": +"@ethersproject/properties@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/properties@npm:5.7.0" dependencies: @@ -3727,45 +3603,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/providers@npm:5.7.2, @ethersproject/providers@npm:^5.7.1, @ethersproject/providers@npm:^5.7.2": - version: 5.7.2 - resolution: "@ethersproject/providers@npm:5.7.2" - dependencies: - "@ethersproject/abstract-provider": ^5.7.0 - "@ethersproject/abstract-signer": ^5.7.0 - "@ethersproject/address": ^5.7.0 - "@ethersproject/base64": ^5.7.0 - "@ethersproject/basex": ^5.7.0 - "@ethersproject/bignumber": ^5.7.0 - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/constants": ^5.7.0 - "@ethersproject/hash": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - "@ethersproject/networks": ^5.7.0 - "@ethersproject/properties": ^5.7.0 - "@ethersproject/random": ^5.7.0 - "@ethersproject/rlp": ^5.7.0 - "@ethersproject/sha2": ^5.7.0 - "@ethersproject/strings": ^5.7.0 - "@ethersproject/transactions": ^5.7.0 - "@ethersproject/web": ^5.7.0 - bech32: 1.1.4 - ws: 7.4.6 - checksum: 1754c731a5ca6782ae9677f4a9cd8b6246c4ef21a966c9a01b133750f3c578431ec43ec254e699969c4a0f87e84463ded50f96b415600aabd37d2056aee58c19 - languageName: node - linkType: hard - -"@ethersproject/random@npm:5.7.0, @ethersproject/random@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/random@npm:5.7.0" - dependencies: - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - checksum: 017829c91cff6c76470852855108115b0b52c611b6be817ed1948d56ba42d6677803ec2012aa5ae298a7660024156a64c11fcf544e235e239ab3f89f0fff7345 - languageName: node - linkType: hard - -"@ethersproject/rlp@npm:5.7.0, @ethersproject/rlp@npm:^5.7.0": +"@ethersproject/rlp@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/rlp@npm:5.7.0" dependencies: @@ -3775,18 +3613,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/sha2@npm:5.7.0, @ethersproject/sha2@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/sha2@npm:5.7.0" - dependencies: - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - hash.js: 1.1.7 - checksum: 09321057c022effbff4cc2d9b9558228690b5dd916329d75c4b1ffe32ba3d24b480a367a7cc92d0f0c0b1c896814d03351ae4630e2f1f7160be2bcfbde435dbc - languageName: node - linkType: hard - -"@ethersproject/signing-key@npm:5.7.0, @ethersproject/signing-key@npm:^5.7.0": +"@ethersproject/signing-key@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/signing-key@npm:5.7.0" dependencies: @@ -3800,21 +3627,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/solidity@npm:5.7.0": - version: 5.7.0 - resolution: "@ethersproject/solidity@npm:5.7.0" - dependencies: - "@ethersproject/bignumber": ^5.7.0 - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/keccak256": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - "@ethersproject/sha2": ^5.7.0 - "@ethersproject/strings": ^5.7.0 - checksum: 9a02f37f801c96068c3e7721f83719d060175bc4e80439fe060e92bd7acfcb6ac1330c7e71c49f4c2535ca1308f2acdcb01e00133129aac00581724c2d6293f3 - languageName: node - linkType: hard - -"@ethersproject/strings@npm:5.7.0, @ethersproject/strings@npm:^5.7.0": +"@ethersproject/strings@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/strings@npm:5.7.0" dependencies: @@ -3825,7 +3638,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/transactions@npm:5.7.0, @ethersproject/transactions@npm:^5.7.0": +"@ethersproject/transactions@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/transactions@npm:5.7.0" dependencies: @@ -3842,41 +3655,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/units@npm:5.7.0": - version: 5.7.0 - resolution: "@ethersproject/units@npm:5.7.0" - dependencies: - "@ethersproject/bignumber": ^5.7.0 - "@ethersproject/constants": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - checksum: 304714f848cd32e57df31bf545f7ad35c2a72adae957198b28cbc62166daa929322a07bff6e9c9ac4577ab6aa0de0546b065ed1b2d20b19e25748b7d475cb0fc - languageName: node - linkType: hard - -"@ethersproject/wallet@npm:5.7.0": - version: 5.7.0 - resolution: "@ethersproject/wallet@npm:5.7.0" - dependencies: - "@ethersproject/abstract-provider": ^5.7.0 - "@ethersproject/abstract-signer": ^5.7.0 - "@ethersproject/address": ^5.7.0 - "@ethersproject/bignumber": ^5.7.0 - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/hash": ^5.7.0 - "@ethersproject/hdnode": ^5.7.0 - "@ethersproject/json-wallets": ^5.7.0 - "@ethersproject/keccak256": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - "@ethersproject/properties": ^5.7.0 - "@ethersproject/random": ^5.7.0 - "@ethersproject/signing-key": ^5.7.0 - "@ethersproject/transactions": ^5.7.0 - "@ethersproject/wordlists": ^5.7.0 - checksum: a4009bf7331eddab38e3015b5e9101ef92de7f705b00a6196b997db0e5635b6d83561674d46c90c6f77b87c0500fe4a6b0183ba13749efc22db59c99deb82fbd - languageName: node - linkType: hard - -"@ethersproject/web@npm:5.7.1, @ethersproject/web@npm:^5.7.0": +"@ethersproject/web@npm:^5.7.0": version: 5.7.1 resolution: "@ethersproject/web@npm:5.7.1" dependencies: @@ -3889,19 +3668,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/wordlists@npm:5.7.0, @ethersproject/wordlists@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/wordlists@npm:5.7.0" - dependencies: - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/hash": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - "@ethersproject/properties": ^5.7.0 - "@ethersproject/strings": ^5.7.0 - checksum: 30eb6eb0731f9ef5faa44bf9c0c6e950bcaaef61e4d2d9ce0ae6d341f4e2d6d1f4ab4f8880bfce03b7aac4b862fb740e1421170cfbf8e2aafc359277d49e6e97 - languageName: node - linkType: hard - "@fastify/busboy@npm:^2.0.0": version: 2.1.0 resolution: "@fastify/busboy@npm:2.1.0" @@ -4395,7 +4161,7 @@ __metadata: version: 0.0.0-use.local resolution: "@noir-lang/backend_barretenberg@workspace:tooling/noir_js_backend_barretenberg" dependencies: - "@aztec/bb.js": "portal:../../../../barretenberg/ts" + "@aztec/bb.js": 0.46.1 "@noir-lang/types": "workspace:*" "@types/node": ^20.6.2 "@types/prettier": ^3 @@ -4547,161 +4313,117 @@ __metadata: languageName: unknown linkType: soft -"@nomicfoundation/ethereumjs-block@npm:5.0.2": - version: 5.0.2 - resolution: "@nomicfoundation/ethereumjs-block@npm:5.0.2" - dependencies: - "@nomicfoundation/ethereumjs-common": 4.0.2 - "@nomicfoundation/ethereumjs-rlp": 5.0.2 - "@nomicfoundation/ethereumjs-trie": 6.0.2 - "@nomicfoundation/ethereumjs-tx": 5.0.2 - "@nomicfoundation/ethereumjs-util": 9.0.2 - ethereum-cryptography: 0.1.3 - ethers: ^5.7.1 - checksum: 7ff744f44a01f1c059ca7812a1cfc8089f87aa506af6cb39c78331dca71b32993cbd6fa05ad03f8c4f4fab73bb998a927af69e0d8ff01ae192ee5931606e09f5 +"@nomicfoundation/edr-darwin-arm64@npm:0.4.2": + version: 0.4.2 + resolution: "@nomicfoundation/edr-darwin-arm64@npm:0.4.2" + checksum: 7835e998c2ef83924efac0694bb4392f6abf770dc7f935dd28abc1a291f830cade14750d83a46a3205338e4ddff943dda60a9849317cf42edd38d7a2ce843588 languageName: node linkType: hard -"@nomicfoundation/ethereumjs-blockchain@npm:7.0.2": - version: 7.0.2 - resolution: "@nomicfoundation/ethereumjs-blockchain@npm:7.0.2" - dependencies: - "@nomicfoundation/ethereumjs-block": 5.0.2 - "@nomicfoundation/ethereumjs-common": 4.0.2 - "@nomicfoundation/ethereumjs-ethash": 3.0.2 - "@nomicfoundation/ethereumjs-rlp": 5.0.2 - "@nomicfoundation/ethereumjs-trie": 6.0.2 - "@nomicfoundation/ethereumjs-tx": 5.0.2 - "@nomicfoundation/ethereumjs-util": 9.0.2 - abstract-level: ^1.0.3 - debug: ^4.3.3 - ethereum-cryptography: 0.1.3 - level: ^8.0.0 - lru-cache: ^5.1.1 - memory-level: ^1.0.0 - checksum: b7e440dcd73e32aa72d13bfd28cb472773c9c60ea808a884131bf7eb3f42286ad594a0864215f599332d800f3fe1f772fff4b138d2dcaa8f41e4d8389bff33e7 +"@nomicfoundation/edr-darwin-x64@npm:0.4.2": + version: 0.4.2 + resolution: "@nomicfoundation/edr-darwin-x64@npm:0.4.2" + checksum: 94daa26610621e85cb025feb37bb93e9b89c59f908bf3eae70720d2b86632dbb1236420ae3ae6f685d563ba52519d5f860e68ccd898fa1fced831961dea2c08a languageName: node linkType: hard -"@nomicfoundation/ethereumjs-common@npm:4.0.2": - version: 4.0.2 - resolution: "@nomicfoundation/ethereumjs-common@npm:4.0.2" - dependencies: - "@nomicfoundation/ethereumjs-util": 9.0.2 - crc-32: ^1.2.0 - checksum: f0d84704d6254d374299c19884312bd5666974b4b6f342d3f10bc76e549de78d20e45a53d25fbdc146268a52335497127e4f069126da7c60ac933a158e704887 +"@nomicfoundation/edr-linux-arm64-gnu@npm:0.4.2": + version: 0.4.2 + resolution: "@nomicfoundation/edr-linux-arm64-gnu@npm:0.4.2" + checksum: a7181e237f6ece8bd97e0f75972044dbf584c506bbac5bef586d9f7d627a2c07a279a2d892837bbedc80ea3dfb39fa66becc297238b5d715a942eed2a50745cd languageName: node linkType: hard -"@nomicfoundation/ethereumjs-ethash@npm:3.0.2": - version: 3.0.2 - resolution: "@nomicfoundation/ethereumjs-ethash@npm:3.0.2" - dependencies: - "@nomicfoundation/ethereumjs-block": 5.0.2 - "@nomicfoundation/ethereumjs-rlp": 5.0.2 - "@nomicfoundation/ethereumjs-util": 9.0.2 - abstract-level: ^1.0.3 - bigint-crypto-utils: ^3.0.23 - ethereum-cryptography: 0.1.3 - checksum: e4011e4019dd9b92f7eeebfc1e6c9a9685c52d8fd0ee4f28f03e50048a23b600c714490827f59fdce497b3afb503b3fd2ebf6815ff307e9949c3efeff1403278 +"@nomicfoundation/edr-linux-arm64-musl@npm:0.4.2": + version: 0.4.2 + resolution: "@nomicfoundation/edr-linux-arm64-musl@npm:0.4.2" + checksum: 5a849484b7a104a7e1497774c4117afc58f64d57d30889d4f6f676dddb5c695192c0789b8be0b71171a2af770167a28aa301ae3ece7a2a156d82d94388639b66 languageName: node linkType: hard -"@nomicfoundation/ethereumjs-evm@npm:2.0.2": - version: 2.0.2 - resolution: "@nomicfoundation/ethereumjs-evm@npm:2.0.2" - dependencies: - "@ethersproject/providers": ^5.7.1 - "@nomicfoundation/ethereumjs-common": 4.0.2 - "@nomicfoundation/ethereumjs-tx": 5.0.2 - "@nomicfoundation/ethereumjs-util": 9.0.2 - debug: ^4.3.3 - ethereum-cryptography: 0.1.3 - mcl-wasm: ^0.7.1 - rustbn.js: ~0.2.0 - checksum: a23cf570836ddc147606b02df568069de946108e640f902358fef67e589f6b371d856056ee44299d9b4e3497f8ae25faa45e6b18fefd90e9b222dc6a761d85f0 +"@nomicfoundation/edr-linux-x64-gnu@npm:0.4.2": + version: 0.4.2 + resolution: "@nomicfoundation/edr-linux-x64-gnu@npm:0.4.2" + checksum: 0520dd9a583976fd0f49dfe6c23227f03cd811a395dc5eed1a2922b4358d7c71fdcfea8f389d4a0e23b4ec53e1435959a544380f94e48122a75f94a42b177ac7 languageName: node linkType: hard -"@nomicfoundation/ethereumjs-rlp@npm:5.0.2": - version: 5.0.2 - resolution: "@nomicfoundation/ethereumjs-rlp@npm:5.0.2" - bin: - rlp: bin/rlp - checksum: a74434cadefca9aa8754607cc1ad7bb4bbea4ee61c6214918e60a5bbee83206850346eb64e39fd1fe97f854c7ec0163e01148c0c881dda23881938f0645a0ef2 +"@nomicfoundation/edr-linux-x64-musl@npm:0.4.2": + version: 0.4.2 + resolution: "@nomicfoundation/edr-linux-x64-musl@npm:0.4.2" + checksum: 80c3b4346d8c27539bc005b09db233dedd8930310d1a049827661e69a8e03be9cbac27eb620a6ae9bfd46a2fbe22f83cee5af8d9e63178925d74d9c656246708 languageName: node linkType: hard -"@nomicfoundation/ethereumjs-statemanager@npm:2.0.2": - version: 2.0.2 - resolution: "@nomicfoundation/ethereumjs-statemanager@npm:2.0.2" - dependencies: - "@nomicfoundation/ethereumjs-common": 4.0.2 - "@nomicfoundation/ethereumjs-rlp": 5.0.2 - debug: ^4.3.3 - ethereum-cryptography: 0.1.3 - ethers: ^5.7.1 - js-sdsl: ^4.1.4 - checksum: 3ab6578e252e53609afd98d8ba42a99f182dcf80252f23ed9a5e0471023ffb2502130f85fc47fa7c94cd149f9be799ed9a0942ca52a143405be9267f4ad94e64 +"@nomicfoundation/edr-win32-x64-msvc@npm:0.4.2": + version: 0.4.2 + resolution: "@nomicfoundation/edr-win32-x64-msvc@npm:0.4.2" + checksum: 736fb866fd5c2708560cbd5ae72815b5fc96e650cd74bc8bab0a1cb0e8baede4f595fdceb445c159814a6a7e8e691de227a5db49f61b3cd0ddfafd5715b397ab languageName: node linkType: hard -"@nomicfoundation/ethereumjs-trie@npm:6.0.2": - version: 6.0.2 - resolution: "@nomicfoundation/ethereumjs-trie@npm:6.0.2" +"@nomicfoundation/edr@npm:^0.4.1": + version: 0.4.2 + resolution: "@nomicfoundation/edr@npm:0.4.2" dependencies: - "@nomicfoundation/ethereumjs-rlp": 5.0.2 - "@nomicfoundation/ethereumjs-util": 9.0.2 - "@types/readable-stream": ^2.3.13 - ethereum-cryptography: 0.1.3 - readable-stream: ^3.6.0 - checksum: d4da918d333851b9f2cce7dbd25ab5753e0accd43d562d98fd991b168b6a08d1794528f0ade40fe5617c84900378376fe6256cdbe52c8d66bf4c53293bbc7c40 + "@nomicfoundation/edr-darwin-arm64": 0.4.2 + "@nomicfoundation/edr-darwin-x64": 0.4.2 + "@nomicfoundation/edr-linux-arm64-gnu": 0.4.2 + "@nomicfoundation/edr-linux-arm64-musl": 0.4.2 + "@nomicfoundation/edr-linux-x64-gnu": 0.4.2 + "@nomicfoundation/edr-linux-x64-musl": 0.4.2 + "@nomicfoundation/edr-win32-x64-msvc": 0.4.2 + checksum: 8c8457257b59ed9a29d88b7492e98e974d24e8318903e876a14dc0f6d5dc77948cd9053937d9730f54f920ba82ce3d244cab518d068359bcc20df88623f171ef languageName: node linkType: hard -"@nomicfoundation/ethereumjs-tx@npm:5.0.2": - version: 5.0.2 - resolution: "@nomicfoundation/ethereumjs-tx@npm:5.0.2" +"@nomicfoundation/ethereumjs-common@npm:4.0.4": + version: 4.0.4 + resolution: "@nomicfoundation/ethereumjs-common@npm:4.0.4" dependencies: - "@chainsafe/ssz": ^0.9.2 - "@ethersproject/providers": ^5.7.2 - "@nomicfoundation/ethereumjs-common": 4.0.2 - "@nomicfoundation/ethereumjs-rlp": 5.0.2 - "@nomicfoundation/ethereumjs-util": 9.0.2 - ethereum-cryptography: 0.1.3 - checksum: 0bbcea75786b2ccb559afe2ecc9866fb4566a9f157b6ffba4f50960d14f4b3da2e86e273f6fadda9b860e67cfcabf589970fb951b328cb5f900a585cd21842a2 + "@nomicfoundation/ethereumjs-util": 9.0.4 + checksum: ce3f6e4ae15b976efdb7ccda27e19aadb62b5ffee209f9503e68b4fd8633715d4d697c0cc10ccd35f5e4e977edd05100d0f214e28880ec64fff77341dc34fcdf languageName: node linkType: hard -"@nomicfoundation/ethereumjs-util@npm:9.0.2": - version: 9.0.2 - resolution: "@nomicfoundation/ethereumjs-util@npm:9.0.2" +"@nomicfoundation/ethereumjs-rlp@npm:5.0.4": + version: 5.0.4 + resolution: "@nomicfoundation/ethereumjs-rlp@npm:5.0.4" + bin: + rlp: bin/rlp.cjs + checksum: ee2c2e5776c73801dc5ed636f4988b599b4563c2d0037da542ea57eb237c69dd1ac555f6bcb5e06f70515b6459779ba0d68252a6e105132b4659ab4bf62919b0 + languageName: node + linkType: hard + +"@nomicfoundation/ethereumjs-tx@npm:5.0.4": + version: 5.0.4 + resolution: "@nomicfoundation/ethereumjs-tx@npm:5.0.4" dependencies: - "@chainsafe/ssz": ^0.10.0 - "@nomicfoundation/ethereumjs-rlp": 5.0.2 + "@nomicfoundation/ethereumjs-common": 4.0.4 + "@nomicfoundation/ethereumjs-rlp": 5.0.4 + "@nomicfoundation/ethereumjs-util": 9.0.4 ethereum-cryptography: 0.1.3 - checksum: 3a08f7b88079ef9f53b43da9bdcb8195498fd3d3911c2feee2571f4d1204656053f058b2f650471c86f7d2d0ba2f814768c7cfb0f266eede41c848356afc4900 + peerDependencies: + c-kzg: ^2.1.2 + peerDependenciesMeta: + c-kzg: + optional: true + checksum: 0f1c87716682ccbcf4d92ffc6cf8ab557e658b90319d82be3219a091a736859f8803c73c98e4863682e3e86d264751c472d33ff6d3c3daf4e75b5f01d0af8fa3 languageName: node linkType: hard -"@nomicfoundation/ethereumjs-vm@npm:7.0.2": - version: 7.0.2 - resolution: "@nomicfoundation/ethereumjs-vm@npm:7.0.2" - dependencies: - "@nomicfoundation/ethereumjs-block": 5.0.2 - "@nomicfoundation/ethereumjs-blockchain": 7.0.2 - "@nomicfoundation/ethereumjs-common": 4.0.2 - "@nomicfoundation/ethereumjs-evm": 2.0.2 - "@nomicfoundation/ethereumjs-rlp": 5.0.2 - "@nomicfoundation/ethereumjs-statemanager": 2.0.2 - "@nomicfoundation/ethereumjs-trie": 6.0.2 - "@nomicfoundation/ethereumjs-tx": 5.0.2 - "@nomicfoundation/ethereumjs-util": 9.0.2 - debug: ^4.3.3 +"@nomicfoundation/ethereumjs-util@npm:9.0.4": + version: 9.0.4 + resolution: "@nomicfoundation/ethereumjs-util@npm:9.0.4" + dependencies: + "@nomicfoundation/ethereumjs-rlp": 5.0.4 ethereum-cryptography: 0.1.3 - mcl-wasm: ^0.7.1 - rustbn.js: ~0.2.0 - checksum: 1c25ba4d0644cadb8a2b0241a4bb02e578bfd7f70e3492b855c2ab5c120cb159cb8f7486f84dc1597884bd1697feedbfb5feb66e91352afb51f3694fd8e4a043 + peerDependencies: + c-kzg: ^2.1.2 + peerDependenciesMeta: + c-kzg: + optional: true + checksum: 754439f72b11cad2d8986707ad020077dcc763c4055f73e2668a0b4cadb22aa4407faa9b3c587d9eb5b97ac337afbe037eb642bc1d5a16197284f83db3462cbe languageName: node linkType: hard @@ -6126,16 +5848,6 @@ __metadata: languageName: node linkType: hard -"@types/readable-stream@npm:^2.3.13": - version: 2.3.15 - resolution: "@types/readable-stream@npm:2.3.15" - dependencies: - "@types/node": "*" - safe-buffer: ~5.1.1 - checksum: ec36f525cad09b6c65a1dafcb5ad99b9e2ed824ec49b7aa23180ac427e5d35b8a0706193ecd79ab4253a283ad485ba03d5917a98daaaa144f0ea34f4823e9d82 - languageName: node - linkType: hard - "@types/readable-stream@npm:^4": version: 4.0.10 resolution: "@types/readable-stream@npm:4.0.10" @@ -7046,21 +6758,6 @@ __metadata: languageName: node linkType: hard -"abstract-level@npm:^1.0.0, abstract-level@npm:^1.0.2, abstract-level@npm:^1.0.3": - version: 1.0.3 - resolution: "abstract-level@npm:1.0.3" - dependencies: - buffer: ^6.0.3 - catering: ^2.1.0 - is-buffer: ^2.0.5 - level-supports: ^4.0.0 - level-transcoder: ^1.0.1 - module-error: ^1.0.1 - queue-microtask: ^1.2.3 - checksum: 70d61a3924526ebc257b138992052f9ff571a6cee5a7660836e37a1cc7081273c3acf465dd2f5e1897b38dc743a6fd9dba14a5d8a2a9d39e5787cd3da99f301d - languageName: node - linkType: hard - "abstract-leveldown@npm:~0.12.0, abstract-leveldown@npm:~0.12.1": version: 0.12.4 resolution: "abstract-leveldown@npm:0.12.4" @@ -7135,13 +6832,6 @@ __metadata: languageName: node linkType: hard -"aes-js@npm:3.0.0": - version: 3.0.0 - resolution: "aes-js@npm:3.0.0" - checksum: 251e26d533cd1a915b44896b17d5ed68c24a02484cfdd2e74ec700a309267db96651ea4eb657bf20aac32a3baa61f6e34edf8e2fec2de440a655da9942d334b8 - languageName: node - linkType: hard - "aes-js@npm:4.0.0-beta.5": version: 4.0.0-beta.5 resolution: "aes-js@npm:4.0.0-beta.5" @@ -7772,13 +7462,6 @@ __metadata: languageName: node linkType: hard -"bech32@npm:1.1.4": - version: 1.1.4 - resolution: "bech32@npm:1.1.4" - checksum: 0e98db619191548390d6f09ff68b0253ba7ae6a55db93dfdbb070ba234c1fd3308c0606fbcc95fad50437227b10011e2698b89f0181f6e7f845c499bd14d0f4b - languageName: node - linkType: hard - "big.js@npm:^5.2.2": version: 5.2.2 resolution: "big.js@npm:5.2.2" @@ -7786,13 +7469,6 @@ __metadata: languageName: node linkType: hard -"bigint-crypto-utils@npm:^3.0.23": - version: 3.3.0 - resolution: "bigint-crypto-utils@npm:3.3.0" - checksum: 9598ce57b23f776c8936d44114c9f051e62b5fa654915b664784cbcbacc5aa0485f4479571c51ff58008abb1210c0d6a234853742f07cf84bda890f2a1e01000 - languageName: node - linkType: hard - "binary-extensions@npm:^2.0.0": version: 2.2.0 resolution: "binary-extensions@npm:2.2.0" @@ -7895,7 +7571,7 @@ __metadata: languageName: node linkType: hard -"boxen@npm:^5.0.0": +"boxen@npm:^5.0.0, boxen@npm:^5.1.2": version: 5.1.2 resolution: "boxen@npm:5.1.2" dependencies: @@ -7978,18 +7654,6 @@ __metadata: languageName: node linkType: hard -"browser-level@npm:^1.0.1": - version: 1.0.1 - resolution: "browser-level@npm:1.0.1" - dependencies: - abstract-level: ^1.0.2 - catering: ^2.1.1 - module-error: ^1.0.2 - run-parallel-limit: ^1.1.0 - checksum: 67fbc77ce832940bfa25073eccff279f512ad56f545deb996a5b23b02316f5e76f4a79d381acc27eda983f5c9a2566aaf9c97e4fdd0748288c4407307537a29b - languageName: node - linkType: hard - "browser-stdout@npm:1.3.1": version: 1.3.1 resolution: "browser-stdout@npm:1.3.1" @@ -8262,20 +7926,6 @@ __metadata: languageName: node linkType: hard -"case@npm:^1.6.3": - version: 1.6.3 - resolution: "case@npm:1.6.3" - checksum: febe73278f910b0d28aab7efd6f51c235f9aa9e296148edb56dfb83fd58faa88308c30ce9a0122b6e53e0362c44f4407105bd5ef89c46860fc2b184e540fd68d - languageName: node - linkType: hard - -"catering@npm:^2.1.0, catering@npm:^2.1.1": - version: 2.1.1 - resolution: "catering@npm:2.1.1" - checksum: 205daefa69c935b0c19f3d8f2e0a520dd69aebe9bda55902958003f7c9cff8f967dfb90071b421bd6eb618576f657a89d2bc0986872c9bc04bbd66655e9d4bd6 - languageName: node - linkType: hard - "ccount@npm:^1.0.0": version: 1.1.0 resolution: "ccount@npm:1.1.0" @@ -8565,20 +8215,6 @@ __metadata: languageName: node linkType: hard -"classic-level@npm:^1.2.0": - version: 1.3.0 - resolution: "classic-level@npm:1.3.0" - dependencies: - abstract-level: ^1.0.2 - catering: ^2.1.0 - module-error: ^1.0.1 - napi-macros: ^2.2.2 - node-gyp: latest - node-gyp-build: ^4.3.0 - checksum: 773da48aef52a041115d413fee8340b357a4da2eb505764f327183b155edd7cc9d24819eb4f707c83dbdae8588024f5dddeb322125567c59d5d1f6f16334cdb9 - languageName: node - linkType: hard - "clean-css@npm:^5.2.2, clean-css@npm:^5.3.0, clean-css@npm:^5.3.2, clean-css@npm:~5.3.2": version: 5.3.3 resolution: "clean-css@npm:5.3.3" @@ -8881,13 +8517,6 @@ __metadata: languageName: node linkType: hard -"commander@npm:3.0.2": - version: 3.0.2 - resolution: "commander@npm:3.0.2" - checksum: 6d14ad030d1904428139487ed31febcb04c1604db2b8d9fae711f60ee6718828dc0e11602249e91c8a97b0e721e9c6d53edbc166bad3cde1596851d59a8f824d - languageName: node - linkType: hard - "commander@npm:^10.0.0, commander@npm:^10.0.1": version: 10.0.1 resolution: "commander@npm:10.0.1" @@ -8923,7 +8552,7 @@ __metadata: languageName: node linkType: hard -"commander@npm:^8.3.0": +"commander@npm:^8.1.0, commander@npm:^8.3.0": version: 8.3.0 resolution: "commander@npm:8.3.0" checksum: 0f82321821fc27b83bd409510bb9deeebcfa799ff0bf5d102128b500b7af22872c0c92cb6a0ebc5a4cf19c6b550fba9cedfa7329d18c6442a625f851377bacf0 @@ -9231,15 +8860,6 @@ __metadata: languageName: node linkType: hard -"crc-32@npm:^1.2.0": - version: 1.2.2 - resolution: "crc-32@npm:1.2.2" - bin: - crc32: bin/crc32.njs - checksum: ad2d0ad0cbd465b75dcaeeff0600f8195b686816ab5f3ba4c6e052a07f728c3e70df2e3ca9fd3d4484dc4ba70586e161ca5a2334ec8bf5a41bf022a6103ff243 - languageName: node - linkType: hard - "create-hash@npm:^1.1.0, create-hash@npm:^1.1.2, create-hash@npm:^1.2.0": version: 1.2.0 resolution: "create-hash@npm:1.2.0" @@ -9669,7 +9289,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:4.3.4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.2.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": +"debug@npm:4, debug@npm:4.3.4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.2.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4": version: 4.3.4 resolution: "debug@npm:4.3.4" dependencies: @@ -10922,44 +10542,6 @@ __metadata: languageName: node linkType: hard -"ethers@npm:^5.7.1": - version: 5.7.2 - resolution: "ethers@npm:5.7.2" - dependencies: - "@ethersproject/abi": 5.7.0 - "@ethersproject/abstract-provider": 5.7.0 - "@ethersproject/abstract-signer": 5.7.0 - "@ethersproject/address": 5.7.0 - "@ethersproject/base64": 5.7.0 - "@ethersproject/basex": 5.7.0 - "@ethersproject/bignumber": 5.7.0 - "@ethersproject/bytes": 5.7.0 - "@ethersproject/constants": 5.7.0 - "@ethersproject/contracts": 5.7.0 - "@ethersproject/hash": 5.7.0 - "@ethersproject/hdnode": 5.7.0 - "@ethersproject/json-wallets": 5.7.0 - "@ethersproject/keccak256": 5.7.0 - "@ethersproject/logger": 5.7.0 - "@ethersproject/networks": 5.7.1 - "@ethersproject/pbkdf2": 5.7.0 - "@ethersproject/properties": 5.7.0 - "@ethersproject/providers": 5.7.2 - "@ethersproject/random": 5.7.0 - "@ethersproject/rlp": 5.7.0 - "@ethersproject/sha2": 5.7.0 - "@ethersproject/signing-key": 5.7.0 - "@ethersproject/solidity": 5.7.0 - "@ethersproject/strings": 5.7.0 - "@ethersproject/transactions": 5.7.0 - "@ethersproject/units": 5.7.0 - "@ethersproject/wallet": 5.7.0 - "@ethersproject/web": 5.7.1 - "@ethersproject/wordlists": 5.7.0 - checksum: b7c08cf3e257185a7946117dbbf764433b7ba0e77c27298dec6088b3bc871aff711462b0621930c56880ff0a7ceb8b1d3a361ffa259f93377b48e34107f62553 - languageName: node - linkType: hard - "ethers@npm:^6.7.1": version: 6.9.0 resolution: "ethers@npm:6.9.0" @@ -11557,19 +11139,6 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:^0.30.0": - version: 0.30.0 - resolution: "fs-extra@npm:0.30.0" - dependencies: - graceful-fs: ^4.1.2 - jsonfile: ^2.1.0 - klaw: ^1.0.0 - path-is-absolute: ^1.0.0 - rimraf: ^2.2.8 - checksum: 6edfd65fc813baa27f1603778c0f5ec11f8c5006a20b920437813ee2023eba18aeec8bef1c89b2e6c84f9fc90fdc7c916f4a700466c8c69d22a35d018f2570f0 - languageName: node - linkType: hard - "fs-extra@npm:^10.0.0, fs-extra@npm:^10.1.0": version: 10.1.0 resolution: "fs-extra@npm:10.1.0" @@ -11703,13 +11272,6 @@ __metadata: languageName: node linkType: hard -"functional-red-black-tree@npm:^1.0.1": - version: 1.0.1 - resolution: "functional-red-black-tree@npm:1.0.1" - checksum: ca6c170f37640e2d94297da8bb4bf27a1d12bea3e00e6a3e007fd7aa32e37e000f5772acf941b4e4f3cf1c95c3752033d0c509af157ad8f526e7f00723b9eb9f - languageName: node - linkType: hard - "fwd-stream@npm:^1.0.4": version: 1.0.4 resolution: "fwd-stream@npm:1.0.4" @@ -12050,7 +11612,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.1.9, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": +"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: ac85f94da92d8eb6b7f5a8b20ce65e43d66761c55ce85ac96df6865308390da45a8d3f0296dd3a663de65d30ba497bd46c696cc1e248c72b13d6d567138a4fc7 @@ -12092,22 +11654,16 @@ __metadata: languageName: node linkType: hard -"hardhat@npm:^2.17.4": - version: 2.19.2 - resolution: "hardhat@npm:2.19.2" +"hardhat@npm:^2.22.6": + version: 2.22.6 + resolution: "hardhat@npm:2.22.6" dependencies: "@ethersproject/abi": ^5.1.2 "@metamask/eth-sig-util": ^4.0.0 - "@nomicfoundation/ethereumjs-block": 5.0.2 - "@nomicfoundation/ethereumjs-blockchain": 7.0.2 - "@nomicfoundation/ethereumjs-common": 4.0.2 - "@nomicfoundation/ethereumjs-evm": 2.0.2 - "@nomicfoundation/ethereumjs-rlp": 5.0.2 - "@nomicfoundation/ethereumjs-statemanager": 2.0.2 - "@nomicfoundation/ethereumjs-trie": 6.0.2 - "@nomicfoundation/ethereumjs-tx": 5.0.2 - "@nomicfoundation/ethereumjs-util": 9.0.2 - "@nomicfoundation/ethereumjs-vm": 7.0.2 + "@nomicfoundation/edr": ^0.4.1 + "@nomicfoundation/ethereumjs-common": 4.0.4 + "@nomicfoundation/ethereumjs-tx": 5.0.4 + "@nomicfoundation/ethereumjs-util": 9.0.4 "@nomicfoundation/solidity-analyzer": ^0.1.0 "@sentry/node": ^5.18.1 "@types/bn.js": ^5.1.0 @@ -12115,6 +11671,7 @@ __metadata: adm-zip: ^0.4.16 aggregate-error: ^3.0.0 ansi-escapes: ^4.3.0 + boxen: ^5.1.2 chalk: ^2.4.2 chokidar: ^3.4.0 ci-info: ^2.0.0 @@ -12137,7 +11694,7 @@ __metadata: raw-body: ^2.4.1 resolve: 1.17.0 semver: ^6.3.0 - solc: 0.7.3 + solc: 0.8.26 source-map-support: ^0.5.13 stacktrace-parser: ^0.1.10 tsort: 0.0.1 @@ -12154,7 +11711,7 @@ __metadata: optional: true bin: hardhat: internal/cli/bootstrap.js - checksum: 0b5499890e46750ca8c51bbe1205599b1424a2e5293b40c9f7cb56320d56b9935fbd4e276de370e07664ae81fa57dc7ab227bf2b2363f5732ef9f06df1a9a6d9 + checksum: 5aec1824db3575d63754de18c2629bcd820bc836d836f8a6346bcd9aa2ae4c397e090c43ea482ee765b704e018001015b5c84c5ded301a6a1144129c1a4c509b languageName: node linkType: hard @@ -13119,7 +12676,7 @@ __metadata: eslint: ^8.57.0 eslint-plugin-prettier: ^5.1.3 ethers: ^6.7.1 - hardhat: ^2.17.4 + hardhat: ^2.22.6 prettier: 3.2.5 smol-toml: ^1.1.2 toml: ^3.0.0 @@ -13247,7 +12804,7 @@ __metadata: languageName: node linkType: hard -"is-buffer@npm:^2.0.0, is-buffer@npm:^2.0.5": +"is-buffer@npm:^2.0.0": version: 2.0.5 resolution: "is-buffer@npm:2.0.5" checksum: 764c9ad8b523a9f5a32af29bdf772b08eb48c04d2ad0a7240916ac2688c983bf5f8504bf25b35e66240edeb9d9085461f9b5dae1f3d2861c6b06a65fe983de42 @@ -13800,13 +13357,6 @@ __metadata: languageName: node linkType: hard -"js-sdsl@npm:^4.1.4": - version: 4.4.2 - resolution: "js-sdsl@npm:4.4.2" - checksum: ba705adc1788bf3c6f6c8e5077824f2bb4f0acab5a984420ce5cc492c7fff3daddc26335ad2c9a67d4f5e3241ec790f9e5b72a625adcf20cf321d2fd85e62b8b - languageName: node - linkType: hard - "js-sha3@npm:0.8.0": version: 0.8.0 resolution: "js-sha3@npm:0.8.0" @@ -13943,18 +13493,6 @@ __metadata: languageName: node linkType: hard -"jsonfile@npm:^2.1.0": - version: 2.4.0 - resolution: "jsonfile@npm:2.4.0" - dependencies: - graceful-fs: ^4.1.6 - dependenciesMeta: - graceful-fs: - optional: true - checksum: f5064aabbc9e35530dc471d8b203ae1f40dbe949ddde4391c6f6a6d310619a15f0efdae5587df594d1d70c555193aaeee9d2ed4aec9ffd5767bd5e4e62d49c3d - languageName: node - linkType: hard - "jsonfile@npm:^4.0.0": version: 4.0.0 resolution: "jsonfile@npm:4.0.0" @@ -14053,18 +13591,6 @@ __metadata: languageName: node linkType: hard -"klaw@npm:^1.0.0": - version: 1.3.1 - resolution: "klaw@npm:1.3.1" - dependencies: - graceful-fs: ^4.1.9 - dependenciesMeta: - graceful-fs: - optional: true - checksum: 8f69e4797c26e7c3f2426bfa85f38a3da3c2cb1b4c6bd850d2377aed440d41ce9d806f2885c2e2e224372c56af4b1d43b8a499adecf9a05e7373dc6b8b7c52e4 - languageName: node - linkType: hard - "kleur@npm:^3.0.3": version: 3.0.3 resolution: "kleur@npm:3.0.3" @@ -14266,33 +13792,6 @@ __metadata: languageName: node linkType: hard -"level-supports@npm:^4.0.0": - version: 4.0.1 - resolution: "level-supports@npm:4.0.1" - checksum: d4552b42bb8cdeada07b0f6356c7a90fefe76279147331f291aceae26e3e56d5f927b09ce921647c0230bfe03ddfbdcef332be921e5c2194421ae2bfa3cf6368 - languageName: node - linkType: hard - -"level-transcoder@npm:^1.0.1": - version: 1.0.1 - resolution: "level-transcoder@npm:1.0.1" - dependencies: - buffer: ^6.0.3 - module-error: ^1.0.1 - checksum: 304f08d802faf3491a533b6d87ad8be3cabfd27f2713bbe9d4c633bf50fcb9460eab5a6776bf015e101ead7ba1c1853e05e7f341112f17a9d0cb37ee5a421a25 - languageName: node - linkType: hard - -"level@npm:^8.0.0": - version: 8.0.0 - resolution: "level@npm:8.0.0" - dependencies: - browser-level: ^1.0.1 - classic-level: ^1.2.0 - checksum: 13eb25bd71bfdca6cd714d1233adf9da97de9a8a4bf9f28d62a390b5c96d0250abaf983eb90eb8c4e89c7a985bb330750683d106f12670e5ea8fba1d7e608a1f - languageName: node - linkType: hard - "levelup@npm:^0.18.2": version: 0.18.6 resolution: "levelup@npm:0.18.6" @@ -14718,13 +14217,6 @@ __metadata: languageName: node linkType: hard -"mcl-wasm@npm:^0.7.1": - version: 0.7.9 - resolution: "mcl-wasm@npm:0.7.9" - checksum: 6b6ed5084156b98b2db70b223e1ba2c01953970b48a2e0c4ea3eeb9296610e6b3bfb2a2cce9e92e2d7ad61778b5f5a630e705e663835e915ba188c174a0a37fa - languageName: node - linkType: hard - "md5.js@npm:^1.3.4": version: 1.3.5 resolution: "md5.js@npm:1.3.5" @@ -15086,17 +14578,6 @@ __metadata: languageName: node linkType: hard -"memory-level@npm:^1.0.0": - version: 1.0.0 - resolution: "memory-level@npm:1.0.0" - dependencies: - abstract-level: ^1.0.0 - functional-red-black-tree: ^1.0.1 - module-error: ^1.0.1 - checksum: 80b1b7aedaf936e754adbcd7b9303018c3684fb32f9992fd967c448f145d177f16c724fbba9ed3c3590a9475fd563151eae664d69b83d2ad48714852e9fc5c72 - languageName: node - linkType: hard - "memorystream@npm:^0.3.1": version: 0.3.1 resolution: "memorystream@npm:0.3.1" @@ -15944,13 +15425,6 @@ __metadata: languageName: node linkType: hard -"module-error@npm:^1.0.1, module-error@npm:^1.0.2": - version: 1.0.2 - resolution: "module-error@npm:1.0.2" - checksum: 5d653e35bd55b3e95f8aee2cdac108082ea892e71b8f651be92cde43e4ee86abee4fa8bd7fc3fe5e68b63926d42f63c54cd17b87a560c31f18739295575a3962 - languageName: node - linkType: hard - "mrmime@npm:^1.0.0": version: 1.0.1 resolution: "mrmime@npm:1.0.1" @@ -16016,13 +15490,6 @@ __metadata: languageName: node linkType: hard -"napi-macros@npm:^2.2.2": - version: 2.2.2 - resolution: "napi-macros@npm:2.2.2" - checksum: c6f9bd71cdbbc37ddc3535aa5be481238641d89585b8a3f4d301cb89abf459e2d294810432bb7d12056d1f9350b1a0899a5afcf460237a3da6c398cf0fec7629 - languageName: node - linkType: hard - "natural-compare@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare@npm:1.4.0" @@ -16125,7 +15592,7 @@ __metadata: languageName: node linkType: hard -"node-gyp-build@npm:^4.2.0, node-gyp-build@npm:^4.3.0": +"node-gyp-build@npm:^4.2.0": version: 4.7.1 resolution: "node-gyp-build@npm:4.7.1" bin: @@ -17685,7 +17152,7 @@ __metadata: languageName: node linkType: hard -"queue-microtask@npm:^1.2.2, queue-microtask@npm:^1.2.3": +"queue-microtask@npm:^1.2.2": version: 1.2.3 resolution: "queue-microtask@npm:1.2.3" checksum: b676f8c040cdc5b12723ad2f91414d267605b26419d5c821ff03befa817ddd10e238d22b25d604920340fd73efd8ba795465a0377c4adf45a4a41e4234e42dc4 @@ -18420,7 +17887,7 @@ __metadata: languageName: node linkType: hard -"require-from-string@npm:^2.0.0, require-from-string@npm:^2.0.2": +"require-from-string@npm:^2.0.2": version: 2.0.2 resolution: "require-from-string@npm:2.0.2" checksum: a03ef6895445f33a4015300c426699bc66b2b044ba7b670aa238610381b56d3f07c686251740d575e22f4c87531ba662d06937508f0f3c0f1ddc04db3130560b @@ -18588,17 +18055,6 @@ __metadata: languageName: node linkType: hard -"rimraf@npm:^2.2.8": - version: 2.7.1 - resolution: "rimraf@npm:2.7.1" - dependencies: - glob: ^7.1.3 - bin: - rimraf: ./bin.js - checksum: cdc7f6eacb17927f2a075117a823e1c5951792c6498ebcce81ca8203454a811d4cf8900314154d3259bb8f0b42ab17f67396a8694a54cae3283326e57ad250cd - languageName: node - linkType: hard - "rimraf@npm:^3.0.2": version: 3.0.2 resolution: "rimraf@npm:3.0.2" @@ -18724,15 +18180,6 @@ __metadata: languageName: node linkType: hard -"run-parallel-limit@npm:^1.1.0": - version: 1.1.0 - resolution: "run-parallel-limit@npm:1.1.0" - dependencies: - queue-microtask: ^1.2.2 - checksum: 672c3b87e7f939c684b9965222b361421db0930223ed1e43ebf0e7e48ccc1a022ea4de080bef4d5468434e2577c33b7681e3f03b7593fdc49ad250a55381123c - languageName: node - linkType: hard - "run-parallel@npm:^1.1.9": version: 1.2.0 resolution: "run-parallel@npm:1.2.0" @@ -18742,13 +18189,6 @@ __metadata: languageName: node linkType: hard -"rustbn.js@npm:~0.2.0": - version: 0.2.0 - resolution: "rustbn.js@npm:0.2.0" - checksum: 2148e7ba34e70682907ee29df4784639e6eb025481b2c91249403b7ec57181980161868d9aa24822a5075dd1bb5a180dfedc77309e5f0d27b6301f9b563af99a - languageName: node - linkType: hard - "rxjs@npm:^7.5.4": version: 7.8.1 resolution: "rxjs@npm:7.8.1" @@ -18840,7 +18280,7 @@ __metadata: languageName: node linkType: hard -"scrypt-js@npm:3.0.1, scrypt-js@npm:^3.0.0": +"scrypt-js@npm:^3.0.0": version: 3.0.1 resolution: "scrypt-js@npm:3.0.1" checksum: b7c7d1a68d6ca946f2fbb0778e0c4ec63c65501b54023b2af7d7e9f48fdb6c6580d6f7675cd53bda5944c5ebc057560d5a6365079752546865defb3b79dea454 @@ -19332,22 +18772,20 @@ __metadata: languageName: node linkType: hard -"solc@npm:0.7.3": - version: 0.7.3 - resolution: "solc@npm:0.7.3" +"solc@npm:0.8.26": + version: 0.8.26 + resolution: "solc@npm:0.8.26" dependencies: command-exists: ^1.2.8 - commander: 3.0.2 + commander: ^8.1.0 follow-redirects: ^1.12.1 - fs-extra: ^0.30.0 js-sha3: 0.8.0 memorystream: ^0.3.1 - require-from-string: ^2.0.0 semver: ^5.5.0 tmp: 0.0.33 bin: - solcjs: solcjs - checksum: 2d8eb16c6d8f648213c94dc8d977cffe5099cba7d41c82d92d769ef71ae8320a985065ce3d6c306440a85f8e8d2b27fb30bdd3ac38f69e5c1fa0ab8a3fb2f217 + solcjs: solc.js + checksum: e3eaeac76e60676377b357af8f3919d4c8c6a74b74112b49279fe8c74a3dfa1de8afe4788689fc307453bde336edc8572988d2cf9e909f84d870420eb640400c languageName: node linkType: hard @@ -21628,21 +21066,6 @@ __metadata: languageName: node linkType: hard -"ws@npm:7.4.6": - version: 7.4.6 - resolution: "ws@npm:7.4.6" - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - checksum: 3a990b32ed08c72070d5e8913e14dfcd831919205be52a3ff0b4cdd998c8d554f167c9df3841605cde8b11d607768cacab3e823c58c96a5c08c987e093eb767a - languageName: node - linkType: hard - "ws@npm:8.16.0, ws@npm:^8.16.0": version: 8.16.0 resolution: "ws@npm:8.16.0"