diff --git a/.noir-sync-commit b/.noir-sync-commit index 08c4705c49d..8f0c7e675db 100644 --- a/.noir-sync-commit +++ b/.noir-sync-commit @@ -1 +1 @@ -78262c96d5b116c77e50653f9059da60824db812 +2174ffb92b5d88e7e0926c91f42bc7f849e8ddc1 \ No newline at end of file diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr index bf12d310027..77a885c5eb7 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr @@ -290,11 +290,11 @@ global TUBE_PROOF_LENGTH = RECURSIVE_PROOF_LENGTH; // in the future these can di global VERIFICATION_KEY_LENGTH_IN_FIELDS = 128; // The length is 2 + AvmFlavor::NUM_PRECOMPUTED_ENTITIES * 4 -global AVM_VERIFICATION_KEY_LENGTH_IN_FIELDS = 2 + 16 * 4; +global AVM_VERIFICATION_KEY_LENGTH_IN_FIELDS: u32 = 2 + 16 * 4; // `AVM_PROOF_LENGTH_IN_FIELDS` must be updated when AVM circuit changes. // To determine latest value, hover `COMPUTED_AVM_PROOF_LENGTH_IN_FIELDS` // in barretenberg/cpp/src/barretenberg/vm/avm/generated/flavor.hpp -global AVM_PROOF_LENGTH_IN_FIELDS = 3812; +global AVM_PROOF_LENGTH_IN_FIELDS: u32 = 3812; /** * Enumerate the hash_indices which are used for pedersen hashing. * We start from 1 to avoid the default generators. The generator indices are listed 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 b5d04e87e0a..9ca260d1b95 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/note_interface.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/note_interface.rs @@ -769,6 +769,7 @@ pub fn inject_note_exports( note.borrow().id, "get_note_type_id", false, + true, ) .ok_or(( AztecMacroError::CouldNotExportStorageLayout { diff --git a/noir/noir-repo/aztec_macros/src/transforms/storage.rs b/noir/noir-repo/aztec_macros/src/transforms/storage.rs index d056dfc4412..e8ee1675546 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/storage.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/storage.rs @@ -360,6 +360,7 @@ pub fn assign_storage_slots( storage_struct.borrow().id, "init", false, + true, ) .ok_or(( AztecMacroError::CouldNotAssignStorageSlots { diff --git a/noir/noir-repo/aztec_macros/src/utils/parse_utils.rs b/noir/noir-repo/aztec_macros/src/utils/parse_utils.rs index 61410bbb5b2..efa31860b6e 100644 --- a/noir/noir-repo/aztec_macros/src/utils/parse_utils.rs +++ b/noir/noir-repo/aztec_macros/src/utils/parse_utils.rs @@ -295,6 +295,12 @@ fn empty_expression(expression: &mut Expression) { empty_unresolved_type(&mut path.typ); empty_path(&mut path.trait_path); empty_ident(&mut path.impl_item); + empty_type_args(&mut path.trait_generics); + } + ExpressionKind::TypePath(path) => { + empty_unresolved_type(&mut path.typ); + empty_ident(&mut path.item); + empty_type_args(&mut path.turbofish); } ExpressionKind::Quote(..) | ExpressionKind::Resolved(_) diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs index 9586d08e10c..3d428e97622 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs @@ -1944,6 +1944,15 @@ impl AcirContext { Ok(()) } + /// Insert the MemoryInit for the Return Data array, using the provided witnesses + pub(crate) fn initialize_return_data(&mut self, block_id: BlockId, init: Vec) { + self.acir_ir.push_opcode(Opcode::MemoryInit { + block_id, + init, + block_type: BlockType::ReturnData, + }); + } + /// Initializes an array in memory with the given values `optional_values`. /// If `optional_values` is empty, then the array is initialized with zeros. pub(crate) fn initialize_array( 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 15b44fde65d..430801dacf6 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 @@ -434,16 +434,9 @@ impl<'a> Context<'a> { for instruction_id in entry_block.instructions() { warnings.extend(self.convert_ssa_instruction(*instruction_id, dfg, ssa, brillig)?); } - let (return_vars, return_warnings) = self.convert_ssa_return(entry_block.unwrap_terminator(), dfg)?; - let call_data_arrays: Vec = - self.data_bus.call_data.iter().map(|cd| cd.array_id).collect(); - for call_data_array in call_data_arrays { - self.ensure_array_is_initialized(call_data_array, dfg)?; - } - // TODO: This is a naive method of assigning the return values to their witnesses as // we're likely to get a number of constraints which are asserting one witness to be equal to another. // @@ -452,6 +445,7 @@ impl<'a> Context<'a> { self.acir_context.assert_eq_var(*witness_var, return_var, None)?; } + self.initialize_databus(&return_witnesses, dfg)?; warnings.extend(return_warnings); warnings.extend(self.acir_context.warnings.clone()); @@ -459,6 +453,34 @@ impl<'a> Context<'a> { Ok(self.acir_context.finish(input_witness, return_witnesses, warnings)) } + fn initialize_databus( + &mut self, + witnesses: &Vec, + dfg: &DataFlowGraph, + ) -> Result<(), RuntimeError> { + // Initialize return_data using provided witnesses + if let Some(return_data) = self.data_bus.return_data { + let block_id = self.block_id(&return_data); + let already_initialized = self.initialized_arrays.contains(&block_id); + if !already_initialized { + // We hijack ensure_array_is_initialized() because we want the return data to use the return value witnesses, + // but the databus contains the computed values instead, that have just been asserted to be equal to the return values. + // We do not use initialize_array either for the case where a constant value is returned. + // In that case, the constant value has already been assigned a witness and the returned acir vars will be + // converted to it, instead of the corresponding return value witness. + self.acir_context.initialize_return_data(block_id, witnesses.to_owned()); + } + } + + // Initialize call_data + let call_data_arrays: Vec = + self.data_bus.call_data.iter().map(|cd| cd.array_id).collect(); + for call_data_array in call_data_arrays { + self.ensure_array_is_initialized(call_data_array, dfg)?; + } + Ok(()) + } + fn convert_brillig_main( mut self, main_func: &Function, @@ -1792,19 +1814,9 @@ impl<'a> Context<'a> { _ => unreachable!("ICE: Program must have a singular return"), }; - return_values.iter().fold(0, |acc, value_id| { - let is_databus = self - .data_bus - .return_data - .map_or(false, |return_databus| dfg[*value_id] == dfg[return_databus]); - - if is_databus { - // We do not return value for the data bus. - acc - } else { - acc + dfg.type_of_value(*value_id).flattened_size() - } - }) + return_values + .iter() + .fold(0, |acc, value_id| acc + dfg.type_of_value(*value_id).flattened_size()) } /// Converts an SSA terminator's return values into their ACIR representations @@ -1824,27 +1836,13 @@ impl<'a> Context<'a> { let mut has_constant_return = false; let mut return_vars: Vec = Vec::new(); for value_id in return_values { - let is_databus = self - .data_bus - .return_data - .map_or(false, |return_databus| dfg[*value_id] == dfg[return_databus]); let value = self.convert_value(*value_id, dfg); // `value` may or may not be an array reference. Calling `flatten` will expand the array if there is one. let acir_vars = self.acir_context.flatten(value)?; for (acir_var, _) in acir_vars { has_constant_return |= self.acir_context.is_constant(&acir_var); - if is_databus { - // We do not return value for the data bus. - self.ensure_array_is_initialized( - self.data_bus.return_data.expect( - "`is_databus == true` implies `data_bus.return_data` is `Some`", - ), - dfg, - )?; - } else { - return_vars.push(acir_var); - } + return_vars.push(acir_var); } } 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 cce8a50601e..c581ea9d62a 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs @@ -14,7 +14,7 @@ use acvm::{acir::AcirField, FieldElement}; use iter_extended::vecmap; use noirc_errors::{Span, Spanned}; -use super::{AsTraitPath, UnaryRhsMemberAccess}; +use super::{AsTraitPath, TypePath, UnaryRhsMemberAccess}; #[derive(Debug, PartialEq, Eq, Clone)] pub enum ExpressionKind { @@ -38,6 +38,7 @@ pub enum ExpressionKind { Comptime(BlockExpression, Span), Unsafe(BlockExpression, Span), AsTraitPath(AsTraitPath), + TypePath(TypePath), // This variant is only emitted when inlining the result of comptime // code. It is used to translate function values back into the AST while @@ -621,6 +622,7 @@ impl Display for ExpressionKind { write!(f, "quote {{ {} }}", tokens.join(" ")) } AsTraitPath(path) => write!(f, "{path}"), + TypePath(path) => write!(f, "{path}"), InternedStatement(_) => write!(f, "?InternedStatement"), } } @@ -787,6 +789,16 @@ impl Display for AsTraitPath { } } +impl Display for TypePath { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}::{}", self.typ, self.item)?; + if !self.turbofish.is_empty() { + write!(f, "::{}", self.turbofish)?; + } + Ok(()) + } +} + impl FunctionDefinition { pub fn normal( name: &Ident, 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 2317b96a1f1..d67501e932b 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs @@ -389,6 +389,16 @@ pub struct AsTraitPath { pub impl_item: Ident, } +/// A special kind of path in the form `Type::ident::` +/// Unlike normal paths, the type here can be a primitive type or +/// interned type. +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub struct TypePath { + pub typ: UnresolvedType, + pub item: Ident, + pub turbofish: GenericTypeArgs, +} + // Note: Path deliberately doesn't implement Recoverable. // No matter which default value we could give in Recoverable::error, // it would most likely cause further errors during name resolution diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/visitor.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/visitor.rs index abf76545f0a..9a2fb79ca88 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/visitor.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/visitor.rs @@ -22,7 +22,7 @@ use crate::{ use super::{ FunctionReturnType, GenericTypeArgs, IntegerBitSize, ItemVisibility, Pattern, Signedness, - TraitImplItemKind, UnresolvedGenerics, UnresolvedTraitConstraint, UnresolvedType, + TraitImplItemKind, TypePath, UnresolvedGenerics, UnresolvedTraitConstraint, UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression, }; @@ -340,6 +340,10 @@ pub trait Visitor { true } + fn visit_type_path(&mut self, _: &TypePath, _: Span) -> bool { + true + } + fn visit_unresolved_type(&mut self, _: &UnresolvedType) -> bool { true } @@ -838,6 +842,7 @@ impl Expression { ExpressionKind::AsTraitPath(as_trait_path) => { as_trait_path.accept(self.span, visitor); } + ExpressionKind::TypePath(path) => path.accept(self.span, visitor), ExpressionKind::Quote(tokens) => visitor.visit_quote(tokens), ExpressionKind::Resolved(expr_id) => visitor.visit_resolved_expression(*expr_id), ExpressionKind::Interned(id) => visitor.visit_interned_expression(*id), @@ -1208,6 +1213,19 @@ impl AsTraitPath { } } +impl TypePath { + pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + if visitor.visit_type_path(self, span) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + self.typ.accept(visitor); + self.turbofish.accept(visitor); + } +} + impl UnresolvedType { pub fn accept(&self, visitor: &mut impl Visitor) { if visitor.visit_unresolved_type(self) { 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 375df4e532a..a3b71f3e211 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -18,7 +18,7 @@ use crate::{ HirArrayLiteral, HirBinaryOp, HirBlockExpression, HirCallExpression, HirCastExpression, HirConstructorExpression, HirExpression, HirIfExpression, HirIndexExpression, HirInfixExpression, HirLambda, HirMemberAccess, HirMethodCallExpression, - HirMethodReference, HirPrefixExpression, + HirPrefixExpression, }, traits::TraitConstraint, }, @@ -76,6 +76,7 @@ impl<'context> Elaborator<'context> { (HirExpression::Error, Type::Error) } ExpressionKind::AsTraitPath(_) => todo!("Implement AsTraitPath"), + ExpressionKind::TypePath(path) => return self.elaborate_type_path(path), }; let id = self.interner.push_expr(hir_expr); self.interner.push_expr_location(id, expr.span, self.file); @@ -406,21 +407,13 @@ impl<'context> Elaborator<'context> { 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) { + match self.lookup_method(&object_type, method_name, span, true) { Some(method_ref) => { // 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 - } - }; + let func_id = method_ref + .func_id(self.interner) + .expect("Expected trait function to be a DefinitionKind::Function"); let generics = if func_id != FuncId::dummy_id() { let function_type = self.interner.function_meta(&func_id).typ.clone(); @@ -478,7 +471,17 @@ impl<'context> Elaborator<'context> { // 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); + let mut typ = self.type_check_call(&function_call, func_type, function_args, span); + if is_macro_call { + if self.in_comptime_context() { + typ = self.interner.next_type_variable(); + } else { + let args = function_call.arguments.clone(); + return self + .call_macro(function_call.func, args, location, typ) + .unwrap_or_else(|| (HirExpression::Error, Type::Error)); + } + } (HirExpression::Call(function_call), typ) } None => (HirExpression::Error, Type::Error), 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 905cdc3df79..f9016b1ca65 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs @@ -97,7 +97,7 @@ pub struct Elaborator<'context> { def_maps: &'context mut DefMaps, - file: FileId, + pub(crate) file: FileId, in_unsafe_block: bool, nested_loops: usize, 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 c2e42b7574c..7afa3215566 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -3,17 +3,17 @@ use noirc_errors::{Location, Span}; use rustc_hash::FxHashSet as HashSet; use crate::{ - ast::{UnresolvedType, ERROR_IDENT}, + ast::{TypePath, UnresolvedType, ERROR_IDENT}, hir::{ def_collector::dc_crate::CompilationError, resolution::errors::ResolverError, type_check::{Source, TypeCheckError}, }, hir_def::{ - expr::{HirIdent, ImplKind}, + expr::{HirIdent, HirMethodReference, ImplKind}, stmt::HirPattern, }, - macros_api::{HirExpression, Ident, Path, Pattern}, + macros_api::{Expression, ExpressionKind, HirExpression, Ident, Path, Pattern}, node_interner::{DefinitionId, DefinitionKind, ExprId, FuncId, GlobalId, TraitImplKind}, ResolvedGeneric, Shared, StructType, Type, TypeBindings, }; @@ -697,4 +697,43 @@ impl<'context> Elaborator<'context> { let id = DefinitionId::dummy_id(); (HirIdent::non_trait_method(id, location), 0) } + + pub(super) fn elaborate_type_path(&mut self, path: TypePath) -> (ExprId, Type) { + let span = path.item.span(); + let typ = self.resolve_type(path.typ); + + let Some(method) = self.lookup_method(&typ, &path.item.0.contents, span, false) else { + let error = Expression::new(ExpressionKind::Error, span); + return self.elaborate_expression(error); + }; + + let func_id = method + .func_id(self.interner) + .expect("Expected trait function to be a DefinitionKind::Function"); + + let generics = self.resolve_type_args(path.turbofish, func_id, span).0; + let generics = (!generics.is_empty()).then_some(generics); + + let location = Location::new(span, self.file); + let id = self.interner.function_definition_id(func_id); + + let impl_kind = match method { + HirMethodReference::FuncId(_) => ImplKind::NotATraitMethod, + HirMethodReference::TraitMethodId(method_id, generics) => { + let mut constraint = + self.interner.get_trait(method_id.trait_id).as_constraint(span); + constraint.trait_generics = generics; + ImplKind::TraitMethod(method_id, constraint, false) + } + }; + + let ident = HirIdent { location, id, impl_kind }; + let id = self.interner.push_expr(HirExpression::Ident(ident.clone(), generics.clone())); + self.interner.push_expr_location(id, location.span, location.file); + + let typ = self.type_check_variable(ident, id, generics); + self.interner.push_expr_type(id, typ.clone()); + + (id, typ) + } } 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 816535ba564..f40b62f2f31 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs @@ -715,7 +715,7 @@ impl<'context> Elaborator<'context> { } } - pub(super) fn unify( + pub fn unify( &mut self, actual: &Type, expected: &Type, @@ -1306,11 +1306,13 @@ impl<'context> Elaborator<'context> { object_type: &Type, method_name: &str, span: Span, + has_self_arg: bool, ) -> 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) { + match self.interner.lookup_method(object_type, id, method_name, false, has_self_arg) + { Some(method_id) => Some(HirMethodReference::FuncId(method_id)), None => { self.push_err(TypeCheckError::UnresolvedMethodCall { @@ -1339,9 +1341,9 @@ impl<'context> Elaborator<'context> { // This may be a struct or a primitive type. Type::MutableReference(element) => self .interner - .lookup_primitive_trait_method_mut(element.as_ref(), method_name) + .lookup_primitive_trait_method_mut(element.as_ref(), method_name, has_self_arg) .map(HirMethodReference::FuncId) - .or_else(|| self.lookup_method(&element, method_name, span)), + .or_else(|| self.lookup_method(&element, method_name, span, has_self_arg)), // 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 @@ -1353,7 +1355,8 @@ impl<'context> Elaborator<'context> { None } - other => match self.interner.lookup_primitive_method(&other, method_name) { + other => match self.interner.lookup_primitive_method(&other, method_name, has_self_arg) + { Some(method_id) => Some(HirMethodReference::FuncId(method_id)), None => { // It could be that this type is a composite type that is bound to a trait, 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 fe29262fc33..b5ed8126e33 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 @@ -12,6 +12,7 @@ use crate::ast::{BinaryOpKind, FunctionKind, IntegerBitSize, Signedness}; use crate::elaborator::Elaborator; use crate::graph::CrateId; use crate::hir::def_map::ModuleId; +use crate::hir::type_check::TypeCheckError; use crate::hir_def::expr::ImplKind; use crate::hir_def::function::FunctionBody; use crate::macros_api::UnaryOp; @@ -1298,6 +1299,13 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { elaborator.elaborate_expression(expr).0 }); result = self.evaluate(expr)?; + + // Macro calls are typed as type variables during type checking. + // Now that we know the type we need to further unify it in case there + // are inconsistencies or the type needs to be known. + let expected_type = self.elaborator.interner.id_type(id); + let actual_type = result.get_type(); + self.unify(&actual_type, &expected_type, location); } Ok(result) } @@ -1311,6 +1319,18 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { } } + fn unify(&mut self, actual: &Type, expected: &Type, location: Location) { + // We need to swap out the elaborator's file since we may be + // in a different one currently, and it uses that for the error location. + let old_file = std::mem::replace(&mut self.elaborator.file, location.file); + self.elaborator.unify(actual, expected, || TypeCheckError::TypeMismatch { + expected_typ: expected.to_string(), + expr_typ: actual.to_string(), + expr_span: location.span, + }); + self.elaborator.file = old_file; + } + fn evaluate_method_call( &mut self, call: HirMethodCallExpression, @@ -1332,8 +1352,9 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { struct_def.borrow().id, method_name, false, + true, ), - _ => self.elaborator.interner.lookup_primitive_method(&typ, method_name), + _ => self.elaborator.interner.lookup_primitive_method(&typ, method_name, true), }; if let Some(method) = method { 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 b027d069536..4d0787628cd 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 @@ -862,7 +862,17 @@ fn remove_interned_in_expression_kind( vecmap(block.statements, |stmt| remove_interned_in_statement(interner, stmt)); ExpressionKind::Unsafe(BlockExpression { statements }, span) } - ExpressionKind::AsTraitPath(_) => expr, + ExpressionKind::AsTraitPath(mut path) => { + path.typ = remove_interned_in_unresolved_type(interner, path.typ); + path.trait_generics = + remove_interned_in_generic_type_args(interner, path.trait_generics); + ExpressionKind::AsTraitPath(path) + } + ExpressionKind::TypePath(mut path) => { + path.typ = remove_interned_in_unresolved_type(interner, path.typ); + path.turbofish = remove_interned_in_generic_type_args(interner, path.turbofish); + ExpressionKind::TypePath(path) + } ExpressionKind::Resolved(id) => { let expr = interner.expression(&id); expr.to_display_ast(interner, Span::default()).kind diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/generics.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/generics.rs index 697c78745f9..b86e2350279 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/generics.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/generics.rs @@ -5,7 +5,7 @@ use iter_extended::vecmap; use crate::{ hir_def::traits::NamedType, macros_api::NodeInterner, - node_interner::{TraitId, TypeAliasId}, + node_interner::{FuncId, TraitId, TypeAliasId}, ResolvedGeneric, StructType, Type, }; @@ -97,6 +97,28 @@ impl Generic for Ref<'_, StructType> { } } +impl Generic for FuncId { + fn item_kind(&self) -> &'static str { + "function" + } + + fn item_name(&self, interner: &NodeInterner) -> String { + interner.function_name(self).to_string() + } + + fn generics(&self, interner: &NodeInterner) -> Vec { + interner.function_meta(self).direct_generics.clone() + } + + fn accepts_named_type_args(&self) -> bool { + false + } + + fn named_generics(&self, _interner: &NodeInterner) -> Vec { + Vec::new() + } +} + /// TraitGenerics are different from regular generics in that they can /// also contain associated type arguments. #[derive(Default, PartialEq, Eq, Clone, Hash, Ord, PartialOrd)] 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 40c16d00356..063b960863c 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 @@ -4,7 +4,9 @@ use noirc_errors::Location; use crate::ast::{BinaryOp, BinaryOpKind, Ident, UnaryOp}; use crate::hir::type_check::generics::TraitGenerics; -use crate::node_interner::{DefinitionId, ExprId, FuncId, NodeInterner, StmtId, TraitMethodId}; +use crate::node_interner::{ + DefinitionId, DefinitionKind, ExprId, FuncId, NodeInterner, StmtId, TraitMethodId, +}; use crate::token::Tokens; use crate::Shared; @@ -203,6 +205,21 @@ pub enum HirMethodReference { TraitMethodId(TraitMethodId, TraitGenerics), } +impl HirMethodReference { + pub fn func_id(&self, interner: &NodeInterner) -> Option { + match self { + HirMethodReference::FuncId(func_id) => Some(*func_id), + HirMethodReference::TraitMethodId(method_id, _) => { + let id = interner.trait_method_id(*method_id); + match &interner.try_definition(id)?.kind { + DefinitionKind::Function(func_id) => Some(*func_id), + _ => None, + } + } + } + } +} + impl HirMethodCallExpression { /// Converts a method call into a function call /// 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 7bb9fb83e70..c170d2cc08f 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 @@ -1680,7 +1680,7 @@ impl Type { if let (Type::Array(_size, element1), Type::Slice(element2)) = (&this, &target) { // We can only do the coercion if the `as_slice` method exists. // This is usually true, but some tests don't have access to the standard library. - if let Some(as_slice) = interner.lookup_primitive_method(&this, "as_slice") { + if let Some(as_slice) = interner.lookup_primitive_method(&this, "as_slice", true) { // Still have to ensure the element types match. // Don't need to issue an error here if not, it will be done in unify_with_coercions let mut bindings = TypeBindings::new(); 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 065e33608ba..d3e5e270b3c 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs @@ -1373,7 +1373,8 @@ impl NodeInterner { Type::Struct(struct_type, _generics) => { let id = struct_type.borrow().id; - if let Some(existing) = self.lookup_method(self_type, id, &method_name, true) { + if let Some(existing) = self.lookup_method(self_type, id, &method_name, true, true) + { return Some(existing); } @@ -1770,6 +1771,7 @@ impl NodeInterner { id: StructId, method_name: &str, force_type_check: bool, + has_self_arg: bool, ) -> Option { let methods = self.struct_methods.get(&id).and_then(|h| h.get(method_name)); @@ -1781,7 +1783,7 @@ impl NodeInterner { } } - self.find_matching_method(typ, methods, method_name) + self.find_matching_method(typ, methods, method_name, has_self_arg) } /// Select the 1 matching method with an object type matching `typ` @@ -1790,32 +1792,40 @@ impl NodeInterner { typ: &Type, methods: Option<&Methods>, method_name: &str, + has_self_arg: bool, ) -> Option { - if let Some(method) = methods.and_then(|m| m.find_matching_method(typ, self)) { + if let Some(method) = methods.and_then(|m| m.find_matching_method(typ, has_self_arg, self)) + { Some(method) } else { // Failed to find a match for the type in question, switch to looking at impls // for all types `T`, e.g. `impl Foo for T` let global_methods = self.primitive_methods.get(&TypeMethodKey::Generic)?.get(method_name)?; - global_methods.find_matching_method(typ, self) + global_methods.find_matching_method(typ, has_self_arg, self) } } /// Looks up a given method name on the given primitive type. - pub fn lookup_primitive_method(&self, typ: &Type, method_name: &str) -> Option { + pub fn lookup_primitive_method( + &self, + typ: &Type, + method_name: &str, + has_self_arg: bool, + ) -> Option { let key = get_type_method_key(typ)?; let methods = self.primitive_methods.get(&key)?.get(method_name)?; - self.find_matching_method(typ, Some(methods), method_name) + self.find_matching_method(typ, Some(methods), method_name, has_self_arg) } pub fn lookup_primitive_trait_method_mut( &self, typ: &Type, method_name: &str, + has_self_arg: bool, ) -> Option { let typ = Type::MutableReference(Box::new(typ.clone())); - self.lookup_primitive_method(&typ, method_name) + self.lookup_primitive_method(&typ, method_name, has_self_arg) } /// Returns what the next trait impl id is expected to be. @@ -2256,19 +2266,30 @@ impl Methods { } /// Select the 1 matching method with an object type matching `typ` - fn find_matching_method(&self, typ: &Type, interner: &NodeInterner) -> Option { + fn find_matching_method( + &self, + typ: &Type, + has_self_param: bool, + interner: &NodeInterner, + ) -> Option { // When adding methods we always check they do not overlap, so there should be // at most 1 matching method in this list. for method in self.iter() { match interner.function_meta(&method).typ.instantiate(interner).0 { Type::Function(args, _, _, _) => { - if let Some(object) = args.first() { - let mut bindings = TypeBindings::new(); - - if object.try_unify(typ, &mut bindings).is_ok() { - Type::apply_type_bindings(bindings); - return Some(method); + if has_self_param { + if let Some(object) = args.first() { + let mut bindings = TypeBindings::new(); + + if object.try_unify(typ, &mut bindings).is_ok() { + Type::apply_type_bindings(bindings); + return Some(method); + } } + } else { + // Just return the first method whose name matches since we + // can't match object types on static methods. + return Some(method); } } Type::Error => (), 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 5852ef7b68c..337563213e5 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs @@ -23,7 +23,7 @@ //! prevent other parsers from being tried afterward since there is no longer an error. Thus, they should //! be limited to cases like the above `fn` example where it is clear we shouldn't back out of the //! current parser to try alternative parsers in a `choice` expression. -use self::path::as_trait_path; +use self::path::{as_trait_path, type_path}; use self::primitives::{ interned_statement, interned_statement_expr, keyword, macro_quote_marker, mutable_reference, variable, @@ -1150,6 +1150,7 @@ where variable(), literal(), as_trait_path(parse_type()).map(ExpressionKind::AsTraitPath), + type_path(parse_type()), macro_quote_marker(), interned_expr(), interned_statement_expr(), 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 ea121c6f6db..3e964d271a0 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 @@ -1,4 +1,5 @@ -use crate::ast::{AsTraitPath, Path, PathKind, PathSegment, UnresolvedType}; +use crate::ast::{AsTraitPath, Path, PathKind, PathSegment, TypePath, UnresolvedType}; +use crate::macros_api::ExpressionKind; use crate::parser::{NoirParser, ParserError, ParserErrorReason}; use crate::token::{Keyword, Token}; @@ -6,8 +7,8 @@ use crate::token::{Keyword, Token}; use chumsky::prelude::*; use super::keyword; -use super::primitives::{ident, path_segment, path_segment_no_turbofish}; -use super::types::generic_type_args; +use super::primitives::{ident, path_segment, path_segment_no_turbofish, turbofish}; +use super::types::{generic_type_args, primitive_type}; pub(super) fn path<'a>( type_parser: impl NoirParser + 'a, @@ -68,6 +69,23 @@ pub(super) fn as_trait_path<'a>( }) } +/// Parses `MyType::path_segment` +/// These paths only support exactly two segments. +/// Unlike normal paths `MyType` here can also be a primitive type or interned type +/// in addition to a named type. +pub(super) fn type_path<'a>( + type_parser: impl NoirParser + 'a, +) -> impl NoirParser + 'a { + primitive_type() + .then_ignore(just(Token::DoubleColon)) + .then(ident()) + .then(turbofish(type_parser)) + .map(|((typ, item), turbofish)| { + let turbofish = turbofish.unwrap_or_default(); + ExpressionKind::TypePath(TypePath { typ, item, turbofish }) + }) +} + fn empty_path() -> impl NoirParser { let make_path = |kind| move |_, span| Path { segments: Vec::new(), kind, span }; let path_kind = |key, kind| keyword(key).map_with_span(make_path(kind)); 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 d4458190d03..9dd41d1a288 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 @@ -24,12 +24,7 @@ pub(super) fn parse_type_inner<'a>( recursive_type_parser: impl NoirParser + 'a, ) -> impl NoirParser + 'a { choice(( - field_type(), - int_type(), - bool_type(), - string_type(), - comptime_type(), - resolved_type(), + primitive_type(), format_string_type(recursive_type_parser.clone()), named_type(recursive_type_parser.clone()), named_trait(recursive_type_parser.clone()), @@ -40,6 +35,17 @@ pub(super) fn parse_type_inner<'a>( function_type(recursive_type_parser.clone()), mutable_reference_type(recursive_type_parser.clone()), as_trait_path_type(recursive_type_parser), + )) +} + +pub(super) fn primitive_type() -> impl NoirParser { + choice(( + field_type(), + int_type(), + bool_type(), + string_type(), + comptime_type(), + resolved_type(), interned_unresolved_type(), )) } diff --git a/noir/noir-repo/noir_stdlib/src/meta/mod.nr b/noir/noir-repo/noir_stdlib/src/meta/mod.nr index f19ccc523fc..1d787ebcdc1 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/mod.nr @@ -130,8 +130,13 @@ mod tests { // let _a: Quoted = quote { 1 }; let _a: Quoted = quote_one(); - // let _b: i32 = 1; - let _b: i32 = quote_one!(); + // let _b: Field = 1; + let _b: Field = quote_one!(); + + // Since integers default to fields, if we + // want a different type we have to explicitly cast + // let _c: i32 = 1 as i32; + let _c: i32 = quote_one!() as i32; } } // docs:end:quote-example diff --git a/noir/noir-repo/test_programs/compile_success_empty/macro_result_type/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/macro_result_type/Nargo.toml new file mode 100644 index 00000000000..664e405d5fe --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/macro_result_type/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "macro_result_type" +type = "bin" +authors = [""] +compiler_version = ">=0.34.0" + +[dependencies] diff --git a/noir/noir-repo/test_programs/compile_success_empty/macro_result_type/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/macro_result_type/src/main.nr new file mode 100644 index 00000000000..4dc97d1d422 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/macro_result_type/src/main.nr @@ -0,0 +1,13 @@ +fn main() { + comptime + { + let signature = "hello".as_ctstring(); + let string = signature.as_quoted_str!(); + let result = half(string); + assert_eq(result, 2); + } +} + +comptime fn half(_s: str) -> u32 { + N / 2 +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/macro_result_type/t.rs b/noir/noir-repo/test_programs/compile_success_empty/macro_result_type/t.rs new file mode 100644 index 00000000000..bcd91d7bf5d --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/macro_result_type/t.rs @@ -0,0 +1,12 @@ + +trait Foo { + fn foo() {} +} + +impl Foo<3> for () { + fn foo() {} +} + +fn main() { + let _ = Foo::foo(); +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/type_path/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/type_path/Nargo.toml new file mode 100644 index 00000000000..0e76437d15f --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/type_path/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "type_path" +type = "bin" +authors = [""] +compiler_version = ">=0.34.0" + +[dependencies] diff --git a/noir/noir-repo/test_programs/compile_success_empty/type_path/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/type_path/src/main.nr new file mode 100644 index 00000000000..96f3a29d96b --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/type_path/src/main.nr @@ -0,0 +1,15 @@ +fn main() { + comptime + { + let foo = quote { Foo }.as_type(); + quote { + $foo::static() + } + } +} + +struct Foo {} + +impl Foo { + fn static() {} +} diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion.rs b/noir/noir-repo/tooling/lsp/src/requests/completion.rs index 17652d37509..0d1d7af7ae6 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion.rs @@ -578,7 +578,7 @@ impl<'a> NodeFinder<'a> { } Type::TypeVariable(var, _) | Type::NamedGeneric(var, _, _) => { if let TypeBinding::Bound(typ) = &*var.borrow() { - self.complete_type_fields_and_methods( + return self.complete_type_fields_and_methods( typ, prefix, function_completion_kind, @@ -627,15 +627,16 @@ impl<'a> NodeFinder<'a> { for (name, methods) in methods_by_name { for func_id in methods.iter() { if name_matches(name, prefix) { - if let Some(completion_item) = self.function_completion_item( + let completion_items = self.function_completion_items( name, func_id, function_completion_kind, function_kind, None, // attribute first type self_prefix, - ) { - self.completion_items.push(completion_item); + ); + if !completion_items.is_empty() { + self.completion_items.extend(completion_items); self.suggested_module_def_ids.insert(ModuleDefId::FunctionId(func_id)); } } @@ -654,15 +655,16 @@ impl<'a> NodeFinder<'a> { for (name, func_id) in &trait_.method_ids { if name_matches(name, prefix) { - if let Some(completion_item) = self.function_completion_item( + let completion_items = self.function_completion_items( name, *func_id, function_completion_kind, function_kind, None, // attribute first type self_prefix, - ) { - self.completion_items.push(completion_item); + ); + if !completion_items.is_empty() { + self.completion_items.extend(completion_items); self.suggested_module_def_ids.insert(ModuleDefId::FunctionId(*func_id)); } } @@ -742,14 +744,15 @@ impl<'a> NodeFinder<'a> { let per_ns = module_data.find_name(ident); if let Some((module_def_id, visibility, _)) = per_ns.types { if is_visible(module_id, self.module_id, visibility, self.def_maps) { - if let Some(completion_item) = self.module_def_id_completion_item( + let completion_items = self.module_def_id_completion_items( module_def_id, name.clone(), function_completion_kind, function_kind, requested_items, - ) { - self.completion_items.push(completion_item); + ); + if !completion_items.is_empty() { + self.completion_items.extend(completion_items); self.suggested_module_def_ids.insert(module_def_id); } } @@ -757,14 +760,15 @@ impl<'a> NodeFinder<'a> { if let Some((module_def_id, visibility, _)) = per_ns.values { if is_visible(module_id, self.module_id, visibility, self.def_maps) { - if let Some(completion_item) = self.module_def_id_completion_item( + let completion_items = self.module_def_id_completion_items( module_def_id, name.clone(), function_completion_kind, function_kind, requested_items, - ) { - self.completion_items.push(completion_item); + ); + if !completion_items.is_empty() { + self.completion_items.extend(completion_items); self.suggested_module_def_ids.insert(module_def_id); } } diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion/auto_import.rs b/noir/noir-repo/tooling/lsp/src/requests/completion/auto_import.rs index f9c5dab0672..2713ae252bf 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion/auto_import.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/auto_import.rs @@ -29,73 +29,78 @@ impl<'a> NodeFinder<'a> { continue; } - let Some(mut completion_item) = self.module_def_id_completion_item( + let completion_items = self.module_def_id_completion_items( *module_def_id, name.clone(), function_completion_kind, FunctionKind::Any, requested_items, - ) else { + ); + + if completion_items.is_empty() { continue; }; - let module_full_path = if let Some(defining_module) = defining_module { - relative_module_id_path( - *defining_module, - &self.module_id, - current_module_parent_id, - self.interner, - ) - } else { - let Some(module_full_path) = relative_module_full_path( - *module_def_id, - *visibility, - self.module_id, - current_module_parent_id, - self.interner, - self.def_maps, - ) else { - continue; + self.suggested_module_def_ids.insert(*module_def_id); + + for mut completion_item in completion_items { + let module_full_path = if let Some(defining_module) = defining_module { + relative_module_id_path( + *defining_module, + &self.module_id, + current_module_parent_id, + self.interner, + ) + } else { + let Some(module_full_path) = relative_module_full_path( + *module_def_id, + *visibility, + self.module_id, + current_module_parent_id, + self.interner, + self.def_maps, + ) else { + continue; + }; + module_full_path }; - module_full_path - }; - let full_path = if defining_module.is_some() - || !matches!(module_def_id, ModuleDefId::ModuleId(..)) - { - format!("{}::{}", module_full_path, name) - } else { - module_full_path - }; + let full_path = if defining_module.is_some() + || !matches!(module_def_id, ModuleDefId::ModuleId(..)) + { + format!("{}::{}", module_full_path, name) + } else { + module_full_path + }; - let mut label_details = completion_item.label_details.unwrap(); - label_details.detail = Some(format!("(use {})", full_path)); - completion_item.label_details = Some(label_details); + let mut label_details = completion_item.label_details.unwrap(); + label_details.detail = Some(format!("(use {})", full_path)); + completion_item.label_details = Some(label_details); - let line = self.auto_import_line as u32; - let character = (self.nesting * 4) as u32; - let indent = " ".repeat(self.nesting * 4); - let mut newlines = "\n"; + let line = self.auto_import_line as u32; + let character = (self.nesting * 4) as u32; + let indent = " ".repeat(self.nesting * 4); + let mut newlines = "\n"; - // If the line we are inserting into is not an empty line, insert an extra line to make some room - if let Some(line_text) = self.lines.get(line as usize) { - if !line_text.trim().is_empty() { - newlines = "\n\n"; + // If the line we are inserting into is not an empty line, insert an extra line to make some room + if let Some(line_text) = self.lines.get(line as usize) { + if !line_text.trim().is_empty() { + newlines = "\n\n"; + } } - } - completion_item.additional_text_edits = Some(vec![TextEdit { - range: Range { - start: Position { line, character }, - end: Position { line, character }, - }, - new_text: format!("use {};{}{}", full_path, newlines, indent), - }]); + completion_item.additional_text_edits = Some(vec![TextEdit { + range: Range { + start: Position { line, character }, + end: Position { line, character }, + }, + new_text: format!("use {};{}{}", full_path, newlines, indent), + }]); - completion_item.sort_text = Some(auto_import_sort_text()); + completion_item.sort_text = Some(auto_import_sort_text()); - self.completion_items.push(completion_item); - self.suggested_module_def_ids.insert(*module_def_id); + self.completion_items.push(completion_item); + } } } } 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 56b1776b228..5273e49d4b4 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 @@ -20,17 +20,17 @@ use super::{ }; impl<'a> NodeFinder<'a> { - pub(super) fn module_def_id_completion_item( + pub(super) fn module_def_id_completion_items( &self, module_def_id: ModuleDefId, name: String, function_completion_kind: FunctionCompletionKind, function_kind: FunctionKind, requested_items: RequestedItems, - ) -> Option { + ) -> Vec { match requested_items { RequestedItems::OnlyTypes => match module_def_id { - ModuleDefId::FunctionId(_) | ModuleDefId::GlobalId(_) => return None, + ModuleDefId::FunctionId(_) | ModuleDefId::GlobalId(_) => return Vec::new(), ModuleDefId::ModuleId(_) | ModuleDefId::TypeId(_) | ModuleDefId::TypeAliasId(_) @@ -38,7 +38,7 @@ impl<'a> NodeFinder<'a> { }, RequestedItems::OnlyAttributeFunctions(..) => { if !matches!(module_def_id, ModuleDefId::FunctionId(..)) { - return None; + return Vec::new(); } } RequestedItems::AnyItems => (), @@ -57,8 +57,8 @@ impl<'a> NodeFinder<'a> { }; match module_def_id { - ModuleDefId::ModuleId(id) => Some(self.module_completion_item(name, id)), - ModuleDefId::FunctionId(func_id) => self.function_completion_item( + ModuleDefId::ModuleId(id) => vec![self.module_completion_item(name, id)], + ModuleDefId::FunctionId(func_id) => self.function_completion_items( &name, func_id, function_completion_kind, @@ -66,10 +66,10 @@ impl<'a> NodeFinder<'a> { attribute_first_type.as_ref(), false, // self_prefix ), - ModuleDefId::TypeId(struct_id) => Some(self.struct_completion_item(name, struct_id)), - ModuleDefId::TypeAliasId(id) => Some(self.type_alias_completion_item(name, id)), - ModuleDefId::TraitId(trait_id) => Some(self.trait_completion_item(name, trait_id)), - ModuleDefId::GlobalId(global_id) => Some(self.global_completion_item(name, global_id)), + ModuleDefId::TypeId(struct_id) => vec![self.struct_completion_item(name, struct_id)], + ModuleDefId::TypeAliasId(id) => vec![self.type_alias_completion_item(name, id)], + ModuleDefId::TraitId(trait_id) => vec![self.trait_completion_item(name, trait_id)], + ModuleDefId::GlobalId(global_id) => vec![self.global_completion_item(name, global_id)], } } @@ -133,7 +133,7 @@ impl<'a> NodeFinder<'a> { self.completion_item_with_doc_comments(ReferenceId::Global(global_id), completion_item) } - pub(super) fn function_completion_item( + pub(super) fn function_completion_items( &self, name: &String, func_id: FuncId, @@ -141,7 +141,7 @@ impl<'a> NodeFinder<'a> { function_kind: FunctionKind, attribute_first_type: Option<&Type>, self_prefix: bool, - ) -> Option { + ) -> Vec { let func_meta = self.interner.function_meta(&func_id); let func_self_type = if let Some((pattern, typ, _)) = func_meta.parameters.0.first() { @@ -161,12 +161,12 @@ impl<'a> NodeFinder<'a> { if let Some(attribute_first_type) = attribute_first_type { if func_meta.parameters.is_empty() { - return None; + return Vec::new(); } let (_, typ, _) = &func_meta.parameters.0[0]; if typ != attribute_first_type { - return None; + return Vec::new(); } } @@ -186,23 +186,71 @@ impl<'a> NodeFinder<'a> { } if self_type != func_self_type { - return None; + return Vec::new(); } } 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; + return Vec::new(); } } } } else { - return None; + return Vec::new(); } } } + let make_completion_item = |is_macro_call| { + self.function_completion_item( + name, + func_id, + func_meta, + func_self_type, + function_completion_kind, + function_kind, + attribute_first_type, + self_prefix, + is_macro_call, + ) + }; + + // When suggesting functions in attributes, never suggest a macro call + if attribute_first_type.is_some() { + return vec![make_completion_item(false)]; + } + + // Special case: the `unquote` macro + // (it's unlikely users will define a function named `unquote` that does something different than std's unquote) + if name == "unquote" { + return vec![make_completion_item(true)]; + } + + let modifiers = self.interner.function_modifiers(&func_id); + if modifiers.is_comptime + && matches!(func_meta.return_type(), Type::Quoted(QuotedType::Quoted)) + { + vec![make_completion_item(false), make_completion_item(true)] + } else { + vec![make_completion_item(false)] + } + } + + #[allow(clippy::too_many_arguments)] + pub(super) fn function_completion_item( + &self, + name: &String, + func_id: FuncId, + func_meta: &FuncMeta, + func_self_type: Option<&Type>, + function_completion_kind: FunctionCompletionKind, + function_kind: FunctionKind, + attribute_first_type: Option<&Type>, + self_prefix: bool, + is_macro_call: bool, + ) -> CompletionItem { let is_operator = if let Some(trait_impl_id) = &func_meta.trait_impl { let trait_impl = self.interner.get_trait_implementation(*trait_impl_id); let trait_impl = trait_impl.borrow(); @@ -211,6 +259,7 @@ impl<'a> NodeFinder<'a> { false }; let name = if self_prefix { format!("self.{}", name) } else { name.clone() }; + let name = if is_macro_call { format!("{}!", name) } else { name }; let name = &name; let description = func_meta_type_to_string(func_meta, func_self_type.is_some()); let mut has_arguments = false; @@ -269,10 +318,8 @@ impl<'a> NodeFinder<'a> { } } }; - let completion_item = - self.completion_item_with_doc_comments(ReferenceId::Function(func_id), completion_item); - Some(completion_item) + self.completion_item_with_doc_comments(ReferenceId::Function(func_id), completion_item) } fn compute_function_insert_text( 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 50f4412d7a8..23d137c1647 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs @@ -1919,7 +1919,7 @@ mod completion_tests { #[some>|<] fn foo() {} - fn some_attr(f: FunctionDefinition, x: Field) {} + comptime fn some_attr(f: FunctionDefinition, x: Field) -> Quoted {} fn some_other_function(x: Field) {} "#; @@ -1928,7 +1928,7 @@ mod completion_tests { vec![function_completion_item( "some_attr(…)", "some_attr(${1:x})", - "fn(FunctionDefinition, Field)", + "fn(FunctionDefinition, Field) -> Quoted", )], ) .await; @@ -2119,4 +2119,39 @@ mod completion_tests { assert_completion(src, vec![field_completion_item("bar", "Field")]).await; } + + #[test] + async fn test_suggests_macro_call_if_comptime_function_returns_quoted() { + let src = r#" + comptime fn foobar() -> Quoted {} + + fn main() { + fooba>|< + } + "#; + + assert_completion_excluding_auto_import( + src, + vec![ + function_completion_item("foobar!()", "foobar!()", "fn() -> Quoted"), + function_completion_item("foobar()", "foobar()", "fn() -> Quoted"), + ], + ) + .await; + } + + #[test] + async fn test_only_suggests_macro_call_for_unquote() { + let src = r#" + use std::meta::unquote; + + fn main() { + unquot>|< + } + "#; + + let completions = get_completions(src).await; + assert_eq!(completions.len(), 1); + assert_eq!(completions[0].label, "unquote!(…)"); + } } diff --git a/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs b/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs index f05680903b8..2eef4f6e262 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs @@ -522,6 +522,7 @@ fn get_expression_name(expression: &Expression) -> Option { ExpressionKind::Cast(cast) => get_expression_name(&cast.lhs), ExpressionKind::Parenthesized(expr) => get_expression_name(expr), ExpressionKind::AsTraitPath(path) => Some(path.impl_item.to_string()), + ExpressionKind::TypePath(path) => Some(path.item.to_string()), ExpressionKind::Constructor(..) | ExpressionKind::Infix(..) | ExpressionKind::Index(..) diff --git a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs index 81a7a219e06..873b5c87056 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs @@ -192,7 +192,20 @@ pub(crate) fn rewrite( } ExpressionKind::AsTraitPath(path) => { let trait_path = rewrite_path(visitor, shape, path.trait_path); - format!("<{} as {}>::{}", path.typ, trait_path, path.impl_item) + + if path.trait_generics.is_empty() { + format!("<{} as {}>::{}", path.typ, trait_path, path.impl_item) + } else { + let generics = path.trait_generics; + format!("<{} as {}::{}>::{}", path.typ, trait_path, generics, path.impl_item) + } + } + ExpressionKind::TypePath(path) => { + if path.turbofish.is_empty() { + format!("{}::{}", path.typ, path.item) + } else { + format!("{}::{}::{}", path.typ, path.item, path.turbofish) + } } } }