From 7ea4709743aaaf2768d35d92cad3fbd4a8404fb0 Mon Sep 17 00:00:00 2001 From: Aztec Bot <49558828+AztecBot@users.noreply.github.com> Date: Tue, 17 Sep 2024 13:29:45 -0400 Subject: [PATCH] feat: Sync from noir (#8543) Automated pull of development from the [noir](https://github.com/noir-lang/noir) programming language, a dependency of Aztec. BEGIN_COMMIT_OVERRIDE feat: add `Expr::as_for` and `Expr::as_for_range` (https://github.com/noir-lang/noir/pull/6039) fix: Fix canonicalization bug (https://github.com/noir-lang/noir/pull/6033) fix: Parse a statement as an expression (https://github.com/noir-lang/noir/pull/6040) chore: rename CustomAtrribute to CustomAttribute (https://github.com/noir-lang/noir/pull/6038) fix: error on `&mut x` when `x` is not mutable (https://github.com/noir-lang/noir/pull/6037) feat: add `Expr::as_constructor` (https://github.com/noir-lang/noir/pull/5980) chore: Fix docs (https://github.com/noir-lang/noir/pull/6035) chore: document array methods (https://github.com/noir-lang/noir/pull/6034) END_COMMIT_OVERRIDE --------- Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com> --- .noir-sync-commit | 2 +- .../aztec-nr/aztec/src/keys/getters/test.nr | 2 +- .../contracts/nft_contract/src/main.nr | 2 +- .../aztec_macros/src/transforms/storage.rs | 8 +- .../aztec_macros/src/utils/parse_utils.rs | 3 +- .../noirc_frontend/src/ast/expression.rs | 17 +- .../compiler/noirc_frontend/src/ast/mod.rs | 13 + .../noirc_frontend/src/ast/statement.rs | 2 +- .../noirc_frontend/src/ast/structure.rs | 3 +- .../compiler/noirc_frontend/src/ast/traits.rs | 1 + .../noirc_frontend/src/ast/visitor.rs | 9 +- .../noirc_frontend/src/elaborator/comptime.rs | 5 +- .../src/elaborator/expressions.rs | 92 +++- .../noirc_frontend/src/elaborator/mod.rs | 4 +- .../noirc_frontend/src/elaborator/patterns.rs | 1 + .../noirc_frontend/src/elaborator/scope.rs | 11 +- .../noirc_frontend/src/hir/comptime/errors.rs | 4 +- .../src/hir/comptime/hir_to_display_ast.rs | 2 +- .../src/hir/comptime/interpreter.rs | 38 +- .../src/hir/comptime/interpreter/builtin.rs | 213 ++++++++-- .../interpreter/builtin/builtin_helpers.rs | 10 +- .../noirc_frontend/src/hir/comptime/value.rs | 42 +- .../src/hir/def_collector/dc_mod.rs | 31 +- .../src/hir/def_map/module_data.rs | 18 +- .../src/hir/resolution/errors.rs | 11 +- .../src/hir/type_check/errors.rs | 5 +- .../noirc_frontend/src/hir_def/function.rs | 4 +- .../src/hir_def/types/arithmetic.rs | 22 +- .../noirc_frontend/src/lexer/lexer.rs | 6 +- .../noirc_frontend/src/lexer/token.rs | 25 +- .../compiler/noirc_frontend/src/locations.rs | 3 +- .../noirc_frontend/src/parser/parser.rs | 22 +- .../src/parser/parser/primitives.rs | 22 +- .../src/parser/parser/structs.rs | 13 +- .../src/parser/parser/traits.rs | 27 +- .../compiler/noirc_frontend/src/tests.rs | 183 +++++++- .../noirc_frontend/src/usage_tracker.rs | 7 +- .../docs/docs/noir/concepts/comptime.md | 11 + .../docs/noir/concepts/data_types/arrays.md | 1 - .../docs/noir/concepts/data_types/structs.md | 12 + .../docs/docs/noir/concepts/traits.md | 10 + .../docs/noir/standard_library/meta/expr.md | 34 ++ noir/noir-repo/noir_stdlib/src/append.nr | 2 +- noir/noir-repo/noir_stdlib/src/array/mod.nr | 143 ++++++- noir/noir-repo/noir_stdlib/src/bigint.nr | 16 +- noir/noir-repo/noir_stdlib/src/cmp.nr | 6 +- .../src/collections/bounded_vec.nr | 2 +- .../noir_stdlib/src/collections/map.nr | 2 +- .../noir_stdlib/src/collections/umap.nr | 2 +- .../noir_stdlib/src/collections/vec.nr | 2 +- noir/noir-repo/noir_stdlib/src/convert.nr | 4 +- noir/noir-repo/noir_stdlib/src/default.nr | 2 +- .../noir-repo/noir_stdlib/src/ec/consts/te.nr | 2 +- .../noir-repo/noir_stdlib/src/ec/montcurve.nr | 8 +- noir/noir-repo/noir_stdlib/src/ec/swcurve.nr | 8 +- noir/noir-repo/noir_stdlib/src/ec/tecurve.nr | 8 +- .../noir_stdlib/src/embedded_curve_ops.nr | 4 +- noir/noir-repo/noir_stdlib/src/hash/mimc.nr | 2 +- noir/noir-repo/noir_stdlib/src/hash/mod.nr | 8 +- .../noir_stdlib/src/hash/poseidon/mod.nr | 4 +- .../noir_stdlib/src/hash/poseidon2.nr | 4 +- .../noir_stdlib/src/meta/ctstring.nr | 4 +- noir/noir-repo/noir_stdlib/src/meta/expr.nr | 398 +++++++++--------- noir/noir-repo/noir_stdlib/src/meta/mod.nr | 10 + noir/noir-repo/noir_stdlib/src/meta/op.nr | 4 +- noir/noir-repo/noir_stdlib/src/ops/arith.nr | 12 +- noir/noir-repo/noir_stdlib/src/ops/bit.nr | 12 +- noir/noir-repo/noir_stdlib/src/option.nr | 2 +- noir/noir-repo/noir_stdlib/src/test.nr | 2 +- noir/noir-repo/noir_stdlib/src/uint128.nr | 2 +- .../arithmetic_generics/src/main.nr | 7 +- .../Nargo.toml | 7 + .../src/main.nr | 27 ++ .../comptime_to_radix/Nargo.toml | 6 + .../comptime_to_radix/src/main.nr | 10 + .../use_callers_scope/Nargo.toml | 7 + .../use_callers_scope/src/main.nr | 34 ++ .../comptime_expr/src/main.nr | 123 ++++++ noir/noir-repo/tooling/lsp/src/lib.rs | 24 +- .../tooling/lsp/src/notifications/mod.rs | 23 +- .../code_action/fill_struct_fields.rs | 12 +- .../requests/code_action/import_or_qualify.rs | 8 +- .../lsp/src/requests/code_lens_request.rs | 2 +- .../tooling/lsp/src/requests/completion.rs | 180 +++++++- .../lsp/src/requests/completion/tests.rs | 106 ++++- .../tooling/lsp/src/requests/inlay_hint.rs | 1 + .../noir-repo/tooling/lsp/src/requests/mod.rs | 82 +++- .../tooling/lsp/src/requests/references.rs | 2 +- .../tooling/lsp/src/requests/rename.rs | 2 +- .../tooling/lsp/src/requests/test_run.rs | 2 +- .../tooling/nargo_fmt/src/rewrite/expr.rs | 11 +- 91 files changed, 1802 insertions(+), 500 deletions(-) create mode 100644 noir/noir-repo/test_programs/compile_success_empty/comptime_parse_statement_as_expression/Nargo.toml create mode 100644 noir/noir-repo/test_programs/compile_success_empty/comptime_parse_statement_as_expression/src/main.nr create mode 100644 noir/noir-repo/test_programs/compile_success_empty/comptime_to_radix/Nargo.toml create mode 100644 noir/noir-repo/test_programs/compile_success_empty/comptime_to_radix/src/main.nr create mode 100644 noir/noir-repo/test_programs/compile_success_empty/use_callers_scope/Nargo.toml create mode 100644 noir/noir-repo/test_programs/compile_success_empty/use_callers_scope/src/main.nr diff --git a/.noir-sync-commit b/.noir-sync-commit index ebc0e1fd902..7612c2a3e18 100644 --- a/.noir-sync-commit +++ b/.noir-sync-commit @@ -1 +1 @@ -fc74c55ffed892962413c6fe15af62e1d2e7b785 +5bbd9ba9a6d6494fd16813b44036b78c871f6613 diff --git a/noir-projects/aztec-nr/aztec/src/keys/getters/test.nr b/noir-projects/aztec-nr/aztec/src/keys/getters/test.nr index d83594e04f0..f6a54b19a72 100644 --- a/noir-projects/aztec-nr/aztec/src/keys/getters/test.nr +++ b/noir-projects/aztec-nr/aztec/src/keys/getters/test.nr @@ -18,7 +18,7 @@ fn setup() -> (TestEnvironment, PrivateContext, TestAccount) { #[test(should_fail_with="Invalid public keys hint for address")] fn test_get_current_keys_unknown_unregistered() { - let (_, context, account) = setup(); + let (_, mut context, account) = setup(); let _ = OracleMock::mock("getPublicKeysAndPartialAddress").returns([0; KEY_ORACLE_RESPONSE_LENGTH]).times(1); let _ = get_current_public_keys(&mut context, account.address); diff --git a/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr b/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr index dc11a6861c4..2dfa13de545 100644 --- a/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr @@ -233,7 +233,7 @@ contract NFT { let from_ovpk_m = get_current_public_keys(&mut context, from).ovpk_m; let to_keys = get_current_public_keys(&mut context, to); - let new_note = NFTNote::new(token_id, to_keys.npk_m.hash()); + let mut new_note = NFTNote::new(token_id, to_keys.npk_m.hash()); nfts.at(to).insert(&mut new_note).emit(encode_and_encrypt_note_with_keys(&mut context, from_ovpk_m, to_keys.ivpk_m, to)); } diff --git a/noir/noir-repo/aztec_macros/src/transforms/storage.rs b/noir/noir-repo/aztec_macros/src/transforms/storage.rs index a6bf2e14fb3..d056dfc4412 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/storage.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/storage.rs @@ -222,9 +222,11 @@ pub fn generate_storage_implementation( }) .collect(); - let storage_constructor_statement = make_statement(StatementKind::Expression(expression( - ExpressionKind::constructor((chained_path!(storage_struct_name), field_constructors)), - ))); + let storage_constructor_statement = + make_statement(StatementKind::Expression(expression(ExpressionKind::constructor(( + UnresolvedType::from_path(chained_path!(storage_struct_name)), + field_constructors, + ))))); // This is the type over which the impl is generic. let generic_context_ident = ident("Context"); diff --git a/noir/noir-repo/aztec_macros/src/utils/parse_utils.rs b/noir/noir-repo/aztec_macros/src/utils/parse_utils.rs index cdad842dd05..61410bbb5b2 100644 --- a/noir/noir-repo/aztec_macros/src/utils/parse_utils.rs +++ b/noir/noir-repo/aztec_macros/src/utils/parse_utils.rs @@ -299,6 +299,7 @@ fn empty_expression(expression: &mut Expression) { ExpressionKind::Quote(..) | ExpressionKind::Resolved(_) | ExpressionKind::Interned(_) + | ExpressionKind::InternedStatement(_) | ExpressionKind::Error => (), } } @@ -505,7 +506,7 @@ fn empty_method_call_expression(method_call_expression: &mut MethodCallExpressio } fn empty_constructor_expression(constructor_expression: &mut ConstructorExpression) { - empty_path(&mut constructor_expression.type_name); + empty_unresolved_type(&mut constructor_expression.typ); for (name, expression) in constructor_expression.fields.iter_mut() { empty_ident(name); empty_expression(expression); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs index f242180134d..cce8a50601e 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs @@ -7,7 +7,7 @@ use crate::ast::{ }; use crate::hir::def_collector::errors::DefCollectorErrorKind; use crate::macros_api::StructId; -use crate::node_interner::{ExprId, InternedExpressionKind, QuotedTypeId}; +use crate::node_interner::{ExprId, InternedExpressionKind, InternedStatementKind, QuotedTypeId}; use crate::token::{Attributes, FunctionAttribute, Token, Tokens}; use crate::{Kind, Type}; use acvm::{acir::AcirField, FieldElement}; @@ -48,6 +48,10 @@ pub enum ExpressionKind { // The actual ExpressionKind can be retrieved with a NodeInterner. Interned(InternedExpressionKind), + /// Interned statements are allowed to be parsed as expressions in case they resolve + /// to an StatementKind::Expression or StatementKind::Semi. + InternedStatement(InternedStatementKind), + Error, } @@ -200,9 +204,11 @@ impl ExpressionKind { ExpressionKind::Literal(Literal::FmtStr(contents)) } - pub fn constructor((type_name, fields): (Path, Vec<(Ident, Expression)>)) -> ExpressionKind { + pub fn constructor( + (typ, fields): (UnresolvedType, Vec<(Ident, Expression)>), + ) -> ExpressionKind { ExpressionKind::Constructor(Box::new(ConstructorExpression { - type_name, + typ, fields, struct_type: None, })) @@ -536,7 +542,7 @@ pub struct MethodCallExpression { #[derive(Debug, PartialEq, Eq, Clone)] pub struct ConstructorExpression { - pub type_name: Path, + pub typ: UnresolvedType, pub fields: Vec<(Ident, Expression)>, /// This may be filled out during macro expansion @@ -615,6 +621,7 @@ impl Display for ExpressionKind { write!(f, "quote {{ {} }}", tokens.join(" ")) } AsTraitPath(path) => write!(f, "{path}"), + InternedStatement(_) => write!(f, "?InternedStatement"), } } } @@ -717,7 +724,7 @@ impl Display for ConstructorExpression { let fields = self.fields.iter().map(|(ident, expr)| format!("{ident}: {expr}")).collect::>(); - write!(f, "({} {{ {} }})", self.type_name, fields.join(", ")) + write!(f, "({} {{ {} }})", self.typ, fields.join(", ")) } } 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 12a8aec05eb..fe9153c2cf4 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/mod.rs @@ -344,6 +344,19 @@ impl UnresolvedType { pub(crate) fn is_type_expression(&self) -> bool { matches!(&self.typ, UnresolvedTypeData::Expression(_)) } + + pub fn from_path(mut path: Path) -> Self { + let span = path.span; + let last_segment = path.segments.last_mut().unwrap(); + let generics = last_segment.generics.take(); + let generic_type_args = if let Some(generics) = generics { + GenericTypeArgs { ordered_args: generics, named_args: Vec::new() } + } else { + GenericTypeArgs::default() + }; + let typ = UnresolvedTypeData::Named(path, generic_type_args, true); + UnresolvedType { typ, span } + } } impl UnresolvedTypeData { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs index 52c39a49e8a..dbbb5212369 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs @@ -620,7 +620,7 @@ impl Pattern { } Some(Expression { kind: ExpressionKind::Constructor(Box::new(ConstructorExpression { - type_name: path.clone(), + typ: UnresolvedType::from_path(path.clone()), fields, struct_type: None, })), diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/structure.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/structure.rs index cd42abb29c7..1675629ff14 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/structure.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/structure.rs @@ -6,13 +6,14 @@ use crate::token::SecondaryAttribute; use iter_extended::vecmap; use noirc_errors::Span; -use super::Documented; +use super::{Documented, ItemVisibility}; /// Ast node for a struct #[derive(Clone, Debug, PartialEq, Eq)] pub struct NoirStruct { pub name: Ident, pub attributes: Vec, + pub visibility: ItemVisibility, pub generics: UnresolvedGenerics, pub fields: Vec>, pub span: Span, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs index 9d3bd0dfa77..3df9939dc70 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs @@ -22,6 +22,7 @@ pub struct NoirTrait { pub span: Span, pub items: Vec>, pub attributes: Vec, + pub visibility: ItemVisibility, } /// Any declaration inside the body of a trait that a user is required to 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 755b3bc670e..abf76545f0a 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::{ InternedUnresolvedTypeData, QuotedTypeId, }, parser::{Item, ItemKind, ParsedSubModule}, - token::{CustomAtrribute, SecondaryAttribute, Tokens}, + token::{CustomAttribute, SecondaryAttribute, Tokens}, ParsedModule, QuotedType, }; @@ -461,7 +461,7 @@ pub trait Visitor { true } - fn visit_custom_attribute(&mut self, _: &CustomAtrribute, _target: AttributeTarget) {} + fn visit_custom_attribute(&mut self, _: &CustomAttribute, _target: AttributeTarget) {} } impl ParsedModule { @@ -841,6 +841,7 @@ impl Expression { ExpressionKind::Quote(tokens) => visitor.visit_quote(tokens), ExpressionKind::Resolved(expr_id) => visitor.visit_resolved_expression(*expr_id), ExpressionKind::Interned(id) => visitor.visit_interned_expression(*id), + ExpressionKind::InternedStatement(id) => visitor.visit_interned_statement(*id), ExpressionKind::Error => visitor.visit_error_expression(), } } @@ -948,7 +949,7 @@ impl ConstructorExpression { } pub fn accept_children(&self, visitor: &mut impl Visitor) { - self.type_name.accept(visitor); + self.typ.accept(visitor); for (_field_name, expression) in &self.fields { expression.accept(visitor); @@ -1377,7 +1378,7 @@ impl SecondaryAttribute { } } -impl CustomAtrribute { +impl CustomAttribute { 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 c2347adcbee..ca441758322 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/comptime.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/comptime.rs @@ -102,6 +102,9 @@ impl<'context> Elaborator<'context> { elaborator.function_context.push(FunctionContext::default()); elaborator.scopes.start_function(); + elaborator.local_module = self.local_module; + elaborator.file = self.file; + setup(&mut elaborator); elaborator.populate_scope_from_comptime_scopes(); @@ -316,7 +319,7 @@ impl<'context> Elaborator<'context> { // If the function is varargs, push the type of the last slice element N times // to account for N extra arguments. let modifiers = interpreter.elaborator.interner.function_modifiers(&function); - let is_varargs = modifiers.attributes.is_varargs(); + let is_varargs = modifiers.attributes.has_varargs(); let varargs_type = if is_varargs { parameters.pop() } else { None }; let varargs_elem_type = varargs_type.as_ref().and_then(|t| t.slice_element_type()); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs index 15d6ed5506b..311c8f401be 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -5,8 +5,8 @@ use rustc_hash::FxHashSet as HashSet; use crate::{ ast::{ - ArrayLiteral, ConstructorExpression, IfExpression, InfixExpression, Lambda, - UnresolvedTypeExpression, + ArrayLiteral, ConstructorExpression, IfExpression, InfixExpression, Lambda, UnaryOp, + UnresolvedTypeData, UnresolvedTypeExpression, }, hir::{ comptime::{self, InterpreterError}, @@ -25,9 +25,9 @@ use crate::{ macros_api::{ BlockExpression, CallExpression, CastExpression, Expression, ExpressionKind, HirLiteral, HirStatement, Ident, IndexExpression, Literal, MemberAccessExpression, - MethodCallExpression, PrefixExpression, + MethodCallExpression, PrefixExpression, StatementKind, }, - node_interner::{DefinitionKind, ExprId, FuncId, TraitMethodId}, + node_interner::{DefinitionKind, ExprId, FuncId, InternedStatementKind, TraitMethodId}, token::Tokens, QuotedType, Shared, StructType, Type, }; @@ -67,6 +67,9 @@ impl<'context> Elaborator<'context> { let expr = Expression::new(expr_kind.clone(), expr.span); return self.elaborate_expression(expr); } + ExpressionKind::InternedStatement(id) => { + return self.elaborate_interned_statement_as_expr(id, expr.span); + } ExpressionKind::Error => (HirExpression::Error, Type::Error), ExpressionKind::Unquote(_) => { self.push_err(ResolverError::UnquoteUsedOutsideQuote { span: expr.span }); @@ -80,6 +83,29 @@ impl<'context> Elaborator<'context> { (id, typ) } + fn elaborate_interned_statement_as_expr( + &mut self, + id: InternedStatementKind, + span: Span, + ) -> (ExprId, Type) { + match self.interner.get_statement_kind(id) { + StatementKind::Expression(expr) | StatementKind::Semi(expr) => { + self.elaborate_expression(expr.clone()) + } + StatementKind::Interned(id) => self.elaborate_interned_statement_as_expr(*id, span), + StatementKind::Error => { + let expr = Expression::new(ExpressionKind::Error, span); + self.elaborate_expression(expr) + } + other => { + let statement = other.to_string(); + self.push_err(ResolverError::InvalidInternedStatementInExpr { statement, span }); + let expr = Expression::new(ExpressionKind::Error, span); + self.elaborate_expression(expr) + } + } + } + pub(super) fn elaborate_block(&mut self, block: BlockExpression) -> (HirExpression, Type) { let (block, typ) = self.elaborate_block_expression(block); (HirExpression::Block(block), typ) @@ -248,10 +274,17 @@ impl<'context> Elaborator<'context> { } fn elaborate_prefix(&mut self, prefix: PrefixExpression, span: Span) -> (ExprId, Type) { + let rhs_span = prefix.rhs.span; + let (rhs, rhs_type) = self.elaborate_expression(prefix.rhs); let trait_id = self.interner.get_prefix_operator_trait_method(&prefix.operator); let operator = prefix.operator; + + if let UnaryOp::MutableReference = operator { + self.check_can_mutate(rhs, rhs_span); + } + let expr = HirExpression::Prefix(HirPrefixExpression { operator, rhs, trait_method_id: trait_id }); let expr_id = self.interner.push_expr(expr); @@ -264,6 +297,26 @@ impl<'context> Elaborator<'context> { (expr_id, typ) } + fn check_can_mutate(&mut self, expr_id: ExprId, span: Span) { + let expr = self.interner.expression(&expr_id); + match expr { + HirExpression::Ident(hir_ident, _) => { + if let Some(definition) = self.interner.try_definition(hir_ident.id) { + if !definition.mutable { + self.push_err(TypeCheckError::CannotMutateImmutableVariable { + name: definition.name.clone(), + span, + }); + } + } + } + HirExpression::MemberAccess(member_access) => { + self.check_can_mutate(member_access.lhs, span); + } + _ => (), + } + } + fn elaborate_index(&mut self, index_expr: IndexExpression) -> (HirExpression, Type) { let span = index_expr.index.span; let (index, index_type) = self.elaborate_expression(index_expr.index); @@ -436,11 +489,29 @@ impl<'context> Elaborator<'context> { &mut self, constructor: ConstructorExpression, ) -> (HirExpression, Type) { + let span = constructor.typ.span; + + // A constructor type can either be a Path or an interned UnresolvedType. + // We represent both as UnresolvedType (with Path being a Named UnresolvedType) + // and error if we don't get a Named path. + let mut typ = constructor.typ.typ; + if let UnresolvedTypeData::Interned(id) = typ { + typ = self.interner.get_unresolved_type_data(id).clone(); + } + let UnresolvedTypeData::Named(mut path, generics, _) = typ else { + self.push_err(ResolverError::NonStructUsedInConstructor { typ: typ.to_string(), span }); + return (HirExpression::Error, Type::Error); + }; + + let last_segment = path.segments.last_mut().unwrap(); + if !generics.ordered_args.is_empty() { + last_segment.generics = Some(generics.ordered_args); + } + let exclude_last_segment = true; - self.check_unsupported_turbofish_usage(&constructor.type_name, exclude_last_segment); + self.check_unsupported_turbofish_usage(&path, exclude_last_segment); - let span = constructor.type_name.span(); - let last_segment = constructor.type_name.last_segment(); + let last_segment = path.last_segment(); let is_self_type = last_segment.ident.is_self_type_name(); let (r#type, struct_generics) = if let Some(struct_id) = constructor.struct_type { @@ -448,10 +519,13 @@ impl<'context> Elaborator<'context> { let generics = typ.borrow().instantiate(self.interner); (typ, generics) } else { - match self.lookup_type_or_error(constructor.type_name) { + match self.lookup_type_or_error(path) { Some(Type::Struct(r#type, struct_generics)) => (r#type, struct_generics), Some(typ) => { - self.push_err(ResolverError::NonStructUsedInConstructor { typ, span }); + self.push_err(ResolverError::NonStructUsedInConstructor { + typ: typ.to_string(), + span, + }); return (HirExpression::Error, Type::Error); } None => return (HirExpression::Error, Type::Error), diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs index 6871152edb5..dbfbe9d4558 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs @@ -29,7 +29,7 @@ use crate::{ DefinitionKind, DependencyId, ExprId, FuncId, FunctionModifiers, GlobalId, ReferenceId, TraitId, TypeAliasId, }, - token::CustomAtrribute, + token::CustomAttribute, Shared, Type, TypeVariable, }; use crate::{ @@ -800,7 +800,7 @@ impl<'context> Elaborator<'context> { let attributes = func.secondary_attributes().iter(); let attributes = attributes.filter_map(|secondary_attribute| secondary_attribute.as_custom()); - let attributes: Vec = attributes.cloned().collect(); + let attributes: Vec = attributes.cloned().collect(); let meta = FuncMeta { name: name_ident, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/patterns.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/patterns.rs index 09357e77c0b..c2e42b7574c 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -188,6 +188,7 @@ impl<'context> Elaborator<'context> { Some(Type::Struct(struct_type, generics)) => (struct_type, generics), None => return error_identifier(self), Some(typ) => { + let typ = typ.to_string(); self.push_err(ResolverError::NonStructUsedInConstructor { typ, span }); return error_identifier(self); } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/scope.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/scope.rs index 7a98e1856b3..8e746256142 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/scope.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/scope.rs @@ -38,11 +38,20 @@ impl<'context> Elaborator<'context> { }) } - pub(super) fn module_id(&self) -> ModuleId { + pub fn module_id(&self) -> ModuleId { assert_ne!(self.local_module, LocalModuleId::dummy_id(), "local_module is unset"); ModuleId { krate: self.crate_id, local_id: self.local_module } } + pub fn replace_module(&mut self, new_module: ModuleId) -> ModuleId { + assert_ne!(new_module.local_id, LocalModuleId::dummy_id(), "local_module is unset"); + let current_module = self.module_id(); + + self.crate_id = new_module.krate; + self.local_module = new_module.local_id; + current_module + } + pub(super) fn resolve_path_or_error( &mut self, 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 9c4761f3156..e97d0ae7d5e 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 @@ -394,8 +394,8 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { let mut diagnostic = CustomDiagnostic::simple_error(primary, secondary, location.span); - // Only take at most 3 frames starting from the top of the stack to avoid producing too much output - for frame in call_stack.iter().rev().take(3) { + // Only take at most 5 frames starting from the top of the stack to avoid producing too much output + for frame in call_stack.iter().rev().take(5) { diagnostic.add_secondary_with_file("".to_string(), frame.span, frame.file); } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs index c404b95d4b2..d998dfa69fd 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs @@ -141,7 +141,7 @@ impl HirExpression { let struct_type = None; ExpressionKind::Constructor(Box::new(ConstructorExpression { - type_name, + typ: UnresolvedType::from_path(type_name), fields, struct_type, })) 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 281318c5b7e..fe29262fc33 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 @@ -133,9 +133,14 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { return self.call_special(function, arguments, return_type, location); } - // Wait until after call_special to set the current function so that builtin functions like - // `.as_type()` still call the resolver in the caller's scope. - let old_function = self.current_function.replace(function); + // Don't change the current function scope if we're in a #[use_callers_scope] function. + // This will affect where `Expression::resolve`, `Quoted::as_type`, and similar functions resolve. + let mut old_function = self.current_function; + let modifiers = self.elaborator.interner.function_modifiers(&function); + if !modifiers.attributes.has_use_callers_scope() { + self.current_function = Some(function); + } + let result = self.call_user_defined_function(function, arguments, location); self.current_function = old_function; result @@ -242,6 +247,26 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { } fn call_closure( + &mut self, + closure: HirLambda, + environment: Vec, + arguments: Vec<(Value, Location)>, + function_scope: Option, + module_scope: ModuleId, + call_location: Location, + ) -> IResult { + // Set the closure's scope to that of the function it was originally evaluated in + let old_module = self.elaborator.replace_module(module_scope); + let old_function = std::mem::replace(&mut self.current_function, function_scope); + + let result = self.call_closure_inner(closure, environment, arguments, call_location); + + self.current_function = old_function; + self.elaborator.replace_module(old_module); + result + } + + fn call_closure_inner( &mut self, closure: HirLambda, environment: Vec, @@ -1276,7 +1301,9 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { } Ok(result) } - Value::Closure(closure, env, _) => self.call_closure(closure, env, arguments, location), + Value::Closure(closure, env, _, function_scope, module_scope) => { + self.call_closure(closure, env, arguments, function_scope, module_scope, location) + } value => { let typ = value.get_type().into_owned(); Err(InterpreterError::NonFunctionCalled { typ, location }) @@ -1458,7 +1485,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { try_vecmap(&lambda.captures, |capture| self.lookup_id(capture.ident.id, location))?; let typ = self.elaborator.interner.id_type(id).follow_bindings(); - Ok(Value::Closure(lambda, environment, typ)) + let module = self.elaborator.module_id(); + Ok(Value::Closure(lambda, environment, typ, self.current_function, module)) } fn evaluate_quote(&mut self, mut tokens: Tokens, expr_id: ExprId) -> IResult { 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 695f6eb7d16..fd11aba5a56 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 @@ -7,7 +7,8 @@ use builtin_helpers::{ 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, has_named_attribute, hir_pattern_to_tokens, - mutate_func_meta_type, parse, replace_func_meta_parameters, replace_func_meta_return_type, + mutate_func_meta_type, parse, quote_ident, replace_func_meta_parameters, + replace_func_meta_return_type, }; use chumsky::{chain::Chain, prelude::choice, Parser}; use im::Vector; @@ -18,17 +19,17 @@ use rustc_hash::FxHashMap as HashMap; use crate::{ ast::{ - ArrayLiteral, BlockExpression, ConstrainKind, Expression, ExpressionKind, FunctionKind, - FunctionReturnType, IntegerBitSize, LValue, Literal, Pattern, Statement, StatementKind, - UnaryOp, UnresolvedType, UnresolvedTypeData, Visibility, + ArrayLiteral, BlockExpression, ConstrainKind, Expression, ExpressionKind, ForRange, + FunctionKind, FunctionReturnType, IntegerBitSize, LValue, Literal, Pattern, Statement, + StatementKind, UnaryOp, UnresolvedType, UnresolvedTypeData, Visibility, }, - hir::def_collector::dc_crate::CollectedItems, hir::{ comptime::{ errors::IResult, value::{ExprValue, TypedExpr}, InterpreterError, Value, }, + def_collector::dc_crate::CollectedItems, def_map::ModuleId, }, hir_def::function::FunctionBody, @@ -71,12 +72,18 @@ impl<'local, 'context> Interpreter<'local, 'context> { "expr_as_bool" => expr_as_bool(interner, arguments, return_type, location), "expr_as_cast" => expr_as_cast(interner, arguments, return_type, location), "expr_as_comptime" => expr_as_comptime(interner, arguments, return_type, location), + "expr_as_constructor" => { + expr_as_constructor(interner, arguments, return_type, location) + } + "expr_as_for" => expr_as_for(interner, arguments, return_type, location), + "expr_as_for_range" => expr_as_for_range(interner, arguments, return_type, location), "expr_as_function_call" => { expr_as_function_call(interner, arguments, return_type, location) } "expr_as_if" => expr_as_if(interner, arguments, return_type, location), "expr_as_index" => expr_as_index(interner, arguments, return_type, location), "expr_as_integer" => expr_as_integer(interner, arguments, return_type, location), + "expr_as_lambda" => expr_as_lambda(interner, arguments, return_type, location), "expr_as_let" => expr_as_let(interner, arguments, return_type, location), "expr_as_member_access" => { expr_as_member_access(interner, arguments, return_type, location) @@ -168,8 +175,8 @@ impl<'local, 'context> Interpreter<'local, 'context> { "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), "to_be_radix" => to_be_radix(arguments, return_type, location), + "to_le_radix" => to_le_radix(arguments, return_type, location), "trait_constraint_eq" => trait_constraint_eq(arguments, location), "trait_constraint_hash" => trait_constraint_hash(arguments, location), "trait_def_as_trait_constraint" => { @@ -751,38 +758,22 @@ fn quoted_tokens(arguments: Vec<(Value, Location)>, location: Location) -> IResu )) } -fn to_le_radix( +fn to_be_radix( arguments: Vec<(Value, Location)>, return_type: Type, location: Location, ) -> IResult { - let (value, radix) = check_two_arguments(arguments, location)?; + let le_radix_limbs = to_le_radix(arguments, return_type, location)?; - let value = get_field(value)?; - let radix = get_u32(radix)?; - let limb_count = if let Type::Array(length, _) = return_type { - if let Type::Constant(limb_count) = *length { - limb_count - } else { - return Err(InterpreterError::TypeAnnotationsNeededForMethodCall { location }); - } - } else { - return Err(InterpreterError::TypeAnnotationsNeededForMethodCall { location }); + let Value::Array(limbs, typ) = le_radix_limbs else { + unreachable!("`to_le_radix` should always return an array"); }; + let be_radix_limbs = limbs.into_iter().rev().collect(); - // Decompose the integer into its radix digits in little endian form. - let decomposed_integer = compute_to_radix_le(value, radix); - let decomposed_integer = vecmap(0..limb_count as usize, |i| match decomposed_integer.get(i) { - Some(digit) => Value::U8(*digit), - None => Value::U8(0), - }); - Ok(Value::Array( - decomposed_integer.into(), - Type::Integer(Signedness::Unsigned, IntegerBitSize::Eight), - )) + Ok(Value::Array(be_radix_limbs, typ)) } -fn to_be_radix( +fn to_le_radix( arguments: Vec<(Value, Location)>, return_type: Type, location: Location, @@ -803,13 +794,10 @@ fn to_be_radix( // Decompose the integer into its radix digits in little endian form. let decomposed_integer = compute_to_radix_le(value, radix); - - // Iterate in reverse to get the big endian result. - let decomposed_integer = - vecmap((0..limb_count as usize).rev(), |i| match decomposed_integer.get(i) { - Some(digit) => Value::U8(*digit), - None => Value::U8(0), - }); + let decomposed_integer = vecmap(0..limb_count as usize, |i| match decomposed_integer.get(i) { + Some(digit) => Value::U8(*digit), + None => Value::U8(0), + }); Ok(Value::Array( decomposed_integer.into(), Type::Integer(Signedness::Unsigned, IntegerBitSize::Eight), @@ -1447,6 +1435,87 @@ fn expr_as_comptime( }) } +// fn as_constructor(self) -> Option<(Quoted, [(Quoted, Expr)])> +fn expr_as_constructor( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let expr_value = get_expr(interner, self_argument)?; + let expr_value = unwrap_expr_value(interner, expr_value); + + let option_value = + if let ExprValue::Expression(ExpressionKind::Constructor(constructor)) = expr_value { + let typ = Value::UnresolvedType(constructor.typ.typ); + let fields = constructor.fields.into_iter(); + let fields = fields.map(|(name, value)| { + Value::Tuple(vec![quote_ident(&name), Value::expression(value.kind)]) + }); + let fields = fields.collect(); + let fields_type = Type::Slice(Box::new(Type::Tuple(vec![ + Type::Quoted(QuotedType::Quoted), + Type::Quoted(QuotedType::Expr), + ]))); + let fields = Value::Slice(fields, fields_type); + Some(Value::Tuple(vec![typ, fields])) + } else { + None + }; + + option(return_type, option_value) +} + +// fn as_for(self) -> Option<(Quoted, Expr, Expr)> +fn expr_as_for( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(interner, arguments, return_type, location, |expr| { + if let ExprValue::Statement(StatementKind::For(for_statement)) = expr { + if let ForRange::Array(array) = for_statement.range { + let identifier = + Value::Quoted(Rc::new(vec![Token::Ident(for_statement.identifier.0.contents)])); + let array = Value::expression(array.kind); + let body = Value::expression(for_statement.block.kind); + Some(Value::Tuple(vec![identifier, array, body])) + } else { + None + } + } else { + None + } + }) +} + +// fn as_for_range(self) -> Option<(Quoted, Expr, Expr, Expr)> +fn expr_as_for_range( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(interner, arguments, return_type, location, |expr| { + if let ExprValue::Statement(StatementKind::For(for_statement)) = expr { + if let ForRange::Range(from, to) = for_statement.range { + let identifier = + Value::Quoted(Rc::new(vec![Token::Ident(for_statement.identifier.0.contents)])); + let from = Value::expression(from.kind); + let to = Value::expression(to.kind); + let body = Value::expression(for_statement.block.kind); + Some(Value::Tuple(vec![identifier, from, to, body])) + } else { + None + } + } else { + None + } + }) +} + // fn as_function_call(self) -> Option<(Expr, [Expr])> fn expr_as_function_call( interner: &NodeInterner, @@ -1544,6 +1613,68 @@ fn expr_as_integer( }) } +// fn as_lambda(self) -> Option<([(Expr, Option)], Option, Expr)> +fn expr_as_lambda( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(interner, arguments, return_type.clone(), location, |expr| { + if let ExprValue::Expression(ExpressionKind::Lambda(lambda)) = expr { + // ([(Expr, Option)], Option, Expr) + let option_type = extract_option_generic_type(return_type); + let Type::Tuple(mut tuple_types) = option_type else { + panic!("Expected the return type option generic arg to be a tuple"); + }; + assert_eq!(tuple_types.len(), 3); + + // Expr + tuple_types.pop().unwrap(); + + // Option + let option_unresolved_type = tuple_types.pop().unwrap(); + + let parameters = lambda + .parameters + .into_iter() + .map(|(pattern, typ)| { + let pattern = Value::pattern(pattern); + let typ = if let UnresolvedTypeData::Unspecified = typ.typ { + None + } else { + Some(Value::UnresolvedType(typ.typ)) + }; + let typ = option(option_unresolved_type.clone(), typ).unwrap(); + Value::Tuple(vec![pattern, typ]) + }) + .collect(); + let parameters = Value::Slice( + parameters, + Type::Slice(Box::new(Type::Tuple(vec![ + Type::Quoted(QuotedType::Expr), + Type::Quoted(QuotedType::UnresolvedType), + ]))), + ); + + let return_type = lambda.return_type.typ; + let return_type = if let UnresolvedTypeData::Unspecified = return_type { + None + } else { + Some(return_type) + }; + let return_type = return_type.map(Value::UnresolvedType); + let return_type = option(option_unresolved_type, return_type).ok()?; + + let body = Value::expression(lambda.body.kind); + + Some(Value::Tuple(vec![parameters, return_type, body])) + } else { + None + } + }) +} + // fn as_let(self) -> Option<(Expr, Option, Expr)> fn expr_as_let( interner: &NodeInterner, @@ -1588,15 +1719,13 @@ fn expr_as_member_access( ) -> IResult { expr_as(interner, arguments, return_type, location, |expr| match expr { ExprValue::Expression(ExpressionKind::MemberAccess(member_access)) => { - let tokens = Rc::new(vec![Token::Ident(member_access.rhs.0.contents.clone())]); Some(Value::Tuple(vec![ Value::expression(member_access.lhs.kind), - Value::Quoted(tokens), + quote_ident(&member_access.rhs), ])) } ExprValue::LValue(crate::ast::LValue::MemberAccess { object, field_name, span: _ }) => { - let tokens = Rc::new(vec![Token::Ident(field_name.0.contents.clone())]); - Some(Value::Tuple(vec![Value::lvalue(*object), Value::Quoted(tokens)])) + Some(Value::Tuple(vec![Value::lvalue(*object), quote_ident(&field_name)])) } _ => None, }) @@ -1613,9 +1742,7 @@ fn expr_as_method_call( if let ExprValue::Expression(ExpressionKind::MethodCall(method_call)) = expr { let object = Value::expression(method_call.object.kind); - let name_tokens = - Rc::new(vec![Token::Ident(method_call.method_name.0.contents.clone())]); - let name = Value::Quoted(name_tokens); + let name = quote_ident(&method_call.method_name); let generics = method_call.generics.unwrap_or_default().into_iter(); let generics = generics.map(|generic| Value::UnresolvedType(generic.typ)).collect(); 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 1456238e522..db42d6c4170 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 @@ -6,7 +6,7 @@ use noirc_errors::Location; use crate::{ ast::{ - BlockExpression, ExpressionKind, IntegerBitSize, LValue, Pattern, Signedness, + BlockExpression, ExpressionKind, Ident, IntegerBitSize, LValue, Pattern, Signedness, StatementKind, UnresolvedTypeData, }, hir::{ @@ -468,6 +468,14 @@ pub(super) fn has_named_attribute(name: &str, attributes: &[SecondaryAttribute]) false } +pub(super) fn quote_ident(ident: &Ident) -> Value { + Value::Quoted(ident_to_tokens(ident)) +} + +pub(super) fn ident_to_tokens(ident: &Ident) -> Rc> { + Rc::new(vec![Token::Ident(ident.0.contents.clone())]) +} + pub(super) fn hash_item( arguments: Vec<(Value, Location)>, location: Location, 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 d517b6fab38..34be0e9f49e 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 @@ -24,7 +24,7 @@ use crate::{ Expression, ExpressionKind, HirExpression, HirLiteral, Literal, NodeInterner, Path, StructId, }, - node_interner::{ExprId, FuncId, StmtId, TraitId, TraitImplId}, + node_interner::{ExprId, FuncId, InternedStatementKind, StmtId, TraitId, TraitImplId}, parser::{self, NoirParser, TopLevelStatement}, token::{SpannedToken, Token, Tokens}, QuotedType, Shared, Type, TypeBindings, @@ -51,7 +51,11 @@ pub enum Value { FormatString(Rc, Type), CtString(Rc), Function(FuncId, Type, Rc), - Closure(HirLambda, Vec, Type), + + // Closures also store their original scope (function & module) + // in case they use functions such as `Quoted::as_type` which require them. + Closure(HirLambda, Vec, Type, Option, ModuleId), + Tuple(Vec), Struct(HashMap, Value>, Type), Pointer(Shared, /* auto_deref */ bool), @@ -125,7 +129,7 @@ impl Value { } Value::FormatString(_, typ) => return Cow::Borrowed(typ), Value::Function(_, typ, _) => return Cow::Borrowed(typ), - Value::Closure(_, _, typ) => return Cow::Borrowed(typ), + Value::Closure(_, _, typ, ..) => return Cow::Borrowed(typ), Value::Tuple(fields) => { Type::Tuple(vecmap(fields, |field| field.get_type().into_owned())) } @@ -221,11 +225,6 @@ impl Value { interner.store_instantiation_bindings(expr_id, unwrap_rc(bindings)); ExpressionKind::Resolved(expr_id) } - Value::Closure(_lambda, _env, _typ) => { - // TODO: How should a closure's environment be inlined? - let item = "Returning closures from a comptime fn".into(); - return Err(InterpreterError::Unimplemented { item, location }); - } Value::Tuple(fields) => { let fields = try_vecmap(fields, |field| field.into_expression(interner, location))?; ExpressionKind::Tuple(fields) @@ -244,7 +243,7 @@ impl Value { // Since we've provided the struct_type, the path should be ignored. let type_name = Path::from_single(String::new(), location.span); ExpressionKind::Constructor(Box::new(ConstructorExpression { - type_name, + typ: UnresolvedType::from_path(type_name), fields, struct_type, })) @@ -293,6 +292,7 @@ impl Value { | Value::Zeroed(_) | Value::Type(_) | Value::UnresolvedType(_) + | Value::Closure(..) | Value::ModuleDefinition(_) => { let typ = self.get_type().into_owned(); let value = self.display(interner).to_string(); @@ -370,11 +370,6 @@ impl Value { interner.store_instantiation_bindings(expr_id, unwrap_rc(bindings)); return Ok(expr_id); } - Value::Closure(_lambda, _env, _typ) => { - // TODO: How should a closure's environment be inlined? - let item = "Returning closures from a comptime fn".into(); - return Err(InterpreterError::Unimplemented { item, location }); - } Value::Tuple(fields) => { let fields = try_vecmap(fields, |field| field.into_hir_expression(interner, location))?; @@ -427,6 +422,7 @@ impl Value { | Value::Zeroed(_) | Value::Type(_) | Value::UnresolvedType(_) + | Value::Closure(..) | Value::ModuleDefinition(_) => { let typ = self.get_type().into_owned(); let value = self.display(interner).to_string(); @@ -454,6 +450,9 @@ impl Value { Value::Expr(ExprValue::Expression(expr)) => { Token::InternedExpr(interner.push_expression_kind(expr)) } + Value::Expr(ExprValue::Statement(StatementKind::Expression(expr))) => { + Token::InternedExpr(interner.push_expression_kind(expr.kind)) + } Value::Expr(ExprValue::Statement(statement)) => { Token::InternedStatement(interner.push_statement_kind(statement)) } @@ -598,7 +597,7 @@ impl<'value, 'interner> Display for ValuePrinter<'value, 'interner> { Value::CtString(value) => write!(f, "{value}"), Value::FormatString(value, _) => write!(f, "{value}"), Value::Function(..) => write!(f, "(function)"), - Value::Closure(_, _, _) => write!(f, "(closure)"), + Value::Closure(..) => write!(f, "(closure)"), Value::Tuple(fields) => { let fields = vecmap(fields, |field| field.display(self.interner).to_string()); write!(f, "({})", fields.join(", ")) @@ -872,9 +871,22 @@ fn remove_interned_in_expression_kind( remove_interned_in_expression_kind(interner, expr) } ExpressionKind::Error => expr, + ExpressionKind::InternedStatement(id) => remove_interned_in_statement_expr(interner, id), } } +fn remove_interned_in_statement_expr( + interner: &NodeInterner, + id: InternedStatementKind, +) -> ExpressionKind { + let expr = match interner.get_statement_kind(id).clone() { + StatementKind::Expression(expr) | StatementKind::Semi(expr) => expr.kind, + StatementKind::Interned(id) => remove_interned_in_statement_expr(interner, id), + _ => ExpressionKind::Error, + }; + remove_interned_in_expression_kind(interner, expr) +} + fn remove_interned_in_literal(interner: &NodeInterner, literal: Literal) -> Literal { match literal { Literal::Array(array_literal) => { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs index ea14f2e3346..508765f943c 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -392,8 +392,20 @@ impl<'a> ModCollector<'a> { context.def_interner.set_doc_comments(ReferenceId::Trait(trait_id), doc_comments); // Add the trait to scope so its path can be looked up later - let result = self.def_collector.def_map.modules[self.module_id.0] - .declare_trait(name.clone(), trait_id); + let visibility = trait_definition.visibility; + let result = self.def_collector.def_map.modules[self.module_id.0].declare_trait( + name.clone(), + visibility, + trait_id, + ); + + let parent_module_id = ModuleId { krate, local_id: self.module_id }; + context.def_interner.usage_tracker.add_unused_item( + parent_module_id, + name.clone(), + UnusedItem::Trait(trait_id), + visibility, + ); if let Err((first_def, second_def)) = result { let error = DefCollectorErrorKind::Duplicate { @@ -976,7 +988,17 @@ pub fn collect_struct( } // Add the struct to scope so its path can be looked up later - let result = def_map.modules[module_id.0].declare_struct(name.clone(), id); + let visibility = unresolved.struct_def.visibility; + let result = def_map.modules[module_id.0].declare_struct(name.clone(), visibility, id); + + let parent_module_id = ModuleId { krate, local_id: module_id }; + + interner.usage_tracker.add_unused_item( + parent_module_id, + name.clone(), + UnusedItem::Struct(id), + visibility, + ); if let Err((first_def, second_def)) = result { let error = DefCollectorErrorKind::Duplicate { @@ -988,8 +1010,7 @@ pub fn collect_struct( } if interner.is_in_lsp_mode() { - let parent_module_id = ModuleId { krate, local_id: module_id }; - interner.register_struct(id, name.to_string(), parent_module_id); + interner.register_struct(id, name.to_string(), visibility, parent_module_id); } Some((id, unresolved)) diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/module_data.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/module_data.rs index 6a0f472ef75..645d8650c7e 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/module_data.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/module_data.rs @@ -100,8 +100,13 @@ impl ModuleData { self.declare(name, ItemVisibility::Public, id.into(), None) } - pub fn declare_struct(&mut self, name: Ident, id: StructId) -> Result<(), (Ident, Ident)> { - self.declare(name, ItemVisibility::Public, ModuleDefId::TypeId(id), None) + pub fn declare_struct( + &mut self, + name: Ident, + visibility: ItemVisibility, + id: StructId, + ) -> Result<(), (Ident, Ident)> { + self.declare(name, visibility, ModuleDefId::TypeId(id), None) } pub fn declare_type_alias( @@ -112,8 +117,13 @@ impl ModuleData { self.declare(name, ItemVisibility::Public, id.into(), None) } - pub fn declare_trait(&mut self, name: Ident, id: TraitId) -> Result<(), (Ident, Ident)> { - self.declare(name, ItemVisibility::Public, ModuleDefId::TraitId(id), None) + pub fn declare_trait( + &mut self, + name: Ident, + visibility: ItemVisibility, + id: TraitId, + ) -> Result<(), (Ident, Ident)> { + self.declare(name, visibility, ModuleDefId::TraitId(id), None) } pub fn declare_child_module( diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs index cb726a01dec..08f57ae562a 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs @@ -55,7 +55,7 @@ pub enum ResolverError { #[error("Test functions are not allowed to have any parameters")] TestFunctionHasParameters { span: Span }, #[error("Only struct types can be used in constructor expressions")] - NonStructUsedInConstructor { typ: Type, span: Span }, + NonStructUsedInConstructor { typ: String, span: Span }, #[error("Only struct types can have generics")] NonStructWithGenerics { span: Span }, #[error("Cannot apply generics on Self type")] @@ -130,6 +130,8 @@ pub enum ResolverError { ComptimeTypeInRuntimeCode { typ: String, span: Span }, #[error("Comptime variable `{name}` cannot be mutated in a non-comptime context")] MutatingComptimeInNonComptimeContext { name: String, span: Span }, + #[error("Failed to parse `{statement}` as an expression")] + InvalidInternedStatementInExpr { statement: String, span: Span }, } impl ResolverError { @@ -531,6 +533,13 @@ impl<'a> From<&'a ResolverError> for Diagnostic { *span, ) }, + ResolverError::InvalidInternedStatementInExpr { statement, span } => { + Diagnostic::simple_error( + format!("Failed to parse `{statement}` as an expression"), + "The statement was used from a macro here".to_string(), + *span, + ) + }, } } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/errors.rs index 5eb7a40dcd7..626084ce1b6 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/errors.rs @@ -65,8 +65,10 @@ pub enum TypeCheckError { UnsupportedCast { span: Span }, #[error("Index {index} is out of bounds for this tuple {lhs_type} of length {length}")] TupleIndexOutOfBounds { index: usize, lhs_type: Type, length: usize, span: Span }, - #[error("Variable {name} must be mutable to be assigned to")] + #[error("Variable `{name}` must be mutable to be assigned to")] VariableMustBeMutable { name: String, span: Span }, + #[error("Cannot mutate immutable variable `{name}`")] + CannotMutateImmutableVariable { name: String, span: Span }, #[error("No method named '{method_name}' found for type '{object_type}'")] UnresolvedMethodCall { method_name: String, object_type: Type, span: Span }, #[error("Integers must have the same signedness LHS is {sign_x:?}, RHS is {sign_y:?}")] @@ -268,6 +270,7 @@ impl<'a> From<&'a TypeCheckError> for Diagnostic { | TypeCheckError::UnsupportedCast { span } | TypeCheckError::TupleIndexOutOfBounds { span, .. } | TypeCheckError::VariableMustBeMutable { span, .. } + | TypeCheckError::CannotMutateImmutableVariable { span, .. } | TypeCheckError::UnresolvedMethodCall { span, .. } | TypeCheckError::IntegerSignedness { span, .. } | TypeCheckError::IntegerBitWidth { span, .. } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/function.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/function.rs index 8e3baef1d00..39c87607446 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/function.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/function.rs @@ -10,7 +10,7 @@ use crate::graph::CrateId; use crate::hir::def_map::LocalModuleId; use crate::macros_api::{BlockExpression, StructId}; use crate::node_interner::{ExprId, NodeInterner, TraitId, TraitImplId}; -use crate::token::CustomAtrribute; +use crate::token::CustomAttribute; use crate::{ResolvedGeneric, Type}; /// A Hir function is a block expression with a list of statements. @@ -167,7 +167,7 @@ pub struct FuncMeta { pub self_type: Option, /// Custom attributes attached to this function. - pub custom_attributes: Vec, + pub custom_attributes: Vec, } #[derive(Debug, Clone)] diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types/arithmetic.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types/arithmetic.rs index 44a7526c894..8c177615ec6 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types/arithmetic.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types/arithmetic.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeSet; +use std::collections::BTreeMap; use crate::{BinaryTypeOperator, Type, TypeBindings, UnificationError}; @@ -52,7 +52,8 @@ impl Type { fn sort_commutative(lhs: &Type, op: BinaryTypeOperator, rhs: &Type) -> Type { let mut queue = vec![lhs.clone(), rhs.clone()]; - let mut sorted = BTreeSet::new(); + // Maps each term to the number of times that term was used. + let mut sorted = BTreeMap::new(); let zero_value = if op == BinaryTypeOperator::Addition { 0 } else { 1 }; let mut constant = zero_value; @@ -68,20 +69,27 @@ impl Type { if let Some(result) = op.function(constant, new_constant) { constant = result; } else { - sorted.insert(Type::Constant(new_constant)); + *sorted.entry(Type::Constant(new_constant)).or_default() += 1; } } other => { - sorted.insert(other); + *sorted.entry(other).or_default() += 1; } } } if let Some(first) = sorted.pop_first() { - let mut typ = first.clone(); + let (mut typ, first_type_count) = first.clone(); - for rhs in sorted { - typ = Type::InfixExpr(Box::new(typ), op, Box::new(rhs.clone())); + // - 1 since `typ` already is set to the first instance + for _ in 0..first_type_count - 1 { + typ = Type::InfixExpr(Box::new(typ), op, Box::new(first.0.clone())); + } + + for (rhs, rhs_count) in sorted { + for _ in 0..rhs_count { + typ = Type::InfixExpr(Box::new(typ), op, Box::new(rhs.clone())); + } } if constant != zero_value { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/lexer/lexer.rs b/noir/noir-repo/compiler/noirc_frontend/src/lexer/lexer.rs index 7fbbb4fccef..a4ec19ba363 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/lexer/lexer.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/lexer/lexer.rs @@ -690,7 +690,7 @@ mod tests { use iter_extended::vecmap; use super::*; - use crate::token::{CustomAtrribute, FunctionAttribute, SecondaryAttribute, TestScope}; + use crate::token::{CustomAttribute, FunctionAttribute, SecondaryAttribute, TestScope}; #[test] fn test_single_double_char() { @@ -818,7 +818,7 @@ mod tests { let token = lexer.next_token().unwrap(); assert_eq!( token.token(), - &Token::Attribute(Attribute::Secondary(SecondaryAttribute::Custom(CustomAtrribute { + &Token::Attribute(Attribute::Secondary(SecondaryAttribute::Custom(CustomAttribute { contents: "custom(hello)".to_string(), span: Span::from(0..16), contents_span: Span::from(2..15) @@ -916,7 +916,7 @@ mod tests { let token = lexer.next_token().unwrap(); assert_eq!( token.token(), - &Token::InnerAttribute(SecondaryAttribute::Custom(CustomAtrribute { + &Token::InnerAttribute(SecondaryAttribute::Custom(CustomAttribute { contents: "something".to_string(), span: Span::from(0..13), contents_span: Span::from(3..12), diff --git a/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs b/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs index 83b82623ae7..f7e0a85c79c 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs @@ -698,9 +698,13 @@ impl Attributes { self.function.as_ref().map_or(false, |func_attribute| func_attribute.is_no_predicates()) } - pub fn is_varargs(&self) -> bool { + pub fn has_varargs(&self) -> bool { self.secondary.iter().any(|attr| matches!(attr, SecondaryAttribute::Varargs)) } + + pub fn has_use_callers_scope(&self) -> bool { + self.secondary.iter().any(|attr| matches!(attr, SecondaryAttribute::UseCallersScope)) + } } /// An Attribute can be either a Primary Attribute or a Secondary Attribute @@ -799,9 +803,10 @@ impl Attribute { )) } ["varargs"] => Attribute::Secondary(SecondaryAttribute::Varargs), + ["use_callers_scope"] => Attribute::Secondary(SecondaryAttribute::UseCallersScope), tokens => { tokens.iter().try_for_each(|token| validate(token))?; - Attribute::Secondary(SecondaryAttribute::Custom(CustomAtrribute { + Attribute::Secondary(SecondaryAttribute::Custom(CustomAttribute { contents: word.to_owned(), span, contents_span, @@ -910,15 +915,20 @@ pub enum SecondaryAttribute { ContractLibraryMethod, Export, Field(String), - Custom(CustomAtrribute), + Custom(CustomAttribute), Abi(String), /// A variable-argument comptime function. Varargs, + + /// Treat any metaprogramming functions within this one as resolving + /// within the scope of the calling function/module rather than this one. + /// This affects functions such as `Expression::resolve` or `Quoted::as_type`. + UseCallersScope, } impl SecondaryAttribute { - pub(crate) fn as_custom(&self) -> Option<&CustomAtrribute> { + pub(crate) fn as_custom(&self) -> Option<&CustomAttribute> { if let Self::Custom(attribute) = self { Some(attribute) } else { @@ -937,6 +947,7 @@ impl SecondaryAttribute { SecondaryAttribute::Custom(custom) => custom.name(), SecondaryAttribute::Abi(_) => Some("abi".to_string()), SecondaryAttribute::Varargs => Some("varargs".to_string()), + SecondaryAttribute::UseCallersScope => Some("use_callers_scope".to_string()), } } } @@ -954,12 +965,13 @@ impl fmt::Display for SecondaryAttribute { SecondaryAttribute::Field(ref k) => write!(f, "#[field({k})]"), SecondaryAttribute::Abi(ref k) => write!(f, "#[abi({k})]"), SecondaryAttribute::Varargs => write!(f, "#[varargs]"), + SecondaryAttribute::UseCallersScope => write!(f, "#[use_callers_scope]"), } } } #[derive(PartialEq, Eq, Hash, Debug, Clone, PartialOrd, Ord)] -pub struct CustomAtrribute { +pub struct CustomAttribute { pub contents: String, // The span of the entire attribute, including leading `#[` and trailing `]` pub span: Span, @@ -967,7 +979,7 @@ pub struct CustomAtrribute { pub contents_span: Span, } -impl CustomAtrribute { +impl CustomAttribute { fn name(&self) -> Option { let mut lexer = Lexer::new(&self.contents); let token = lexer.next()?.ok()?; @@ -1003,6 +1015,7 @@ impl AsRef for SecondaryAttribute { SecondaryAttribute::ContractLibraryMethod => "", SecondaryAttribute::Export => "", SecondaryAttribute::Varargs => "", + SecondaryAttribute::UseCallersScope => "", } } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/locations.rs b/noir/noir-repo/compiler/noirc_frontend/src/locations.rs index 58de235455c..cba667d5dcb 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/locations.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/locations.rs @@ -298,11 +298,10 @@ impl NodeInterner { &mut self, id: StructId, name: String, + visibility: ItemVisibility, parent_module_id: ModuleId, ) { self.add_definition_location(ReferenceId::Struct(id), Some(parent_module_id)); - - let visibility = ItemVisibility::Public; self.register_name_for_auto_import(name, ModuleDefId::TypeId(id), visibility, None); } 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 0ffeb691d35..972ad0e8c90 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs @@ -24,10 +24,14 @@ //! be limited to cases like the above `fn` example where it is clear we shouldn't back out of the //! current parser to try alternative parsers in a `choice` expression. use self::path::as_trait_path; -use self::primitives::{keyword, macro_quote_marker, mutable_reference, variable}; +use self::primitives::{ + interned_statement, interned_statement_expr, keyword, macro_quote_marker, mutable_reference, + variable, +}; use self::types::{generic_type_args, maybe_comp_time}; use attributes::{attributes, inner_attribute, validate_secondary_attributes}; use doc_comments::{inner_doc_comments, outer_doc_comments}; +use types::interned_unresolved_type; pub use types::parse_type; use visibility::item_visibility; pub use visibility::visibility; @@ -511,15 +515,6 @@ where keyword(Keyword::Comptime).ignore_then(comptime_statement).map(StatementKind::Comptime) } -pub(super) fn interned_statement() -> impl NoirParser { - token_kind(TokenKind::InternedStatement).map(|token| match token { - Token::InternedStatement(id) => StatementKind::Interned(id), - _ => { - unreachable!("token_kind(InternedStatement) guarantees we parse an interned statement") - } - }) -} - /// Comptime in an expression position only accepts entire blocks fn comptime_expr<'a, S>(statement: S) -> impl NoirParser + 'a where @@ -1157,6 +1152,7 @@ where as_trait_path(parse_type()).map(ExpressionKind::AsTraitPath), macro_quote_marker(), interned_expr(), + interned_statement_expr(), )) .map_with_span(Expression::new) .or(parenthesized(expr_parser.clone()).map_with_span(|sub_expr, span| { @@ -1228,7 +1224,11 @@ fn constructor(expr_parser: impl ExprParser) -> impl NoirParser .allow_trailing() .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)); - path(super::parse_type()).then(args).map(ExpressionKind::constructor) + let path = path(super::parse_type()).map(UnresolvedType::from_path); + let interned_unresolved_type = interned_unresolved_type(); + let typ = choice((path, interned_unresolved_type)); + + typ.then(args).map(ExpressionKind::constructor) } fn constructor_field

(expr_parser: P) -> impl NoirParser<(Ident, Expression)> diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/primitives.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/primitives.rs index c1516e2c927..7fcca89f70c 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/primitives.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/primitives.rs @@ -1,7 +1,7 @@ use chumsky::prelude::*; use crate::ast::{ExpressionKind, GenericTypeArgs, Ident, PathSegment, UnaryOp}; -use crate::macros_api::UnresolvedType; +use crate::macros_api::{StatementKind, UnresolvedType}; use crate::parser::ParserErrorReason; use crate::{ parser::{labels::ParsingRuleLabel, ExprParser, NoirParser, ParserError}, @@ -126,6 +126,26 @@ pub(super) fn interned_expr() -> impl NoirParser { }) } +pub(super) fn interned_statement() -> impl NoirParser { + token_kind(TokenKind::InternedStatement).map(|token| match token { + Token::InternedStatement(id) => StatementKind::Interned(id), + _ => { + unreachable!("token_kind(InternedStatement) guarantees we parse an interned statement") + } + }) +} + +// This rule is so that we can re-parse StatementKind::Expression and Semi in +// an expression position (ignoring the semicolon) if needed. +pub(super) fn interned_statement_expr() -> impl NoirParser { + token_kind(TokenKind::InternedStatement).map(|token| match token { + Token::InternedStatement(id) => ExpressionKind::InternedStatement(id), + _ => { + unreachable!("token_kind(InternedStatement) guarantees we parse an interned statement") + } + }) +} + #[cfg(test)] mod test { use crate::parser::parser::{ diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/structs.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/structs.rs index 66d30a6f407..729f276e9b8 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/structs.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/structs.rs @@ -1,6 +1,7 @@ use chumsky::prelude::*; use crate::ast::{Documented, NoirStruct, StructField}; +use crate::parser::parser::visibility::item_visibility; use crate::{ parser::{ parser::{ @@ -30,13 +31,21 @@ pub(super) fn struct_definition() -> impl NoirParser { .or(just(Semicolon).to(Vec::new())); attributes() + .then(item_visibility()) .then_ignore(keyword(Struct)) .then(ident()) .then(function::generics()) .then(fields) - .validate(|(((attributes, name), generics), fields), span, emit| { + .validate(|((((attributes, visibility), name), generics), fields), span, emit| { let attributes = validate_secondary_attributes(attributes, span, emit); - TopLevelStatementKind::Struct(NoirStruct { name, attributes, generics, fields, span }) + TopLevelStatementKind::Struct(NoirStruct { + name, + attributes, + visibility, + generics, + fields, + span, + }) }) } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/traits.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/traits.rs index 51d2d858319..77c6e87a842 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/traits.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/traits.rs @@ -4,6 +4,7 @@ use super::attributes::{attributes, validate_secondary_attributes}; use super::doc_comments::outer_doc_comments; use super::function::{function_modifiers, function_return_type}; use super::path::path_no_turbofish; +use super::visibility::item_visibility; use super::{ block, expression, fresh_statement, function, function_declaration_parameters, let_statement, }; @@ -42,22 +43,26 @@ pub(super) fn trait_definition() -> impl NoirParser { }); attributes() + .then(item_visibility()) .then_ignore(keyword(Keyword::Trait)) .then(ident()) .then(function::generics()) .then(where_clause()) .then(trait_body_or_error) - .validate(|((((attributes, name), generics), where_clause), items), span, emit| { - let attributes = validate_secondary_attributes(attributes, span, emit); - TopLevelStatementKind::Trait(NoirTrait { - name, - generics, - where_clause, - span, - items, - attributes, - }) - }) + .validate( + |(((((attributes, visibility), name), generics), where_clause), items), span, emit| { + let attributes = validate_secondary_attributes(attributes, span, emit); + TopLevelStatementKind::Trait(NoirTrait { + name, + generics, + where_clause, + span, + items, + attributes, + visibility, + }) + }, + ) } fn trait_body() -> impl NoirParser>> { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs index 414bfa5fa5b..fb7aaeb847b 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs @@ -514,7 +514,7 @@ fn check_trait_wrong_parameter2() { #[test] fn check_trait_wrong_parameter_type() { let src = " - trait Default { + pub trait Default { fn default(x: Field, y: NotAType) -> Field; } @@ -1565,11 +1565,11 @@ fn struct_numeric_generic_in_function() { #[test] fn struct_numeric_generic_in_struct() { let src = r#" - struct Foo { + pub struct Foo { inner: u64 } - struct Bar { } + pub struct Bar { } "#; let errors = get_program_errors(src); assert_eq!(errors.len(), 1); @@ -1658,7 +1658,7 @@ fn numeric_generic_in_function_signature() { #[test] fn numeric_generic_as_struct_field_type() { let src = r#" - struct Foo { + pub struct Foo { a: Field, b: N, } @@ -1674,7 +1674,7 @@ fn numeric_generic_as_struct_field_type() { #[test] fn normal_generic_as_array_length() { let src = r#" - struct Foo { + pub struct Foo { a: Field, b: [Field; N], } @@ -1719,11 +1719,11 @@ fn numeric_generic_as_param_type() { #[test] fn numeric_generic_used_in_nested_type_fail() { let src = r#" - struct Foo { + pub struct Foo { a: Field, b: Bar, } - struct Bar { + pub struct Bar { inner: N } "#; @@ -1738,11 +1738,11 @@ fn numeric_generic_used_in_nested_type_fail() { #[test] fn normal_generic_used_in_nested_array_length_fail() { let src = r#" - struct Foo { + pub struct Foo { a: Field, b: Bar, } - struct Bar { + pub struct Bar { inner: [Field; N] } "#; @@ -1756,11 +1756,11 @@ fn numeric_generic_used_in_nested_type_pass() { // The order of these structs should not be changed to make sure // that we are accurately resolving all struct generics before struct fields let src = r#" - struct NestedNumeric { + pub struct NestedNumeric { a: Field, b: InnerNumeric } - struct InnerNumeric { + pub struct InnerNumeric { inner: [u64; N], } "#; @@ -2721,7 +2721,7 @@ fn bit_not_on_untyped_integer() { #[test] fn duplicate_struct_field() { let src = r#" - struct Foo { + pub struct Foo { x: i32, x: i32, } @@ -2742,8 +2742,8 @@ fn duplicate_struct_field() { assert_eq!(first_def.to_string(), "x"); assert_eq!(second_def.to_string(), "x"); - assert_eq!(first_def.span().start(), 26); - assert_eq!(second_def.span().start(), 42); + assert_eq!(first_def.span().start(), 30); + assert_eq!(second_def.span().start(), 46); } #[test] @@ -2996,11 +2996,11 @@ fn uses_self_type_inside_trait() { #[test] fn uses_self_type_in_trait_where_clause() { let src = r#" - trait Trait { + pub trait Trait { fn trait_func() -> bool; } - trait Foo where Self: Trait { + pub trait Foo where Self: Trait { fn foo(self) -> bool { self.trait_func() } @@ -3222,7 +3222,7 @@ fn errors_on_unused_private_import() { pub fn bar() {} pub fn baz() {} - trait Foo { + pub trait Foo { } } @@ -3258,7 +3258,7 @@ fn errors_on_unused_pub_crate_import() { pub fn bar() {} pub fn baz() {} - trait Foo { + pub trait Foo { } } @@ -3423,6 +3423,58 @@ fn errors_on_unused_function() { assert_eq!(*item_type, "function"); } +#[test] +fn errors_on_unused_struct() { + let src = r#" + struct Foo {} + struct Bar {} + + fn main() { + let _ = Bar {}; + } + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::ResolverError(ResolverError::UnusedItem { ident, item_type }) = + &errors[0].0 + else { + panic!("Expected an unused item error"); + }; + + assert_eq!(ident.to_string(), "Foo"); + assert_eq!(*item_type, "struct"); +} + +#[test] +fn errors_on_unused_trait() { + let src = r#" + trait Foo {} + trait Bar {} + + pub struct Baz { + } + + impl Bar for Baz {} + + fn main() { + } + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::ResolverError(ResolverError::UnusedItem { ident, item_type }) = + &errors[0].0 + else { + panic!("Expected an unused item error"); + }; + + assert_eq!(ident.to_string(), "Foo"); + assert_eq!(*item_type, "trait"); +} + #[test] fn constrained_reference_to_unconstrained() { let src = r#" @@ -3462,3 +3514,98 @@ fn comptime_type_in_runtime_code() { CompilationError::ResolverError(ResolverError::ComptimeTypeInRuntimeCode { .. }) )); } + +#[test] +fn arithmetic_generics_canonicalization_deduplication_regression() { + let source = r#" + struct ArrData { + a: [Field; N], + b: [Field; N + N - 1], + } + + fn main() { + let _f: ArrData<5> = ArrData { + a: [0; 5], + b: [0; 9], + }; + } + "#; + let errors = get_program_errors(source); + assert_eq!(errors.len(), 0); +} + +#[test] +fn cannot_mutate_immutable_variable() { + let src = r#" + fn main() { + let array = [1]; + mutate(&mut array); + } + + fn mutate(_: &mut [Field; 1]) {} + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::TypeError(TypeCheckError::CannotMutateImmutableVariable { name, .. }) = + &errors[0].0 + else { + panic!("Expected a CannotMutateImmutableVariable error"); + }; + + assert_eq!(name, "array"); +} + +#[test] +fn cannot_mutate_immutable_variable_on_member_access() { + let src = r#" + struct Foo { + x: Field + } + + fn main() { + let foo = Foo { x: 0 }; + mutate(&mut foo.x); + } + + fn mutate(foo: &mut Field) { + *foo = 1; + } + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::TypeError(TypeCheckError::CannotMutateImmutableVariable { name, .. }) = + &errors[0].0 + else { + panic!("Expected a CannotMutateImmutableVariable error"); + }; + + assert_eq!(name, "foo"); +} + +#[test] +fn does_not_crash_when_passing_mutable_undefined_variable() { + let src = r#" + fn main() { + mutate(&mut undefined); + } + + fn mutate(foo: &mut Field) { + *foo = 1; + } + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::ResolverError(ResolverError::VariableNotDeclared { name, .. }) = + &errors[0].0 + else { + panic!("Expected a VariableNotDeclared error"); + }; + + assert_eq!(name, "undefined"); +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/usage_tracker.rs b/noir/noir-repo/compiler/noirc_frontend/src/usage_tracker.rs index 836f9824436..b6f41dc72f2 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/usage_tracker.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/usage_tracker.rs @@ -3,13 +3,16 @@ use std::collections::HashMap; use crate::{ ast::{Ident, ItemVisibility}, hir::def_map::ModuleId, - node_interner::FuncId, + macros_api::StructId, + node_interner::{FuncId, TraitId}, }; #[derive(Debug)] pub enum UnusedItem { Import, Function(FuncId), + Struct(StructId), + Trait(TraitId), } impl UnusedItem { @@ -17,6 +20,8 @@ impl UnusedItem { match self { UnusedItem::Import => "import", UnusedItem::Function(_) => "function", + UnusedItem::Struct(_) => "struct", + UnusedItem::Trait(_) => "trait", } } } diff --git a/noir/noir-repo/docs/docs/noir/concepts/comptime.md b/noir/noir-repo/docs/docs/noir/concepts/comptime.md index ba078c763d0..cea3a896c17 100644 --- a/noir/noir-repo/docs/docs/noir/concepts/comptime.md +++ b/noir/noir-repo/docs/docs/noir/concepts/comptime.md @@ -238,6 +238,17 @@ The following is an incomplete list of some `comptime` types along with some use There are many more functions available by exploring the `std::meta` module and its submodules. Using these methods is the key to writing powerful metaprogramming libraries. +## `#[use_callers_scope]` + +Since certain functions such as `Quoted::as_type`, `Expression::as_type`, or `Quoted::as_trait_constraint` will attempt +to resolve their contents in a particular scope - it can be useful to change the scope they resolve in. By default +these functions will resolve in the current function's scope which is usually the attribute function they are called in. +If you're working on a library however, this may be a completely different module or crate to the item you're trying to +use the attribute on. If you want to be able to use `Quoted::as_type` to refer to types local to the caller's scope for +example, you can annotate your attribute function with `#[use_callers_scope]`. This will ensure your attribute, and any +closures it uses, can refer to anything in the caller's scope. `#[use_callers_scope]` also works recursively. So if both +your attribute function and a helper function it calls use it, then they can both refer to the same original caller. + --- # Example: Derive diff --git a/noir/noir-repo/docs/docs/noir/concepts/data_types/arrays.md b/noir/noir-repo/docs/docs/noir/concepts/data_types/arrays.md index d0d8eb70aa6..bb68e60fe53 100644 --- a/noir/noir-repo/docs/docs/noir/concepts/data_types/arrays.md +++ b/noir/noir-repo/docs/docs/noir/concepts/data_types/arrays.md @@ -251,7 +251,6 @@ fn main() { let any = arr.any(|a| a == 5); assert(any); } - ``` ### as_str_unchecked diff --git a/noir/noir-repo/docs/docs/noir/concepts/data_types/structs.md b/noir/noir-repo/docs/docs/noir/concepts/data_types/structs.md index dbf68c99813..e529347f27d 100644 --- a/noir/noir-repo/docs/docs/noir/concepts/data_types/structs.md +++ b/noir/noir-repo/docs/docs/noir/concepts/data_types/structs.md @@ -68,3 +68,15 @@ fn get_octopus() -> Animal { The new variables can be bound with names different from the original struct field names, as showcased in the `legs --> feet` binding in the example above. + +By default, like functions, structs are private to the module the exist in. You can use `pub` +to make the struct public or `pub(crate)` to make it public to just its crate: + +```rust +// This struct is now public +pub struct Animal { + hands: Field, + legs: Field, + eyes: u8, +} +``` \ No newline at end of file diff --git a/noir/noir-repo/docs/docs/noir/concepts/traits.md b/noir/noir-repo/docs/docs/noir/concepts/traits.md index 597c62c737c..b07d2cf3a08 100644 --- a/noir/noir-repo/docs/docs/noir/concepts/traits.md +++ b/noir/noir-repo/docs/docs/noir/concepts/traits.md @@ -463,3 +463,13 @@ impl Default for Wrapper { Since we have an impl for our own type, the behavior of this code will not change even if `some_library` is updated to provide its own `impl Default for Foo`. The downside of this pattern is that it requires extra wrapping and unwrapping of values when converting to and from the `Wrapper` and `Foo` types. + +### Visibility + +By default, like functions, traits are private to the module the exist in. You can use `pub` +to make the trait public or `pub(crate)` to make it public to just its crate: + +```rust +// This trait is now public +pub trait Trait {} +``` \ No newline at end of file diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/expr.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/expr.md index 7ee33027354..4e2d09102b5 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/meta/expr.md +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/expr.md @@ -52,6 +52,13 @@ a slice containing each statement. If this expression is a boolean literal, return that literal. +### as_cast + +#include_code as_cast noir_stdlib/src/meta/expr.nr rust + +If this expression is a cast expression (`expr as type`), returns the casted +expression and the type to cast to. + ### as_comptime #include_code as_comptime noir_stdlib/src/meta/expr.nr rust @@ -59,6 +66,27 @@ If this expression is a boolean literal, return that literal. If this expression is a `comptime { stmt1; stmt2; ...; stmtN }` block, return each statement in the block. +### as_constructor + +#include_code as_constructor noir_stdlib/src/meta/expr.nr rust + +If this expression is a constructor `Type { field1: expr1, ..., fieldN: exprN }`, +return the type and the fields. + +### as_for + +#include_code as_for noir_stdlib/src/meta/expr.nr rust + +If this expression is a for statement over a single expression, return the identifier, +the expression and the for loop body. + +### as_for_range + +#include_code as_for noir_stdlib/src/meta/expr.nr rust + +If this expression is a for statement over a range, return the identifier, +the range start, the range end and the for loop body. + ### as_function_call #include_code as_function_call noir_stdlib/src/meta/expr.nr rust @@ -88,6 +116,12 @@ array and the index. If this expression is an integer literal, return the integer as a field as well as whether the integer is negative (true) or not (false). +### as_lambda + +#include_code as_lambda noir_stdlib/src/meta/expr.nr rust + +If this expression is a lambda, returns the parameters, return type and body. + ### as_let #include_code as_let noir_stdlib/src/meta/expr.nr rust diff --git a/noir/noir-repo/noir_stdlib/src/append.nr b/noir/noir-repo/noir_stdlib/src/append.nr index 22baa9205a0..5f41dae8046 100644 --- a/noir/noir-repo/noir_stdlib/src/append.nr +++ b/noir/noir-repo/noir_stdlib/src/append.nr @@ -7,7 +7,7 @@ // - `T::empty().append(x) == x` // - `x.append(T::empty()) == x` // docs:start:append-trait -trait Append { +pub trait Append { fn empty() -> Self; fn append(self, other: Self) -> Self; } diff --git a/noir/noir-repo/noir_stdlib/src/array/mod.nr b/noir/noir-repo/noir_stdlib/src/array/mod.nr index f3cf6b78081..46acf619dd2 100644 --- a/noir/noir-repo/noir_stdlib/src/array/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/array/mod.nr @@ -6,15 +6,42 @@ mod check_shuffle; mod quicksort; impl [T; N] { - /// Returns the length of the slice. + /// Returns the length of this array. + /// + /// ```noir + /// fn len(self) -> Field + /// ``` + /// + /// example + /// + /// ```noir + /// fn main() { + /// let array = [42, 42]; + /// assert(array.len() == 2); + /// } + /// ``` #[builtin(array_len)] pub fn len(self) -> u32 {} + /// Returns this array as a slice. + /// + /// ```noir + /// let array = [1, 2]; + /// let slice = array.as_slice(); + /// assert_eq(slice, &[1, 2]); + /// ``` #[builtin(as_slice)] pub fn as_slice(self) -> [T] {} - // Apply a function to each element of an array, returning a new array - // containing the mapped elements. + /// Applies a function to each element of this array, returning a new array containing the mapped elements. + /// + /// Example: + /// + /// ```rust + /// let a = [1, 2, 3]; + /// let b = a.map(|a| a * 2); + /// assert_eq(b, [2, 4, 6]); + /// ``` pub fn map(self, f: fn[Env](T) -> U) -> [U; N] { let first_elem = f(self[0]); let mut ret = [first_elem; N]; @@ -26,9 +53,24 @@ impl [T; N] { ret } - // Apply a function to each element of the array and an accumulator value, - // returning the final accumulated value. This function is also sometimes - // called `foldl`, `fold_left`, `reduce`, or `inject`. + /// Applies a function to each element of the array, returning the final accumulated value. The first + /// parameter is the initial value. + /// + /// This is a left fold, so the given function will be applied to the accumulator and first element of + /// the array, then the second, and so on. For a given call the expected result would be equivalent to: + /// + /// ```rust + /// let a1 = [1]; + /// let a2 = [1, 2]; + /// let a3 = [1, 2, 3]; + /// + /// let f = |a, b| a - b; + /// a1.fold(10, f); //=> f(10, 1) + /// a2.fold(10, f); //=> f(f(10, 1), 2) + /// a3.fold(10, f); //=> f(f(f(10, 1), 2), 3) + /// + /// assert_eq(a3.fold(10, f), 10 - 1 - 2 - 3); + /// ``` pub fn fold(self, mut accumulator: U, f: fn[Env](U, T) -> U) -> U { for elem in self { accumulator = f(accumulator, elem); @@ -36,9 +78,17 @@ impl [T; N] { accumulator } - // Apply a function to each element of the array and an accumulator value, - // returning the final accumulated value. Unlike fold, reduce uses the first - // element of the given array as its starting accumulator value. + /// Same as fold, but uses the first element as the starting element. + /// + /// Example: + /// + /// ```noir + /// fn main() { + /// let arr = [1, 2, 3, 4]; + /// let reduced = arr.reduce(|a, b| a + b); + /// assert(reduced == 10); + /// } + /// ``` pub fn reduce(self, f: fn[Env](T, T) -> T) -> T { let mut accumulator = self[0]; for i in 1..self.len() { @@ -47,7 +97,17 @@ impl [T; N] { accumulator } - // Returns true if all elements in the array satisfy the predicate + /// Returns true if all the elements in this array satisfy the given predicate. + /// + /// Example: + /// + /// ```noir + /// fn main() { + /// let arr = [2, 2, 2, 2, 2]; + /// let all = arr.all(|a| a == 2); + /// assert(all); + /// } + /// ``` pub fn all(self, predicate: fn[Env](T) -> bool) -> bool { let mut ret = true; for elem in self { @@ -56,7 +116,17 @@ impl [T; N] { ret } - // Returns true if any element in the array satisfies the predicate + /// Returns true if any of the elements in this array satisfy the given predicate. + /// + /// Example: + /// + /// ```noir + /// fn main() { + /// let arr = [2, 2, 2, 2, 5]; + /// let any = arr.any(|a| a == 5); + /// assert(any); + /// } + /// ``` pub fn any(self, predicate: fn[Env](T) -> bool) -> bool { let mut ret = false; for elem in self { @@ -67,17 +137,44 @@ impl [T; N] { } impl [T; N] where T: Ord + Eq { + /// Returns a new sorted array. The original array remains untouched. Notice that this function will + /// only work for arrays of fields or integers, not for any arbitrary type. This is because the sorting + /// logic it uses internally is optimized specifically for these values. If you need a sort function to + /// sort any type, you should use the `sort_via` function. + /// + /// Example: + /// + /// ```rust + /// fn main() { + /// let arr = [42, 32]; + /// let sorted = arr.sort(); + /// assert(sorted == [32, 42]); + /// } + /// ``` pub fn sort(self) -> Self { self.sort_via(|a: T, b: T| a <= b) } } impl [T; N] where T: Eq { - - /// Sorts the array using a custom predicate function `ordering`. - /// - /// The `ordering` function must be designed to return `true` for equal valued inputs - /// If this is not done, `sort_via` will fail to sort inputs with duplicated elements. + /// Returns a new sorted array by sorting it with a custom comparison function. + /// The original array remains untouched. + /// The ordering function must return true if the first argument should be sorted to be before the second argument or is equal to the second argument. + /// + /// Using this method with an operator like `<` that does not return `true` for equal values will result in an assertion failure for arrays with equal elements. + /// + /// Example: + /// + /// ```rust + /// fn main() { + /// let arr = [42, 32] + /// let sorted_ascending = arr.sort_via(|a, b| a <= b); + /// assert(sorted_ascending == [32, 42]); // verifies + /// + /// let sorted_descending = arr.sort_via(|a, b| a >= b); + /// assert(sorted_descending == [32, 42]); // does not verify + /// } + /// ``` pub fn sort_via(self, ordering: fn[Env](T, T) -> bool) -> Self { unsafe { // Safety: `sorted` array is checked to be: @@ -99,13 +196,23 @@ impl [T; N] where T: Eq { } impl [u8; N] { - /// Convert a sequence of bytes as-is into a string. - /// This function performs no UTF-8 validation or similar. + /// Converts a byte array of type `[u8; N]` to a string. Note that this performs no UTF-8 validation - + /// the given array is interpreted as-is as a string. + /// + /// Example: + /// + /// ```rust + /// fn main() { + /// let hi = [104, 105].as_str_unchecked(); + /// assert_eq(hi, "hi"); + /// } + /// ``` #[builtin(array_as_str_unchecked)] pub fn as_str_unchecked(self) -> str {} } impl From> for [u8; N] { + /// Returns an array of the string bytes. fn from(s: str) -> Self { s.as_bytes() } diff --git a/noir/noir-repo/noir_stdlib/src/bigint.nr b/noir/noir-repo/noir_stdlib/src/bigint.nr index 34eca47046d..0015f480794 100644 --- a/noir/noir-repo/noir_stdlib/src/bigint.nr +++ b/noir/noir-repo/noir_stdlib/src/bigint.nr @@ -12,7 +12,7 @@ global secpr1_fq = &[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF]; global secpr1_fr = &[81, 37, 99, 252, 194, 202, 185, 243, 132, 158, 23, 167, 173, 250, 230, 188, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255]; // docs:start:big_int_definition -struct BigInt { +pub struct BigInt { pointer: u32, modulus: u32, } @@ -43,13 +43,13 @@ impl BigInt { } } -trait BigField { +pub trait BigField { fn from_le_bytes(bytes: [u8]) -> Self; fn from_le_bytes_32(bytes: [u8; 32]) -> Self; fn to_le_bytes(self) -> [u8]; } -struct Secpk1Fq { +pub struct Secpk1Fq { array: [u8;32], } @@ -106,7 +106,7 @@ impl Eq for Secpk1Fq { } } -struct Secpk1Fr { +pub struct Secpk1Fr { array: [u8;32], } @@ -163,7 +163,7 @@ impl Eq for Secpk1Fr { } } -struct Bn254Fr { +pub struct Bn254Fr { array: [u8;32], } @@ -220,7 +220,7 @@ impl Eq for Bn254Fr { } } -struct Bn254Fq { +pub struct Bn254Fq { array: [u8;32], } @@ -277,7 +277,7 @@ impl Eq for Bn254Fq { } } -struct Secpr1Fq { +pub struct Secpr1Fq { array: [u8;32], } @@ -334,7 +334,7 @@ impl Eq for Secpr1Fq { } } -struct Secpr1Fr { +pub struct Secpr1Fr { array: [u8;32], } diff --git a/noir/noir-repo/noir_stdlib/src/cmp.nr b/noir/noir-repo/noir_stdlib/src/cmp.nr index e76116951c5..ac7e3df66ad 100644 --- a/noir/noir-repo/noir_stdlib/src/cmp.nr +++ b/noir/noir-repo/noir_stdlib/src/cmp.nr @@ -2,7 +2,7 @@ use crate::meta::derive_via; #[derive_via(derive_eq)] // docs:start:eq-trait -trait Eq { +pub trait Eq { fn eq(self, other: Self) -> bool; } // docs:end:eq-trait @@ -150,7 +150,7 @@ impl Eq for Ordering { // Noir doesn't have enums yet so we emulate (Lt | Eq | Gt) with a struct // that has 3 public functions for constructing the struct. -struct Ordering { +pub struct Ordering { result: Field, } @@ -173,7 +173,7 @@ impl Ordering { #[derive_via(derive_ord)] // docs:start:ord-trait -trait Ord { +pub trait Ord { fn cmp(self, other: Self) -> Ordering; } // docs:end:ord-trait diff --git a/noir/noir-repo/noir_stdlib/src/collections/bounded_vec.nr b/noir/noir-repo/noir_stdlib/src/collections/bounded_vec.nr index 3e77172c9d3..7cd9942e02a 100644 --- a/noir/noir-repo/noir_stdlib/src/collections/bounded_vec.nr +++ b/noir/noir-repo/noir_stdlib/src/collections/bounded_vec.nr @@ -22,7 +22,7 @@ use crate::{cmp::Eq, convert::From}; /// assert(vector.len() == 5); /// assert(vector.max_len() == 10); /// ``` -struct BoundedVec { +pub struct BoundedVec { storage: [T; MaxLen], len: u32, } diff --git a/noir/noir-repo/noir_stdlib/src/collections/map.nr b/noir/noir-repo/noir_stdlib/src/collections/map.nr index def15f718ca..d3c4d3d99b4 100644 --- a/noir/noir-repo/noir_stdlib/src/collections/map.nr +++ b/noir/noir-repo/noir_stdlib/src/collections/map.nr @@ -30,7 +30,7 @@ global MAX_LOAD_FACTOR_DEN0MINATOR = 4; /// /// let two = map.get(1).unwrap(); /// ``` -struct HashMap { +pub struct HashMap { _table: [Slot; N], /// Amount of valid elements in the map. diff --git a/noir/noir-repo/noir_stdlib/src/collections/umap.nr b/noir/noir-repo/noir_stdlib/src/collections/umap.nr index aa944404751..9d23e216731 100644 --- a/noir/noir-repo/noir_stdlib/src/collections/umap.nr +++ b/noir/noir-repo/noir_stdlib/src/collections/umap.nr @@ -11,7 +11,7 @@ use crate::hash::{Hash, Hasher, BuildHasher}; // // Compared to the constrained HashMap type, UHashMap can grow automatically // as needed and is more efficient since it can break out of loops early. -struct UHashMap { +pub struct UHashMap { _table: [Slot], // Amount of valid elements in the map. diff --git a/noir/noir-repo/noir_stdlib/src/collections/vec.nr b/noir/noir-repo/noir_stdlib/src/collections/vec.nr index cedae7f5ce1..f24ed4ac783 100644 --- a/noir/noir-repo/noir_stdlib/src/collections/vec.nr +++ b/noir/noir-repo/noir_stdlib/src/collections/vec.nr @@ -1,4 +1,4 @@ -struct Vec { +pub struct Vec { slice: [T] } // A mutable vector type implemented as a wrapper around immutable slices. diff --git a/noir/noir-repo/noir_stdlib/src/convert.nr b/noir/noir-repo/noir_stdlib/src/convert.nr index 0b2bd4f2eb7..a38a54ce365 100644 --- a/noir/noir-repo/noir_stdlib/src/convert.nr +++ b/noir/noir-repo/noir_stdlib/src/convert.nr @@ -1,5 +1,5 @@ // docs:start:from-trait -trait From { +pub trait From { fn from(input: T) -> Self; } // docs:end:from-trait @@ -11,7 +11,7 @@ impl From for T { } // docs:start:into-trait -trait Into { +pub trait Into { fn into(self) -> T; } diff --git a/noir/noir-repo/noir_stdlib/src/default.nr b/noir/noir-repo/noir_stdlib/src/default.nr index 3dcc04c7d0c..00bb278fd48 100644 --- a/noir/noir-repo/noir_stdlib/src/default.nr +++ b/noir/noir-repo/noir_stdlib/src/default.nr @@ -2,7 +2,7 @@ use crate::meta::derive_via; #[derive_via(derive_default)] // docs:start:default-trait -trait Default { +pub trait Default { fn default() -> Self; } // docs:end:default-trait diff --git a/noir/noir-repo/noir_stdlib/src/ec/consts/te.nr b/noir/noir-repo/noir_stdlib/src/ec/consts/te.nr index 8cea7654e39..349e518cdb2 100644 --- a/noir/noir-repo/noir_stdlib/src/ec/consts/te.nr +++ b/noir/noir-repo/noir_stdlib/src/ec/consts/te.nr @@ -1,7 +1,7 @@ use crate::ec::tecurve::affine::Point as TEPoint; use crate::ec::tecurve::affine::Curve as TECurve; -struct BabyJubjub { +pub struct BabyJubjub { curve: TECurve, base8: TEPoint, suborder: Field, diff --git a/noir/noir-repo/noir_stdlib/src/ec/montcurve.nr b/noir/noir-repo/noir_stdlib/src/ec/montcurve.nr index 676b1cd81a7..68b5c67dcba 100644 --- a/noir/noir-repo/noir_stdlib/src/ec/montcurve.nr +++ b/noir/noir-repo/noir_stdlib/src/ec/montcurve.nr @@ -15,14 +15,14 @@ mod affine { use crate::cmp::Eq; // Curve specification - struct Curve { // Montgomery Curve configuration (ky^2 = x^3 + j*x^2 + x) + pub struct Curve { // Montgomery Curve configuration (ky^2 = x^3 + j*x^2 + x) j: Field, k: Field, // Generator as point in Cartesian coordinates gen: Point } // Point in Cartesian coordinates - struct Point { + pub struct Point { x: Field, y: Field, infty: bool // Indicator for point at infinity @@ -222,14 +222,14 @@ mod curvegroup { use crate::ec::tecurve::curvegroup::Point as TEPoint; use crate::cmp::Eq; - struct Curve { // Montgomery Curve configuration (ky^2 z = x*(x^2 + j*x*z + z*z)) + pub struct Curve { // Montgomery Curve configuration (ky^2 z = x*(x^2 + j*x*z + z*z)) j: Field, k: Field, // Generator as point in projective coordinates gen: Point } // Point in projective coordinates - struct Point { + pub struct Point { x: Field, y: Field, z: Field diff --git a/noir/noir-repo/noir_stdlib/src/ec/swcurve.nr b/noir/noir-repo/noir_stdlib/src/ec/swcurve.nr index 9620b23948d..238b0ce3c91 100644 --- a/noir/noir-repo/noir_stdlib/src/ec/swcurve.nr +++ b/noir/noir-repo/noir_stdlib/src/ec/swcurve.nr @@ -10,7 +10,7 @@ mod affine { use crate::cmp::Eq; // Curve specification - struct Curve { // Short Weierstraß curve + pub struct Curve { // Short Weierstraß curve // Coefficients in defining equation y^2 = x^3 + ax + b a: Field, b: Field, @@ -18,7 +18,7 @@ mod affine { gen: Point } // Point in Cartesian coordinates - struct Point { + pub struct Point { x: Field, y: Field, infty: bool // Indicator for point at infinity @@ -194,7 +194,7 @@ mod curvegroup { use crate::cmp::Eq; // Curve specification - struct Curve { // Short Weierstraß curve + pub struct Curve { // Short Weierstraß curve // Coefficients in defining equation y^2 = x^3 + axz^4 + bz^6 a: Field, b: Field, @@ -202,7 +202,7 @@ mod curvegroup { gen: Point } // Point in three-dimensional Jacobian coordinates - struct Point { + pub struct Point { x: Field, y: Field, z: Field // z = 0 corresponds to point at infinity. diff --git a/noir/noir-repo/noir_stdlib/src/ec/tecurve.nr b/noir/noir-repo/noir_stdlib/src/ec/tecurve.nr index 9973678a048..760d9dc2b82 100644 --- a/noir/noir-repo/noir_stdlib/src/ec/tecurve.nr +++ b/noir/noir-repo/noir_stdlib/src/ec/tecurve.nr @@ -12,7 +12,7 @@ mod affine { use crate::cmp::Eq; // Curve specification - struct Curve { // Twisted Edwards curve + pub struct Curve { // Twisted Edwards curve // Coefficients in defining equation ax^2 + y^2 = 1 + dx^2y^2 a: Field, d: Field, @@ -20,7 +20,7 @@ mod affine { gen: Point } // Point in Cartesian coordinates - struct Point { + pub struct Point { x: Field, y: Field } @@ -204,7 +204,7 @@ mod curvegroup { use crate::cmp::Eq; // Curve specification - struct Curve { // Twisted Edwards curve + pub struct Curve { // Twisted Edwards curve // Coefficients in defining equation a(x^2 + y^2)z^2 = z^4 + dx^2y^2 a: Field, d: Field, @@ -212,7 +212,7 @@ mod curvegroup { gen: Point } // Point in extended twisted Edwards coordinates - struct Point { + pub struct Point { x: Field, y: Field, t: Field, diff --git a/noir/noir-repo/noir_stdlib/src/embedded_curve_ops.nr b/noir/noir-repo/noir_stdlib/src/embedded_curve_ops.nr index 094ad5204c3..d93b4f41cf0 100644 --- a/noir/noir-repo/noir_stdlib/src/embedded_curve_ops.nr +++ b/noir/noir-repo/noir_stdlib/src/embedded_curve_ops.nr @@ -4,7 +4,7 @@ use crate::cmp::Eq; /// A point on the embedded elliptic curve /// By definition, the base field of the embedded curve is the scalar field of the proof system curve, i.e the Noir Field. /// x and y denotes the Weierstrass coordinates of the point, if is_infinite is false. -struct EmbeddedCurvePoint { +pub struct EmbeddedCurvePoint { x: Field, y: Field, is_infinite: bool @@ -56,7 +56,7 @@ impl Eq for EmbeddedCurvePoint { /// Scalar for the embedded curve represented as low and high limbs /// By definition, the scalar field of the embedded curve is base field of the proving system curve. /// It may not fit into a Field element, so it is represented with two Field elements; its low and high limbs. -struct EmbeddedCurveScalar { +pub struct EmbeddedCurveScalar { lo: Field, hi: Field, } diff --git a/noir/noir-repo/noir_stdlib/src/hash/mimc.nr b/noir/noir-repo/noir_stdlib/src/hash/mimc.nr index 6145a387f26..2b5af6cc68a 100644 --- a/noir/noir-repo/noir_stdlib/src/hash/mimc.nr +++ b/noir/noir-repo/noir_stdlib/src/hash/mimc.nr @@ -126,7 +126,7 @@ pub fn mimc_bn254(array: [Field; N]) -> Field { r } -struct MimcHasher { +pub struct MimcHasher { _state: [Field], } diff --git a/noir/noir-repo/noir_stdlib/src/hash/mod.nr b/noir/noir-repo/noir_stdlib/src/hash/mod.nr index 4c67a0cfa8b..9aa7d220593 100644 --- a/noir/noir-repo/noir_stdlib/src/hash/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/hash/mod.nr @@ -140,7 +140,7 @@ pub fn poseidon2_permutation(_input: [Field; N], _state_length: u32) // Hash trait shall be implemented per type. #[derive_via(derive_hash)] -trait Hash { +pub trait Hash { fn hash(self, state: &mut H) where H: Hasher; } @@ -155,18 +155,18 @@ comptime fn derive_hash(s: StructDefinition) -> Quoted { // Hasher trait shall be implemented by algorithms to provide hash-agnostic means. // TODO: consider making the types generic here ([u8], [Field], etc.) -trait Hasher{ +pub trait Hasher { fn finish(self) -> Field; fn write(&mut self, input: Field); } // BuildHasher is a factory trait, responsible for production of specific Hasher. -trait BuildHasher where H: Hasher{ +pub trait BuildHasher where H: Hasher { fn build_hasher(self) -> H; } -struct BuildHasherDefault; +pub struct BuildHasherDefault; impl BuildHasher for BuildHasherDefault where diff --git a/noir/noir-repo/noir_stdlib/src/hash/poseidon/mod.nr b/noir/noir-repo/noir_stdlib/src/hash/poseidon/mod.nr index 47403e0c1d3..8a1025ada71 100644 --- a/noir/noir-repo/noir_stdlib/src/hash/poseidon/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/hash/poseidon/mod.nr @@ -5,7 +5,7 @@ use crate::default::Default; // A config struct defining the parameters of the Poseidon instance to use. // // A thorough writeup of this method (along with an unoptimized method) can be found at: https://spec.filecoin.io/algorithms/crypto/poseidon/ -struct PoseidonConfig { +pub struct PoseidonConfig { // State width, should be equal to `T` t: Field, // Number of full rounds. should be even @@ -165,7 +165,7 @@ fn sigma(x: [Field; O]) -> [Field; O] { y } -struct PoseidonHasher{ +pub struct PoseidonHasher{ _state: [Field], } diff --git a/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr b/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr index 9f28cf3e904..6b61ca32302 100644 --- a/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr +++ b/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr @@ -3,7 +3,7 @@ use crate::default::Default; comptime global RATE: u32 = 3; -struct Poseidon2 { +pub struct Poseidon2 { cache: [Field;3], state: [Field;4], cache_size: u32, @@ -82,7 +82,7 @@ impl Poseidon2 { } } -struct Poseidon2Hasher{ +pub struct Poseidon2Hasher{ _state: [Field], } diff --git a/noir/noir-repo/noir_stdlib/src/meta/ctstring.nr b/noir/noir-repo/noir_stdlib/src/meta/ctstring.nr index 0ef8ac67cfb..ec205fa6f88 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/ctstring.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/ctstring.nr @@ -3,7 +3,7 @@ use crate::append::Append; impl CtString { // docs:start:new comptime fn new() -> Self { - // docs::end::new + // docs:end:new "".as_ctstring() } @@ -41,7 +41,7 @@ impl Append for CtString { } // docs:start:as-ctstring -trait AsCtString { +pub trait AsCtString { comptime fn as_ctstring(self) -> CtString; } // docs:end:as-ctstring diff --git a/noir/noir-repo/noir_stdlib/src/meta/expr.nr b/noir/noir-repo/noir_stdlib/src/meta/expr.nr index 72e1a88cea8..c96f7d27442 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/expr.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/expr.nr @@ -1,131 +1,229 @@ +//! Contains methods on the built-in `Expr` type for quoted, syntactically valid expressions. + use crate::option::Option; use crate::meta::op::UnaryOp; use crate::meta::op::BinaryOp; impl Expr { + /// If this expression is an array literal `[elem1, ..., elemN]`, this returns a slice of each element in the array. #[builtin(expr_as_array)] // docs:start:as_array comptime fn as_array(self) -> Option<[Expr]> {} // docs:end:as_array + /// If this expression is an assert, this returns the assert expression and the optional message. #[builtin(expr_as_assert)] // docs:start:as_assert comptime fn as_assert(self) -> Option<(Expr, Option)> {} // docs:end:as_assert + /// If this expression is an assert_eq, this returns the left-hand-side and right-hand-side + /// expressions, together with the optional message. #[builtin(expr_as_assert_eq)] // docs:start:as_assert_eq comptime fn as_assert_eq(self) -> Option<(Expr, Expr, Option)> {} // docs:end:as_assert_eq + /// If this expression is an assignment, this returns a tuple with the left hand side + /// and right hand side in order. #[builtin(expr_as_assign)] // docs:start:as_assign comptime fn as_assign(self) -> Option<(Expr, Expr)> {} // docs:end:as_assign - #[builtin(expr_as_integer)] - // docs:start:as_integer - comptime fn as_integer(self) -> Option<(Field, bool)> {} - // docs:end:as_integer - + /// If this expression is a binary operator operation ` `, + /// return the left-hand side, operator, and the right-hand side of the operation. #[builtin(expr_as_binary_op)] // docs:start:as_binary_op comptime fn as_binary_op(self) -> Option<(Expr, BinaryOp, Expr)> {} // docs:end:as_binary_op + /// If this expression is a block `{ stmt1; stmt2; ...; stmtN }`, return + /// a slice containing each statement. #[builtin(expr_as_block)] // docs:start:as_block comptime fn as_block(self) -> Option<[Expr]> {} // docs:end:as_block + /// If this expression is a boolean literal, return that literal. #[builtin(expr_as_bool)] // docs:start:as_bool comptime fn as_bool(self) -> Option {} // docs:end:as_bool + /// If this expression is a cast expression `expr as type`, returns the casted + /// expression and the type to cast to. + // docs:start:as_cast #[builtin(expr_as_cast)] comptime fn as_cast(self) -> Option<(Expr, UnresolvedType)> {} + // docs:end:as_cast + /// If this expression is a `comptime { stmt1; stmt2; ...; stmtN }` block, + /// return each statement in the block. #[builtin(expr_as_comptime)] // docs:start:as_comptime comptime fn as_comptime(self) -> Option<[Expr]> {} // docs:end:as_comptime + /// If this expression is a constructor `Type { field1: expr1, ..., fieldN: exprN }`, + /// return the type and the fields. + #[builtin(expr_as_constructor)] + // docs:start:as_constructor + comptime fn as_constructor(self) -> Option<(UnresolvedType, [(Quoted, Expr)])> {} + // docs:end:as_constructor + + /// If this expression is a for statement over a single expression, return the identifier, + /// the expression and the for loop body. + #[builtin(expr_as_for)] + // docs:start:as_for + comptime fn as_for(self) -> Option<(Quoted, Expr, Expr)> {} + // docs:end:as_for + + /// If this expression is a for statement over a range, return the identifier, + /// the range start, the range end and the for loop body. + #[builtin(expr_as_for_range)] + // docs:start:as_for_range + comptime fn as_for_range(self) -> Option<(Quoted, Expr, Expr, Expr)> {} + // docs:end:as_for_range + + /// If this expression is a function call `foo(arg1, ..., argN)`, return + /// the function and a slice of each argument. #[builtin(expr_as_function_call)] // docs:start:as_function_call comptime fn as_function_call(self) -> Option<(Expr, [Expr])> {} // docs:end:as_function_call + /// If this expression is an `if condition { then_branch } else { else_branch }`, + /// return the condition, then branch, and else branch. If there is no else branch, + /// `None` is returned for that branch instead. #[builtin(expr_as_if)] // docs:start:as_if comptime fn as_if(self) -> Option<(Expr, Expr, Option)> {} // docs:end:as_if + /// If this expression is an index into an array `array[index]`, return the + /// array and the index. #[builtin(expr_as_index)] // docs:start:as_index comptime fn as_index(self) -> Option<(Expr, Expr)> {} // docs:end:as_index + /// If this expression is an integer literal, return the integer as a field + /// as well as whether the integer is negative (true) or not (false). + #[builtin(expr_as_integer)] + // docs:start:as_integer + comptime fn as_integer(self) -> Option<(Field, bool)> {} + // docs:end:as_integer + + /// If this expression is a lambda, returns the parameters, return type and body. + #[builtin(expr_as_lambda)] + // docs:start:as_lambda + comptime fn as_lambda(self) -> Option<([(Expr, Option)], Option, Expr)> {} + // docs:end:as_lambda + + /// If this expression is a let statement, returns the let pattern as an `Expr`, + /// the optional type annotation, and the assigned expression. #[builtin(expr_as_let)] // docs:start:as_let comptime fn as_let(self) -> Option<(Expr, Option, Expr)> {} // docs:end:as_let + /// If this expression is a member access `foo.bar`, return the struct/tuple + /// expression and the field. The field will be represented as a quoted value. #[builtin(expr_as_member_access)] // docs:start:as_member_access comptime fn as_member_access(self) -> Option<(Expr, Quoted)> {} // docs:end:as_member_access + /// If this expression is a method call `foo.bar::(arg1, ..., argN)`, return + /// the receiver, method name, a slice of each generic argument, and a slice of each argument. #[builtin(expr_as_method_call)] // docs:start:as_method_call comptime fn as_method_call(self) -> Option<(Expr, Quoted, [UnresolvedType], [Expr])> {} // docs:end:as_method_call + /// If this expression is a repeated element array `[elem; length]`, return + /// the repeated element and the length expressions. #[builtin(expr_as_repeated_element_array)] // docs:start:as_repeated_element_array comptime fn as_repeated_element_array(self) -> Option<(Expr, Expr)> {} // docs:end:as_repeated_element_array + /// If this expression is a repeated element slice `[elem; length]`, return + /// the repeated element and the length expressions. #[builtin(expr_as_repeated_element_slice)] // docs:start:as_repeated_element_slice comptime fn as_repeated_element_slice(self) -> Option<(Expr, Expr)> {} // docs:end:as_repeated_element_slice + /// If this expression is a slice literal `&[elem1, ..., elemN]`, + /// return each element of the slice. #[builtin(expr_as_slice)] // docs:start:as_slice comptime fn as_slice(self) -> Option<[Expr]> {} // docs:end:as_slice + /// If this expression is a tuple `(field1, ..., fieldN)`, + /// return each element of the tuple. #[builtin(expr_as_tuple)] // docs:start:as_tuple comptime fn as_tuple(self) -> Option<[Expr]> {} // docs:end:as_tuple + /// If this expression is a unary operation ` `, + /// return the unary operator as well as the right-hand side expression. #[builtin(expr_as_unary_op)] // docs:start:as_unary_op comptime fn as_unary_op(self) -> Option<(UnaryOp, Expr)> {} // docs:end:as_unary_op + /// If this expression is an `unsafe { stmt1; ...; stmtN }` block, + /// return each statement inside in a slice. #[builtin(expr_as_unsafe)] // docs:start:as_unsafe comptime fn as_unsafe(self) -> Option<[Expr]> {} // docs:end:as_unsafe + /// Returns `true` if this expression is trailed by a semicolon. + /// + /// Example: + /// + /// ```noir + /// comptime { + /// let expr1 = quote { 1 + 2 }.as_expr().unwrap(); + /// let expr2 = quote { 1 + 2; }.as_expr().unwrap(); + /// + /// assert(expr1.as_binary_op().is_some()); + /// assert(expr2.as_binary_op().is_some()); + /// + /// assert(!expr1.has_semicolon()); + /// assert(expr2.has_semicolon()); + /// } + /// ``` #[builtin(expr_has_semicolon)] // docs:start:has_semicolon comptime fn has_semicolon(self) -> bool {} // docs:end:has_semicolon + /// Returns `true` if this expression is `break`. #[builtin(expr_is_break)] // docs:start:is_break comptime fn is_break(self) -> bool {} // docs:end:is_break + /// Returns `true` if this expression is `continue`. #[builtin(expr_is_continue)] // docs:start:is_continue comptime fn is_continue(self) -> bool {} // docs:end:is_continue + /// Applies a mapping function to this expression and to all of its sub-expressions. + /// `f` will be applied to each sub-expression first, then applied to the expression itself. + /// + /// This happens recursively for every expression within `self`. + /// + /// For example, calling `modify` on `(&[1], &[2, 3])` with an `f` that returns `Option::some` + /// for expressions that are integers, doubling them, would return `(&[2], &[4, 6])`. // docs:start:modify comptime fn modify(self, f: fn[Env](Expr) -> Option) -> Expr { // docs:end:modify @@ -137,8 +235,12 @@ impl Expr { let result = result.or_else(|| modify_block(self, f)); let result = result.or_else(|| modify_cast(self, f)); let result = result.or_else(|| modify_comptime(self, f)); + let result = result.or_else(|| modify_constructor(self, f)); let result = result.or_else(|| modify_if(self, f)); let result = result.or_else(|| modify_index(self, f)); + let result = result.or_else(|| modify_for(self, f)); + let result = result.or_else(|| modify_for_range(self, f)); + let result = result.or_else(|| modify_lambda(self, f)); let result = result.or_else(|| modify_let(self, f)); let result = result.or_else(|| modify_function_call(self, f)); let result = result.or_else(|| modify_member_access(self, f)); @@ -158,12 +260,22 @@ impl Expr { } } + /// Returns this expression as a `Quoted` value. It's the same as `quote { $self }`. // docs:start:quoted comptime fn quoted(self) -> Quoted { // docs:end:quoted quote { $self } } + /// Resolves and type-checks this expression and returns the result as a `TypedExpr`. + /// + /// The `in_function` argument specifies where the expression is resolved: + /// - If it's `none`, the expression is resolved in the function where `resolve` was called + /// - If it's `some`, the expression is resolved in the given function + /// + /// If any names used by this expression are not in scope or if there are any type errors, + /// this will give compiler errors as if the expression was written directly into + /// the current `comptime` function. #[builtin(expr_resolve)] // docs:start:resolve comptime fn resolve(self, in_function: Option) -> TypedExpr {} @@ -252,6 +364,19 @@ comptime fn modify_comptime(expr: Expr, f: fn[Env](Expr) -> Option) - ) } +comptime fn modify_constructor(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_constructor().map( + |expr: (UnresolvedType, [(Quoted, Expr)])| { + let (typ, fields) = expr; + let fields = fields.map(|field: (Quoted, Expr)| { + let (name, value) = field; + (name, value.modify(f)) + }); + new_constructor(typ, fields) + } + ) +} + comptime fn modify_function_call(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { expr.as_function_call().map( |expr: (Expr, [Expr])| { @@ -286,6 +411,40 @@ comptime fn modify_index(expr: Expr, f: fn[Env](Expr) -> Option) -> O ) } +comptime fn modify_for(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_for().map( + |expr: (Quoted, Expr, Expr)| { + let (identifier, array, body) = expr; + let array = array.modify(f); + let body = body.modify(f); + new_for(identifier, array, body) + } + ) +} + +comptime fn modify_for_range(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_for_range().map( + |expr: (Quoted, Expr, Expr, Expr)| { + let (identifier, from, to, body) = expr; + let from = from.modify(f); + let to = to.modify(f); + let body = body.modify(f); + new_for_range(identifier, from, to, body) + } + ) +} + +comptime fn modify_lambda(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_lambda().map( + |expr: ([(Expr, Option)], Option, Expr)| { + let (params, return_type, body) = expr; + let params = params.map(|param: (Expr, Option)| (param.0.modify(f), param.1)); + let body = body.modify(f); + new_lambda(params, return_type, body) + } + ) +} + comptime fn modify_let(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { expr.as_let().map( |expr: (Expr, Option, Expr)| { @@ -427,6 +586,16 @@ comptime fn new_comptime(exprs: [Expr]) -> Expr { quote { comptime { $exprs }}.as_expr().unwrap() } +comptime fn new_constructor(typ: UnresolvedType, fields: [(Quoted, Expr)]) -> Expr { + let fields = fields.map( + |field: (Quoted, Expr)| { + let (name, value) = field; + quote { $name: $value } + } + ).join(quote { , }); + quote { $typ { $fields }}.as_expr().unwrap() +} + comptime fn new_if(condition: Expr, consequence: Expr, alternative: Option) -> Expr { if alternative.is_some() { let alternative = alternative.unwrap(); @@ -436,10 +605,43 @@ comptime fn new_if(condition: Expr, consequence: Expr, alternative: Option } } +comptime fn new_for(identifier: Quoted, array: Expr, body: Expr) -> Expr { + quote { for $identifier in $array { $body } }.as_expr().unwrap() +} + +comptime fn new_for_range(identifier: Quoted, from: Expr, to: Expr, body: Expr) -> Expr { + quote { for $identifier in $from .. $to { $body } }.as_expr().unwrap() +} + comptime fn new_index(object: Expr, index: Expr) -> Expr { quote { $object[$index] }.as_expr().unwrap() } +comptime fn new_lambda( + params: [(Expr, Option)], + return_type: Option, + body: Expr +) -> Expr { + let params = params.map( + |param: (Expr, Option)| { + let (name, typ) = param; + if typ.is_some() { + let typ = typ.unwrap(); + quote { $name: $typ } + } else { + quote { $name } + } + } + ).join(quote { , }); + + if return_type.is_some() { + let return_type = return_type.unwrap(); + quote { |$params| -> $return_type { $body } }.as_expr().unwrap() + } else { + quote { |$params| { $body } }.as_expr().unwrap() + } +} + comptime fn new_let(pattern: Expr, typ: Option, expr: Expr) -> Expr { if typ.is_some() { let typ = typ.unwrap(); @@ -501,189 +703,3 @@ comptime fn new_unsafe(exprs: [Expr]) -> Expr { comptime fn join_expressions(exprs: [Expr], separator: Quoted) -> Quoted { exprs.map(|expr: Expr| expr.quoted()).join(separator) } - -mod tests { - use crate::meta::op::UnaryOp; - use crate::meta::op::BinaryOp; - - #[test] - fn test_expr_as_array() { - comptime - { - let expr = quote { [1, 2, 4] }.as_expr().unwrap(); - let elems = expr.as_array().unwrap(); - assert_eq(elems.len(), 3); - assert_eq(elems[0].as_integer().unwrap(), (1, false)); - assert_eq(elems[1].as_integer().unwrap(), (2, false)); - assert_eq(elems[2].as_integer().unwrap(), (4, false)); - } - } - - #[test] - fn test_expr_as_integer() { - comptime - { - let expr = quote { 1 }.as_expr().unwrap(); - assert_eq((1, false), expr.as_integer().unwrap()); - - let expr = quote { -2 }.as_expr().unwrap(); - assert_eq((2, true), expr.as_integer().unwrap()); - } - } - - #[test] - fn test_expr_as_binary_op() { - comptime - { - assert(get_binary_op(quote { x + y }).is_add()); - assert(get_binary_op(quote { x - y }).is_subtract()); - assert(get_binary_op(quote { x * y }).is_multiply()); - assert(get_binary_op(quote { x / y }).is_divide()); - assert(get_binary_op(quote { x == y }).is_equal()); - assert(get_binary_op(quote { x != y }).is_not_equal()); - assert(get_binary_op(quote { x < y }).is_less_than()); - assert(get_binary_op(quote { x <= y }).is_less_than_or_equal()); - assert(get_binary_op(quote { x > y }).is_greater_than()); - assert(get_binary_op(quote { x >= y }).is_greater_than_or_equal()); - assert(get_binary_op(quote { x & y }).is_and()); - assert(get_binary_op(quote { x | y }).is_or()); - assert(get_binary_op(quote { x ^ y }).is_xor()); - assert(get_binary_op(quote { x >> y }).is_shift_right()); - assert(get_binary_op(quote { x << y }).is_shift_left()); - assert(get_binary_op(quote { x % y }).is_modulo()); - } - } - - #[test] - fn test_expr_as_bool() { - comptime - { - let expr = quote { false }.as_expr().unwrap(); - assert(expr.as_bool().unwrap() == false); - - let expr = quote { true }.as_expr().unwrap(); - assert_eq(expr.as_bool().unwrap(), true); - } - } - - #[test] - fn test_expr_as_function_call() { - comptime - { - let expr = quote { foo(42) }.as_expr().unwrap(); - let (_function, args) = expr.as_function_call().unwrap(); - assert_eq(args.len(), 1); - assert_eq(args[0].as_integer().unwrap(), (42, false)); - } - } - - #[test] - fn test_expr_as_if() { - comptime - { - let expr = quote { if 1 { 2 } }.as_expr().unwrap(); - let (_condition, _consequence, alternative) = expr.as_if().unwrap(); - assert(alternative.is_none()); - - let expr = quote { if 1 { 2 } else { 3 } }.as_expr().unwrap(); - let (_condition, _consequence, alternative) = expr.as_if().unwrap(); - assert(alternative.is_some()); - } - } - - #[test] - fn test_expr_as_index() { - comptime - { - let expr = quote { foo[bar] }.as_expr().unwrap(); - assert(expr.as_index().is_some()); - } - } - - #[test] - fn test_expr_as_member_access() { - comptime - { - let expr = quote { foo.bar }.as_expr().unwrap(); - let (_, name) = expr.as_member_access().unwrap(); - assert_eq(name, quote { bar }); - } - } - - #[test] - fn test_expr_as_repeated_element_array() { - comptime - { - let expr = quote { [1; 3] }.as_expr().unwrap(); - let (expr, length) = expr.as_repeated_element_array().unwrap(); - assert_eq(expr.as_integer().unwrap(), (1, false)); - assert_eq(length.as_integer().unwrap(), (3, false)); - } - } - - #[test] - fn test_expr_as_repeated_element_slice() { - comptime - { - let expr = quote { &[1; 3] }.as_expr().unwrap(); - let (expr, length) = expr.as_repeated_element_slice().unwrap(); - assert_eq(expr.as_integer().unwrap(), (1, false)); - assert_eq(length.as_integer().unwrap(), (3, false)); - } - } - - #[test] - fn test_expr_as_slice() { - comptime - { - let expr = quote { &[1, 3, 5] }.as_expr().unwrap(); - let elems = expr.as_slice().unwrap(); - assert_eq(elems.len(), 3); - assert_eq(elems[0].as_integer().unwrap(), (1, false)); - assert_eq(elems[1].as_integer().unwrap(), (3, false)); - assert_eq(elems[2].as_integer().unwrap(), (5, false)); - } - } - - #[test] - fn test_expr_as_tuple() { - comptime - { - let expr = quote { (1, 2) }.as_expr().unwrap(); - let tuple_exprs = expr.as_tuple().unwrap(); - assert_eq(tuple_exprs.len(), 2); - } - } - - #[test] - fn test_expr_as_unary_op() { - comptime - { - assert(get_unary_op(quote { -x }).is_minus()); - assert(get_unary_op(quote { !x }).is_not()); - assert(get_unary_op(quote { &mut x }).is_mutable_reference()); - assert(get_unary_op(quote { *x }).is_dereference()); - } - } - - #[test] - fn test_automatically_unwraps_parenthesized_expression() { - comptime - { - let expr = quote { ((if 1 { 2 })) }.as_expr().unwrap(); - assert(expr.as_if().is_some()); - } - } - - comptime fn get_unary_op(quoted: Quoted) -> UnaryOp { - let expr = quoted.as_expr().unwrap(); - let (op, _) = expr.as_unary_op().unwrap(); - op - } - - comptime fn get_binary_op(quoted: Quoted) -> BinaryOp { - let expr = quoted.as_expr().unwrap(); - let (_, op, _) = expr.as_binary_op().unwrap(); - op - } -} diff --git a/noir/noir-repo/noir_stdlib/src/meta/mod.nr b/noir/noir-repo/noir_stdlib/src/meta/mod.nr index 44e8ddc17fc..f19ccc523fc 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/mod.nr @@ -237,4 +237,14 @@ mod tests { ) } // docs:end:big-derive-usage-example + + // This function is just to remove unused warnings + fn remove_unused_warnings() { + let _: Bar = crate::panic::panic(f""); + let _: MyStruct = crate::panic::panic(f""); + let _: MyOtherStruct = crate::panic::panic(f""); + let _ = derive_do_nothing(crate::panic::panic(f"")); + let _ = derive_do_nothing_alt(crate::panic::panic(f"")); + remove_unused_warnings(); + } } diff --git a/noir/noir-repo/noir_stdlib/src/meta/op.nr b/noir/noir-repo/noir_stdlib/src/meta/op.nr index 31a815f07ba..197daaabaa6 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/op.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/op.nr @@ -1,5 +1,5 @@ #[derive(Eq, Hash)] -struct UnaryOp { +pub struct UnaryOp { op: Field } @@ -47,7 +47,7 @@ impl UnaryOp { } #[derive(Eq, Hash)] -struct BinaryOp { +pub struct BinaryOp { op: Field } diff --git a/noir/noir-repo/noir_stdlib/src/ops/arith.nr b/noir/noir-repo/noir_stdlib/src/ops/arith.nr index 653dfd59978..195ae777580 100644 --- a/noir/noir-repo/noir_stdlib/src/ops/arith.nr +++ b/noir/noir-repo/noir_stdlib/src/ops/arith.nr @@ -1,5 +1,5 @@ // docs:start:add-trait -trait Add { +pub trait Add { fn add(self, other: Self) -> Self; } // docs:end:add-trait @@ -58,7 +58,7 @@ impl Add for i64 { } // docs:start:sub-trait -trait Sub { +pub trait Sub { fn sub(self, other: Self) -> Self; } // docs:end:sub-trait @@ -117,7 +117,7 @@ impl Sub for i64 { } // docs:start:mul-trait -trait Mul { +pub trait Mul { fn mul(self, other: Self) -> Self; } // docs:end:mul-trait @@ -176,7 +176,7 @@ impl Mul for i64 { } // docs:start:div-trait -trait Div { +pub trait Div { fn div(self, other: Self) -> Self; } // docs:end:div-trait @@ -235,7 +235,7 @@ impl Div for i64 { } // docs:start:rem-trait -trait Rem{ +pub trait Rem { fn rem(self, other: Self) -> Self; } // docs:end:rem-trait @@ -288,7 +288,7 @@ impl Rem for i64 { } // docs:start:neg-trait -trait Neg { +pub trait Neg { fn neg(self) -> Self; } // docs:end:neg-trait diff --git a/noir/noir-repo/noir_stdlib/src/ops/bit.nr b/noir/noir-repo/noir_stdlib/src/ops/bit.nr index 0c0329efe4c..70c52ac38b0 100644 --- a/noir/noir-repo/noir_stdlib/src/ops/bit.nr +++ b/noir/noir-repo/noir_stdlib/src/ops/bit.nr @@ -1,5 +1,5 @@ // docs:start:not-trait -trait Not { +pub trait Not { fn not(self: Self) -> Self; } // docs:end:not-trait @@ -60,7 +60,7 @@ impl Not for i64 { // docs:end:not-trait-impls // docs:start:bitor-trait -trait BitOr { +pub trait BitOr { fn bitor(self, other: Self) -> Self; } // docs:end:bitor-trait @@ -119,7 +119,7 @@ impl BitOr for i64 { } // docs:start:bitand-trait -trait BitAnd { +pub trait BitAnd { fn bitand(self, other: Self) -> Self; } // docs:end:bitand-trait @@ -178,7 +178,7 @@ impl BitAnd for i64 { } // docs:start:bitxor-trait -trait BitXor { +pub trait BitXor { fn bitxor(self, other: Self) -> Self; } // docs:end:bitxor-trait @@ -237,7 +237,7 @@ impl BitXor for i64 { } // docs:start:shl-trait -trait Shl { +pub trait Shl { fn shl(self, other: u8) -> Self; } // docs:end:shl-trait @@ -290,7 +290,7 @@ impl Shl for i64 { } // docs:start:shr-trait -trait Shr { +pub trait Shr { fn shr(self, other: u8) -> Self; } // docs:end:shr-trait diff --git a/noir/noir-repo/noir_stdlib/src/option.nr b/noir/noir-repo/noir_stdlib/src/option.nr index 2823ba8af1b..6b3a2bf2f3c 100644 --- a/noir/noir-repo/noir_stdlib/src/option.nr +++ b/noir/noir-repo/noir_stdlib/src/option.nr @@ -2,7 +2,7 @@ use crate::hash::{Hash, Hasher}; use crate::cmp::{Ordering, Ord, Eq}; use crate::default::Default; -struct Option { +pub struct Option { _is_some: bool, _value: T, } diff --git a/noir/noir-repo/noir_stdlib/src/test.nr b/noir/noir-repo/noir_stdlib/src/test.nr index f8db6079193..e906ad54d55 100644 --- a/noir/noir-repo/noir_stdlib/src/test.nr +++ b/noir/noir-repo/noir_stdlib/src/test.nr @@ -16,7 +16,7 @@ unconstrained fn set_mock_times_oracle(id: Field, times: u64) {} #[oracle(clear_mock)] unconstrained fn clear_mock_oracle(id: Field) {} -struct OracleMock { +pub struct OracleMock { id: Field, } diff --git a/noir/noir-repo/noir_stdlib/src/uint128.nr b/noir/noir-repo/noir_stdlib/src/uint128.nr index 91c3369f889..a3d7491eaff 100644 --- a/noir/noir-repo/noir_stdlib/src/uint128.nr +++ b/noir/noir-repo/noir_stdlib/src/uint128.nr @@ -3,7 +3,7 @@ use crate::cmp::{Eq, Ord, Ordering}; global pow64 : Field = 18446744073709551616; //2^64; global pow63 : Field = 9223372036854775808; // 2^63; -struct U128 { +pub struct U128 { lo: Field, hi: Field, } diff --git a/noir/noir-repo/test_programs/compile_success_empty/arithmetic_generics/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/arithmetic_generics/src/main.nr index ad8dff6c7b9..0a7d319485c 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/arithmetic_generics/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/arithmetic_generics/src/main.nr @@ -101,8 +101,11 @@ fn demo_proof() -> Equiv, (Equiv, (), W, () let p1: Equiv, (), W, ()> = mul_comm(); let p2: Equiv, (), W, ()> = mul_add::(); let p3_sub: Equiv, (), W, ()> = mul_one_r(); - let p3: Equiv, (), W, ()> = add_equiv_r::(p3_sub); - equiv_trans(equiv_trans(p1, p2), p3) + let _p3: Equiv, (), W, ()> = add_equiv_r::(p3_sub); + let _p1_to_2 = equiv_trans(p1, p2); + + // equiv_trans(p1_to_2, p3) + std::mem::zeroed() } fn test_constant_folding() { diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_parse_statement_as_expression/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/comptime_parse_statement_as_expression/Nargo.toml new file mode 100644 index 00000000000..65fa5b1ed06 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_parse_statement_as_expression/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_parse_statement_as_expression" +type = "bin" +authors = [""] +compiler_version = ">=0.33.0" + +[dependencies] diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_parse_statement_as_expression/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_parse_statement_as_expression/src/main.nr new file mode 100644 index 00000000000..4fa47dbc2f7 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_parse_statement_as_expression/src/main.nr @@ -0,0 +1,27 @@ +fn main() { + comptime + { + let block = quote[{ + 1; + 2; + 3 + }]; + let statements = block.as_expr().unwrap().as_block().unwrap(); + let last = statements.pop_back().1; + + // `3` should fit in an expression position even though it is + // originally a StatementKind::Expression + let regression1 = quote[{ + let _ = $last; + }]; + assert(regression1.as_expr().is_some()); + + // `1;` should fit in an expression position even though it is + // originally a StatementKind::Semi + let first = statements.pop_front().0; + let regression2 = quote[{ + let _ = $first; + }]; + assert(regression2.as_expr().is_some()); + } +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_to_radix/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/comptime_to_radix/Nargo.toml new file mode 100644 index 00000000000..35713aff805 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_to_radix/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "comptime_to_radix" +type = "bin" +authors = [""] + +[dependencies] diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_to_radix/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_to_radix/src/main.nr new file mode 100644 index 00000000000..959a7c12007 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_to_radix/src/main.nr @@ -0,0 +1,10 @@ +fn main() { + comptime + { + let le_bytes: [u8; 3] = 257.to_le_bytes(); + assert_eq(le_bytes, [1, 1, 0]); + + let be_bytes: [u8; 3] = 257.to_be_bytes(); + assert_eq(be_bytes, [0, 1, 1]); + } +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/use_callers_scope/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/use_callers_scope/Nargo.toml new file mode 100644 index 00000000000..14402c7d7b1 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/use_callers_scope/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "use_callers_scope" +type = "bin" +authors = [""] +compiler_version = ">=0.34.0" + +[dependencies] diff --git a/noir/noir-repo/test_programs/compile_success_empty/use_callers_scope/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/use_callers_scope/src/main.nr new file mode 100644 index 00000000000..b4e8a7f7c4d --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/use_callers_scope/src/main.nr @@ -0,0 +1,34 @@ +#[bar::struct_attr] +struct Foo {} + +struct Bar {} + +#[bar::fn_attr] +fn main() {} + +mod bar { + #[use_callers_scope] + pub comptime fn struct_attr(_: StructDefinition) { + let _ = quote { Bar }.as_type(); + } + + #[use_callers_scope] + pub comptime fn fn_attr(_: FunctionDefinition) { + let _ = quote { Bar }.as_type(); + let _ = nested(); + + // Ensure closures can still access Bar even + // though `map` separates them from `fn_attr`. + let _ = &[1, 2, 3].map( + |_| { + quote { Bar }.as_type() + } + ); + } + + // use_callers_scope should also work nested + #[use_callers_scope] + comptime fn nested() -> Type { + quote { Bar }.as_type() + } +} diff --git a/noir/noir-repo/test_programs/noir_test_success/comptime_expr/src/main.nr b/noir/noir-repo/test_programs/noir_test_success/comptime_expr/src/main.nr index c1f70e7acee..709180879a0 100644 --- a/noir/noir-repo/test_programs/noir_test_success/comptime_expr/src/main.nr +++ b/noir/noir-repo/test_programs/noir_test_success/comptime_expr/src/main.nr @@ -326,6 +326,35 @@ mod tests { } } + #[test] + fn test_expr_as_constructor() { + comptime + { + let expr = quote { Foo { a: 1, b: 2 } }.as_expr().unwrap(); + let (_typ, fields) = expr.as_constructor().unwrap(); + assert_eq(fields.len(), 2); + assert_eq(fields[0].0, quote { a }); + assert_eq(fields[0].1.as_integer().unwrap(), (1, false)); + assert_eq(fields[1].0, quote { b }); + assert_eq(fields[1].1.as_integer().unwrap(), (2, false)); + } + } + + #[test] + fn test_expr_modify_for_constructor() { + comptime + { + let expr = quote { foo::bar::Baz:: { a: 1, b: 2 } }.as_expr().unwrap(); + let expr = expr.modify(times_two); + let (_typ, fields) = expr.as_constructor().unwrap(); + assert_eq(fields.len(), 2); + assert_eq(fields[0].0, quote { a }); + assert_eq(fields[0].1.as_integer().unwrap(), (2, false)); + assert_eq(fields[1].0, quote { b }); + assert_eq(fields[1].1.as_integer().unwrap(), (4, false)); + } + } + // This test can't only be around the comptime block since that will cause // `nargo fmt` to remove the comptime keyword. // docs:start:as_expr_example @@ -606,6 +635,48 @@ mod tests { } } + #[test] + fn test_expr_as_lambda() { + comptime + { + let expr = quote { |x: Field| -> Field { 1 } }.as_expr().unwrap(); + let (params, return_type, body) = expr.as_lambda().unwrap(); + assert_eq(params.len(), 1); + assert(params[0].1.unwrap().is_field()); + assert(return_type.unwrap().is_field()); + assert_eq(body.as_block().unwrap()[0].as_integer().unwrap(), (1, false)); + + let expr = quote { |x| { 1 } }.as_expr().unwrap(); + let (params, return_type, body) = expr.as_lambda().unwrap(); + assert_eq(params.len(), 1); + assert(params[0].1.is_none()); + assert(return_type.is_none()); + assert_eq(body.as_block().unwrap()[0].as_integer().unwrap(), (1, false)); + } + } + + #[test] + fn test_expr_modify_lambda() { + comptime + { + let expr = quote { |x: Field| -> Field { 1 } }.as_expr().unwrap(); + let expr = expr.modify(times_two); + let (params, return_type, body) = expr.as_lambda().unwrap(); + assert_eq(params.len(), 1); + assert(params[0].1.unwrap().is_field()); + assert(return_type.unwrap().is_field()); + assert_eq(body.as_block().unwrap()[0].as_block().unwrap()[0].as_integer().unwrap(), (2, false)); + + let expr = quote { |x| { 1 } }.as_expr().unwrap(); + let expr = expr.modify(times_two); + let (params, return_type, body) = expr.as_lambda().unwrap(); + assert_eq(params.len(), 1); + assert(params[0].1.is_none()); + assert(return_type.is_none()); + assert_eq(body.as_block().unwrap()[0].as_block().unwrap()[0].as_integer().unwrap(), (2, false)); + } + } + #[test] fn test_expr_as_let() { comptime @@ -641,6 +712,58 @@ mod tests { } } + #[test] + fn test_expr_as_for_statement() { + comptime + { + let expr = quote { for x in 2 { 3 } }.as_expr().unwrap(); + let (index, array, body) = expr.as_for().unwrap(); + assert_eq(index, quote { x }); + assert_eq(array.as_integer().unwrap(), (2, false)); + assert_eq(body.as_block().unwrap()[0].as_integer().unwrap(), (3, false)); + } + } + + #[test] + fn test_expr_modify_for_statement() { + comptime + { + let expr = quote { for x in 2 { 3 } }.as_expr().unwrap(); + let expr = expr.modify(times_two); + let (index, array, body) = expr.as_for().unwrap(); + assert_eq(index, quote { x }); + assert_eq(array.as_integer().unwrap(), (4, false)); + assert_eq(body.as_block().unwrap()[0].as_block().unwrap()[0].as_integer().unwrap(), (6, false)); + } + } + + #[test] + fn test_expr_as_for_range_statement() { + comptime + { + let expr = quote { for x in 2..3 { 4 } }.as_expr().unwrap(); + let (index, from, to, body) = expr.as_for_range().unwrap(); + assert_eq(index, quote { x }); + assert_eq(from.as_integer().unwrap(), (2, false)); + assert_eq(to.as_integer().unwrap(), (3, false)); + assert_eq(body.as_block().unwrap()[0].as_integer().unwrap(), (4, false)); + } + } + + #[test] + fn test_expr_modify_for_range_statement() { + comptime + { + let expr = quote { for x in 2..3 { 4 } }.as_expr().unwrap(); + let expr = expr.modify(times_two); + let (index, from, to, body) = expr.as_for_range().unwrap(); + assert_eq(index, quote { x }); + assert_eq(from.as_integer().unwrap(), (4, false)); + assert_eq(to.as_integer().unwrap(), (6, false)); + assert_eq(body.as_block().unwrap()[0].as_block().unwrap()[0].as_integer().unwrap(), (8, false)); + } + } + #[test] fn test_automatically_unwraps_parenthesized_expression() { comptime diff --git a/noir/noir-repo/tooling/lsp/src/lib.rs b/noir/noir-repo/tooling/lsp/src/lib.rs index 0e090a07336..39d4c3faa61 100644 --- a/noir/noir-repo/tooling/lsp/src/lib.rs +++ b/noir/noir-repo/tooling/lsp/src/lib.rs @@ -35,7 +35,7 @@ use nargo::{ use nargo_toml::{find_file_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_driver::{file_manager_with_stdlib, prepare_crate, NOIR_ARTIFACT_VERSION_STRING}; use noirc_frontend::{ - graph::{CrateId, CrateName}, + graph::{CrateGraph, CrateId, CrateName}, hir::{ def_map::{parse_file, CrateDefMap}, Context, FunctionNameMatch, ParsedFiles, @@ -92,15 +92,26 @@ pub struct LspState { open_documents_count: usize, input_files: HashMap, cached_lenses: HashMap>, - cached_definitions: HashMap, cached_parsed_files: HashMap))>, - cached_def_maps: HashMap>, + workspace_cache: HashMap, + package_cache: HashMap, options: LspInitializationOptions, // Tracks files that currently have errors, by package root. files_with_errors: HashMap>, } +struct WorkspaceCacheData { + file_manager: FileManager, +} + +struct PackageCacheData { + crate_id: CrateId, + crate_graph: CrateGraph, + node_interner: NodeInterner, + def_maps: BTreeMap, +} + impl LspState { fn new( client: &ClientSocket, @@ -112,12 +123,11 @@ impl LspState { solver: WrapperSolver(Box::new(solver)), input_files: HashMap::new(), cached_lenses: HashMap::new(), - cached_definitions: HashMap::new(), - open_documents_count: 0, cached_parsed_files: HashMap::new(), - cached_def_maps: HashMap::new(), + workspace_cache: HashMap::new(), + package_cache: HashMap::new(), + open_documents_count: 0, options: Default::default(), - files_with_errors: HashMap::new(), } } diff --git a/noir/noir-repo/tooling/lsp/src/notifications/mod.rs b/noir/noir-repo/tooling/lsp/src/notifications/mod.rs index 96e339ee212..6cddd278e62 100644 --- a/noir/noir-repo/tooling/lsp/src/notifications/mod.rs +++ b/noir/noir-repo/tooling/lsp/src/notifications/mod.rs @@ -2,7 +2,9 @@ use std::collections::HashSet; use std::ops::ControlFlow; use std::path::PathBuf; -use crate::insert_all_files_for_workspace_into_file_manager; +use crate::{ + insert_all_files_for_workspace_into_file_manager, PackageCacheData, WorkspaceCacheData, +}; use async_lsp::{ErrorCode, LanguageClient, ResponseError}; use fm::{FileManager, FileMap}; use fxhash::FxHashMap as HashMap; @@ -79,7 +81,8 @@ pub(super) fn on_did_close_text_document( state.open_documents_count -= 1; if state.open_documents_count == 0 { - state.cached_definitions.clear(); + state.package_cache.clear(); + state.workspace_cache.clear(); } let document_uri = params.text_document.uri; @@ -155,8 +158,15 @@ pub(crate) fn process_workspace_for_noir_document( Some(&file_path), ); state.cached_lenses.insert(document_uri.to_string(), collected_lenses); - state.cached_definitions.insert(package.root_dir.clone(), context.def_interner); - state.cached_def_maps.insert(package.root_dir.clone(), context.def_maps); + state.package_cache.insert( + package.root_dir.clone(), + PackageCacheData { + crate_id, + crate_graph: context.crate_graph, + node_interner: context.def_interner, + def_maps: context.def_maps, + }, + ); let fm = &context.file_manager; let files = fm.as_file_map(); @@ -166,6 +176,11 @@ pub(crate) fn process_workspace_for_noir_document( } } + state.workspace_cache.insert( + workspace.root_dir.clone(), + WorkspaceCacheData { file_manager: workspace_file_manager }, + ); + Ok(()) } diff --git a/noir/noir-repo/tooling/lsp/src/requests/code_action/fill_struct_fields.rs b/noir/noir-repo/tooling/lsp/src/requests/code_action/fill_struct_fields.rs index f57fbc652ad..be8602d99a9 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/code_action/fill_struct_fields.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/code_action/fill_struct_fields.rs @@ -1,6 +1,9 @@ use lsp_types::TextEdit; use noirc_errors::{Location, Span}; -use noirc_frontend::{ast::ConstructorExpression, node_interner::ReferenceId}; +use noirc_frontend::{ + ast::{ConstructorExpression, UnresolvedTypeData}, + node_interner::ReferenceId, +}; use crate::byte_span_to_range; @@ -12,8 +15,11 @@ impl<'a> CodeActionFinder<'a> { return; } - // Find out which struct this is - let location = Location::new(constructor.type_name.last_ident().span(), self.file); + let UnresolvedTypeData::Named(path, _, _) = &constructor.typ.typ else { + return; + }; + + let location = Location::new(path.span, self.file); let Some(ReferenceId::Struct(struct_id)) = self.interner.find_referenced(location) else { return; }; diff --git a/noir/noir-repo/tooling/lsp/src/requests/code_action/import_or_qualify.rs b/noir/noir-repo/tooling/lsp/src/requests/code_action/import_or_qualify.rs index d537144ce7b..03a953bde85 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/code_action/import_or_qualify.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/code_action/import_or_qualify.rs @@ -134,7 +134,7 @@ mod tests { let src = r#" mod foo { mod bar { - struct SomeTypeInBar {} + pub struct SomeTypeInBar {} } } @@ -144,7 +144,7 @@ mod tests { let expected = r#" mod foo { mod bar { - struct SomeTypeInBar {} + pub struct SomeTypeInBar {} } } @@ -160,7 +160,7 @@ mod tests { let src = r#"mod foo { mod bar { - struct SomeTypeInBar {} + pub struct SomeTypeInBar {} } } @@ -170,7 +170,7 @@ fn foo(x: SomeType>| NodeFinder<'a> { } fn complete_constructor_field_name(&mut self, constructor_expression: &ConstructorExpression) { - let location = - Location::new(constructor_expression.type_name.last_ident().span(), self.file); + let span = if let UnresolvedTypeData::Named(path, _, _) = &constructor_expression.typ.typ { + path.last_ident().span() + } else { + constructor_expression.typ.span + }; + + let location = Location::new(span, self.file); let Some(ReferenceId::Struct(struct_id)) = self.interner.find_referenced(location) else { return; }; @@ -546,6 +551,7 @@ impl<'a> NodeFinder<'a> { function_completion_kind: FunctionCompletionKind, self_prefix: bool, ) { + let typ = &typ; match typ { Type::Struct(struct_type, generics) => { self.complete_struct_fields(&struct_type.borrow(), generics, prefix, self_prefix); @@ -570,6 +576,16 @@ impl<'a> NodeFinder<'a> { Type::Tuple(types) => { self.complete_tuple_fields(types, self_prefix); } + Type::TypeVariable(var, _) | Type::NamedGeneric(var, _, _) => { + if let TypeBinding::Bound(typ) = &*var.borrow() { + self.complete_type_fields_and_methods( + typ, + prefix, + function_completion_kind, + self_prefix, + ); + } + } Type::FieldElement | Type::Array(_, _) | Type::Slice(_) @@ -578,9 +594,7 @@ impl<'a> NodeFinder<'a> { | Type::String(_) | Type::FmtString(_, _) | Type::Unit - | Type::TypeVariable(_, _) | Type::TraitAsType(_, _, _) - | Type::NamedGeneric(_, _, _) | Type::Function(..) | Type::Forall(_, _) | Type::Constant(_) @@ -927,7 +941,8 @@ impl<'a> NodeFinder<'a> { if let Some(ReferenceId::Local(definition_id)) = self.interner.find_referenced(location) { - self.self_type = Some(self.interner.definition_type(definition_id)); + self.self_type = + Some(self.interner.definition_type(definition_id).follow_bindings()); } } } @@ -936,6 +951,32 @@ impl<'a> NodeFinder<'a> { } } + fn get_lvalue_type(&self, lvalue: &LValue) -> Option { + match lvalue { + LValue::Ident(ident) => { + let location = Location::new(ident.span(), self.file); + if let Some(ReferenceId::Local(definition_id)) = + self.interner.find_referenced(location) + { + let typ = self.interner.definition_type(definition_id); + Some(typ) + } else { + None + } + } + LValue::MemberAccess { object, field_name, .. } => { + let typ = self.get_lvalue_type(object)?; + get_field_type(&typ, &field_name.0.contents) + } + LValue::Index { array, .. } => { + let typ = self.get_lvalue_type(array)?; + get_array_element_type(typ) + } + LValue::Dereference(lvalue, ..) => self.get_lvalue_type(lvalue), + LValue::Interned(..) => None, + } + } + fn includes_span(&self, span: Span) -> bool { span.start() as usize <= self.byte_index && self.byte_index <= span.end() as usize } @@ -1148,7 +1189,6 @@ impl<'a> Visitor for NodeFinder<'a> { if after_dot && call_expression.func.span.end() as usize == self.byte_index - 1 { let location = Location::new(call_expression.func.span, self.file); 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( @@ -1179,7 +1219,6 @@ impl<'a> Visitor for NodeFinder<'a> { if self.includes_span(method_call_expression.method_name.span()) { let location = Location::new(method_call_expression.object.span, self.file); if let Some(typ) = self.interner.type_at_location(location) { - let typ = typ.follow_bindings(); let prefix = method_call_expression.method_name.to_string(); let offset = self.byte_index - method_call_expression.method_name.span().start() as usize; @@ -1253,6 +1292,7 @@ impl<'a> Visitor for NodeFinder<'a> { } fn visit_lvalue_ident(&mut self, ident: &Ident) { + // If we have `foo.>|<` we suggest `foo`'s type fields and methods if self.byte == Some(b'.') && ident.span().end() as usize == self.byte_index - 1 { let location = Location::new(ident.span(), self.file); if let Some(ReferenceId::Local(definition_id)) = self.interner.find_referenced(location) @@ -1270,6 +1310,72 @@ impl<'a> Visitor for NodeFinder<'a> { } } + fn visit_lvalue_member_access( + &mut self, + object: &LValue, + field_name: &Ident, + span: Span, + ) -> bool { + // If we have `foo.bar.>|<` we solve the type of `foo`, get the field `bar`, + // then suggest methods of the resulting type. + if self.byte == Some(b'.') && span.end() as usize == self.byte_index - 1 { + if let Some(typ) = self.get_lvalue_type(object) { + if let Some(typ) = get_field_type(&typ, &field_name.0.contents) { + let prefix = ""; + let self_prefix = false; + self.complete_type_fields_and_methods( + &typ, + prefix, + FunctionCompletionKind::NameAndParameters, + self_prefix, + ); + } + } + + return false; + } + true + } + + fn visit_lvalue_index(&mut self, array: &LValue, _index: &Expression, span: Span) -> bool { + // If we have `foo[index].>|<` we solve the type of `foo`, then get the array/slice element type, + // then suggest methods of that type. + if self.byte == Some(b'.') && span.end() as usize == self.byte_index - 1 { + if let Some(typ) = self.get_lvalue_type(array) { + if let Some(typ) = get_array_element_type(typ) { + let prefix = ""; + let self_prefix = false; + self.complete_type_fields_and_methods( + &typ, + prefix, + FunctionCompletionKind::NameAndParameters, + self_prefix, + ); + } + } + return false; + } + true + } + + fn visit_lvalue_dereference(&mut self, lvalue: &LValue, span: Span) -> bool { + if self.byte == Some(b'.') && span.end() as usize == self.byte_index - 1 { + if let Some(typ) = self.get_lvalue_type(lvalue) { + let prefix = ""; + let self_prefix = false; + self.complete_type_fields_and_methods( + &typ, + prefix, + FunctionCompletionKind::NameAndParameters, + self_prefix, + ); + } + return false; + } + + true + } + fn visit_variable(&mut self, path: &Path, _: Span) -> bool { self.find_in_path(path, RequestedItems::AnyItems); false @@ -1289,7 +1395,6 @@ impl<'a> Visitor for NodeFinder<'a> { { let location = Location::new(expression.span, self.file); 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( @@ -1325,7 +1430,11 @@ impl<'a> Visitor for NodeFinder<'a> { constructor_expression: &ConstructorExpression, _: Span, ) -> bool { - self.find_in_path(&constructor_expression.type_name, RequestedItems::OnlyTypes); + let UnresolvedTypeData::Named(path, _, _) = &constructor_expression.typ.typ else { + return true; + }; + + self.find_in_path(path, RequestedItems::OnlyTypes); // Check if we need to autocomplete the field name if constructor_expression @@ -1355,7 +1464,6 @@ impl<'a> Visitor for NodeFinder<'a> { // Assuming member_access_expression is of the form `foo.bar`, we are right after `bar` let location = Location::new(member_access_expression.lhs.span, self.file); 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( @@ -1425,7 +1533,7 @@ impl<'a> Visitor for NodeFinder<'a> { false } - fn visit_custom_attribute(&mut self, attribute: &CustomAtrribute, target: AttributeTarget) { + fn visit_custom_attribute(&mut self, attribute: &CustomAttribute, target: AttributeTarget) { if self.byte_index != attribute.contents_span.end() as usize { return; } @@ -1434,6 +1542,48 @@ impl<'a> Visitor for NodeFinder<'a> { } } +fn get_field_type(typ: &Type, name: &str) -> Option { + match typ { + Type::Struct(struct_type, generics) => { + Some(struct_type.borrow().get_field(name, generics)?.0) + } + Type::Tuple(types) => { + if let Ok(index) = name.parse::() { + types.get(index as usize).cloned() + } else { + None + } + } + Type::Alias(alias_type, generics) => Some(alias_type.borrow().get_type(generics)), + Type::TypeVariable(var, _) | Type::NamedGeneric(var, _, _) => { + if let TypeBinding::Bound(typ) = &*var.borrow() { + get_field_type(typ, name) + } else { + None + } + } + _ => None, + } +} + +fn get_array_element_type(typ: Type) -> Option { + match typ { + Type::Array(_, typ) | Type::Slice(typ) => Some(*typ), + Type::Alias(alias_type, generics) => { + let typ = alias_type.borrow().get_type(&generics); + get_array_element_type(typ) + } + Type::TypeVariable(var, _) | Type::NamedGeneric(var, _, _) => { + if let TypeBinding::Bound(typ) = &*var.borrow() { + get_array_element_type(typ.clone()) + } else { + None + } + } + _ => None, + } +} + /// Returns true if name matches a prefix written in code. /// `prefix` must already be in snake case. /// This method splits both name and prefix by underscore, 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 f019d9980ab..50f4412d7a8 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs @@ -176,7 +176,7 @@ mod completion_tests { async fn test_use_struct() { let src = r#" mod foo { - struct Foo {} + pub struct Foo {} } use foo::>|< "#; @@ -2015,4 +2015,108 @@ mod completion_tests { ) .await; } + + #[test] + async fn test_suggests_when_assignment_follows_in_chain_1() { + let src = r#" + struct Foo { + bar: Bar + } + + struct Bar { + baz: Field + } + + fn f(foo: Foo) { + let mut x = 1; + + foo.bar.>|< + + x = 2; + }"#; + + assert_completion(src, vec![field_completion_item("baz", "Field")]).await; + } + + #[test] + async fn test_suggests_when_assignment_follows_in_chain_2() { + let src = r#" + struct Foo { + bar: Bar + } + + struct Bar { + baz: Baz + } + + struct Baz { + qux: Field + } + + fn f(foo: Foo) { + let mut x = 1; + + foo.bar.baz.>|< + + x = 2; + }"#; + + assert_completion(src, vec![field_completion_item("qux", "Field")]).await; + } + + #[test] + async fn test_suggests_when_assignment_follows_in_chain_3() { + let src = r#" + struct Foo { + foo: Field + } + + fn execute() { + let a = Foo { foo: 1 }; + a.>|< + + x = 1; + }"#; + + assert_completion(src, vec![field_completion_item("foo", "Field")]).await; + } + + #[test] + async fn test_suggests_when_assignment_follows_in_chain_4() { + let src = r#" + struct Foo { + bar: Bar + } + + struct Bar { + baz: Field + } + + fn execute() { + let foo = Foo { foo: 1 }; + foo.bar.>|< + + x = 1; + }"#; + + assert_completion(src, vec![field_completion_item("baz", "Field")]).await; + } + + #[test] + async fn test_suggests_when_assignment_follows_in_chain_with_index() { + let src = r#" + struct Foo { + bar: Field + } + + fn f(foos: [Foo; 3]) { + let mut x = 1; + + foos[0].>|< + + x = 2; + }"#; + + assert_completion(src, vec![field_completion_item("bar", "Field")]).await; + } } diff --git a/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs b/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs index 43e0227fa26..012e2cbef13 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs @@ -542,6 +542,7 @@ fn get_expression_name(expression: &Expression) -> Option { | ExpressionKind::Comptime(..) | ExpressionKind::Resolved(..) | ExpressionKind::Interned(..) + | ExpressionKind::InternedStatement(..) | ExpressionKind::Literal(..) | ExpressionKind::Unsafe(..) | ExpressionKind::Error => None, diff --git a/noir/noir-repo/tooling/lsp/src/requests/mod.rs b/noir/noir-repo/tooling/lsp/src/requests/mod.rs index fea758e0e52..c8d3e2f5f0e 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/mod.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/mod.rs @@ -2,9 +2,9 @@ use std::collections::BTreeMap; use std::path::PathBuf; use std::{collections::HashMap, future::Future}; -use crate::insert_all_files_for_workspace_into_file_manager; +use crate::{insert_all_files_for_workspace_into_file_manager, parse_diff, PackageCacheData}; use crate::{ - parse_diff, resolve_workspace_for_source_path, + resolve_workspace_for_source_path, types::{CodeLensOptions, InitializeParams}, }; use async_lsp::{ErrorCode, ResponseError}; @@ -407,7 +407,7 @@ pub(crate) struct ProcessRequestCallbackArgs<'a> { location: noirc_errors::Location, files: &'a FileMap, interner: &'a NodeInterner, - interners: &'a HashMap, + package_cache: &'a HashMap, crate_id: CrateId, crate_name: String, dependencies: &'a Vec, @@ -432,6 +432,63 @@ where ResponseError::new(ErrorCode::REQUEST_FAILED, "Could not find package for file") })?; + // In practice when `process_request` is called, a document in the project should already have been + // open so both the workspace and package cache will have data. However, just in case this isn't true + // for some reason, and also for tests (some tests just test a request without going through the full + // LSP workflow), we have a fallback where we type-check the workspace/package, then continue with + // processing the request. + let Some(workspace_cache_data) = state.workspace_cache.get(&workspace.root_dir) else { + return process_request_no_workspace_cache(state, text_document_position_params, callback); + }; + + let Some(package_cache_data) = state.package_cache.get(&package.root_dir) else { + return process_request_no_workspace_cache(state, text_document_position_params, callback); + }; + + let file_manager = &workspace_cache_data.file_manager; + let interner = &package_cache_data.node_interner; + let def_maps = &package_cache_data.def_maps; + let crate_graph = &package_cache_data.crate_graph; + let crate_id = package_cache_data.crate_id; + + let files = file_manager.as_file_map(); + + let location = position_to_location( + files, + &PathString::from(file_path), + &text_document_position_params.position, + )?; + + Ok(callback(ProcessRequestCallbackArgs { + location, + files, + interner, + package_cache: &state.package_cache, + crate_id, + crate_name: package.name.to_string(), + dependencies: &crate_graph[crate_id].dependencies, + def_maps, + })) +} + +pub(crate) fn process_request_no_workspace_cache( + state: &mut LspState, + text_document_position_params: TextDocumentPositionParams, + callback: F, +) -> Result +where + F: FnOnce(ProcessRequestCallbackArgs) -> T, +{ + let file_path = + text_document_position_params.text_document.uri.to_file_path().map_err(|_| { + ResponseError::new(ErrorCode::REQUEST_FAILED, "URI is not a valid file path") + })?; + + let workspace = resolve_workspace_for_source_path(file_path.as_path()).unwrap(); + let package = crate::workspace_package_for_file(&workspace, &file_path).ok_or_else(|| { + ResponseError::new(ErrorCode::REQUEST_FAILED, "Could not find package for file") + })?; + let mut workspace_file_manager = workspace.new_file_manager(); insert_all_files_for_workspace_into_file_manager( state, @@ -445,9 +502,9 @@ where let interner; let def_maps; - if let Some(def_interner) = state.cached_definitions.get(&package.root_dir) { - interner = def_interner; - def_maps = state.cached_def_maps.get(&package.root_dir).unwrap(); + if let Some(package_cache) = state.package_cache.get(&package.root_dir) { + interner = &package_cache.node_interner; + def_maps = &package_cache.def_maps; } else { // We ignore the warnings and errors produced by compilation while resolving the definition let _ = noirc_driver::check_crate(&mut context, crate_id, &Default::default()); @@ -455,7 +512,7 @@ where def_maps = &context.def_maps; } - let files = context.file_manager.as_file_map(); + let files = workspace_file_manager.as_file_map(); let location = position_to_location( files, @@ -467,17 +524,18 @@ where location, files, interner, - interners: &state.cached_definitions, + package_cache: &state.package_cache, crate_id, crate_name: package.name.to_string(), - dependencies: &context.crate_graph[context.root_crate_id()].dependencies, + dependencies: &context.crate_graph[crate_id].dependencies, def_maps, })) } + pub(crate) fn find_all_references_in_workspace( location: noirc_errors::Location, interner: &NodeInterner, - cached_interners: &HashMap, + package_cache: &HashMap, files: &FileMap, include_declaration: bool, include_self_type_name: bool, @@ -499,10 +557,10 @@ pub(crate) fn find_all_references_in_workspace( include_declaration, include_self_type_name, ); - for interner in cached_interners.values() { + for cache_data in package_cache.values() { locations.extend(find_all_references( referenced_location, - interner, + &cache_data.node_interner, files, include_declaration, include_self_type_name, diff --git a/noir/noir-repo/tooling/lsp/src/requests/references.rs b/noir/noir-repo/tooling/lsp/src/requests/references.rs index c720156659d..6d3f92447cb 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/references.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/references.rs @@ -16,7 +16,7 @@ pub(crate) fn on_references_request( find_all_references_in_workspace( args.location, args.interner, - args.interners, + args.package_cache, args.files, include_declaration, true, diff --git a/noir/noir-repo/tooling/lsp/src/requests/rename.rs b/noir/noir-repo/tooling/lsp/src/requests/rename.rs index 84956681167..95dd6b506be 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/rename.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/rename.rs @@ -38,7 +38,7 @@ pub(crate) fn on_rename_request( let rename_changes = find_all_references_in_workspace( args.location, args.interner, - args.interners, + args.package_cache, args.files, true, false, diff --git a/noir/noir-repo/tooling/lsp/src/requests/test_run.rs b/noir/noir-repo/tooling/lsp/src/requests/test_run.rs index 081eb815c8b..50c699bb6a6 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/test_run.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/test_run.rs @@ -84,7 +84,7 @@ fn on_test_run_request_inner( &state.solver, &mut context, &test_function, - false, + true, None, Some(workspace.root_dir.clone()), Some(package.name.to_string()), diff --git a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs index caa60b17cc2..81a7a219e06 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs @@ -143,9 +143,9 @@ pub(crate) fn rewrite( super::parenthesized(visitor, shape, span, *sub_expr) } ExpressionKind::Constructor(constructor) => { - let type_name = visitor.slice(span.start()..constructor.type_name.span().end()); - let fields_span = visitor - .span_before(constructor.type_name.span().end()..span.end(), Token::LeftBrace); + let type_name = visitor.slice(span.start()..constructor.typ.span.end()); + let fields_span = + visitor.span_before(constructor.typ.span.end()..span.end(), Token::LeftBrace); visitor.format_struct_lit(type_name, fields_span, *constructor) } @@ -178,6 +178,11 @@ pub(crate) fn rewrite( ExpressionKind::Interned(_) => { unreachable!("ExpressionKind::Interned should only emitted by the comptime interpreter") } + ExpressionKind::InternedStatement(_) => { + unreachable!( + "ExpressionKind::InternedStatement should only emitted by the comptime interpreter" + ) + } ExpressionKind::Unquote(expr) => { if matches!(&expr.kind, ExpressionKind::Variable(..)) { format!("${expr}")