diff --git a/.noir-sync-commit b/.noir-sync-commit index cfaf9d81cf27..6d1a3f2c2323 100644 --- a/.noir-sync-commit +++ b/.noir-sync-commit @@ -1 +1 @@ -b84009ca428a5790acf53a6c027146b706170574 +3dab4dd771b7d8b9242ce3a9aeff5770f4d85cf6 diff --git a/noir/noir-repo/.gitattributes b/noir/noir-repo/.gitattributes deleted file mode 100644 index 204cff5c0973..000000000000 --- a/noir/noir-repo/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -*.nr linguist-language=rust 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 7da5efd0b5a3..e9650a625e80 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/comptime.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/comptime.rs @@ -15,7 +15,7 @@ use crate::{ }, dc_mod, }, - def_map::ModuleId, + def_map::{LocalModuleId, ModuleId}, resolution::errors::ResolverError, }, hir_def::expr::HirIdent, @@ -30,6 +30,24 @@ use crate::{ use super::{Elaborator, FunctionContext, ResolverMeta}; +#[derive(Debug, Copy, Clone)] +struct AttributeContext { + // The file where generated items should be added + file: FileId, + // The module where generated items should be added + module: LocalModuleId, + // The file where the attribute is located + attribute_file: FileId, + // The module where the attribute is located + attribute_module: LocalModuleId, +} + +impl AttributeContext { + fn new(file: FileId, module: LocalModuleId) -> Self { + Self { file, module, attribute_file: file, attribute_module: module } + } +} + impl<'context> Elaborator<'context> { /// Elaborate an expression from the middle of a comptime scope. /// When this happens we require additional information to know @@ -89,15 +107,22 @@ impl<'context> Elaborator<'context> { } } - pub(super) fn run_comptime_attributes_on_item( + fn run_comptime_attributes_on_item( &mut self, attributes: &[SecondaryAttribute], item: Value, span: Span, + attribute_context: AttributeContext, generated_items: &mut CollectedItems, ) { for attribute in attributes { - self.run_comptime_attribute_on_item(attribute, &item, span, generated_items); + self.run_comptime_attribute_on_item( + attribute, + &item, + span, + attribute_context, + generated_items, + ); } } @@ -106,6 +131,7 @@ impl<'context> Elaborator<'context> { attribute: &SecondaryAttribute, item: &Value, span: Span, + attribute_context: AttributeContext, generated_items: &mut CollectedItems, ) { if let SecondaryAttribute::Custom(attribute) = attribute { @@ -114,6 +140,7 @@ impl<'context> Elaborator<'context> { item.clone(), span, attribute.contents_span, + attribute_context, generated_items, ) { self.errors.push(error); @@ -127,8 +154,12 @@ impl<'context> Elaborator<'context> { item: Value, span: Span, attribute_span: Span, + attribute_context: AttributeContext, generated_items: &mut CollectedItems, ) -> Result<(), (CompilationError, FileId)> { + self.file = attribute_context.attribute_file; + self.local_module = attribute_context.attribute_module; + let location = Location::new(attribute_span, self.file); let Some((function, arguments)) = Self::parse_attribute(attribute, location)? else { // Do not issue an error if the attribute is unknown @@ -156,6 +187,9 @@ impl<'context> Elaborator<'context> { return Err((ResolverError::NonFunctionInAnnotation { span }.into(), self.file)); }; + self.file = attribute_context.file; + self.local_module = attribute_context.module; + let mut interpreter = self.setup_interpreter(); let mut arguments = Self::handle_attribute_arguments( &mut interpreter, @@ -463,18 +497,28 @@ impl<'context> Elaborator<'context> { let attributes = &trait_.trait_def.attributes; let item = Value::TraitDefinition(*trait_id); let span = trait_.trait_def.span; - self.local_module = trait_.module_id; - self.file = trait_.file_id; - self.run_comptime_attributes_on_item(attributes, item, span, &mut generated_items); + let context = AttributeContext::new(trait_.file_id, trait_.module_id); + self.run_comptime_attributes_on_item( + attributes, + item, + span, + context, + &mut generated_items, + ); } for (struct_id, struct_def) in types { let attributes = &struct_def.struct_def.attributes; let item = Value::StructDefinition(*struct_id); let span = struct_def.struct_def.span; - self.local_module = struct_def.module_id; - self.file = struct_def.file_id; - self.run_comptime_attributes_on_item(attributes, item, span, &mut generated_items); + let context = AttributeContext::new(struct_def.file_id, struct_def.module_id); + self.run_comptime_attributes_on_item( + attributes, + item, + span, + context, + &mut generated_items, + ); } self.run_attributes_on_functions(functions, &mut generated_items); @@ -496,10 +540,14 @@ impl<'context> Elaborator<'context> { let attribute = &module_attribute.attribute; let span = Span::default(); - self.local_module = module_attribute.attribute_module_id; - self.file = module_attribute.attribute_file_id; + let context = AttributeContext { + file: module_attribute.file_id, + module: module_attribute.module_id, + attribute_file: module_attribute.attribute_file_id, + attribute_module: module_attribute.attribute_module_id, + }; - self.run_comptime_attribute_on_item(attribute, &item, span, generated_items); + self.run_comptime_attribute_on_item(attribute, &item, span, context, generated_items); } } @@ -509,15 +557,20 @@ impl<'context> Elaborator<'context> { generated_items: &mut CollectedItems, ) { for function_set in function_sets { - self.file = function_set.file_id; self.self_type = function_set.self_type.clone(); for (local_module, function_id, function) in &function_set.functions { - self.local_module = *local_module; + let context = AttributeContext::new(function_set.file_id, *local_module); let attributes = function.secondary_attributes(); let item = Value::FunctionDefinition(*function_id); let span = function.span(); - self.run_comptime_attributes_on_item(attributes, item, span, generated_items); + self.run_comptime_attributes_on_item( + attributes, + item, + span, + context, + generated_items, + ); } } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/lints.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/lints.rs index 78df10fa94c1..8253921d305f 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/lints.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/lints.rs @@ -1,20 +1,19 @@ use crate::{ - ast::FunctionKind, + ast::{FunctionKind, Ident}, graph::CrateId, hir::{ resolution::errors::{PubPosition, ResolverError}, type_check::TypeCheckError, }, - hir_def::expr::HirIdent, + hir_def::{expr::HirIdent, function::FuncMeta}, macros_api::{ - HirExpression, HirLiteral, NodeInterner, NoirFunction, Signedness, UnaryOp, - UnresolvedTypeData, Visibility, + HirExpression, HirLiteral, NodeInterner, NoirFunction, Signedness, UnaryOp, Visibility, }, - node_interner::{DefinitionKind, ExprId, FuncId}, + node_interner::{DefinitionKind, ExprId, FuncId, FunctionModifiers}, Type, }; -use noirc_errors::Span; +use noirc_errors::{Span, Spanned}; pub(super) fn deprecated_function(interner: &NodeInterner, expr: ExprId) -> Option { let HirExpression::Ident(HirIdent { location, id, impl_kind: _ }, _) = @@ -39,16 +38,17 @@ pub(super) fn deprecated_function(interner: &NodeInterner, expr: ExprId) -> Opti /// Inline attributes are only relevant for constrained functions /// as all unconstrained functions are not inlined and so /// associated attributes are disallowed. -pub(super) fn inlining_attributes(func: &NoirFunction) -> Option { - if func.def.is_unconstrained { - let attributes = func.attributes().clone(); - - if attributes.is_no_predicates() { - Some(ResolverError::NoPredicatesAttributeOnUnconstrained { - ident: func.name_ident().clone(), - }) - } else if attributes.is_foldable() { - Some(ResolverError::FoldAttributeOnUnconstrained { ident: func.name_ident().clone() }) +pub(super) fn inlining_attributes( + func: &FuncMeta, + modifiers: &FunctionModifiers, +) -> Option { + if modifiers.is_unconstrained { + if modifiers.attributes.is_no_predicates() { + let ident = func_meta_name_ident(func, modifiers); + Some(ResolverError::NoPredicatesAttributeOnUnconstrained { ident }) + } else if modifiers.attributes.is_foldable() { + let ident = func_meta_name_ident(func, modifiers); + Some(ResolverError::FoldAttributeOnUnconstrained { ident }) } else { None } @@ -59,24 +59,30 @@ pub(super) fn inlining_attributes(func: &NoirFunction) -> Option /// Attempting to define new low level (`#[builtin]` or `#[foreign]`) functions outside of the stdlib is disallowed. pub(super) fn low_level_function_outside_stdlib( - func: &NoirFunction, + func: &FuncMeta, + modifiers: &FunctionModifiers, crate_id: CrateId, ) -> Option { let is_low_level_function = - func.attributes().function.as_ref().map_or(false, |func| func.is_low_level()); + modifiers.attributes.function.as_ref().map_or(false, |func| func.is_low_level()); if !crate_id.is_stdlib() && is_low_level_function { - Some(ResolverError::LowLevelFunctionOutsideOfStdlib { ident: func.name_ident().clone() }) + let ident = func_meta_name_ident(func, modifiers); + Some(ResolverError::LowLevelFunctionOutsideOfStdlib { ident }) } else { None } } /// Oracle definitions (functions with the `#[oracle]` attribute) must be marked as unconstrained. -pub(super) fn oracle_not_marked_unconstrained(func: &NoirFunction) -> Option { +pub(super) fn oracle_not_marked_unconstrained( + func: &FuncMeta, + modifiers: &FunctionModifiers, +) -> Option { let is_oracle_function = - func.attributes().function.as_ref().map_or(false, |func| func.is_oracle()); - if is_oracle_function && !func.def.is_unconstrained { - Some(ResolverError::OracleMarkedAsConstrained { ident: func.name_ident().clone() }) + modifiers.attributes.function.as_ref().map_or(false, |func| func.is_oracle()); + if is_oracle_function && !modifiers.is_unconstrained { + let ident = func_meta_name_ident(func, modifiers); + Some(ResolverError::OracleMarkedAsConstrained { ident }) } else { None } @@ -106,12 +112,13 @@ pub(super) fn oracle_called_from_constrained_function( } /// `pub` is required on return types for entry point functions -pub(super) fn missing_pub(func: &NoirFunction, is_entry_point: bool) -> Option { - if is_entry_point - && func.return_type().typ != UnresolvedTypeData::Unit - && func.def.return_visibility == Visibility::Private +pub(super) fn missing_pub(func: &FuncMeta, modifiers: &FunctionModifiers) -> Option { + if func.is_entry_point + && func.return_type() != &Type::Unit + && func.return_visibility == Visibility::Private { - Some(ResolverError::NecessaryPub { ident: func.name_ident().clone() }) + let ident = func_meta_name_ident(func, modifiers); + Some(ResolverError::NecessaryPub { ident }) } else { None } @@ -119,11 +126,12 @@ pub(super) fn missing_pub(func: &NoirFunction, is_entry_point: bool) -> Option Option { - if !is_entry_point && func.kind == FunctionKind::Recursive { - Some(ResolverError::MisplacedRecursiveAttribute { ident: func.name_ident().clone() }) + if !func.is_entry_point && func.kind == FunctionKind::Recursive { + let ident = func_meta_name_ident(func, modifiers); + Some(ResolverError::MisplacedRecursiveAttribute { ident }) } else { None } @@ -163,14 +171,13 @@ pub(super) fn unconstrained_function_return( /// /// Application of `pub` to other functions is not meaningful and is a mistake. pub(super) fn unnecessary_pub_return( - func: &NoirFunction, + func: &FuncMeta, + modifiers: &FunctionModifiers, is_entry_point: bool, ) -> Option { - if !is_entry_point && func.def.return_visibility == Visibility::Public { - Some(ResolverError::UnnecessaryPub { - ident: func.name_ident().clone(), - position: PubPosition::ReturnType, - }) + if !is_entry_point && func.return_visibility == Visibility::Public { + let ident = func_meta_name_ident(func, modifiers); + Some(ResolverError::UnnecessaryPub { ident, position: PubPosition::ReturnType }) } else { None } @@ -252,3 +259,7 @@ pub(crate) fn overflowing_int( errors } + +fn func_meta_name_ident(func: &FuncMeta, modifiers: &FunctionModifiers) -> Ident { + Ident(Spanned::from(func.name.location.span, modifiers.name.clone())) +} 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 161742029f6c..6b23336b5ea9 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs @@ -26,7 +26,8 @@ use crate::{ SecondaryAttribute, StructId, }, node_interner::{ - DefinitionKind, DependencyId, ExprId, FuncId, GlobalId, ReferenceId, TraitId, TypeAliasId, + DefinitionKind, DependencyId, ExprId, FuncId, FunctionModifiers, GlobalId, ReferenceId, + TraitId, TypeAliasId, }, token::CustomAtrribute, Shared, Type, TypeVariable, @@ -417,6 +418,10 @@ impl<'context> Elaborator<'context> { self.trait_bounds = func_meta.trait_constraints.clone(); self.function_context.push(FunctionContext::default()); + let modifiers = self.interner.function_modifiers(&id).clone(); + + self.run_function_lints(&func_meta, &modifiers); + self.introduce_generics_into_scope(func_meta.all_generics.clone()); // The DefinitionIds for each parameter were already created in define_function_meta @@ -731,20 +736,6 @@ impl<'context> Elaborator<'context> { let is_entry_point = self.is_entry_point_function(func, in_contract); - self.run_lint(|_| lints::inlining_attributes(func).map(Into::into)); - self.run_lint(|_| lints::missing_pub(func, is_entry_point).map(Into::into)); - self.run_lint(|elaborator| { - lints::unnecessary_pub_return(func, elaborator.pub_allowed(func, in_contract)) - .map(Into::into) - }); - self.run_lint(|_| lints::oracle_not_marked_unconstrained(func).map(Into::into)); - self.run_lint(|elaborator| { - lints::low_level_function_outside_stdlib(func, elaborator.crate_id).map(Into::into) - }); - self.run_lint(|_| { - lints::recursive_non_entrypoint_function(func, is_entry_point).map(Into::into) - }); - // Both the #[fold] and #[no_predicates] alter a function's inline type and code generation in similar ways. // In certain cases such as type checking (for which the following flag will be used) both attributes // indicate we should code generate in the same way. Thus, we unify the attributes into one flag here. @@ -858,6 +849,23 @@ impl<'context> Elaborator<'context> { self.current_item = None; } + fn run_function_lints(&mut self, func: &FuncMeta, modifiers: &FunctionModifiers) { + self.run_lint(|_| lints::inlining_attributes(func, modifiers).map(Into::into)); + self.run_lint(|_| lints::missing_pub(func, modifiers).map(Into::into)); + self.run_lint(|_| { + let pub_allowed = func.is_entry_point || modifiers.attributes.is_foldable(); + lints::unnecessary_pub_return(func, modifiers, pub_allowed).map(Into::into) + }); + self.run_lint(|_| lints::oracle_not_marked_unconstrained(func, modifiers).map(Into::into)); + self.run_lint(|elaborator| { + lints::low_level_function_outside_stdlib(func, modifiers, elaborator.crate_id) + .map(Into::into) + }); + self.run_lint(|_| { + lints::recursive_non_entrypoint_function(func, modifiers).map(Into::into) + }); + } + /// Only sized types are valid to be used as main's parameters or the parameters to a contract /// function. If the given type is not sized (e.g. contains a slice or NamedGeneric type), an /// error is issued. 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 f6585786eebf..5d4d814f3ee7 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 @@ -207,6 +207,10 @@ pub enum InterpreterError { index: usize, location: Location, }, + InvalidAttribute { + attribute: String, + 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. @@ -276,6 +280,7 @@ impl InterpreterError { | InterpreterError::MultipleMatchingImpls { location, .. } | InterpreterError::ExpectedIdentForStructField { location, .. } | InterpreterError::TypeAnnotationsNeededForMethodCall { location } => *location, + InterpreterError::InvalidAttribute { location, .. } => *location, InterpreterError::FailedToParseMacro { error, file, .. } => { Location::new(error.span(), *file) @@ -579,6 +584,11 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { let secondary = format!("`{value}` is not a valid field name for `set_fields`"); CustomDiagnostic::simple_error(msg, secondary, location.span) } + InterpreterError::InvalidAttribute { attribute, location } => { + let msg = format!("`{attribute}` is not a valid attribute"); + let secondary = "Note that this method expects attribute contents, without the leading `#[` or trailing `]`".to_string(); + CustomDiagnostic::simple_error(msg, secondary, location.span) + } } } } 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 65c9c3f018db..0efe944fd68b 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 @@ -6,13 +6,13 @@ use std::{ use acvm::{AcirField, FieldElement}; use builtin_helpers::{ block_expression_to_value, check_argument_count, check_function_not_yet_resolved, - check_one_argument, check_three_arguments, check_two_arguments, get_expr, get_field, + check_one_argument, check_three_arguments, check_two_arguments, get_bool, get_expr, get_field, get_format_string, get_function_def, get_module, get_quoted, get_slice, get_struct, get_trait_constraint, get_trait_def, get_trait_impl, get_tuple, get_type, get_typed_expr, - get_u32, get_unresolved_type, hir_pattern_to_tokens, mutate_func_meta_type, parse, - replace_func_meta_parameters, replace_func_meta_return_type, + get_u32, get_unresolved_type, has_named_attribute, hir_pattern_to_tokens, + mutate_func_meta_type, parse, replace_func_meta_parameters, replace_func_meta_return_type, }; -use chumsky::{prelude::choice, Parser}; +use chumsky::{chain::Chain, prelude::choice, Parser}; use im::Vector; use iter_extended::{try_vecmap, vecmap}; use noirc_errors::Location; @@ -25,7 +25,6 @@ use crate::{ FunctionReturnType, IntegerBitSize, LValue, Literal, Statement, StatementKind, UnaryOp, UnresolvedType, UnresolvedTypeData, Visibility, }, - elaborator::Elaborator, hir::comptime::{ errors::IResult, value::{ExprValue, TypedExpr}, @@ -36,7 +35,7 @@ use crate::{ macros_api::{HirExpression, HirLiteral, Ident, ModuleDefId, NodeInterner, Signedness}, node_interner::{DefinitionKind, TraitImplKind}, parser::{self}, - token::Token, + token::{Attribute, SecondaryAttribute, Token}, QuotedType, Shared, Type, }; @@ -97,6 +96,8 @@ impl<'local, 'context> Interpreter<'local, 'context> { "expr_resolve" => expr_resolve(self, arguments, location), "is_unconstrained" => Ok(Value::Bool(true)), "fmtstr_quoted_contents" => fmtstr_quoted_contents(interner, arguments, location), + "fresh_type_variable" => fresh_type_variable(interner), + "function_def_add_attribute" => function_def_add_attribute(self, arguments, location), "function_def_body" => function_def_body(interner, arguments, location), "function_def_has_named_attribute" => { function_def_has_named_attribute(interner, arguments, location) @@ -109,6 +110,9 @@ impl<'local, 'context> Interpreter<'local, 'context> { "function_def_set_return_type" => { function_def_set_return_type(self, arguments, location) } + "function_def_set_return_public" => { + function_def_set_return_public(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), @@ -123,6 +127,7 @@ impl<'local, 'context> Interpreter<'local, 'context> { "quoted_as_trait_constraint" => quoted_as_trait_constraint(self, arguments, location), "quoted_as_type" => quoted_as_type(self, arguments, location), "quoted_eq" => quoted_eq(arguments, location), + "quoted_tokens" => quoted_tokens(arguments, location), "slice_insert" => slice_insert(interner, arguments, location), "slice_pop_back" => slice_pop_back(interner, arguments, location, call_stack), "slice_pop_front" => slice_pop_front(interner, arguments, location, call_stack), @@ -130,9 +135,13 @@ 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_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_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), @@ -263,6 +272,50 @@ fn str_as_bytes( Ok(Value::Array(bytes, byte_array_type)) } +// fn add_attribute(self, attribute: str) +fn struct_def_add_attribute( + interpreter: &mut Interpreter, + 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 mut tokens = Lexer::lex(&format!("#[{}]", attribute)).0 .0; + if let Some(Token::EOF) = tokens.last().map(|token| token.token()) { + tokens.pop(); + } + if tokens.len() != 1 { + return Err(InterpreterError::InvalidAttribute { + attribute: attribute.to_string(), + location: attribute_location, + }); + } + + let token = tokens.into_iter().next().unwrap().into_token(); + let Token::Attribute(attribute) = token else { + return Err(InterpreterError::InvalidAttribute { + attribute: attribute.to_string(), + location: attribute_location, + }); + }; + + let Attribute::Secondary(attribute) = attribute else { + return Err(InterpreterError::InvalidAttribute { + attribute: attribute.to_string(), + location: attribute_location, + }); + }; + + let struct_id = get_struct(self_argument)?; + interpreter.elaborator.interner.update_struct_attributes(struct_id, |attributes| { + attributes.push(attribute.clone()); + }); + + Ok(Value::Unit) +} + /// fn as_type(self) -> Type fn struct_def_as_type( interner: &NodeInterner, @@ -300,6 +353,25 @@ fn struct_def_generics( Ok(Value::Slice(generics.collect(), typ)) } +// fn has_named_attribute(self, name: Quoted) -> bool +fn struct_def_has_named_attribute( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let (self_argument, name) = check_two_arguments(arguments, location)?; + let struct_id = get_struct(self_argument)?; + + let name = get_quoted(name)?; + let name = name.iter().map(|token| token.to_string()).collect::>().join(""); + + let attributes = interner.struct_attributes(&struct_id); + let attributes = attributes.iter().filter_map(|attribute| attribute.as_custom()); + let attributes = attributes.map(|attribute| &attribute.contents); + + Ok(Value::Bool(has_named_attribute(&name, attributes, location))) +} + /// fn fields(self) -> [(Quoted, Type)] /// Returns (name, type) pairs of each field of this StructDefinition fn struct_def_fields( @@ -535,6 +607,17 @@ fn quoted_as_type( Ok(Value::Type(typ)) } +// fn tokens(quoted: Quoted) -> [Quoted] +fn quoted_tokens(arguments: Vec<(Value, Location)>, location: Location) -> IResult { + let argument = check_one_argument(arguments, location)?; + let value = get_quoted(argument)?; + + Ok(Value::Slice( + value.iter().map(|token| Value::Quoted(Rc::new(vec![token.clone()]))).collect(), + Type::Slice(Box::new(Type::Quoted(QuotedType::Quoted))), + )) +} + fn to_le_radix( arguments: Vec<(Value, Location)>, return_type: Type, @@ -598,11 +681,10 @@ fn type_as_constant( location: Location, ) -> IResult { type_as(arguments, return_type, location, |typ| { - if let Type::Constant(n) = typ { - Some(Value::U32(n)) - } else { - None - } + // Prefer to use `evaluate_to_u32` over matching on `Type::Constant` + // since arithmetic generics may be `Type::InfixExpr`s which evaluate to + // constants but are not actually the `Type::Constant` variant. + typ.evaluate_to_u32().map(Value::U32) }) } @@ -704,7 +786,7 @@ where F: FnOnce(Type) -> Option, { let value = check_one_argument(arguments, location)?; - let typ = get_type(value)?; + let typ = get_type(value)?.follow_bindings(); let option_value = f(typ); @@ -733,13 +815,13 @@ fn type_get_trait_impl( let typ = get_type(typ)?; let (trait_id, generics) = get_trait_constraint(constraint)?; - let option_value = match interner.try_lookup_trait_implementation( + let option_value = match interner.lookup_trait_implementation( &typ, trait_id, &generics.ordered, &generics.named, ) { - Ok((TraitImplKind::Normal(trait_impl_id), _)) => Some(Value::TraitImpl(trait_impl_id)), + Ok(TraitImplKind::Normal(trait_impl_id)) => Some(Value::TraitImpl(trait_impl_id)), _ => None, }; @@ -758,7 +840,7 @@ fn type_implements( let (trait_id, generics) = get_trait_constraint(constraint)?; let implements = interner - .try_lookup_trait_implementation(&typ, trait_id, &generics.ordered, &generics.named) + .lookup_trait_implementation(&typ, trait_id, &generics.ordered, &generics.named) .is_ok(); Ok(Value::Bool(implements)) } @@ -1654,6 +1736,62 @@ fn fmtstr_quoted_contents( Ok(Value::Quoted(Rc::new(tokens))) } +// fn fresh_type_variable() -> Type +fn fresh_type_variable(interner: &NodeInterner) -> IResult { + Ok(Value::Type(interner.next_type_variable())) +} + +// fn add_attribute(self, attribute: str) +fn function_def_add_attribute( + interpreter: &mut Interpreter, + 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 mut tokens = Lexer::lex(&format!("#[{}]", attribute)).0 .0; + if let Some(Token::EOF) = tokens.last().map(|token| token.token()) { + tokens.pop(); + } + if tokens.len() != 1 { + return Err(InterpreterError::InvalidAttribute { + attribute: attribute.to_string(), + location: attribute_location, + }); + } + + let token = tokens.into_iter().next().unwrap().into_token(); + let Token::Attribute(attribute) = token else { + return Err(InterpreterError::InvalidAttribute { + attribute: attribute.to_string(), + location: attribute_location, + }); + }; + + let func_id = get_function_def(self_argument)?; + check_function_not_yet_resolved(interpreter, func_id, location)?; + + let function_modifiers = interpreter.elaborator.interner.function_modifiers_mut(&func_id); + + match &attribute { + Attribute::Function(attribute) => { + function_modifiers.attributes.function = Some(attribute.clone()); + } + Attribute::Secondary(attribute) => { + function_modifiers.attributes.secondary.push(attribute.clone()); + } + } + + if let Attribute::Secondary(SecondaryAttribute::Custom(attribute)) = attribute { + let func_meta = interpreter.elaborator.interner.function_meta_mut(&func_id); + func_meta.custom_attributes.push(attribute); + } + + Ok(Value::Unit) +} + // fn body(self) -> Expr fn function_def_body( interner: &NodeInterner, @@ -1678,31 +1816,15 @@ fn function_def_has_named_attribute( ) -> IResult { let (self_argument, name) = check_two_arguments(arguments, location)?; let func_id = get_function_def(self_argument)?; - let name = get_quoted(name)?; let func_meta = interner.function_meta(&func_id); - let attributes = &func_meta.custom_attributes; - if attributes.is_empty() { - return Ok(Value::Bool(false)); - }; + let name = get_quoted(name)?; let name = name.iter().map(|token| token.to_string()).collect::>().join(""); - for attribute in attributes { - let parse_result = Elaborator::parse_attribute(&attribute.contents, location); - let Ok(Some((function, _arguments))) = parse_result else { - continue; - }; - - let ExpressionKind::Variable(path) = function.kind else { - continue; - }; - - if path.last_name() == name { - return Ok(Value::Bool(true)); - } - } + let attributes = &func_meta.custom_attributes; + let attributes = attributes.iter().map(|attribute| &attribute.contents); - Ok(Value::Bool(false)) + Ok(Value::Bool(has_named_attribute(&name, attributes, location))) } // fn name(self) -> Quoted @@ -1871,6 +1993,25 @@ fn function_def_set_return_type( Ok(Value::Unit) } +// fn set_return_public(self, public: bool) +fn function_def_set_return_public( + interpreter: &mut Interpreter, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let (self_argument, public) = check_two_arguments(arguments, location)?; + + let func_id = get_function_def(self_argument)?; + check_function_not_yet_resolved(interpreter, func_id, location)?; + + let public = get_bool(public)?; + + let func_meta = interpreter.elaborator.interner.function_meta_mut(&func_id); + func_meta.return_visibility = if public { Visibility::Public } else { Visibility::Private }; + + Ok(Value::Unit) +} + // fn functions(self) -> [FunctionDefinition] fn module_functions( interpreter: &Interpreter, @@ -1904,27 +2045,13 @@ fn module_has_named_attribute( let (self_argument, name) = check_two_arguments(arguments, location)?; let module_id = get_module(self_argument)?; let module_data = interpreter.elaborator.get_module(module_id); - let name = get_quoted(name)?; + let name = get_quoted(name)?; let name = name.iter().map(|token| token.to_string()).collect::>().join(""); let attributes = module_data.outer_attributes.iter().chain(&module_data.inner_attributes); - for attribute in attributes { - let parse_result = Elaborator::parse_attribute(attribute, location); - let Ok(Some((function, _arguments))) = parse_result else { - continue; - }; - - let ExpressionKind::Variable(path) = function.kind else { - continue; - }; - - if path.last_name() == name { - return Ok(Value::Bool(true)); - } - } - Ok(Value::Bool(false)) + Ok(Value::Bool(has_named_attribute(&name, attributes, location))) } // fn is_contract(self) -> bool diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs index ff3da6d253f1..f90d50807b82 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs @@ -8,6 +8,7 @@ use crate::{ BlockExpression, ExpressionKind, IntegerBitSize, LValue, Signedness, StatementKind, UnresolvedTypeData, }, + elaborator::Elaborator, hir::{ comptime::{ errors::IResult, @@ -90,6 +91,13 @@ pub(crate) fn get_array( } } +pub(crate) fn get_bool((value, location): (Value, Location)) -> IResult { + match value { + Value::Bool(value) => Ok(value), + value => type_mismatch(value, Type::Bool, location), + } +} + pub(crate) fn get_slice( interner: &NodeInterner, (value, location): (Value, Location), @@ -437,3 +445,26 @@ pub(super) fn block_expression_to_value(block_expr: BlockExpression) -> Value { Value::Slice(statements, typ) } + +pub(super) fn has_named_attribute<'a>( + name: &'a str, + attributes: impl Iterator, + location: Location, +) -> bool { + for attribute in attributes { + let parse_result = Elaborator::parse_attribute(attribute, location); + let Ok(Some((function, _arguments))) = parse_result else { + continue; + }; + + let ExpressionKind::Variable(path) = function.kind else { + continue; + }; + + if path.last_name() == name { + return true; + } + } + + false +} 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 7d6e4475c7bd..9387e6b17332 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 @@ -655,7 +655,7 @@ impl<'value, 'interner> Display for ValuePrinter<'value, 'interner> { } Value::ModuleDefinition(_) => write!(f, "(module)"), Value::Zeroed(typ) => write!(f, "(zeroed {typ})"), - Value::Type(typ) => write!(f, "{}", typ), + Value::Type(typ) => write!(f, "{:?}", typ), Value::Expr(ExprValue::Expression(expr)) => { write!(f, "{}", remove_interned_in_expression_kind(self.interner, expr.clone())) } 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 113a4fb3888f..d47e6522756d 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 @@ -1099,13 +1099,19 @@ impl Type { | Type::Unit | Type::Constant(_) | Type::Slice(_) - | Type::TypeVariable(_, _) - | Type::NamedGeneric(_, _, _) | Type::Function(_, _, _, _) | Type::FmtString(_, _) | Type::InfixExpr(..) | Type::Error => true, + Type::TypeVariable(type_var, _) | Type::NamedGeneric(type_var, _, _) => { + if let TypeBinding::Bound(typ) = &*type_var.borrow() { + typ.is_valid_for_unconstrained_boundary() + } else { + true + } + } + // Quoted objects only exist at compile-time where the only execution // environment is the interpreter. In this environment, they are valid. Type::Quoted(_) => true, 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 596d15176bce..2995e90ab016 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs @@ -26,7 +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, + block, expression, fresh_statement, lvalue, parse_program, parse_type, pattern, + top_level_items, 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 2bc7a88c6c51..1aee697aa881 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs @@ -28,7 +28,8 @@ use self::primitives::{keyword, macro_quote_marker, mutable_reference, variable} use self::types::{generic_type_args, maybe_comp_time}; use attributes::{attributes, inner_attribute, validate_secondary_attributes}; pub use types::parse_type; -use visibility::visibility_modifier; +use visibility::item_visibility; +pub use visibility::visibility; use super::{ foldl_with_span, labels::ParsingRuleLabel, parameter_name_recovery, parameter_recovery, @@ -459,7 +460,7 @@ fn module_declaration() -> impl NoirParser { } fn use_statement() -> impl NoirParser { - visibility_modifier() + item_visibility() .then_ignore(keyword(Keyword::Use)) .then(use_tree()) .map(|(visibility, use_tree)| TopLevelStatement::Import(use_tree, visibility)) @@ -737,15 +738,6 @@ fn call_data() -> impl NoirParser { }) } -fn optional_visibility() -> impl NoirParser { - keyword(Keyword::Pub) - .map(|_| Visibility::Public) - .or(call_data()) - .or(keyword(Keyword::ReturnData).map(|_| Visibility::ReturnData)) - .or_not() - .map(|opt| opt.unwrap_or(Visibility::Private)) -} - pub fn expression() -> impl ExprParser { recursive(|expr| { expression_with_precedence( diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/function.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/function.rs index 9328c882e54f..05138bfffd9b 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/function.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/function.rs @@ -1,10 +1,10 @@ use super::{ attributes::{attributes, validate_attributes}, - block, fresh_statement, ident, keyword, maybe_comp_time, nothing, optional_visibility, - parameter_name_recovery, parameter_recovery, parenthesized, parse_type, pattern, + block, fresh_statement, ident, keyword, maybe_comp_time, nothing, parameter_name_recovery, + parameter_recovery, parenthesized, parse_type, pattern, primitives::token_kind, self_parameter, - visibility::visibility_modifier, + visibility::{item_visibility, visibility}, where_clause, NoirParser, }; use crate::token::{Keyword, Token, TokenKind}; @@ -79,13 +79,9 @@ pub(super) fn function_definition(allow_self: bool) -> impl NoirParser impl NoirParser<(bool, ItemVisibility, bool)> { - keyword(Keyword::Unconstrained) - .or_not() - .then(visibility_modifier()) - .then(maybe_comp_time()) - .map(|((unconstrained, visibility), comptime)| { - (unconstrained.is_some(), visibility, comptime) - }) + keyword(Keyword::Unconstrained).or_not().then(item_visibility()).then(maybe_comp_time()).map( + |((unconstrained, visibility), comptime)| (unconstrained.is_some(), visibility, comptime), + ) } pub(super) fn numeric_generic() -> impl NoirParser { @@ -142,14 +138,12 @@ pub(super) fn generics() -> impl NoirParser { pub(super) fn function_return_type() -> impl NoirParser<(Visibility, FunctionReturnType)> { #[allow(deprecated)] - just(Token::Arrow) - .ignore_then(optional_visibility()) - .then(spanned(parse_type())) - .or_not() - .map_with_span(|ret, span| match ret { + just(Token::Arrow).ignore_then(visibility()).then(spanned(parse_type())).or_not().map_with_span( + |ret, span| match ret { Some((visibility, (ty, _))) => (visibility, FunctionReturnType::Ty(ty)), None => (Visibility::Private, FunctionReturnType::Default(span)), - }) + }, + ) } fn function_parameters<'a>(allow_self: bool) -> impl NoirParser> + 'a { @@ -158,7 +152,7 @@ fn function_parameters<'a>(allow_self: bool) -> impl NoirParser> + 'a let full_parameter = pattern() .recover_via(parameter_name_recovery()) .then_ignore(just(Token::Colon)) - .then(optional_visibility()) + .then(visibility()) .then(typ) .map_with_span(|((pattern, visibility), typ), span| Param { visibility, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/visibility.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/visibility.rs index d9c1abf2123f..ea46becc9327 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/visibility.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/visibility.rs @@ -4,15 +4,15 @@ use chumsky::{ }; use crate::{ - ast::ItemVisibility, + ast::{ItemVisibility, Visibility}, parser::NoirParser, token::{Keyword, Token}, }; -use super::primitives::keyword; +use super::{call_data, primitives::keyword}; /// visibility_modifier: 'pub(crate)'? 'pub'? '' -pub(crate) fn visibility_modifier() -> impl NoirParser { +pub(crate) fn item_visibility() -> impl NoirParser { let is_pub_crate = (keyword(Keyword::Pub) .then_ignore(just(Token::LeftParen)) .then_ignore(keyword(Keyword::Crate)) @@ -25,3 +25,12 @@ pub(crate) fn visibility_modifier() -> impl NoirParser { choice((is_pub_crate, is_pub, is_private)) } + +pub fn visibility() -> impl NoirParser { + keyword(Keyword::Pub) + .map(|_| Visibility::Public) + .or(call_data()) + .or(keyword(Keyword::ReturnData).map(|_| Visibility::ReturnData)) + .or_not() + .map(|opt| opt.unwrap_or(Visibility::Private)) +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs index 04c4e414858a..cd075d2c374d 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs @@ -3366,7 +3366,7 @@ fn warns_on_re_export_of_item_with_less_visibility() { } #[test] -fn unoquted_integer_as_integer_token() { +fn unquoted_integer_as_integer_token() { let src = r#" trait Serialize { fn serialize() {} @@ -3377,7 +3377,7 @@ fn unoquted_integer_as_integer_token() { fn attr(_f: FunctionDefinition) -> Quoted { let serialized_len = 1; - // We are testing that when we unoqute $serialized_len, it's unquoted + // We are testing that when we unquote $serialized_len, it's unquoted // as the token `1` and not as something else that later won't be parsed correctly // in the context of a generic argument. quote { @@ -3424,3 +3424,32 @@ fn errors_on_unused_function() { assert_eq!(ident.to_string(), "foo"); assert_eq!(*item_type, "function"); } + +#[test] +fn constrained_reference_to_unconstrained() { + let src = r#" + fn main(mut x: u32, y: pub u32) { + let x_ref = &mut x; + if x == 5 { + unsafe { + mut_ref_input(x_ref, y); + } + } + + assert(x == 10); + } + + unconstrained fn mut_ref_input(x: &mut u32, y: u32) { + *x = y; + } + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::TypeError(TypeCheckError::ConstrainedReferenceToUnconstrained { .. }) = + &errors[0].0 + else { + panic!("Expected an error about passing a constrained reference to unconstrained"); + }; +} 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 5657e05fff7a..7c7531fb24a5 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 @@ -7,6 +7,14 @@ a function definition in the source program. ## Methods +### add_attribute + +#include_code add_attribute noir_stdlib/src/meta/function_def.nr rust + +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. + ### body #include_code body noir_stdlib/src/meta/function_def.nr rust @@ -65,3 +73,11 @@ each parameter pattern to be a syntactically valid parameter. Mutates the function's return type to a new type. This is only valid on functions in the current crate which have not yet been resolved. This means any functions called at compile-time are invalid targets for this method. + +### set_return_public + +#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. +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/quoted.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/quoted.md index bf79f2e5d9f6..ac075d966485 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/meta/quoted.md +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/quoted.md @@ -50,6 +50,12 @@ stream doesn't parse to a type or if the type isn't a valid type in scope. #include_code implements_example test_programs/compile_success_empty/comptime_type/src/main.nr rust +### tokens + +#include_code tokens noir_stdlib/src/meta/quoted.nr rust + +Returns a slice of the individual tokens that form this token stream. + ## Trait Implementations ```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 95e377dffd4f..5da4a458d883 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 @@ -7,6 +7,12 @@ This type corresponds to `struct Name { field1: Type1, ... }` items in the sourc ## Methods +### add_attribute + +#include_code add_attribute noir_stdlib/src/meta/struct_def.nr rust + +Adds an attribute to the struct. + ### as_type #include_code as_type noir_stdlib/src/meta/struct_def.nr rust @@ -44,6 +50,12 @@ comptime fn example(foo: StructDefinition) { Returns each field of this struct as a pair of (field name, field type). +### has_named_attribute + +#include_code has_named_attribute noir_stdlib/src/meta/struct_def.nr rust + +Returns true if this struct has a custom attribute with the given name. + ### set_fields #include_code set_fields noir_stdlib/src/meta/struct_def.nr rust diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/typ.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/typ.md index 0b6f8d5f77d4..1334092a9faa 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/meta/typ.md +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/typ.md @@ -5,6 +5,30 @@ title: Type `std::meta::typ` contains methods on the built-in `Type` type used for representing a type in the source program. +## Functions + +#include_code fresh_type_variable noir_stdlib/src/meta/typ.nr rust + +Creates and returns an unbound type variable. This is a special kind of type internal +to type checking which will type check with any other type. When it is type checked +against another type it will also be set to that type. For example, if `a` is a type +variable and we have the type equality `(a, i32) = (u8, i32)`, the compiler will set +`a` equal to `u8`. + +Unbound type variables will often be rendered as `_` while printing them. Bound type +variables will appear as the type they are bound to. + +This can be used in conjunction with functions which internally perform type checks +such as `Type::implements` or `Type::get_trait_impl` to potentially grab some of the types used. + +Note that calling `Type::implements` or `Type::get_trait_impl` on a type variable will always +fail. + +Example: + +#include_code serialize-setup test_programs/compile_success_empty/comptime_type/src/main.nr rust +#include_code fresh-type-variable-example test_programs/compile_success_empty/comptime_type/src/main.nr rust + ## Methods ### as_array 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 cbbbfb2f9018..0bff86ef1020 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/function_def.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/function_def.nr @@ -1,4 +1,9 @@ impl FunctionDefinition { + #[builtin(function_def_add_attribute)] + // docs:start:add_attribute + fn add_attribute(self, attribute: str) {} + // docs:end:add_attribute + #[builtin(function_def_body)] // docs:start:body fn body(self) -> Expr {} @@ -38,4 +43,9 @@ impl FunctionDefinition { // docs:start:set_return_type fn set_return_type(self, return_type: Type) {} // docs:end:set_return_type + + #[builtin(function_def_set_return_public)] + // docs:start:set_return_public + fn set_return_public(self, public: bool) {} + // docs:end:set_return_public } diff --git a/noir/noir-repo/noir_stdlib/src/meta/quoted.nr b/noir/noir-repo/noir_stdlib/src/meta/quoted.nr index 9fd1e9026baa..3fab43359c1f 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/quoted.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/quoted.nr @@ -3,24 +3,29 @@ use crate::option::Option; impl Quoted { #[builtin(quoted_as_expr)] -// docs:start:as_expr + // docs:start:as_expr fn as_expr(self) -> Option {} // docs:end:as_expr #[builtin(quoted_as_module)] -// docs:start:as_module + // docs:start:as_module fn as_module(self) -> Option {} // docs:end:as_module #[builtin(quoted_as_trait_constraint)] -// docs:start:as_trait_constraint + // docs:start:as_trait_constraint fn as_trait_constraint(self) -> TraitConstraint {} // docs:end:as_trait_constraint #[builtin(quoted_as_type)] -// docs:start:as_type + // docs:start:as_type fn as_type(self) -> Type {} // docs:end:as_type + + #[builtin(quoted_tokens)] + // docs:start:tokens + fn tokens(self) -> [Quoted] {} + // docs:end:tokens } impl Eq for Quoted { diff --git a/noir/noir-repo/noir_stdlib/src/meta/struct_def.nr b/noir/noir-repo/noir_stdlib/src/meta/struct_def.nr index 1ca1b6a39252..6c0270a8eec1 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/struct_def.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/struct_def.nr @@ -1,4 +1,9 @@ impl StructDefinition { + #[builtin(struct_def_add_attribute)] + // docs:start:add_attribute + fn add_attribute(self, attribute: str) {} + // docs:end:add_attribute + /// 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)] @@ -6,6 +11,11 @@ impl StructDefinition { fn as_type(self) -> Type {} // docs:end:as_type + #[builtin(struct_def_has_named_attribute)] + // docs:start:has_named_attribute + fn has_named_attribute(self, name: Quoted) -> bool {} + // docs:end:has_named_attribute + /// Return each generic on this struct. #[builtin(struct_def_generics)] // docs:start:generics diff --git a/noir/noir-repo/noir_stdlib/src/meta/typ.nr b/noir/noir-repo/noir_stdlib/src/meta/typ.nr index 12dc91a49253..71bd6fd7f1c9 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/typ.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/typ.nr @@ -1,6 +1,11 @@ use crate::cmp::Eq; use crate::option::Option; +#[builtin(fresh_type_variable)] +// docs:start:fresh_type_variable +pub fn fresh_type_variable() -> Type {} +// docs:end:fresh_type_variable + impl Type { #[builtin(type_as_array)] // docs:start:as_array diff --git a/noir/noir-repo/test_programs/compile_success_empty/attributes_struct/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/attributes_struct/src/main.nr index 0bad42aee576..25c9402035c0 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/attributes_struct/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/attributes_struct/src/main.nr @@ -6,3 +6,16 @@ struct SomeStruct { } fn main() {} + +// Check that add_attribute and has_named_attribute work well + +#[add_attribute] +struct Foo { + +} + +fn add_attribute(s: StructDefinition) { + assert(!s.has_named_attribute(quote { foo })); + s.add_attribute("foo"); + assert(s.has_named_attribute(quote { foo })); +} 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 8528bbce1536..48651022b313 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 @@ -3,7 +3,7 @@ use std::meta::type_of; struct Foo { x: Field, field: Field } #[function_attr] -fn foo(w: i32, y: Field, Foo { x, field: some_field }: Foo, mut a: bool, (b, c): (i32, i32)) -> i32 { +pub fn foo(w: i32, y: Field, Foo { x, field: some_field }: Foo, mut a: bool, (b, c): (i32, i32)) -> i32 { let _ = (w, y, x, some_field, a, b, c); 1 } @@ -57,3 +57,15 @@ comptime fn mutate_add_one(f: FunctionDefinition) { fn main() { assert_eq(add_one(41), 42); } + +contract some_contract { + // No pub on the return type is an error + #[super::set_pub_return] + pub fn bar() -> Field { + 1 + } +} + +fn set_pub_return(f: FunctionDefinition) { + f.set_return_public(true); +} 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 5722d42ca263..1d1690c4017c 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 @@ -1,8 +1,8 @@ #[outer_attribute] mod foo { #![some_attribute] - fn x() {} - fn y() {} + pub fn x() {} + pub fn y() {} } contract bar {} @@ -13,7 +13,7 @@ mod another_module {} #[outer_attribute_func] mod yet_another_module { #![super::inner_attribute_func] - fn foo() {} + pub fn foo() {} } #[outer_attribute_separate_module] @@ -25,14 +25,16 @@ fn increment_counter() { counter += 1; } -fn outer_attribute_func(m: Module) { +fn outer_attribute_func(m: Module) -> Quoted { assert_eq(m.name(), quote { yet_another_module }); increment_counter(); + quote { pub fn generated_outer_function() {} } } -fn inner_attribute_func(m: Module) { +fn inner_attribute_func(m: Module) -> Quoted { assert_eq(m.name(), quote { yet_another_module }); increment_counter(); + quote { pub fn generated_inner_function() {} } } fn outer_attribute_separate_module(m: Module) { @@ -68,6 +70,9 @@ fn main() { } assert_eq(counter, 4); + + yet_another_module::generated_outer_function(); + yet_another_module::generated_inner_function(); } // docs:start:as_module_example diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_type/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_type/src/main.nr index 0b15c5605b39..c9307570c87f 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_type/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_type/src/main.nr @@ -19,6 +19,18 @@ struct StructDoesNotImplementSomeTrait { } +// docs:start:serialize-setup +trait Serialize {} + +impl Serialize<1> for Field {} + +impl Serialize for [T; N] + where T: Serialize {} + +impl Serialize for (T, U) + where T: Serialize, U: Serialize {} +// docs:end:serialize-setup + fn main() { comptime { @@ -115,6 +127,29 @@ fn main() { let str_type = quote { str<10> }.as_type(); let constant = str_type.as_str().unwrap(); assert_eq(constant.as_constant().unwrap(), 10); + + // Check std::meta::typ::fresh_type_variable + // docs:start:fresh-type-variable-example + let typevar1 = std::meta::typ::fresh_type_variable(); + let constraint = quote { Serialize<$typevar1> }.as_trait_constraint(); + let field_type = quote { Field }.as_type(); + + // Search for a trait impl (binding typevar1 to 1 when the impl is found): + assert(field_type.implements(constraint)); + + // typevar1 should be bound to the "1" generic now: + assert_eq(typevar1.as_constant().unwrap(), 1); + + // If we want to do the same with a different type, we need to + // create a new type variable now that `typevar1` is bound + let typevar2 = std::meta::typ::fresh_type_variable(); + let constraint = quote { Serialize<$typevar2> }.as_trait_constraint(); + let array_type = quote { [(Field, Field); 5] }.as_type(); + assert(array_type.implements(constraint)); + + // Now typevar2 should be bound to the serialized pair size 2 times the array length 5 + assert_eq(typevar2.as_constant().unwrap(), 10); + // docs:end:fresh-type-variable-example } } 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 54340075b152..f449177a0271 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion/builtins.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/builtins.rs @@ -97,6 +97,7 @@ pub(super) fn keyword_builtin_type(keyword: &Keyword) -> Option<&'static str> { Keyword::Expr => Some("Expr"), Keyword::Field => Some("Field"), Keyword::FunctionDefinition => Some("FunctionDefinition"), + Keyword::Module => Some("Module"), Keyword::Quoted => Some("Quoted"), Keyword::StructDefinition => Some("StructDefinition"), Keyword::TraitConstraint => Some("TraitConstraint"), @@ -128,7 +129,6 @@ pub(super) fn keyword_builtin_type(keyword: &Keyword) -> Option<&'static str> { | Keyword::In | Keyword::Let | Keyword::Mod - | Keyword::Module | Keyword::Mut | Keyword::Pub | Keyword::Return