diff --git a/.noir-sync-commit b/.noir-sync-commit index 158729d3edf..cd2891c1558 100644 --- a/.noir-sync-commit +++ b/.noir-sync-commit @@ -1 +1 @@ -5730e678d3b5aedacf327b1a9c2b69cc6916c176 +e2f7e950c44883228d5e1230b04c83e479de7ed0 diff --git a/noir/noir-repo/compiler/noirc_driver/src/lib.rs b/noir/noir-repo/compiler/noirc_driver/src/lib.rs index 72c95823553..b28606d3739 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/lib.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/lib.rs @@ -557,6 +557,7 @@ pub fn compile_no_check( let force_compile = force_compile || options.print_acir || options.show_brillig + || options.force_brillig || options.show_ssa || options.emit_ssa; @@ -578,7 +579,7 @@ pub fn compile_no_check( emit_ssa: if options.emit_ssa { Some(context.package_build_path.clone()) } else { None }, }; - let SsaProgramArtifact { program, debug, warnings, names, error_types, .. } = + let SsaProgramArtifact { program, debug, warnings, names, brillig_names, error_types, .. } = create_program(program, &ssa_evaluator_options)?; let abi = abi_gen::gen_abi(context, &main_function, return_visibility, error_types); @@ -593,5 +594,6 @@ pub fn compile_no_check( noir_version: NOIR_ARTIFACT_VERSION_STRING.to_string(), warnings, names, + brillig_names, }) } diff --git a/noir/noir-repo/compiler/noirc_driver/src/program.rs b/noir/noir-repo/compiler/noirc_driver/src/program.rs index 8e02de0b8b3..88460482928 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/program.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/program.rs @@ -29,4 +29,6 @@ pub struct CompiledProgram { pub warnings: Vec, /// Names of the functions in the program. These are used for more informative debugging and benchmarking. pub names: Vec, + /// Names of the unconstrained functions in the program. + pub brillig_names: Vec, } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen.rs index ee61a9d13d3..d6364cefc9a 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen.rs @@ -27,5 +27,7 @@ pub(crate) fn convert_ssa_function( BrilligBlock::compile(&mut function_context, &mut brillig_context, block, &func.dfg); } - brillig_context.artifact() + let mut artifact = brillig_context.artifact(); + artifact.name = func.name().to_string(); + artifact } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs index dff4da56c1e..fca1f60544d 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs @@ -55,6 +55,7 @@ pub(crate) fn directive_invert() -> GeneratedBrillig { ], assert_messages: Default::default(), locations: Default::default(), + name: "directive_invert".to_string(), } } @@ -109,5 +110,6 @@ pub(crate) fn directive_quotient() -> GeneratedBrillig { ], assert_messages: Default::default(), locations: Default::default(), + name: "directive_integer_quotient".to_string(), } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs index 2d0bdb5955c..53964f64170 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs @@ -22,6 +22,7 @@ pub(crate) struct GeneratedBrillig { pub(crate) byte_code: Vec>, pub(crate) locations: BTreeMap, pub(crate) assert_messages: BTreeMap, + pub(crate) name: String, } #[derive(Default, Debug, Clone)] @@ -49,6 +50,8 @@ pub(crate) struct BrilligArtifact { locations: BTreeMap, /// The current call stack. All opcodes that are pushed will be associated with this call stack. call_stack: CallStack, + /// Name of the function, only used for debugging purposes. + pub(crate) name: String, } /// A pointer to a location in the opcode. @@ -81,6 +84,7 @@ impl BrilligArtifact { byte_code: self.byte_code, locations: self.locations, assert_messages: self.assert_messages, + name: self.name, } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs index b632958f37f..9daf98e606b 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs @@ -151,6 +151,7 @@ pub struct SsaProgramArtifact { pub main_input_witnesses: Vec, pub main_return_witnesses: Vec, pub names: Vec, + pub brillig_names: Vec, pub error_types: BTreeMap, } @@ -167,6 +168,7 @@ impl SsaProgramArtifact { main_input_witnesses: Vec::default(), main_return_witnesses: Vec::default(), names: Vec::default(), + brillig_names: Vec::default(), error_types, } } @@ -202,8 +204,10 @@ pub fn create_program( let func_sigs = program.function_signatures.clone(); let recursive = program.recursive; - let ArtifactsAndWarnings((generated_acirs, generated_brillig, error_types), ssa_level_warnings) = - optimize_into_acir(program, options)?; + let ArtifactsAndWarnings( + (generated_acirs, generated_brillig, brillig_function_names, error_types), + ssa_level_warnings, + ) = optimize_into_acir(program, options)?; if options.force_brillig_output { assert_eq!( generated_acirs.len(), @@ -236,6 +240,7 @@ pub fn create_program( program_artifact.add_circuit(circuit_artifact, is_main); is_main = false; } + program_artifact.brillig_names = brillig_function_names; Ok(program_artifact) } 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 22b13aeae79..9f11267f100 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 @@ -282,6 +282,7 @@ impl AcirValue { pub(crate) type Artifacts = ( Vec>, Vec>, + Vec, BTreeMap, ); @@ -334,11 +335,13 @@ impl Ssa { } } - let brillig = vecmap(shared_context.generated_brillig, |brillig| BrilligBytecode { - bytecode: brillig.byte_code, - }); + let (brillig_bytecode, brillig_names) = shared_context + .generated_brillig + .into_iter() + .map(|brillig| (BrilligBytecode { bytecode: brillig.byte_code }, brillig.name)) + .unzip(); - Ok((acirs, brillig, self.error_selector_to_type)) + Ok((acirs, brillig_bytecode, brillig_names, self.error_selector_to_type)) } } @@ -955,6 +958,8 @@ impl<'a> Context<'a> { BrilligFunctionContext::return_values(func), BrilligFunctionContext::function_id_to_function_label(func.id()), ); + entry_point.name = func.name().to_string(); + // Link the entry point with all dependencies while let Some(unresolved_fn_label) = entry_point.first_unresolved_function_call() { let artifact = &brillig.find_by_function_label(unresolved_fn_label.clone()); @@ -2966,7 +2971,7 @@ mod test { let ssa = builder.finish(); - let (acir_functions, _, _) = ssa + let (acir_functions, _, _, _) = ssa .into_acir(&Brillig::default(), ExpressionWidth::default()) .expect("Should compile manually written SSA into ACIR"); // Expected result: @@ -3061,7 +3066,7 @@ mod test { let ssa = builder.finish(); - let (acir_functions, _, _) = ssa + let (acir_functions, _, _, _) = ssa .into_acir(&Brillig::default(), ExpressionWidth::default()) .expect("Should compile manually written SSA into ACIR"); // The expected result should look very similar to the above test expect that the input witnesses of the `Call` @@ -3151,7 +3156,7 @@ mod test { let ssa = builder.finish(); - let (acir_functions, _, _) = ssa + let (acir_functions, _, _, _) = ssa .into_acir(&Brillig::default(), ExpressionWidth::default()) .expect("Should compile manually written SSA into ACIR"); @@ -3265,7 +3270,7 @@ mod test { let ssa = builder.finish(); let brillig = ssa.to_brillig(false); - let (acir_functions, brillig_functions, _) = ssa + let (acir_functions, brillig_functions, _, _) = ssa .into_acir(&brillig, ExpressionWidth::default()) .expect("Should compile manually written SSA into ACIR"); @@ -3329,7 +3334,7 @@ mod test { // The Brillig bytecode we insert for the stdlib is hardcoded so we do not need to provide any // Brillig artifacts to the ACIR gen pass. - let (acir_functions, brillig_functions, _) = ssa + let (acir_functions, brillig_functions, _, _) = ssa .into_acir(&Brillig::default(), ExpressionWidth::default()) .expect("Should compile manually written SSA into ACIR"); @@ -3403,7 +3408,7 @@ mod test { let brillig = ssa.to_brillig(false); println!("{}", ssa); - let (acir_functions, brillig_functions, _) = ssa + let (acir_functions, brillig_functions, _, _) = ssa .into_acir(&brillig, ExpressionWidth::default()) .expect("Should compile manually written SSA into ACIR"); @@ -3491,7 +3496,7 @@ mod test { let brillig = ssa.to_brillig(false); println!("{}", ssa); - let (acir_functions, brillig_functions, _) = ssa + let (acir_functions, brillig_functions, _, _) = ssa .into_acir(&brillig, ExpressionWidth::default()) .expect("Should compile manually written SSA into ACIR"); 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 093dc370eb6..33f8c9d8332 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 @@ -897,6 +897,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::Bool(lhs == rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::Bool(lhs == rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::Bool(lhs == rhs)), + (Value::Bool(lhs), Value::Bool(rhs)) => Ok(Value::Bool(lhs == rhs)), (lhs, rhs) => make_error(self, lhs, rhs, "=="), }, BinaryOpKind::NotEqual => match (lhs, rhs) { @@ -909,6 +910,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::Bool(lhs != rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::Bool(lhs != rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::Bool(lhs != rhs)), + (Value::Bool(lhs), Value::Bool(rhs)) => Ok(Value::Bool(lhs != rhs)), (lhs, rhs) => make_error(self, lhs, rhs, "!="), }, BinaryOpKind::Less => match (lhs, rhs) { 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 5698cc8d3bd..4ed72cc0263 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 @@ -17,8 +17,8 @@ use rustc_hash::FxHashMap as HashMap; use crate::{ ast::{ - ExpressionKind, FunctionKind, FunctionReturnType, IntegerBitSize, UnresolvedType, - UnresolvedTypeData, Visibility, + ExpressionKind, FunctionKind, FunctionReturnType, IntegerBitSize, Literal, UnaryOp, + UnresolvedType, UnresolvedTypeData, Visibility, }, hir::comptime::{errors::IResult, value::add_token_spans, InterpreterError, Value}, hir_def::function::FunctionBody, @@ -47,7 +47,12 @@ impl<'local, 'context> Interpreter<'local, 'context> { "array_as_str_unchecked" => array_as_str_unchecked(interner, arguments, location), "array_len" => array_len(interner, arguments, location), "as_slice" => as_slice(interner, arguments, location), + "expr_as_bool" => expr_as_bool(arguments, return_type, location), "expr_as_function_call" => expr_as_function_call(arguments, return_type, location), + "expr_as_if" => expr_as_if(arguments, return_type, location), + "expr_as_index" => expr_as_index(arguments, return_type, location), + "expr_as_unary_op" => expr_as_unary_op(arguments, return_type, location), + "expr_as_tuple" => expr_as_tuple(arguments, return_type, location), "is_unconstrained" => Ok(Value::Bool(true)), "function_def_name" => function_def_name(interner, arguments, location), "function_def_parameters" => function_def_parameters(interner, arguments, location), @@ -750,6 +755,21 @@ fn zeroed(return_type: Type) -> IResult { } } +// fn as_bool(self) -> Option +fn expr_as_bool( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(arguments, return_type, location, |expr| { + if let ExpressionKind::Literal(Literal::Bool(bool)) = expr { + Some(Value::Bool(bool)) + } else { + None + } + }) +} + // fn as_function_call(self) -> Option<(Expr, [Expr])> fn expr_as_function_call( arguments: Vec<(Value, Location)>, @@ -770,6 +790,108 @@ fn expr_as_function_call( }) } +// fn as_if(self) -> Option<(Expr, Expr, Option)> +fn expr_as_if( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(arguments, return_type.clone(), location, |expr| { + if let ExpressionKind::If(if_expr) = expr { + // Get the type of `Option` + let option_type = extract_option_generic_type(return_type.clone()); + let Type::Tuple(option_types) = option_type else { + panic!("Expected the return type option generic arg to be a tuple"); + }; + assert_eq!(option_types.len(), 3); + let alternative_option_type = option_types[2].clone(); + + let alternative = + option(alternative_option_type, if_expr.alternative.map(|e| Value::Expr(e.kind))); + + Some(Value::Tuple(vec![ + Value::Expr(if_expr.condition.kind), + Value::Expr(if_expr.consequence.kind), + alternative.ok()?, + ])) + } else { + None + } + }) +} + +// fn as_index(self) -> Option +fn expr_as_index( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(arguments, return_type, location, |expr| { + if let ExpressionKind::Index(index_expr) = expr { + Some(Value::Tuple(vec![ + Value::Expr(index_expr.collection.kind), + Value::Expr(index_expr.index.kind), + ])) + } else { + None + } + }) +} + +// fn as_unary_op(self) -> Option<(UnaryOp, Expr)> +fn expr_as_unary_op( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(arguments, return_type.clone(), location, |expr| { + if let ExpressionKind::Prefix(prefix_expr) = expr { + let option_type = extract_option_generic_type(return_type); + let Type::Tuple(mut tuple_types) = option_type else { + panic!("Expected the return type option generic arg to be a tuple"); + }; + assert_eq!(tuple_types.len(), 2); + + tuple_types.pop().unwrap(); + let unary_op_type = tuple_types.pop().unwrap(); + + // These values should match the values used in noir_stdlib/src/meta/op.nr + let unary_op_value = match prefix_expr.operator { + UnaryOp::Minus => 0_u128, + UnaryOp::Not => 1_u128, + UnaryOp::MutableReference => 2_u128, + UnaryOp::Dereference { .. } => 3_u128, + }; + + let mut fields = HashMap::default(); + fields.insert(Rc::new("op".to_string()), Value::Field(unary_op_value.into())); + + let unary_op = Value::Struct(fields, unary_op_type); + let rhs = Value::Expr(prefix_expr.rhs.kind); + Some(Value::Tuple(vec![unary_op, rhs])) + } else { + None + } + }) +} + +// fn as_tuple(self) -> Option<[Expr]> +fn expr_as_tuple( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(arguments, return_type, location, |expr| { + if let ExpressionKind::Tuple(expressions) = expr { + let expressions = expressions.into_iter().map(|expr| Value::Expr(expr.kind)).collect(); + let typ = Type::Slice(Box::new(Type::Quoted(QuotedType::Expr))); + Some(Value::Slice(expressions, typ)) + } else { + None + } + }) +} + // Helper function for implementing the `expr_as_...` functions. fn expr_as( arguments: Vec<(Value, Location)>, @@ -781,8 +903,12 @@ where F: FnOnce(ExpressionKind) -> Option, { let self_argument = check_one_argument(arguments, location)?; - let expr = get_expr(self_argument)?; - let option_value = f(expr); + let mut expression_kind = get_expr(self_argument)?; + while let ExpressionKind::Parenthesized(expression) = expression_kind { + expression_kind = expression.kind; + } + + let option_value = f(expression_kind); option(return_type, option_value) } diff --git a/noir/noir-repo/noir_stdlib/src/meta/expr.nr b/noir/noir-repo/noir_stdlib/src/meta/expr.nr index 54681632543..3230ce42457 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/expr.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/expr.nr @@ -1,6 +1,22 @@ use crate::option::Option; +use crate::meta::op::UnaryOp; impl Expr { + #[builtin(expr_as_bool)] + fn as_bool(self) -> Option {} + #[builtin(expr_as_function_call)] fn as_function_call(self) -> Option<(Expr, [Expr])> {} + + #[builtin(expr_as_if)] + fn as_if(self) -> Option<(Expr, Expr, Option)> {} + + #[builtin(expr_as_index)] + fn as_index(self) -> Option<(Expr, Expr)> {} + + #[builtin(expr_as_tuple)] + fn as_tuple(self) -> Option<[Expr]> {} + + #[builtin(expr_as_unary_op)] + fn as_unary_op(self) -> Option<(UnaryOp, Expr)> {} } diff --git a/noir/noir-repo/noir_stdlib/src/meta/mod.nr b/noir/noir-repo/noir_stdlib/src/meta/mod.nr index e081d4ecd4b..beaee8e44ea 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/mod.nr @@ -5,6 +5,7 @@ use crate::hash::poseidon2::Poseidon2Hasher; mod expr; mod function_def; mod module; +mod op; mod struct_def; mod trait_constraint; mod trait_def; diff --git a/noir/noir-repo/noir_stdlib/src/meta/op.nr b/noir/noir-repo/noir_stdlib/src/meta/op.nr new file mode 100644 index 00000000000..1fbd4884841 --- /dev/null +++ b/noir/noir-repo/noir_stdlib/src/meta/op.nr @@ -0,0 +1,21 @@ +struct UnaryOp { + op: Field +} + +impl UnaryOp { + fn is_minus(self) -> bool { + self.op == 0 + } + + fn is_not(self) -> bool { + self.op == 1 + } + + fn is_mutable_reference(self) -> bool { + self.op == 2 + } + + fn is_dereference(self) -> bool { + self.op == 3 + } +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_exp/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_exp/src/main.nr index 8b6f7b480c7..eb0d27e2a38 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_exp/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_exp/src/main.nr @@ -1,8 +1,55 @@ fn main() { comptime { + // Check Expr::as_function_call let expr = quote { foo(bar) }.as_expr().unwrap(); let (_function, args) = expr.as_function_call().unwrap(); assert_eq(args.len(), 1); + + // Check Expr::as_index + let expr = quote { foo[bar] }.as_expr().unwrap(); + let _ = expr.as_index().unwrap(); + + // Check Expr::as_tuple + let expr = quote { (1, 2) }.as_expr().unwrap(); + let tuple_exprs = expr.as_tuple().unwrap(); + assert_eq(tuple_exprs.len(), 2); + + // Check Expr::as_if + let expr = quote { if 1 { 2 } }.as_expr().unwrap(); + let (_condition, _consequence, alternative) = expr.as_if().unwrap(); + assert(alternative.is_none()); + + let expr = quote { if 1 { 2 } else { 3 } }.as_expr().unwrap(); + let (_condition, _consequence, alternative) = expr.as_if().unwrap(); + assert(alternative.is_some()); + + // Check parenthesized expression is automatically unwrapped + let expr = quote { ((if 1 { 2 })) }.as_expr().unwrap(); + assert(expr.as_if().is_some()); + + // Check Expr::as_bool + let expr = quote { false }.as_expr().unwrap(); + assert(expr.as_bool().unwrap() == false); + + let expr = quote { true }.as_expr().unwrap(); + assert_eq(expr.as_bool().unwrap(), true); + + // Check Expr::as_unary_op + let expr = quote { -x }.as_expr().unwrap(); + let (op, _) = expr.as_unary_op().unwrap(); + assert(op.is_minus()); + + let expr = quote { !x }.as_expr().unwrap(); + let (op, _) = expr.as_unary_op().unwrap(); + assert(op.is_not()); + + let expr = quote { &mut x }.as_expr().unwrap(); + let (op, _) = expr.as_unary_op().unwrap(); + assert(op.is_mutable_reference()); + + let expr = quote { *x }.as_expr().unwrap(); + let (op, _) = expr.as_unary_op().unwrap(); + assert(op.is_dereference()); } } diff --git a/noir/noir-repo/tooling/lsp/src/lib.rs b/noir/noir-repo/tooling/lsp/src/lib.rs index ca34d7686fd..796137a8e9f 100644 --- a/noir/noir-repo/tooling/lsp/src/lib.rs +++ b/noir/noir-repo/tooling/lsp/src/lib.rs @@ -23,7 +23,7 @@ use fxhash::FxHashSet; use lsp_types::{ request::{ Completion, DocumentSymbolRequest, HoverRequest, InlayHintRequest, PrepareRenameRequest, - References, Rename, + References, Rename, SignatureHelpRequest, }, CodeLens, }; @@ -55,7 +55,7 @@ use requests::{ 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, LspInitializationOptions, + on_signature_help_request, on_test_run_request, on_tests_request, LspInitializationOptions, }; use serde_json::Value as JsonValue; use thiserror::Error; @@ -143,6 +143,7 @@ impl NargoLspService { .request::(on_hover_request) .request::(on_inlay_hint_request) .request::(on_completion_request) + .request::(on_signature_help_request) .notification::(on_initialized) .notification::(on_did_change_configuration) .notification::(on_did_open_text_document) diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion.rs b/noir/noir-repo/tooling/lsp/src/requests/completion.rs index ad2082d75a8..dd3131aba56 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion.rs @@ -4,26 +4,28 @@ use std::{ }; use async_lsp::ResponseError; -use completion_items::{crate_completion_item, simple_completion_item}; +use completion_items::{ + crate_completion_item, field_completion_item, simple_completion_item, + struct_field_completion_item, +}; use fm::{FileId, PathString}; use kinds::{FunctionCompletionKind, FunctionKind, ModuleCompletionKind, RequestedItems}; use lsp_types::{CompletionItem, CompletionItemKind, CompletionParams, CompletionResponse}; use noirc_errors::{Location, Span}; use noirc_frontend::{ ast::{ - ArrayLiteral, AsTraitPath, BlockExpression, CallExpression, CastExpression, - ConstrainStatement, ConstructorExpression, Expression, ForLoopStatement, ForRange, - FunctionReturnType, Ident, IfExpression, IndexExpression, InfixExpression, LValue, Lambda, - LetStatement, Literal, MemberAccessExpression, MethodCallExpression, NoirFunction, - NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, Path, PathKind, PathSegment, Pattern, - Statement, TraitImplItem, TraitItem, TypeImpl, UnresolvedGeneric, UnresolvedGenerics, - UnresolvedType, UseTree, UseTreeKind, + AsTraitPath, BlockExpression, ConstructorExpression, Expression, ExpressionKind, + ForLoopStatement, Ident, IfExpression, LValue, Lambda, LetStatement, + MemberAccessExpression, NoirFunction, NoirStruct, NoirTraitImpl, Path, PathKind, + PathSegment, Pattern, Statement, StatementKind, TraitItem, TypeImpl, UnresolvedGeneric, + UnresolvedGenerics, UnresolvedType, UnresolvedTypeData, UseTree, UseTreeKind, }, graph::{CrateId, Dependency}, hir::{ def_map::{CrateDefMap, LocalModuleId, ModuleId}, resolution::path_resolver::{PathResolver, StandardPathResolver}, }, + hir_def::traits::Trait, macros_api::{ModuleDefId, NodeInterner}, node_interner::ReferenceId, parser::{Item, ItemKind}, @@ -40,6 +42,7 @@ mod completion_items; mod kinds; mod sort_text; mod tests; +mod traversal; pub(crate) fn on_completion_request( state: &mut LspState, @@ -154,12 +157,6 @@ impl<'a> NodeFinder<'a> { } } - fn find_in_parsed_module(&mut self, parsed_module: &ParsedModule) { - for item in &parsed_module.items { - self.find_in_item(item); - } - } - fn find_in_item(&mut self, item: &Item) { if !self.includes_span(item.span) { return; @@ -230,14 +227,6 @@ impl<'a> NodeFinder<'a> { self.type_parameters.clear(); } - fn find_in_trait_impl_item(&mut self, item: &TraitImplItem) { - match item { - TraitImplItem::Function(noir_function) => self.find_in_noir_function(noir_function), - TraitImplItem::Constant(_, _, _) => (), - TraitImplItem::Type { .. } => (), - } - } - fn find_in_type_impl(&mut self, type_impl: &TypeImpl) { self.type_parameters.clear(); self.collect_type_parameters_in_generics(&type_impl.generics); @@ -254,10 +243,6 @@ impl<'a> NodeFinder<'a> { self.type_parameters.clear(); } - fn find_in_noir_type_alias(&mut self, noir_type_alias: &NoirTypeAlias) { - self.find_in_unresolved_type(&noir_type_alias.typ); - } - fn find_in_noir_struct(&mut self, noir_struct: &NoirStruct) { self.type_parameters.clear(); self.collect_type_parameters_in_generics(&noir_struct.generics); @@ -269,12 +254,6 @@ impl<'a> NodeFinder<'a> { self.type_parameters.clear(); } - fn find_in_noir_trait(&mut self, noir_trait: &NoirTrait) { - for item in &noir_trait.items { - self.find_in_trait_item(item); - } - } - fn find_in_trait_item(&mut self, trait_item: &TraitItem) { match trait_item { TraitItem::Function { @@ -334,22 +313,22 @@ impl<'a> NodeFinder<'a> { fn find_in_statement(&mut self, statement: &Statement) { match &statement.kind { - noirc_frontend::ast::StatementKind::Let(let_statement) => { + StatementKind::Let(let_statement) => { self.find_in_let_statement(let_statement, true); } - noirc_frontend::ast::StatementKind::Constrain(constrain_statement) => { + StatementKind::Constrain(constrain_statement) => { self.find_in_constrain_statement(constrain_statement); } - noirc_frontend::ast::StatementKind::Expression(expression) => { + StatementKind::Expression(expression) => { self.find_in_expression(expression); } - noirc_frontend::ast::StatementKind::Assign(assign_statement) => { + StatementKind::Assign(assign_statement) => { self.find_in_assign_statement(assign_statement); } - noirc_frontend::ast::StatementKind::For(for_loop_statement) => { + StatementKind::For(for_loop_statement) => { self.find_in_for_loop_statement(for_loop_statement); } - noirc_frontend::ast::StatementKind::Comptime(statement) => { + StatementKind::Comptime(statement) => { // When entering a comptime block, regular local variables shouldn't be offered anymore let old_local_variables = self.local_variables.clone(); self.local_variables.clear(); @@ -358,12 +337,10 @@ impl<'a> NodeFinder<'a> { self.local_variables = old_local_variables; } - noirc_frontend::ast::StatementKind::Semi(expression) => { + StatementKind::Semi(expression) => { self.find_in_expression(expression); } - noirc_frontend::ast::StatementKind::Break - | noirc_frontend::ast::StatementKind::Continue - | noirc_frontend::ast::StatementKind::Error => (), + StatementKind::Break | StatementKind::Continue | StatementKind::Error => (), } } @@ -380,22 +357,6 @@ impl<'a> NodeFinder<'a> { } } - fn find_in_constrain_statement(&mut self, constrain_statement: &ConstrainStatement) { - self.find_in_expression(&constrain_statement.0); - - if let Some(exp) = &constrain_statement.1 { - self.find_in_expression(exp); - } - } - - fn find_in_assign_statement( - &mut self, - assign_statement: &noirc_frontend::ast::AssignStatement, - ) { - self.find_in_lvalue(&assign_statement.lvalue); - self.find_in_expression(&assign_statement.expression); - } - fn find_in_for_loop_statement(&mut self, for_loop_statement: &ForLoopStatement) { let old_local_variables = self.local_variables.clone(); let ident = &for_loop_statement.identifier; @@ -430,69 +391,53 @@ impl<'a> NodeFinder<'a> { } } - fn find_in_for_range(&mut self, for_range: &ForRange) { - match for_range { - ForRange::Range(start, end) => { - self.find_in_expression(start); - self.find_in_expression(end); - } - ForRange::Array(expression) => self.find_in_expression(expression), - } - } - - fn find_in_expressions(&mut self, expressions: &[Expression]) { - for expression in expressions { - self.find_in_expression(expression); - } - } - fn find_in_expression(&mut self, expression: &Expression) { match &expression.kind { - noirc_frontend::ast::ExpressionKind::Literal(literal) => self.find_in_literal(literal), - noirc_frontend::ast::ExpressionKind::Block(block_expression) => { + ExpressionKind::Literal(literal) => self.find_in_literal(literal), + ExpressionKind::Block(block_expression) => { self.find_in_block_expression(block_expression); } - noirc_frontend::ast::ExpressionKind::Prefix(prefix_expression) => { + ExpressionKind::Prefix(prefix_expression) => { self.find_in_expression(&prefix_expression.rhs); } - noirc_frontend::ast::ExpressionKind::Index(index_expression) => { + ExpressionKind::Index(index_expression) => { self.find_in_index_expression(index_expression); } - noirc_frontend::ast::ExpressionKind::Call(call_expression) => { + ExpressionKind::Call(call_expression) => { self.find_in_call_expression(call_expression); } - noirc_frontend::ast::ExpressionKind::MethodCall(method_call_expression) => { + ExpressionKind::MethodCall(method_call_expression) => { self.find_in_method_call_expression(method_call_expression); } - noirc_frontend::ast::ExpressionKind::Constructor(constructor_expression) => { + ExpressionKind::Constructor(constructor_expression) => { self.find_in_constructor_expression(constructor_expression); } - noirc_frontend::ast::ExpressionKind::MemberAccess(member_access_expression) => { + ExpressionKind::MemberAccess(member_access_expression) => { self.find_in_member_access_expression(member_access_expression); } - noirc_frontend::ast::ExpressionKind::Cast(cast_expression) => { + ExpressionKind::Cast(cast_expression) => { self.find_in_cast_expression(cast_expression); } - noirc_frontend::ast::ExpressionKind::Infix(infix_expression) => { + ExpressionKind::Infix(infix_expression) => { self.find_in_infix_expression(infix_expression); } - noirc_frontend::ast::ExpressionKind::If(if_expression) => { + ExpressionKind::If(if_expression) => { self.find_in_if_expression(if_expression); } - noirc_frontend::ast::ExpressionKind::Variable(path) => { + ExpressionKind::Variable(path) => { self.find_in_path(path, RequestedItems::AnyItems); } - noirc_frontend::ast::ExpressionKind::Tuple(expressions) => { + ExpressionKind::Tuple(expressions) => { self.find_in_expressions(expressions); } - noirc_frontend::ast::ExpressionKind::Lambda(lambda) => self.find_in_lambda(lambda), - noirc_frontend::ast::ExpressionKind::Parenthesized(expression) => { + ExpressionKind::Lambda(lambda) => self.find_in_lambda(lambda), + ExpressionKind::Parenthesized(expression) => { self.find_in_expression(expression); } - noirc_frontend::ast::ExpressionKind::Unquote(expression) => { + ExpressionKind::Unquote(expression) => { self.find_in_expression(expression); } - noirc_frontend::ast::ExpressionKind::Comptime(block_expression, _) => { + ExpressionKind::Comptime(block_expression, _) => { // When entering a comptime block, regular local variables shouldn't be offered anymore let old_local_variables = self.local_variables.clone(); self.local_variables.clear(); @@ -501,15 +446,13 @@ impl<'a> NodeFinder<'a> { self.local_variables = old_local_variables; } - noirc_frontend::ast::ExpressionKind::Unsafe(block_expression, _) => { + ExpressionKind::Unsafe(block_expression, _) => { self.find_in_block_expression(block_expression); } - noirc_frontend::ast::ExpressionKind::AsTraitPath(as_trait_path) => { + ExpressionKind::AsTraitPath(as_trait_path) => { self.find_in_as_trait_path(as_trait_path); } - noirc_frontend::ast::ExpressionKind::Quote(_) - | noirc_frontend::ast::ExpressionKind::Resolved(_) - | noirc_frontend::ast::ExpressionKind::Error => (), + ExpressionKind::Quote(_) | ExpressionKind::Resolved(_) | ExpressionKind::Error => (), } // "foo." (no identifier afterwards) is parsed as the expression on the left hand-side of the dot. @@ -530,49 +473,48 @@ impl<'a> NodeFinder<'a> { } } - fn find_in_literal(&mut self, literal: &Literal) { - match literal { - Literal::Array(array_literal) => self.find_in_array_literal(array_literal), - Literal::Slice(array_literal) => self.find_in_array_literal(array_literal), - Literal::Bool(_) - | Literal::Integer(_, _) - | Literal::Str(_) - | Literal::RawStr(_, _) - | Literal::FmtStr(_) - | Literal::Unit => (), + fn find_in_constructor_expression(&mut self, constructor_expression: &ConstructorExpression) { + self.find_in_path(&constructor_expression.type_name, RequestedItems::OnlyTypes); + + // Check if we need to autocomplete the field name + if constructor_expression + .fields + .iter() + .any(|(field_name, _)| field_name.span().end() as usize == self.byte_index) + { + self.complete_constructor_field_name(constructor_expression); + return; } - } - fn find_in_array_literal(&mut self, array_literal: &ArrayLiteral) { - match array_literal { - ArrayLiteral::Standard(expressions) => self.find_in_expressions(expressions), - ArrayLiteral::Repeated { repeated_element, length } => { - self.find_in_expression(repeated_element); - self.find_in_expression(length); - } + for (_field_name, expression) in &constructor_expression.fields { + self.find_in_expression(expression); } } - fn find_in_index_expression(&mut self, index_expression: &IndexExpression) { - self.find_in_expression(&index_expression.collection); - self.find_in_expression(&index_expression.index); - } + fn complete_constructor_field_name(&mut self, constructor_expression: &ConstructorExpression) { + let location = + Location::new(constructor_expression.type_name.last_ident().span(), self.file); + let Some(ReferenceId::Struct(struct_id)) = self.interner.find_referenced(location) else { + return; + }; - fn find_in_call_expression(&mut self, call_expression: &CallExpression) { - self.find_in_expression(&call_expression.func); - self.find_in_expressions(&call_expression.arguments); - } + let struct_type = self.interner.get_struct(struct_id); + let struct_type = struct_type.borrow(); - fn find_in_method_call_expression(&mut self, method_call_expression: &MethodCallExpression) { - self.find_in_expression(&method_call_expression.object); - self.find_in_expressions(&method_call_expression.arguments); - } + // First get all of the struct's fields + let mut fields = HashMap::new(); + let fields_as_written = struct_type.get_fields_as_written(); + for (field, typ) in &fields_as_written { + fields.insert(field, typ); + } - fn find_in_constructor_expression(&mut self, constructor_expression: &ConstructorExpression) { - self.find_in_path(&constructor_expression.type_name, RequestedItems::OnlyTypes); + // Remove the ones that already exists in the constructor + for (field, _) in &constructor_expression.fields { + fields.remove(&field.0.contents); + } - for (_field_name, expression) in &constructor_expression.fields { - self.find_in_expression(expression); + for (field, typ) in fields { + self.completion_items.push(struct_field_completion_item(field, typ)); } } @@ -596,15 +538,6 @@ impl<'a> NodeFinder<'a> { self.find_in_expression(&member_access_expression.lhs); } - fn find_in_cast_expression(&mut self, cast_expression: &CastExpression) { - self.find_in_expression(&cast_expression.lhs); - } - - fn find_in_infix_expression(&mut self, infix_expression: &InfixExpression) { - self.find_in_expression(&infix_expression.lhs); - self.find_in_expression(&infix_expression.rhs); - } - fn find_in_if_expression(&mut self, if_expression: &IfExpression) { self.find_in_expression(&if_expression.condition); @@ -638,21 +571,6 @@ impl<'a> NodeFinder<'a> { self.find_in_path(&as_trait_path.trait_path, RequestedItems::OnlyTypes); } - fn find_in_function_return_type(&mut self, return_type: &FunctionReturnType) { - match return_type { - noirc_frontend::ast::FunctionReturnType::Default(_) => (), - noirc_frontend::ast::FunctionReturnType::Ty(unresolved_type) => { - self.find_in_unresolved_type(unresolved_type); - } - } - } - - fn find_in_unresolved_types(&mut self, unresolved_type: &[UnresolvedType]) { - for unresolved_type in unresolved_type { - self.find_in_unresolved_type(unresolved_type); - } - } - fn find_in_unresolved_type(&mut self, unresolved_type: &UnresolvedType) { if let Some(span) = unresolved_type.span { if !self.includes_span(span) { @@ -661,48 +579,48 @@ impl<'a> NodeFinder<'a> { } match &unresolved_type.typ { - noirc_frontend::ast::UnresolvedTypeData::Array(_, unresolved_type) => { + UnresolvedTypeData::Array(_, unresolved_type) => { self.find_in_unresolved_type(unresolved_type); } - noirc_frontend::ast::UnresolvedTypeData::Slice(unresolved_type) => { + UnresolvedTypeData::Slice(unresolved_type) => { self.find_in_unresolved_type(unresolved_type); } - noirc_frontend::ast::UnresolvedTypeData::Parenthesized(unresolved_type) => { + UnresolvedTypeData::Parenthesized(unresolved_type) => { self.find_in_unresolved_type(unresolved_type); } - noirc_frontend::ast::UnresolvedTypeData::Named(path, unresolved_types, _) => { + UnresolvedTypeData::Named(path, unresolved_types, _) => { self.find_in_path(path, RequestedItems::OnlyTypes); self.find_in_unresolved_types(unresolved_types); } - noirc_frontend::ast::UnresolvedTypeData::TraitAsType(path, unresolved_types) => { + UnresolvedTypeData::TraitAsType(path, unresolved_types) => { self.find_in_path(path, RequestedItems::OnlyTypes); self.find_in_unresolved_types(unresolved_types); } - noirc_frontend::ast::UnresolvedTypeData::MutableReference(unresolved_type) => { + UnresolvedTypeData::MutableReference(unresolved_type) => { self.find_in_unresolved_type(unresolved_type); } - noirc_frontend::ast::UnresolvedTypeData::Tuple(unresolved_types) => { + UnresolvedTypeData::Tuple(unresolved_types) => { self.find_in_unresolved_types(unresolved_types); } - noirc_frontend::ast::UnresolvedTypeData::Function(args, ret, env, _) => { + UnresolvedTypeData::Function(args, ret, env, _) => { self.find_in_unresolved_types(args); self.find_in_unresolved_type(ret); self.find_in_unresolved_type(env); } - noirc_frontend::ast::UnresolvedTypeData::AsTraitPath(as_trait_path) => { + UnresolvedTypeData::AsTraitPath(as_trait_path) => { self.find_in_as_trait_path(as_trait_path); } - noirc_frontend::ast::UnresolvedTypeData::Expression(_) - | noirc_frontend::ast::UnresolvedTypeData::FormatString(_, _) - | noirc_frontend::ast::UnresolvedTypeData::String(_) - | noirc_frontend::ast::UnresolvedTypeData::Unspecified - | noirc_frontend::ast::UnresolvedTypeData::Quoted(_) - | noirc_frontend::ast::UnresolvedTypeData::FieldElement - | noirc_frontend::ast::UnresolvedTypeData::Integer(_, _) - | noirc_frontend::ast::UnresolvedTypeData::Bool - | noirc_frontend::ast::UnresolvedTypeData::Unit - | noirc_frontend::ast::UnresolvedTypeData::Resolved(_) - | noirc_frontend::ast::UnresolvedTypeData::Error => (), + UnresolvedTypeData::Expression(_) + | UnresolvedTypeData::FormatString(_, _) + | UnresolvedTypeData::String(_) + | UnresolvedTypeData::Unspecified + | UnresolvedTypeData::Quoted(_) + | UnresolvedTypeData::FieldElement + | UnresolvedTypeData::Integer(_, _) + | UnresolvedTypeData::Bool + | UnresolvedTypeData::Unit + | UnresolvedTypeData::Resolved(_) + | UnresolvedTypeData::Error => (), } } @@ -758,8 +676,9 @@ impl<'a> NodeFinder<'a> { self.complete_type_methods(&type_alias.typ, &prefix, FunctionKind::Any); return; } - ModuleDefId::TraitId(_) => { - // For now we don't suggest trait methods + ModuleDefId::TraitId(trait_id) => { + let trait_ = self.interner.get_trait(trait_id); + self.complete_trait_methods(trait_, &prefix, FunctionKind::Any); return; } ModuleDefId::GlobalId(_) => return, @@ -977,6 +896,9 @@ impl<'a> NodeFinder<'a> { let type_alias = type_alias.borrow(); return self.complete_type_fields_and_methods(&type_alias.typ, prefix); } + Type::Tuple(types) => { + self.complete_tuple_fields(types); + } Type::FieldElement | Type::Array(_, _) | Type::Slice(_) @@ -985,7 +907,6 @@ impl<'a> NodeFinder<'a> { | Type::String(_) | Type::FmtString(_, _) | Type::Unit - | Type::Tuple(_) | Type::TypeVariable(_, _) | Type::TraitAsType(_, _, _) | Type::NamedGeneric(_, _, _) @@ -1020,23 +941,44 @@ impl<'a> NodeFinder<'a> { } } + fn complete_trait_methods( + &mut self, + trait_: &Trait, + prefix: &str, + function_kind: FunctionKind, + ) { + for (name, func_id) in &trait_.method_ids { + if name_matches(name, prefix) { + if let Some(completion_item) = self.function_completion_item( + *func_id, + FunctionCompletionKind::NameAndParameters, + function_kind, + ) { + self.completion_items.push(completion_item); + } + } + } + } + fn complete_struct_fields( &mut self, struct_type: &StructType, generics: &[Type], prefix: &str, ) { - for (name, typ) in struct_type.get_fields(generics) { - if name_matches(&name, prefix) { - self.completion_items.push(simple_completion_item( - name, - CompletionItemKind::FIELD, - Some(typ.to_string()), - )); + for (name, typ) in &struct_type.get_fields(generics) { + if name_matches(name, prefix) { + self.completion_items.push(struct_field_completion_item(name, typ)); } } } + fn complete_tuple_fields(&mut self, types: &[Type]) { + for (index, typ) in types.iter().enumerate() { + self.completion_items.push(field_completion_item(&index.to_string(), typ.to_string())); + } + } + #[allow(clippy::too_many_arguments)] fn complete_in_module( &mut self, diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion/completion_items.rs b/noir/noir-repo/tooling/lsp/src/requests/completion/completion_items.rs index db08dc9054a..70afc43fe55 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion/completion_items.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/completion_items.rs @@ -1,4 +1,6 @@ -use lsp_types::{CompletionItem, CompletionItemKind, CompletionItemLabelDetails, InsertTextFormat}; +use lsp_types::{ + Command, CompletionItem, CompletionItemKind, CompletionItemLabelDetails, InsertTextFormat, +}; use noirc_frontend::{ hir_def::{function::FuncMeta, stmt::HirPattern}, macros_api::{ModuleDefId, StructId}, @@ -88,7 +90,7 @@ impl<'a> NodeFinder<'a> { let func_meta = self.interner.function_meta(&func_id); let name = &self.interner.function_name(&func_id).to_string(); - let func_self_type = if let Some((pattern, typ, _)) = func_meta.parameters.0.get(0) { + let func_self_type = if let Some((pattern, typ, _)) = func_meta.parameters.0.first() { if self.hir_pattern_is_self_type(pattern) { if let Type::MutableReference(mut_typ) = typ { let typ: &Type = mut_typ; @@ -121,6 +123,14 @@ impl<'a> NodeFinder<'a> { if self_type != func_self_type { return None; } + } else if let Type::Tuple(self_tuple_types) = self_type { + // Tuple types of different lengths seem to also have methods defined on all of them, + // so here we reject methods for tuples where the length doesn't match. + if let Type::Tuple(func_self_tuple_types) = func_self_type { + if self_tuple_types.len() != func_self_tuple_types.len() { + return None; + } + } } } else { return None; @@ -164,6 +174,13 @@ impl<'a> NodeFinder<'a> { completion_item }; + let completion_item = match function_completion_kind { + FunctionCompletionKind::Name => completion_item, + FunctionCompletionKind::NameAndParameters => { + completion_item_with_trigger_parameter_hints_command(completion_item) + } + }; + Some(completion_item) } @@ -281,6 +298,14 @@ fn type_to_self_string(typ: &Type, string: &mut String) { } } +pub(super) fn struct_field_completion_item(field: &str, typ: &Type) -> CompletionItem { + field_completion_item(field, typ.to_string()) +} + +pub(super) fn field_completion_item(field: &str, typ: impl Into) -> CompletionItem { + simple_completion_item(field, CompletionItemKind::FIELD, Some(typ.into())) +} + pub(super) fn simple_completion_item( label: impl Into, kind: CompletionItemKind, @@ -342,3 +367,16 @@ pub(super) fn completion_item_with_sort_text( ) -> CompletionItem { CompletionItem { sort_text: Some(sort_text), ..completion_item } } + +pub(super) fn completion_item_with_trigger_parameter_hints_command( + completion_item: CompletionItem, +) -> CompletionItem { + CompletionItem { + command: Some(Command { + title: "Trigger parameter hints".to_string(), + command: "editor.action.triggerParameterHints".to_string(), + arguments: None, + }), + ..completion_item + } +} diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs b/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs index 1c89cc09413..ff028954f7b 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs @@ -5,8 +5,10 @@ mod completion_tests { requests::{ completion::{ completion_items::{ - completion_item_with_sort_text, crate_completion_item, module_completion_item, - simple_completion_item, snippet_completion_item, + completion_item_with_sort_text, + completion_item_with_trigger_parameter_hints_command, crate_completion_item, + field_completion_item, module_completion_item, simple_completion_item, + snippet_completion_item, }, sort_text::self_mismatch_sort_text, }, @@ -22,7 +24,7 @@ mod completion_tests { }; use tokio::test; - async fn assert_completion(src: &str, expected: Vec) { + async fn get_completions(src: &str) -> Vec { let (mut state, noir_text_document) = test_utils::init_lsp_server("document_symbol").await; let (line, column) = src @@ -48,8 +50,6 @@ mod completion_tests { }, ); - // Get inlay hints. These should now be relative to the changed text, - // not the saved file's text. let response = on_completion_request( &mut state, CompletionParams { @@ -70,10 +70,12 @@ mod completion_tests { panic!("Expected response to be CompletionResponse::Array"); }; - let mut items = items.clone(); + items + } + + fn assert_items_match(mut items: Vec, mut expected: Vec) { items.sort_by_key(|item| item.label.clone()); - let mut expected = expected.clone(); expected.sort_by_key(|item| item.label.clone()); if items != expected { @@ -90,6 +92,24 @@ mod completion_tests { assert_eq!(items, expected); } + async fn assert_completion(src: &str, expected: Vec) { + let items = get_completions(src).await; + assert_items_match(items, expected); + } + + pub(super) fn function_completion_item( + label: impl Into, + insert_text: impl Into, + description: impl Into, + ) -> CompletionItem { + completion_item_with_trigger_parameter_hints_command(snippet_completion_item( + label, + CompletionItemKind::FUNCTION, + insert_text, + Some(description.into()), + )) + } + #[test] async fn test_use_first_segment() { let src = r#" @@ -406,16 +426,7 @@ mod completion_tests { h>|< } "#; - assert_completion( - src, - vec![snippet_completion_item( - "hello()", - CompletionItemKind::FUNCTION, - "hello()", - Some("fn()".to_string()), - )], - ) - .await; + assert_completion(src, vec![function_completion_item("hello()", "hello()", "fn()")]).await; } #[test] @@ -429,11 +440,10 @@ mod completion_tests { "#; assert_completion( src, - vec![snippet_completion_item( + vec![function_completion_item( "hello(…)", - CompletionItemKind::FUNCTION, "hello(${1:x}, ${2:y})", - Some("fn(i32, Field)".to_string()), + "fn(i32, Field)".to_string(), )], ) .await; @@ -455,12 +465,7 @@ mod completion_tests { "assert(${1:predicate})", Some("fn(T)".to_string()), ), - snippet_completion_item( - "assert_constant(…)", - CompletionItemKind::FUNCTION, - "assert_constant(${1:x})", - Some("fn(T)".to_string()), - ), + function_completion_item("assert_constant(…)", "assert_constant(${1:x})", "fn(T)"), snippet_completion_item( "assert_eq(…)", CompletionItemKind::FUNCTION, @@ -967,15 +972,7 @@ mod completion_tests { s.p>|< } "#; - assert_completion( - src, - vec![simple_completion_item( - "property", - CompletionItemKind::FIELD, - Some("i32".to_string()), - )], - ) - .await; + assert_completion(src, vec![field_completion_item("property", "i32")]).await; } #[test] @@ -989,15 +986,7 @@ mod completion_tests { s.p>|< } "#; - assert_completion( - src, - vec![simple_completion_item( - "property", - CompletionItemKind::FIELD, - Some("i32".to_string()), - )], - ) - .await; + assert_completion(src, vec![field_completion_item("property", "i32")]).await; } #[test] @@ -1011,15 +1000,7 @@ mod completion_tests { s.>|< } "#; - assert_completion( - src, - vec![simple_completion_item( - "property", - CompletionItemKind::FIELD, - Some("i32".to_string()), - )], - ) - .await; + assert_completion(src, vec![field_completion_item("property", "i32")]).await; } #[test] @@ -1037,11 +1018,7 @@ mod completion_tests { some.property.>|< } "#; - assert_completion( - src, - vec![simple_completion_item("bar", CompletionItemKind::FIELD, Some("i32".to_string()))], - ) - .await; + assert_completion(src, vec![field_completion_item("bar", "i32")]).await; } #[test] @@ -1063,18 +1040,8 @@ mod completion_tests { assert_completion( src, vec![ - snippet_completion_item( - "foobar(…)", - CompletionItemKind::FUNCTION, - "foobar(${1:x})", - Some("fn(self, i32)".to_string()), - ), - snippet_completion_item( - "foobar2(…)", - CompletionItemKind::FUNCTION, - "foobar2(${1:x})", - Some("fn(&mut self, i32)".to_string()), - ), + function_completion_item("foobar(…)", "foobar(${1:x})", "fn(self, i32)"), + function_completion_item("foobar2(…)", "foobar2(${1:x})", "fn(&mut self, i32)"), ], ) .await; @@ -1102,12 +1069,7 @@ mod completion_tests { "#; assert_completion( src, - vec![snippet_completion_item( - "foobar(…)", - CompletionItemKind::FUNCTION, - "foobar(${1:x})", - Some("fn(self, i32)".to_string()), - )], + vec![function_completion_item("foobar(…)", "foobar(${1:x})", "fn(self, i32)")], ) .await; } @@ -1131,12 +1093,7 @@ mod completion_tests { "#; assert_completion( src, - vec![snippet_completion_item( - "foobar(…)", - CompletionItemKind::FUNCTION, - "foobar(${1:x})", - Some("fn(self, i32)".to_string()), - )], + vec![function_completion_item("foobar(…)", "foobar(${1:x})", "fn(self, i32)")], ) .await; } @@ -1161,29 +1118,22 @@ mod completion_tests { src, vec![ completion_item_with_sort_text( - snippet_completion_item( + function_completion_item( "foobar(…)", - CompletionItemKind::FUNCTION, "foobar(${1:self}, ${2:x})", - Some("fn(self, i32)".to_string()), + "fn(self, i32)", ), self_mismatch_sort_text(), ), completion_item_with_sort_text( - snippet_completion_item( + function_completion_item( "foobar2(…)", - CompletionItemKind::FUNCTION, "foobar2(${1:self}, ${2:x})", - Some("fn(&mut self, i32)".to_string()), + "fn(&mut self, i32)", ), self_mismatch_sort_text(), ), - snippet_completion_item( - "foobar3(…)", - CompletionItemKind::FUNCTION, - "foobar3(${1:y})", - Some("fn(i32)".to_string()), - ), + function_completion_item("foobar3(…)", "foobar3(${1:y})", "fn(i32)"), ], ) .await; @@ -1207,12 +1157,7 @@ mod completion_tests { "#; assert_completion( src, - vec![snippet_completion_item( - "foobar(…)", - CompletionItemKind::FUNCTION, - "foobar(${1:x})", - Some("fn(self, i32)".to_string()), - )], + vec![function_completion_item("foobar(…)", "foobar(${1:x})", "fn(self, i32)")], ) .await; } @@ -1239,29 +1184,22 @@ mod completion_tests { src, vec![ completion_item_with_sort_text( - snippet_completion_item( + function_completion_item( "foobar(…)", - CompletionItemKind::FUNCTION, "foobar(${1:self}, ${2:x})", - Some("fn(self, i32)".to_string()), + "fn(self, i32)", ), self_mismatch_sort_text(), ), completion_item_with_sort_text( - snippet_completion_item( + function_completion_item( "foobar2(…)", - CompletionItemKind::FUNCTION, "foobar2(${1:self}, ${2:x})", - Some("fn(&mut self, i32)".to_string()), + "fn(&mut self, i32)", ), self_mismatch_sort_text(), ), - snippet_completion_item( - "foobar3(…)", - CompletionItemKind::FUNCTION, - "foobar3(${1:y})", - Some("fn(i32)".to_string()), - ), + function_completion_item("foobar3(…)", "foobar3(${1:y})", "fn(i32)"), ], ) .await; @@ -1278,11 +1216,7 @@ mod completion_tests { if s.>|< } "#; - assert_completion( - src, - vec![simple_completion_item("foo", CompletionItemKind::FIELD, Some("i32".to_string()))], - ) - .await; + assert_completion(src, vec![field_completion_item("foo", "i32")]).await; } #[test] @@ -1295,11 +1229,7 @@ mod completion_tests { f.bar & f.>|< } "#; - assert_completion( - src, - vec![simple_completion_item("bar", CompletionItemKind::FIELD, Some("Bar".to_string()))], - ) - .await; + assert_completion(src, vec![field_completion_item("bar", "Bar")]).await; } #[test] @@ -1315,16 +1245,8 @@ mod completion_tests { f.foo().>|< } "#; - assert_completion( - src, - vec![snippet_completion_item( - "foo()", - CompletionItemKind::FUNCTION, - "foo()", - Some("fn(self) -> Foo".to_string()), - )], - ) - .await; + assert_completion(src, vec![function_completion_item("foo()", "foo()", "fn(self) -> Foo")]) + .await; } #[test] @@ -1342,10 +1264,63 @@ mod completion_tests { x = 2; } "#; + assert_completion(src, vec![field_completion_item("bar", "i32")]).await; + } + + #[test] + async fn test_completes_tuple_fields() { + let src = r#" + fn main() { + let tuple = (1, true); + tuple.>|< + } + "#; + + let items = get_completions(src).await; + let items = items.into_iter().filter(|item| item.kind == Some(CompletionItemKind::FIELD)); + let items = items.collect(); + + assert_items_match( + items, + vec![field_completion_item("0", "Field"), field_completion_item("1", "bool")], + ); + } + + #[test] + async fn test_completes_constructor_fields() { + let src = r#" + mod foobar { + struct Foo { + bb: i32, + bbb: Field, + bbbb: bool, + bbbbb: str<6>, + } + } + + fn main() { + foobar::Foo { bbb: 1, b>|<, bbbbb } + } + "#; assert_completion( src, - vec![simple_completion_item("bar", CompletionItemKind::FIELD, Some("i32".to_string()))], + vec![field_completion_item("bb", "i32"), field_completion_item("bbbb", "bool")], ) .await; } + + #[test] + async fn test_completes_trait_methods() { + let src = r#" + trait One { + fn one() -> Self; + } + + fn main() { + One::>|< + } + "#; + assert_completion(src, vec![function_completion_item("one()", "one()", "fn() -> Self")]) + .await; + } } diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion/traversal.rs b/noir/noir-repo/tooling/lsp/src/requests/completion/traversal.rs new file mode 100644 index 00000000000..b487c8baf36 --- /dev/null +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/traversal.rs @@ -0,0 +1,132 @@ +/// This file includes the completion logic that's just about +/// traversing the AST without any additional logic. +use noirc_frontend::{ + ast::{ + ArrayLiteral, AssignStatement, CallExpression, CastExpression, ConstrainStatement, + Expression, ForRange, FunctionReturnType, IndexExpression, InfixExpression, Literal, + MethodCallExpression, NoirTrait, NoirTypeAlias, TraitImplItem, UnresolvedType, + }, + ParsedModule, +}; + +use super::NodeFinder; + +impl<'a> NodeFinder<'a> { + pub(super) fn find_in_parsed_module(&mut self, parsed_module: &ParsedModule) { + for item in &parsed_module.items { + self.find_in_item(item); + } + } + + pub(super) fn find_in_trait_impl_item(&mut self, item: &TraitImplItem) { + match item { + TraitImplItem::Function(noir_function) => self.find_in_noir_function(noir_function), + TraitImplItem::Constant(_, _, _) => (), + TraitImplItem::Type { .. } => (), + } + } + + pub(super) fn find_in_noir_trait(&mut self, noir_trait: &NoirTrait) { + for item in &noir_trait.items { + self.find_in_trait_item(item); + } + } + + pub(super) fn find_in_constrain_statement(&mut self, constrain_statement: &ConstrainStatement) { + self.find_in_expression(&constrain_statement.0); + + if let Some(exp) = &constrain_statement.1 { + self.find_in_expression(exp); + } + } + + pub(super) fn find_in_assign_statement(&mut self, assign_statement: &AssignStatement) { + self.find_in_lvalue(&assign_statement.lvalue); + self.find_in_expression(&assign_statement.expression); + } + + pub(super) fn find_in_for_range(&mut self, for_range: &ForRange) { + match for_range { + ForRange::Range(start, end) => { + self.find_in_expression(start); + self.find_in_expression(end); + } + ForRange::Array(expression) => self.find_in_expression(expression), + } + } + + pub(super) fn find_in_expressions(&mut self, expressions: &[Expression]) { + for expression in expressions { + self.find_in_expression(expression); + } + } + + pub(super) fn find_in_literal(&mut self, literal: &Literal) { + match literal { + Literal::Array(array_literal) => self.find_in_array_literal(array_literal), + Literal::Slice(array_literal) => self.find_in_array_literal(array_literal), + Literal::Bool(_) + | Literal::Integer(_, _) + | Literal::Str(_) + | Literal::RawStr(_, _) + | Literal::FmtStr(_) + | Literal::Unit => (), + } + } + + pub(super) fn find_in_array_literal(&mut self, array_literal: &ArrayLiteral) { + match array_literal { + ArrayLiteral::Standard(expressions) => self.find_in_expressions(expressions), + ArrayLiteral::Repeated { repeated_element, length } => { + self.find_in_expression(repeated_element); + self.find_in_expression(length); + } + } + } + + pub(super) fn find_in_index_expression(&mut self, index_expression: &IndexExpression) { + self.find_in_expression(&index_expression.collection); + self.find_in_expression(&index_expression.index); + } + + pub(super) fn find_in_call_expression(&mut self, call_expression: &CallExpression) { + self.find_in_expression(&call_expression.func); + self.find_in_expressions(&call_expression.arguments); + } + + pub(super) fn find_in_method_call_expression( + &mut self, + method_call_expression: &MethodCallExpression, + ) { + self.find_in_expression(&method_call_expression.object); + self.find_in_expressions(&method_call_expression.arguments); + } + + pub(super) fn find_in_cast_expression(&mut self, cast_expression: &CastExpression) { + self.find_in_expression(&cast_expression.lhs); + } + + pub(super) fn find_in_infix_expression(&mut self, infix_expression: &InfixExpression) { + self.find_in_expression(&infix_expression.lhs); + self.find_in_expression(&infix_expression.rhs); + } + + pub(super) fn find_in_unresolved_types(&mut self, unresolved_type: &[UnresolvedType]) { + for unresolved_type in unresolved_type { + self.find_in_unresolved_type(unresolved_type); + } + } + + pub(super) fn find_in_function_return_type(&mut self, return_type: &FunctionReturnType) { + match return_type { + noirc_frontend::ast::FunctionReturnType::Default(_) => (), + noirc_frontend::ast::FunctionReturnType::Ty(unresolved_type) => { + self.find_in_unresolved_type(unresolved_type); + } + } + } + + pub(super) fn find_in_noir_type_alias(&mut self, noir_type_alias: &NoirTypeAlias) { + self.find_in_unresolved_type(&noir_type_alias.typ); + } +} diff --git a/noir/noir-repo/tooling/lsp/src/requests/mod.rs b/noir/noir-repo/tooling/lsp/src/requests/mod.rs index b6c26110e59..e88c7f11465 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/mod.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/mod.rs @@ -46,6 +46,7 @@ mod inlay_hint; mod profile_run; mod references; mod rename; +mod signature_help; mod test_run; mod tests; @@ -56,7 +57,8 @@ pub(crate) use { 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, + rename::on_rename_request, signature_help::on_signature_help_request, + test_run::on_test_run_request, tests::on_tests_request, }; /// LSP client will send initialization request after the server has started. @@ -241,6 +243,15 @@ pub(crate) fn on_initialize( }, completion_item: None, })), + signature_help_provider: Some(lsp_types::OneOf::Right( + lsp_types::SignatureHelpOptions { + trigger_characters: Some(vec!["(".to_string(), ",".to_string()]), + retrigger_characters: None, + work_done_progress_options: WorkDoneProgressOptions { + work_done_progress: None, + }, + }, + )), }, server_info: None, }) diff --git a/noir/noir-repo/tooling/lsp/src/requests/signature_help.rs b/noir/noir-repo/tooling/lsp/src/requests/signature_help.rs new file mode 100644 index 00000000000..c2c69185547 --- /dev/null +++ b/noir/noir-repo/tooling/lsp/src/requests/signature_help.rs @@ -0,0 +1,291 @@ +use std::future::{self, Future}; + +use async_lsp::ResponseError; +use fm::{FileId, PathString}; +use lsp_types::{ + ParameterInformation, ParameterLabel, SignatureHelp, SignatureHelpParams, SignatureInformation, +}; +use noirc_errors::{Location, Span}; +use noirc_frontend::{ + ast::{CallExpression, Expression, FunctionReturnType, MethodCallExpression}, + hir_def::{function::FuncMeta, stmt::HirPattern}, + macros_api::NodeInterner, + node_interner::ReferenceId, + ParsedModule, Type, +}; + +use crate::{utils, LspState}; + +use super::process_request; + +mod tests; +mod traversal; + +pub(crate) fn on_signature_help_request( + state: &mut LspState, + params: SignatureHelpParams, +) -> impl Future, ResponseError>> { + let uri = params.text_document_position_params.clone().text_document.uri; + + let result = process_request(state, params.text_document_position_params.clone(), |args| { + let path = PathString::from_path(uri.to_file_path().unwrap()); + args.files.get_file_id(&path).and_then(|file_id| { + utils::position_to_byte_index( + args.files, + file_id, + ¶ms.text_document_position_params.position, + ) + .and_then(|byte_index| { + let file = args.files.get_file(file_id).unwrap(); + let source = file.source(); + let (parsed_module, _errors) = noirc_frontend::parse_program(source); + + let mut finder = SignatureFinder::new(file_id, byte_index, args.interner); + finder.find(&parsed_module) + }) + }) + }); + future::ready(result) +} + +struct SignatureFinder<'a> { + file: FileId, + byte_index: usize, + interner: &'a NodeInterner, + signature_help: Option, +} + +impl<'a> SignatureFinder<'a> { + fn new(file: FileId, byte_index: usize, interner: &'a NodeInterner) -> Self { + Self { file, byte_index, interner, signature_help: None } + } + + fn find(&mut self, parsed_module: &ParsedModule) -> Option { + self.find_in_parsed_module(parsed_module); + + self.signature_help.clone() + } + + fn find_in_call_expression(&mut self, call_expression: &CallExpression, span: Span) { + self.find_in_expression(&call_expression.func); + self.find_in_expressions(&call_expression.arguments); + + let arguments_span = Span::from(call_expression.func.span.end() + 1..span.end() - 1); + let span = call_expression.func.span; + let name_span = Span::from(span.end() - 1..span.end()); + let has_self = false; + + self.try_compute_signature_help( + &call_expression.arguments, + arguments_span, + name_span, + has_self, + ); + } + + fn find_in_method_call_expression( + &mut self, + method_call_expression: &MethodCallExpression, + span: Span, + ) { + self.find_in_expression(&method_call_expression.object); + self.find_in_expressions(&method_call_expression.arguments); + + let arguments_span = + Span::from(method_call_expression.method_name.span().end() + 1..span.end() - 1); + let name_span = method_call_expression.method_name.span(); + let has_self = true; + + self.try_compute_signature_help( + &method_call_expression.arguments, + arguments_span, + name_span, + has_self, + ); + } + + fn try_compute_signature_help( + &mut self, + arguments: &[Expression], + arguments_span: Span, + name_span: Span, + has_self: bool, + ) { + if self.signature_help.is_some() { + return; + } + + if !self.includes_span(arguments_span) { + return; + } + + let mut active_parameter = None; + for (index, arg) in arguments.iter().enumerate() { + if self.includes_span(arg.span) || arg.span.start() as usize >= self.byte_index { + active_parameter = Some(index as u32); + break; + } + } + + if active_parameter.is_none() { + active_parameter = Some(arguments.len() as u32); + } + + let location = Location::new(name_span, self.file); + + // Check if the call references a named function + if let Some(ReferenceId::Function(func_id)) = self.interner.find_referenced(location) { + let name = self.interner.function_name(&func_id); + let func_meta = self.interner.function_meta(&func_id); + + let signature_information = + self.func_meta_signature_information(func_meta, name, active_parameter, has_self); + self.set_signature_help(signature_information); + return; + } + + // Otherwise, the call must be a reference to an fn type + if let Some(mut typ) = self.interner.type_at_location(location) { + if let Type::Forall(_, forall_typ) = typ { + typ = *forall_typ; + } + if let Type::Function(args, return_type, _, unconstrained) = typ { + let signature_information = self.function_type_signature_information( + &args, + &return_type, + unconstrained, + active_parameter, + ); + self.set_signature_help(signature_information); + } + } + } + + fn func_meta_signature_information( + &self, + func_meta: &FuncMeta, + name: &str, + active_parameter: Option, + has_self: bool, + ) -> SignatureInformation { + let mut label = String::new(); + let mut parameters = Vec::new(); + + label.push_str("fn "); + label.push_str(name); + label.push('('); + for (index, (pattern, typ, _)) in func_meta.parameters.0.iter().enumerate() { + if index > 0 { + label.push_str(", "); + } + + if has_self && index == 0 { + if let Type::MutableReference(..) = typ { + label.push_str("&mut self"); + } else { + label.push_str("self"); + } + } else { + let parameter_start = label.chars().count(); + + self.hir_pattern_to_argument(pattern, &mut label); + label.push_str(": "); + label.push_str(&typ.to_string()); + + let parameter_end = label.chars().count(); + + parameters.push(ParameterInformation { + label: ParameterLabel::LabelOffsets([ + parameter_start as u32, + parameter_end as u32, + ]), + documentation: None, + }); + } + } + label.push(')'); + + match &func_meta.return_type { + FunctionReturnType::Default(_) => (), + FunctionReturnType::Ty(typ) => { + label.push_str(" -> "); + label.push_str(&typ.to_string()); + } + } + + SignatureInformation { + label, + documentation: None, + parameters: Some(parameters), + active_parameter, + } + } + + fn function_type_signature_information( + &self, + args: &[Type], + return_type: &Type, + unconstrained: bool, + active_parameter: Option, + ) -> SignatureInformation { + let mut label = String::new(); + let mut parameters = Vec::new(); + + if unconstrained { + label.push_str("unconstrained "); + } + label.push_str("fn("); + for (index, typ) in args.iter().enumerate() { + if index > 0 { + label.push_str(", "); + } + + let parameter_start = label.chars().count(); + label.push_str(&typ.to_string()); + let parameter_end = label.chars().count(); + + parameters.push(ParameterInformation { + label: ParameterLabel::LabelOffsets([parameter_start as u32, parameter_end as u32]), + documentation: None, + }); + } + label.push(')'); + + if let Type::Unit = return_type { + // Nothing + } else { + label.push_str(" -> "); + label.push_str(&return_type.to_string()); + } + + SignatureInformation { + label, + documentation: None, + parameters: Some(parameters), + active_parameter, + } + } + + fn hir_pattern_to_argument(&self, pattern: &HirPattern, text: &mut String) { + match pattern { + HirPattern::Identifier(hir_ident) => { + text.push_str(self.interner.definition_name(hir_ident.id)); + } + HirPattern::Mutable(pattern, _) => self.hir_pattern_to_argument(pattern, text), + HirPattern::Tuple(_, _) | HirPattern::Struct(_, _, _) => text.push('_'), + } + } + + fn set_signature_help(&mut self, signature_information: SignatureInformation) { + let signature_help = SignatureHelp { + active_parameter: signature_information.active_parameter, + signatures: vec![signature_information], + active_signature: Some(0), + }; + self.signature_help = Some(signature_help); + } + + fn includes_span(&self, span: Span) -> bool { + span.start() as usize <= self.byte_index && self.byte_index <= span.end() as usize + } +} diff --git a/noir/noir-repo/tooling/lsp/src/requests/signature_help/tests.rs b/noir/noir-repo/tooling/lsp/src/requests/signature_help/tests.rs new file mode 100644 index 00000000000..c48ee159084 --- /dev/null +++ b/noir/noir-repo/tooling/lsp/src/requests/signature_help/tests.rs @@ -0,0 +1,196 @@ +#[cfg(test)] +mod signature_help_tests { + use crate::{ + notifications::on_did_open_text_document, requests::on_signature_help_request, test_utils, + }; + + use lsp_types::{ + DidOpenTextDocumentParams, ParameterLabel, Position, SignatureHelp, SignatureHelpParams, + TextDocumentIdentifier, TextDocumentItem, TextDocumentPositionParams, + WorkDoneProgressParams, + }; + use tokio::test; + + async fn get_signature_help(src: &str) -> SignatureHelp { + let (mut state, noir_text_document) = test_utils::init_lsp_server("document_symbol").await; + + let (line, column) = src + .lines() + .enumerate() + .find_map(|(line_index, line)| { + line.find(">|<").map(|char_index| (line_index, char_index)) + }) + .expect("Expected to find one >|< in the source code"); + + let src = src.replace(">|<", ""); + + on_did_open_text_document( + &mut state, + DidOpenTextDocumentParams { + text_document: TextDocumentItem { + uri: noir_text_document.clone(), + language_id: "noir".to_string(), + version: 0, + text: src.to_string(), + }, + }, + ); + + on_signature_help_request( + &mut state, + SignatureHelpParams { + context: None, + text_document_position_params: TextDocumentPositionParams { + text_document: TextDocumentIdentifier { uri: noir_text_document }, + position: Position { line: line as u32, character: column as u32 }, + }, + work_done_progress_params: WorkDoneProgressParams { work_done_token: None }, + }, + ) + .await + .expect("Could not execute on_signature_help_request") + .unwrap() + } + + fn check_label(signature_label: &str, parameter_label: &ParameterLabel, expected_string: &str) { + let ParameterLabel::LabelOffsets(offsets) = parameter_label else { + panic!("Expected label to be LabelOffsets, got {:?}", parameter_label); + }; + + assert_eq!(&signature_label[offsets[0] as usize..offsets[1] as usize], expected_string); + } + + #[test] + async fn test_signature_help_for_call_at_first_argument() { + let src = r#" + fn foo(x: i32, y: Field) -> u32 { 0 } + fn wrapper(x: u32) {} + + fn bar() { + wrapper(foo(>|<1, 2)); + } + "#; + + let signature_help = get_signature_help(src).await; + assert_eq!(signature_help.signatures.len(), 1); + + let signature = &signature_help.signatures[0]; + assert_eq!(signature.label, "fn foo(x: i32, y: Field) -> u32"); + + let params = signature.parameters.as_ref().unwrap(); + assert_eq!(params.len(), 2); + + check_label(&signature.label, ¶ms[0].label, "x: i32"); + check_label(&signature.label, ¶ms[1].label, "y: Field"); + + assert_eq!(signature.active_parameter, Some(0)); + } + + #[test] + async fn test_signature_help_for_call_between_arguments() { + let src = r#" + fn foo(x: i32, y: Field) -> u32 { 0 } + + fn bar() { + foo(1,>|< 2); + } + "#; + + let signature_help = get_signature_help(src).await; + assert_eq!(signature_help.signatures.len(), 1); + + let signature = &signature_help.signatures[0]; + assert_eq!(signature.active_parameter, Some(1)); + } + + #[test] + async fn test_signature_help_for_call_at_second_argument() { + let src = r#" + fn foo(x: i32, y: Field) -> u32 { 0 } + + fn bar() { + foo(1, >|<2); + } + "#; + + let signature_help = get_signature_help(src).await; + assert_eq!(signature_help.signatures.len(), 1); + + let signature = &signature_help.signatures[0]; + assert_eq!(signature.active_parameter, Some(1)); + } + + #[test] + async fn test_signature_help_for_call_past_last_argument() { + let src = r#" + fn foo(x: i32, y: Field) -> u32 { 0 } + + fn bar() { + foo(1, 2, >|<); + } + "#; + + let signature_help = get_signature_help(src).await; + assert_eq!(signature_help.signatures.len(), 1); + + let signature = &signature_help.signatures[0]; + assert_eq!(signature.active_parameter, Some(2)); + } + + #[test] + async fn test_signature_help_for_method_call() { + let src = r#" + struct Foo {} + + impl Foo { + fn foo(self, x: i32, y: Field) -> u32 { 0 } + } + + fn wrapper(x: u32) {} + + fn bar(f: Foo) { + wrapper(f.foo(>|<1, 2)); + } + "#; + + let signature_help = get_signature_help(src).await; + assert_eq!(signature_help.signatures.len(), 1); + + let signature = &signature_help.signatures[0]; + assert_eq!(signature.label, "fn foo(self, x: i32, y: Field) -> u32"); + + let params = signature.parameters.as_ref().unwrap(); + assert_eq!(params.len(), 2); + + check_label(&signature.label, ¶ms[0].label, "x: i32"); + check_label(&signature.label, ¶ms[1].label, "y: Field"); + + assert_eq!(signature.active_parameter, Some(0)); + } + + #[test] + async fn test_signature_help_for_fn_call() { + let src = r#" + fn foo(x: i32, y: Field) -> u32 { 0 } + + fn bar() { + let f = foo; + f(>|<1, 2); + } + "#; + + let signature_help = get_signature_help(src).await; + assert_eq!(signature_help.signatures.len(), 1); + + let signature = &signature_help.signatures[0]; + assert_eq!(signature.label, "fn(i32, Field) -> u32"); + + let params = signature.parameters.as_ref().unwrap(); + assert_eq!(params.len(), 2); + + check_label(&signature.label, ¶ms[0].label, "i32"); + check_label(&signature.label, ¶ms[1].label, "Field"); + + assert_eq!(signature.active_parameter, Some(0)); + } +} diff --git a/noir/noir-repo/tooling/lsp/src/requests/signature_help/traversal.rs b/noir/noir-repo/tooling/lsp/src/requests/signature_help/traversal.rs new file mode 100644 index 00000000000..22f92a86124 --- /dev/null +++ b/noir/noir-repo/tooling/lsp/src/requests/signature_help/traversal.rs @@ -0,0 +1,304 @@ +/// This file includes the signature help logic that's just about +/// traversing the AST without any additional logic. +use super::SignatureFinder; + +use noirc_frontend::{ + ast::{ + ArrayLiteral, AssignStatement, BlockExpression, CastExpression, ConstrainStatement, + ConstructorExpression, Expression, ExpressionKind, ForLoopStatement, ForRange, + IfExpression, IndexExpression, InfixExpression, LValue, Lambda, LetStatement, Literal, + MemberAccessExpression, NoirFunction, NoirTrait, NoirTraitImpl, Statement, StatementKind, + TraitImplItem, TraitItem, TypeImpl, + }, + parser::{Item, ItemKind}, + ParsedModule, +}; + +impl<'a> SignatureFinder<'a> { + pub(super) fn find_in_parsed_module(&mut self, parsed_module: &ParsedModule) { + for item in &parsed_module.items { + self.find_in_item(item); + } + } + + pub(super) fn find_in_item(&mut self, item: &Item) { + if !self.includes_span(item.span) { + return; + } + + match &item.kind { + ItemKind::Submodules(parsed_sub_module) => { + self.find_in_parsed_module(&parsed_sub_module.contents); + } + ItemKind::Function(noir_function) => self.find_in_noir_function(noir_function), + ItemKind::TraitImpl(noir_trait_impl) => self.find_in_noir_trait_impl(noir_trait_impl), + ItemKind::Impl(type_impl) => self.find_in_type_impl(type_impl), + ItemKind::Global(let_statement) => self.find_in_let_statement(let_statement), + ItemKind::Trait(noir_trait) => self.find_in_noir_trait(noir_trait), + ItemKind::Import(..) + | ItemKind::TypeAlias(_) + | ItemKind::Struct(_) + | ItemKind::ModuleDecl(_) => (), + } + } + + pub(super) fn find_in_noir_function(&mut self, noir_function: &NoirFunction) { + self.find_in_block_expression(&noir_function.def.body); + } + + pub(super) fn find_in_noir_trait_impl(&mut self, noir_trait_impl: &NoirTraitImpl) { + for item in &noir_trait_impl.items { + self.find_in_trait_impl_item(item); + } + } + + pub(super) fn find_in_trait_impl_item(&mut self, item: &TraitImplItem) { + match item { + TraitImplItem::Function(noir_function) => self.find_in_noir_function(noir_function), + TraitImplItem::Constant(_, _, _) => (), + TraitImplItem::Type { .. } => (), + } + } + + pub(super) fn find_in_type_impl(&mut self, type_impl: &TypeImpl) { + for (method, span) in &type_impl.methods { + if self.includes_span(*span) { + self.find_in_noir_function(method); + } + } + } + + pub(super) fn find_in_noir_trait(&mut self, noir_trait: &NoirTrait) { + for item in &noir_trait.items { + self.find_in_trait_item(item); + } + } + + pub(super) fn find_in_trait_item(&mut self, trait_item: &TraitItem) { + match trait_item { + TraitItem::Function { body, .. } => { + if let Some(body) = body { + self.find_in_block_expression(body); + }; + } + TraitItem::Constant { default_value, .. } => { + if let Some(default_value) = default_value { + self.find_in_expression(default_value); + } + } + TraitItem::Type { .. } => (), + } + } + + pub(super) fn find_in_block_expression(&mut self, block_expression: &BlockExpression) { + for statement in &block_expression.statements { + if self.includes_span(statement.span) { + self.find_in_statement(statement); + } + } + } + + pub(super) fn find_in_statement(&mut self, statement: &Statement) { + if !self.includes_span(statement.span) { + return; + } + + match &statement.kind { + StatementKind::Let(let_statement) => { + self.find_in_let_statement(let_statement); + } + StatementKind::Constrain(constrain_statement) => { + self.find_in_constrain_statement(constrain_statement); + } + StatementKind::Expression(expression) => { + self.find_in_expression(expression); + } + StatementKind::Assign(assign_statement) => { + self.find_in_assign_statement(assign_statement); + } + StatementKind::For(for_loop_statement) => { + self.find_in_for_loop_statement(for_loop_statement); + } + StatementKind::Comptime(statement) => { + self.find_in_statement(statement); + } + StatementKind::Semi(expression) => { + self.find_in_expression(expression); + } + StatementKind::Break | StatementKind::Continue | StatementKind::Error => (), + } + } + + pub(super) fn find_in_let_statement(&mut self, let_statement: &LetStatement) { + self.find_in_expression(&let_statement.expression); + } + + pub(super) fn find_in_constrain_statement(&mut self, constrain_statement: &ConstrainStatement) { + self.find_in_expression(&constrain_statement.0); + + if let Some(exp) = &constrain_statement.1 { + self.find_in_expression(exp); + } + } + + pub(super) fn find_in_assign_statement(&mut self, assign_statement: &AssignStatement) { + self.find_in_lvalue(&assign_statement.lvalue); + self.find_in_expression(&assign_statement.expression); + } + + pub(super) fn find_in_for_loop_statement(&mut self, for_loop_statement: &ForLoopStatement) { + self.find_in_for_range(&for_loop_statement.range); + self.find_in_expression(&for_loop_statement.block); + } + + pub(super) fn find_in_lvalue(&mut self, lvalue: &LValue) { + match lvalue { + LValue::Ident(_) => (), + LValue::MemberAccess { object, field_name: _, span: _ } => self.find_in_lvalue(object), + LValue::Index { array, index, span: _ } => { + self.find_in_lvalue(array); + self.find_in_expression(index); + } + LValue::Dereference(lvalue, _) => self.find_in_lvalue(lvalue), + } + } + + pub(super) fn find_in_for_range(&mut self, for_range: &ForRange) { + match for_range { + ForRange::Range(start, end) => { + self.find_in_expression(start); + self.find_in_expression(end); + } + ForRange::Array(expression) => self.find_in_expression(expression), + } + } + + pub(super) fn find_in_expressions(&mut self, expressions: &[Expression]) { + for expression in expressions { + self.find_in_expression(expression); + } + } + + pub(super) fn find_in_expression(&mut self, expression: &Expression) { + match &expression.kind { + ExpressionKind::Literal(literal) => self.find_in_literal(literal), + ExpressionKind::Block(block_expression) => { + self.find_in_block_expression(block_expression); + } + ExpressionKind::Prefix(prefix_expression) => { + self.find_in_expression(&prefix_expression.rhs); + } + ExpressionKind::Index(index_expression) => { + self.find_in_index_expression(index_expression); + } + ExpressionKind::Call(call_expression) => { + self.find_in_call_expression(call_expression, expression.span); + } + ExpressionKind::MethodCall(method_call_expression) => { + self.find_in_method_call_expression(method_call_expression, expression.span); + } + ExpressionKind::Constructor(constructor_expression) => { + self.find_in_constructor_expression(constructor_expression); + } + ExpressionKind::MemberAccess(member_access_expression) => { + self.find_in_member_access_expression(member_access_expression); + } + ExpressionKind::Cast(cast_expression) => { + self.find_in_cast_expression(cast_expression); + } + ExpressionKind::Infix(infix_expression) => { + self.find_in_infix_expression(infix_expression); + } + ExpressionKind::If(if_expression) => { + self.find_in_if_expression(if_expression); + } + ExpressionKind::Tuple(expressions) => { + self.find_in_expressions(expressions); + } + ExpressionKind::Lambda(lambda) => self.find_in_lambda(lambda), + ExpressionKind::Parenthesized(expression) => { + self.find_in_expression(expression); + } + ExpressionKind::Unquote(expression) => { + self.find_in_expression(expression); + } + ExpressionKind::Comptime(block_expression, _) => { + self.find_in_block_expression(block_expression); + } + ExpressionKind::Unsafe(block_expression, _) => { + self.find_in_block_expression(block_expression); + } + ExpressionKind::Variable(_) + | ExpressionKind::AsTraitPath(_) + | ExpressionKind::Quote(_) + | ExpressionKind::Resolved(_) + | ExpressionKind::Error => (), + } + } + + pub(super) fn find_in_literal(&mut self, literal: &Literal) { + match literal { + Literal::Array(array_literal) => self.find_in_array_literal(array_literal), + Literal::Slice(array_literal) => self.find_in_array_literal(array_literal), + Literal::Bool(_) + | Literal::Integer(_, _) + | Literal::Str(_) + | Literal::RawStr(_, _) + | Literal::FmtStr(_) + | Literal::Unit => (), + } + } + + pub(super) fn find_in_array_literal(&mut self, array_literal: &ArrayLiteral) { + match array_literal { + ArrayLiteral::Standard(expressions) => self.find_in_expressions(expressions), + ArrayLiteral::Repeated { repeated_element, length } => { + self.find_in_expression(repeated_element); + self.find_in_expression(length); + } + } + } + + pub(super) fn find_in_index_expression(&mut self, index_expression: &IndexExpression) { + self.find_in_expression(&index_expression.collection); + self.find_in_expression(&index_expression.index); + } + + pub(super) fn find_in_constructor_expression( + &mut self, + constructor_expression: &ConstructorExpression, + ) { + for (_field_name, expression) in &constructor_expression.fields { + self.find_in_expression(expression); + } + } + + pub(super) fn find_in_member_access_expression( + &mut self, + member_access_expression: &MemberAccessExpression, + ) { + self.find_in_expression(&member_access_expression.lhs); + } + + pub(super) fn find_in_cast_expression(&mut self, cast_expression: &CastExpression) { + self.find_in_expression(&cast_expression.lhs); + } + + pub(super) fn find_in_infix_expression(&mut self, infix_expression: &InfixExpression) { + self.find_in_expression(&infix_expression.lhs); + self.find_in_expression(&infix_expression.rhs); + } + + pub(super) fn find_in_if_expression(&mut self, if_expression: &IfExpression) { + self.find_in_expression(&if_expression.condition); + self.find_in_expression(&if_expression.consequence); + + if let Some(alternative) = &if_expression.alternative { + self.find_in_expression(alternative); + } + } + + pub(super) fn find_in_lambda(&mut self, lambda: &Lambda) { + self.find_in_expression(&lambda.body); + } +} diff --git a/noir/noir-repo/tooling/lsp/src/types.rs b/noir/noir-repo/tooling/lsp/src/types.rs index 5afda0d292a..3ac1f35e18e 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::{ CompletionOptions, DeclarationCapability, DefinitionOptions, DocumentSymbolOptions, - HoverOptions, InlayHintOptions, OneOf, ReferencesOptions, RenameOptions, + HoverOptions, InlayHintOptions, OneOf, ReferencesOptions, RenameOptions, SignatureHelpOptions, TypeDefinitionProviderCapability, }; use noirc_driver::DebugFile; @@ -161,6 +161,10 @@ pub(crate) struct ServerCapabilities { /// The server provides completion support. #[serde(skip_serializing_if = "Option::is_none")] pub(crate) completion_provider: Option>, + + /// The server provides signature help support. + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) signature_help_provider: Option>, } #[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)] 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 a6395d1c8c9..2fd189172d8 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 @@ -100,8 +100,7 @@ pub(crate) fn run(args: InfoCommand, config: NargoConfig) -> Result<(), CliError } else { // Otherwise print human-readable table. if !info_report.programs.is_empty() { - let mut program_table = - table!([Fm->"Package", Fm->"Function", Fm->"Expression Width", Fm->"ACIR Opcodes"]); + let mut program_table = table!([Fm->"Package", Fm->"Function", Fm->"Expression Width", Fm->"ACIR Opcodes", Fm->"Brillig Opcodes"]); for program_info in info_report.programs { let program_rows: Vec = program_info.into(); @@ -176,18 +175,31 @@ struct ProgramInfo { #[serde(skip)] expression_width: ExpressionWidth, functions: Vec, + unconstrained_functions_opcodes: usize, + unconstrained_functions: Vec, } impl From for Vec { fn from(program_info: ProgramInfo) -> Self { - vecmap(program_info.functions, |function| { + let mut main = vecmap(program_info.functions, |function| { row![ Fm->format!("{}", program_info.package_name), Fc->format!("{}", function.name), format!("{:?}", program_info.expression_width), Fc->format!("{}", function.acir_opcodes), + Fc->format!("{}", program_info.unconstrained_functions_opcodes), ] - }) + }); + main.extend(vecmap(program_info.unconstrained_functions, |function| { + row![ + Fm->format!("{}", program_info.package_name), + Fc->format!("{}", function.name), + format!("N/A", ), + Fc->format!("N/A"), + Fc->format!("{}", function.acir_opcodes), + ] + })); + main } } @@ -235,5 +247,31 @@ fn count_opcodes_and_gates_in_program( }) .collect(); - ProgramInfo { package_name: package.name.to_string(), expression_width, functions } + let opcodes_len: Vec = compiled_program + .bytecode + .unconstrained_functions + .iter() + .map(|func| func.bytecode.len()) + .collect(); + let unconstrained_functions_opcodes = compiled_program + .bytecode + .unconstrained_functions + .into_par_iter() + .map(|function| function.bytecode.len()) + .sum(); + let unconstrained_info: Vec = compiled_program + .brillig_names + .clone() + .iter() + .zip(opcodes_len) + .map(|(name, len)| FunctionInfo { name: name.clone(), acir_opcodes: len }) + .collect(); + + ProgramInfo { + package_name: package.name.to_string(), + expression_width, + functions, + unconstrained_functions_opcodes, + unconstrained_functions: unconstrained_info, + } } diff --git a/noir/noir-repo/tooling/noirc_artifacts/src/program.rs b/noir/noir-repo/tooling/noirc_artifacts/src/program.rs index 91f02157414..ffffc6345d5 100644 --- a/noir/noir-repo/tooling/noirc_artifacts/src/program.rs +++ b/noir/noir-repo/tooling/noirc_artifacts/src/program.rs @@ -37,6 +37,8 @@ pub struct ProgramArtifact { pub file_map: BTreeMap, pub names: Vec, + /// Names of the unconstrained functions in the program. + pub brillig_names: Vec, } impl From for ProgramArtifact { @@ -49,6 +51,7 @@ impl From for ProgramArtifact { debug_symbols: ProgramDebugInfo { debug_infos: compiled_program.debug }, file_map: compiled_program.file_map, names: compiled_program.names, + brillig_names: compiled_program.brillig_names, } } } @@ -64,6 +67,7 @@ impl From for CompiledProgram { file_map: program.file_map, warnings: vec![], names: program.names, + brillig_names: program.brillig_names, } } } diff --git a/noir/noir-repo/tooling/profiler/src/cli/gates_flamegraph_cmd.rs b/noir/noir-repo/tooling/profiler/src/cli/gates_flamegraph_cmd.rs index e072c63f274..7c6e77917a5 100644 --- a/noir/noir-repo/tooling/profiler/src/cli/gates_flamegraph_cmd.rs +++ b/noir/noir-repo/tooling/profiler/src/cli/gates_flamegraph_cmd.rs @@ -173,6 +173,7 @@ mod tests { debug_symbols: ProgramDebugInfo { debug_infos: vec![DebugInfo::default()] }, file_map: BTreeMap::default(), names: vec!["main".to_string()], + brillig_names: Vec::new(), }; // Write the artifact to a file diff --git a/noir/noir-repo/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs b/noir/noir-repo/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs index f91e50d8716..d7f3cbb9b85 100644 --- a/noir/noir-repo/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs +++ b/noir/noir-repo/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs @@ -188,6 +188,7 @@ mod tests { debug_symbols: ProgramDebugInfo { debug_infos: vec![DebugInfo::default()] }, file_map: BTreeMap::default(), names: vec!["main".to_string()], + brillig_names: Vec::new(), }; // Write the artifact to a file @@ -228,6 +229,7 @@ mod tests { debug_symbols: ProgramDebugInfo { debug_infos: vec![DebugInfo::default()] }, file_map: BTreeMap::default(), names: vec!["main".to_string()], + brillig_names: vec!["main".to_string()], }; // Write the artifact to a file