diff --git a/noir/noir-repo/aztec_macros/src/utils/hir_utils.rs b/noir/noir-repo/aztec_macros/src/utils/hir_utils.rs index 0a8ce371708d..200ce3099cbd 100644 --- a/noir/noir-repo/aztec_macros/src/utils/hir_utils.rs +++ b/noir/noir-repo/aztec_macros/src/utils/hir_utils.rs @@ -195,7 +195,7 @@ pub fn inject_fn( let trait_id = None; items.functions.push(UnresolvedFunctions { file_id, functions, trait_id, self_type: None }); - let mut errors = Elaborator::elaborate(context, *crate_id, items, None, false); + let mut errors = Elaborator::elaborate(context, *crate_id, items, None); errors.retain(|(error, _)| !CustomDiagnostic::from(error).is_warning()); if !errors.is_empty() { @@ -241,7 +241,7 @@ pub fn inject_global( let mut items = CollectedItems::default(); items.globals.push(UnresolvedGlobal { file_id, module_id, global_id, stmt_def: global }); - let _errors = Elaborator::elaborate(context, *crate_id, items, None, false); + let _errors = Elaborator::elaborate(context, *crate_id, items, None); } pub fn fully_qualified_note_path(context: &HirContext, note_id: StructId) -> Option { diff --git a/noir/noir-repo/compiler/noirc_driver/src/lib.rs b/noir/noir-repo/compiler/noirc_driver/src/lib.rs index a315e7ed3975..31c279bc0f31 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/lib.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/lib.rs @@ -120,10 +120,6 @@ pub struct CompileOptions { #[arg(long, hide = true)] pub show_artifact_paths: bool, - /// Temporary flag to enable the experimental arithmetic generics feature - #[arg(long, hide = true)] - pub arithmetic_generics: bool, - /// Flag to turn off the compiler check for under constrained values. /// Warning: This can improve compilation speed but can also lead to correctness errors. /// This check should always be run on production code. @@ -289,7 +285,6 @@ pub fn check_crate( crate_id, context, options.debug_comptime_in_file.as_deref(), - options.arithmetic_generics, error_on_unused_imports, macros, ); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs index 89445c4d1954..8c128c452d07 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs @@ -181,7 +181,12 @@ impl<'f> PerFunctionContext<'f> { self.last_loads.get(store_address).is_none() }; - if remove_load && !is_reference_param { + let is_reference_alias = block + .expressions + .get(store_address) + .map_or(false, |expression| matches!(expression, Expression::Dereference(_))); + + if remove_load && !is_reference_param && !is_reference_alias { self.instructions_to_remove.insert(*store_instruction); } } @@ -286,19 +291,19 @@ impl<'f> PerFunctionContext<'f> { } else { references.mark_value_used(address, self.inserter.function); - let expression = if let Some(expression) = references.expressions.get(&result) { - expression.clone() - } else { - references.expressions.insert(result, Expression::Other(result)); - Expression::Other(result) - }; - if let Some(aliases) = references.aliases.get_mut(&expression) { + let expression = + references.expressions.entry(result).or_insert(Expression::Other(result)); + // Make sure this load result is marked an alias to itself + if let Some(aliases) = references.aliases.get_mut(expression) { + // If we have an alias set, add to the set aliases.insert(result); } else { + // Otherwise, create a new alias set containing just the load result references .aliases .insert(Expression::Other(result), AliasSet::known(result)); } + // Mark that we know a load result is equivalent to the address of a load. references.set_known_value(result, address); self.last_loads.insert(address, (instruction, block_id)); @@ -789,4 +794,98 @@ mod tests { // We expect the last eq to be optimized out assert_eq!(b1_instructions.len(), 0); } + + #[test] + fn keep_store_to_alias_in_loop_block() { + // This test makes sure the instruction `store Field 2 at v5` in b2 remains after mem2reg. + // Although the only instruction on v5 is a lone store without any loads, + // v5 is an alias of the reference v0 which is stored in v2. + // This test makes sure that we are not inadvertently removing stores to aliases across blocks. + // + // acir(inline) fn main f0 { + // b0(): + // v0 = allocate + // store Field 0 at v0 + // v2 = allocate + // store v0 at v2 + // jmp b1(Field 0) + // b1(v3: Field): + // v4 = eq v3, Field 0 + // jmpif v4 then: b2, else: b3 + // b2(): + // v5 = load v2 + // store Field 2 at v5 + // v8 = add v3, Field 1 + // jmp b1(v8) + // b3(): + // v9 = load v0 + // v10 = eq v9, Field 2 + // constrain v9 == Field 2 + // v11 = load v2 + // v12 = load v10 + // v13 = eq v12, Field 2 + // constrain v11 == Field 2 + // return + // } + let main_id = Id::test_new(0); + let mut builder = FunctionBuilder::new("main".into(), main_id); + + let v0 = builder.insert_allocate(Type::field()); + let zero = builder.numeric_constant(0u128, Type::field()); + builder.insert_store(v0, zero); + + let v2 = builder.insert_allocate(Type::field()); + // Construct alias + builder.insert_store(v2, v0); + let v2_type = builder.current_function.dfg.type_of_value(v2); + assert!(builder.current_function.dfg.value_is_reference(v2)); + + let b1 = builder.insert_block(); + builder.terminate_with_jmp(b1, vec![zero]); + + // Loop header + builder.switch_to_block(b1); + let v3 = builder.add_block_parameter(b1, Type::field()); + let is_zero = builder.insert_binary(v3, BinaryOp::Eq, zero); + + let b2 = builder.insert_block(); + let b3 = builder.insert_block(); + builder.terminate_with_jmpif(is_zero, b2, b3); + + // Loop body + builder.switch_to_block(b2); + let v5 = builder.insert_load(v2, v2_type.clone()); + let two = builder.numeric_constant(2u128, Type::field()); + builder.insert_store(v5, two); + let one = builder.numeric_constant(1u128, Type::field()); + let v3_plus_one = builder.insert_binary(v3, BinaryOp::Add, one); + builder.terminate_with_jmp(b1, vec![v3_plus_one]); + + builder.switch_to_block(b3); + let v9 = builder.insert_load(v0, Type::field()); + let _ = builder.insert_binary(v9, BinaryOp::Eq, two); + + builder.insert_constrain(v9, two, None); + let v11 = builder.insert_load(v2, v2_type); + let v12 = builder.insert_load(v11, Type::field()); + let _ = builder.insert_binary(v12, BinaryOp::Eq, two); + + builder.insert_constrain(v11, two, None); + builder.terminate_with_return(vec![]); + + let ssa = builder.finish(); + + // We expect the same result as above. + let ssa = ssa.mem2reg(); + + let main = ssa.main(); + assert_eq!(main.reachable_blocks().len(), 4); + + // The store from the original SSA should remain + assert_eq!(count_stores(main.entry_block(), &main.dfg), 2); + assert_eq!(count_stores(b2, &main.dfg), 1); + + assert_eq!(count_loads(b2, &main.dfg), 1); + assert_eq!(count_loads(b3, &main.dfg), 3); + } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/mod.rs index 3fd632492013..1ed88115fa03 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/mod.rs @@ -12,6 +12,7 @@ mod traits; mod type_alias; mod visitor; +pub use visitor::AttributeTarget; pub use visitor::Visitor; pub use expression::*; 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 0aeeed39dd01..64b479b5fd6b 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/visitor.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/visitor.rs @@ -16,7 +16,7 @@ use crate::{ QuotedTypeId, }, parser::{Item, ItemKind, ParsedSubModule}, - token::{SecondaryAttribute, Tokens}, + token::{CustomAtrribute, SecondaryAttribute, Tokens}, ParsedModule, QuotedType, }; @@ -26,6 +26,13 @@ use super::{ UnresolvedTypeExpression, }; +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum AttributeTarget { + Module, + Struct, + Function, +} + /// Implements the [Visitor pattern](https://en.wikipedia.org/wiki/Visitor_pattern) for Noir's AST. /// /// In this implementation, methods must return a bool: @@ -433,7 +440,15 @@ pub trait Visitor { true } - fn visit_secondary_attribute(&mut self, _: &SecondaryAttribute, _: Span) {} + fn visit_secondary_attribute( + &mut self, + _: &SecondaryAttribute, + _target: AttributeTarget, + ) -> bool { + true + } + + fn visit_custom_attribute(&mut self, _: &CustomAtrribute, _target: AttributeTarget) {} } impl ParsedModule { @@ -484,7 +499,7 @@ impl Item { module_declaration.accept(self.span, visitor); } ItemKind::InnerAttribute(attribute) => { - attribute.accept(self.span, visitor); + attribute.accept(AttributeTarget::Module, visitor); } } } @@ -492,6 +507,10 @@ impl Item { impl ParsedSubModule { pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + for attribute in &self.outer_attributes { + attribute.accept(AttributeTarget::Module, visitor); + } + if visitor.visit_parsed_submodule(self, span) { self.accept_children(visitor); } @@ -510,6 +529,10 @@ impl NoirFunction { } pub fn accept_children(&self, visitor: &mut impl Visitor) { + for attribute in self.secondary_attributes() { + attribute.accept(AttributeTarget::Function, visitor); + } + for param in &self.def.parameters { param.typ.accept(visitor); } @@ -674,6 +697,10 @@ impl NoirStruct { } pub fn accept_children(&self, visitor: &mut impl Visitor) { + for attribute in &self.attributes { + attribute.accept(AttributeTarget::Struct, visitor); + } + for (_name, unresolved_type) in &self.fields { unresolved_type.accept(visitor); } @@ -694,6 +721,10 @@ impl NoirTypeAlias { impl ModuleDeclaration { pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + for attribute in &self.outer_attributes { + attribute.accept(AttributeTarget::Module, visitor); + } + visitor.visit_module_declaration(self, span); } } @@ -1295,8 +1326,22 @@ impl Pattern { } impl SecondaryAttribute { - pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { - visitor.visit_secondary_attribute(self, span); + pub fn accept(&self, target: AttributeTarget, visitor: &mut impl Visitor) { + if visitor.visit_secondary_attribute(self, target) { + self.accept_children(target, visitor); + } + } + + pub fn accept_children(&self, target: AttributeTarget, visitor: &mut impl Visitor) { + if let SecondaryAttribute::Custom(custom) = self { + custom.accept(target, visitor); + } + } +} + +impl CustomAtrribute { + pub fn accept(&self, target: AttributeTarget, visitor: &mut impl Visitor) { + visitor.visit_custom_attribute(self, target); } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/comptime.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/comptime.rs index e9650a625e80..0cd0824b6d9a 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/comptime.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/comptime.rs @@ -52,10 +52,41 @@ impl<'context> Elaborator<'context> { /// Elaborate an expression from the middle of a comptime scope. /// When this happens we require additional information to know /// what variables should be in scope. - pub fn elaborate_item_from_comptime<'a, T>( + pub fn elaborate_item_from_comptime_in_function<'a, T>( &'a mut self, current_function: Option, f: impl FnOnce(&mut Elaborator<'a>) -> T, + ) -> T { + self.elaborate_item_from_comptime(f, |elaborator| { + if let Some(function) = current_function { + let meta = elaborator.interner.function_meta(&function); + elaborator.current_item = Some(DependencyId::Function(function)); + elaborator.crate_id = meta.source_crate; + elaborator.local_module = meta.source_module; + elaborator.file = meta.source_file; + elaborator.introduce_generics_into_scope(meta.all_generics.clone()); + } + }) + } + + pub fn elaborate_item_from_comptime_in_module<'a, T>( + &'a mut self, + module: ModuleId, + file: FileId, + f: impl FnOnce(&mut Elaborator<'a>) -> T, + ) -> T { + self.elaborate_item_from_comptime(f, |elaborator| { + elaborator.current_item = None; + elaborator.crate_id = module.krate; + elaborator.local_module = module.local_id; + elaborator.file = file; + }) + } + + fn elaborate_item_from_comptime<'a, T>( + &'a mut self, + f: impl FnOnce(&mut Elaborator<'a>) -> T, + setup: impl FnOnce(&mut Elaborator<'a>), ) -> T { // Create a fresh elaborator to ensure no state is changed from // this elaborator @@ -64,21 +95,13 @@ impl<'context> Elaborator<'context> { self.def_maps, self.crate_id, self.debug_comptime_in_file, - self.enable_arithmetic_generics, self.interpreter_call_stack.clone(), ); elaborator.function_context.push(FunctionContext::default()); elaborator.scopes.start_function(); - if let Some(function) = current_function { - let meta = elaborator.interner.function_meta(&function); - elaborator.current_item = Some(DependencyId::Function(function)); - elaborator.crate_id = meta.source_crate; - elaborator.local_module = meta.source_module; - elaborator.file = meta.source_file; - elaborator.introduce_generics_into_scope(meta.all_generics.clone()); - } + setup(&mut elaborator); elaborator.populate_scope_from_comptime_scopes(); @@ -352,7 +375,7 @@ impl<'context> Elaborator<'context> { } } - fn add_item( + pub(crate) fn add_item( &mut self, item: TopLevelStatement, generated_items: &mut CollectedItems, 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 6b23336b5ea9..d321d04bef9d 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs @@ -168,9 +168,6 @@ pub struct Elaborator<'context> { /// they are elaborated (e.g. in a function's type or another global's RHS). unresolved_globals: BTreeMap, - /// Temporary flag to enable the experimental arithmetic generics feature - enable_arithmetic_generics: bool, - pub(crate) interpreter_call_stack: im::Vector, } @@ -194,7 +191,6 @@ impl<'context> Elaborator<'context> { def_maps: &'context mut DefMaps, crate_id: CrateId, debug_comptime_in_file: Option, - enable_arithmetic_generics: bool, interpreter_call_stack: im::Vector, ) -> Self { Self { @@ -217,7 +213,6 @@ impl<'context> Elaborator<'context> { current_trait_impl: None, debug_comptime_in_file, unresolved_globals: BTreeMap::new(), - enable_arithmetic_generics, current_trait: None, interpreter_call_stack, } @@ -227,14 +222,12 @@ impl<'context> Elaborator<'context> { context: &'context mut Context, crate_id: CrateId, debug_comptime_in_file: Option, - enable_arithmetic_generics: bool, ) -> Self { Self::new( &mut context.def_interner, &mut context.def_maps, crate_id, debug_comptime_in_file, - enable_arithmetic_generics, im::Vector::new(), ) } @@ -244,16 +237,8 @@ impl<'context> Elaborator<'context> { crate_id: CrateId, items: CollectedItems, debug_comptime_in_file: Option, - enable_arithmetic_generics: bool, ) -> Vec<(CompilationError, FileId)> { - Self::elaborate_and_return_self( - context, - crate_id, - items, - debug_comptime_in_file, - enable_arithmetic_generics, - ) - .errors + Self::elaborate_and_return_self(context, crate_id, items, debug_comptime_in_file).errors } pub fn elaborate_and_return_self( @@ -261,20 +246,14 @@ impl<'context> Elaborator<'context> { crate_id: CrateId, items: CollectedItems, debug_comptime_in_file: Option, - enable_arithmetic_generics: bool, ) -> Self { - let mut this = Self::from_context( - context, - crate_id, - debug_comptime_in_file, - enable_arithmetic_generics, - ); + let mut this = Self::from_context(context, crate_id, debug_comptime_in_file); this.elaborate_items(items); this.check_and_pop_function_context(); this } - fn elaborate_items(&mut self, mut items: CollectedItems) { + pub(crate) fn elaborate_items(&mut self, mut items: CollectedItems) { // We must first resolve and intern the globals before we can resolve any stmts inside each function. // Each function uses its own resolver with a newly created ScopeForest, and must be resolved again to be within a function's scope // 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 8dccd5f0344e..39ef4e0bb8e8 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs @@ -450,7 +450,6 @@ impl<'context> Elaborator<'context> { } UnresolvedTypeExpression::Constant(int, _) => Type::Constant(int), UnresolvedTypeExpression::BinaryOperation(lhs, op, rhs, span) => { - let (lhs_span, rhs_span) = (lhs.span(), rhs.span()); let lhs = self.convert_expression_type(*lhs); let rhs = self.convert_expression_type(*rhs); @@ -463,15 +462,7 @@ impl<'context> Elaborator<'context> { Type::Error } } - (lhs, rhs) => { - if !self.enable_arithmetic_generics { - let span = - if !matches!(lhs, Type::Constant(_)) { lhs_span } else { rhs_span }; - self.push_err(ResolverError::InvalidArrayLengthExpr { span }); - } - - Type::InfixExpr(Box::new(lhs), op, Box::new(rhs)).canonicalize() - } + (lhs, rhs) => Type::InfixExpr(Box::new(lhs), op, Box::new(rhs)).canonicalize(), } } UnresolvedTypeExpression::AsTraitPath(path) => self.resolve_as_trait_path(*path), diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/errors.rs index 5d4d814f3ee7..4f419a2119d6 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/errors.rs @@ -194,7 +194,6 @@ pub enum InterpreterError { candidates: Vec, location: Location, }, - Unimplemented { item: String, location: Location, @@ -211,6 +210,16 @@ pub enum InterpreterError { attribute: String, location: Location, }, + GenericNameShouldBeAnIdent { + name: Rc, + location: Location, + }, + DuplicateGeneric { + name: Rc, + struct_name: String, + duplicate_location: Location, + existing_location: Location, + }, // These cases are not errors, they are just used to prevent us from running more code // until the loop can be resumed properly. These cases will never be displayed to users. @@ -279,8 +288,10 @@ impl InterpreterError { | InterpreterError::FunctionAlreadyResolved { location, .. } | InterpreterError::MultipleMatchingImpls { location, .. } | InterpreterError::ExpectedIdentForStructField { location, .. } + | InterpreterError::InvalidAttribute { location, .. } + | InterpreterError::GenericNameShouldBeAnIdent { location, .. } + | InterpreterError::DuplicateGeneric { duplicate_location: location, .. } | InterpreterError::TypeAnnotationsNeededForMethodCall { location } => *location, - InterpreterError::InvalidAttribute { location, .. } => *location, InterpreterError::FailedToParseMacro { error, file, .. } => { Location::new(error.span(), *file) @@ -589,6 +600,32 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { let secondary = "Note that this method expects attribute contents, without the leading `#[` or trailing `]`".to_string(); CustomDiagnostic::simple_error(msg, secondary, location.span) } + InterpreterError::GenericNameShouldBeAnIdent { name, location } => { + let msg = + "Generic name needs to be a valid identifier (one word beginning with a letter)" + .to_string(); + let secondary = format!("`{name}` is not a valid identifier"); + CustomDiagnostic::simple_error(msg, secondary, location.span) + } + InterpreterError::DuplicateGeneric { + name, + struct_name, + duplicate_location, + existing_location, + } => { + let msg = format!("`{struct_name}` already has a generic named `{name}`"); + let secondary = format!("`{name}` added here a second time"); + let mut error = + CustomDiagnostic::simple_error(msg, secondary, duplicate_location.span); + + let existing_msg = format!("`{name}` was previously defined here"); + error.add_secondary_with_file( + existing_msg, + existing_location.span, + existing_location.file, + ); + error + } } } } 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 9f559b7c5e66..5f58c18d66e4 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 @@ -2,6 +2,7 @@ use std::collections::VecDeque; use std::{collections::hash_map::Entry, rc::Rc}; use acvm::{acir::AcirField, FieldElement}; +use fm::FileId; use im::Vector; use iter_extended::try_vecmap; use noirc_errors::Location; @@ -10,6 +11,7 @@ use rustc_hash::FxHashMap as HashMap; use crate::ast::{BinaryOpKind, FunctionKind, IntegerBitSize, Signedness}; use crate::elaborator::Elaborator; use crate::graph::CrateId; +use crate::hir::def_map::ModuleId; use crate::hir_def::expr::ImplKind; use crate::hir_def::function::FunctionBody; use crate::macros_api::UnaryOp; @@ -170,7 +172,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Some(body) => Ok(body), None => { if matches!(&meta.function_body, FunctionBody::Unresolved(..)) { - self.elaborate_item(None, |elaborator| { + self.elaborate_in_function(None, |elaborator| { elaborator.elaborate_function(function); }); @@ -183,13 +185,25 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { } } - fn elaborate_item( + fn elaborate_in_function( &mut self, function: Option, f: impl FnOnce(&mut Elaborator) -> T, ) -> T { self.unbind_generics_from_previous_function(); - let result = self.elaborator.elaborate_item_from_comptime(function, f); + let result = self.elaborator.elaborate_item_from_comptime_in_function(function, f); + self.rebind_generics_from_previous_function(); + result + } + + fn elaborate_in_module( + &mut self, + module: ModuleId, + file: FileId, + f: impl FnOnce(&mut Elaborator) -> T, + ) -> T { + self.unbind_generics_from_previous_function(); + let result = self.elaborator.elaborate_item_from_comptime_in_module(module, file, f); self.rebind_generics_from_previous_function(); result } @@ -1244,7 +1258,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { let mut result = self.call_function(function_id, arguments, bindings, location)?; if call.is_macro_call { let expr = result.into_expression(self.elaborator.interner, location)?; - let expr = self.elaborate_item(self.current_function, |elaborator| { + let expr = self.elaborate_in_function(self.current_function, |elaborator| { elaborator.elaborate_expression(expr).0 }); result = self.evaluate(expr)?; 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 0efe944fd68b..c3aeac4aec4c 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 @@ -25,10 +25,14 @@ use crate::{ FunctionReturnType, IntegerBitSize, LValue, Literal, Statement, StatementKind, UnaryOp, UnresolvedType, UnresolvedTypeData, Visibility, }, - hir::comptime::{ - errors::IResult, - value::{ExprValue, TypedExpr}, - InterpreterError, Value, + hir::def_collector::dc_crate::CollectedItems, + hir::{ + comptime::{ + errors::IResult, + value::{ExprValue, TypedExpr}, + InterpreterError, Value, + }, + def_map::ModuleId, }, hir_def::function::FunctionBody, lexer::Lexer, @@ -36,7 +40,7 @@ use crate::{ node_interner::{DefinitionKind, TraitImplKind}, parser::{self}, token::{Attribute, SecondaryAttribute, Token}, - QuotedType, Shared, Type, + Kind, QuotedType, ResolvedGeneric, Shared, Type, TypeVariable, }; use self::builtin_helpers::{get_array, get_str, get_u8}; @@ -102,6 +106,10 @@ impl<'local, 'context> Interpreter<'local, 'context> { "function_def_has_named_attribute" => { function_def_has_named_attribute(interner, arguments, location) } + "function_def_is_unconstrained" => { + function_def_is_unconstrained(interner, arguments, location) + } + "function_def_module" => function_def_module(interner, arguments, location), "function_def_name" => function_def_name(interner, arguments, location), "function_def_parameters" => function_def_parameters(interner, arguments, location), "function_def_return_type" => function_def_return_type(interner, arguments, location), @@ -113,6 +121,10 @@ impl<'local, 'context> Interpreter<'local, 'context> { "function_def_set_return_public" => { function_def_set_return_public(self, arguments, location) } + "function_def_set_unconstrained" => { + function_def_set_unconstrained(self, arguments, location) + } + "module_add_item" => module_add_item(self, arguments, location), "module_functions" => module_functions(self, arguments, location), "module_has_named_attribute" => module_has_named_attribute(self, arguments, location), "module_is_contract" => module_is_contract(self, arguments, location), @@ -135,13 +147,16 @@ impl<'local, 'context> Interpreter<'local, 'context> { "slice_push_front" => slice_push_front(interner, arguments, location), "slice_remove" => slice_remove(interner, arguments, location, call_stack), "str_as_bytes" => str_as_bytes(interner, arguments, location), - "struct_def_add_attribute" => struct_def_add_attribute(self, arguments, location), + "struct_def_add_attribute" => struct_def_add_attribute(interner, arguments, location), + "struct_def_add_generic" => struct_def_add_generic(interner, arguments, location), "struct_def_as_type" => struct_def_as_type(interner, arguments, location), "struct_def_fields" => struct_def_fields(interner, arguments, location), "struct_def_generics" => struct_def_generics(interner, arguments, location), "struct_def_has_named_attribute" => { struct_def_has_named_attribute(interner, arguments, location) } + "struct_def_module" => struct_def_module(self, arguments, location), + "struct_def_name" => struct_def_name(interner, arguments, location), "struct_def_set_fields" => struct_def_set_fields(interner, arguments, location), "to_le_radix" => to_le_radix(arguments, return_type, location), "trait_constraint_eq" => trait_constraint_eq(interner, arguments, location), @@ -274,13 +289,13 @@ fn str_as_bytes( // fn add_attribute(self, attribute: str) fn struct_def_add_attribute( - interpreter: &mut Interpreter, + interner: &mut NodeInterner, arguments: Vec<(Value, Location)>, location: Location, ) -> IResult { let (self_argument, attribute) = check_two_arguments(arguments, location)?; let attribute_location = attribute.1; - let attribute = get_str(interpreter.elaborator.interner, attribute)?; + let attribute = get_str(interner, attribute)?; let mut tokens = Lexer::lex(&format!("#[{}]", attribute)).0 .0; if let Some(Token::EOF) = tokens.last().map(|token| token.token()) { @@ -309,13 +324,68 @@ fn struct_def_add_attribute( }; let struct_id = get_struct(self_argument)?; - interpreter.elaborator.interner.update_struct_attributes(struct_id, |attributes| { + interner.update_struct_attributes(struct_id, |attributes| { attributes.push(attribute.clone()); }); Ok(Value::Unit) } +// fn add_generic(self, generic_name: str) +fn struct_def_add_generic( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let (self_argument, generic) = check_two_arguments(arguments, location)?; + let generic_location = generic.1; + let generic = get_str(interner, generic)?; + + let mut tokens = Lexer::lex(&generic).0 .0; + if let Some(Token::EOF) = tokens.last().map(|token| token.token()) { + tokens.pop(); + } + + if tokens.len() != 1 { + return Err(InterpreterError::GenericNameShouldBeAnIdent { + name: generic, + location: generic_location, + }); + } + + let Token::Ident(generic_name) = tokens.pop().unwrap().into_token() else { + return Err(InterpreterError::GenericNameShouldBeAnIdent { + name: generic, + location: generic_location, + }); + }; + + let struct_id = get_struct(self_argument)?; + let the_struct = interner.get_struct(struct_id); + let mut the_struct = the_struct.borrow_mut(); + let name = Rc::new(generic_name); + + for generic in &the_struct.generics { + if generic.name == name { + return Err(InterpreterError::DuplicateGeneric { + name, + struct_name: the_struct.name.to_string(), + existing_location: Location::new(generic.span, the_struct.location.file), + duplicate_location: generic_location, + }); + } + } + + let type_var = TypeVariable::unbound(interner.next_type_variable_id()); + let span = generic_location.span; + let kind = Kind::Normal; + let typ = Type::NamedGeneric(type_var.clone(), name.clone(), kind.clone()); + let new_generic = ResolvedGeneric { name, type_var, span, kind }; + the_struct.generics.push(new_generic); + + Ok(Value::Type(typ)) +} + /// fn as_type(self) -> Type fn struct_def_as_type( interner: &NodeInterner, @@ -399,6 +469,39 @@ fn struct_def_fields( Ok(Value::Slice(fields, typ)) } +// fn module(self) -> Module +fn struct_def_module( + interpreter: &Interpreter, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let struct_id = get_struct(self_argument)?; + let struct_module_id = struct_id.module_id(); + + // A struct's module is its own module. To get the module where its defined we need + // to look for its parent. + let module_data = interpreter.elaborator.get_module(struct_module_id); + let parent_local_id = module_data.parent.expect("Expected struct module parent to exist"); + let parent = ModuleId { krate: struct_module_id.krate, local_id: parent_local_id }; + + Ok(Value::ModuleDefinition(parent)) +} + +// fn name(self) -> Quoted +fn struct_def_name( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let struct_id = get_struct(self_argument)?; + let the_struct = interner.get_struct(struct_id); + + let name = Token::Ident(the_struct.borrow().name.to_string()); + Ok(Value::Quoted(Rc::new(vec![name]))) +} + /// fn set_fields(self, new_fields: [(Quoted, Type)]) {} /// Returns (name, type) pairs of each field of this StructDefinition fn struct_def_set_fields( @@ -568,9 +671,10 @@ fn quoted_as_module( let path = parse(argument, parser::path_no_turbofish(), "a path").ok(); let option_value = path.and_then(|path| { - let module = interpreter.elaborate_item(interpreter.current_function, |elaborator| { - elaborator.resolve_module_by_path(path) - }); + let module = interpreter + .elaborate_in_function(interpreter.current_function, |elaborator| { + elaborator.resolve_module_by_path(path) + }); module.map(Value::ModuleDefinition) }); @@ -586,7 +690,7 @@ fn quoted_as_trait_constraint( let argument = check_one_argument(arguments, location)?; let trait_bound = parse(argument, parser::trait_bound(), "a trait constraint")?; let bound = interpreter - .elaborate_item(interpreter.current_function, |elaborator| { + .elaborate_in_function(interpreter.current_function, |elaborator| { elaborator.resolve_trait_bound(&trait_bound, Type::Unit) }) .ok_or(InterpreterError::FailedToResolveTraitBound { trait_bound, location })?; @@ -602,8 +706,8 @@ fn quoted_as_type( ) -> IResult { let argument = check_one_argument(arguments, location)?; let typ = parse(argument, parser::parse_type(), "a type")?; - let typ = - interpreter.elaborate_item(interpreter.current_function, |elab| elab.resolve_type(typ)); + let typ = interpreter + .elaborate_in_function(interpreter.current_function, |elab| elab.resolve_type(typ)); Ok(Value::Type(typ)) } @@ -1673,23 +1777,25 @@ fn expr_resolve( interpreter.current_function }; - let value = interpreter.elaborate_item(function_to_resolve_in, |elaborator| match expr_value { - ExprValue::Expression(expression_kind) => { - let expr = Expression { kind: expression_kind, span: self_argument_location.span }; - let (expr_id, _) = elaborator.elaborate_expression(expr); - Value::TypedExpr(TypedExpr::ExprId(expr_id)) - } - ExprValue::Statement(statement_kind) => { - let statement = Statement { kind: statement_kind, span: self_argument_location.span }; - let (stmt_id, _) = elaborator.elaborate_statement(statement); - Value::TypedExpr(TypedExpr::StmtId(stmt_id)) - } - ExprValue::LValue(lvalue) => { - let expr = lvalue.as_expression(); - let (expr_id, _) = elaborator.elaborate_expression(expr); - Value::TypedExpr(TypedExpr::ExprId(expr_id)) - } - }); + let value = + interpreter.elaborate_in_function(function_to_resolve_in, |elaborator| match expr_value { + ExprValue::Expression(expression_kind) => { + let expr = Expression { kind: expression_kind, span: self_argument_location.span }; + let (expr_id, _) = elaborator.elaborate_expression(expr); + Value::TypedExpr(TypedExpr::ExprId(expr_id)) + } + ExprValue::Statement(statement_kind) => { + let statement = + Statement { kind: statement_kind, span: self_argument_location.span }; + let (stmt_id, _) = elaborator.elaborate_statement(statement); + Value::TypedExpr(TypedExpr::StmtId(stmt_id)) + } + ExprValue::LValue(lvalue) => { + let expr = lvalue.as_expression(); + let (expr_id, _) = elaborator.elaborate_expression(expr); + Value::TypedExpr(TypedExpr::ExprId(expr_id)) + } + }); Ok(value) } @@ -1827,6 +1933,30 @@ fn function_def_has_named_attribute( Ok(Value::Bool(has_named_attribute(&name, attributes, location))) } +// fn is_unconstrained(self) -> bool +fn function_def_is_unconstrained( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let func_id = get_function_def(self_argument)?; + let is_unconstrained = interner.function_modifiers(&func_id).is_unconstrained; + Ok(Value::Bool(is_unconstrained)) +} + +// fn module(self) -> Module +fn function_def_module( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let func_id = get_function_def(self_argument)?; + let module = interner.function_module(func_id); + Ok(Value::ModuleDefinition(module)) +} + // fn name(self) -> Quoted fn function_def_name( interner: &NodeInterner, @@ -1945,7 +2075,7 @@ fn function_def_set_parameters( "a pattern", )?; - let hir_pattern = interpreter.elaborate_item(Some(func_id), |elaborator| { + let hir_pattern = interpreter.elaborate_in_function(Some(func_id), |elaborator| { elaborator.elaborate_pattern_and_store_ids( parameter_pattern, parameter_type.clone(), @@ -2012,6 +2142,53 @@ fn function_def_set_return_public( Ok(Value::Unit) } +// fn set_unconstrained(self, value: bool) +fn function_def_set_unconstrained( + interpreter: &mut Interpreter, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let (self_argument, unconstrained) = check_two_arguments(arguments, location)?; + + let func_id = get_function_def(self_argument)?; + check_function_not_yet_resolved(interpreter, func_id, location)?; + + let unconstrained = get_bool(unconstrained)?; + + let modifiers = interpreter.elaborator.interner.function_modifiers_mut(&func_id); + modifiers.is_unconstrained = unconstrained; + + Ok(Value::Unit) +} + +// fn add_item(self, item: Quoted) +fn module_add_item( + interpreter: &mut Interpreter, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let (self_argument, item) = check_two_arguments(arguments, location)?; + let module_id = get_module(self_argument)?; + let module_data = interpreter.elaborator.get_module(module_id); + + let parser = parser::top_level_items(); + let top_level_statements = parse(item, parser, "a top-level item")?; + + interpreter.elaborate_in_module(module_id, module_data.location.file, |elaborator| { + let mut generated_items = CollectedItems::default(); + + for top_level_statement in top_level_statements { + elaborator.add_item(top_level_statement, &mut generated_items, location); + } + + if !generated_items.is_empty() { + elaborator.elaborate_items(generated_items); + } + }); + + Ok(Value::Unit) +} + // fn functions(self) -> [FunctionDefinition] fn module_functions( interpreter: &Interpreter, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/tests.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/tests.rs index 64b489422a01..a47dbeace50c 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/tests.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/tests.rs @@ -51,7 +51,7 @@ fn interpret_helper(src: &str) -> Result { let main = context.get_main_function(&krate).expect("Expected 'main' function"); let mut elaborator = - Elaborator::elaborate_and_return_self(&mut context, krate, collector.items, None, false); + Elaborator::elaborate_and_return_self(&mut context, krate, collector.items, None); assert_eq!(elaborator.errors.len(), 0); let mut interpreter = elaborator.setup_interpreter(); 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 9387e6b17332..04c557552bd7 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 @@ -653,7 +653,13 @@ impl<'value, 'interner> Display for ValuePrinter<'value, 'interner> { Value::FunctionDefinition(function_id) => { write!(f, "{}", self.interner.function_name(function_id)) } - Value::ModuleDefinition(_) => write!(f, "(module)"), + Value::ModuleDefinition(module_id) => { + if let Some(attributes) = self.interner.try_module_attributes(module_id) { + write!(f, "{}", &attributes.name) + } else { + write!(f, "(crate root)") + } + } Value::Zeroed(typ) => write!(f, "(zeroed {typ})"), Value::Type(typ) => write!(f, "{:?}", typ), Value::Expr(ExprValue::Expression(expr)) => { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index 3cfa0989d7df..7ee1840690a8 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -271,7 +271,6 @@ impl DefCollector { ast: SortedModule, root_file_id: FileId, debug_comptime_in_file: Option<&str>, - enable_arithmetic_generics: bool, error_on_unused_items: bool, macro_processors: &[&dyn MacroProcessor], ) -> Vec<(CompilationError, FileId)> { @@ -291,7 +290,6 @@ impl DefCollector { dep.crate_id, context, debug_comptime_in_file, - enable_arithmetic_generics, error_on_usage_tracker, macro_processors, )); @@ -471,13 +469,8 @@ impl DefCollector { }) }); - let mut more_errors = Elaborator::elaborate( - context, - crate_id, - def_collector.items, - debug_comptime_in_file, - enable_arithmetic_generics, - ); + let mut more_errors = + Elaborator::elaborate(context, crate_id, def_collector.items, debug_comptime_in_file); errors.append(&mut more_errors); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs index a1c4d04cb300..75b860bf2c67 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs @@ -76,7 +76,6 @@ impl CrateDefMap { crate_id: CrateId, context: &mut Context, debug_comptime_in_file: Option<&str>, - enable_arithmetic_generics: bool, error_on_unused_imports: bool, macro_processors: &[&dyn MacroProcessor], ) -> Vec<(CompilationError, FileId)> { @@ -133,7 +132,6 @@ impl CrateDefMap { ast, root_file_id, debug_comptime_in_file, - enable_arithmetic_generics, error_on_unused_imports, macro_processors, )); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs index 2995e90ab016..66be0fdced54 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs @@ -26,8 +26,8 @@ use noirc_errors::Span; pub use parser::path::path_no_turbofish; pub use parser::traits::trait_bound; pub use parser::{ - block, expression, fresh_statement, lvalue, parse_program, parse_type, pattern, - top_level_items, visibility, + block, expression, fresh_statement, lvalue, module, parse_program, parse_type, pattern, + top_level_items, top_level_statement, visibility, }; #[derive(Debug, Clone)] diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs index 1aee697aa881..48d25e7a1d80 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs @@ -175,7 +175,7 @@ fn program() -> impl NoirParser { /// module: top_level_statement module /// | %empty -fn module() -> impl NoirParser { +pub fn module() -> impl NoirParser { recursive(|module_parser| { empty() .to(ParsedModule::default()) @@ -202,7 +202,7 @@ pub fn top_level_items() -> impl NoirParser> { /// | module_declaration /// | use_statement /// | global_declaration -fn top_level_statement<'a>( +pub fn top_level_statement<'a>( module_parser: impl NoirParser + 'a, ) -> impl NoirParser + 'a { choice(( diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs index cd075d2c374d..3e01c3701543 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs @@ -96,7 +96,6 @@ pub(crate) fn get_program(src: &str) -> (ParsedModule, Context, Vec<(Compilation }; let debug_comptime_in_file = None; - let enable_arithmetic_generics = false; let error_on_unused_imports = true; let macro_processors = &[]; @@ -107,7 +106,6 @@ pub(crate) fn get_program(src: &str) -> (ParsedModule, Context, Vec<(Compilation program.clone().into_sorted(), root_file_id, debug_comptime_in_file, - enable_arithmetic_generics, error_on_unused_imports, macro_processors, )); diff --git a/noir/noir-repo/docs/docs/noir/concepts/generics.md b/noir/noir-repo/docs/docs/noir/concepts/generics.md index 3e416eee0936..f05540f9f55c 100644 --- a/noir/noir-repo/docs/docs/noir/concepts/generics.md +++ b/noir/noir-repo/docs/docs/noir/concepts/generics.md @@ -45,17 +45,20 @@ fn main() { The `print` function will print `Hello!` an arbitrary number of times, twice in this case. +## Numeric Generics + If we want to be generic over array lengths (which are type-level integers), we can use numeric -generics. Using these looks just like using regular generics, but these generics can resolve to -integers at compile-time, rather than resolving to types. Here's an example of a struct that is -generic over the size of the array it contains internally: +generics. Using these looks similar to using regular generics, but introducing them into scope +requires declaring them with `let MyGenericName: IntegerType`. This can be done anywhere a normal +generic is declared. Instead of types, these generics resolve to integers at compile-time. +Here's an example of a struct that is generic over the size of the array it contains internally: ```rust -struct BigInt { +struct BigInt { limbs: [u32; N], } -impl BigInt { +impl BigInt { // `N` is in scope of all methods in the impl fn first(first: BigInt, second: BigInt) -> Self { assert(first.limbs != second.limbs); @@ -77,7 +80,7 @@ This is what [traits](../concepts/traits.md) are for in Noir. Here's an example any type `T` that implements the `Eq` trait for equality: ```rust -fn first_element_is_equal(array1: [T; N], array2: [T; N]) -> bool +fn first_element_is_equal(array1: [T; N], array2: [T; N]) -> bool where T: Eq { if (array1.len() == 0) | (array2.len() == 0) { @@ -161,3 +164,47 @@ fn example() { assert(10 as u32 == foo.generic_method::()); } ``` + +## Arithmetic Generics + +In addition to numeric generics, Noir also allows a limited form of arithmetic on generics. +When you have a numeric generic such as `N`, you can use the following operators on it in a +type position: `+`, `-`, `*`, `/`, and `%`. + +Note that type checking arithmetic generics is a best effort guess from the compiler and there +are many cases of types that are equal that the compiler may not see as such. For example, +we know that `T * (N + M)` should be equal to `T*N + T*M` but the compiler does not currently +apply the distributive law and thus sees these as different types. + +Even with this limitation though, the compiler can handle common cases decently well: + +```rust +trait Serialize { + fn serialize(self) -> [Field; N]; +} + +impl Serialize<1> for Field { + fn serialize(self) -> [Field; 1] { + [self] + } +} + +impl Serialize for [T; N] + where T: Serialize { .. } + +impl Serialize for (T, U) + where T: Serialize, U: Serialize { .. } + +fn main() { + let data = (1, [2, 3, 4]); + assert(data.serialize().len(), 4); +} +``` + +Note that if there is any over or underflow the types will fail to unify: + +#include_code underflow-example test_programs/compile_failure/arithmetic_generics_underflow/src/main.nr rust + +This also applies if there is underflow in an intermediate calculation: + +#include_code intermediate-underflow-example test_programs/compile_failure/arithmetic_generics_intermediate_underflow/src/main.nr rust diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/function_def.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/function_def.md index 7c7531fb24a5..63dd1fad2e51 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/meta/function_def.md +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/function_def.md @@ -11,7 +11,7 @@ a function definition in the source program. #include_code add_attribute noir_stdlib/src/meta/function_def.nr rust -Adds an attribute to the function. This is only valid +Adds an attribute to the function. This is only valid on functions in the current crate which have not yet been resolved. This means any functions called at compile-time are invalid targets for this method. @@ -19,7 +19,7 @@ This means any functions called at compile-time are invalid targets for this met #include_code body noir_stdlib/src/meta/function_def.nr rust -Returns the body of the function as an expression. This is only valid +Returns the body of the function as an expression. This is only valid on functions in the current crate which have not yet been resolved. This means any functions called at compile-time are invalid targets for this method. @@ -29,6 +29,18 @@ This means any functions called at compile-time are invalid targets for this met Returns true if this function has a custom attribute with the given name. +### is_unconstrained + +#include_code is_unconstrained noir_stdlib/src/meta/function_def.nr rust + +Returns true if this function is unconstrained. + +### module + +#include_code module noir_stdlib/src/meta/function_def.nr rust + +Returns the module where the function is defined. + ### name #include_code name noir_stdlib/src/meta/function_def.nr rust @@ -78,6 +90,14 @@ This means any functions called at compile-time are invalid targets for this met #include_code set_return_public noir_stdlib/src/meta/function_def.nr rust -Mutates the function's return visibility to public (if `true` is given) or private (if `false` is given). -This is only valid on functions in the current crate which have not yet been resolved. +Mutates the function's return visibility to public (if `true` is given) or private (if `false` is given). +This is only valid on functions in the current crate which have not yet been resolved. +This means any functions called at compile-time are invalid targets for this method. + +### set_unconstrained + +#include_code set_unconstrained noir_stdlib/src/meta/function_def.nr rust + +Mutates the function to be unconstrained (if `true` is given) or not (if `false` is given). +This is only valid on functions in the current crate which have not yet been resolved. This means any functions called at compile-time are invalid targets for this method. \ No newline at end of file diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/module.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/module.md index 870e366461c1..de042760d510 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/meta/module.md +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/module.md @@ -8,6 +8,14 @@ declarations in the source program. ## Methods +### add_item + +#include_code add_item noir_stdlib/src/meta/module.nr rust + +Adds a top-level item (a function, a struct, a global, etc.) to the module. +Adding multiple items in one go is also valid if the `Quoted` value has multiple items in it. +Note that the items are type-checked as if they are inside the module they are being added to. + ### name #include_code name noir_stdlib/src/meta/module.nr rust diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/struct_def.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/struct_def.md index 5da4a458d883..c088e538fc9a 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/meta/struct_def.md +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/struct_def.md @@ -13,6 +13,24 @@ This type corresponds to `struct Name { field1: Type1, ... }` items in the sourc Adds an attribute to the struct. +### add_generic + +#include_code add_generic noir_stdlib/src/meta/struct_def.nr rust + +Adds an generic to the struct. Returns the new generic type. +Errors if the given generic name isn't a single identifier or if +the struct already has a generic with the same name. + +This method should be used carefully, if there is existing code referring +to the struct type it may be checked before this function is called and +see the struct with the original number of generics. This method should +thus be preferred to use on code generated from other macros and structs +that are not used in function signatures. + +Example: + +#include_code add-generic-example test_programs/compile_success_empty/comptime_struct_definition/src/main.nr rust + ### as_type #include_code as_type noir_stdlib/src/meta/struct_def.nr rust @@ -56,6 +74,21 @@ Returns each field of this struct as a pair of (field name, field type). Returns true if this struct has a custom attribute with the given name. +### module + +#include_code module noir_stdlib/src/meta/struct_def.nr rust + +Returns the module where the struct is defined. + +### name + +#include_code name noir_stdlib/src/meta/struct_def.nr rust + +Returns the name of this struct + +Note that the returned quoted value will be just the struct name, it will +not be the full path to the struct, nor will it include any generics. + ### set_fields #include_code set_fields noir_stdlib/src/meta/struct_def.nr rust diff --git a/noir/noir-repo/noir_stdlib/src/cmp.nr b/noir/noir-repo/noir_stdlib/src/cmp.nr index b7f473429a7e..521604a4e202 100644 --- a/noir/noir-repo/noir_stdlib/src/cmp.nr +++ b/noir/noir-repo/noir_stdlib/src/cmp.nr @@ -11,7 +11,13 @@ trait Eq { comptime fn derive_eq(s: StructDefinition) -> Quoted { let signature = quote { fn eq(_self: Self, _other: Self) -> bool }; let for_each_field = |name| quote { (_self.$name == _other.$name) }; - let body = |fields| fields; + let body = |fields| { + if s.fields().len() == 0 { + quote { true } + } else { + fields + } + }; crate::meta::make_trait_impl(s, quote { Eq }, signature, for_each_field, quote { & }, body) } // docs:end:derive_eq diff --git a/noir/noir-repo/noir_stdlib/src/meta/function_def.nr b/noir/noir-repo/noir_stdlib/src/meta/function_def.nr index 0bff86ef1020..5ce3dbeabff1 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/function_def.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/function_def.nr @@ -14,6 +14,16 @@ impl FunctionDefinition { fn has_named_attribute(self, name: Quoted) -> bool {} // docs:end:has_named_attribute + #[builtin(function_def_is_unconstrained)] + // docs:start:is_unconstrained + fn is_unconstrained(self) -> bool {} + // docs:end:is_unconstrained + + #[builtin(function_def_module)] + // docs:start:module + fn module(self) -> Module {} + // docs:end:module + #[builtin(function_def_name)] // docs:start:name fn name(self) -> Quoted {} @@ -48,4 +58,9 @@ impl FunctionDefinition { // docs:start:set_return_public fn set_return_public(self, public: bool) {} // docs:end:set_return_public + + #[builtin(function_def_set_unconstrained)] + // docs:start:set_unconstrained + fn set_unconstrained(self, value: bool) {} + // docs:end:set_unconstrained } diff --git a/noir/noir-repo/noir_stdlib/src/meta/module.nr b/noir/noir-repo/noir_stdlib/src/meta/module.nr index b3f76812b8ab..bee6612e1bfb 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/module.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/module.nr @@ -1,4 +1,9 @@ impl Module { + #[builtin(module_add_item)] + // docs:start:add_item + fn add_item(self, item: Quoted) {} + // docs:end:add_item + #[builtin(module_has_named_attribute)] // docs:start:has_named_attribute fn has_named_attribute(self, name: Quoted) -> bool {} diff --git a/noir/noir-repo/noir_stdlib/src/meta/struct_def.nr b/noir/noir-repo/noir_stdlib/src/meta/struct_def.nr index 6c0270a8eec1..5db720b91d3e 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/struct_def.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/struct_def.nr @@ -4,10 +4,15 @@ impl StructDefinition { fn add_attribute(self, attribute: str) {} // docs:end:add_attribute + #[builtin(struct_def_add_generic)] + // docs:start:add_generic + fn add_generic(self, generic_name: str) -> Type {} + // docs:end:add_generic + /// Return a syntactic version of this struct definition as a type. /// For example, `as_type(quote { type Foo { ... } })` would return `Foo` #[builtin(struct_def_as_type)] -// docs:start:as_type + // docs:start:as_type fn as_type(self) -> Type {} // docs:end:as_type @@ -18,17 +23,27 @@ impl StructDefinition { /// Return each generic on this struct. #[builtin(struct_def_generics)] -// docs:start:generics + // docs:start:generics fn generics(self) -> [Type] {} // docs:end:generics /// Returns (name, type) pairs of each field in this struct. Each type is as-is /// with any generic arguments unchanged. #[builtin(struct_def_fields)] -// docs:start:fields + // docs:start:fields fn fields(self) -> [(Quoted, Type)] {} // docs:end:fields + #[builtin(struct_def_module)] + // docs:start:module + fn module(self) -> Module {} + // docs:end:module + + #[builtin(struct_def_name)] + // docs:start:name + fn name(self) -> Quoted {} + // docs:end:name + /// Sets the fields of this struct to the given fields list. /// All existing fields of the struct will be overridden with the given fields. /// Each element of the fields list corresponds to the name and type of a field. diff --git a/noir/noir-repo/test_programs/compile_failure/arithmetic_generics_intermediate_underflow/Nargo.toml b/noir/noir-repo/test_programs/compile_failure/arithmetic_generics_intermediate_underflow/Nargo.toml new file mode 100644 index 000000000000..0c5d98628a10 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_failure/arithmetic_generics_intermediate_underflow/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "arithmetic_generics_intermediate_underflow" +type = "bin" +authors = [""] +compiler_version = ">=0.33.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/compile_failure/arithmetic_generics_intermediate_underflow/src/main.nr b/noir/noir-repo/test_programs/compile_failure/arithmetic_generics_intermediate_underflow/src/main.nr new file mode 100644 index 000000000000..58cf2f648e5f --- /dev/null +++ b/noir/noir-repo/test_programs/compile_failure/arithmetic_generics_intermediate_underflow/src/main.nr @@ -0,0 +1,32 @@ +// docs:start:intermediate-underflow-example +fn main() { + // From main it looks like there's nothing sketchy going on + seems_fine([]); +} + +// Since `seems_fine` says it can receive and return any length N +fn seems_fine(array: [Field; N]) -> [Field; N] { + // But inside `seems_fine` we pop from the array which + // requires the length to be greater than zero. + + // error: Could not determine array length `(0 - 1)` + push_zero(pop(array)) +} + +fn pop(array: [Field; N]) -> [Field; N - 1] { + let mut result: [Field; N - 1] = std::mem::zeroed(); + for i in 0..N { + result[i] = array[i]; + } + result +} + +fn push_zero(array: [Field; N]) -> [Field; N + 1] { + let mut result: [Field; N + 1] = std::mem::zeroed(); + for i in 0..N { + result[i] = array[i]; + } + // index N is already zeroed + result +} +// docs:end:intermediate-underflow-example diff --git a/noir/noir-repo/test_programs/compile_failure/arithmetic_generics_underflow/Nargo.toml b/noir/noir-repo/test_programs/compile_failure/arithmetic_generics_underflow/Nargo.toml new file mode 100644 index 000000000000..f024f4c3b594 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_failure/arithmetic_generics_underflow/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "arithmetic_generics_underflow" +type = "bin" +authors = [""] +compiler_version = ">=0.33.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/compile_failure/arithmetic_generics_underflow/src/main.nr b/noir/noir-repo/test_programs/compile_failure/arithmetic_generics_underflow/src/main.nr new file mode 100644 index 000000000000..4df83ac56e0a --- /dev/null +++ b/noir/noir-repo/test_programs/compile_failure/arithmetic_generics_underflow/src/main.nr @@ -0,0 +1,14 @@ +// docs:start:underflow-example +fn pop(array: [Field; N]) -> [Field; N - 1] { + let mut result: [Field; N - 1] = std::mem::zeroed(); + for i in 0..N { + result[i] = array[i]; + } + result +} + +fn main() { + // error: Could not determine array length `(0 - 1)` + pop([]); +} +// docs:end:underflow-example diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_function_definition/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_function_definition/src/main.nr index 48651022b313..62f119cc0c0d 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_function_definition/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_function_definition/src/main.nr @@ -69,3 +69,20 @@ contract some_contract { fn set_pub_return(f: FunctionDefinition) { f.set_return_public(true); } + +mod foo { + #[attr] + pub fn some() {} + + fn attr(f: FunctionDefinition) { + assert_eq(f.module().name(), quote { foo }); + + assert(!f.is_unconstrained()); + + f.set_unconstrained(true); + assert(f.is_unconstrained()); + + f.set_unconstrained(false); + assert(!f.is_unconstrained()); + } +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_module/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_module/src/main.nr index 1d1690c4017c..baf45c517edb 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_module/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_module/src/main.nr @@ -42,6 +42,22 @@ fn outer_attribute_separate_module(m: Module) { increment_counter(); } +struct Foo {} + +#[add_function] +mod add_to_me { + fn add_to_me_function() {} +} + +fn add_function(m: Module) { + m.add_item( + quote { pub fn added_function() -> super::Foo { + add_to_me_function(); + super::Foo {} + } } + ); +} + fn main() { comptime { @@ -73,6 +89,8 @@ fn main() { yet_another_module::generated_outer_function(); yet_another_module::generated_inner_function(); + + let _ = add_to_me::added_function(); } // docs:start:as_module_example diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_type_definition/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/comptime_struct_definition/Nargo.toml similarity index 69% rename from noir/noir-repo/test_programs/compile_success_empty/comptime_type_definition/Nargo.toml rename to noir/noir-repo/test_programs/compile_success_empty/comptime_struct_definition/Nargo.toml index 099545a9e719..4495d27e0285 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_type_definition/Nargo.toml +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_struct_definition/Nargo.toml @@ -1,5 +1,5 @@ [package] -name = "comptime_type_definition" +name = "comptime_struct_definition" type = "bin" authors = [""] compiler_version = ">=0.31.0" diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_struct_definition/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_struct_definition/src/main.nr new file mode 100644 index 000000000000..da2871a253dd --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_struct_definition/src/main.nr @@ -0,0 +1,50 @@ +#[my_comptime_fn] +struct MyType { + field1: [A; 10], + field2: (B, C), +} + +#[mutate_struct_fields] +struct I32AndField { + z: i8, +} + +comptime fn my_comptime_fn(typ: StructDefinition) { + let _ = typ.as_type(); + assert_eq(typ.generics().len(), 3); + assert_eq(typ.fields().len(), 2); + assert_eq(typ.name(), quote { MyType }); +} + +comptime fn mutate_struct_fields(s: StructDefinition) { + let fields = &[ + (quote[x], quote[i32].as_type()), + (quote[y], quote[Field].as_type()) + ]; + s.set_fields(fields); +} + +mod foo { + #[attr] + struct Foo {} + + comptime fn attr(s: StructDefinition) { + assert_eq(s.module().name(), quote { foo }); + } + + #[add_generic] + struct Bar {} + + // docs:start:add-generic-example + comptime fn add_generic(s: StructDefinition) { + assert_eq(s.generics().len(), 0); + let new_generic = s.add_generic("T"); + + let generics = s.generics(); + assert_eq(generics.len(), 1); + assert_eq(generics[0], new_generic); + } + // docs:end:add-generic-example +} + +fn main() {} diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_type_definition/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_type_definition/src/main.nr deleted file mode 100644 index aca8d067dde3..000000000000 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_type_definition/src/main.nr +++ /dev/null @@ -1,26 +0,0 @@ -fn main() {} - -#[my_comptime_fn] -struct MyType { - field1: [A; 10], - field2: (B, C), -} - -#[mutate_struct_fields] -struct I32AndField { - z: i8, -} - -comptime fn my_comptime_fn(typ: StructDefinition) { - let _ = typ.as_type(); - assert_eq(typ.generics().len(), 3); - assert_eq(typ.fields().len(), 2); -} - -comptime fn mutate_struct_fields(s: StructDefinition) { - let fields = &[ - (quote[x], quote[i32].as_type()), - (quote[y], quote[Field].as_type()) - ]; - s.set_fields(fields); -} diff --git a/noir/noir-repo/test_programs/compile_success_empty/references_aliasing/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/references_aliasing/src/main.nr index 0d96bc2a7346..d3e4257851bc 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/references_aliasing/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/references_aliasing/src/main.nr @@ -5,6 +5,7 @@ fn main() { assert(*xref == 101); regression_2445(); + single_alias_inside_loop(); } fn increment(mut r: &mut Field) { @@ -26,3 +27,15 @@ fn regression_2445() { assert(**array[0] == 2); assert(**array[1] == 2); } + +fn single_alias_inside_loop() { + let mut var = 0; + let ref = &mut &mut var; + + for _ in 0..1 { + **ref = 2; + } + + assert(var == 2); + assert(**ref == 2); +} diff --git a/noir/noir-repo/test_programs/execution_success/derive/src/main.nr b/noir/noir-repo/test_programs/execution_success/derive/src/main.nr index 5ec2fb32a799..b32612831d71 100644 --- a/noir/noir-repo/test_programs/execution_success/derive/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/derive/src/main.nr @@ -35,6 +35,9 @@ struct MyOtherOtherStruct { x: T, } +#[derive(Eq, Default, Hash, Ord)] +struct EmptyStruct { } + fn main() { let s = MyStruct { my_field: 1 }; s.do_nothing(); @@ -53,6 +56,9 @@ fn main() { let mut hasher = TestHasher { result: 0 }; o1.hash(&mut hasher); assert_eq(hasher.finish(), 12 + 24 + 54); + + let empty = EmptyStruct {}; + assert_eq(empty, empty); } struct TestHasher { diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion.rs b/noir/noir-repo/tooling/lsp/src/requests/completion.rs index 59758f4b972f..dad0d37aba74 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion.rs @@ -5,7 +5,7 @@ use std::{ use async_lsp::ResponseError; use completion_items::{ - crate_completion_item, field_completion_item, simple_completion_item, + crate_completion_item, field_completion_item, simple_completion_item, snippet_completion_item, struct_field_completion_item, }; use convert_case::{Case, Casing}; @@ -15,11 +15,11 @@ use lsp_types::{CompletionItem, CompletionItemKind, CompletionParams, Completion use noirc_errors::{Location, Span}; use noirc_frontend::{ ast::{ - AsTraitPath, BlockExpression, CallExpression, ConstructorExpression, Expression, - ExpressionKind, ForLoopStatement, GenericTypeArgs, Ident, IfExpression, ItemVisibility, - Lambda, LetStatement, MemberAccessExpression, MethodCallExpression, NoirFunction, - NoirStruct, NoirTraitImpl, Path, PathKind, Pattern, Statement, TypeImpl, UnresolvedGeneric, - UnresolvedGenerics, UnresolvedType, UseTree, UseTreeKind, Visitor, + AsTraitPath, AttributeTarget, BlockExpression, CallExpression, ConstructorExpression, + Expression, ExpressionKind, ForLoopStatement, GenericTypeArgs, Ident, IfExpression, + ItemVisibility, Lambda, LetStatement, MemberAccessExpression, MethodCallExpression, + NoirFunction, NoirStruct, NoirTraitImpl, Path, PathKind, Pattern, Statement, TypeImpl, + UnresolvedGeneric, UnresolvedGenerics, UnresolvedType, UseTree, UseTreeKind, Visitor, }, graph::{CrateId, Dependency}, hir::def_map::{CrateDefMap, LocalModuleId, ModuleId}, @@ -27,6 +27,7 @@ use noirc_frontend::{ macros_api::{ModuleDefId, NodeInterner}, node_interner::ReferenceId, parser::{Item, ItemKind, ParsedSubModule}, + token::CustomAtrribute, ParsedModule, StructType, Type, }; use sort_text::underscore_sort_text; @@ -106,6 +107,7 @@ struct NodeFinder<'a> { nesting: usize, /// The line where an auto_import must be inserted auto_import_line: usize, + self_type: Option, } impl<'a> NodeFinder<'a> { @@ -147,6 +149,7 @@ impl<'a> NodeFinder<'a> { suggested_module_def_ids: HashSet::new(), nesting: 0, auto_import_line: 0, + self_type: None, } } @@ -191,8 +194,9 @@ impl<'a> NodeFinder<'a> { fields.remove(&field.0.contents); } + let self_prefix = false; for (field, typ) in fields { - self.completion_items.push(struct_field_completion_item(field, typ)); + self.completion_items.push(struct_field_completion_item(field, typ, self_prefix)); } } @@ -293,6 +297,7 @@ impl<'a> NodeFinder<'a> { &prefix, FunctionKind::Any, function_completion_kind, + false, // self_prefix ); return; } @@ -308,6 +313,7 @@ impl<'a> NodeFinder<'a> { &prefix, FunctionKind::Any, function_completion_kind, + false, // self_prefix ); return; } @@ -340,11 +346,21 @@ impl<'a> NodeFinder<'a> { self.local_variables_completion(&prefix); self.builtin_functions_completion(&prefix, function_completion_kind); self.builtin_values_completion(&prefix); + if let Some(self_type) = &self.self_type { + let self_prefix = true; + self.complete_type_fields_and_methods( + &self_type.clone(), + &prefix, + function_completion_kind, + self_prefix, + ); + } } RequestedItems::OnlyTypes => { self.builtin_types_completion(&prefix); self.type_parameters_completion(&prefix); } + RequestedItems::OnlyAttributeFunctions(..) => (), } self.complete_auto_imports(&prefix, requested_items, function_completion_kind); } @@ -518,16 +534,18 @@ impl<'a> NodeFinder<'a> { typ: &Type, prefix: &str, function_completion_kind: FunctionCompletionKind, + self_prefix: bool, ) { match typ { Type::Struct(struct_type, generics) => { - self.complete_struct_fields(&struct_type.borrow(), generics, prefix); + self.complete_struct_fields(&struct_type.borrow(), generics, prefix, self_prefix); } Type::MutableReference(typ) => { return self.complete_type_fields_and_methods( typ, prefix, function_completion_kind, + self_prefix, ); } Type::Alias(type_alias, _) => { @@ -536,10 +554,11 @@ impl<'a> NodeFinder<'a> { &type_alias.typ, prefix, function_completion_kind, + self_prefix, ); } Type::Tuple(types) => { - self.complete_tuple_fields(types); + self.complete_tuple_fields(types, self_prefix); } Type::FieldElement | Type::Array(_, _) @@ -565,6 +584,7 @@ impl<'a> NodeFinder<'a> { prefix, FunctionKind::SelfType(typ), function_completion_kind, + self_prefix, ); } @@ -574,6 +594,7 @@ impl<'a> NodeFinder<'a> { prefix: &str, function_kind: FunctionKind, function_completion_kind: FunctionCompletionKind, + self_prefix: bool, ) { let Some(methods_by_name) = self.interner.get_type_methods(typ) else { return; @@ -587,6 +608,8 @@ impl<'a> NodeFinder<'a> { func_id, function_completion_kind, function_kind, + None, // attribute first type + self_prefix, ) { self.completion_items.push(completion_item); self.suggested_module_def_ids.insert(ModuleDefId::FunctionId(func_id)); @@ -603,6 +626,8 @@ impl<'a> NodeFinder<'a> { function_kind: FunctionKind, function_completion_kind: FunctionCompletionKind, ) { + let self_prefix = false; + for (name, func_id) in &trait_.method_ids { if name_matches(name, prefix) { if let Some(completion_item) = self.function_completion_item( @@ -610,6 +635,8 @@ impl<'a> NodeFinder<'a> { *func_id, function_completion_kind, function_kind, + None, // attribute first type + self_prefix, ) { self.completion_items.push(completion_item); self.suggested_module_def_ids.insert(ModuleDefId::FunctionId(*func_id)); @@ -623,17 +650,19 @@ impl<'a> NodeFinder<'a> { struct_type: &StructType, generics: &[Type], prefix: &str, + self_prefix: bool, ) { for (name, typ) in &struct_type.get_fields(generics) { if name_matches(name, prefix) { - self.completion_items.push(struct_field_completion_item(name, typ)); + self.completion_items.push(struct_field_completion_item(name, typ, self_prefix)); } } } - fn complete_tuple_fields(&mut self, types: &[Type]) { + fn complete_tuple_fields(&mut self, types: &[Type], self_prefix: bool) { for (index, typ) in types.iter().enumerate() { - self.completion_items.push(field_completion_item(&index.to_string(), typ.to_string())); + let name = index.to_string(); + self.completion_items.push(field_completion_item(&name, typ.to_string(), self_prefix)); } } @@ -761,6 +790,66 @@ impl<'a> NodeFinder<'a> { None } + fn suggest_attributes(&mut self, prefix: &str, target: AttributeTarget) { + self.suggest_builtin_attributes(prefix, target); + + let function_completion_kind = FunctionCompletionKind::NameAndParameters; + let requested_items = RequestedItems::OnlyAttributeFunctions(target); + + self.complete_in_module( + self.module_id, + prefix, + PathKind::Plain, + true, + function_completion_kind, + requested_items, + ); + + self.complete_auto_imports(prefix, requested_items, function_completion_kind); + } + + fn suggest_no_arguments_attributes(&mut self, prefix: &str, attributes: &[&str]) { + for name in attributes { + if name_matches(name, prefix) { + self.completion_items.push(simple_completion_item( + *name, + CompletionItemKind::METHOD, + None, + )); + } + } + } + + fn suggest_one_argument_attributes(&mut self, prefix: &str, attributes: &[&str]) { + for name in attributes { + if name_matches(name, prefix) { + self.completion_items.push(snippet_completion_item( + format!("{}(…)", name), + CompletionItemKind::METHOD, + format!("{}(${{1:name}})", name), + None, + )); + } + } + } + + fn try_set_self_type(&mut self, pattern: &Pattern) { + match pattern { + Pattern::Identifier(ident) => { + if ident.0.contents == "self" { + let location = Location::new(ident.span(), self.file); + if let Some(ReferenceId::Local(definition_id)) = + self.interner.find_referenced(location) + { + self.self_type = Some(self.interner.definition_type(definition_id)); + } + } + } + Pattern::Mutable(pattern, ..) => self.try_set_self_type(pattern), + Pattern::Tuple(..) | Pattern::Struct(..) => (), + } + } + fn includes_span(&self, span: Span) -> bool { span.start() as usize <= self.byte_index && self.byte_index <= span.end() as usize } @@ -813,10 +902,15 @@ impl<'a> Visitor for NodeFinder<'a> { } fn visit_noir_function(&mut self, noir_function: &NoirFunction, span: Span) -> bool { + for attribute in noir_function.secondary_attributes() { + attribute.accept(AttributeTarget::Function, self); + } + let old_type_parameters = self.type_parameters.clone(); self.collect_type_parameters_in_generics(&noir_function.def.generics); for param in &noir_function.def.parameters { + self.try_set_self_type(¶m.pattern); param.typ.accept(self); } @@ -830,6 +924,7 @@ impl<'a> Visitor for NodeFinder<'a> { noir_function.def.body.accept(Some(span), self); self.type_parameters = old_type_parameters; + self.self_type = None; false } @@ -871,6 +966,10 @@ impl<'a> Visitor for NodeFinder<'a> { } fn visit_noir_struct(&mut self, noir_struct: &NoirStruct, _: Span) -> bool { + for attribute in &noir_struct.attributes { + attribute.accept(AttributeTarget::Struct, self); + } + self.type_parameters.clear(); self.collect_type_parameters_in_generics(&noir_struct.generics); @@ -945,7 +1044,13 @@ impl<'a> Visitor for NodeFinder<'a> { if let Some(typ) = self.interner.type_at_location(location) { let typ = typ.follow_bindings(); let prefix = ""; - self.complete_type_fields_and_methods(&typ, prefix, FunctionCompletionKind::Name); + let self_prefix = false; + self.complete_type_fields_and_methods( + &typ, + prefix, + FunctionCompletionKind::Name, + self_prefix, + ); return false; } } @@ -973,7 +1078,13 @@ impl<'a> Visitor for NodeFinder<'a> { let offset = self.byte_index - method_call_expression.method_name.span().start() as usize; let prefix = prefix[0..offset].to_string(); - self.complete_type_fields_and_methods(&typ, &prefix, FunctionCompletionKind::Name); + let self_prefix = false; + self.complete_type_fields_and_methods( + &typ, + &prefix, + FunctionCompletionKind::Name, + self_prefix, + ); return false; } } @@ -1042,10 +1153,12 @@ impl<'a> Visitor for NodeFinder<'a> { { let typ = self.interner.definition_type(definition_id); let prefix = ""; + let self_prefix = false; self.complete_type_fields_and_methods( &typ, prefix, FunctionCompletionKind::NameAndParameters, + self_prefix, ); } } @@ -1072,10 +1185,12 @@ impl<'a> Visitor for NodeFinder<'a> { if let Some(typ) = self.interner.type_at_location(location) { let typ = typ.follow_bindings(); let prefix = ""; + let self_prefix = false; self.complete_type_fields_and_methods( &typ, prefix, FunctionCompletionKind::NameAndParameters, + self_prefix, ); } } @@ -1136,10 +1251,12 @@ impl<'a> Visitor for NodeFinder<'a> { if let Some(typ) = self.interner.type_at_location(location) { let typ = typ.follow_bindings(); let prefix = ident.to_string().to_case(Case::Snake); + let self_prefix = false; self.complete_type_fields_and_methods( &typ, &prefix, FunctionCompletionKind::NameAndParameters, + self_prefix, ); return false; } @@ -1201,6 +1318,14 @@ impl<'a> Visitor for NodeFinder<'a> { unresolved_types.accept(self); false } + + fn visit_custom_attribute(&mut self, attribute: &CustomAtrribute, target: AttributeTarget) { + if self.byte_index != attribute.contents_span.end() as usize { + return; + } + + self.suggest_attributes(&attribute.contents, target); + } } /// Returns true if name matches a prefix written in code. diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion/builtins.rs b/noir/noir-repo/tooling/lsp/src/requests/completion/builtins.rs index f449177a0271..bca1061ff47b 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion/builtins.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/builtins.rs @@ -1,5 +1,5 @@ use lsp_types::CompletionItemKind; -use noirc_frontend::token::Keyword; +use noirc_frontend::{ast::AttributeTarget, token::Keyword}; use strum::IntoEnumIterator; use super::{ @@ -84,6 +84,40 @@ impl<'a> NodeFinder<'a> { } } } + + pub(super) fn suggest_builtin_attributes(&mut self, prefix: &str, target: AttributeTarget) { + match target { + AttributeTarget::Module => (), + AttributeTarget::Struct => { + self.suggest_one_argument_attributes(prefix, &["abi"]); + } + AttributeTarget::Function => { + let no_arguments_attributes = &[ + "contract_library_method", + "deprecated", + "export", + "fold", + "no_predicates", + "recursive", + "test", + "varargs", + ]; + self.suggest_no_arguments_attributes(prefix, no_arguments_attributes); + + let one_argument_attributes = &["abi", "field", "foreign", "oracle"]; + self.suggest_one_argument_attributes(prefix, one_argument_attributes); + + if name_matches("test", prefix) || name_matches("should_fail_with", prefix) { + self.completion_items.push(snippet_completion_item( + "test(should_fail_with=\"...\")", + CompletionItemKind::METHOD, + "test(should_fail_with=\"${1:message}\")", + None, + )); + } + } + } + } } pub(super) fn builtin_integer_types() -> [&'static str; 8] { 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 21c3a607b18d..c3afc225f525 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 @@ -2,10 +2,11 @@ use lsp_types::{ Command, CompletionItem, CompletionItemKind, CompletionItemLabelDetails, InsertTextFormat, }; use noirc_frontend::{ + ast::AttributeTarget, hir_def::{function::FuncMeta, stmt::HirPattern}, macros_api::ModuleDefId, node_interner::{FuncId, GlobalId}, - Type, + QuotedType, Type, }; use super::{ @@ -33,9 +34,25 @@ impl<'a> NodeFinder<'a> { | ModuleDefId::TypeAliasId(_) | ModuleDefId::TraitId(_) => (), }, + RequestedItems::OnlyAttributeFunctions(..) => { + if !matches!(module_def_id, ModuleDefId::FunctionId(..)) { + return None; + } + } RequestedItems::AnyItems => (), } + let attribute_first_type = + if let RequestedItems::OnlyAttributeFunctions(target) = requested_items { + match target { + AttributeTarget::Module => Some(Type::Quoted(QuotedType::Module)), + AttributeTarget::Struct => Some(Type::Quoted(QuotedType::StructDefinition)), + AttributeTarget::Function => Some(Type::Quoted(QuotedType::FunctionDefinition)), + } + } else { + None + }; + match module_def_id { ModuleDefId::ModuleId(_) => Some(module_completion_item(name)), ModuleDefId::FunctionId(func_id) => self.function_completion_item( @@ -43,6 +60,8 @@ impl<'a> NodeFinder<'a> { func_id, function_completion_kind, function_kind, + attribute_first_type.as_ref(), + false, // self_prefix ), ModuleDefId::TypeId(..) => Some(self.struct_completion_item(name)), ModuleDefId::TypeAliasId(..) => Some(self.type_alias_completion_item(name)), @@ -77,6 +96,8 @@ impl<'a> NodeFinder<'a> { func_id: FuncId, function_completion_kind: FunctionCompletionKind, function_kind: FunctionKind, + attribute_first_type: Option<&Type>, + self_prefix: bool, ) -> Option { let func_meta = self.interner.function_meta(&func_id); @@ -95,6 +116,17 @@ impl<'a> NodeFinder<'a> { None }; + if let Some(attribute_first_type) = attribute_first_type { + if func_meta.parameters.is_empty() { + return None; + } + + let (_, typ, _) = &func_meta.parameters.0[0]; + if typ != attribute_first_type { + return None; + } + } + match function_kind { FunctionKind::Any => (), FunctionKind::SelfType(mut self_type) => { @@ -135,6 +167,8 @@ impl<'a> NodeFinder<'a> { } else { false }; + let name = if self_prefix { format!("self.{}", name) } else { name.clone() }; + let name = &name; let description = func_meta_type_to_string(func_meta, func_self_type.is_some()); let completion_item = match function_completion_kind { @@ -143,7 +177,13 @@ impl<'a> NodeFinder<'a> { } FunctionCompletionKind::NameAndParameters => { let kind = CompletionItemKind::FUNCTION; - let insert_text = self.compute_function_insert_text(func_meta, name, function_kind); + let skip_first_argument = attribute_first_type.is_some(); + let insert_text = self.compute_function_insert_text( + func_meta, + name, + function_kind, + skip_first_argument, + ); let label = if insert_text.ends_with("()") { format!("{}()", name) } else { @@ -179,13 +219,19 @@ impl<'a> NodeFinder<'a> { func_meta: &FuncMeta, name: &str, function_kind: FunctionKind, + skip_first_argument: bool, ) -> String { let mut text = String::new(); text.push_str(name); text.push('('); + let mut parameters = func_meta.parameters.0.iter(); + if skip_first_argument { + parameters.next(); + } + let mut index = 1; - for (pattern, _, _) in &func_meta.parameters.0 { + for (pattern, _, _) in parameters { if index == 1 { match function_kind { FunctionKind::SelfType(_) => { @@ -294,12 +340,24 @@ 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 struct_field_completion_item( + field: &str, + typ: &Type, + self_type: bool, +) -> CompletionItem { + field_completion_item(field, typ.to_string(), self_type) } -pub(super) fn field_completion_item(field: &str, typ: impl Into) -> CompletionItem { - simple_completion_item(field, CompletionItemKind::FIELD, Some(typ.into())) +pub(super) fn field_completion_item( + field: &str, + typ: impl Into, + self_type: bool, +) -> CompletionItem { + if self_type { + simple_completion_item(format!("self.{field}"), CompletionItemKind::FIELD, Some(typ.into())) + } else { + simple_completion_item(field, CompletionItemKind::FIELD, Some(typ.into())) + } } pub(super) fn simple_completion_item( diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion/kinds.rs b/noir/noir-repo/tooling/lsp/src/requests/completion/kinds.rs index 2fe039ba3310..6fa74ffdb1a2 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion/kinds.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/kinds.rs @@ -1,4 +1,4 @@ -use noirc_frontend::Type; +use noirc_frontend::{ast::AttributeTarget, Type}; /// When suggest a function as a result of completion, whether to autocomplete its name or its name and parameters. #[derive(Clone, Copy, PartialEq, Eq, Debug)] @@ -27,4 +27,6 @@ pub(super) enum RequestedItems { AnyItems, // Only suggest types. OnlyTypes, + // Only attribute functions + OnlyAttributeFunctions(AttributeTarget), } 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 a7cfa77a73de..e6a732e9142a 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs @@ -7,8 +7,7 @@ mod completion_tests { completion_items::{ 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, + module_completion_item, simple_completion_item, snippet_completion_item, }, sort_text::{auto_import_sort_text, self_mismatch_sort_text}, }, @@ -116,6 +115,10 @@ mod completion_tests { )) } + fn field_completion_item(field: &str, typ: impl Into) -> CompletionItem { + crate::requests::completion::field_completion_item(field, typ, false) + } + #[test] async fn test_use_first_segment() { let src = r#" @@ -1888,4 +1891,65 @@ mod completion_tests { Some("(use super::barbaz)".to_string()), ); } + + #[test] + async fn test_suggests_self_fields_and_methods() { + let src = r#" + struct Foo { + foobar: Field, + } + + impl Foo { + fn foobarbaz(self) {} + + fn some_method(self) { + foob>|< + } + } + "#; + + assert_completion_excluding_auto_import( + src, + vec![ + field_completion_item("self.foobar", "Field"), + function_completion_item("self.foobarbaz()", "self.foobarbaz()", "fn(self)"), + ], + ) + .await; + } + + #[test] + async fn test_suggests_built_in_function_attribute() { + let src = r#" + #[dep>|<] + fn foo() {} + "#; + + assert_completion_excluding_auto_import( + src, + vec![simple_completion_item("deprecated", CompletionItemKind::METHOD, None)], + ) + .await; + } + + #[test] + async fn test_suggests_function_attribute() { + let src = r#" + #[some>|<] + fn foo() {} + + fn some_attr(f: FunctionDefinition, x: Field) {} + fn some_other_function(x: Field) {} + "#; + + assert_completion_excluding_auto_import( + src, + vec![function_completion_item( + "some_attr(…)", + "some_attr(${1:x})", + "fn(FunctionDefinition, Field)", + )], + ) + .await; + } } diff --git a/noir/noir-repo/tooling/nargo_cli/build.rs b/noir/noir-repo/tooling/nargo_cli/build.rs index 4dcfccdf0857..7469c8be0f8e 100644 --- a/noir/noir-repo/tooling/nargo_cli/build.rs +++ b/noir/noir-repo/tooling/nargo_cli/build.rs @@ -218,7 +218,7 @@ fn generate_compile_success_empty_tests(test_file: &mut File, test_data_dir: &Pa &test_dir, &format!( r#" - nargo.arg("info").arg("--arithmetic-generics").arg("--json").arg("--force"); + nargo.arg("info").arg("--json").arg("--force"); {assert_zero_opcodes}"#, ),