From d0afc12ea7ae0b3a23a6724c91ec79a57301b0c1 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Thu, 2 May 2024 15:32:58 -0500 Subject: [PATCH 01/38] Add elaborator --- compiler/noirc_frontend/src/elaborator/mod.rs | 121 ++++++++++++++++++ .../noirc_frontend/src/elaborator/scope.rs | 16 +++ .../src/hir/resolution/resolver.rs | 4 +- compiler/noirc_frontend/src/lib.rs | 1 + compiler/noirc_frontend/src/node_interner.rs | 6 +- 5 files changed, 143 insertions(+), 5 deletions(-) create mode 100644 compiler/noirc_frontend/src/elaborator/mod.rs create mode 100644 compiler/noirc_frontend/src/elaborator/scope.rs diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs new file mode 100644 index 00000000000..6046b43e74a --- /dev/null +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -0,0 +1,121 @@ +use crate::{macros_api::{NoirFunction, Expression, BlockExpression, Statement, StatementKind, ExpressionKind, NodeInterner, HirExpression, HirStatement}, node_interner::{FuncId, StmtId, ExprId}, ast::FunctionKind, hir_def::expr::HirBlockExpression, hir::{resolution::errors::ResolverError, def_collector::dc_crate::CompilationError}, Type}; + +mod scope; + +use fm::FileId; +use iter_extended::vecmap; +use scope::Scope; + +struct Elaborator { + globals: Scope, + local_scopes: Vec, + + errors: Vec, + + interner: NodeInterner, + file: FileId, + + in_unconstrained_fn: bool, + nested_loops: usize, +} + +impl Elaborator { + fn elaborate_function(&mut self, function: NoirFunction, id: FuncId) { + match function.kind { + FunctionKind::LowLevel => todo!(), + FunctionKind::Builtin => todo!(), + FunctionKind::Oracle => todo!(), + FunctionKind::Recursive => todo!(), + FunctionKind::Normal => { + let _body = self.elaborate_block(function.def.body); + }, + } + } + + fn elaborate_expression(&mut self, expr: Expression) -> (ExprId, Type) { + let (hir_expr, typ) = match expr.kind { + ExpressionKind::Literal(_) => todo!(), + ExpressionKind::Block(_) => todo!(), + ExpressionKind::Prefix(_) => todo!(), + ExpressionKind::Index(_) => todo!(), + ExpressionKind::Call(_) => todo!(), + ExpressionKind::MethodCall(_) => todo!(), + ExpressionKind::Constructor(_) => todo!(), + ExpressionKind::MemberAccess(_) => todo!(), + ExpressionKind::Cast(_) => todo!(), + ExpressionKind::Infix(_) => todo!(), + ExpressionKind::If(_) => todo!(), + ExpressionKind::Variable(_) => todo!(), + ExpressionKind::Tuple(_) => todo!(), + ExpressionKind::Lambda(_) => todo!(), + ExpressionKind::Parenthesized(expr) => return self.elaborate_expression(*expr), + ExpressionKind::Quote(_) => todo!(), + ExpressionKind::Comptime(_) => todo!(), + ExpressionKind::Error => (HirExpression::Error, Type::Error), + }; + let id = self.interner.push_expr(hir_expr); + self.interner.push_expr_location(id, expr.span, self.file); + self.interner.push_expr_type(id, typ.clone()); + (id, typ) + } + + fn elaborate_statement(&mut self, statement: Statement) -> (StmtId, Type) { + let (hir_statement, typ) = match statement.kind { + StatementKind::Let(_) => todo!(), + StatementKind::Constrain(_) => todo!(), + StatementKind::Expression(_) => todo!(), + StatementKind::Assign(_) => todo!(), + StatementKind::For(_) => todo!(), + StatementKind::Break => self.elaborate_jump(true, statement.span), + StatementKind::Continue => self.elaborate_jump(false, statement.span), + StatementKind::Comptime(_) => todo!(), + StatementKind::Semi(expr) => { + let (expr, _typ) = self.elaborate_expression(expr); + (HirStatement::Semi(expr), Type::Unit) + } + StatementKind::Error => (HirStatement::Error, Type::Error), + }; + let id = self.interner.push_stmt(hir_statement); + self.interner.push_stmt_location(id, statement.span, self.file); + (id, typ) + } + + fn elaborate_block(&mut self, block: BlockExpression) -> HirBlockExpression { + self.push_scope(); + + let statements = vecmap(block.statements, |statement| { + self.elaborate_statement(statement) + }); + + self.pop_scope(); + HirBlockExpression { statements } + } + + fn push_scope(&mut self) { + self.local_scopes.push(Scope::default()); + } + + fn pop_scope(&mut self) { + self.local_scopes.pop(); + } + + fn elaborate_jump(&mut self, is_break: bool, span: noirc_errors::Span) -> (HirStatement, Type) { + if !self.in_unconstrained_fn { + self.push_err(ResolverError::JumpInConstrainedFn { is_break, span }); + } + if self.nested_loops == 0 { + self.push_err(ResolverError::JumpOutsideLoop { is_break, span }); + } + + let expr = if is_break { + HirStatement::Break + } else { + HirStatement::Continue + }; + (expr, self.interner.next_type_variable()) + } + + fn push_err(&mut self, error: impl Into) { + self.errors.push(error.into()); + } +} diff --git a/compiler/noirc_frontend/src/elaborator/scope.rs b/compiler/noirc_frontend/src/elaborator/scope.rs new file mode 100644 index 00000000000..a3a7023a5c1 --- /dev/null +++ b/compiler/noirc_frontend/src/elaborator/scope.rs @@ -0,0 +1,16 @@ +use rustc_hash::FxHashMap as HashMap; + +use crate::{macros_api::StructId, node_interner::{TypeAliasId, DefinitionId}}; +use crate::hir::comptime::Value; + +#[derive(Default)] +pub(super) struct Scope { + types: HashMap, + values: HashMap, + comptime_values: HashMap, +} + +pub(super) enum TypeId { + Struct(StructId), + Alias(TypeAliasId), +} diff --git a/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/compiler/noirc_frontend/src/hir/resolution/resolver.rs index 6c07957b27f..cd9be93fab9 100644 --- a/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -1362,7 +1362,7 @@ impl<'a> Resolver<'a> { StatementKind::Comptime(statement) => { let hir_statement = self.resolve_stmt(statement.kind, statement.span); let statement_id = self.interner.push_stmt(hir_statement); - self.interner.push_statement_location(statement_id, statement.span, self.file); + self.interner.push_stmt_location(statement_id, statement.span, self.file); HirStatement::Comptime(statement_id) } } @@ -1413,7 +1413,7 @@ impl<'a> Resolver<'a> { pub fn intern_stmt(&mut self, stmt: Statement) -> StmtId { let hir_stmt = self.resolve_stmt(stmt.kind, stmt.span); let id = self.interner.push_stmt(hir_stmt); - self.interner.push_statement_location(id, stmt.span, self.file); + self.interner.push_stmt_location(id, stmt.span, self.file); id } diff --git a/compiler/noirc_frontend/src/lib.rs b/compiler/noirc_frontend/src/lib.rs index 958a18ac2fb..b05c635f436 100644 --- a/compiler/noirc_frontend/src/lib.rs +++ b/compiler/noirc_frontend/src/lib.rs @@ -12,6 +12,7 @@ pub mod ast; pub mod debug; +pub mod elaborator; pub mod graph; pub mod lexer; pub mod monomorphization; diff --git a/compiler/noirc_frontend/src/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs index 88adc7a9414..faf89016f96 100644 --- a/compiler/noirc_frontend/src/node_interner.rs +++ b/compiler/noirc_frontend/src/node_interner.rs @@ -532,7 +532,7 @@ impl NodeInterner { self.id_to_type.insert(expr_id.into(), typ); } - /// Store the type for an interned expression + /// Store the type for a definition pub fn push_definition_type(&mut self, definition_id: DefinitionId, typ: Type) { self.definition_to_type.insert(definition_id, typ); } @@ -696,7 +696,7 @@ impl NodeInterner { let statement = self.push_stmt(HirStatement::Error); let span = name.span(); let id = self.push_global(name, local_id, statement, file, attributes, mutable); - self.push_statement_location(statement, span, file); + self.push_stmt_location(statement, span, file); id } @@ -942,7 +942,7 @@ impl NodeInterner { self.id_location(stmt_id) } - pub fn push_statement_location(&mut self, id: StmtId, span: Span, file: FileId) { + pub fn push_stmt_location(&mut self, id: StmtId, span: Span, file: FileId) { self.id_to_location.insert(id.into(), Location::new(span, file)); } From 225303f33e46ed1f09629f6da27008d4529ea404 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Mon, 6 May 2024 15:59:32 -0500 Subject: [PATCH 02/38] More expressions --- compiler/noirc_frontend/src/ast/statement.rs | 5 +- compiler/noirc_frontend/src/elaborator/mod.rs | 497 +++++++++++- .../noirc_frontend/src/elaborator/patterns.rs | 427 ++++++++++ .../noirc_frontend/src/elaborator/scope.rs | 86 +- .../src/elaborator/statements.rs | 357 +++++++++ .../noirc_frontend/src/elaborator/types.rs | 743 ++++++++++++++++++ .../src/hir/resolution/import.rs | 9 + .../src/hir/resolution/resolver.rs | 8 +- .../noirc_frontend/src/hir/type_check/mod.rs | 2 +- compiler/noirc_frontend/src/hir_def/types.rs | 8 +- 10 files changed, 2099 insertions(+), 43 deletions(-) create mode 100644 compiler/noirc_frontend/src/elaborator/patterns.rs create mode 100644 compiler/noirc_frontend/src/elaborator/statements.rs create mode 100644 compiler/noirc_frontend/src/elaborator/types.rs diff --git a/compiler/noirc_frontend/src/ast/statement.rs b/compiler/noirc_frontend/src/ast/statement.rs index 0da39edfd85..94b5841e52c 100644 --- a/compiler/noirc_frontend/src/ast/statement.rs +++ b/compiler/noirc_frontend/src/ast/statement.rs @@ -565,7 +565,7 @@ impl ForRange { identifier: Ident, block: Expression, for_loop_span: Span, - ) -> StatementKind { + ) -> Statement { /// Counter used to generate unique names when desugaring /// code in the parser requires the creation of fresh variables. /// The parser is stateless so this is a static global instead. @@ -662,7 +662,8 @@ impl ForRange { let block = ExpressionKind::Block(BlockExpression { statements: vec![let_array, for_loop], }); - StatementKind::Expression(Expression::new(block, for_loop_span)) + let kind = StatementKind::Expression(Expression::new(block, for_loop_span)); + Statement { kind, span: for_loop_span } } } } diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 6046b43e74a..c590a260494 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -1,12 +1,32 @@ -use crate::{macros_api::{NoirFunction, Expression, BlockExpression, Statement, StatementKind, ExpressionKind, NodeInterner, HirExpression, HirStatement}, node_interner::{FuncId, StmtId, ExprId}, ast::FunctionKind, hir_def::expr::HirBlockExpression, hir::{resolution::errors::ResolverError, def_collector::dc_crate::CompilationError}, Type}; +use std::{rc::Rc, collections::{BTreeMap, BTreeSet}}; + +use crate::{macros_api::{NoirFunction, Expression, BlockExpression, Statement, StatementKind, ExpressionKind, NodeInterner, HirExpression, HirStatement, StructId, Literal, PrefixExpression, IndexExpression, CallExpression, MethodCallExpression, MemberAccessExpression, CastExpression, HirLiteral}, node_interner::{FuncId, StmtId, ExprId, DependencyId, TraitId, DefinitionKind}, ast::{FunctionKind, UnresolvedTraitConstraint, ConstructorExpression, InfixExpression, IfExpression, Lambda, ArrayLiteral, UnresolvedTypeExpression}, hir_def::{expr::{HirBlockExpression, HirIdent, HirArrayLiteral, HirLambda, HirIfExpression, HirPrefixExpression, HirIndexExpression, HirCallExpression}, traits::TraitConstraint}, hir::{resolution::{errors::ResolverError, path_resolver::PathResolver, resolver::LambdaContext}, def_collector::dc_crate::CompilationError, type_check::TypeCheckError, scope::ScopeForest as GenericScopeForest}, Type, TypeVariable}; +use crate::graph::CrateId; +use crate::hir::def_map::CrateDefMap; mod scope; +mod types; +mod patterns; +mod statements; use fm::FileId; use iter_extended::vecmap; +use noirc_errors::{Span, Location}; +use regex::Regex; use scope::Scope; +/// ResolverMetas are tagged onto each definition to track how many times they are used +#[derive(Debug, PartialEq, Eq)] +struct ResolverMeta { + num_times_used: usize, + ident: HirIdent, + warn_if_unused: bool, +} + +type ScopeForest = GenericScopeForest; + struct Elaborator { + scopes: ScopeForest, globals: Scope, local_scopes: Vec, @@ -17,6 +37,65 @@ struct Elaborator { in_unconstrained_fn: bool, nested_loops: usize, + + /// True if the current module is a contract. + /// This is usually determined by self.path_resolver.module_id(), but it can + /// be overridden for impls. Impls are an odd case since the methods within resolve + /// as if they're in the parent module, but should be placed in a child module. + /// Since they should be within a child module, in_contract is manually set to false + /// for these so we can still resolve them in the parent module without them being in a contract. + in_contract: bool, + + /// Contains a mapping of the current struct or functions's generics to + /// unique type variables if we're resolving a struct. Empty otherwise. + /// This is a Vec rather than a map to preserve the order a functions generics + /// were declared in. + generics: Vec<(Rc, TypeVariable, Span)>, + + /// When resolving lambda expressions, we need to keep track of the variables + /// that are captured. We do this in order to create the hidden environment + /// parameter for the lambda function. + lambda_stack: Vec, + + /// Set to the current type if we're resolving an impl + self_type: Option, + + /// The current dependency item we're resolving. + /// Used to link items to their dependencies in the dependency graph + current_item: Option, + + trait_id: Option, + + path_resolver: Rc, + def_maps: BTreeMap, + + /// In-resolution names + /// + /// This needs to be a set because we can have multiple in-resolution + /// names when resolving structs that are declared in reverse order of their + /// dependencies, such as in the following case: + /// + /// ``` + /// struct Wrapper { + /// value: Wrapped + /// } + /// struct Wrapped { + /// } + /// ``` + resolving_ids: BTreeSet, + + trait_bounds: Vec, + + /// All type variables created in the current function. + /// This map is used to default any integer type variables at the end of + /// a function (before checking trait constraints) if a type wasn't already chosen. + type_variables: Vec, + + /// Trait constraints are collected during type checking until they are + /// verified at the end of a function. This is because constraints arise + /// on each variable, but it is only until function calls when the types + /// needed for the trait constraint may become known. + trait_constraints: Vec<(TraitConstraint, ExprId)>, } impl Elaborator { @@ -34,23 +113,23 @@ impl Elaborator { fn elaborate_expression(&mut self, expr: Expression) -> (ExprId, Type) { let (hir_expr, typ) = match expr.kind { - ExpressionKind::Literal(_) => todo!(), - ExpressionKind::Block(_) => todo!(), - ExpressionKind::Prefix(_) => todo!(), - ExpressionKind::Index(_) => todo!(), - ExpressionKind::Call(_) => todo!(), - ExpressionKind::MethodCall(_) => todo!(), - ExpressionKind::Constructor(_) => todo!(), - ExpressionKind::MemberAccess(_) => todo!(), - ExpressionKind::Cast(_) => todo!(), - ExpressionKind::Infix(_) => todo!(), - ExpressionKind::If(_) => todo!(), - ExpressionKind::Variable(_) => todo!(), - ExpressionKind::Tuple(_) => todo!(), - ExpressionKind::Lambda(_) => todo!(), + ExpressionKind::Literal(literal) => self.elaborate_literal(literal, expr.span), + ExpressionKind::Block(block) => self.elaborate_block(block), + ExpressionKind::Prefix(prefix) => self.elaborate_prefix(*prefix), + ExpressionKind::Index(index) => self.elaborate_index(*index), + ExpressionKind::Call(call) => self.elaborate_call(*call, expr.span), + ExpressionKind::MethodCall(methodCall) => self.elaborate_method_call(*methodCall), + ExpressionKind::Constructor(constructor) => self.elaborate_constructor(*constructor), + ExpressionKind::MemberAccess(memberAccess) => self.elaborate_member_access(*memberAccess), + ExpressionKind::Cast(cast) => self.elaborate_cast(*cast), + ExpressionKind::Infix(infix) => self.elaborate_infix(*infix), + ExpressionKind::If(if_) => self.elaborate_if(*if_), + ExpressionKind::Variable(variable) => return self.elaborate_variable(variable), + ExpressionKind::Tuple(tuple) => self.elaborate_tuple(tuple), + ExpressionKind::Lambda(lambda) => self.elaborate_lambda(*lambda), ExpressionKind::Parenthesized(expr) => return self.elaborate_expression(*expr), - ExpressionKind::Quote(_) => todo!(), - ExpressionKind::Comptime(_) => todo!(), + ExpressionKind::Quote(quote) => self.elaborate_quote(quote), + ExpressionKind::Comptime(comptime) => self.elaborate_comptime_block(comptime), ExpressionKind::Error => (HirExpression::Error, Type::Error), }; let id = self.interner.push_expr(hir_expr); @@ -59,36 +138,59 @@ impl Elaborator { (id, typ) } - fn elaborate_statement(&mut self, statement: Statement) -> (StmtId, Type) { - let (hir_statement, typ) = match statement.kind { - StatementKind::Let(_) => todo!(), - StatementKind::Constrain(_) => todo!(), - StatementKind::Expression(_) => todo!(), - StatementKind::Assign(_) => todo!(), - StatementKind::For(_) => todo!(), + fn elaborate_statement_value(&mut self, statement: Statement) -> (HirStatement, Type) { + match statement.kind { + StatementKind::Let(let_stmt) => self.elaborate_let(let_stmt), + StatementKind::Constrain(constrain) => self.elaborate_constrain(constrain), + StatementKind::Assign(assign) => self.elaborate_assign(assign), + StatementKind::For(for_stmt) => self.elaborate_for(for_stmt), StatementKind::Break => self.elaborate_jump(true, statement.span), StatementKind::Continue => self.elaborate_jump(false, statement.span), - StatementKind::Comptime(_) => todo!(), + StatementKind::Comptime(statement) => self.elaborate_comptime(*statement), + StatementKind::Expression(expr) => { + let (expr, typ) = self.elaborate_expression(expr); + (HirStatement::Expression(expr), typ) + }, StatementKind::Semi(expr) => { let (expr, _typ) = self.elaborate_expression(expr); (HirStatement::Semi(expr), Type::Unit) } StatementKind::Error => (HirStatement::Error, Type::Error), - }; + } + } + + fn elaborate_statement(&mut self, statement: Statement) -> (StmtId, Type) { + let (hir_statement, typ) = self.elaborate_statement_value(statement); let id = self.interner.push_stmt(hir_statement); self.interner.push_stmt_location(id, statement.span, self.file); (id, typ) } - fn elaborate_block(&mut self, block: BlockExpression) -> HirBlockExpression { + fn elaborate_block(&mut self, block: BlockExpression) -> (HirExpression, Type) { self.push_scope(); + let mut block_type = Type::Unit; + let mut statements = Vec::with_capacity(block.statements.len()); - let statements = vecmap(block.statements, |statement| { - self.elaborate_statement(statement) - }); + for (i, statement) in block.statements.into_iter().enumerate() { + let (id, stmt_type) = self.elaborate_statement(statement); + + if let HirStatement::Semi(expr) = self.interner.statement(&id) { + let inner_expr_type = self.interner.id_type(expr); + let span = self.interner.expr_span(&expr); + + self.unify(&inner_expr_type, &Type::Unit, || TypeCheckError::UnusedResultError { + expr_type: inner_expr_type.clone(), + expr_span: span, + }); + + if i + 1 == statements.len() { + block_type = stmt_type; + } + } + } self.pop_scope(); - HirBlockExpression { statements } + (HirExpression::Block(HirBlockExpression { statements }), block_type) } fn push_scope(&mut self) { @@ -118,4 +220,337 @@ impl Elaborator { fn push_err(&mut self, error: impl Into) { self.errors.push(error.into()); } + + fn elaborate_literal(&mut self, literal: Literal, span: Span) -> (HirExpression, Type) { + use HirExpression::Literal as Lit; + match literal { + Literal::Unit => (Lit(HirLiteral::Unit), Type::Unit), + Literal::Bool(b) => (Lit(HirLiteral::Bool(b)), Type::Bool), + Literal::Integer(integer, sign) => { + let int = HirLiteral::Integer(integer, sign); + (Lit(int), self.polymorphic_integer_or_field()) + } + Literal::Str(str) | Literal::RawStr(str, _) => { + let len = Type::Constant(str.len() as u64); + (Lit(HirLiteral::Str(str)), Type::String(Box::new(len))) + } + Literal::FmtStr(str) => self.elaborate_fmt_string(str, span), + Literal::Array(array_literal) => self.elaborate_array_literal(array_literal, span, true), + Literal::Slice(array_literal) => self.elaborate_array_literal(array_literal, span, false), + } + } + + fn elaborate_array_literal(&mut self, array_literal: ArrayLiteral, span: Span, is_array: bool) -> (HirExpression, Type) { + let (expr, elem_type, length) = match array_literal { + ArrayLiteral::Standard(elements) => { + let mut first_elem_type = self.interner.next_type_variable(); + let first_span = elements.first().map(|elem| elem.span).unwrap_or(span); + + let elements = vecmap(elements.into_iter().enumerate(), |(i, elem)| { + let span = elem.span; + let (elem_id, elem_type) = self.elaborate_expression(elem); + + self.unify(&elem_type, &first_elem_type, || { + TypeCheckError::NonHomogeneousArray { + first_span, + first_type: first_elem_type.to_string(), + first_index: 0, + second_span: span, + second_type: elem_type.to_string(), + second_index: i, + } + .add_context("elements in an array must have the same type") + }); + elem_id + }); + + let length = Type::Constant(elements.len() as u64); + (HirArrayLiteral::Standard(elements), first_elem_type, length) + } + ArrayLiteral::Repeated { repeated_element, length } => { + let span = length.span; + let length = + UnresolvedTypeExpression::from_expr(*length, span).unwrap_or_else(|error| { + self.push_err(ResolverError::ParserError(Box::new(error))); + UnresolvedTypeExpression::Constant(0, span) + }); + + let length = self.convert_expression_type(length); + let (repeated_element, elem_type) = self.elaborate_expression(*repeated_element); + + let length_clone = length.clone(); + (HirArrayLiteral::Repeated { repeated_element, length }, elem_type, length_clone) + } + }; + let constructor = if is_array { HirLiteral::Array } else { HirLiteral::Slice }; + let elem_type = Box::new(elem_type); + let typ = if is_array { Type::Array(Box::new(length), elem_type) } else { Type::Slice(elem_type) }; + (HirExpression::Literal(constructor(expr)), typ) + } + + fn elaborate_fmt_string(&mut self, str: String, call_expr_span: Span) -> (HirExpression, Type) { + let re = Regex::new(r"\{([a-zA-Z0-9_]+)\}") + .expect("ICE: an invalid regex pattern was used for checking format strings"); + + let mut fmt_str_idents = Vec::new(); + let mut capture_types = Vec::new(); + + for field in re.find_iter(&str) { + let matched_str = field.as_str(); + let ident_name = &matched_str[1..(matched_str.len() - 1)]; + + let scope_tree = self.scopes.current_scope_tree(); + let variable = scope_tree.find(ident_name); + if let Some((old_value, _)) = variable { + old_value.num_times_used += 1; + let ident = HirExpression::Ident(old_value.ident.clone()); + let expr_id = self.interner.push_expr(ident); + self.interner.push_expr_location(expr_id, call_expr_span, self.file); + fmt_str_idents.push(expr_id); + } else if ident_name.parse::().is_ok() { + self.push_err(ResolverError::NumericConstantInFormatString { + name: ident_name.to_owned(), + span: call_expr_span, + }); + } else { + self.push_err(ResolverError::VariableNotDeclared { + name: ident_name.to_owned(), + span: call_expr_span, + }); + } + } + + let len = Type::Constant(str.len() as u64); + let typ = Type::FmtString(Box::new(len), Box::new(Type::Tuple(capture_types))); + (HirExpression::Literal(HirLiteral::FmtStr(str, fmt_str_idents)), typ) + } + + fn elaborate_prefix(&mut self, prefix: PrefixExpression) -> (HirExpression, Type) { + let span = prefix.rhs.span; + let (rhs, rhs_type) = self.elaborate_expression(prefix.rhs); + let ret_type = self.type_check_prefix_operand(&prefix.operator, &rhs_type, span); + (HirExpression::Prefix(HirPrefixExpression { operator: prefix.operator, rhs }), ret_type) + } + + fn elaborate_index(&mut self, index_expr: IndexExpression) -> (HirExpression, Type) { + let (index, index_type) = self.elaborate_expression(index_expr.index); + let span = index_expr.index.span; + + let expected = self.polymorphic_integer_or_field(); + self.unify(&index_type, &expected, || { + TypeCheckError::TypeMismatch { + expected_typ: "an integer".to_owned(), + expr_typ: index_type.to_string(), + expr_span: span, + } + }); + + // When writing `a[i]`, if `a : &mut ...` then automatically dereference `a` as many + // times as needed to get the underlying array. + let lhs_span = index_expr.collection.span; + let (lhs, lhs_type) = self.elaborate_expression(index_expr.collection); + let (collection, lhs_type) = self.insert_auto_dereferences(lhs, lhs_type); + + let typ = match lhs_type.follow_bindings() { + // XXX: We can check the array bounds here also, but it may be better to constant fold first + // and have ConstId instead of ExprId for constants + Type::Array(_, base_type) => *base_type, + Type::Slice(base_type) => *base_type, + Type::Error => Type::Error, + typ => { + self.push_err(TypeCheckError::TypeMismatch { + expected_typ: "Array".to_owned(), + expr_typ: typ.to_string(), + expr_span: lhs_span, + }); + Type::Error + } + }; + + let expr = HirExpression::Index(HirIndexExpression { collection, index }); + (expr, typ) + } + + fn elaborate_call(&mut self, call: CallExpression, span: Span) -> (HirExpression, Type) { + // Get the span and name of path for error reporting + let (func, func_type) = self.elaborate_expression(*call.func); + + let arguments = vecmap(call.arguments, |arg| self.elaborate_expression(arg)); + let location = Location::new(span, self.file); + let expr = HirExpression::Call(HirCallExpression { func, arguments, location }); + (expr, typ) + + + // Need to setup these flags here as `self` is borrowed mutably to type check the rest of the call expression + // These flags are later used to type check calls to unconstrained functions from constrained functions + let current_func = self.current_function; + let func_mod = current_func.map(|func| self.interner.function_modifiers(&func)); + let is_current_func_constrained = + func_mod.map_or(true, |func_mod| !func_mod.is_unconstrained); + let is_unconstrained_call = self.is_unconstrained_call(&call_expr.func); + + self.check_if_deprecated(&call_expr.func); + + let function = self.check_expression(&call_expr.func); + + let args = vecmap(&call_expr.arguments, |arg| { + let typ = self.check_expression(arg); + (typ, *arg, self.interner.expr_span(arg)) + }); + + // Check that we are not passing a mutable reference from a constrained runtime to an unconstrained runtime + if is_current_func_constrained && is_unconstrained_call { + for (typ, _, _) in args.iter() { + if matches!(&typ.follow_bindings(), Type::MutableReference(_)) { + self.errors.push(TypeCheckError::ConstrainedReferenceToUnconstrained { + span: self.interner.expr_span(expr_id), + }); + return Type::Error; + } + } + } + + let span = self.interner.expr_span(expr_id); + let return_type = self.bind_function_type(function, args, span); + + // Check that we are not passing a slice from an unconstrained runtime to a constrained runtime + if is_current_func_constrained && is_unconstrained_call { + if return_type.contains_slice() { + self.errors.push(TypeCheckError::UnconstrainedSliceReturnToConstrained { + span: self.interner.expr_span(expr_id), + }); + return Type::Error; + } else if matches!(&return_type.follow_bindings(), Type::MutableReference(_)) { + self.errors.push(TypeCheckError::UnconstrainedReferenceToConstrained { + span: self.interner.expr_span(expr_id), + }); + return Type::Error; + } + }; + + return_type + } + + fn elaborate_method_call(&mut self, method_call: MethodCallExpression) -> (HirExpression, Type) { + todo!() + } + + fn elaborate_constructor(&mut self, constructor: ConstructorExpression) -> (HirExpression, Type) { + todo!() + } + + fn elaborate_member_access(&mut self, member_access: MemberAccessExpression) -> (HirExpression, Type) { + todo!() + } + + fn elaborate_cast(&mut self, cast: CastExpression) -> (HirExpression, Type) { + todo!() + } + + fn elaborate_infix(&mut self, infix: InfixExpression) -> (HirExpression, Type) { + todo!() + } + + fn elaborate_if(&mut self, if_expr: IfExpression) -> (HirExpression, Type) { + let expr_span = if_expr.condition.span; + let (condition, cond_type) = self.elaborate_expression(if_expr.condition); + let (consequence, mut ret_type) = self.elaborate_expression(if_expr.consequence); + + self.unify(&cond_type, &Type::Bool, || TypeCheckError::TypeMismatch { + expected_typ: Type::Bool.to_string(), + expr_typ: cond_type.to_string(), + expr_span, + }); + + let alternative = if_expr.alternative.map(|alternative| { + let expr_span = alternative.span; + let (else_, else_type) = self.elaborate_expression(alternative); + + self.unify(&ret_type, &else_type, || { + let err = TypeCheckError::TypeMismatch { + expected_typ: ret_type.to_string(), + expr_typ: else_type.to_string(), + expr_span, + }; + + let context = if ret_type == Type::Unit { + "Are you missing a semicolon at the end of your 'else' branch?" + } else if else_type == Type::Unit { + "Are you missing a semicolon at the end of the first block of this 'if'?" + } else { + "Expected the types of both if branches to be equal" + }; + + err.add_context(context) + }); + else_ + }); + + if alternative.is_none() { + ret_type = Type::Unit; + } + + let if_expr = HirIfExpression { condition, consequence, alternative }; + (HirExpression::If(if_expr), ret_type) + } + + fn elaborate_tuple(&mut self, tuple: Vec) -> (HirExpression, Type) { + let mut element_ids = Vec::with_capacity(tuple.len()); + let mut element_types = Vec::with_capacity(tuple.len()); + + for element in tuple { + let (id, typ) = self.elaborate_expression(element); + element_ids.push(id); + element_types.push(typ); + } + + (HirExpression::Tuple(element_ids), Type::Tuple(element_types)) + } + + fn elaborate_lambda(&mut self, lambda: Lambda) -> (HirExpression, Type) { + self.push_scope(); + let scope_index = self.scopes.current_scope_index(); + + self.lambda_stack.push(LambdaContext { captures: Vec::new(), scope_index }); + + let mut arg_types = Vec::with_capacity(lambda.parameters.len()); + let parameters = vecmap(lambda.parameters, |(pattern, typ)| { + let parameter = DefinitionKind::Local(None); + let typ = self.resolve_inferred_type(typ); + arg_types.push(typ.clone()); + (self.elaborate_pattern(pattern, typ.clone(), parameter), typ) + }); + + let return_type = self.resolve_inferred_type(lambda.return_type); + let body_span = lambda.body.span; + let (body, body_type) = self.elaborate_expression(lambda.body); + + let lambda_context = self.lambda_stack.pop().unwrap(); + self.pop_scope(); + + self.unify(&body_type, &return_type, || TypeCheckError::TypeMismatch { + expected_typ: return_type.to_string(), + expr_typ: body_type.to_string(), + expr_span: body_span, + }); + + let captured_vars = vecmap(&lambda_context.captures, |capture| { + self.interner.definition_type(capture.ident.id) + }); + + let env_type = + if captured_vars.is_empty() { Type::Unit } else { Type::Tuple(captured_vars) }; + + let captures = lambda_context.captures; + let expr = HirExpression::Lambda(HirLambda { parameters, return_type, body, captures }); + (expr, Type::Function(arg_types, Box::new(body_type), Box::new(env_type))) + } + + fn elaborate_quote(&mut self, block: BlockExpression) -> (HirExpression, Type) { + (HirExpression::Quote(block), Type::Code) + } + + fn elaborate_comptime_block(&mut self, comptime: BlockExpression) -> (HirExpression, Type) { + todo!("Elaborate comptime block") + } } diff --git a/compiler/noirc_frontend/src/elaborator/patterns.rs b/compiler/noirc_frontend/src/elaborator/patterns.rs new file mode 100644 index 00000000000..449e7a3db2f --- /dev/null +++ b/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -0,0 +1,427 @@ +use iter_extended::vecmap; +use noirc_errors::{Span, Location}; +use rustc_hash::FxHashSet as HashSet; + +use crate::{macros_api::{Pattern, Ident, Path, HirExpression}, Type, node_interner::{DefinitionKind, DefinitionId, ExprId, TraitImplKind}, hir_def::{stmt::HirPattern, expr::{HirIdent, ImplKind}}, hir::{resolution::errors::ResolverError, type_check::{TypeCheckError, Source}}, ast::ERROR_IDENT, Shared, StructType, TypeBindings}; + +use super::{Elaborator, ResolverMeta}; + +impl Elaborator { + pub fn elaborate_pattern(&mut self, pattern: Pattern, expected_type: Type, definition_kind: DefinitionKind) -> HirPattern { + self.elaborate_pattern_mut(pattern, expected_type, definition_kind, None) + } + + fn elaborate_pattern_mut( + &mut self, + pattern: Pattern, + expected_type: Type, + definition: DefinitionKind, + mutable: Option, + ) -> HirPattern { + match pattern { + Pattern::Identifier(name) => { + // If this definition is mutable, do not store the rhs because it will + // not always refer to the correct value of the variable + let definition = match (mutable, definition) { + (Some(_), DefinitionKind::Local(_)) => DefinitionKind::Local(None), + (_, other) => other, + }; + let ident = self.add_variable_decl(name, mutable.is_some(), true, definition); + self.interner.push_definition_type(ident.id, expected_type); + HirPattern::Identifier(ident) + } + Pattern::Mutable(pattern, span, _) => { + if let Some(first_mut) = mutable { + self.push_err(ResolverError::UnnecessaryMut { first_mut, second_mut: span }); + } + + let pattern = self.elaborate_pattern_mut(*pattern, expected_type, definition, Some(span)); + let location = Location::new(span, self.file); + HirPattern::Mutable(Box::new(pattern), location) + } + Pattern::Tuple(fields, span) => { + let field_types = match expected_type { + Type::Tuple(fields) => fields, + Type::Error => Vec::new(), + expected_type => { + let tuple = + Type::Tuple(vecmap(fields, |_| self.interner.next_type_variable())); + + self.push_err(TypeCheckError::TypeMismatchWithSource { + expected: expected_type, + actual: tuple, + span, + source: Source::Assignment, + }); + Vec::new() + } + }; + + let fields = vecmap(fields.into_iter().enumerate(), |(i, field)| { + let field_type = field_types.get(i).cloned().unwrap_or(Type::Error); + self.elaborate_pattern_mut(field, field_type, definition.clone(), mutable) + }); + let location = Location::new(span, self.file); + HirPattern::Tuple(fields, location) + } + Pattern::Struct(name, fields, span) => { + self.elaborate_struct_pattern(name, fields, span, pattern, expected_type, definition, mutable) + } + } + } + + fn elaborate_struct_pattern( + &mut self, + name: Path, + fields: Vec<(Ident, Pattern)>, + span: Span, + pattern: Pattern, + expected_type: Type, + definition: DefinitionKind, + mutable: Option, + ) -> HirPattern { + let error_identifier = |this: &mut Self| { + // Must create a name here to return a HirPattern::Identifier. Allowing + // shadowing here lets us avoid further errors if we define ERROR_IDENT + // multiple times. + let name = ERROR_IDENT.into(); + let identifier = this.add_variable_decl(name, false, true, definition.clone()); + HirPattern::Identifier(identifier) + }; + + let (struct_type, generics) = match self.lookup_type_or_error(name) { + Some(Type::Struct(struct_type, generics)) => (struct_type, generics), + None => return error_identifier(self), + Some(typ) => { + self.push_err(ResolverError::NonStructUsedInConstructor { typ, span }); + return error_identifier(self); + } + }; + + let actual_type = Type::Struct(struct_type, generics); + let location = Location::new(span, self.file); + + self.unify(&actual_type, &expected_type, || TypeCheckError::TypeMismatchWithSource { + expected: expected_type.clone(), + actual: actual_type, + span: location.span, + source: Source::Assignment, + }); + + let typ = struct_type.clone(); + let fields = self.resolve_constructor_pattern_fields(typ, fields, span, expected_type, definition, mutable); + + HirPattern::Struct(expected_type, fields, location) + } + + /// Resolve all the fields of a struct constructor expression. + /// Ensures all fields are present, none are repeated, and all + /// are part of the struct. + fn resolve_constructor_pattern_fields( + &mut self, + struct_type: Shared, + fields: Vec<(Ident, Pattern)>, + span: Span, + expected_type: Type, + definition: DefinitionKind, + mutable: Option, + ) -> Vec<(Ident, HirPattern)> { + let mut ret = Vec::with_capacity(fields.len()); + let mut seen_fields = HashSet::default(); + let mut unseen_fields = struct_type.borrow().field_names(); + + for (field, pattern) in fields { + let field_type = expected_type.get_field_type(&field.0.contents).unwrap_or(Type::Error); + let resolved = self.elaborate_pattern_mut(pattern, field_type, definition, mutable); + + if unseen_fields.contains(&field) { + unseen_fields.remove(&field); + seen_fields.insert(field.clone()); + } else if seen_fields.contains(&field) { + // duplicate field + self.push_err(ResolverError::DuplicateField { field: field.clone() }); + } else { + // field not required by struct + self.push_err(ResolverError::NoSuchField { + field: field.clone(), + struct_definition: struct_type.borrow().name.clone(), + }); + } + + ret.push((field, resolved)); + } + + if !unseen_fields.is_empty() { + self.push_err(ResolverError::MissingFields { + span, + missing_fields: unseen_fields.into_iter().map(|field| field.to_string()).collect(), + struct_definition: struct_type.borrow().name.clone(), + }); + } + + ret + } + + pub fn add_variable_decl( + &mut self, + name: Ident, + mutable: bool, + allow_shadowing: bool, + definition: DefinitionKind, + ) -> HirIdent { + self.add_variable_decl_inner(name, mutable, allow_shadowing, true, definition) + } + + fn add_variable_decl_inner( + &mut self, + name: Ident, + mutable: bool, + allow_shadowing: bool, + warn_if_unused: bool, + definition: DefinitionKind, + ) -> HirIdent { + if definition.is_global() { + return self.add_global_variable_decl(name, definition); + } + + let location = Location::new(name.span(), self.file); + let id = + self.interner.push_definition(name.0.contents.clone(), mutable, definition, location); + let ident = HirIdent::non_trait_method(id, location); + let resolver_meta = + ResolverMeta { num_times_used: 0, ident: ident.clone(), warn_if_unused }; + + let scope = self.scopes.get_mut_scope(); + let old_value = scope.add_key_value(name.0.contents.clone(), resolver_meta); + + if !allow_shadowing { + if let Some(old_value) = old_value { + self.push_err(ResolverError::DuplicateDefinition { + name: name.0.contents, + first_span: old_value.ident.location.span, + second_span: location.span, + }); + } + } + + ident + } + + fn add_global_variable_decl(&mut self, name: Ident, definition: DefinitionKind) -> HirIdent { + let scope = self.scopes.get_mut_scope(); + + // This check is necessary to maintain the same definition ids in the interner. Currently, each function uses a new resolver that has its own ScopeForest and thus global scope. + // We must first check whether an existing definition ID has been inserted as otherwise there will be multiple definitions for the same global statement. + // This leads to an error in evaluation where the wrong definition ID is selected when evaluating a statement using the global. The check below prevents this error. + let mut global_id = None; + let global = self.interner.get_all_globals(); + for global_info in global { + if global_info.ident == name + && global_info.local_id == self.path_resolver.local_module_id() + { + global_id = Some(global_info.id); + } + } + + let (ident, resolver_meta) = if let Some(id) = global_id { + let global = self.interner.get_global(id); + let hir_ident = HirIdent::non_trait_method(global.definition_id, global.location); + let ident = hir_ident.clone(); + let resolver_meta = ResolverMeta { num_times_used: 0, ident, warn_if_unused: true }; + (hir_ident, resolver_meta) + } else { + let location = Location::new(name.span(), self.file); + let id = + self.interner.push_definition(name.0.contents.clone(), false, definition, location); + let ident = HirIdent::non_trait_method(id, location); + let resolver_meta = + ResolverMeta { num_times_used: 0, ident: ident.clone(), warn_if_unused: true }; + (ident, resolver_meta) + }; + + let old_global_value = scope.add_key_value(name.0.contents.clone(), resolver_meta); + if let Some(old_global_value) = old_global_value { + self.push_err(ResolverError::DuplicateDefinition { + name: name.0.contents.clone(), + first_span: old_global_value.ident.location.span, + second_span: name.span(), + }); + } + ident + } + + // Checks for a variable having been declared before + // variable declaration and definition cannot be separate in Noir + // Once the variable has been found, intern and link `name` to this definition + // return the IdentId of `name` + // + // If a variable is not found, then an error is logged and a dummy id + // is returned, for better error reporting UX + pub fn find_variable_or_default(&mut self, name: &Ident) -> (HirIdent, usize) { + self.find_variable(name).unwrap_or_else(|error| { + self.push_err(error); + let id = DefinitionId::dummy_id(); + let location = Location::new(name.span(), self.file); + (HirIdent::non_trait_method(id, location), 0) + }) + } + + pub fn find_variable(&mut self, name: &Ident) -> Result<(HirIdent, usize), ResolverError> { + // Find the definition for this Ident + let scope_tree = self.scopes.current_scope_tree(); + let variable = scope_tree.find(&name.0.contents); + + let location = Location::new(name.span(), self.file); + if let Some((variable_found, scope)) = variable { + variable_found.num_times_used += 1; + let id = variable_found.ident.id; + Ok((HirIdent::non_trait_method(id, location), scope)) + } else { + Err(ResolverError::VariableNotDeclared { + name: name.0.contents.clone(), + span: name.0.span(), + }) + } + } + + pub fn elaborate_variable(&mut self, variable: Path) -> (ExprId, Type) { + let span = variable.span; + let expr = self.resolve_variable(variable); + let id = self.interner.push_expr(HirExpression::Ident(expr.clone())); + self.interner.push_expr_location(id, span, self.file); + let typ = self.type_check_variable(expr, id); + self.interner.push_expr_type(id, typ.clone()); + (id, typ) + } + + fn resolve_variable(&mut self, path: Path) -> HirIdent { + if let Some((method, constraint, assumed)) = self.resolve_trait_generic_path(&path) + { + HirIdent { + location: Location::new(path.span, self.file), + id: self.interner.trait_method_id(method), + impl_kind: ImplKind::TraitMethod(method, constraint, assumed), + } + } else { + // If the Path is being used as an Expression, then it is referring to a global from a separate module + // Otherwise, then it is referring to an Identifier + // This lookup allows support of such statements: let x = foo::bar::SOME_GLOBAL + 10; + // If the expression is a singular indent, we search the resolver's current scope as normal. + let (hir_ident, var_scope_index) = self.get_ident_from_path(path); + + if hir_ident.id != DefinitionId::dummy_id() { + match self.interner.definition(hir_ident.id).kind { + DefinitionKind::Function(id) => { + if let Some(current_item) = self.current_item { + self.interner.add_function_dependency(current_item, id); + } + } + DefinitionKind::Global(global_id) => { + if let Some(current_item) = self.current_item { + self.interner.add_global_dependency(current_item, global_id); + } + } + DefinitionKind::GenericType(_) => { + // Initialize numeric generics to a polymorphic integer type in case + // they're used in expressions. We must do this here since type_check_variable + // does not check definition kinds and otherwise expects parameters to + // already be typed. + if self.interner.definition_type(hir_ident.id) == Type::Error { + let typ = Type::polymorphic_integer_or_field(&mut self.interner); + self.interner.push_definition_type(hir_ident.id, typ); + } + } + DefinitionKind::Local(_) => { + // only local variables can be captured by closures. + self.resolve_local_variable(hir_ident.clone(), var_scope_index); + } + } + } + + hir_ident + } + } + + fn type_check_variable(&mut self, ident: HirIdent, expr_id: ExprId) -> Type { + let mut bindings = TypeBindings::new(); + + // Add type bindings from any constraints that were used. + // We need to do this first since otherwise instantiating the type below + // will replace each trait generic with a fresh type variable, rather than + // the type used in the trait constraint (if it exists). See #4088. + if let ImplKind::TraitMethod(_, constraint, _) = &ident.impl_kind { + let the_trait = self.interner.get_trait(constraint.trait_id); + assert_eq!(the_trait.generics.len(), constraint.trait_generics.len()); + + for (param, arg) in the_trait.generics.iter().zip(&constraint.trait_generics) { + // Avoid binding t = t + if !arg.occurs(param.id()) { + bindings.insert(param.id(), (param.clone(), arg.clone())); + } + } + } + + // An identifiers type may be forall-quantified in the case of generic functions. + // E.g. `fn foo(t: T, field: Field) -> T` has type `forall T. fn(T, Field) -> T`. + // We must instantiate identifiers at every call site to replace this T with a new type + // variable to handle generic functions. + let t = self.interner.id_type_substitute_trait_as_type(ident.id); + + // This instantiates a trait's generics as well which need to be set + // when the constraint below is later solved for when the function is + // finished. How to link the two? + let (typ, bindings) = t.instantiate_with_bindings(bindings, &mut self.interner); + + // Push any trait constraints required by this definition to the context + // to be checked later when the type of this variable is further constrained. + if let Some(definition) = self.interner.try_definition(ident.id) { + if let DefinitionKind::Function(function) = definition.kind { + let function = self.interner.function_meta(&function); + + for mut constraint in function.trait_constraints.clone() { + constraint.apply_bindings(&bindings); + self.trait_constraints.push((constraint, expr_id)); + } + } + } + + if let ImplKind::TraitMethod(_, mut constraint, assumed) = ident.impl_kind { + constraint.apply_bindings(&bindings); + if assumed { + let trait_impl = TraitImplKind::Assumed { + object_type: constraint.typ, + trait_generics: constraint.trait_generics, + }; + self.interner.select_impl_for_expression(expr_id, trait_impl); + } else { + // Currently only one impl can be selected per expr_id, so this + // constraint needs to be pushed after any other constraints so + // that monomorphization can resolve this trait method to the correct impl. + self.trait_constraints.push((constraint, expr_id)); + } + } + + self.interner.store_instantiation_bindings(expr_id, bindings); + typ + } + + fn get_ident_from_path(&mut self, path: Path) -> (HirIdent, usize) { + let location = Location::new(path.span(), self.file); + + let error = match path.as_ident().map(|ident| self.find_variable(ident)) { + Some(Ok(found)) => return found, + // Try to look it up as a global, but still issue the first error if we fail + Some(Err(error)) => match self.lookup_global(path) { + Ok(id) => return (HirIdent::non_trait_method(id, location), 0), + Err(_) => error, + }, + None => match self.lookup_global(path) { + Ok(id) => return (HirIdent::non_trait_method(id, location), 0), + Err(error) => error, + }, + }; + self.push_err(error); + let id = DefinitionId::dummy_id(); + (HirIdent::non_trait_method(id, location), 0) + } +} diff --git a/compiler/noirc_frontend/src/elaborator/scope.rs b/compiler/noirc_frontend/src/elaborator/scope.rs index a3a7023a5c1..f8abc779ae7 100644 --- a/compiler/noirc_frontend/src/elaborator/scope.rs +++ b/compiler/noirc_frontend/src/elaborator/scope.rs @@ -1,8 +1,10 @@ use rustc_hash::FxHashMap as HashMap; -use crate::{macros_api::StructId, node_interner::{TypeAliasId, DefinitionId}}; +use crate::{macros_api::{StructId, Path}, node_interner::{TypeAliasId, DefinitionId, TraitId}, hir::{def_map::{TryFromModuleDefId, ModuleDefId}, resolution::errors::ResolverError}, Shared, StructType, hir_def::{traits::Trait, expr::{HirIdent, HirCapturedVar}}}; use crate::hir::comptime::Value; +use super::Elaborator; + #[derive(Default)] pub(super) struct Scope { types: HashMap, @@ -14,3 +16,85 @@ pub(super) enum TypeId { Struct(StructId), Alias(TypeAliasId), } + +impl Elaborator { + pub fn lookup(&mut self, path: Path) -> Result { + let span = path.span(); + let id = self.resolve_path(path)?; + T::try_from(id).ok_or_else(|| ResolverError::Expected { + expected: T::description(), + got: id.as_str().to_owned(), + span, + }) + } + + pub fn resolve_path(&mut self, path: Path) -> Result { + let path_resolution = self.path_resolver.resolve(&self.def_maps, path)?; + + if let Some(error) = path_resolution.error { + self.push_err(error); + } + + Ok(path_resolution.module_def_id) + } + + pub fn get_struct(&self, type_id: StructId) -> Shared { + self.interner.get_struct(type_id) + } + + pub fn get_trait_mut(&mut self, trait_id: TraitId) -> &mut Trait { + self.interner.get_trait_mut(trait_id) + } + + pub fn resolve_local_variable(&mut self, hir_ident: HirIdent, var_scope_index: usize) { + let mut transitive_capture_index: Option = None; + + for lambda_index in 0..self.lambda_stack.len() { + if self.lambda_stack[lambda_index].scope_index > var_scope_index { + // Beware: the same variable may be captured multiple times, so we check + // for its presence before adding the capture below. + let pos = self.lambda_stack[lambda_index] + .captures + .iter() + .position(|capture| capture.ident.id == hir_ident.id); + + if pos.is_none() { + self.lambda_stack[lambda_index].captures.push(HirCapturedVar { + ident: hir_ident.clone(), + transitive_capture_index, + }); + } + + if lambda_index + 1 < self.lambda_stack.len() { + // There is more than one closure between the current scope and + // the scope of the variable, so this is a propagated capture. + // We need to track the transitive capture index as we go up in + // the closure stack. + transitive_capture_index = Some(pos.unwrap_or( + // If this was a fresh capture, we added it to the end of + // the captures vector: + self.lambda_stack[lambda_index].captures.len() - 1, + )); + } + } + } + } + + pub fn lookup_global(&mut self, path: Path) -> Result { + let span = path.span(); + let id = self.resolve_path(path)?; + + if let Some(function) = TryFromModuleDefId::try_from(id) { + return Ok(self.interner.function_definition_id(function)); + } + + if let Some(global) = TryFromModuleDefId::try_from(id) { + let global = self.interner.get_global(global); + return Ok(global.definition_id); + } + + let expected = "global variable".into(); + let got = "local variable".into(); + Err(ResolverError::Expected { span, expected, got }) + } +} diff --git a/compiler/noirc_frontend/src/elaborator/statements.rs b/compiler/noirc_frontend/src/elaborator/statements.rs new file mode 100644 index 00000000000..a7e8a9f4bd9 --- /dev/null +++ b/compiler/noirc_frontend/src/elaborator/statements.rs @@ -0,0 +1,357 @@ +use noirc_errors::{Span, Location}; + +use crate::{macros_api::{LetStatement, HirStatement, ForLoopStatement, ForRange, Statement}, Type, node_interner::{DefinitionKind, DefinitionId}, hir::type_check::{TypeCheckError, Source}, hir_def::{stmt::{HirLetStatement, HirConstrainStatement, HirAssignStatement, HirForStatement, HirLValue}, expr::HirIdent}, ast::{ConstrainStatement, AssignStatement, LValue}}; + +use super::Elaborator; + +impl Elaborator { + pub fn elaborate_let(&self, let_stmt: LetStatement) -> (HirStatement, Type) { + let expr_span = let_stmt.expression.span; + let (expression, expr_type) = self.elaborate_expression(let_stmt.expression); + let definition = DefinitionKind::Local(Some(expression)); + let annotated_type = self.resolve_type(let_stmt.r#type); + + // First check if the LHS is unspecified + // If so, then we give it the same type as the expression + let r#type = if annotated_type != Type::Error { + // Now check if LHS is the same type as the RHS + // Importantly, we do not coerce any types implicitly + self.unify_with_coercions(&expr_type, &annotated_type, expression, || { + TypeCheckError::TypeMismatch { + expected_typ: annotated_type.to_string(), + expr_typ: expr_type.to_string(), + expr_span, + } + }); + if annotated_type.is_unsigned() { + self.lint_overflowing_uint(&expression, &annotated_type); + } + annotated_type + } else { + expr_type + }; + + let let_ = HirLetStatement { + pattern: self.elaborate_pattern(let_stmt.pattern, r#type, definition), + r#type, + expression, + attributes: let_stmt.attributes, + comptime: let_stmt.comptime, + }; + (HirStatement::Let(let_), Type::Unit) + } + + pub fn elaborate_constrain(&mut self, stmt: ConstrainStatement) -> (HirStatement, Type) { + let expr_span = stmt.0.span; + let (expr_id, expr_type) = self.elaborate_expression(stmt.0); + + // Must type check the assertion message expression so that we instantiate bindings + let msg = stmt.1.map(|assert_msg_expr| self.elaborate_expression(assert_msg_expr).0); + + self.unify(&expr_type, &Type::Bool, || TypeCheckError::TypeMismatch { + expr_typ: expr_type.to_string(), + expected_typ: Type::Bool.to_string(), + expr_span, + }); + + (HirStatement::Constrain(HirConstrainStatement(expr_id, self.file, msg)), Type::Unit) + } + + pub fn elaborate_assign(&mut self, assign: AssignStatement) -> (HirStatement, Type) { + let span = assign.expression.span; + let (expression, expr_type) = self.elaborate_expression(assign.expression); + let (lvalue, lvalue_type, mutable) = self.elaborate_lvalue(assign.lvalue, span); + + if !mutable { + let (name, span) = self.get_lvalue_name_and_span(&lvalue); + self.push_err(TypeCheckError::VariableMustBeMutable { name, span }); + } + + self.unify_with_coercions(&expr_type, &lvalue_type, expression, || { + TypeCheckError::TypeMismatchWithSource { + actual: expr_type.clone(), + expected: lvalue_type.clone(), + span, + source: Source::Assignment, + } + }); + + let stmt = HirAssignStatement { lvalue, expression }; + (HirStatement::Assign(stmt), Type::Unit) + } + + pub fn elaborate_for(&mut self, for_loop: ForLoopStatement) -> (HirStatement, Type) { + let (start, end) = match for_loop.range { + ForRange::Range(start, end) => (start, end), + ForRange::Array(_) => { + let for_stmt = + for_loop.range.into_for(for_loop.identifier, for_loop.block, for_loop.span); + + return self.elaborate_statement_value(for_stmt); + } + }; + + let start_span = start.span; + let end_span = end.span; + + let (start_range, start_range_type) = self.elaborate_expression(start); + let (end_range, end_range_type) = self.elaborate_expression(end); + let (identifier, block) = (for_loop.identifier, for_loop.block); + + self.nested_loops += 1; + self.push_scope(); + + // TODO: For loop variables are currently mutable by default since we haven't + // yet implemented syntax for them to be optionally mutable. + let kind = DefinitionKind::Local(None); + let identifier = self.add_variable_decl(identifier, false, true, kind); + + // Check that start range and end range have the same types + let range_span = start_span.merge(end_span); + self.unify(&start_range_type, &end_range_type, || TypeCheckError::TypeMismatch { + expected_typ: start_range_type.to_string(), + expr_typ: end_range_type.to_string(), + expr_span: range_span, + }); + + let expected_type = self.polymorphic_integer(); + + self.unify(&start_range_type, &expected_type, || TypeCheckError::TypeCannotBeUsed { + typ: start_range_type.clone(), + place: "for loop", + span: range_span, + }); + + self.interner.push_definition_type(identifier.id, start_range_type); + + let (block, _block_type) = self.elaborate_expression(block); + + self.pop_scope(); + self.nested_loops -= 1; + + let statement = HirStatement::For(HirForStatement { + start_range, + end_range, + block, + identifier, + }); + + (statement, Type::Unit) + } + + fn get_lvalue_name_and_span(&self, lvalue: &HirLValue) -> (String, Span) { + match lvalue { + HirLValue::Ident(name, _) => { + let span = name.location.span; + + if let Some(definition) = self.interner.try_definition(name.id) { + (definition.name.clone(), span) + } else { + ("(undeclared variable)".into(), span) + } + } + HirLValue::MemberAccess { object, .. } => self.get_lvalue_name_and_span(object), + HirLValue::Index { array, .. } => self.get_lvalue_name_and_span(array), + HirLValue::Dereference { lvalue, .. } => self.get_lvalue_name_and_span(lvalue), + } + } + + fn elaborate_lvalue(&mut self, lvalue: LValue, assign_span: Span) -> (HirLValue, Type, bool) { + match lvalue { + LValue::Ident(ident) => { + let mut mutable = true; + let (ident, scope_index) = self.find_variable_or_default(&ident); + self.resolve_local_variable(ident.clone(), scope_index); + + let typ = if ident.id == DefinitionId::dummy_id() { + Type::Error + } else { + if let Some(definition) = self.interner.try_definition(ident.id) { + mutable = definition.mutable; + } + + let typ = self.interner.definition_type(ident.id).instantiate(&mut self.interner).0; + typ.follow_bindings() + }; + + (HirLValue::Ident(ident.clone(), typ.clone()), typ, mutable) + } + LValue::MemberAccess { object, field_name, span } => { + let (object, lhs_type, mut mutable) = self.elaborate_lvalue(*object, assign_span); + let mut object = Box::new(object); + let field_name = field_name.clone(); + + let object_ref = &mut object; + let mutable_ref = &mut mutable; + let location = Location::new(span, self.file); + + let dereference_lhs = move |_: &mut Self, _, element_type| { + // We must create a temporary value first to move out of object_ref before + // we eventually reassign to it. + let id = DefinitionId::dummy_id(); + let ident = HirIdent::non_trait_method(id, location); + let tmp_value = HirLValue::Ident(ident, Type::Error); + + let lvalue = std::mem::replace(object_ref, Box::new(tmp_value)); + *object_ref = + Box::new(HirLValue::Dereference { lvalue, element_type, location }); + *mutable_ref = true; + }; + + let name = &field_name.0.contents; + let (object_type, field_index) = self + .check_field_access(&lhs_type, name, field_name.span(), Some(dereference_lhs)) + .unwrap_or((Type::Error, 0)); + + let field_index = Some(field_index); + let typ = object_type.clone(); + let lvalue = + HirLValue::MemberAccess { object, field_name, field_index, typ, location }; + (lvalue, object_type, mutable) + } + LValue::Index { array, index, span } => { + let expr_span = index.span; + let (index, index_type) = self.elaborate_expression(index); + let location = Location::new(span, self.file); + + let expected = self.polymorphic_integer_or_field(); + self.unify(&index_type, &expected, || { + TypeCheckError::TypeMismatch { + expected_typ: "an integer".to_owned(), + expr_typ: index_type.to_string(), + expr_span, + } + }); + + let (mut lvalue, mut lvalue_type, mut mutable) = + self.elaborate_lvalue(*array, assign_span); + + // Before we check that the lvalue is an array, try to dereference it as many times + // as needed to unwrap any &mut wrappers. + while let Type::MutableReference(element) = lvalue_type.follow_bindings() { + let element_type = element.as_ref().clone(); + lvalue = + HirLValue::Dereference { lvalue: Box::new(lvalue), element_type, location }; + lvalue_type = *element; + // We know this value to be mutable now since we found an `&mut` + mutable = true; + } + + let typ = match lvalue_type.follow_bindings() { + Type::Array(_, elem_type) => *elem_type, + Type::Slice(elem_type) => *elem_type, + Type::Error => Type::Error, + Type::String(_) => { + let (_lvalue_name, lvalue_span) = self.get_lvalue_name_and_span(&lvalue); + self.push_err(TypeCheckError::StringIndexAssign { span: lvalue_span }); + Type::Error + } + other => { + // TODO: Need a better span here + self.push_err(TypeCheckError::TypeMismatch { + expected_typ: "array".to_string(), + expr_typ: other.to_string(), + expr_span: assign_span, + }); + Type::Error + } + }; + + let array = Box::new(lvalue); + let array_type = typ.clone(); + (HirLValue::Index { array, index, typ, location }, array_type, mutable) + } + LValue::Dereference(lvalue, span) => { + let (lvalue, reference_type, _) = self.elaborate_lvalue(*lvalue, assign_span); + let lvalue = Box::new(lvalue); + let location = Location::new(span, self.file); + + let element_type = Type::type_variable(self.interner.next_type_variable_id()); + let expected_type = Type::MutableReference(Box::new(element_type.clone())); + + self.unify(&reference_type, &expected_type, || TypeCheckError::TypeMismatch { + expected_typ: expected_type.to_string(), + expr_typ: reference_type.to_string(), + expr_span: assign_span, + }); + + // Dereferences are always mutable since we already type checked against a &mut T + let typ = element_type.clone(); + let lvalue = HirLValue::Dereference { lvalue, element_type, location }; + (lvalue, typ, true) + } + } + } + + /// Type checks a field access, adding dereference operators as necessary + pub fn check_field_access( + &mut self, + lhs_type: &Type, + field_name: &str, + span: Span, + dereference_lhs: Option, + ) -> Option<(Type, usize)> { + let lhs_type = lhs_type.follow_bindings(); + + match &lhs_type { + Type::Struct(s, args) => { + let s = s.borrow(); + if let Some((field, index)) = s.get_field(field_name, args) { + return Some((field, index)); + } + } + Type::Tuple(elements) => { + if let Ok(index) = field_name.parse::() { + let length = elements.len(); + if index < length { + return Some((elements[index].clone(), index)); + } else { + self.push_err(TypeCheckError::TupleIndexOutOfBounds { + index, + lhs_type, + length, + span, + }); + return None; + } + } + } + // If the lhs is a mutable reference we automatically transform + // lhs.field into (*lhs).field + Type::MutableReference(element) => { + if let Some(mut dereference_lhs) = dereference_lhs { + dereference_lhs(self, lhs_type.clone(), element.as_ref().clone()); + return self.check_field_access( + element, + field_name, + span, + Some(dereference_lhs), + ); + } else { + let (element, index) = + self.check_field_access(element, field_name, span, dereference_lhs)?; + return Some((Type::MutableReference(Box::new(element)), index)); + } + } + _ => (), + } + + // If we get here the type has no field named 'access.rhs'. + // Now we specialize the error message based on whether we know the object type in question yet. + if let Type::TypeVariable(..) = &lhs_type { + self.push_err(TypeCheckError::TypeAnnotationsNeeded { span }); + } else if lhs_type != Type::Error { + self.push_err(TypeCheckError::AccessUnknownMember { + lhs_type, + field_name: field_name.to_string(), + span, + }); + } + + None + } + + pub fn elaborate_comptime(&self, statement: Statement) -> (HirStatement, Type) { + todo!("Comptime scanning") + } +} diff --git a/compiler/noirc_frontend/src/elaborator/types.rs b/compiler/noirc_frontend/src/elaborator/types.rs new file mode 100644 index 00000000000..51afad3210b --- /dev/null +++ b/compiler/noirc_frontend/src/elaborator/types.rs @@ -0,0 +1,743 @@ +use std::rc::Rc; + +use iter_extended::vecmap; +use noirc_errors::{Location, Span}; + +use crate::{macros_api::{UnresolvedType, Path, SecondaryAttribute, PathKind, UnresolvedTypeData, HirExpression, HirLiteral, UnaryOp}, Type, hir::{resolution::{errors::ResolverError, resolver::SELF_TYPE_NAME, import::PathResolution}, def_map::ModuleDefId, type_check::TypeCheckError}, Generics, TypeVariable, ast::{UnresolvedTypeExpression, UnresolvedTraitConstraint, BinaryOpKind}, Shared, TypeAlias, StructType, hir_def::{traits::{Trait, TraitConstraint}, expr::HirPrefixExpression}, node_interner::{TraitMethodId, GlobalId, ExprId, DefinitionKind}, TypeVariableKind}; + +use super::Elaborator; + + + +impl Elaborator { + /// Translates an UnresolvedType to a Type + pub fn resolve_type(&mut self, typ: UnresolvedType) -> Type { + let span = typ.span; + let resolved_type = self.resolve_type_inner(typ, &mut vec![]); + if resolved_type.is_nested_slice() { + self.push_err(ResolverError::NestedSlices { span: span.unwrap() }); + } + + resolved_type + } + + /// Translates an UnresolvedType into a Type and appends any + /// freshly created TypeVariables created to new_variables. + fn resolve_type_inner(&mut self, typ: UnresolvedType, new_variables: &mut Generics) -> Type { + use crate::ast::UnresolvedTypeData::*; + + let resolved_type = match typ.typ { + FieldElement => Type::FieldElement, + Array(size, elem) => { + let elem = Box::new(self.resolve_type_inner(*elem, new_variables)); + let size = self.resolve_array_size(Some(size), new_variables); + Type::Array(Box::new(size), elem) + } + Slice(elem) => { + let elem = Box::new(self.resolve_type_inner(*elem, new_variables)); + Type::Slice(elem) + } + Expression(expr) => self.convert_expression_type(expr), + Integer(sign, bits) => Type::Integer(sign, bits), + Bool => Type::Bool, + String(size) => { + let resolved_size = self.resolve_array_size(size, new_variables); + Type::String(Box::new(resolved_size)) + } + FormatString(size, fields) => { + let resolved_size = self.convert_expression_type(size); + let fields = self.resolve_type_inner(*fields, new_variables); + Type::FmtString(Box::new(resolved_size), Box::new(fields)) + } + Code => Type::Code, + Unit => Type::Unit, + Unspecified => Type::Error, + Error => Type::Error, + Named(path, args, _) => self.resolve_named_type(path, args, new_variables), + TraitAsType(path, args) => self.resolve_trait_as_type(path, args, new_variables), + + Tuple(fields) => { + Type::Tuple(vecmap(fields, |field| self.resolve_type_inner(field, new_variables))) + } + Function(args, ret, env) => { + let args = vecmap(args, |arg| self.resolve_type_inner(arg, new_variables)); + let ret = Box::new(self.resolve_type_inner(*ret, new_variables)); + + // expect() here is valid, because the only places we don't have a span are omitted types + // e.g. a function without return type implicitly has a spanless UnresolvedType::Unit return type + // To get an invalid env type, the user must explicitly specify the type, which will have a span + let env_span = + env.span.expect("Unexpected missing span for closure environment type"); + + let env = Box::new(self.resolve_type_inner(*env, new_variables)); + + match *env { + Type::Unit | Type::Tuple(_) | Type::NamedGeneric(_, _) => { + Type::Function(args, ret, env) + } + _ => { + self.push_err(ResolverError::InvalidClosureEnvironment { + typ: *env, + span: env_span, + }); + Type::Error + } + } + } + MutableReference(element) => { + Type::MutableReference(Box::new(self.resolve_type_inner(*element, new_variables))) + } + Parenthesized(typ) => self.resolve_type_inner(*typ, new_variables), + }; + + if let Type::Struct(_, _) = resolved_type { + if let Some(unresolved_span) = typ.span { + // Record the location of the type reference + self.interner.push_type_ref_location( + resolved_type.clone(), + Location::new(unresolved_span, self.file), + ); + } + } + resolved_type + } + + fn find_generic(&self, target_name: &str) -> Option<&(Rc, TypeVariable, Span)> { + self.generics.iter().find(|(name, _, _)| name.as_ref() == target_name) + } + + fn resolve_named_type( + &mut self, + path: Path, + args: Vec, + new_variables: &mut Generics, + ) -> Type { + if args.is_empty() { + if let Some(typ) = self.lookup_generic_or_global_type(&path) { + return typ; + } + } + + // Check if the path is a type variable first. We currently disallow generics on type + // variables since we do not support higher-kinded types. + if path.segments.len() == 1 { + let name = &path.last_segment().0.contents; + + if name == SELF_TYPE_NAME { + if let Some(self_type) = self.self_type.clone() { + if !args.is_empty() { + self.push_err(ResolverError::GenericsOnSelfType { span: path.span() }); + } + return self_type; + } + } + } + + let span = path.span(); + let mut args = vecmap(args, |arg| self.resolve_type_inner(arg, new_variables)); + + if let Some(type_alias) = self.lookup_type_alias(path.clone()) { + let type_alias = type_alias.borrow(); + let expected_generic_count = type_alias.generics.len(); + let type_alias_string = type_alias.to_string(); + let id = type_alias.id; + + self.verify_generics_count(expected_generic_count, &mut args, span, || { + type_alias_string + }); + + if let Some(item) = self.current_item { + self.interner.add_type_alias_dependency(item, id); + } + + // Collecting Type Alias references [Location]s to be used by LSP in order + // to resolve the definition of the type alias + self.interner.add_type_alias_ref(id, Location::new(span, self.file)); + + // Because there is no ordering to when type aliases (and other globals) are resolved, + // it is possible for one to refer to an Error type and issue no error if it is set + // equal to another type alias. Fixing this fully requires an analysis to create a DFG + // of definition ordering, but for now we have an explicit check here so that we at + // least issue an error that the type was not found instead of silently passing. + let alias = self.interner.get_type_alias(id); + return Type::Alias(alias, args); + } + + match self.lookup_struct_or_error(path) { + Some(struct_type) => { + if self.resolving_ids.contains(&struct_type.borrow().id) { + self.push_err(ResolverError::SelfReferentialStruct { + span: struct_type.borrow().name.span(), + }); + + return Type::Error; + } + + let expected_generic_count = struct_type.borrow().generics.len(); + if !self.in_contract + && self + .interner + .struct_attributes(&struct_type.borrow().id) + .iter() + .any(|attr| matches!(attr, SecondaryAttribute::Abi(_))) + { + self.push_err(ResolverError::AbiAttributeOutsideContract { + span: struct_type.borrow().name.span(), + }); + } + self.verify_generics_count(expected_generic_count, &mut args, span, || { + struct_type.borrow().to_string() + }); + + if let Some(current_item) = self.current_item { + let dependency_id = struct_type.borrow().id; + self.interner.add_type_dependency(current_item, dependency_id); + } + + Type::Struct(struct_type, args) + } + None => Type::Error, + } + } + + fn resolve_trait_as_type( + &mut self, + path: Path, + args: Vec, + new_variables: &mut Generics, + ) -> Type { + let args = vecmap(args, |arg| self.resolve_type_inner(arg, new_variables)); + + if let Some(t) = self.lookup_trait_or_error(path) { + Type::TraitAsType(t.id, Rc::new(t.name.to_string()), args) + } else { + Type::Error + } + } + + fn verify_generics_count( + &mut self, + expected_count: usize, + args: &mut Vec, + span: Span, + type_name: impl FnOnce() -> String, + ) { + if args.len() != expected_count { + self.push_err(ResolverError::IncorrectGenericCount { + span, + item_name: type_name(), + actual: args.len(), + expected: expected_count, + }); + + // Fix the generic count so we can continue typechecking + args.resize_with(expected_count, || Type::Error); + } + } + + fn lookup_generic_or_global_type(&mut self, path: &Path) -> Option { + if path.segments.len() == 1 { + let name = &path.last_segment().0.contents; + if let Some((name, var, _)) = self.find_generic(name) { + return Some(Type::NamedGeneric(var.clone(), name.clone())); + } + } + + // If we cannot find a local generic of the same name, try to look up a global + match self.path_resolver.resolve(&self.def_maps, path.clone()) { + Ok(PathResolution { module_def_id: ModuleDefId::GlobalId(id), error }) => { + if let Some(current_item) = self.current_item { + self.interner.add_global_dependency(current_item, id); + } + + if let Some(error) = error { + self.push_err(error); + } + Some(Type::Constant(self.eval_global_as_array_length(id, path))) + } + _ => None, + } + } + + fn resolve_array_size( + &mut self, + length: Option, + new_variables: &mut Generics, + ) -> Type { + match length { + None => { + let id = self.interner.next_type_variable_id(); + let typevar = TypeVariable::unbound(id); + new_variables.push(typevar.clone()); + + // 'Named'Generic is a bit of a misnomer here, we want a type variable that + // wont be bound over but this one has no name since we do not currently + // require users to explicitly be generic over array lengths. + Type::NamedGeneric(typevar, Rc::new("".into())) + } + Some(length) => self.convert_expression_type(length), + } + } + + pub fn convert_expression_type(&mut self, length: UnresolvedTypeExpression) -> Type { + match length { + UnresolvedTypeExpression::Variable(path) => { + self.lookup_generic_or_global_type(&path).unwrap_or_else(|| { + self.push_err(ResolverError::NoSuchNumericTypeVariable { path }); + Type::Constant(0) + }) + } + UnresolvedTypeExpression::Constant(int, _) => Type::Constant(int), + UnresolvedTypeExpression::BinaryOperation(lhs, op, rhs, _) => { + let (lhs_span, rhs_span) = (lhs.span(), rhs.span()); + let lhs = self.convert_expression_type(*lhs); + let rhs = self.convert_expression_type(*rhs); + + match (lhs, rhs) { + (Type::Constant(lhs), Type::Constant(rhs)) => { + Type::Constant(op.function()(lhs, rhs)) + } + (lhs, _) => { + let span = + if !matches!(lhs, Type::Constant(_)) { lhs_span } else { rhs_span }; + self.push_err(ResolverError::InvalidArrayLengthExpr { span }); + Type::Constant(0) + } + } + } + } + } + + /// Lookup a given struct type by name. + fn lookup_struct_or_error(&mut self, path: Path) -> Option> { + match self.lookup(path) { + Ok(struct_id) => Some(self.get_struct(struct_id)), + Err(error) => { + self.push_err(error); + None + } + } + } + + /// Lookup a given trait by name/path. + fn lookup_trait_or_error(&mut self, path: Path) -> Option<&mut Trait> { + match self.lookup(path) { + Ok(trait_id) => Some(self.get_trait_mut(trait_id)), + Err(error) => { + self.push_err(error); + None + } + } + } + + /// Looks up a given type by name. + /// This will also instantiate any struct types found. + pub fn lookup_type_or_error(&mut self, path: Path) -> Option { + let ident = path.as_ident(); + if ident.map_or(false, |i| i == SELF_TYPE_NAME) { + if let Some(typ) = &self.self_type { + return Some(typ.clone()); + } + } + + match self.lookup(path) { + Ok(struct_id) => { + let struct_type = self.get_struct(struct_id); + let generics = struct_type.borrow().instantiate(&mut self.interner); + Some(Type::Struct(struct_type, generics)) + } + Err(error) => { + self.push_err(error); + None + } + } + } + + fn lookup_type_alias(&mut self, path: Path) -> Option> { + self.lookup(path).ok().map(|id| self.interner.get_type_alias(id)) + } + + // this resolves Self::some_static_method, inside an impl block (where we don't have a concrete self_type) + fn resolve_trait_static_method_by_self( + &mut self, + path: &Path, + ) -> Option<(TraitMethodId, TraitConstraint, bool)> { + let trait_id = self.trait_id?; + + if path.kind == PathKind::Plain && path.segments.len() == 2 { + let name = &path.segments[0].0.contents; + let method = &path.segments[1]; + + if name == SELF_TYPE_NAME { + let the_trait = self.interner.get_trait(trait_id); + let method = the_trait.find_method(method.0.contents.as_str())?; + + let constraint = TraitConstraint { + typ: self.self_type.clone()?, + trait_generics: Type::from_generics(&the_trait.generics), + trait_id, + }; + return Some((method, constraint, false)); + } + } + None + } + + // this resolves TraitName::some_static_method + fn resolve_trait_static_method( + &mut self, + path: &Path, + ) -> Option<(TraitMethodId, TraitConstraint, bool)> { + if path.kind == PathKind::Plain && path.segments.len() == 2 { + let method = &path.segments[1]; + + let mut trait_path = path.clone(); + trait_path.pop(); + let trait_id = self.lookup(trait_path).ok()?; + let the_trait = self.interner.get_trait(trait_id); + + let method = the_trait.find_method(method.0.contents.as_str())?; + let constraint = TraitConstraint { + typ: Type::TypeVariable( + the_trait.self_type_typevar.clone(), + TypeVariableKind::Normal, + ), + trait_generics: Type::from_generics(&the_trait.generics), + trait_id, + }; + return Some((method, constraint, false)); + } + None + } + + // This resolves a static trait method T::trait_method by iterating over the where clause + // + // Returns the trait method, trait constraint, and whether the impl is assumed from a where + // clause. This is always true since this helper searches where clauses for a generic constraint. + // E.g. `t.method()` with `where T: Foo` in scope will return `(Foo::method, T, vec![Bar])` + fn resolve_trait_method_by_named_generic( + &mut self, + path: &Path, + ) -> Option<(TraitMethodId, TraitConstraint, bool)> { + if path.segments.len() != 2 { + return None; + } + + for UnresolvedTraitConstraint { typ, trait_bound } in self.trait_bounds.clone() { + if let UnresolvedTypeData::Named(constraint_path, _, _) = &typ.typ { + // if `path` is `T::method_name`, we're looking for constraint of the form `T: SomeTrait` + if constraint_path.segments.len() == 1 + && path.segments[0] != constraint_path.last_segment() + { + continue; + } + + if let Ok(ModuleDefId::TraitId(trait_id)) = + self.resolve_path(trait_bound.trait_path.clone()) + { + let the_trait = self.interner.get_trait(trait_id); + if let Some(method) = + the_trait.find_method(path.segments.last().unwrap().0.contents.as_str()) + { + let constraint = TraitConstraint { + trait_id, + typ: self.resolve_type(typ.clone()), + trait_generics: vecmap(trait_bound.trait_generics, |typ| { + self.resolve_type(typ) + }), + }; + return Some((method, constraint, true)); + } + } + } + } + None + } + + // Try to resolve the given trait method path. + // + // Returns the trait method, trait constraint, and whether the impl is assumed to exist by a where clause or not + // E.g. `t.method()` with `where T: Foo` in scope will return `(Foo::method, T, vec![Bar])` + pub fn resolve_trait_generic_path( + &mut self, + path: &Path, + ) -> Option<(TraitMethodId, TraitConstraint, bool)> { + self.resolve_trait_static_method_by_self(path) + .or_else(|| self.resolve_trait_static_method(path)) + .or_else(|| self.resolve_trait_method_by_named_generic(path)) + } + + fn eval_global_as_array_length(&mut self, global: GlobalId, path: &Path) -> u64 { + let Some(stmt) = self.interner.get_global_let_statement(global) else { + let path = path.clone(); + self.push_err(ResolverError::NoSuchNumericTypeVariable { path }); + return 0; + }; + + let length = stmt.expression; + let span = self.interner.expr_span(&length); + let result = self.try_eval_array_length_id(length, span); + + match result.map(|length| length.try_into()) { + Ok(Ok(length_value)) => return length_value, + Ok(Err(_cast_err)) => self.push_err(ResolverError::IntegerTooLarge { span }), + Err(Some(error)) => self.push_err(error), + Err(None) => (), + } + 0 + } + + fn try_eval_array_length_id( + &self, + rhs: ExprId, + span: Span, + ) -> Result> { + // Arbitrary amount of recursive calls to try before giving up + let fuel = 100; + self.try_eval_array_length_id_with_fuel(rhs, span, fuel) + } + + fn try_eval_array_length_id_with_fuel( + &self, + rhs: ExprId, + span: Span, + fuel: u32, + ) -> Result> { + if fuel == 0 { + // If we reach here, it is likely from evaluating cyclic globals. We expect an error to + // be issued for them after name resolution so issue no error now. + return Err(None); + } + + match self.interner.expression(&rhs) { + HirExpression::Literal(HirLiteral::Integer(int, false)) => { + int.try_into_u128().ok_or(Some(ResolverError::IntegerTooLarge { span })) + } + HirExpression::Ident(ident) => { + let definition = self.interner.definition(ident.id); + match definition.kind { + DefinitionKind::Global(global_id) => { + let let_statement = self.interner.get_global_let_statement(global_id); + if let Some(let_statement) = let_statement { + let expression = let_statement.expression; + self.try_eval_array_length_id_with_fuel(expression, span, fuel - 1) + } else { + Err(Some(ResolverError::InvalidArrayLengthExpr { span })) + } + } + _ => Err(Some(ResolverError::InvalidArrayLengthExpr { span })), + } + } + HirExpression::Infix(infix) => { + let lhs = self.try_eval_array_length_id_with_fuel(infix.lhs, span, fuel - 1)?; + let rhs = self.try_eval_array_length_id_with_fuel(infix.rhs, span, fuel - 1)?; + + match infix.operator.kind { + BinaryOpKind::Add => Ok(lhs + rhs), + BinaryOpKind::Subtract => Ok(lhs - rhs), + BinaryOpKind::Multiply => Ok(lhs * rhs), + BinaryOpKind::Divide => Ok(lhs / rhs), + BinaryOpKind::Equal => Ok((lhs == rhs) as u128), + BinaryOpKind::NotEqual => Ok((lhs != rhs) as u128), + BinaryOpKind::Less => Ok((lhs < rhs) as u128), + BinaryOpKind::LessEqual => Ok((lhs <= rhs) as u128), + BinaryOpKind::Greater => Ok((lhs > rhs) as u128), + BinaryOpKind::GreaterEqual => Ok((lhs >= rhs) as u128), + BinaryOpKind::And => Ok(lhs & rhs), + BinaryOpKind::Or => Ok(lhs | rhs), + BinaryOpKind::Xor => Ok(lhs ^ rhs), + BinaryOpKind::ShiftRight => Ok(lhs >> rhs), + BinaryOpKind::ShiftLeft => Ok(lhs << rhs), + BinaryOpKind::Modulo => Ok(lhs % rhs), + } + } + _other => Err(Some(ResolverError::InvalidArrayLengthExpr { span })), + } + } + + /// Check if an assignment is overflowing with respect to `annotated_type` + /// in a declaration statement where `annotated_type` is an unsigned integer + pub fn lint_overflowing_uint(&mut self, rhs_expr: &ExprId, annotated_type: &Type) { + let expr = self.interner.expression(rhs_expr); + let span = self.interner.expr_span(rhs_expr); + match expr { + HirExpression::Literal(HirLiteral::Integer(value, false)) => { + let v = value.to_u128(); + if let Type::Integer(_, bit_count) = annotated_type { + let bit_count: u32 = (*bit_count).into(); + let max = 1 << bit_count; + if v >= max { + self.push_err(TypeCheckError::OverflowingAssignment { + expr: value, + ty: annotated_type.clone(), + range: format!("0..={}", max - 1), + span, + }); + }; + }; + } + HirExpression::Prefix(expr) => { + self.lint_overflowing_uint(&expr.rhs, annotated_type); + if matches!(expr.operator, UnaryOp::Minus) { + self.push_err(TypeCheckError::InvalidUnaryOp { + kind: "annotated_type".to_string(), + span, + }); + } + } + HirExpression::Infix(expr) => { + self.lint_overflowing_uint(&expr.lhs, annotated_type); + self.lint_overflowing_uint(&expr.rhs, annotated_type); + } + _ => {} + } + } + + pub fn unify( + &mut self, + actual: &Type, + expected: &Type, + make_error: impl FnOnce() -> TypeCheckError, + ) { + let mut errors = Vec::new(); + actual.unify(expected, &mut errors, make_error); + self.errors.extend(errors.into_iter().map(Into::into)); + } + + /// Wrapper of Type::unify_with_coercions using self.errors + pub fn unify_with_coercions( + &mut self, + actual: &Type, + expected: &Type, + expression: ExprId, + make_error: impl FnOnce() -> TypeCheckError, + ) { + let mut errors = Vec::new(); + actual.unify_with_coercions( + expected, + expression, + &mut self.interner, + &mut errors, + make_error, + ); + self.errors.extend(errors.into_iter().map(Into::into)); + } + + /// Return a fresh integer or field type variable and log it + /// in self.type_variables to default it later. + pub fn polymorphic_integer_or_field(&mut self) -> Type { + let typ = Type::polymorphic_integer_or_field(&mut self.interner); + self.type_variables.push(typ.clone()); + typ + } + + /// Return a fresh integer type variable and log it + /// in self.type_variables to default it later. + pub fn polymorphic_integer(&mut self) -> Type { + let typ = Type::polymorphic_integer(&mut self.interner); + self.type_variables.push(typ.clone()); + typ + } + + /// Translates a (possibly Unspecified) UnresolvedType to a Type. + /// Any UnresolvedType::Unspecified encountered are replaced with fresh type variables. + pub fn resolve_inferred_type(&mut self, typ: UnresolvedType) -> Type { + match &typ.typ { + UnresolvedTypeData::Unspecified => self.interner.next_type_variable(), + _ => self.resolve_type_inner(typ, &mut vec![]), + } + } + + pub fn type_check_prefix_operand( + &mut self, + op: &crate::ast::UnaryOp, + rhs_type: &Type, + span: Span, + ) -> Type { + let mut unify = |expected| { + self.unify(rhs_type, &expected, || TypeCheckError::TypeMismatch { + expr_typ: rhs_type.to_string(), + expected_typ: expected.to_string(), + expr_span: span, + }); + expected + }; + + match op { + crate::ast::UnaryOp::Minus => { + if rhs_type.is_unsigned() { + self.push_err(TypeCheckError::InvalidUnaryOp { kind: rhs_type.to_string(), span }); + } + let expected = self.polymorphic_integer_or_field(); + self.unify(rhs_type, &expected, || TypeCheckError::InvalidUnaryOp { + kind: rhs_type.to_string(), + span, + }); + expected + } + crate::ast::UnaryOp::Not => { + let rhs_type = rhs_type.follow_bindings(); + + // `!` can work on booleans or integers + if matches!(rhs_type, Type::Integer(..)) { + return rhs_type; + } + + unify(Type::Bool) + } + crate::ast::UnaryOp::MutableReference => { + Type::MutableReference(Box::new(rhs_type.follow_bindings())) + } + crate::ast::UnaryOp::Dereference { implicitly_added: _ } => { + let element_type = self.interner.next_type_variable(); + unify(Type::MutableReference(Box::new(element_type.clone()))); + element_type + } + } + } + + /// Insert as many dereference operations as necessary to automatically dereference a method + /// call object to its base value type T. + pub fn insert_auto_dereferences(&mut self, object: ExprId, typ: Type) -> (ExprId, Type) { + if let Type::MutableReference(element) = typ { + let location = self.interner.id_location(object); + + let object = self.interner.push_expr(HirExpression::Prefix(HirPrefixExpression { + operator: UnaryOp::Dereference { implicitly_added: true }, + rhs: object, + })); + self.interner.push_expr_type(object, element.as_ref().clone()); + self.interner.push_expr_location(object, location.span, location.file); + + // Recursively dereference to allow for converting &mut &mut T to T + self.insert_auto_dereferences(object, *element) + } else { + (object, typ) + } + } + + /// Given a method object: `(*foo).bar` of a method call `(*foo).bar.baz()`, remove the + /// implicitly added dereference operator if one is found. + /// + /// Returns Some(new_expr_id) if a dereference was removed and None otherwise. + fn try_remove_implicit_dereference(&mut self, object: ExprId) -> Option { + match self.interner.expression(&object) { + HirExpression::MemberAccess(mut access) => { + let new_lhs = self.try_remove_implicit_dereference(access.lhs)?; + access.lhs = new_lhs; + access.is_offset = true; + + // `object` will have a different type now, which will be filled in + // later when type checking the method call as a function call. + self.interner.replace_expr(&object, HirExpression::MemberAccess(access)); + Some(object) + } + HirExpression::Prefix(prefix) => match prefix.operator { + // Found a dereference we can remove. Now just replace it with its rhs to remove it. + UnaryOp::Dereference { implicitly_added: true } => Some(prefix.rhs), + _ => None, + }, + _ => None, + } + } +} diff --git a/compiler/noirc_frontend/src/hir/resolution/import.rs b/compiler/noirc_frontend/src/hir/resolution/import.rs index 8850331f683..343113836ed 100644 --- a/compiler/noirc_frontend/src/hir/resolution/import.rs +++ b/compiler/noirc_frontend/src/hir/resolution/import.rs @@ -2,11 +2,14 @@ use noirc_errors::{CustomDiagnostic, Span}; use thiserror::Error; use crate::graph::CrateId; +use crate::hir::def_collector::dc_crate::CompilationError; use std::collections::BTreeMap; use crate::ast::{Ident, ItemVisibility, Path, PathKind}; use crate::hir::def_map::{CrateDefMap, LocalModuleId, ModuleDefId, ModuleId, PerNs}; +use super::errors::ResolverError; + #[derive(Debug, Clone)] pub struct ImportDirective { pub module_id: LocalModuleId, @@ -53,6 +56,12 @@ pub struct ResolvedImport { pub error: Option, } +impl From for CompilationError { + fn from(error: PathResolutionError) -> Self { + Self::ResolverError(ResolverError::PathResolutionError(error)) + } +} + impl<'a> From<&'a PathResolutionError> for CustomDiagnostic { fn from(error: &'a PathResolutionError) -> Self { match &error { diff --git a/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/compiler/noirc_frontend/src/hir/resolution/resolver.rs index cd9be93fab9..fc4cd4f591c 100644 --- a/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -56,17 +56,17 @@ use crate::hir_def::{ use super::errors::{PubPosition, ResolverError}; use super::import::PathResolution; -const SELF_TYPE_NAME: &str = "Self"; +pub const SELF_TYPE_NAME: &str = "Self"; type Scope = GenericScope; type ScopeTree = GenericScopeTree; type ScopeForest = GenericScopeForest; pub struct LambdaContext { - captures: Vec, + pub captures: Vec, /// the index in the scope tree /// (sometimes being filled by ScopeTree's find method) - scope_index: usize, + pub scope_index: usize, } /// The primary jobs of the Resolver are to validate that every variable found refers to exactly 1 @@ -1346,7 +1346,7 @@ impl<'a> Resolver<'a> { range @ ForRange::Array(_) => { let for_stmt = range.into_for(for_loop.identifier, for_loop.block, for_loop.span); - self.resolve_stmt(for_stmt, for_loop.span) + self.resolve_stmt(for_stmt.kind, for_loop.span) } } } diff --git a/compiler/noirc_frontend/src/hir/type_check/mod.rs b/compiler/noirc_frontend/src/hir/type_check/mod.rs index 0f8131d6ebb..2e448858d9e 100644 --- a/compiler/noirc_frontend/src/hir/type_check/mod.rs +++ b/compiler/noirc_frontend/src/hir/type_check/mod.rs @@ -25,7 +25,7 @@ use crate::{ Type, TypeBindings, }; -use self::errors::Source; +pub use self::errors::Source; pub struct TypeChecker<'interner> { interner: &'interner mut NodeInterner, diff --git a/compiler/noirc_frontend/src/hir_def/types.rs b/compiler/noirc_frontend/src/hir_def/types.rs index 637f3c99e89..225cd04491b 100644 --- a/compiler/noirc_frontend/src/hir_def/types.rs +++ b/compiler/noirc_frontend/src/hir_def/types.rs @@ -1423,14 +1423,14 @@ impl Type { /// Retrieves the type of the given field name /// Panics if the type is not a struct or tuple. - pub fn get_field_type(&self, field_name: &str) -> Type { + pub fn get_field_type(&self, field_name: &str) -> Option { match self { - Type::Struct(def, args) => def.borrow().get_field(field_name, args).unwrap().0, + Type::Struct(def, args) => def.borrow().get_field(field_name, args).map(|(typ, _)| typ), Type::Tuple(fields) => { let mut fields = fields.iter().enumerate(); - fields.find(|(i, _)| i.to_string() == *field_name).unwrap().1.clone() + fields.find(|(i, _)| i.to_string() == *field_name).map(|(_, typ)| typ).cloned() } - other => panic!("Tried to iterate over the fields of '{other}', which has none"), + _ => None, } } From 29e67a10e00333bb4f1dc5a5c92bf55ffb7b3b7b Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Tue, 7 May 2024 14:42:35 -0500 Subject: [PATCH 03/38] Finish each expression kind --- compiler/noirc_frontend/src/elaborator/mod.rs | 412 ++++++++--- .../noirc_frontend/src/elaborator/patterns.rs | 75 +- .../noirc_frontend/src/elaborator/scope.rs | 26 +- .../src/elaborator/statements.rs | 52 +- .../noirc_frontend/src/elaborator/types.rs | 658 +++++++++++++++++- .../noirc_frontend/src/hir/type_check/expr.rs | 4 +- compiler/noirc_frontend/src/hir_def/expr.rs | 11 +- 7 files changed, 1078 insertions(+), 160 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index c590a260494..ab197fd026b 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -1,18 +1,51 @@ -use std::{rc::Rc, collections::{BTreeMap, BTreeSet}}; +#![allow(unused)] +use std::{ + collections::{BTreeMap, BTreeSet}, + rc::Rc, +}; -use crate::{macros_api::{NoirFunction, Expression, BlockExpression, Statement, StatementKind, ExpressionKind, NodeInterner, HirExpression, HirStatement, StructId, Literal, PrefixExpression, IndexExpression, CallExpression, MethodCallExpression, MemberAccessExpression, CastExpression, HirLiteral}, node_interner::{FuncId, StmtId, ExprId, DependencyId, TraitId, DefinitionKind}, ast::{FunctionKind, UnresolvedTraitConstraint, ConstructorExpression, InfixExpression, IfExpression, Lambda, ArrayLiteral, UnresolvedTypeExpression}, hir_def::{expr::{HirBlockExpression, HirIdent, HirArrayLiteral, HirLambda, HirIfExpression, HirPrefixExpression, HirIndexExpression, HirCallExpression}, traits::TraitConstraint}, hir::{resolution::{errors::ResolverError, path_resolver::PathResolver, resolver::LambdaContext}, def_collector::dc_crate::CompilationError, type_check::TypeCheckError, scope::ScopeForest as GenericScopeForest}, Type, TypeVariable}; use crate::graph::CrateId; use crate::hir::def_map::CrateDefMap; +use crate::{ + ast::{ + ArrayLiteral, ConstructorExpression, FunctionKind, IfExpression, InfixExpression, Lambda, + UnresolvedTraitConstraint, UnresolvedTypeExpression, + }, + hir::{ + def_collector::dc_crate::CompilationError, + resolution::{errors::ResolverError, path_resolver::PathResolver, resolver::LambdaContext}, + scope::ScopeForest as GenericScopeForest, + type_check::TypeCheckError, + }, + hir_def::{ + expr::{ + HirArrayLiteral, HirBinaryOp, HirBlockExpression, HirCallExpression, HirCastExpression, + HirConstructorExpression, HirIdent, HirIfExpression, HirIndexExpression, + HirInfixExpression, HirLambda, HirMemberAccess, HirMethodCallExpression, + HirMethodReference, HirPrefixExpression, + }, + traits::TraitConstraint, + }, + macros_api::{ + BlockExpression, CallExpression, CastExpression, Expression, ExpressionKind, HirExpression, + HirLiteral, HirStatement, Ident, IndexExpression, Literal, MemberAccessExpression, + MethodCallExpression, NodeInterner, NoirFunction, PrefixExpression, Statement, + StatementKind, StructId, + }, + node_interner::{DefinitionKind, DependencyId, ExprId, FuncId, StmtId, TraitId}, + Shared, StructType, Type, TypeVariable, +}; -mod scope; -mod types; mod patterns; +mod scope; mod statements; +mod types; use fm::FileId; use iter_extended::vecmap; -use noirc_errors::{Span, Location}; +use noirc_errors::{Location, Span}; use regex::Regex; +use rustc_hash::FxHashSet as HashSet; use scope::Scope; /// ResolverMetas are tagged onto each definition to track how many times they are used @@ -86,6 +119,8 @@ struct Elaborator { trait_bounds: Vec, + current_function: Option, + /// All type variables created in the current function. /// This map is used to default any integer type variables at the end of /// a function (before checking trait constraints) if a type wasn't already chosen. @@ -99,7 +134,8 @@ struct Elaborator { } impl Elaborator { - fn elaborate_function(&mut self, function: NoirFunction, id: FuncId) { + fn elaborate_function(&mut self, function: NoirFunction, _id: FuncId) { + // This is a stub until the elaborator is connected to dc_crate match function.kind { FunctionKind::LowLevel => todo!(), FunctionKind::Builtin => todo!(), @@ -107,7 +143,7 @@ impl Elaborator { FunctionKind::Recursive => todo!(), FunctionKind::Normal => { let _body = self.elaborate_block(function.def.body); - }, + } } } @@ -118,11 +154,13 @@ impl Elaborator { ExpressionKind::Prefix(prefix) => self.elaborate_prefix(*prefix), ExpressionKind::Index(index) => self.elaborate_index(*index), ExpressionKind::Call(call) => self.elaborate_call(*call, expr.span), - ExpressionKind::MethodCall(methodCall) => self.elaborate_method_call(*methodCall), + ExpressionKind::MethodCall(call) => self.elaborate_method_call(*call, expr.span), ExpressionKind::Constructor(constructor) => self.elaborate_constructor(*constructor), - ExpressionKind::MemberAccess(memberAccess) => self.elaborate_member_access(*memberAccess), - ExpressionKind::Cast(cast) => self.elaborate_cast(*cast), - ExpressionKind::Infix(infix) => self.elaborate_infix(*infix), + ExpressionKind::MemberAccess(access) => { + return self.elaborate_member_access(*access, expr.span) + } + ExpressionKind::Cast(cast) => self.elaborate_cast(*cast, expr.span), + ExpressionKind::Infix(infix) => return self.elaborate_infix(*infix, expr.span), ExpressionKind::If(if_) => self.elaborate_if(*if_), ExpressionKind::Variable(variable) => return self.elaborate_variable(variable), ExpressionKind::Tuple(tuple) => self.elaborate_tuple(tuple), @@ -150,7 +188,7 @@ impl Elaborator { StatementKind::Expression(expr) => { let (expr, typ) = self.elaborate_expression(expr); (HirStatement::Expression(expr), typ) - }, + } StatementKind::Semi(expr) => { let (expr, _typ) = self.elaborate_expression(expr); (HirStatement::Semi(expr), Type::Unit) @@ -160,9 +198,10 @@ impl Elaborator { } fn elaborate_statement(&mut self, statement: Statement) -> (StmtId, Type) { + let span = statement.span; let (hir_statement, typ) = self.elaborate_statement_value(statement); let id = self.interner.push_stmt(hir_statement); - self.interner.push_stmt_location(id, statement.span, self.file); + self.interner.push_stmt_location(id, span, self.file); (id, typ) } @@ -173,6 +212,7 @@ impl Elaborator { for (i, statement) in block.statements.into_iter().enumerate() { let (id, stmt_type) = self.elaborate_statement(statement); + statements.push(id); if let HirStatement::Semi(expr) = self.interner.statement(&id) { let inner_expr_type = self.interner.id_type(expr); @@ -209,11 +249,7 @@ impl Elaborator { self.push_err(ResolverError::JumpOutsideLoop { is_break, span }); } - let expr = if is_break { - HirStatement::Break - } else { - HirStatement::Continue - }; + let expr = if is_break { HirStatement::Break } else { HirStatement::Continue }; (expr, self.interner.next_type_variable()) } @@ -235,15 +271,24 @@ impl Elaborator { (Lit(HirLiteral::Str(str)), Type::String(Box::new(len))) } Literal::FmtStr(str) => self.elaborate_fmt_string(str, span), - Literal::Array(array_literal) => self.elaborate_array_literal(array_literal, span, true), - Literal::Slice(array_literal) => self.elaborate_array_literal(array_literal, span, false), + Literal::Array(array_literal) => { + self.elaborate_array_literal(array_literal, span, true) + } + Literal::Slice(array_literal) => { + self.elaborate_array_literal(array_literal, span, false) + } } } - fn elaborate_array_literal(&mut self, array_literal: ArrayLiteral, span: Span, is_array: bool) -> (HirExpression, Type) { + fn elaborate_array_literal( + &mut self, + array_literal: ArrayLiteral, + span: Span, + is_array: bool, + ) -> (HirExpression, Type) { let (expr, elem_type, length) = match array_literal { ArrayLiteral::Standard(elements) => { - let mut first_elem_type = self.interner.next_type_variable(); + let first_elem_type = self.interner.next_type_variable(); let first_span = elements.first().map(|elem| elem.span).unwrap_or(span); let elements = vecmap(elements.into_iter().enumerate(), |(i, elem)| { @@ -284,7 +329,11 @@ impl Elaborator { }; let constructor = if is_array { HirLiteral::Array } else { HirLiteral::Slice }; let elem_type = Box::new(elem_type); - let typ = if is_array { Type::Array(Box::new(length), elem_type) } else { Type::Slice(elem_type) }; + let typ = if is_array { + Type::Array(Box::new(length), elem_type) + } else { + Type::Slice(elem_type) + }; (HirExpression::Literal(constructor(expr)), typ) } @@ -306,6 +355,10 @@ impl Elaborator { let ident = HirExpression::Ident(old_value.ident.clone()); let expr_id = self.interner.push_expr(ident); self.interner.push_expr_location(expr_id, call_expr_span, self.file); + let ident = old_value.ident.clone(); + let typ = self.type_check_variable(ident, expr_id); + self.interner.push_expr_type(expr_id, typ.clone()); + capture_types.push(typ); fmt_str_idents.push(expr_id); } else if ident_name.parse::().is_ok() { self.push_err(ResolverError::NumericConstantInFormatString { @@ -329,20 +382,18 @@ impl Elaborator { let span = prefix.rhs.span; let (rhs, rhs_type) = self.elaborate_expression(prefix.rhs); let ret_type = self.type_check_prefix_operand(&prefix.operator, &rhs_type, span); - (HirExpression::Prefix(HirPrefixExpression { operator: prefix.operator, rhs }), ret_type) + (HirExpression::Prefix(HirPrefixExpression { operator: prefix.operator, rhs }), ret_type) } fn elaborate_index(&mut self, index_expr: IndexExpression) -> (HirExpression, Type) { - let (index, index_type) = self.elaborate_expression(index_expr.index); let span = index_expr.index.span; + let (index, index_type) = self.elaborate_expression(index_expr.index); let expected = self.polymorphic_integer_or_field(); - self.unify(&index_type, &expected, || { - TypeCheckError::TypeMismatch { - expected_typ: "an integer".to_owned(), - expr_typ: index_type.to_string(), - expr_span: span, - } + self.unify(&index_type, &expected, || TypeCheckError::TypeMismatch { + expected_typ: "an integer".to_owned(), + expr_typ: index_type.to_string(), + expr_span: span, }); // When writing `a[i]`, if `a : &mut ...` then automatically dereference `a` as many @@ -372,83 +423,278 @@ impl Elaborator { } fn elaborate_call(&mut self, call: CallExpression, span: Span) -> (HirExpression, Type) { - // Get the span and name of path for error reporting let (func, func_type) = self.elaborate_expression(*call.func); - let arguments = vecmap(call.arguments, |arg| self.elaborate_expression(arg)); + let mut arguments = Vec::with_capacity(call.arguments.len()); + let args = vecmap(call.arguments, |arg| { + let span = arg.span; + let (arg, typ) = self.elaborate_expression(arg); + arguments.push(arg); + (typ, arg, span) + }); + let location = Location::new(span, self.file); - let expr = HirExpression::Call(HirCallExpression { func, arguments, location }); - (expr, typ) + let call = HirCallExpression { func, arguments, location }; + let typ = self.type_check_call(&call, func_type, args, span); + (HirExpression::Call(call), typ) + } + fn check_if_deprecated(&mut self, expr: ExprId) { + if let HirExpression::Ident(HirIdent { location, id, impl_kind: _ }) = + self.interner.expression(&expr) + { + if let Some(DefinitionKind::Function(func_id)) = + self.interner.try_definition(id).map(|def| &def.kind) + { + let attributes = self.interner.function_attributes(func_id); + if let Some(note) = attributes.get_deprecated_note() { + self.push_err(TypeCheckError::CallDeprecated { + name: self.interner.definition_name(id).to_string(), + note, + span: location.span, + }); + } + } + } + } - // Need to setup these flags here as `self` is borrowed mutably to type check the rest of the call expression - // These flags are later used to type check calls to unconstrained functions from constrained functions - let current_func = self.current_function; - let func_mod = current_func.map(|func| self.interner.function_modifiers(&func)); - let is_current_func_constrained = - func_mod.map_or(true, |func_mod| !func_mod.is_unconstrained); - let is_unconstrained_call = self.is_unconstrained_call(&call_expr.func); + fn is_unconstrained_call(&self, expr: ExprId) -> bool { + if let HirExpression::Ident(HirIdent { id, .. }) = self.interner.expression(&expr) { + if let Some(DefinitionKind::Function(func_id)) = + self.interner.try_definition(id).map(|def| &def.kind) + { + let modifiers = self.interner.function_modifiers(func_id); + return modifiers.is_unconstrained; + } + } + false + } - self.check_if_deprecated(&call_expr.func); + fn elaborate_method_call( + &mut self, + method_call: MethodCallExpression, + span: Span, + ) -> (HirExpression, Type) { + let object_span = method_call.object.span; + let (mut object, mut object_type) = self.elaborate_expression(method_call.object); + object_type = object_type.follow_bindings(); + + let method_name = method_call.method_name.0.contents.as_str(); + match self.lookup_method(&object_type, method_name, span) { + Some(method_ref) => { + // Automatically add `&mut` if the method expects a mutable reference and + // the object is not already one. + if let HirMethodReference::FuncId(func_id) = &method_ref { + if *func_id != FuncId::dummy_id() { + let function_type = self.interner.function_meta(func_id).typ.clone(); + + self.try_add_mutable_reference_to_object( + &function_type, + &mut object_type, + &mut object, + ); + } + } - let function = self.check_expression(&call_expr.func); + // These arguments will be given to the desugared function call. + // Compared to the method arguments, they also contain the object. + let mut function_args = Vec::with_capacity(method_call.arguments.len() + 1); + let mut arguments = Vec::with_capacity(method_call.arguments.len()); - let args = vecmap(&call_expr.arguments, |arg| { - let typ = self.check_expression(arg); - (typ, *arg, self.interner.expr_span(arg)) - }); + function_args.push((object_type.clone(), object, object_span)); - // Check that we are not passing a mutable reference from a constrained runtime to an unconstrained runtime - if is_current_func_constrained && is_unconstrained_call { - for (typ, _, _) in args.iter() { - if matches!(&typ.follow_bindings(), Type::MutableReference(_)) { - self.errors.push(TypeCheckError::ConstrainedReferenceToUnconstrained { - span: self.interner.expr_span(expr_id), - }); - return Type::Error; + for arg in method_call.arguments { + let span = arg.span; + let (arg, typ) = self.elaborate_expression(arg); + arguments.push(arg); + function_args.push((typ, arg, span)); } + + let location = Location::new(span, self.file); + let method = method_call.method_name; + let method_call = HirMethodCallExpression { method, object, arguments, location }; + + // Desugar the method call into a normal, resolved function call + // so that the backend doesn't need to worry about methods + // TODO: update object_type here? + let ((function_id, function_name), function_call) = method_call.into_function_call( + &method_ref, + object_type, + location, + &mut self.interner, + ); + + let func_type = self.type_check_variable(function_name, function_id); + + // Type check the new call now that it has been changed from a method call + // to a function call. This way we avoid duplicating code. + let typ = self.type_check_call(&function_call, func_type, function_args, span); + (HirExpression::Call(function_call), typ) } + None => (HirExpression::Error, Type::Error), } + } - let span = self.interner.expr_span(expr_id); - let return_type = self.bind_function_type(function, args, span); + fn elaborate_constructor( + &mut self, + constructor: ConstructorExpression, + ) -> (HirExpression, Type) { + let span = constructor.type_name.span(); + + match self.lookup_type_or_error(constructor.type_name) { + Some(Type::Struct(r#type, struct_generics)) => { + let struct_type = r#type.clone(); + let generics = struct_generics.clone(); + + let fields = constructor.fields; + let field_types = r#type.borrow().get_fields(&struct_generics); + let fields = self.resolve_constructor_expr_fields( + struct_type.clone(), + field_types, + fields, + span, + ); + let expr = HirExpression::Constructor(HirConstructorExpression { + fields, + r#type, + struct_generics, + }); + (expr, Type::Struct(struct_type, generics)) + } + Some(typ) => { + self.push_err(ResolverError::NonStructUsedInConstructor { typ, span }); + (HirExpression::Error, Type::Error) + } + None => (HirExpression::Error, Type::Error), + } + } - // Check that we are not passing a slice from an unconstrained runtime to a constrained runtime - if is_current_func_constrained && is_unconstrained_call { - if return_type.contains_slice() { - self.errors.push(TypeCheckError::UnconstrainedSliceReturnToConstrained { - span: self.interner.expr_span(expr_id), + /// Resolve all the fields of a struct constructor expression. + /// Ensures all fields are present, none are repeated, and all + /// are part of the struct. + fn resolve_constructor_expr_fields( + &mut self, + struct_type: Shared, + field_types: Vec<(String, Type)>, + fields: Vec<(Ident, Expression)>, + span: Span, + ) -> Vec<(Ident, ExprId)> { + let mut ret = Vec::with_capacity(fields.len()); + let mut seen_fields = HashSet::default(); + let mut unseen_fields = struct_type.borrow().field_names(); + + for (field_name, field) in fields { + let expected_type = field_types.iter().find(|(name, _)| name == &field_name.0.contents); + let expected_type = expected_type.map(|(_, typ)| typ).unwrap_or(&Type::Error); + + let field_span = field.span; + let (resolved, field_type) = self.elaborate_expression(field); + + if unseen_fields.contains(&field_name) { + unseen_fields.remove(&field_name); + seen_fields.insert(field_name.clone()); + + self.unify_with_coercions(&field_type, expected_type, resolved, || { + TypeCheckError::TypeMismatch { + expected_typ: expected_type.to_string(), + expr_typ: field_type.to_string(), + expr_span: field_span, + } }); - return Type::Error; - } else if matches!(&return_type.follow_bindings(), Type::MutableReference(_)) { - self.errors.push(TypeCheckError::UnconstrainedReferenceToConstrained { - span: self.interner.expr_span(expr_id), + } else if seen_fields.contains(&field_name) { + // duplicate field + self.push_err(ResolverError::DuplicateField { field: field_name.clone() }); + } else { + // field not required by struct + self.push_err(ResolverError::NoSuchField { + field: field_name.clone(), + struct_definition: struct_type.borrow().name.clone(), }); - return Type::Error; } - }; - return_type - } + ret.push((field_name, resolved)); + } + + if !unseen_fields.is_empty() { + self.push_err(ResolverError::MissingFields { + span, + missing_fields: unseen_fields.into_iter().map(|field| field.to_string()).collect(), + struct_definition: struct_type.borrow().name.clone(), + }); + } - fn elaborate_method_call(&mut self, method_call: MethodCallExpression) -> (HirExpression, Type) { - todo!() + ret } - fn elaborate_constructor(&mut self, constructor: ConstructorExpression) -> (HirExpression, Type) { - todo!() + fn elaborate_member_access( + &mut self, + access: MemberAccessExpression, + span: Span, + ) -> (ExprId, Type) { + let (lhs, lhs_type) = self.elaborate_expression(access.lhs); + let rhs = access.rhs; + // `is_offset` is only used when lhs is a reference and we want to return a reference to rhs + let access = HirMemberAccess { lhs, rhs, is_offset: false }; + let expr_id = self.intern_expr(HirExpression::MemberAccess(access.clone()), span); + let typ = self.type_check_member_access(access, expr_id, lhs_type, span); + self.interner.push_expr_type(expr_id, typ.clone()); + (expr_id, typ) } - fn elaborate_member_access(&mut self, member_access: MemberAccessExpression) -> (HirExpression, Type) { - todo!() + fn intern_expr(&mut self, expr: HirExpression, span: Span) -> ExprId { + let id = self.interner.push_expr(expr); + self.interner.push_expr_location(id, span, self.file); + id } - fn elaborate_cast(&mut self, cast: CastExpression) -> (HirExpression, Type) { - todo!() + fn elaborate_cast(&mut self, cast: CastExpression, span: Span) -> (HirExpression, Type) { + let (lhs, lhs_type) = self.elaborate_expression(cast.lhs); + let r#type = self.resolve_type(cast.r#type); + let result = self.check_cast(lhs_type, &r#type, span); + let expr = HirExpression::Cast(HirCastExpression { lhs, r#type }); + (expr, result) } - fn elaborate_infix(&mut self, infix: InfixExpression) -> (HirExpression, Type) { - todo!() + fn elaborate_infix(&mut self, infix: InfixExpression, span: Span) -> (ExprId, Type) { + let (lhs, lhs_type) = self.elaborate_expression(infix.lhs); + let (rhs, rhs_type) = self.elaborate_expression(infix.rhs); + let trait_id = self.interner.get_operator_trait_method(infix.operator.contents); + + let operator = HirBinaryOp::new(infix.operator, self.file); + let expr = HirExpression::Infix(HirInfixExpression { + lhs, + operator, + trait_method_id: trait_id, + rhs, + }); + + let expr_id = self.interner.push_expr(expr); + self.interner.push_expr_location(expr_id, span, self.file); + + let typ = match self.infix_operand_type_rules(&lhs_type, &operator, &rhs_type, span) { + Ok((typ, use_impl)) => { + if use_impl { + // Delay checking the trait constraint until the end of the function. + // Checking it now could bind an unbound type variable to any type + // that implements the trait. + let constraint = TraitConstraint { + typ: lhs_type.clone(), + trait_id: trait_id.trait_id, + trait_generics: Vec::new(), + }; + self.trait_constraints.push((constraint, expr_id)); + self.type_check_operator_method(expr_id, trait_id, &lhs_type, span); + } + typ + } + Err(error) => { + self.push_err(error); + Type::Error + } + }; + + self.interner.push_expr_type(expr_id, typ.clone()); + (expr_id, typ) } fn elaborate_if(&mut self, if_expr: IfExpression) -> (HirExpression, Type) { @@ -461,7 +707,7 @@ impl Elaborator { expr_typ: cond_type.to_string(), expr_span, }); - + let alternative = if_expr.alternative.map(|alternative| { let expr_span = alternative.span; let (else_, else_type) = self.elaborate_expression(alternative); @@ -550,7 +796,7 @@ impl Elaborator { (HirExpression::Quote(block), Type::Code) } - fn elaborate_comptime_block(&mut self, comptime: BlockExpression) -> (HirExpression, Type) { + fn elaborate_comptime_block(&mut self, _comptime: BlockExpression) -> (HirExpression, Type) { todo!("Elaborate comptime block") } } diff --git a/compiler/noirc_frontend/src/elaborator/patterns.rs b/compiler/noirc_frontend/src/elaborator/patterns.rs index 449e7a3db2f..868989d074a 100644 --- a/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -1,13 +1,31 @@ use iter_extended::vecmap; -use noirc_errors::{Span, Location}; +use noirc_errors::{Location, Span}; use rustc_hash::FxHashSet as HashSet; -use crate::{macros_api::{Pattern, Ident, Path, HirExpression}, Type, node_interner::{DefinitionKind, DefinitionId, ExprId, TraitImplKind}, hir_def::{stmt::HirPattern, expr::{HirIdent, ImplKind}}, hir::{resolution::errors::ResolverError, type_check::{TypeCheckError, Source}}, ast::ERROR_IDENT, Shared, StructType, TypeBindings}; +use crate::{ + ast::ERROR_IDENT, + hir::{ + resolution::errors::ResolverError, + type_check::{Source, TypeCheckError}, + }, + hir_def::{ + expr::{HirIdent, ImplKind}, + stmt::HirPattern, + }, + macros_api::{HirExpression, Ident, Path, Pattern}, + node_interner::{DefinitionId, DefinitionKind, ExprId, TraitImplKind}, + Shared, StructType, Type, TypeBindings, +}; use super::{Elaborator, ResolverMeta}; impl Elaborator { - pub fn elaborate_pattern(&mut self, pattern: Pattern, expected_type: Type, definition_kind: DefinitionKind) -> HirPattern { + pub(super) fn elaborate_pattern( + &mut self, + pattern: Pattern, + expected_type: Type, + definition_kind: DefinitionKind, + ) -> HirPattern { self.elaborate_pattern_mut(pattern, expected_type, definition_kind, None) } @@ -35,7 +53,8 @@ impl Elaborator { self.push_err(ResolverError::UnnecessaryMut { first_mut, second_mut: span }); } - let pattern = self.elaborate_pattern_mut(*pattern, expected_type, definition, Some(span)); + let pattern = + self.elaborate_pattern_mut(*pattern, expected_type, definition, Some(span)); let location = Location::new(span, self.file); HirPattern::Mutable(Box::new(pattern), location) } @@ -45,7 +64,7 @@ impl Elaborator { Type::Error => Vec::new(), expected_type => { let tuple = - Type::Tuple(vecmap(fields, |_| self.interner.next_type_variable())); + Type::Tuple(vecmap(&fields, |_| self.interner.next_type_variable())); self.push_err(TypeCheckError::TypeMismatchWithSource { expected: expected_type, @@ -64,9 +83,14 @@ impl Elaborator { let location = Location::new(span, self.file); HirPattern::Tuple(fields, location) } - Pattern::Struct(name, fields, span) => { - self.elaborate_struct_pattern(name, fields, span, pattern, expected_type, definition, mutable) - } + Pattern::Struct(name, fields, span) => self.elaborate_struct_pattern( + name, + fields, + span, + expected_type, + definition, + mutable, + ), } } @@ -75,7 +99,6 @@ impl Elaborator { name: Path, fields: Vec<(Ident, Pattern)>, span: Span, - pattern: Pattern, expected_type: Type, definition: DefinitionKind, mutable: Option, @@ -98,18 +121,25 @@ impl Elaborator { } }; - let actual_type = Type::Struct(struct_type, generics); + let actual_type = Type::Struct(struct_type.clone(), generics); let location = Location::new(span, self.file); self.unify(&actual_type, &expected_type, || TypeCheckError::TypeMismatchWithSource { expected: expected_type.clone(), - actual: actual_type, + actual: actual_type.clone(), span: location.span, source: Source::Assignment, }); let typ = struct_type.clone(); - let fields = self.resolve_constructor_pattern_fields(typ, fields, span, expected_type, definition, mutable); + let fields = self.resolve_constructor_pattern_fields( + typ, + fields, + span, + expected_type.clone(), + definition, + mutable, + ); HirPattern::Struct(expected_type, fields, location) } @@ -132,7 +162,8 @@ impl Elaborator { for (field, pattern) in fields { let field_type = expected_type.get_field_type(&field.0.contents).unwrap_or(Type::Error); - let resolved = self.elaborate_pattern_mut(pattern, field_type, definition, mutable); + let resolved = + self.elaborate_pattern_mut(pattern, field_type, definition.clone(), mutable); if unseen_fields.contains(&field) { unseen_fields.remove(&field); @@ -162,7 +193,7 @@ impl Elaborator { ret } - pub fn add_variable_decl( + pub(super) fn add_variable_decl( &mut self, name: Ident, mutable: bool, @@ -257,7 +288,7 @@ impl Elaborator { // // If a variable is not found, then an error is logged and a dummy id // is returned, for better error reporting UX - pub fn find_variable_or_default(&mut self, name: &Ident) -> (HirIdent, usize) { + pub(super) fn find_variable_or_default(&mut self, name: &Ident) -> (HirIdent, usize) { self.find_variable(name).unwrap_or_else(|error| { self.push_err(error); let id = DefinitionId::dummy_id(); @@ -266,7 +297,10 @@ impl Elaborator { }) } - pub fn find_variable(&mut self, name: &Ident) -> Result<(HirIdent, usize), ResolverError> { + pub(super) fn find_variable( + &mut self, + name: &Ident, + ) -> Result<(HirIdent, usize), ResolverError> { // Find the definition for this Ident let scope_tree = self.scopes.current_scope_tree(); let variable = scope_tree.find(&name.0.contents); @@ -284,7 +318,7 @@ impl Elaborator { } } - pub fn elaborate_variable(&mut self, variable: Path) -> (ExprId, Type) { + pub(super) fn elaborate_variable(&mut self, variable: Path) -> (ExprId, Type) { let span = variable.span; let expr = self.resolve_variable(variable); let id = self.interner.push_expr(HirExpression::Ident(expr.clone())); @@ -295,8 +329,7 @@ impl Elaborator { } fn resolve_variable(&mut self, path: Path) -> HirIdent { - if let Some((method, constraint, assumed)) = self.resolve_trait_generic_path(&path) - { + if let Some((method, constraint, assumed)) = self.resolve_trait_generic_path(&path) { HirIdent { location: Location::new(path.span, self.file), id: self.interner.trait_method_id(method), @@ -342,7 +375,7 @@ impl Elaborator { } } - fn type_check_variable(&mut self, ident: HirIdent, expr_id: ExprId) -> Type { + pub(super) fn type_check_variable(&mut self, ident: HirIdent, expr_id: ExprId) -> Type { let mut bindings = TypeBindings::new(); // Add type bindings from any constraints that were used. @@ -370,7 +403,7 @@ impl Elaborator { // This instantiates a trait's generics as well which need to be set // when the constraint below is later solved for when the function is // finished. How to link the two? - let (typ, bindings) = t.instantiate_with_bindings(bindings, &mut self.interner); + let (typ, bindings) = t.instantiate_with_bindings(bindings, &self.interner); // Push any trait constraints required by this definition to the context // to be checked later when the type of this variable is further constrained. diff --git a/compiler/noirc_frontend/src/elaborator/scope.rs b/compiler/noirc_frontend/src/elaborator/scope.rs index f8abc779ae7..21562a8eef7 100644 --- a/compiler/noirc_frontend/src/elaborator/scope.rs +++ b/compiler/noirc_frontend/src/elaborator/scope.rs @@ -1,7 +1,19 @@ use rustc_hash::FxHashMap as HashMap; -use crate::{macros_api::{StructId, Path}, node_interner::{TypeAliasId, DefinitionId, TraitId}, hir::{def_map::{TryFromModuleDefId, ModuleDefId}, resolution::errors::ResolverError}, Shared, StructType, hir_def::{traits::Trait, expr::{HirIdent, HirCapturedVar}}}; use crate::hir::comptime::Value; +use crate::{ + hir::{ + def_map::{ModuleDefId, TryFromModuleDefId}, + resolution::errors::ResolverError, + }, + hir_def::{ + expr::{HirCapturedVar, HirIdent}, + traits::Trait, + }, + macros_api::{Path, StructId}, + node_interner::{DefinitionId, TraitId, TypeAliasId}, + Shared, StructType, +}; use super::Elaborator; @@ -18,7 +30,7 @@ pub(super) enum TypeId { } impl Elaborator { - pub fn lookup(&mut self, path: Path) -> Result { + pub(super) fn lookup(&mut self, path: Path) -> Result { let span = path.span(); let id = self.resolve_path(path)?; T::try_from(id).ok_or_else(|| ResolverError::Expected { @@ -28,7 +40,7 @@ impl Elaborator { }) } - pub fn resolve_path(&mut self, path: Path) -> Result { + pub(super) fn resolve_path(&mut self, path: Path) -> Result { let path_resolution = self.path_resolver.resolve(&self.def_maps, path)?; if let Some(error) = path_resolution.error { @@ -38,15 +50,15 @@ impl Elaborator { Ok(path_resolution.module_def_id) } - pub fn get_struct(&self, type_id: StructId) -> Shared { + pub(super) fn get_struct(&self, type_id: StructId) -> Shared { self.interner.get_struct(type_id) } - pub fn get_trait_mut(&mut self, trait_id: TraitId) -> &mut Trait { + pub(super) fn get_trait_mut(&mut self, trait_id: TraitId) -> &mut Trait { self.interner.get_trait_mut(trait_id) } - pub fn resolve_local_variable(&mut self, hir_ident: HirIdent, var_scope_index: usize) { + pub(super) fn resolve_local_variable(&mut self, hir_ident: HirIdent, var_scope_index: usize) { let mut transitive_capture_index: Option = None; for lambda_index in 0..self.lambda_stack.len() { @@ -80,7 +92,7 @@ impl Elaborator { } } - pub fn lookup_global(&mut self, path: Path) -> Result { + pub(super) fn lookup_global(&mut self, path: Path) -> Result { let span = path.span(); let id = self.resolve_path(path)?; diff --git a/compiler/noirc_frontend/src/elaborator/statements.rs b/compiler/noirc_frontend/src/elaborator/statements.rs index a7e8a9f4bd9..06c4306fb74 100644 --- a/compiler/noirc_frontend/src/elaborator/statements.rs +++ b/compiler/noirc_frontend/src/elaborator/statements.rs @@ -1,11 +1,23 @@ -use noirc_errors::{Span, Location}; - -use crate::{macros_api::{LetStatement, HirStatement, ForLoopStatement, ForRange, Statement}, Type, node_interner::{DefinitionKind, DefinitionId}, hir::type_check::{TypeCheckError, Source}, hir_def::{stmt::{HirLetStatement, HirConstrainStatement, HirAssignStatement, HirForStatement, HirLValue}, expr::HirIdent}, ast::{ConstrainStatement, AssignStatement, LValue}}; +use noirc_errors::{Location, Span}; + +use crate::{ + ast::{AssignStatement, ConstrainStatement, LValue}, + hir::type_check::{Source, TypeCheckError}, + hir_def::{ + expr::HirIdent, + stmt::{ + HirAssignStatement, HirConstrainStatement, HirForStatement, HirLValue, HirLetStatement, + }, + }, + macros_api::{ForLoopStatement, ForRange, HirStatement, LetStatement, Statement}, + node_interner::{DefinitionId, DefinitionKind}, + Type, +}; use super::Elaborator; impl Elaborator { - pub fn elaborate_let(&self, let_stmt: LetStatement) -> (HirStatement, Type) { + pub(super) fn elaborate_let(&mut self, let_stmt: LetStatement) -> (HirStatement, Type) { let expr_span = let_stmt.expression.span; let (expression, expr_type) = self.elaborate_expression(let_stmt.expression); let definition = DefinitionKind::Local(Some(expression)); @@ -32,7 +44,7 @@ impl Elaborator { }; let let_ = HirLetStatement { - pattern: self.elaborate_pattern(let_stmt.pattern, r#type, definition), + pattern: self.elaborate_pattern(let_stmt.pattern, r#type.clone(), definition), r#type, expression, attributes: let_stmt.attributes, @@ -41,7 +53,7 @@ impl Elaborator { (HirStatement::Let(let_), Type::Unit) } - pub fn elaborate_constrain(&mut self, stmt: ConstrainStatement) -> (HirStatement, Type) { + pub(super) fn elaborate_constrain(&mut self, stmt: ConstrainStatement) -> (HirStatement, Type) { let expr_span = stmt.0.span; let (expr_id, expr_type) = self.elaborate_expression(stmt.0); @@ -57,7 +69,7 @@ impl Elaborator { (HirStatement::Constrain(HirConstrainStatement(expr_id, self.file, msg)), Type::Unit) } - pub fn elaborate_assign(&mut self, assign: AssignStatement) -> (HirStatement, Type) { + pub(super) fn elaborate_assign(&mut self, assign: AssignStatement) -> (HirStatement, Type) { let span = assign.expression.span; let (expression, expr_type) = self.elaborate_expression(assign.expression); let (lvalue, lvalue_type, mutable) = self.elaborate_lvalue(assign.lvalue, span); @@ -80,7 +92,7 @@ impl Elaborator { (HirStatement::Assign(stmt), Type::Unit) } - pub fn elaborate_for(&mut self, for_loop: ForLoopStatement) -> (HirStatement, Type) { + pub(super) fn elaborate_for(&mut self, for_loop: ForLoopStatement) -> (HirStatement, Type) { let (start, end) = match for_loop.range { ForRange::Range(start, end) => (start, end), ForRange::Array(_) => { @@ -129,12 +141,8 @@ impl Elaborator { self.pop_scope(); self.nested_loops -= 1; - let statement = HirStatement::For(HirForStatement { - start_range, - end_range, - block, - identifier, - }); + let statement = + HirStatement::For(HirForStatement { start_range, end_range, block, identifier }); (statement, Type::Unit) } @@ -170,7 +178,7 @@ impl Elaborator { mutable = definition.mutable; } - let typ = self.interner.definition_type(ident.id).instantiate(&mut self.interner).0; + let typ = self.interner.definition_type(ident.id).instantiate(&self.interner).0; typ.follow_bindings() }; @@ -215,12 +223,10 @@ impl Elaborator { let location = Location::new(span, self.file); let expected = self.polymorphic_integer_or_field(); - self.unify(&index_type, &expected, || { - TypeCheckError::TypeMismatch { - expected_typ: "an integer".to_owned(), - expr_typ: index_type.to_string(), - expr_span, - } + self.unify(&index_type, &expected, || TypeCheckError::TypeMismatch { + expected_typ: "an integer".to_owned(), + expr_typ: index_type.to_string(), + expr_span, }); let (mut lvalue, mut lvalue_type, mut mutable) = @@ -284,7 +290,7 @@ impl Elaborator { } /// Type checks a field access, adding dereference operators as necessary - pub fn check_field_access( + pub(super) fn check_field_access( &mut self, lhs_type: &Type, field_name: &str, @@ -351,7 +357,7 @@ impl Elaborator { None } - pub fn elaborate_comptime(&self, statement: Statement) -> (HirStatement, Type) { + pub(super) fn elaborate_comptime(&self, _statement: Statement) -> (HirStatement, Type) { todo!("Comptime scanning") } } diff --git a/compiler/noirc_frontend/src/elaborator/types.rs b/compiler/noirc_frontend/src/elaborator/types.rs index 51afad3210b..2dbec33c754 100644 --- a/compiler/noirc_frontend/src/elaborator/types.rs +++ b/compiler/noirc_frontend/src/elaborator/types.rs @@ -3,15 +3,37 @@ use std::rc::Rc; use iter_extended::vecmap; use noirc_errors::{Location, Span}; -use crate::{macros_api::{UnresolvedType, Path, SecondaryAttribute, PathKind, UnresolvedTypeData, HirExpression, HirLiteral, UnaryOp}, Type, hir::{resolution::{errors::ResolverError, resolver::SELF_TYPE_NAME, import::PathResolution}, def_map::ModuleDefId, type_check::TypeCheckError}, Generics, TypeVariable, ast::{UnresolvedTypeExpression, UnresolvedTraitConstraint, BinaryOpKind}, Shared, TypeAlias, StructType, hir_def::{traits::{Trait, TraitConstraint}, expr::HirPrefixExpression}, node_interner::{TraitMethodId, GlobalId, ExprId, DefinitionKind}, TypeVariableKind}; +use crate::{ + ast::{BinaryOpKind, IntegerBitSize, UnresolvedTraitConstraint, UnresolvedTypeExpression}, + hir::{ + def_map::ModuleDefId, + resolution::{ + errors::ResolverError, + import::PathResolution, + resolver::{verify_mutable_reference, SELF_TYPE_NAME}, + }, + type_check::{Source, TypeCheckError}, + }, + hir_def::{ + expr::{ + HirBinaryOp, HirCallExpression, HirMemberAccess, HirMethodReference, + HirPrefixExpression, + }, + traits::{Trait, TraitConstraint}, + }, + macros_api::{ + HirExpression, HirLiteral, Path, PathKind, SecondaryAttribute, Signedness, UnaryOp, + UnresolvedType, UnresolvedTypeData, + }, + node_interner::{DefinitionKind, ExprId, GlobalId, TraitImplKind, TraitMethodId}, + Generics, Shared, StructType, Type, TypeAlias, TypeBinding, TypeVariable, TypeVariableKind, +}; use super::Elaborator; - - impl Elaborator { /// Translates an UnresolvedType to a Type - pub fn resolve_type(&mut self, typ: UnresolvedType) -> Type { + pub(super) fn resolve_type(&mut self, typ: UnresolvedType) -> Type { let span = typ.span; let resolved_type = self.resolve_type_inner(typ, &mut vec![]); if resolved_type.is_nested_slice() { @@ -279,7 +301,7 @@ impl Elaborator { } } - pub fn convert_expression_type(&mut self, length: UnresolvedTypeExpression) -> Type { + pub(super) fn convert_expression_type(&mut self, length: UnresolvedTypeExpression) -> Type { match length { UnresolvedTypeExpression::Variable(path) => { self.lookup_generic_or_global_type(&path).unwrap_or_else(|| { @@ -332,7 +354,7 @@ impl Elaborator { /// Looks up a given type by name. /// This will also instantiate any struct types found. - pub fn lookup_type_or_error(&mut self, path: Path) -> Option { + pub(super) fn lookup_type_or_error(&mut self, path: Path) -> Option { let ident = path.as_ident(); if ident.map_or(false, |i| i == SELF_TYPE_NAME) { if let Some(typ) = &self.self_type { @@ -458,7 +480,7 @@ impl Elaborator { // // Returns the trait method, trait constraint, and whether the impl is assumed to exist by a where clause or not // E.g. `t.method()` with `where T: Foo` in scope will return `(Foo::method, T, vec![Bar])` - pub fn resolve_trait_generic_path( + pub(super) fn resolve_trait_generic_path( &mut self, path: &Path, ) -> Option<(TraitMethodId, TraitConstraint, bool)> { @@ -557,7 +579,7 @@ impl Elaborator { /// Check if an assignment is overflowing with respect to `annotated_type` /// in a declaration statement where `annotated_type` is an unsigned integer - pub fn lint_overflowing_uint(&mut self, rhs_expr: &ExprId, annotated_type: &Type) { + pub(super) fn lint_overflowing_uint(&mut self, rhs_expr: &ExprId, annotated_type: &Type) { let expr = self.interner.expression(rhs_expr); let span = self.interner.expr_span(rhs_expr); match expr { @@ -593,7 +615,7 @@ impl Elaborator { } } - pub fn unify( + pub(super) fn unify( &mut self, actual: &Type, expected: &Type, @@ -605,7 +627,7 @@ impl Elaborator { } /// Wrapper of Type::unify_with_coercions using self.errors - pub fn unify_with_coercions( + pub(super) fn unify_with_coercions( &mut self, actual: &Type, expected: &Type, @@ -625,7 +647,7 @@ impl Elaborator { /// Return a fresh integer or field type variable and log it /// in self.type_variables to default it later. - pub fn polymorphic_integer_or_field(&mut self) -> Type { + pub(super) fn polymorphic_integer_or_field(&mut self) -> Type { let typ = Type::polymorphic_integer_or_field(&mut self.interner); self.type_variables.push(typ.clone()); typ @@ -633,7 +655,7 @@ impl Elaborator { /// Return a fresh integer type variable and log it /// in self.type_variables to default it later. - pub fn polymorphic_integer(&mut self) -> Type { + pub(super) fn polymorphic_integer(&mut self) -> Type { let typ = Type::polymorphic_integer(&mut self.interner); self.type_variables.push(typ.clone()); typ @@ -641,21 +663,21 @@ impl Elaborator { /// Translates a (possibly Unspecified) UnresolvedType to a Type. /// Any UnresolvedType::Unspecified encountered are replaced with fresh type variables. - pub fn resolve_inferred_type(&mut self, typ: UnresolvedType) -> Type { + pub(super) fn resolve_inferred_type(&mut self, typ: UnresolvedType) -> Type { match &typ.typ { UnresolvedTypeData::Unspecified => self.interner.next_type_variable(), _ => self.resolve_type_inner(typ, &mut vec![]), } } - pub fn type_check_prefix_operand( + pub(super) fn type_check_prefix_operand( &mut self, op: &crate::ast::UnaryOp, rhs_type: &Type, span: Span, ) -> Type { - let mut unify = |expected| { - self.unify(rhs_type, &expected, || TypeCheckError::TypeMismatch { + let mut unify = |this: &mut Self, expected| { + this.unify(rhs_type, &expected, || TypeCheckError::TypeMismatch { expr_typ: rhs_type.to_string(), expected_typ: expected.to_string(), expr_span: span, @@ -666,7 +688,10 @@ impl Elaborator { match op { crate::ast::UnaryOp::Minus => { if rhs_type.is_unsigned() { - self.push_err(TypeCheckError::InvalidUnaryOp { kind: rhs_type.to_string(), span }); + self.push_err(TypeCheckError::InvalidUnaryOp { + kind: rhs_type.to_string(), + span, + }); } let expected = self.polymorphic_integer_or_field(); self.unify(rhs_type, &expected, || TypeCheckError::InvalidUnaryOp { @@ -683,14 +708,14 @@ impl Elaborator { return rhs_type; } - unify(Type::Bool) + unify(self, Type::Bool) } crate::ast::UnaryOp::MutableReference => { Type::MutableReference(Box::new(rhs_type.follow_bindings())) } crate::ast::UnaryOp::Dereference { implicitly_added: _ } => { let element_type = self.interner.next_type_variable(); - unify(Type::MutableReference(Box::new(element_type.clone()))); + unify(self, Type::MutableReference(Box::new(element_type.clone()))); element_type } } @@ -698,7 +723,7 @@ impl Elaborator { /// Insert as many dereference operations as necessary to automatically dereference a method /// call object to its base value type T. - pub fn insert_auto_dereferences(&mut self, object: ExprId, typ: Type) -> (ExprId, Type) { + pub(super) fn insert_auto_dereferences(&mut self, object: ExprId, typ: Type) -> (ExprId, Type) { if let Type::MutableReference(element) = typ { let location = self.interner.id_location(object); @@ -740,4 +765,597 @@ impl Elaborator { _ => None, } } + + fn bind_function_type_impl( + &mut self, + fn_params: &[Type], + fn_ret: &Type, + callsite_args: &[(Type, ExprId, Span)], + span: Span, + ) -> Type { + if fn_params.len() != callsite_args.len() { + self.push_err(TypeCheckError::ParameterCountMismatch { + expected: fn_params.len(), + found: callsite_args.len(), + span, + }); + return Type::Error; + } + + for (param, (arg, _, arg_span)) in fn_params.iter().zip(callsite_args) { + self.unify(arg, param, || TypeCheckError::TypeMismatch { + expected_typ: param.to_string(), + expr_typ: arg.to_string(), + expr_span: *arg_span, + }); + } + + fn_ret.clone() + } + + pub(super) fn bind_function_type( + &mut self, + function: Type, + args: Vec<(Type, ExprId, Span)>, + span: Span, + ) -> Type { + // Could do a single unification for the entire function type, but matching beforehand + // lets us issue a more precise error on the individual argument that fails to type check. + match function { + Type::TypeVariable(binding, TypeVariableKind::Normal) => { + if let TypeBinding::Bound(typ) = &*binding.borrow() { + return self.bind_function_type(typ.clone(), args, span); + } + + let ret = self.interner.next_type_variable(); + let args = vecmap(args, |(arg, _, _)| arg); + let env_type = self.interner.next_type_variable(); + let expected = Type::Function(args, Box::new(ret.clone()), Box::new(env_type)); + + if let Err(error) = binding.try_bind(expected, span) { + self.push_err(error); + } + ret + } + // ignoring env for subtype on purpose + Type::Function(parameters, ret, _env) => { + self.bind_function_type_impl(¶meters, &ret, &args, span) + } + Type::Error => Type::Error, + found => { + self.push_err(TypeCheckError::ExpectedFunction { found, span }); + Type::Error + } + } + } + + pub(super) fn check_cast(&mut self, from: Type, to: &Type, span: Span) -> Type { + match from.follow_bindings() { + Type::Integer(..) + | Type::FieldElement + | Type::TypeVariable(_, TypeVariableKind::IntegerOrField) + | Type::TypeVariable(_, TypeVariableKind::Integer) + | Type::Bool => (), + + Type::TypeVariable(_, _) => { + self.push_err(TypeCheckError::TypeAnnotationsNeeded { span }); + return Type::Error; + } + Type::Error => return Type::Error, + from => { + self.push_err(TypeCheckError::InvalidCast { from, span }); + return Type::Error; + } + } + + match to { + Type::Integer(sign, bits) => Type::Integer(*sign, *bits), + Type::FieldElement => Type::FieldElement, + Type::Bool => Type::Bool, + Type::Error => Type::Error, + _ => { + self.push_err(TypeCheckError::UnsupportedCast { span }); + Type::Error + } + } + } + + // Given a binary comparison operator and another type. This method will produce the output type + // and a boolean indicating whether to use the trait impl corresponding to the operator + // or not. A value of false indicates the caller to use a primitive operation for this + // operator, while a true value indicates a user-provided trait impl is required. + fn comparator_operand_type_rules( + &mut self, + lhs_type: &Type, + rhs_type: &Type, + op: &HirBinaryOp, + span: Span, + ) -> Result<(Type, bool), TypeCheckError> { + use Type::*; + + match (lhs_type, rhs_type) { + // Avoid reporting errors multiple times + (Error, _) | (_, Error) => Ok((Bool, false)), + (Alias(alias, args), other) | (other, Alias(alias, args)) => { + let alias = alias.borrow().get_type(args); + self.comparator_operand_type_rules(&alias, other, op, span) + } + + // Matches on TypeVariable must be first to follow any type + // bindings. + (TypeVariable(var, _), other) | (other, TypeVariable(var, _)) => { + if let TypeBinding::Bound(binding) = &*var.borrow() { + return self.comparator_operand_type_rules(other, binding, op, span); + } + + let use_impl = self.bind_type_variables_for_infix(lhs_type, op, rhs_type, span); + Ok((Bool, use_impl)) + } + (Integer(sign_x, bit_width_x), Integer(sign_y, bit_width_y)) => { + if sign_x != sign_y { + return Err(TypeCheckError::IntegerSignedness { + sign_x: *sign_x, + sign_y: *sign_y, + span, + }); + } + if bit_width_x != bit_width_y { + return Err(TypeCheckError::IntegerBitWidth { + bit_width_x: *bit_width_x, + bit_width_y: *bit_width_y, + span, + }); + } + Ok((Bool, false)) + } + (FieldElement, FieldElement) => { + if op.kind.is_valid_for_field_type() { + Ok((Bool, false)) + } else { + Err(TypeCheckError::FieldComparison { span }) + } + } + + // <= and friends are technically valid for booleans, just not very useful + (Bool, Bool) => Ok((Bool, false)), + + (lhs, rhs) => { + self.unify(lhs, rhs, || TypeCheckError::TypeMismatchWithSource { + expected: lhs.clone(), + actual: rhs.clone(), + span: op.location.span, + source: Source::Binary, + }); + Ok((Bool, true)) + } + } + } + + /// Handles the TypeVariable case for checking binary operators. + /// Returns true if we should use the impl for the operator instead of the primitive + /// version of it. + fn bind_type_variables_for_infix( + &mut self, + lhs_type: &Type, + op: &HirBinaryOp, + rhs_type: &Type, + span: Span, + ) -> bool { + self.unify(lhs_type, rhs_type, || TypeCheckError::TypeMismatchWithSource { + expected: lhs_type.clone(), + actual: rhs_type.clone(), + source: Source::Binary, + span, + }); + + let use_impl = !lhs_type.is_numeric(); + + // If this operator isn't valid for fields we have to possibly narrow + // TypeVariableKind::IntegerOrField to TypeVariableKind::Integer. + // Doing so also ensures a type error if Field is used. + // The is_numeric check is to allow impls for custom types to bypass this. + if !op.kind.is_valid_for_field_type() && lhs_type.is_numeric() { + let target = Type::polymorphic_integer(&mut self.interner); + + use crate::ast::BinaryOpKind::*; + use TypeCheckError::*; + self.unify(lhs_type, &target, || match op.kind { + Less | LessEqual | Greater | GreaterEqual => FieldComparison { span }, + And | Or | Xor | ShiftRight | ShiftLeft => FieldBitwiseOp { span }, + Modulo => FieldModulo { span }, + other => unreachable!("Operator {other:?} should be valid for Field"), + }); + } + + use_impl + } + + // Given a binary operator and another type. This method will produce the output type + // and a boolean indicating whether to use the trait impl corresponding to the operator + // or not. A value of false indicates the caller to use a primitive operation for this + // operator, while a true value indicates a user-provided trait impl is required. + pub(super) fn infix_operand_type_rules( + &mut self, + lhs_type: &Type, + op: &HirBinaryOp, + rhs_type: &Type, + span: Span, + ) -> Result<(Type, bool), TypeCheckError> { + if op.kind.is_comparator() { + return self.comparator_operand_type_rules(lhs_type, rhs_type, op, span); + } + + use Type::*; + match (lhs_type, rhs_type) { + // An error type on either side will always return an error + (Error, _) | (_, Error) => Ok((Error, false)), + (Alias(alias, args), other) | (other, Alias(alias, args)) => { + let alias = alias.borrow().get_type(args); + self.infix_operand_type_rules(&alias, op, other, span) + } + + // Matches on TypeVariable must be first so that we follow any type + // bindings. + (TypeVariable(int, _), other) | (other, TypeVariable(int, _)) => { + if let TypeBinding::Bound(binding) = &*int.borrow() { + return self.infix_operand_type_rules(binding, op, other, span); + } + if op.kind == BinaryOpKind::ShiftLeft || op.kind == BinaryOpKind::ShiftRight { + self.unify( + rhs_type, + &Type::Integer(Signedness::Unsigned, IntegerBitSize::Eight), + || TypeCheckError::InvalidShiftSize { span }, + ); + let use_impl = if lhs_type.is_numeric() { + let integer_type = Type::polymorphic_integer(&mut self.interner); + self.bind_type_variables_for_infix(lhs_type, op, &integer_type, span) + } else { + true + }; + return Ok((lhs_type.clone(), use_impl)); + } + let use_impl = self.bind_type_variables_for_infix(lhs_type, op, rhs_type, span); + Ok((other.clone(), use_impl)) + } + (Integer(sign_x, bit_width_x), Integer(sign_y, bit_width_y)) => { + if op.kind == BinaryOpKind::ShiftLeft || op.kind == BinaryOpKind::ShiftRight { + if *sign_y != Signedness::Unsigned || *bit_width_y != IntegerBitSize::Eight { + return Err(TypeCheckError::InvalidShiftSize { span }); + } + return Ok((Integer(*sign_x, *bit_width_x), false)); + } + if sign_x != sign_y { + return Err(TypeCheckError::IntegerSignedness { + sign_x: *sign_x, + sign_y: *sign_y, + span, + }); + } + if bit_width_x != bit_width_y { + return Err(TypeCheckError::IntegerBitWidth { + bit_width_x: *bit_width_x, + bit_width_y: *bit_width_y, + span, + }); + } + Ok((Integer(*sign_x, *bit_width_x), false)) + } + // The result of two Fields is always a witness + (FieldElement, FieldElement) => { + if !op.kind.is_valid_for_field_type() { + if op.kind == BinaryOpKind::Modulo { + return Err(TypeCheckError::FieldModulo { span }); + } else { + return Err(TypeCheckError::FieldBitwiseOp { span }); + } + } + Ok((FieldElement, false)) + } + + (Bool, Bool) => Ok((Bool, false)), + + (lhs, rhs) => { + if op.kind == BinaryOpKind::ShiftLeft || op.kind == BinaryOpKind::ShiftRight { + if rhs == &Type::Integer(Signedness::Unsigned, IntegerBitSize::Eight) { + return Ok((lhs.clone(), true)); + } + return Err(TypeCheckError::InvalidShiftSize { span }); + } + self.unify(lhs, rhs, || TypeCheckError::TypeMismatchWithSource { + expected: lhs.clone(), + actual: rhs.clone(), + span: op.location.span, + source: Source::Binary, + }); + Ok((lhs.clone(), true)) + } + } + } + + /// Prerequisite: verify_trait_constraint of the operator's trait constraint. + /// + /// Although by this point the operator is expected to already have a trait impl, + /// we still need to match the operator's type against the method's instantiated type + /// to ensure the instantiation bindings are correct and the monomorphizer can + /// re-apply the needed bindings. + pub(super) fn type_check_operator_method( + &mut self, + expr_id: ExprId, + trait_method_id: TraitMethodId, + object_type: &Type, + span: Span, + ) { + let the_trait = self.interner.get_trait(trait_method_id.trait_id); + + let method = &the_trait.methods[trait_method_id.method_index]; + let (method_type, mut bindings) = method.typ.clone().instantiate(&self.interner); + + match method_type { + Type::Function(args, _, _) => { + // We can cheat a bit and match against only the object type here since no operator + // overload uses other generic parameters or return types aside from the object type. + let expected_object_type = &args[0]; + self.unify(object_type, expected_object_type, || TypeCheckError::TypeMismatch { + expected_typ: expected_object_type.to_string(), + expr_typ: object_type.to_string(), + expr_span: span, + }); + } + other => { + unreachable!("Expected operator method to have a function type, but found {other}") + } + } + + // We must also remember to apply these substitutions to the object_type + // referenced by the selected trait impl, if one has yet to be selected. + let impl_kind = self.interner.get_selected_impl_for_expression(expr_id); + if let Some(TraitImplKind::Assumed { object_type, trait_generics }) = impl_kind { + let the_trait = self.interner.get_trait(trait_method_id.trait_id); + let object_type = object_type.substitute(&bindings); + bindings.insert( + the_trait.self_type_typevar_id, + (the_trait.self_type_typevar.clone(), object_type.clone()), + ); + self.interner.select_impl_for_expression( + expr_id, + TraitImplKind::Assumed { object_type, trait_generics }, + ); + } + + self.interner.store_instantiation_bindings(expr_id, bindings); + } + + pub(super) fn type_check_member_access( + &mut self, + mut access: HirMemberAccess, + expr_id: ExprId, + lhs_type: Type, + span: Span, + ) -> Type { + let access_lhs = &mut access.lhs; + + let dereference_lhs = |this: &mut Self, lhs_type, element| { + let old_lhs = *access_lhs; + *access_lhs = this.interner.push_expr(HirExpression::Prefix(HirPrefixExpression { + operator: crate::ast::UnaryOp::Dereference { implicitly_added: true }, + rhs: old_lhs, + })); + this.interner.push_expr_type(old_lhs, lhs_type); + this.interner.push_expr_type(*access_lhs, element); + + let old_location = this.interner.id_location(old_lhs); + this.interner.push_expr_location(*access_lhs, span, old_location.file); + }; + + // If this access is just a field offset, we want to avoid dereferencing + let dereference_lhs = (!access.is_offset).then_some(dereference_lhs); + + match self.check_field_access(&lhs_type, &access.rhs.0.contents, span, dereference_lhs) { + Some((element_type, index)) => { + self.interner.set_field_index(expr_id, index); + // We must update `access` in case we added any dereferences to it + self.interner.replace_expr(&expr_id, HirExpression::MemberAccess(access)); + element_type + } + None => Type::Error, + } + } + + pub(super) fn lookup_method( + &mut self, + object_type: &Type, + method_name: &str, + span: Span, + ) -> Option { + match object_type.follow_bindings() { + Type::Struct(typ, _args) => { + let id = typ.borrow().id; + match self.interner.lookup_method(object_type, id, method_name, false) { + Some(method_id) => Some(HirMethodReference::FuncId(method_id)), + None => { + self.push_err(TypeCheckError::UnresolvedMethodCall { + method_name: method_name.to_string(), + object_type: object_type.clone(), + span, + }); + None + } + } + } + // TODO: We should allow method calls on `impl Trait`s eventually. + // For now it is fine since they are only allowed on return types. + Type::TraitAsType(..) => { + self.push_err(TypeCheckError::UnresolvedMethodCall { + method_name: method_name.to_string(), + object_type: object_type.clone(), + span, + }); + None + } + Type::NamedGeneric(_, _) => { + let func_meta = self.interner.function_meta( + &self.current_function.expect("unexpected method outside a function"), + ); + + for constraint in &func_meta.trait_constraints { + if *object_type == constraint.typ { + if let Some(the_trait) = self.interner.try_get_trait(constraint.trait_id) { + for (method_index, method) in the_trait.methods.iter().enumerate() { + if method.name.0.contents == method_name { + let trait_method = TraitMethodId { + trait_id: constraint.trait_id, + method_index, + }; + return Some(HirMethodReference::TraitMethodId( + trait_method, + constraint.trait_generics.clone(), + )); + } + } + } + } + } + + self.push_err(TypeCheckError::UnresolvedMethodCall { + method_name: method_name.to_string(), + object_type: object_type.clone(), + span, + }); + None + } + // Mutable references to another type should resolve to methods of their element type. + // This may be a struct or a primitive type. + Type::MutableReference(element) => self + .interner + .lookup_primitive_trait_method_mut(element.as_ref(), method_name) + .map(HirMethodReference::FuncId) + .or_else(|| self.lookup_method(&element, method_name, span)), + + // If we fail to resolve the object to a struct type, we have no way of type + // checking its arguments as we can't even resolve the name of the function + Type::Error => None, + + // The type variable must be unbound at this point since follow_bindings was called + Type::TypeVariable(_, TypeVariableKind::Normal) => { + self.push_err(TypeCheckError::TypeAnnotationsNeeded { span }); + None + } + + other => match self.interner.lookup_primitive_method(&other, method_name) { + Some(method_id) => Some(HirMethodReference::FuncId(method_id)), + None => { + self.push_err(TypeCheckError::UnresolvedMethodCall { + method_name: method_name.to_string(), + object_type: object_type.clone(), + span, + }); + None + } + }, + } + } + + pub(super) fn type_check_call( + &mut self, + call: &HirCallExpression, + func_type: Type, + args: Vec<(Type, ExprId, Span)>, + span: Span, + ) -> Type { + // Need to setup these flags here as `self` is borrowed mutably to type check the rest of the call expression + // These flags are later used to type check calls to unconstrained functions from constrained functions + let func_mod = self.current_function.map(|func| self.interner.function_modifiers(&func)); + let is_current_func_constrained = + func_mod.map_or(true, |func_mod| !func_mod.is_unconstrained); + + let is_unconstrained_call = self.is_unconstrained_call(call.func); + self.check_if_deprecated(call.func); + + // Check that we are not passing a mutable reference from a constrained runtime to an unconstrained runtime + if is_current_func_constrained && is_unconstrained_call { + for (typ, _, _) in args.iter() { + if matches!(&typ.follow_bindings(), Type::MutableReference(_)) { + self.push_err(TypeCheckError::ConstrainedReferenceToUnconstrained { span }); + } + } + } + + let return_type = self.bind_function_type(func_type, args, span); + + // Check that we are not passing a slice from an unconstrained runtime to a constrained runtime + if is_current_func_constrained && is_unconstrained_call { + if return_type.contains_slice() { + self.push_err(TypeCheckError::UnconstrainedSliceReturnToConstrained { span }); + } else if matches!(&return_type.follow_bindings(), Type::MutableReference(_)) { + self.push_err(TypeCheckError::UnconstrainedReferenceToConstrained { span }); + } + }; + + return_type + } + + /// Check if the given method type requires a mutable reference to the object type, and check + /// if the given object type is already a mutable reference. If not, add one. + /// This is used to automatically transform a method call: `foo.bar()` into a function + /// call: `bar(&mut foo)`. + /// + /// A notable corner case of this function is where it interacts with auto-deref of `.`. + /// If a field is being mutated e.g. `foo.bar.mutate_bar()` where `foo: &mut Foo`, the compiler + /// will insert a dereference before bar `(*foo).bar.mutate_bar()` which would cause us to + /// mutate a copy of bar rather than a reference to it. We must check for this corner case here + /// and remove the implicitly added dereference operator if we find one. + pub(super) fn try_add_mutable_reference_to_object( + &mut self, + function_type: &Type, + object_type: &mut Type, + object: &mut ExprId, + ) { + let expected_object_type = match function_type { + Type::Function(args, _, _) => args.first(), + Type::Forall(_, typ) => match typ.as_ref() { + Type::Function(args, _, _) => args.first(), + typ => unreachable!("Unexpected type for function: {typ}"), + }, + typ => unreachable!("Unexpected type for function: {typ}"), + }; + + if let Some(expected_object_type) = expected_object_type { + let actual_type = object_type.follow_bindings(); + + if matches!(expected_object_type.follow_bindings(), Type::MutableReference(_)) { + if !matches!(actual_type, Type::MutableReference(_)) { + if let Err(error) = verify_mutable_reference(&self.interner, *object) { + self.push_err(TypeCheckError::ResolverError(error)); + } + + let new_type = Type::MutableReference(Box::new(actual_type)); + *object_type = new_type.clone(); + + // First try to remove a dereference operator that may have been implicitly + // inserted by a field access expression `foo.bar` on a mutable reference `foo`. + let new_object = self.try_remove_implicit_dereference(*object); + + // If that didn't work, then wrap the whole expression in an `&mut` + *object = new_object.unwrap_or_else(|| { + let location = self.interner.id_location(*object); + + let new_object = + self.interner.push_expr(HirExpression::Prefix(HirPrefixExpression { + operator: UnaryOp::MutableReference, + rhs: *object, + })); + self.interner.push_expr_type(new_object, new_type); + self.interner.push_expr_location(new_object, location.span, location.file); + new_object + }); + } + // Otherwise if the object type is a mutable reference and the method is not, insert as + // many dereferences as needed. + } else if matches!(actual_type, Type::MutableReference(_)) { + let (new_object, new_type) = self.insert_auto_dereferences(*object, actual_type); + *object_type = new_type; + *object = new_object; + } + } + } } diff --git a/compiler/noirc_frontend/src/hir/type_check/expr.rs b/compiler/noirc_frontend/src/hir/type_check/expr.rs index 9b40c959981..48598109829 100644 --- a/compiler/noirc_frontend/src/hir/type_check/expr.rs +++ b/compiler/noirc_frontend/src/hir/type_check/expr.rs @@ -250,14 +250,14 @@ impl<'interner> TypeChecker<'interner> { } // TODO: update object_type here? - let function_call = method_call.into_function_call( + let (_, function_call) = method_call.into_function_call( &method_ref, object_type, location, self.interner, ); - self.interner.replace_expr(expr_id, function_call); + self.interner.replace_expr(expr_id, HirExpression::Call(function_call)); // Type check the new call now that it has been changed from a method call // to a function call. This way we avoid duplicating code. diff --git a/compiler/noirc_frontend/src/hir_def/expr.rs b/compiler/noirc_frontend/src/hir_def/expr.rs index bf7d9b7b4ba..8df6785e0eb 100644 --- a/compiler/noirc_frontend/src/hir_def/expr.rs +++ b/compiler/noirc_frontend/src/hir_def/expr.rs @@ -200,13 +200,15 @@ pub enum HirMethodReference { impl HirMethodCallExpression { /// Converts a method call into a function call + /// + /// Returns ((func_var_id, func_var), call_expr) pub fn into_function_call( mut self, method: &HirMethodReference, object_type: Type, location: Location, interner: &mut NodeInterner, - ) -> HirExpression { + ) -> ((ExprId, HirIdent), HirCallExpression) { let mut arguments = vec![self.object]; arguments.append(&mut self.arguments); @@ -224,10 +226,11 @@ impl HirMethodCallExpression { (id, ImplKind::TraitMethod(*method_id, constraint, false)) } }; - let func = HirExpression::Ident(HirIdent { location, id, impl_kind }); - let func = interner.push_expr(func); + let func_var = HirIdent { location, id, impl_kind }; + let func = interner.push_expr(HirExpression::Ident(func_var.clone())); interner.push_expr_location(func, location.span, location.file); - HirExpression::Call(HirCallExpression { func, arguments, location }) + let expr = HirCallExpression { func, arguments, location }; + ((func, func_var), expr) } } From 6b300283910b7f91c905498b4ad8cfc74ebb7780 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Wed, 8 May 2024 10:30:35 -0500 Subject: [PATCH 04/38] Add compiler flag --- compiler/noirc_driver/src/lib.rs | 11 +++- .../src/hir/def_collector/dc_crate.rs | 57 ++++++++++++------- .../noirc_frontend/src/hir/def_map/mod.rs | 3 +- tooling/lsp/src/notifications/mod.rs | 4 +- tooling/lsp/src/requests/code_lens_request.rs | 2 +- tooling/lsp/src/requests/goto_declaration.rs | 2 +- tooling/lsp/src/requests/goto_definition.rs | 2 +- tooling/lsp/src/requests/test_run.rs | 2 +- tooling/lsp/src/requests/tests.rs | 2 +- tooling/nargo_cli/src/cli/check_cmd.rs | 4 +- tooling/nargo_cli/src/cli/export_cmd.rs | 1 + tooling/nargo_cli/src/cli/test_cmd.rs | 2 + 12 files changed, 60 insertions(+), 32 deletions(-) diff --git a/compiler/noirc_driver/src/lib.rs b/compiler/noirc_driver/src/lib.rs index cd5a1c4a7fb..e3277fd78ec 100644 --- a/compiler/noirc_driver/src/lib.rs +++ b/compiler/noirc_driver/src/lib.rs @@ -103,6 +103,10 @@ pub struct CompileOptions { /// Force Brillig output (for step debugging) #[arg(long, hide = true)] pub force_brillig: bool, + + /// Enable the experimental elaborator pass + #[arg(long, hide = true)] + pub use_elaborator: bool, } fn parse_expression_width(input: &str) -> Result { @@ -245,12 +249,13 @@ pub fn check_crate( crate_id: CrateId, deny_warnings: bool, disable_macros: bool, + use_elaborator: bool, ) -> CompilationResult<()> { let macros: &[&dyn MacroProcessor] = if disable_macros { &[] } else { &[&aztec_macros::AztecMacro as &dyn MacroProcessor] }; let mut errors = vec![]; - let diagnostics = CrateDefMap::collect_defs(crate_id, context, macros); + let diagnostics = CrateDefMap::collect_defs(crate_id, context, use_elaborator, macros); errors.extend(diagnostics.into_iter().map(|(error, file_id)| { let diagnostic = CustomDiagnostic::from(&error); diagnostic.in_file(file_id) @@ -283,7 +288,7 @@ pub fn compile_main( cached_program: Option, ) -> CompilationResult { let (_, mut warnings) = - check_crate(context, crate_id, options.deny_warnings, options.disable_macros)?; + check_crate(context, crate_id, options.deny_warnings, options.disable_macros, options.use_elaborator)?; let main = context.get_main_function(&crate_id).ok_or_else(|| { // TODO(#2155): This error might be a better to exist in Nargo @@ -319,7 +324,7 @@ pub fn compile_contract( options: &CompileOptions, ) -> CompilationResult { let (_, warnings) = - check_crate(context, crate_id, options.deny_warnings, options.disable_macros)?; + check_crate(context, crate_id, options.deny_warnings, options.disable_macros, options.use_elaborator)?; // TODO: We probably want to error if contracts is empty let contracts = context.get_all_contracts(&crate_id); diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index 2f6b101e62f..860e0408a67 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -38,6 +38,10 @@ pub struct ResolvedModule { pub trait_impl_functions: Vec<(FileId, FuncId)>, pub errors: Vec<(CompilationError, FileId)>, + + /// True if the experimental elaborator should be used over the normal + /// name resolution and type checking passes. + pub use_elaborator: bool, } /// Stores all of the unresolved functions in a particular file/mod @@ -229,6 +233,7 @@ impl DefCollector { context: &mut Context, ast: SortedModule, root_file_id: FileId, + use_elaborator: bool, macro_processors: &[&dyn MacroProcessor], ) -> Vec<(CompilationError, FileId)> { let mut errors: Vec<(CompilationError, FileId)> = vec![]; @@ -242,7 +247,7 @@ impl DefCollector { let crate_graph = &context.crate_graph[crate_id]; for dep in crate_graph.dependencies.clone() { - errors.extend(CrateDefMap::collect_defs(dep.crate_id, context, macro_processors)); + errors.extend(CrateDefMap::collect_defs(dep.crate_id, context, use_elaborator, macro_processors)); let dep_def_root = context.def_map(&dep.crate_id).expect("ice: def map was just created").root; @@ -323,7 +328,7 @@ impl DefCollector { } } - let mut resolved_module = ResolvedModule { errors, ..Default::default() }; + let mut resolved_module = ResolvedModule { errors, use_elaborator, ..Default::default() }; // We must first resolve and intern the globals before we can resolve any stmts inside each function. // Each function uses its own resolver with a newly created ScopeForest, and must be resolved again to be within a function's scope @@ -481,34 +486,42 @@ fn filter_literal_globals( impl ResolvedModule { fn type_check(&mut self, context: &mut Context) { - self.type_check_globals(&mut context.def_interner); - self.type_check_functions(&mut context.def_interner); - self.type_check_trait_impl_function(&mut context.def_interner); + if !self.use_elaborator { + self.type_check_globals(&mut context.def_interner); + self.type_check_functions(&mut context.def_interner); + self.type_check_trait_impl_function(&mut context.def_interner); + } } fn type_check_globals(&mut self, interner: &mut NodeInterner) { - for (file_id, global_id) in self.globals.iter() { - for error in TypeChecker::check_global(*global_id, interner) { - self.errors.push((error.into(), *file_id)); + if !self.use_elaborator { + for (file_id, global_id) in self.globals.iter() { + for error in TypeChecker::check_global(*global_id, interner) { + self.errors.push((error.into(), *file_id)); + } } } } fn type_check_functions(&mut self, interner: &mut NodeInterner) { - for (file, func) in self.functions.iter() { - for error in type_check_func(interner, *func) { - self.errors.push((error.into(), *file)); + if !self.use_elaborator { + for (file, func) in self.functions.iter() { + for error in type_check_func(interner, *func) { + self.errors.push((error.into(), *file)); + } } } } fn type_check_trait_impl_function(&mut self, interner: &mut NodeInterner) { - for (file, func) in self.trait_impl_functions.iter() { - for error in check_trait_impl_method_matches_declaration(interner, *func) { - self.errors.push((error.into(), *file)); - } - for error in type_check_func(interner, *func) { - self.errors.push((error.into(), *file)); + if !self.use_elaborator { + for (file, func) in self.trait_impl_functions.iter() { + for error in check_trait_impl_method_matches_declaration(interner, *func) { + self.errors.push((error.into(), *file)); + } + for error in type_check_func(interner, *func) { + self.errors.push((error.into(), *file)); + } } } } @@ -540,9 +553,13 @@ impl ResolvedModule { literal_globals: Vec, crate_id: CrateId, ) { - let globals = resolve_globals(context, literal_globals, crate_id); - self.globals.extend(globals.globals); - self.errors.extend(globals.errors); + if self.use_elaborator { + let mut elaborator = Elaborator::new(context); + } else { + let globals = resolve_globals(context, literal_globals, crate_id); + self.globals.extend(globals.globals); + self.errors.extend(globals.errors); + } } /// Counts the number of errors (minus warnings) this program currently has diff --git a/compiler/noirc_frontend/src/hir/def_map/mod.rs b/compiler/noirc_frontend/src/hir/def_map/mod.rs index 590c2e3d6b6..976a359bcd7 100644 --- a/compiler/noirc_frontend/src/hir/def_map/mod.rs +++ b/compiler/noirc_frontend/src/hir/def_map/mod.rs @@ -73,6 +73,7 @@ impl CrateDefMap { pub fn collect_defs( crate_id: CrateId, context: &mut Context, + use_elaborator: bool, macro_processors: &[&dyn MacroProcessor], ) -> Vec<(CompilationError, FileId)> { // Check if this Crate has already been compiled @@ -116,7 +117,7 @@ impl CrateDefMap { }; // Now we want to populate the CrateDefMap using the DefCollector - errors.extend(DefCollector::collect(def_map, context, ast, root_file_id, macro_processors)); + errors.extend(DefCollector::collect(def_map, context, ast, root_file_id, use_elaborator, macro_processors)); errors.extend( parsing_errors.iter().map(|e| (e.clone().into(), root_file_id)).collect::>(), diff --git a/tooling/lsp/src/notifications/mod.rs b/tooling/lsp/src/notifications/mod.rs index 355bb7832c4..3856bdc79e9 100644 --- a/tooling/lsp/src/notifications/mod.rs +++ b/tooling/lsp/src/notifications/mod.rs @@ -56,7 +56,7 @@ pub(super) fn on_did_change_text_document( state.input_files.insert(params.text_document.uri.to_string(), text.clone()); let (mut context, crate_id) = prepare_source(text, state); - let _ = check_crate(&mut context, crate_id, false, false); + let _ = check_crate(&mut context, crate_id, false, false, false); let workspace = match resolve_workspace_for_source_path( params.text_document.uri.to_file_path().unwrap().as_path(), @@ -139,7 +139,7 @@ fn process_noir_document( let (mut context, crate_id) = prepare_package(&workspace_file_manager, &parsed_files, package); - let file_diagnostics = match check_crate(&mut context, crate_id, false, false) { + let file_diagnostics = match check_crate(&mut context, crate_id, false, false, false) { Ok(((), warnings)) => warnings, Err(errors_and_warnings) => errors_and_warnings, }; diff --git a/tooling/lsp/src/requests/code_lens_request.rs b/tooling/lsp/src/requests/code_lens_request.rs index 893ba33d845..744bddedd9d 100644 --- a/tooling/lsp/src/requests/code_lens_request.rs +++ b/tooling/lsp/src/requests/code_lens_request.rs @@ -67,7 +67,7 @@ fn on_code_lens_request_inner( let (mut context, crate_id) = prepare_source(source_string, state); // We ignore the warnings and errors produced by compilation for producing code lenses // because we can still get the test functions even if compilation fails - let _ = check_crate(&mut context, crate_id, false, false); + let _ = check_crate(&mut context, crate_id, false, false, false); let collected_lenses = collect_lenses_for_package(&context, crate_id, &workspace, package, None); diff --git a/tooling/lsp/src/requests/goto_declaration.rs b/tooling/lsp/src/requests/goto_declaration.rs index 8e6d519b895..5cff16b2348 100644 --- a/tooling/lsp/src/requests/goto_declaration.rs +++ b/tooling/lsp/src/requests/goto_declaration.rs @@ -46,7 +46,7 @@ fn on_goto_definition_inner( interner = def_interner; } else { // We ignore the warnings and errors produced by compilation while resolving the definition - let _ = noirc_driver::check_crate(&mut context, crate_id, false, false); + let _ = noirc_driver::check_crate(&mut context, crate_id, false, false, false); interner = &context.def_interner; } diff --git a/tooling/lsp/src/requests/goto_definition.rs b/tooling/lsp/src/requests/goto_definition.rs index 88bb667f2e8..32e13ce00f6 100644 --- a/tooling/lsp/src/requests/goto_definition.rs +++ b/tooling/lsp/src/requests/goto_definition.rs @@ -54,7 +54,7 @@ fn on_goto_definition_inner( interner = def_interner; } else { // We ignore the warnings and errors produced by compilation while resolving the definition - let _ = noirc_driver::check_crate(&mut context, crate_id, false, false); + let _ = noirc_driver::check_crate(&mut context, crate_id, false, false, false); interner = &context.def_interner; } diff --git a/tooling/lsp/src/requests/test_run.rs b/tooling/lsp/src/requests/test_run.rs index 1844a3d9bf0..83b05ba06a2 100644 --- a/tooling/lsp/src/requests/test_run.rs +++ b/tooling/lsp/src/requests/test_run.rs @@ -60,7 +60,7 @@ fn on_test_run_request_inner( Some(package) => { let (mut context, crate_id) = prepare_package(&workspace_file_manager, &parsed_files, package); - if check_crate(&mut context, crate_id, false, false).is_err() { + if check_crate(&mut context, crate_id, false, false, false).is_err() { let result = NargoTestRunResult { id: params.id.clone(), result: "error".to_string(), diff --git a/tooling/lsp/src/requests/tests.rs b/tooling/lsp/src/requests/tests.rs index 5b78fcc65c3..cdf4ad338c4 100644 --- a/tooling/lsp/src/requests/tests.rs +++ b/tooling/lsp/src/requests/tests.rs @@ -61,7 +61,7 @@ fn on_tests_request_inner( prepare_package(&workspace_file_manager, &parsed_files, package); // We ignore the warnings and errors produced by compilation for producing tests // because we can still get the test functions even if compilation fails - let _ = check_crate(&mut context, crate_id, false, false); + let _ = check_crate(&mut context, crate_id, false, false, false); // We don't add test headings for a package if it contains no `#[test]` functions get_package_tests_in_crate(&context, &crate_id, &package.name) diff --git a/tooling/nargo_cli/src/cli/check_cmd.rs b/tooling/nargo_cli/src/cli/check_cmd.rs index 208379b098d..d5313d96076 100644 --- a/tooling/nargo_cli/src/cli/check_cmd.rs +++ b/tooling/nargo_cli/src/cli/check_cmd.rs @@ -87,6 +87,7 @@ fn check_package( compile_options.deny_warnings, compile_options.disable_macros, compile_options.silence_warnings, + compile_options.use_elaborator, )?; if package.is_library() || package.is_contract() { @@ -173,8 +174,9 @@ pub(crate) fn check_crate_and_report_errors( deny_warnings: bool, disable_macros: bool, silence_warnings: bool, + use_elaborator: bool, ) -> Result<(), CompileError> { - let result = check_crate(context, crate_id, deny_warnings, disable_macros); + let result = check_crate(context, crate_id, deny_warnings, disable_macros, use_elaborator); report_errors(result, &context.file_manager, deny_warnings, silence_warnings) } diff --git a/tooling/nargo_cli/src/cli/export_cmd.rs b/tooling/nargo_cli/src/cli/export_cmd.rs index a61f3ccfc02..324eed340ad 100644 --- a/tooling/nargo_cli/src/cli/export_cmd.rs +++ b/tooling/nargo_cli/src/cli/export_cmd.rs @@ -89,6 +89,7 @@ fn compile_exported_functions( compile_options.deny_warnings, compile_options.disable_macros, compile_options.silence_warnings, + compile_options.use_elaborator, )?; let exported_functions = context.get_all_exported_functions_in_crate(&crate_id); diff --git a/tooling/nargo_cli/src/cli/test_cmd.rs b/tooling/nargo_cli/src/cli/test_cmd.rs index 967d4c87e6d..51e21248afd 100644 --- a/tooling/nargo_cli/src/cli/test_cmd.rs +++ b/tooling/nargo_cli/src/cli/test_cmd.rs @@ -175,6 +175,7 @@ fn run_test( crate_id, compile_options.deny_warnings, compile_options.disable_macros, + compile_options.use_elaborator, ) .expect("Any errors should have occurred when collecting test functions"); @@ -208,6 +209,7 @@ fn get_tests_in_package( compile_options.deny_warnings, compile_options.disable_macros, compile_options.silence_warnings, + compile_options.use_elaborator, )?; Ok(context From 7550fb804eb6e19d04999cd99d3d54b0dde68a77 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Wed, 8 May 2024 10:38:52 -0500 Subject: [PATCH 05/38] Move expressions into their own file --- .../src/elaborator/expressions.rs | 604 ++++++++++++++++ compiler/noirc_frontend/src/elaborator/mod.rs | 642 +----------------- .../src/elaborator/statements.rs | 52 +- .../noirc_frontend/src/elaborator/types.rs | 33 +- 4 files changed, 686 insertions(+), 645 deletions(-) create mode 100644 compiler/noirc_frontend/src/elaborator/expressions.rs diff --git a/compiler/noirc_frontend/src/elaborator/expressions.rs b/compiler/noirc_frontend/src/elaborator/expressions.rs new file mode 100644 index 00000000000..19ee67b442c --- /dev/null +++ b/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -0,0 +1,604 @@ +use iter_extended::vecmap; +use noirc_errors::{Location, Span}; +use regex::Regex; +use rustc_hash::FxHashSet as HashSet; + +use crate::{ + ast::{ + ArrayLiteral, ConstructorExpression, IfExpression, InfixExpression, Lambda, + UnresolvedTypeExpression, + }, + hir::{ + resolution::{errors::ResolverError, resolver::LambdaContext}, + type_check::TypeCheckError, + }, + hir_def::{ + expr::{ + HirArrayLiteral, HirBinaryOp, HirBlockExpression, HirCallExpression, HirCastExpression, + HirConstructorExpression, HirIdent, HirIfExpression, HirIndexExpression, + HirInfixExpression, HirLambda, HirMemberAccess, HirMethodCallExpression, + HirMethodReference, HirPrefixExpression, + }, + traits::TraitConstraint, + }, + macros_api::{ + BlockExpression, CallExpression, CastExpression, Expression, ExpressionKind, HirExpression, + HirLiteral, HirStatement, Ident, IndexExpression, Literal, MemberAccessExpression, + MethodCallExpression, PrefixExpression, + }, + node_interner::{DefinitionKind, ExprId, FuncId}, + Shared, StructType, Type, +}; + +use super::Elaborator; + +impl Elaborator { + pub(super) fn elaborate_expression(&mut self, expr: Expression) -> (ExprId, Type) { + let (hir_expr, typ) = match expr.kind { + ExpressionKind::Literal(literal) => self.elaborate_literal(literal, expr.span), + ExpressionKind::Block(block) => self.elaborate_block(block), + ExpressionKind::Prefix(prefix) => self.elaborate_prefix(*prefix), + ExpressionKind::Index(index) => self.elaborate_index(*index), + ExpressionKind::Call(call) => self.elaborate_call(*call, expr.span), + ExpressionKind::MethodCall(call) => self.elaborate_method_call(*call, expr.span), + ExpressionKind::Constructor(constructor) => self.elaborate_constructor(*constructor), + ExpressionKind::MemberAccess(access) => { + return self.elaborate_member_access(*access, expr.span) + } + ExpressionKind::Cast(cast) => self.elaborate_cast(*cast, expr.span), + ExpressionKind::Infix(infix) => return self.elaborate_infix(*infix, expr.span), + ExpressionKind::If(if_) => self.elaborate_if(*if_), + ExpressionKind::Variable(variable) => return self.elaborate_variable(variable), + ExpressionKind::Tuple(tuple) => self.elaborate_tuple(tuple), + ExpressionKind::Lambda(lambda) => self.elaborate_lambda(*lambda), + ExpressionKind::Parenthesized(expr) => return self.elaborate_expression(*expr), + ExpressionKind::Quote(quote) => self.elaborate_quote(quote), + ExpressionKind::Comptime(comptime) => self.elaborate_comptime_block(comptime), + ExpressionKind::Error => (HirExpression::Error, Type::Error), + }; + let id = self.interner.push_expr(hir_expr); + self.interner.push_expr_location(id, expr.span, self.file); + self.interner.push_expr_type(id, typ.clone()); + (id, typ) + } + + pub(super) fn elaborate_block(&mut self, block: BlockExpression) -> (HirExpression, Type) { + self.push_scope(); + let mut block_type = Type::Unit; + let mut statements = Vec::with_capacity(block.statements.len()); + + for (i, statement) in block.statements.into_iter().enumerate() { + let (id, stmt_type) = self.elaborate_statement(statement); + statements.push(id); + + if let HirStatement::Semi(expr) = self.interner.statement(&id) { + let inner_expr_type = self.interner.id_type(expr); + let span = self.interner.expr_span(&expr); + + self.unify(&inner_expr_type, &Type::Unit, || TypeCheckError::UnusedResultError { + expr_type: inner_expr_type.clone(), + expr_span: span, + }); + + if i + 1 == statements.len() { + block_type = stmt_type; + } + } + } + + self.pop_scope(); + (HirExpression::Block(HirBlockExpression { statements }), block_type) + } + + fn elaborate_literal(&mut self, literal: Literal, span: Span) -> (HirExpression, Type) { + use HirExpression::Literal as Lit; + match literal { + Literal::Unit => (Lit(HirLiteral::Unit), Type::Unit), + Literal::Bool(b) => (Lit(HirLiteral::Bool(b)), Type::Bool), + Literal::Integer(integer, sign) => { + let int = HirLiteral::Integer(integer, sign); + (Lit(int), self.polymorphic_integer_or_field()) + } + Literal::Str(str) | Literal::RawStr(str, _) => { + let len = Type::Constant(str.len() as u64); + (Lit(HirLiteral::Str(str)), Type::String(Box::new(len))) + } + Literal::FmtStr(str) => self.elaborate_fmt_string(str, span), + Literal::Array(array_literal) => { + self.elaborate_array_literal(array_literal, span, true) + } + Literal::Slice(array_literal) => { + self.elaborate_array_literal(array_literal, span, false) + } + } + } + + fn elaborate_array_literal( + &mut self, + array_literal: ArrayLiteral, + span: Span, + is_array: bool, + ) -> (HirExpression, Type) { + let (expr, elem_type, length) = match array_literal { + ArrayLiteral::Standard(elements) => { + let first_elem_type = self.interner.next_type_variable(); + let first_span = elements.first().map(|elem| elem.span).unwrap_or(span); + + let elements = vecmap(elements.into_iter().enumerate(), |(i, elem)| { + let span = elem.span; + let (elem_id, elem_type) = self.elaborate_expression(elem); + + self.unify(&elem_type, &first_elem_type, || { + TypeCheckError::NonHomogeneousArray { + first_span, + first_type: first_elem_type.to_string(), + first_index: 0, + second_span: span, + second_type: elem_type.to_string(), + second_index: i, + } + .add_context("elements in an array must have the same type") + }); + elem_id + }); + + let length = Type::Constant(elements.len() as u64); + (HirArrayLiteral::Standard(elements), first_elem_type, length) + } + ArrayLiteral::Repeated { repeated_element, length } => { + let span = length.span; + let length = + UnresolvedTypeExpression::from_expr(*length, span).unwrap_or_else(|error| { + self.push_err(ResolverError::ParserError(Box::new(error))); + UnresolvedTypeExpression::Constant(0, span) + }); + + let length = self.convert_expression_type(length); + let (repeated_element, elem_type) = self.elaborate_expression(*repeated_element); + + let length_clone = length.clone(); + (HirArrayLiteral::Repeated { repeated_element, length }, elem_type, length_clone) + } + }; + let constructor = if is_array { HirLiteral::Array } else { HirLiteral::Slice }; + let elem_type = Box::new(elem_type); + let typ = if is_array { + Type::Array(Box::new(length), elem_type) + } else { + Type::Slice(elem_type) + }; + (HirExpression::Literal(constructor(expr)), typ) + } + + fn elaborate_fmt_string(&mut self, str: String, call_expr_span: Span) -> (HirExpression, Type) { + let re = Regex::new(r"\{([a-zA-Z0-9_]+)\}") + .expect("ICE: an invalid regex pattern was used for checking format strings"); + + let mut fmt_str_idents = Vec::new(); + let mut capture_types = Vec::new(); + + for field in re.find_iter(&str) { + let matched_str = field.as_str(); + let ident_name = &matched_str[1..(matched_str.len() - 1)]; + + let scope_tree = self.scopes.current_scope_tree(); + let variable = scope_tree.find(ident_name); + if let Some((old_value, _)) = variable { + old_value.num_times_used += 1; + let ident = HirExpression::Ident(old_value.ident.clone()); + let expr_id = self.interner.push_expr(ident); + self.interner.push_expr_location(expr_id, call_expr_span, self.file); + let ident = old_value.ident.clone(); + let typ = self.type_check_variable(ident, expr_id); + self.interner.push_expr_type(expr_id, typ.clone()); + capture_types.push(typ); + fmt_str_idents.push(expr_id); + } else if ident_name.parse::().is_ok() { + self.push_err(ResolverError::NumericConstantInFormatString { + name: ident_name.to_owned(), + span: call_expr_span, + }); + } else { + self.push_err(ResolverError::VariableNotDeclared { + name: ident_name.to_owned(), + span: call_expr_span, + }); + } + } + + let len = Type::Constant(str.len() as u64); + let typ = Type::FmtString(Box::new(len), Box::new(Type::Tuple(capture_types))); + (HirExpression::Literal(HirLiteral::FmtStr(str, fmt_str_idents)), typ) + } + + fn elaborate_prefix(&mut self, prefix: PrefixExpression) -> (HirExpression, Type) { + let span = prefix.rhs.span; + let (rhs, rhs_type) = self.elaborate_expression(prefix.rhs); + let ret_type = self.type_check_prefix_operand(&prefix.operator, &rhs_type, span); + (HirExpression::Prefix(HirPrefixExpression { operator: prefix.operator, rhs }), ret_type) + } + + 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); + + let expected = self.polymorphic_integer_or_field(); + self.unify(&index_type, &expected, || TypeCheckError::TypeMismatch { + expected_typ: "an integer".to_owned(), + expr_typ: index_type.to_string(), + expr_span: span, + }); + + // When writing `a[i]`, if `a : &mut ...` then automatically dereference `a` as many + // times as needed to get the underlying array. + let lhs_span = index_expr.collection.span; + let (lhs, lhs_type) = self.elaborate_expression(index_expr.collection); + let (collection, lhs_type) = self.insert_auto_dereferences(lhs, lhs_type); + + let typ = match lhs_type.follow_bindings() { + // XXX: We can check the array bounds here also, but it may be better to constant fold first + // and have ConstId instead of ExprId for constants + Type::Array(_, base_type) => *base_type, + Type::Slice(base_type) => *base_type, + Type::Error => Type::Error, + typ => { + self.push_err(TypeCheckError::TypeMismatch { + expected_typ: "Array".to_owned(), + expr_typ: typ.to_string(), + expr_span: lhs_span, + }); + Type::Error + } + }; + + let expr = HirExpression::Index(HirIndexExpression { collection, index }); + (expr, typ) + } + + fn elaborate_call(&mut self, call: CallExpression, span: Span) -> (HirExpression, Type) { + let (func, func_type) = self.elaborate_expression(*call.func); + + let mut arguments = Vec::with_capacity(call.arguments.len()); + let args = vecmap(call.arguments, |arg| { + let span = arg.span; + let (arg, typ) = self.elaborate_expression(arg); + arguments.push(arg); + (typ, arg, span) + }); + + let location = Location::new(span, self.file); + let call = HirCallExpression { func, arguments, location }; + let typ = self.type_check_call(&call, func_type, args, span); + (HirExpression::Call(call), typ) + } + + fn elaborate_method_call( + &mut self, + method_call: MethodCallExpression, + span: Span, + ) -> (HirExpression, Type) { + let object_span = method_call.object.span; + let (mut object, mut object_type) = self.elaborate_expression(method_call.object); + object_type = object_type.follow_bindings(); + + let method_name = method_call.method_name.0.contents.as_str(); + match self.lookup_method(&object_type, method_name, span) { + Some(method_ref) => { + // Automatically add `&mut` if the method expects a mutable reference and + // the object is not already one. + if let HirMethodReference::FuncId(func_id) = &method_ref { + if *func_id != FuncId::dummy_id() { + let function_type = self.interner.function_meta(func_id).typ.clone(); + + self.try_add_mutable_reference_to_object( + &function_type, + &mut object_type, + &mut object, + ); + } + } + + // These arguments will be given to the desugared function call. + // Compared to the method arguments, they also contain the object. + let mut function_args = Vec::with_capacity(method_call.arguments.len() + 1); + let mut arguments = Vec::with_capacity(method_call.arguments.len()); + + function_args.push((object_type.clone(), object, object_span)); + + for arg in method_call.arguments { + let span = arg.span; + let (arg, typ) = self.elaborate_expression(arg); + arguments.push(arg); + function_args.push((typ, arg, span)); + } + + let location = Location::new(span, self.file); + let method = method_call.method_name; + let method_call = HirMethodCallExpression { method, object, arguments, location }; + + // Desugar the method call into a normal, resolved function call + // so that the backend doesn't need to worry about methods + // TODO: update object_type here? + let ((function_id, function_name), function_call) = method_call.into_function_call( + &method_ref, + object_type, + location, + &mut self.interner, + ); + + let func_type = self.type_check_variable(function_name, function_id); + + // Type check the new call now that it has been changed from a method call + // to a function call. This way we avoid duplicating code. + let typ = self.type_check_call(&function_call, func_type, function_args, span); + (HirExpression::Call(function_call), typ) + } + None => (HirExpression::Error, Type::Error), + } + } + + fn elaborate_constructor( + &mut self, + constructor: ConstructorExpression, + ) -> (HirExpression, Type) { + let span = constructor.type_name.span(); + + match self.lookup_type_or_error(constructor.type_name) { + Some(Type::Struct(r#type, struct_generics)) => { + let struct_type = r#type.clone(); + let generics = struct_generics.clone(); + + let fields = constructor.fields; + let field_types = r#type.borrow().get_fields(&struct_generics); + let fields = self.resolve_constructor_expr_fields( + struct_type.clone(), + field_types, + fields, + span, + ); + let expr = HirExpression::Constructor(HirConstructorExpression { + fields, + r#type, + struct_generics, + }); + (expr, Type::Struct(struct_type, generics)) + } + Some(typ) => { + self.push_err(ResolverError::NonStructUsedInConstructor { typ, span }); + (HirExpression::Error, Type::Error) + } + None => (HirExpression::Error, Type::Error), + } + } + + /// Resolve all the fields of a struct constructor expression. + /// Ensures all fields are present, none are repeated, and all + /// are part of the struct. + fn resolve_constructor_expr_fields( + &mut self, + struct_type: Shared, + field_types: Vec<(String, Type)>, + fields: Vec<(Ident, Expression)>, + span: Span, + ) -> Vec<(Ident, ExprId)> { + let mut ret = Vec::with_capacity(fields.len()); + let mut seen_fields = HashSet::default(); + let mut unseen_fields = struct_type.borrow().field_names(); + + for (field_name, field) in fields { + let expected_type = field_types.iter().find(|(name, _)| name == &field_name.0.contents); + let expected_type = expected_type.map(|(_, typ)| typ).unwrap_or(&Type::Error); + + let field_span = field.span; + let (resolved, field_type) = self.elaborate_expression(field); + + if unseen_fields.contains(&field_name) { + unseen_fields.remove(&field_name); + seen_fields.insert(field_name.clone()); + + self.unify_with_coercions(&field_type, expected_type, resolved, || { + TypeCheckError::TypeMismatch { + expected_typ: expected_type.to_string(), + expr_typ: field_type.to_string(), + expr_span: field_span, + } + }); + } else if seen_fields.contains(&field_name) { + // duplicate field + self.push_err(ResolverError::DuplicateField { field: field_name.clone() }); + } else { + // field not required by struct + self.push_err(ResolverError::NoSuchField { + field: field_name.clone(), + struct_definition: struct_type.borrow().name.clone(), + }); + } + + ret.push((field_name, resolved)); + } + + if !unseen_fields.is_empty() { + self.push_err(ResolverError::MissingFields { + span, + missing_fields: unseen_fields.into_iter().map(|field| field.to_string()).collect(), + struct_definition: struct_type.borrow().name.clone(), + }); + } + + ret + } + + fn elaborate_member_access( + &mut self, + access: MemberAccessExpression, + span: Span, + ) -> (ExprId, Type) { + let (lhs, lhs_type) = self.elaborate_expression(access.lhs); + let rhs = access.rhs; + // `is_offset` is only used when lhs is a reference and we want to return a reference to rhs + let access = HirMemberAccess { lhs, rhs, is_offset: false }; + let expr_id = self.intern_expr(HirExpression::MemberAccess(access.clone()), span); + let typ = self.type_check_member_access(access, expr_id, lhs_type, span); + self.interner.push_expr_type(expr_id, typ.clone()); + (expr_id, typ) + } + + fn intern_expr(&mut self, expr: HirExpression, span: Span) -> ExprId { + let id = self.interner.push_expr(expr); + self.interner.push_expr_location(id, span, self.file); + id + } + + fn elaborate_cast(&mut self, cast: CastExpression, span: Span) -> (HirExpression, Type) { + let (lhs, lhs_type) = self.elaborate_expression(cast.lhs); + let r#type = self.resolve_type(cast.r#type); + let result = self.check_cast(lhs_type, &r#type, span); + let expr = HirExpression::Cast(HirCastExpression { lhs, r#type }); + (expr, result) + } + + fn elaborate_infix(&mut self, infix: InfixExpression, span: Span) -> (ExprId, Type) { + let (lhs, lhs_type) = self.elaborate_expression(infix.lhs); + let (rhs, rhs_type) = self.elaborate_expression(infix.rhs); + let trait_id = self.interner.get_operator_trait_method(infix.operator.contents); + + let operator = HirBinaryOp::new(infix.operator, self.file); + let expr = HirExpression::Infix(HirInfixExpression { + lhs, + operator, + trait_method_id: trait_id, + rhs, + }); + + let expr_id = self.interner.push_expr(expr); + self.interner.push_expr_location(expr_id, span, self.file); + + let typ = match self.infix_operand_type_rules(&lhs_type, &operator, &rhs_type, span) { + Ok((typ, use_impl)) => { + if use_impl { + // Delay checking the trait constraint until the end of the function. + // Checking it now could bind an unbound type variable to any type + // that implements the trait. + let constraint = TraitConstraint { + typ: lhs_type.clone(), + trait_id: trait_id.trait_id, + trait_generics: Vec::new(), + }; + self.trait_constraints.push((constraint, expr_id)); + self.type_check_operator_method(expr_id, trait_id, &lhs_type, span); + } + typ + } + Err(error) => { + self.push_err(error); + Type::Error + } + }; + + self.interner.push_expr_type(expr_id, typ.clone()); + (expr_id, typ) + } + + fn elaborate_if(&mut self, if_expr: IfExpression) -> (HirExpression, Type) { + let expr_span = if_expr.condition.span; + let (condition, cond_type) = self.elaborate_expression(if_expr.condition); + let (consequence, mut ret_type) = self.elaborate_expression(if_expr.consequence); + + self.unify(&cond_type, &Type::Bool, || TypeCheckError::TypeMismatch { + expected_typ: Type::Bool.to_string(), + expr_typ: cond_type.to_string(), + expr_span, + }); + + let alternative = if_expr.alternative.map(|alternative| { + let expr_span = alternative.span; + let (else_, else_type) = self.elaborate_expression(alternative); + + self.unify(&ret_type, &else_type, || { + let err = TypeCheckError::TypeMismatch { + expected_typ: ret_type.to_string(), + expr_typ: else_type.to_string(), + expr_span, + }; + + let context = if ret_type == Type::Unit { + "Are you missing a semicolon at the end of your 'else' branch?" + } else if else_type == Type::Unit { + "Are you missing a semicolon at the end of the first block of this 'if'?" + } else { + "Expected the types of both if branches to be equal" + }; + + err.add_context(context) + }); + else_ + }); + + if alternative.is_none() { + ret_type = Type::Unit; + } + + let if_expr = HirIfExpression { condition, consequence, alternative }; + (HirExpression::If(if_expr), ret_type) + } + + fn elaborate_tuple(&mut self, tuple: Vec) -> (HirExpression, Type) { + let mut element_ids = Vec::with_capacity(tuple.len()); + let mut element_types = Vec::with_capacity(tuple.len()); + + for element in tuple { + let (id, typ) = self.elaborate_expression(element); + element_ids.push(id); + element_types.push(typ); + } + + (HirExpression::Tuple(element_ids), Type::Tuple(element_types)) + } + + fn elaborate_lambda(&mut self, lambda: Lambda) -> (HirExpression, Type) { + self.push_scope(); + let scope_index = self.scopes.current_scope_index(); + + self.lambda_stack.push(LambdaContext { captures: Vec::new(), scope_index }); + + let mut arg_types = Vec::with_capacity(lambda.parameters.len()); + let parameters = vecmap(lambda.parameters, |(pattern, typ)| { + let parameter = DefinitionKind::Local(None); + let typ = self.resolve_inferred_type(typ); + arg_types.push(typ.clone()); + (self.elaborate_pattern(pattern, typ.clone(), parameter), typ) + }); + + let return_type = self.resolve_inferred_type(lambda.return_type); + let body_span = lambda.body.span; + let (body, body_type) = self.elaborate_expression(lambda.body); + + let lambda_context = self.lambda_stack.pop().unwrap(); + self.pop_scope(); + + self.unify(&body_type, &return_type, || TypeCheckError::TypeMismatch { + expected_typ: return_type.to_string(), + expr_typ: body_type.to_string(), + expr_span: body_span, + }); + + let captured_vars = vecmap(&lambda_context.captures, |capture| { + self.interner.definition_type(capture.ident.id) + }); + + let env_type = + if captured_vars.is_empty() { Type::Unit } else { Type::Tuple(captured_vars) }; + + let captures = lambda_context.captures; + let expr = HirExpression::Lambda(HirLambda { parameters, return_type, body, captures }); + (expr, Type::Function(arg_types, Box::new(body_type), Box::new(env_type))) + } + + fn elaborate_quote(&mut self, block: BlockExpression) -> (HirExpression, Type) { + (HirExpression::Quote(block), Type::Code) + } + + fn elaborate_comptime_block(&mut self, _comptime: BlockExpression) -> (HirExpression, Type) { + todo!("Elaborate comptime block") + } +} diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index ab197fd026b..a621f01ed79 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -36,6 +36,7 @@ use crate::{ Shared, StructType, Type, TypeVariable, }; +mod expressions; mod patterns; mod scope; mod statements; @@ -147,92 +148,6 @@ impl Elaborator { } } - fn elaborate_expression(&mut self, expr: Expression) -> (ExprId, Type) { - let (hir_expr, typ) = match expr.kind { - ExpressionKind::Literal(literal) => self.elaborate_literal(literal, expr.span), - ExpressionKind::Block(block) => self.elaborate_block(block), - ExpressionKind::Prefix(prefix) => self.elaborate_prefix(*prefix), - ExpressionKind::Index(index) => self.elaborate_index(*index), - ExpressionKind::Call(call) => self.elaborate_call(*call, expr.span), - ExpressionKind::MethodCall(call) => self.elaborate_method_call(*call, expr.span), - ExpressionKind::Constructor(constructor) => self.elaborate_constructor(*constructor), - ExpressionKind::MemberAccess(access) => { - return self.elaborate_member_access(*access, expr.span) - } - ExpressionKind::Cast(cast) => self.elaborate_cast(*cast, expr.span), - ExpressionKind::Infix(infix) => return self.elaborate_infix(*infix, expr.span), - ExpressionKind::If(if_) => self.elaborate_if(*if_), - ExpressionKind::Variable(variable) => return self.elaborate_variable(variable), - ExpressionKind::Tuple(tuple) => self.elaborate_tuple(tuple), - ExpressionKind::Lambda(lambda) => self.elaborate_lambda(*lambda), - ExpressionKind::Parenthesized(expr) => return self.elaborate_expression(*expr), - ExpressionKind::Quote(quote) => self.elaborate_quote(quote), - ExpressionKind::Comptime(comptime) => self.elaborate_comptime_block(comptime), - ExpressionKind::Error => (HirExpression::Error, Type::Error), - }; - let id = self.interner.push_expr(hir_expr); - self.interner.push_expr_location(id, expr.span, self.file); - self.interner.push_expr_type(id, typ.clone()); - (id, typ) - } - - fn elaborate_statement_value(&mut self, statement: Statement) -> (HirStatement, Type) { - match statement.kind { - StatementKind::Let(let_stmt) => self.elaborate_let(let_stmt), - StatementKind::Constrain(constrain) => self.elaborate_constrain(constrain), - StatementKind::Assign(assign) => self.elaborate_assign(assign), - StatementKind::For(for_stmt) => self.elaborate_for(for_stmt), - StatementKind::Break => self.elaborate_jump(true, statement.span), - StatementKind::Continue => self.elaborate_jump(false, statement.span), - StatementKind::Comptime(statement) => self.elaborate_comptime(*statement), - StatementKind::Expression(expr) => { - let (expr, typ) = self.elaborate_expression(expr); - (HirStatement::Expression(expr), typ) - } - StatementKind::Semi(expr) => { - let (expr, _typ) = self.elaborate_expression(expr); - (HirStatement::Semi(expr), Type::Unit) - } - StatementKind::Error => (HirStatement::Error, Type::Error), - } - } - - fn elaborate_statement(&mut self, statement: Statement) -> (StmtId, Type) { - let span = statement.span; - let (hir_statement, typ) = self.elaborate_statement_value(statement); - let id = self.interner.push_stmt(hir_statement); - self.interner.push_stmt_location(id, span, self.file); - (id, typ) - } - - fn elaborate_block(&mut self, block: BlockExpression) -> (HirExpression, Type) { - self.push_scope(); - let mut block_type = Type::Unit; - let mut statements = Vec::with_capacity(block.statements.len()); - - for (i, statement) in block.statements.into_iter().enumerate() { - let (id, stmt_type) = self.elaborate_statement(statement); - statements.push(id); - - if let HirStatement::Semi(expr) = self.interner.statement(&id) { - let inner_expr_type = self.interner.id_type(expr); - let span = self.interner.expr_span(&expr); - - self.unify(&inner_expr_type, &Type::Unit, || TypeCheckError::UnusedResultError { - expr_type: inner_expr_type.clone(), - expr_span: span, - }); - - if i + 1 == statements.len() { - block_type = stmt_type; - } - } - } - - self.pop_scope(); - (HirExpression::Block(HirBlockExpression { statements }), block_type) - } - fn push_scope(&mut self) { self.local_scopes.push(Scope::default()); } @@ -241,562 +156,7 @@ impl Elaborator { self.local_scopes.pop(); } - fn elaborate_jump(&mut self, is_break: bool, span: noirc_errors::Span) -> (HirStatement, Type) { - if !self.in_unconstrained_fn { - self.push_err(ResolverError::JumpInConstrainedFn { is_break, span }); - } - if self.nested_loops == 0 { - self.push_err(ResolverError::JumpOutsideLoop { is_break, span }); - } - - let expr = if is_break { HirStatement::Break } else { HirStatement::Continue }; - (expr, self.interner.next_type_variable()) - } - fn push_err(&mut self, error: impl Into) { self.errors.push(error.into()); } - - fn elaborate_literal(&mut self, literal: Literal, span: Span) -> (HirExpression, Type) { - use HirExpression::Literal as Lit; - match literal { - Literal::Unit => (Lit(HirLiteral::Unit), Type::Unit), - Literal::Bool(b) => (Lit(HirLiteral::Bool(b)), Type::Bool), - Literal::Integer(integer, sign) => { - let int = HirLiteral::Integer(integer, sign); - (Lit(int), self.polymorphic_integer_or_field()) - } - Literal::Str(str) | Literal::RawStr(str, _) => { - let len = Type::Constant(str.len() as u64); - (Lit(HirLiteral::Str(str)), Type::String(Box::new(len))) - } - Literal::FmtStr(str) => self.elaborate_fmt_string(str, span), - Literal::Array(array_literal) => { - self.elaborate_array_literal(array_literal, span, true) - } - Literal::Slice(array_literal) => { - self.elaborate_array_literal(array_literal, span, false) - } - } - } - - fn elaborate_array_literal( - &mut self, - array_literal: ArrayLiteral, - span: Span, - is_array: bool, - ) -> (HirExpression, Type) { - let (expr, elem_type, length) = match array_literal { - ArrayLiteral::Standard(elements) => { - let first_elem_type = self.interner.next_type_variable(); - let first_span = elements.first().map(|elem| elem.span).unwrap_or(span); - - let elements = vecmap(elements.into_iter().enumerate(), |(i, elem)| { - let span = elem.span; - let (elem_id, elem_type) = self.elaborate_expression(elem); - - self.unify(&elem_type, &first_elem_type, || { - TypeCheckError::NonHomogeneousArray { - first_span, - first_type: first_elem_type.to_string(), - first_index: 0, - second_span: span, - second_type: elem_type.to_string(), - second_index: i, - } - .add_context("elements in an array must have the same type") - }); - elem_id - }); - - let length = Type::Constant(elements.len() as u64); - (HirArrayLiteral::Standard(elements), first_elem_type, length) - } - ArrayLiteral::Repeated { repeated_element, length } => { - let span = length.span; - let length = - UnresolvedTypeExpression::from_expr(*length, span).unwrap_or_else(|error| { - self.push_err(ResolverError::ParserError(Box::new(error))); - UnresolvedTypeExpression::Constant(0, span) - }); - - let length = self.convert_expression_type(length); - let (repeated_element, elem_type) = self.elaborate_expression(*repeated_element); - - let length_clone = length.clone(); - (HirArrayLiteral::Repeated { repeated_element, length }, elem_type, length_clone) - } - }; - let constructor = if is_array { HirLiteral::Array } else { HirLiteral::Slice }; - let elem_type = Box::new(elem_type); - let typ = if is_array { - Type::Array(Box::new(length), elem_type) - } else { - Type::Slice(elem_type) - }; - (HirExpression::Literal(constructor(expr)), typ) - } - - fn elaborate_fmt_string(&mut self, str: String, call_expr_span: Span) -> (HirExpression, Type) { - let re = Regex::new(r"\{([a-zA-Z0-9_]+)\}") - .expect("ICE: an invalid regex pattern was used for checking format strings"); - - let mut fmt_str_idents = Vec::new(); - let mut capture_types = Vec::new(); - - for field in re.find_iter(&str) { - let matched_str = field.as_str(); - let ident_name = &matched_str[1..(matched_str.len() - 1)]; - - let scope_tree = self.scopes.current_scope_tree(); - let variable = scope_tree.find(ident_name); - if let Some((old_value, _)) = variable { - old_value.num_times_used += 1; - let ident = HirExpression::Ident(old_value.ident.clone()); - let expr_id = self.interner.push_expr(ident); - self.interner.push_expr_location(expr_id, call_expr_span, self.file); - let ident = old_value.ident.clone(); - let typ = self.type_check_variable(ident, expr_id); - self.interner.push_expr_type(expr_id, typ.clone()); - capture_types.push(typ); - fmt_str_idents.push(expr_id); - } else if ident_name.parse::().is_ok() { - self.push_err(ResolverError::NumericConstantInFormatString { - name: ident_name.to_owned(), - span: call_expr_span, - }); - } else { - self.push_err(ResolverError::VariableNotDeclared { - name: ident_name.to_owned(), - span: call_expr_span, - }); - } - } - - let len = Type::Constant(str.len() as u64); - let typ = Type::FmtString(Box::new(len), Box::new(Type::Tuple(capture_types))); - (HirExpression::Literal(HirLiteral::FmtStr(str, fmt_str_idents)), typ) - } - - fn elaborate_prefix(&mut self, prefix: PrefixExpression) -> (HirExpression, Type) { - let span = prefix.rhs.span; - let (rhs, rhs_type) = self.elaborate_expression(prefix.rhs); - let ret_type = self.type_check_prefix_operand(&prefix.operator, &rhs_type, span); - (HirExpression::Prefix(HirPrefixExpression { operator: prefix.operator, rhs }), ret_type) - } - - 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); - - let expected = self.polymorphic_integer_or_field(); - self.unify(&index_type, &expected, || TypeCheckError::TypeMismatch { - expected_typ: "an integer".to_owned(), - expr_typ: index_type.to_string(), - expr_span: span, - }); - - // When writing `a[i]`, if `a : &mut ...` then automatically dereference `a` as many - // times as needed to get the underlying array. - let lhs_span = index_expr.collection.span; - let (lhs, lhs_type) = self.elaborate_expression(index_expr.collection); - let (collection, lhs_type) = self.insert_auto_dereferences(lhs, lhs_type); - - let typ = match lhs_type.follow_bindings() { - // XXX: We can check the array bounds here also, but it may be better to constant fold first - // and have ConstId instead of ExprId for constants - Type::Array(_, base_type) => *base_type, - Type::Slice(base_type) => *base_type, - Type::Error => Type::Error, - typ => { - self.push_err(TypeCheckError::TypeMismatch { - expected_typ: "Array".to_owned(), - expr_typ: typ.to_string(), - expr_span: lhs_span, - }); - Type::Error - } - }; - - let expr = HirExpression::Index(HirIndexExpression { collection, index }); - (expr, typ) - } - - fn elaborate_call(&mut self, call: CallExpression, span: Span) -> (HirExpression, Type) { - let (func, func_type) = self.elaborate_expression(*call.func); - - let mut arguments = Vec::with_capacity(call.arguments.len()); - let args = vecmap(call.arguments, |arg| { - let span = arg.span; - let (arg, typ) = self.elaborate_expression(arg); - arguments.push(arg); - (typ, arg, span) - }); - - let location = Location::new(span, self.file); - let call = HirCallExpression { func, arguments, location }; - let typ = self.type_check_call(&call, func_type, args, span); - (HirExpression::Call(call), typ) - } - - fn check_if_deprecated(&mut self, expr: ExprId) { - if let HirExpression::Ident(HirIdent { location, id, impl_kind: _ }) = - self.interner.expression(&expr) - { - if let Some(DefinitionKind::Function(func_id)) = - self.interner.try_definition(id).map(|def| &def.kind) - { - let attributes = self.interner.function_attributes(func_id); - if let Some(note) = attributes.get_deprecated_note() { - self.push_err(TypeCheckError::CallDeprecated { - name: self.interner.definition_name(id).to_string(), - note, - span: location.span, - }); - } - } - } - } - - fn is_unconstrained_call(&self, expr: ExprId) -> bool { - if let HirExpression::Ident(HirIdent { id, .. }) = self.interner.expression(&expr) { - if let Some(DefinitionKind::Function(func_id)) = - self.interner.try_definition(id).map(|def| &def.kind) - { - let modifiers = self.interner.function_modifiers(func_id); - return modifiers.is_unconstrained; - } - } - false - } - - fn elaborate_method_call( - &mut self, - method_call: MethodCallExpression, - span: Span, - ) -> (HirExpression, Type) { - let object_span = method_call.object.span; - let (mut object, mut object_type) = self.elaborate_expression(method_call.object); - object_type = object_type.follow_bindings(); - - let method_name = method_call.method_name.0.contents.as_str(); - match self.lookup_method(&object_type, method_name, span) { - Some(method_ref) => { - // Automatically add `&mut` if the method expects a mutable reference and - // the object is not already one. - if let HirMethodReference::FuncId(func_id) = &method_ref { - if *func_id != FuncId::dummy_id() { - let function_type = self.interner.function_meta(func_id).typ.clone(); - - self.try_add_mutable_reference_to_object( - &function_type, - &mut object_type, - &mut object, - ); - } - } - - // These arguments will be given to the desugared function call. - // Compared to the method arguments, they also contain the object. - let mut function_args = Vec::with_capacity(method_call.arguments.len() + 1); - let mut arguments = Vec::with_capacity(method_call.arguments.len()); - - function_args.push((object_type.clone(), object, object_span)); - - for arg in method_call.arguments { - let span = arg.span; - let (arg, typ) = self.elaborate_expression(arg); - arguments.push(arg); - function_args.push((typ, arg, span)); - } - - let location = Location::new(span, self.file); - let method = method_call.method_name; - let method_call = HirMethodCallExpression { method, object, arguments, location }; - - // Desugar the method call into a normal, resolved function call - // so that the backend doesn't need to worry about methods - // TODO: update object_type here? - let ((function_id, function_name), function_call) = method_call.into_function_call( - &method_ref, - object_type, - location, - &mut self.interner, - ); - - let func_type = self.type_check_variable(function_name, function_id); - - // Type check the new call now that it has been changed from a method call - // to a function call. This way we avoid duplicating code. - let typ = self.type_check_call(&function_call, func_type, function_args, span); - (HirExpression::Call(function_call), typ) - } - None => (HirExpression::Error, Type::Error), - } - } - - fn elaborate_constructor( - &mut self, - constructor: ConstructorExpression, - ) -> (HirExpression, Type) { - let span = constructor.type_name.span(); - - match self.lookup_type_or_error(constructor.type_name) { - Some(Type::Struct(r#type, struct_generics)) => { - let struct_type = r#type.clone(); - let generics = struct_generics.clone(); - - let fields = constructor.fields; - let field_types = r#type.borrow().get_fields(&struct_generics); - let fields = self.resolve_constructor_expr_fields( - struct_type.clone(), - field_types, - fields, - span, - ); - let expr = HirExpression::Constructor(HirConstructorExpression { - fields, - r#type, - struct_generics, - }); - (expr, Type::Struct(struct_type, generics)) - } - Some(typ) => { - self.push_err(ResolverError::NonStructUsedInConstructor { typ, span }); - (HirExpression::Error, Type::Error) - } - None => (HirExpression::Error, Type::Error), - } - } - - /// Resolve all the fields of a struct constructor expression. - /// Ensures all fields are present, none are repeated, and all - /// are part of the struct. - fn resolve_constructor_expr_fields( - &mut self, - struct_type: Shared, - field_types: Vec<(String, Type)>, - fields: Vec<(Ident, Expression)>, - span: Span, - ) -> Vec<(Ident, ExprId)> { - let mut ret = Vec::with_capacity(fields.len()); - let mut seen_fields = HashSet::default(); - let mut unseen_fields = struct_type.borrow().field_names(); - - for (field_name, field) in fields { - let expected_type = field_types.iter().find(|(name, _)| name == &field_name.0.contents); - let expected_type = expected_type.map(|(_, typ)| typ).unwrap_or(&Type::Error); - - let field_span = field.span; - let (resolved, field_type) = self.elaborate_expression(field); - - if unseen_fields.contains(&field_name) { - unseen_fields.remove(&field_name); - seen_fields.insert(field_name.clone()); - - self.unify_with_coercions(&field_type, expected_type, resolved, || { - TypeCheckError::TypeMismatch { - expected_typ: expected_type.to_string(), - expr_typ: field_type.to_string(), - expr_span: field_span, - } - }); - } else if seen_fields.contains(&field_name) { - // duplicate field - self.push_err(ResolverError::DuplicateField { field: field_name.clone() }); - } else { - // field not required by struct - self.push_err(ResolverError::NoSuchField { - field: field_name.clone(), - struct_definition: struct_type.borrow().name.clone(), - }); - } - - ret.push((field_name, resolved)); - } - - if !unseen_fields.is_empty() { - self.push_err(ResolverError::MissingFields { - span, - missing_fields: unseen_fields.into_iter().map(|field| field.to_string()).collect(), - struct_definition: struct_type.borrow().name.clone(), - }); - } - - ret - } - - fn elaborate_member_access( - &mut self, - access: MemberAccessExpression, - span: Span, - ) -> (ExprId, Type) { - let (lhs, lhs_type) = self.elaborate_expression(access.lhs); - let rhs = access.rhs; - // `is_offset` is only used when lhs is a reference and we want to return a reference to rhs - let access = HirMemberAccess { lhs, rhs, is_offset: false }; - let expr_id = self.intern_expr(HirExpression::MemberAccess(access.clone()), span); - let typ = self.type_check_member_access(access, expr_id, lhs_type, span); - self.interner.push_expr_type(expr_id, typ.clone()); - (expr_id, typ) - } - - fn intern_expr(&mut self, expr: HirExpression, span: Span) -> ExprId { - let id = self.interner.push_expr(expr); - self.interner.push_expr_location(id, span, self.file); - id - } - - fn elaborate_cast(&mut self, cast: CastExpression, span: Span) -> (HirExpression, Type) { - let (lhs, lhs_type) = self.elaborate_expression(cast.lhs); - let r#type = self.resolve_type(cast.r#type); - let result = self.check_cast(lhs_type, &r#type, span); - let expr = HirExpression::Cast(HirCastExpression { lhs, r#type }); - (expr, result) - } - - fn elaborate_infix(&mut self, infix: InfixExpression, span: Span) -> (ExprId, Type) { - let (lhs, lhs_type) = self.elaborate_expression(infix.lhs); - let (rhs, rhs_type) = self.elaborate_expression(infix.rhs); - let trait_id = self.interner.get_operator_trait_method(infix.operator.contents); - - let operator = HirBinaryOp::new(infix.operator, self.file); - let expr = HirExpression::Infix(HirInfixExpression { - lhs, - operator, - trait_method_id: trait_id, - rhs, - }); - - let expr_id = self.interner.push_expr(expr); - self.interner.push_expr_location(expr_id, span, self.file); - - let typ = match self.infix_operand_type_rules(&lhs_type, &operator, &rhs_type, span) { - Ok((typ, use_impl)) => { - if use_impl { - // Delay checking the trait constraint until the end of the function. - // Checking it now could bind an unbound type variable to any type - // that implements the trait. - let constraint = TraitConstraint { - typ: lhs_type.clone(), - trait_id: trait_id.trait_id, - trait_generics: Vec::new(), - }; - self.trait_constraints.push((constraint, expr_id)); - self.type_check_operator_method(expr_id, trait_id, &lhs_type, span); - } - typ - } - Err(error) => { - self.push_err(error); - Type::Error - } - }; - - self.interner.push_expr_type(expr_id, typ.clone()); - (expr_id, typ) - } - - fn elaborate_if(&mut self, if_expr: IfExpression) -> (HirExpression, Type) { - let expr_span = if_expr.condition.span; - let (condition, cond_type) = self.elaborate_expression(if_expr.condition); - let (consequence, mut ret_type) = self.elaborate_expression(if_expr.consequence); - - self.unify(&cond_type, &Type::Bool, || TypeCheckError::TypeMismatch { - expected_typ: Type::Bool.to_string(), - expr_typ: cond_type.to_string(), - expr_span, - }); - - let alternative = if_expr.alternative.map(|alternative| { - let expr_span = alternative.span; - let (else_, else_type) = self.elaborate_expression(alternative); - - self.unify(&ret_type, &else_type, || { - let err = TypeCheckError::TypeMismatch { - expected_typ: ret_type.to_string(), - expr_typ: else_type.to_string(), - expr_span, - }; - - let context = if ret_type == Type::Unit { - "Are you missing a semicolon at the end of your 'else' branch?" - } else if else_type == Type::Unit { - "Are you missing a semicolon at the end of the first block of this 'if'?" - } else { - "Expected the types of both if branches to be equal" - }; - - err.add_context(context) - }); - else_ - }); - - if alternative.is_none() { - ret_type = Type::Unit; - } - - let if_expr = HirIfExpression { condition, consequence, alternative }; - (HirExpression::If(if_expr), ret_type) - } - - fn elaborate_tuple(&mut self, tuple: Vec) -> (HirExpression, Type) { - let mut element_ids = Vec::with_capacity(tuple.len()); - let mut element_types = Vec::with_capacity(tuple.len()); - - for element in tuple { - let (id, typ) = self.elaborate_expression(element); - element_ids.push(id); - element_types.push(typ); - } - - (HirExpression::Tuple(element_ids), Type::Tuple(element_types)) - } - - fn elaborate_lambda(&mut self, lambda: Lambda) -> (HirExpression, Type) { - self.push_scope(); - let scope_index = self.scopes.current_scope_index(); - - self.lambda_stack.push(LambdaContext { captures: Vec::new(), scope_index }); - - let mut arg_types = Vec::with_capacity(lambda.parameters.len()); - let parameters = vecmap(lambda.parameters, |(pattern, typ)| { - let parameter = DefinitionKind::Local(None); - let typ = self.resolve_inferred_type(typ); - arg_types.push(typ.clone()); - (self.elaborate_pattern(pattern, typ.clone(), parameter), typ) - }); - - let return_type = self.resolve_inferred_type(lambda.return_type); - let body_span = lambda.body.span; - let (body, body_type) = self.elaborate_expression(lambda.body); - - let lambda_context = self.lambda_stack.pop().unwrap(); - self.pop_scope(); - - self.unify(&body_type, &return_type, || TypeCheckError::TypeMismatch { - expected_typ: return_type.to_string(), - expr_typ: body_type.to_string(), - expr_span: body_span, - }); - - let captured_vars = vecmap(&lambda_context.captures, |capture| { - self.interner.definition_type(capture.ident.id) - }); - - let env_type = - if captured_vars.is_empty() { Type::Unit } else { Type::Tuple(captured_vars) }; - - let captures = lambda_context.captures; - let expr = HirExpression::Lambda(HirLambda { parameters, return_type, body, captures }); - (expr, Type::Function(arg_types, Box::new(body_type), Box::new(env_type))) - } - - fn elaborate_quote(&mut self, block: BlockExpression) -> (HirExpression, Type) { - (HirExpression::Quote(block), Type::Code) - } - - fn elaborate_comptime_block(&mut self, _comptime: BlockExpression) -> (HirExpression, Type) { - todo!("Elaborate comptime block") - } } diff --git a/compiler/noirc_frontend/src/elaborator/statements.rs b/compiler/noirc_frontend/src/elaborator/statements.rs index 06c4306fb74..6ad6d66b2d4 100644 --- a/compiler/noirc_frontend/src/elaborator/statements.rs +++ b/compiler/noirc_frontend/src/elaborator/statements.rs @@ -2,21 +2,55 @@ use noirc_errors::{Location, Span}; use crate::{ ast::{AssignStatement, ConstrainStatement, LValue}, - hir::type_check::{Source, TypeCheckError}, + hir::{ + resolution::errors::ResolverError, + type_check::{Source, TypeCheckError}, + }, hir_def::{ expr::HirIdent, stmt::{ HirAssignStatement, HirConstrainStatement, HirForStatement, HirLValue, HirLetStatement, }, }, - macros_api::{ForLoopStatement, ForRange, HirStatement, LetStatement, Statement}, - node_interner::{DefinitionId, DefinitionKind}, + macros_api::{ + ForLoopStatement, ForRange, HirStatement, LetStatement, Statement, StatementKind, + }, + node_interner::{DefinitionId, DefinitionKind, StmtId}, Type, }; use super::Elaborator; impl Elaborator { + fn elaborate_statement_value(&mut self, statement: Statement) -> (HirStatement, Type) { + match statement.kind { + StatementKind::Let(let_stmt) => self.elaborate_let(let_stmt), + StatementKind::Constrain(constrain) => self.elaborate_constrain(constrain), + StatementKind::Assign(assign) => self.elaborate_assign(assign), + StatementKind::For(for_stmt) => self.elaborate_for(for_stmt), + StatementKind::Break => self.elaborate_jump(true, statement.span), + StatementKind::Continue => self.elaborate_jump(false, statement.span), + StatementKind::Comptime(statement) => self.elaborate_comptime(*statement), + StatementKind::Expression(expr) => { + let (expr, typ) = self.elaborate_expression(expr); + (HirStatement::Expression(expr), typ) + } + StatementKind::Semi(expr) => { + let (expr, _typ) = self.elaborate_expression(expr); + (HirStatement::Semi(expr), Type::Unit) + } + StatementKind::Error => (HirStatement::Error, Type::Error), + } + } + + pub(super) fn elaborate_statement(&mut self, statement: Statement) -> (StmtId, Type) { + let span = statement.span; + let (hir_statement, typ) = self.elaborate_statement_value(statement); + let id = self.interner.push_stmt(hir_statement); + self.interner.push_stmt_location(id, span, self.file); + (id, typ) + } + pub(super) fn elaborate_let(&mut self, let_stmt: LetStatement) -> (HirStatement, Type) { let expr_span = let_stmt.expression.span; let (expression, expr_type) = self.elaborate_expression(let_stmt.expression); @@ -147,6 +181,18 @@ impl Elaborator { (statement, Type::Unit) } + fn elaborate_jump(&mut self, is_break: bool, span: noirc_errors::Span) -> (HirStatement, Type) { + if !self.in_unconstrained_fn { + self.push_err(ResolverError::JumpInConstrainedFn { is_break, span }); + } + if self.nested_loops == 0 { + self.push_err(ResolverError::JumpOutsideLoop { is_break, span }); + } + + let expr = if is_break { HirStatement::Break } else { HirStatement::Continue }; + (expr, self.interner.next_type_variable()) + } + fn get_lvalue_name_and_span(&self, lvalue: &HirLValue) -> (String, Span) { match lvalue { HirLValue::Ident(name, _) => { diff --git a/compiler/noirc_frontend/src/elaborator/types.rs b/compiler/noirc_frontend/src/elaborator/types.rs index 2dbec33c754..06f62501b89 100644 --- a/compiler/noirc_frontend/src/elaborator/types.rs +++ b/compiler/noirc_frontend/src/elaborator/types.rs @@ -16,7 +16,7 @@ use crate::{ }, hir_def::{ expr::{ - HirBinaryOp, HirCallExpression, HirMemberAccess, HirMethodReference, + HirBinaryOp, HirCallExpression, HirIdent, HirMemberAccess, HirMethodReference, HirPrefixExpression, }, traits::{Trait, TraitConstraint}, @@ -1294,6 +1294,37 @@ impl Elaborator { return_type } + fn check_if_deprecated(&mut self, expr: ExprId) { + if let HirExpression::Ident(HirIdent { location, id, impl_kind: _ }) = + self.interner.expression(&expr) + { + if let Some(DefinitionKind::Function(func_id)) = + self.interner.try_definition(id).map(|def| &def.kind) + { + let attributes = self.interner.function_attributes(func_id); + if let Some(note) = attributes.get_deprecated_note() { + self.push_err(TypeCheckError::CallDeprecated { + name: self.interner.definition_name(id).to_string(), + note, + span: location.span, + }); + } + } + } + } + + fn is_unconstrained_call(&self, expr: ExprId) -> bool { + if let HirExpression::Ident(HirIdent { id, .. }) = self.interner.expression(&expr) { + if let Some(DefinitionKind::Function(func_id)) = + self.interner.try_definition(id).map(|def| &def.kind) + { + let modifiers = self.interner.function_modifiers(func_id); + return modifiers.is_unconstrained; + } + } + false + } + /// Check if the given method type requires a mutable reference to the object type, and check /// if the given object type is already a mutable reference. If not, add one. /// This is used to automatically transform a method call: `foo.bar()` into a function From 2e77d8781f96b8f5c0843328f8af2d58b56a6ffe Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Wed, 8 May 2024 12:40:48 -0500 Subject: [PATCH 06/38] Connect elaborator --- .../src/elaborator/expressions.rs | 2 +- compiler/noirc_frontend/src/elaborator/mod.rs | 116 ++++++++++++--- .../noirc_frontend/src/elaborator/patterns.rs | 4 +- .../noirc_frontend/src/elaborator/scope.rs | 65 +++++++-- .../src/elaborator/statements.rs | 2 +- .../noirc_frontend/src/elaborator/types.rs | 17 ++- .../src/hir/def_collector/dc_crate.rs | 137 +++++++++--------- .../src/hir/def_collector/dc_mod.rs | 16 +- tooling/nargo_cli/tests/stdlib-tests.rs | 15 +- 9 files changed, 246 insertions(+), 128 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/expressions.rs b/compiler/noirc_frontend/src/elaborator/expressions.rs index 19ee67b442c..1cc1f35f73e 100644 --- a/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -32,7 +32,7 @@ use crate::{ use super::Elaborator; -impl Elaborator { +impl<'context> Elaborator<'context> { pub(super) fn elaborate_expression(&mut self, expr: Expression) -> (ExprId, Type) { let (hir_expr, typ) = match expr.kind { ExpressionKind::Literal(literal) => self.elaborate_literal(literal, expr.span), diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index a621f01ed79..553fcb938b3 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -4,7 +4,7 @@ use std::{ rc::Rc, }; -use crate::graph::CrateId; +use crate::{graph::CrateId, hir::{Context, resolution::path_resolver::StandardPathResolver, def_map::{ModuleId, LocalModuleId}, def_collector::dc_crate::{DefCollector, CollectedItems}}}; use crate::hir::def_map::CrateDefMap; use crate::{ ast::{ @@ -47,7 +47,6 @@ use iter_extended::vecmap; use noirc_errors::{Location, Span}; use regex::Regex; use rustc_hash::FxHashSet as HashSet; -use scope::Scope; /// ResolverMetas are tagged onto each definition to track how many times they are used #[derive(Debug, PartialEq, Eq)] @@ -59,14 +58,15 @@ struct ResolverMeta { type ScopeForest = GenericScopeForest; -struct Elaborator { +pub struct Elaborator<'context> { scopes: ScopeForest, - globals: Scope, - local_scopes: Vec, - errors: Vec, + errors: Vec<(CompilationError, FileId)>, + + interner: &'context mut NodeInterner, + + def_maps: &'context BTreeMap, - interner: NodeInterner, file: FileId, in_unconstrained_fn: bool, @@ -100,9 +100,6 @@ struct Elaborator { trait_id: Option, - path_resolver: Rc, - def_maps: BTreeMap, - /// In-resolution names /// /// This needs to be a set because we can have multiple in-resolution @@ -132,10 +129,94 @@ struct Elaborator { /// on each variable, but it is only until function calls when the types /// needed for the trait constraint may become known. trait_constraints: Vec<(TraitConstraint, ExprId)>, + + /// The current module this elaborator is in. + /// Initially empty, it is set whenever a new top-level item is resolved. + local_module: LocalModuleId, + + crate_id: CrateId, } -impl Elaborator { - fn elaborate_function(&mut self, function: NoirFunction, _id: FuncId) { +impl<'context> Elaborator<'context> { + pub fn new(context: &'context mut Context, crate_id: CrateId) -> Self { + Self { + scopes: ScopeForest::default(), + errors: Vec::new(), + interner: &mut context.def_interner, + def_maps: &context.def_maps, + file: FileId::dummy(), + in_unconstrained_fn: false, + nested_loops: 0, + in_contract: false, + generics: Vec::new(), + lambda_stack: Vec::new(), + self_type: None, + current_item: None, + trait_id: None, + local_module: LocalModuleId::dummy_id(), + crate_id, + resolving_ids: BTreeSet::new(), + trait_bounds: Vec::new(), + current_function: None, + type_variables: Vec::new(), + trait_constraints: Vec::new(), + } + } + + pub fn elaborate(context: &'context mut Context, crate_id: CrateId, items: CollectedItems) -> Vec<(CompilationError, FileId)> { + let mut this = Self::new(context, crate_id); + + // the resolver filters literal globals first + for global in items.globals { + + } + + for alias in items.type_aliases { + + } + + for trait_ in items.traits { + + } + + for struct_ in items.types { + + } + + for trait_impl in &items.trait_impls { + // only collect now + } + + for impl_ in &items.impls { + // only collect now + } + + // resolver resolves non-literal globals here + + for functions in items.functions { + this.file = functions.file_id; + for (local_module, id, func) in functions.functions { + this.local_module = local_module; + this.elaborate_function(func, id); + } + } + + for impl_ in items.impls { + + } + + for trait_impl in items.trait_impls { + + } + + let cycle_errors = this.interner.check_for_dependency_cycles(); + this.errors.extend(cycle_errors); + + this.errors + } + + fn elaborate_function(&mut self, function: NoirFunction, id: FuncId) { + self.current_function = Some(id); // This is a stub until the elaborator is connected to dc_crate match function.kind { FunctionKind::LowLevel => todo!(), @@ -146,17 +227,10 @@ impl Elaborator { let _body = self.elaborate_block(function.def.body); } } - } - - fn push_scope(&mut self) { - self.local_scopes.push(Scope::default()); - } - - fn pop_scope(&mut self) { - self.local_scopes.pop(); + self.current_function = None; } fn push_err(&mut self, error: impl Into) { - self.errors.push(error.into()); + self.errors.push((error.into(), self.file)); } } diff --git a/compiler/noirc_frontend/src/elaborator/patterns.rs b/compiler/noirc_frontend/src/elaborator/patterns.rs index 868989d074a..0dba6b9d5f5 100644 --- a/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -19,7 +19,7 @@ use crate::{ use super::{Elaborator, ResolverMeta}; -impl Elaborator { +impl<'context> Elaborator<'context> { pub(super) fn elaborate_pattern( &mut self, pattern: Pattern, @@ -248,7 +248,7 @@ impl Elaborator { let global = self.interner.get_all_globals(); for global_info in global { if global_info.ident == name - && global_info.local_id == self.path_resolver.local_module_id() + && global_info.local_id == self.local_module { global_id = Some(global_info.id); } diff --git a/compiler/noirc_frontend/src/elaborator/scope.rs b/compiler/noirc_frontend/src/elaborator/scope.rs index 21562a8eef7..31bffa6abd6 100644 --- a/compiler/noirc_frontend/src/elaborator/scope.rs +++ b/compiler/noirc_frontend/src/elaborator/scope.rs @@ -1,6 +1,12 @@ +use noirc_errors::Spanned; use rustc_hash::FxHashMap as HashMap; +use crate::ast::ERROR_IDENT; use crate::hir::comptime::Value; +use crate::hir::def_map::{LocalModuleId, ModuleId}; +use crate::hir::resolution::path_resolver::{StandardPathResolver, PathResolver}; +use crate::hir::scope::{ScopeTree as GenericScopeTree, Scope as GenericScope}; +use crate::macros_api::Ident; use crate::{ hir::{ def_map::{ModuleDefId, TryFromModuleDefId}, @@ -15,21 +21,12 @@ use crate::{ Shared, StructType, }; -use super::Elaborator; +use super::{Elaborator, ResolverMeta}; -#[derive(Default)] -pub(super) struct Scope { - types: HashMap, - values: HashMap, - comptime_values: HashMap, -} - -pub(super) enum TypeId { - Struct(StructId), - Alias(TypeAliasId), -} +type Scope = GenericScope; +type ScopeTree = GenericScopeTree; -impl Elaborator { +impl<'context> Elaborator<'context> { pub(super) fn lookup(&mut self, path: Path) -> Result { let span = path.span(); let id = self.resolve_path(path)?; @@ -40,8 +37,14 @@ impl Elaborator { }) } + pub(super) 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(super) fn resolve_path(&mut self, path: Path) -> Result { - let path_resolution = self.path_resolver.resolve(&self.def_maps, path)?; + let resolver = StandardPathResolver::new(self.module_id()); + let path_resolution = resolver.resolve(&self.def_maps, path)?; if let Some(error) = path_resolution.error { self.push_err(error); @@ -109,4 +112,38 @@ impl Elaborator { let got = "local variable".into(); Err(ResolverError::Expected { span, expected, got }) } + + pub fn push_scope(&mut self) { + self.scopes.start_scope(); + } + + pub fn pop_scope(&mut self) { + let scope = self.scopes.end_scope(); + self.check_for_unused_variables_in_scope_tree(scope.into()); + } + + fn check_for_unused_variables_in_scope_tree(&mut self, scope_decls: ScopeTree) { + let mut unused_vars = Vec::new(); + for scope in scope_decls.0.into_iter() { + Self::check_for_unused_variables_in_local_scope(scope, &mut unused_vars); + } + + for unused_var in unused_vars.iter() { + if let Some(definition_info) = self.interner.try_definition(unused_var.id) { + let name = &definition_info.name; + if name != ERROR_IDENT && !definition_info.is_global() { + let ident = Ident(Spanned::from(unused_var.location.span, name.to_owned())); + self.push_err(ResolverError::UnusedVariable { ident }); + } + } + } + } + + fn check_for_unused_variables_in_local_scope(decl_map: Scope, unused_vars: &mut Vec) { + let unused_variables = decl_map.filter(|(variable_name, metadata)| { + let has_underscore_prefix = variable_name.starts_with('_'); // XXX: This is used for development mode, and will be removed + metadata.warn_if_unused && metadata.num_times_used == 0 && !has_underscore_prefix + }); + unused_vars.extend(unused_variables.map(|(_, meta)| meta.ident.clone())); + } } diff --git a/compiler/noirc_frontend/src/elaborator/statements.rs b/compiler/noirc_frontend/src/elaborator/statements.rs index 6ad6d66b2d4..e8f5639620b 100644 --- a/compiler/noirc_frontend/src/elaborator/statements.rs +++ b/compiler/noirc_frontend/src/elaborator/statements.rs @@ -21,7 +21,7 @@ use crate::{ use super::Elaborator; -impl Elaborator { +impl<'context> Elaborator<'context> { fn elaborate_statement_value(&mut self, statement: Statement) -> (HirStatement, Type) { match statement.kind { StatementKind::Let(let_stmt) => self.elaborate_let(let_stmt), diff --git a/compiler/noirc_frontend/src/elaborator/types.rs b/compiler/noirc_frontend/src/elaborator/types.rs index 06f62501b89..63dc8356d14 100644 --- a/compiler/noirc_frontend/src/elaborator/types.rs +++ b/compiler/noirc_frontend/src/elaborator/types.rs @@ -31,7 +31,7 @@ use crate::{ use super::Elaborator; -impl Elaborator { +impl<'context> Elaborator<'context> { /// Translates an UnresolvedType to a Type pub(super) fn resolve_type(&mut self, typ: UnresolvedType) -> Type { let span = typ.span; @@ -266,15 +266,12 @@ impl Elaborator { } // If we cannot find a local generic of the same name, try to look up a global - match self.path_resolver.resolve(&self.def_maps, path.clone()) { - Ok(PathResolution { module_def_id: ModuleDefId::GlobalId(id), error }) => { + match self.resolve_path(path.clone()) { + Ok(ModuleDefId::GlobalId(id)) => { if let Some(current_item) = self.current_item { self.interner.add_global_dependency(current_item, id); } - if let Some(error) = error { - self.push_err(error); - } Some(Type::Constant(self.eval_global_as_array_length(id, path))) } _ => None, @@ -623,7 +620,9 @@ impl Elaborator { ) { let mut errors = Vec::new(); actual.unify(expected, &mut errors, make_error); - self.errors.extend(errors.into_iter().map(Into::into)); + self.errors.extend(errors.into_iter().map(|error| { + (error.into(), self.file) + })); } /// Wrapper of Type::unify_with_coercions using self.errors @@ -642,7 +641,9 @@ impl Elaborator { &mut errors, make_error, ); - self.errors.extend(errors.into_iter().map(Into::into)); + self.errors.extend(errors.into_iter().map(|error| { + (error.into(), self.file) + })); } /// Return a fresh integer or field type variable and log it diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index 860e0408a67..62e539cfe0c 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -1,5 +1,6 @@ use super::dc_mod::collect_defs; use super::errors::{DefCollectorErrorKind, DuplicateType}; +use crate::elaborator::Elaborator; use crate::graph::CrateId; use crate::hir::comptime::{Interpreter, InterpreterError}; use crate::hir::def_map::{CrateDefMap, LocalModuleId, ModuleId}; @@ -38,10 +39,6 @@ pub struct ResolvedModule { pub trait_impl_functions: Vec<(FileId, FuncId)>, pub errors: Vec<(CompilationError, FileId)>, - - /// True if the experimental elaborator should be used over the normal - /// name resolution and type checking passes. - pub use_elaborator: bool, } /// Stores all of the unresolved functions in a particular file/mod @@ -133,14 +130,18 @@ pub struct UnresolvedGlobal { /// Given a Crate root, collect all definitions in that crate pub struct DefCollector { pub(crate) def_map: CrateDefMap, - pub(crate) collected_imports: Vec, - pub(crate) collected_functions: Vec, - pub(crate) collected_types: BTreeMap, - pub(crate) collected_type_aliases: BTreeMap, - pub(crate) collected_traits: BTreeMap, - pub(crate) collected_globals: Vec, - pub(crate) collected_impls: ImplMap, - pub(crate) collected_traits_impls: Vec, + pub(crate) imports: Vec, + pub(crate) items: CollectedItems, +} + +pub struct CollectedItems { + pub(crate) functions: Vec, + pub(crate) types: BTreeMap, + pub(crate) type_aliases: BTreeMap, + pub(crate) traits: BTreeMap, + pub(crate) globals: Vec, + pub(crate) impls: ImplMap, + pub(crate) trait_impls: Vec, } /// Maps the type and the module id in which the impl is defined to the functions contained in that @@ -214,14 +215,16 @@ impl DefCollector { fn new(def_map: CrateDefMap) -> DefCollector { DefCollector { def_map, - collected_imports: vec![], - collected_functions: vec![], - collected_types: BTreeMap::new(), - collected_type_aliases: BTreeMap::new(), - collected_traits: BTreeMap::new(), - collected_impls: HashMap::new(), - collected_globals: vec![], - collected_traits_impls: vec![], + imports: vec![], + items: CollectedItems { + functions: vec![], + types: BTreeMap::new(), + type_aliases: BTreeMap::new(), + traits: BTreeMap::new(), + impls: HashMap::new(), + globals: vec![], + trait_impls: vec![], + }, } } @@ -280,18 +283,18 @@ impl DefCollector { // Add the current crate to the collection of DefMaps context.def_maps.insert(crate_id, def_collector.def_map); - inject_prelude(crate_id, context, crate_root, &mut def_collector.collected_imports); + inject_prelude(crate_id, context, crate_root, &mut def_collector.imports); for submodule in submodules { inject_prelude( crate_id, context, LocalModuleId(submodule), - &mut def_collector.collected_imports, + &mut def_collector.imports, ); } // Resolve unresolved imports collected from the crate, one by one. - for collected_import in def_collector.collected_imports { + for collected_import in std::mem::take(&mut def_collector.imports) { match resolve_import(crate_id, &collected_import, &context.def_maps) { Ok(resolved_import) => { if let Some(error) = resolved_import.error { @@ -328,7 +331,13 @@ impl DefCollector { } } - let mut resolved_module = ResolvedModule { errors, use_elaborator, ..Default::default() }; + if use_elaborator { + let mut more_errors = Elaborator::elaborate(context, crate_id, def_collector.items); + more_errors.append(&mut errors); + return errors; + } + + let mut resolved_module = ResolvedModule { errors, ..Default::default() }; // We must first resolve and intern the globals before we can resolve any stmts inside each function. // Each function uses its own resolver with a newly created ScopeForest, and must be resolved again to be within a function's scope @@ -336,25 +345,25 @@ impl DefCollector { // Additionally, we must resolve integer globals before structs since structs may refer to // the values of integer globals as numeric generics. let (literal_globals, other_globals) = - filter_literal_globals(def_collector.collected_globals); + filter_literal_globals(def_collector.items.globals); resolved_module.resolve_globals(context, literal_globals, crate_id); resolved_module.errors.extend(resolve_type_aliases( context, - def_collector.collected_type_aliases, + def_collector.items.type_aliases, crate_id, )); resolved_module.errors.extend(resolve_traits( context, - def_collector.collected_traits, + def_collector.items.traits, crate_id, )); // Must resolve structs before we resolve globals. resolved_module.errors.extend(resolve_structs( context, - def_collector.collected_types, + def_collector.items.types, crate_id, )); @@ -363,7 +372,7 @@ impl DefCollector { resolved_module.errors.extend(collect_trait_impls( context, crate_id, - &mut def_collector.collected_traits_impls, + &mut def_collector.items.trait_impls, )); // Before we resolve any function symbols we must go through our impls and @@ -376,7 +385,7 @@ impl DefCollector { resolved_module.errors.extend(collect_impls( context, crate_id, - &def_collector.collected_impls, + &def_collector.items.impls, )); // We must wait to resolve non-integer globals until after we resolve structs since struct @@ -388,7 +397,7 @@ impl DefCollector { &mut context.def_interner, crate_id, &context.def_maps, - def_collector.collected_functions, + def_collector.items.functions, None, &mut resolved_module.errors, ); @@ -397,13 +406,13 @@ impl DefCollector { &mut context.def_interner, crate_id, &context.def_maps, - def_collector.collected_impls, + def_collector.items.impls, &mut resolved_module.errors, )); resolved_module.trait_impl_functions = resolve_trait_impls( context, - def_collector.collected_traits_impls, + def_collector.items.trait_impls, crate_id, &mut resolved_module.errors, ); @@ -436,15 +445,15 @@ fn inject_prelude( crate_root: LocalModuleId, collected_imports: &mut Vec, ) { - let segments: Vec<_> = "std::prelude" - .split("::") - .map(|segment| crate::ast::Ident::new(segment.into(), Span::default())) - .collect(); + if !crate_id.is_stdlib() { + let segments: Vec<_> = "std::prelude" + .split("::") + .map(|segment| crate::ast::Ident::new(segment.into(), Span::default())) + .collect(); - let path = - Path { segments: segments.clone(), kind: crate::ast::PathKind::Dep, span: Span::default() }; + let path = + Path { segments: segments.clone(), kind: crate::ast::PathKind::Dep, span: Span::default() }; - if !crate_id.is_stdlib() { if let Ok(PathResolution { module_def_id, error }) = path_resolver::resolve_path( &context.def_maps, ModuleId { krate: crate_id, local_id: crate_root }, @@ -486,42 +495,34 @@ fn filter_literal_globals( impl ResolvedModule { fn type_check(&mut self, context: &mut Context) { - if !self.use_elaborator { - self.type_check_globals(&mut context.def_interner); - self.type_check_functions(&mut context.def_interner); - self.type_check_trait_impl_function(&mut context.def_interner); - } + self.type_check_globals(&mut context.def_interner); + self.type_check_functions(&mut context.def_interner); + self.type_check_trait_impl_function(&mut context.def_interner); } fn type_check_globals(&mut self, interner: &mut NodeInterner) { - if !self.use_elaborator { - for (file_id, global_id) in self.globals.iter() { - for error in TypeChecker::check_global(*global_id, interner) { - self.errors.push((error.into(), *file_id)); - } + for (file_id, global_id) in self.globals.iter() { + for error in TypeChecker::check_global(*global_id, interner) { + self.errors.push((error.into(), *file_id)); } } } fn type_check_functions(&mut self, interner: &mut NodeInterner) { - if !self.use_elaborator { - for (file, func) in self.functions.iter() { - for error in type_check_func(interner, *func) { - self.errors.push((error.into(), *file)); - } + for (file, func) in self.functions.iter() { + for error in type_check_func(interner, *func) { + self.errors.push((error.into(), *file)); } } } fn type_check_trait_impl_function(&mut self, interner: &mut NodeInterner) { - if !self.use_elaborator { - for (file, func) in self.trait_impl_functions.iter() { - for error in check_trait_impl_method_matches_declaration(interner, *func) { - self.errors.push((error.into(), *file)); - } - for error in type_check_func(interner, *func) { - self.errors.push((error.into(), *file)); - } + for (file, func) in self.trait_impl_functions.iter() { + for error in check_trait_impl_method_matches_declaration(interner, *func) { + self.errors.push((error.into(), *file)); + } + for error in type_check_func(interner, *func) { + self.errors.push((error.into(), *file)); } } } @@ -553,13 +554,9 @@ impl ResolvedModule { literal_globals: Vec, crate_id: CrateId, ) { - if self.use_elaborator { - let mut elaborator = Elaborator::new(context); - } else { - let globals = resolve_globals(context, literal_globals, crate_id); - self.globals.extend(globals.globals); - self.errors.extend(globals.errors); - } + let globals = resolve_globals(context, literal_globals, crate_id); + self.globals.extend(globals.globals); + self.errors.extend(globals.errors); } /// Counts the number of errors (minus warnings) this program currently has diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs index b2ec7dbc813..e688f192d3d 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -70,7 +70,7 @@ pub fn collect_defs( // Then add the imports to defCollector to resolve once all modules in the hierarchy have been resolved for import in ast.imports { - collector.def_collector.collected_imports.push(ImportDirective { + collector.def_collector.imports.push(ImportDirective { module_id: collector.module_id, path: import.path, alias: import.alias, @@ -126,7 +126,7 @@ impl<'a> ModCollector<'a> { errors.push((err.into(), self.file_id)); } - self.def_collector.collected_globals.push(UnresolvedGlobal { + self.def_collector.items.globals.push(UnresolvedGlobal { file_id: self.file_id, module_id: self.module_id, global_id, @@ -154,7 +154,7 @@ impl<'a> ModCollector<'a> { } let key = (r#impl.object_type, self.module_id); - let methods = self.def_collector.collected_impls.entry(key).or_default(); + let methods = self.def_collector.items.impls.entry(key).or_default(); methods.push((r#impl.generics, r#impl.type_span, unresolved_functions)); } } @@ -191,7 +191,7 @@ impl<'a> ModCollector<'a> { trait_generics: trait_impl.trait_generics, }; - self.def_collector.collected_traits_impls.push(unresolved_trait_impl); + self.def_collector.items.trait_impls.push(unresolved_trait_impl); } } @@ -269,7 +269,7 @@ impl<'a> ModCollector<'a> { } } - self.def_collector.collected_functions.push(unresolved_functions); + self.def_collector.items.functions.push(unresolved_functions); errors } @@ -316,7 +316,7 @@ impl<'a> ModCollector<'a> { } // And store the TypeId -> StructType mapping somewhere it is reachable - self.def_collector.collected_types.insert(id, unresolved); + self.def_collector.items.types.insert(id, unresolved); } definition_errors } @@ -354,7 +354,7 @@ impl<'a> ModCollector<'a> { errors.push((err.into(), self.file_id)); } - self.def_collector.collected_type_aliases.insert(type_alias_id, unresolved); + self.def_collector.items.type_aliases.insert(type_alias_id, unresolved); } errors } @@ -506,7 +506,7 @@ impl<'a> ModCollector<'a> { method_ids, fns_with_default_impl: unresolved_functions, }; - self.def_collector.collected_traits.insert(trait_id, unresolved); + self.def_collector.items.traits.insert(trait_id, unresolved); } errors } diff --git a/tooling/nargo_cli/tests/stdlib-tests.rs b/tooling/nargo_cli/tests/stdlib-tests.rs index 9d377cfaee9..7e168dfe7ba 100644 --- a/tooling/nargo_cli/tests/stdlib-tests.rs +++ b/tooling/nargo_cli/tests/stdlib-tests.rs @@ -10,8 +10,7 @@ use nargo::{ parse_all, prepare_package, }; -#[test] -fn stdlib_noir_tests() { +fn run_stdlib_tests(use_elaborator: bool) { let mut file_manager = file_manager_with_stdlib(&PathBuf::from(".")); file_manager.add_file_with_source_canonical_path(&PathBuf::from("main.nr"), "".to_owned()); let parsed_files = parse_all(&file_manager); @@ -30,7 +29,7 @@ fn stdlib_noir_tests() { let (mut context, dummy_crate_id) = prepare_package(&file_manager, &parsed_files, &dummy_package); - let result = check_crate(&mut context, dummy_crate_id, true, false); + let result = check_crate(&mut context, dummy_crate_id, true, false, use_elaborator); report_errors(result, &context.file_manager, true, false) .expect("Error encountered while compiling standard library"); @@ -60,3 +59,13 @@ fn stdlib_noir_tests() { assert!(!test_report.is_empty(), "Could not find any tests within the stdlib"); assert!(test_report.iter().all(|(_, status)| !status.failed())); } + +#[test] +fn stdlib_noir_tests() { + run_stdlib_tests(false) +} + +#[test] +fn stdlib_elaborator_tests() { + run_stdlib_tests(true) +} From e542d32737b3e6e34d4283757378b9a76247fa9b Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Wed, 8 May 2024 13:04:34 -0500 Subject: [PATCH 07/38] Code review --- .../noirc_frontend/src/elaborator/patterns.rs | 17 ++++++++++------- .../noirc_frontend/src/elaborator/scope.rs | 18 +++--------------- .../noirc_frontend/src/elaborator/types.rs | 9 ++++++++- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/patterns.rs b/compiler/noirc_frontend/src/elaborator/patterns.rs index 868989d074a..b51115417e7 100644 --- a/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -281,15 +281,15 @@ impl Elaborator { ident } - // Checks for a variable having been declared before - // variable declaration and definition cannot be separate in Noir - // Once the variable has been found, intern and link `name` to this definition - // return the IdentId of `name` + // Checks for a variable having been declared before. + // (Variable declaration and definition cannot be separate in Noir.) + // Once the variable has been found, intern and link `name` to this definition, + // returning (the ident, the IdentId of `name`) // // If a variable is not found, then an error is logged and a dummy id // is returned, for better error reporting UX pub(super) fn find_variable_or_default(&mut self, name: &Ident) -> (HirIdent, usize) { - self.find_variable(name).unwrap_or_else(|error| { + self.use_variable(name).unwrap_or_else(|error| { self.push_err(error); let id = DefinitionId::dummy_id(); let location = Location::new(name.span(), self.file); @@ -297,7 +297,10 @@ impl Elaborator { }) } - pub(super) fn find_variable( + /// Lookup and use the specified variable. + /// This will increment its use counter by one and return the variable if found. + /// If the variable is not found, an error is returned. + pub(super) fn use_variable( &mut self, name: &Ident, ) -> Result<(HirIdent, usize), ResolverError> { @@ -441,7 +444,7 @@ impl Elaborator { fn get_ident_from_path(&mut self, path: Path) -> (HirIdent, usize) { let location = Location::new(path.span(), self.file); - let error = match path.as_ident().map(|ident| self.find_variable(ident)) { + let error = match path.as_ident().map(|ident| self.use_variable(ident)) { Some(Ok(found)) => return found, // Try to look it up as a global, but still issue the first error if we fail Some(Err(error)) => match self.lookup_global(path) { diff --git a/compiler/noirc_frontend/src/elaborator/scope.rs b/compiler/noirc_frontend/src/elaborator/scope.rs index 21562a8eef7..e8baba6bd89 100644 --- a/compiler/noirc_frontend/src/elaborator/scope.rs +++ b/compiler/noirc_frontend/src/elaborator/scope.rs @@ -17,18 +17,6 @@ use crate::{ use super::Elaborator; -#[derive(Default)] -pub(super) struct Scope { - types: HashMap, - values: HashMap, - comptime_values: HashMap, -} - -pub(super) enum TypeId { - Struct(StructId), - Alias(TypeAliasId), -} - impl Elaborator { pub(super) fn lookup(&mut self, path: Path) -> Result { let span = path.span(); @@ -65,12 +53,12 @@ impl Elaborator { if self.lambda_stack[lambda_index].scope_index > var_scope_index { // Beware: the same variable may be captured multiple times, so we check // for its presence before adding the capture below. - let pos = self.lambda_stack[lambda_index] + let position = self.lambda_stack[lambda_index] .captures .iter() .position(|capture| capture.ident.id == hir_ident.id); - if pos.is_none() { + if position.is_none() { self.lambda_stack[lambda_index].captures.push(HirCapturedVar { ident: hir_ident.clone(), transitive_capture_index, @@ -82,7 +70,7 @@ impl Elaborator { // the scope of the variable, so this is a propagated capture. // We need to track the transitive capture index as we go up in // the closure stack. - transitive_capture_index = Some(pos.unwrap_or( + transitive_capture_index = Some(position.unwrap_or( // If this was a fresh capture, we added it to the end of // the captures vector: self.lambda_stack[lambda_index].captures.len() - 1, diff --git a/compiler/noirc_frontend/src/elaborator/types.rs b/compiler/noirc_frontend/src/elaborator/types.rs index 06f62501b89..2cd699b919d 100644 --- a/compiler/noirc_frontend/src/elaborator/types.rs +++ b/compiler/noirc_frontend/src/elaborator/types.rs @@ -380,6 +380,9 @@ impl Elaborator { } // this resolves Self::some_static_method, inside an impl block (where we don't have a concrete self_type) + // + // Returns the trait method, trait constraint, and whether the impl is assumed to exist by a where clause or not + // E.g. `t.method()` with `where T: Foo` in scope will return `(Foo::method, T, vec![Bar])` fn resolve_trait_static_method_by_self( &mut self, path: &Path, @@ -406,6 +409,9 @@ impl Elaborator { } // this resolves TraitName::some_static_method + // + // Returns the trait method, trait constraint, and whether the impl is assumed to exist by a where clause or not + // E.g. `t.method()` with `where T: Foo` in scope will return `(Foo::method, T, vec![Bar])` fn resolve_trait_static_method( &mut self, path: &Path, @@ -817,7 +823,8 @@ impl Elaborator { } ret } - // ignoring env for subtype on purpose + // The closure env is ignored on purpose: call arguments never place + // constraints on closure environments. Type::Function(parameters, ret, _env) => { self.bind_function_type_impl(¶meters, &ret, &args, span) } From fd287df05beebe9ef2b43d252ece4dddb64ac5f2 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Wed, 8 May 2024 14:28:22 -0500 Subject: [PATCH 08/38] Finish removing Scope --- compiler/noirc_frontend/src/elaborator/mod.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index a621f01ed79..9a34fa847f5 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -47,7 +47,6 @@ use iter_extended::vecmap; use noirc_errors::{Location, Span}; use regex::Regex; use rustc_hash::FxHashSet as HashSet; -use scope::Scope; /// ResolverMetas are tagged onto each definition to track how many times they are used #[derive(Debug, PartialEq, Eq)] @@ -61,8 +60,6 @@ type ScopeForest = GenericScopeForest; struct Elaborator { scopes: ScopeForest, - globals: Scope, - local_scopes: Vec, errors: Vec, @@ -149,11 +146,11 @@ impl Elaborator { } fn push_scope(&mut self) { - self.local_scopes.push(Scope::default()); + // stub } fn pop_scope(&mut self) { - self.local_scopes.pop(); + // stub } fn push_err(&mut self, error: impl Into) { From 9614b877d2d3ca7969506fd7d600abe1811a4cbe Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Wed, 8 May 2024 15:46:03 -0500 Subject: [PATCH 09/38] Finish functions --- compiler/noirc_driver/src/lib.rs | 18 +- compiler/noirc_frontend/src/ast/function.rs | 9 + .../src/elaborator/expressions.rs | 2 +- compiler/noirc_frontend/src/elaborator/mod.rs | 606 +++++++++++++++++- .../noirc_frontend/src/elaborator/patterns.rs | 12 +- .../noirc_frontend/src/elaborator/scope.rs | 57 +- .../noirc_frontend/src/elaborator/types.rs | 166 +++-- .../src/hir/def_collector/dc_crate.rs | 30 +- .../noirc_frontend/src/hir/def_map/mod.rs | 9 +- .../noirc_frontend/src/hir_def/function.rs | 5 +- 10 files changed, 789 insertions(+), 125 deletions(-) diff --git a/compiler/noirc_driver/src/lib.rs b/compiler/noirc_driver/src/lib.rs index e3277fd78ec..ce60ec9b741 100644 --- a/compiler/noirc_driver/src/lib.rs +++ b/compiler/noirc_driver/src/lib.rs @@ -287,8 +287,13 @@ pub fn compile_main( options: &CompileOptions, cached_program: Option, ) -> CompilationResult { - let (_, mut warnings) = - check_crate(context, crate_id, options.deny_warnings, options.disable_macros, options.use_elaborator)?; + let (_, mut warnings) = check_crate( + context, + crate_id, + options.deny_warnings, + options.disable_macros, + options.use_elaborator, + )?; let main = context.get_main_function(&crate_id).ok_or_else(|| { // TODO(#2155): This error might be a better to exist in Nargo @@ -323,8 +328,13 @@ pub fn compile_contract( crate_id: CrateId, options: &CompileOptions, ) -> CompilationResult { - let (_, warnings) = - check_crate(context, crate_id, options.deny_warnings, options.disable_macros, options.use_elaborator)?; + let (_, warnings) = check_crate( + context, + crate_id, + options.deny_warnings, + options.disable_macros, + options.use_elaborator, + )?; // TODO: We probably want to error if contracts is empty let contracts = context.get_all_contracts(&crate_id); diff --git a/compiler/noirc_frontend/src/ast/function.rs b/compiler/noirc_frontend/src/ast/function.rs index dc426a4642a..8acc068d86a 100644 --- a/compiler/noirc_frontend/src/ast/function.rs +++ b/compiler/noirc_frontend/src/ast/function.rs @@ -32,6 +32,15 @@ pub enum FunctionKind { Recursive, } +impl FunctionKind { + pub fn can_ignore_return_type(self) -> bool { + match self { + FunctionKind::LowLevel | FunctionKind::Builtin | FunctionKind::Oracle => true, + FunctionKind::Normal | FunctionKind::Recursive => false, + } + } +} + impl NoirFunction { pub fn normal(def: FunctionDefinition) -> NoirFunction { NoirFunction { kind: FunctionKind::Normal, def } diff --git a/compiler/noirc_frontend/src/elaborator/expressions.rs b/compiler/noirc_frontend/src/elaborator/expressions.rs index 1cc1f35f73e..47c03ad50a4 100644 --- a/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -443,7 +443,7 @@ impl<'context> Elaborator<'context> { (expr_id, typ) } - fn intern_expr(&mut self, expr: HirExpression, span: Span) -> ExprId { + pub fn intern_expr(&mut self, expr: HirExpression, span: Span) -> ExprId { let id = self.interner.push_expr(expr); self.interner.push_expr_location(id, span, self.file); id diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 553fcb938b3..c4d7efbbff6 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -4,7 +4,6 @@ use std::{ rc::Rc, }; -use crate::{graph::CrateId, hir::{Context, resolution::path_resolver::StandardPathResolver, def_map::{ModuleId, LocalModuleId}, def_collector::dc_crate::{DefCollector, CollectedItems}}}; use crate::hir::def_map::CrateDefMap; use crate::{ ast::{ @@ -35,6 +34,28 @@ use crate::{ node_interner::{DefinitionKind, DependencyId, ExprId, FuncId, StmtId, TraitId}, Shared, StructType, Type, TypeVariable, }; +use crate::{ + ast::{TraitBound, UnresolvedGenerics}, + graph::CrateId, + hir::{ + def_collector::{ + dc_crate::{CollectedItems, DefCollector}, + errors::DefCollectorErrorKind, + }, + def_map::{LocalModuleId, ModuleDefId, ModuleId, MAIN_FUNCTION}, + resolution::{ + errors::PubPosition, + import::{PathResolution, PathResolutionError}, + path_resolver::StandardPathResolver, + }, + Context, + }, + hir_def::function::{FuncMeta, HirFunction}, + macros_api::{Param, Path, UnresolvedType, UnresolvedTypeData, Visibility}, + node_interner::TraitImplId, + token::FunctionAttribute, + Generics, +}; mod expressions; mod patterns; @@ -50,7 +71,7 @@ use rustc_hash::FxHashSet as HashSet; /// ResolverMetas are tagged onto each definition to track how many times they are used #[derive(Debug, PartialEq, Eq)] -struct ResolverMeta { +pub struct ResolverMeta { num_times_used: usize, ident: HirIdent, warn_if_unused: bool, @@ -98,6 +119,10 @@ pub struct Elaborator<'context> { /// Used to link items to their dependencies in the dependency graph current_item: Option, + /// If we're currently resolving methods within a trait impl, this will be set + /// to the corresponding trait impl ID. + current_trait_impl: Option, + trait_id: Option, /// In-resolution names @@ -160,28 +185,25 @@ impl<'context> Elaborator<'context> { current_function: None, type_variables: Vec::new(), trait_constraints: Vec::new(), + current_trait_impl: None, } } - pub fn elaborate(context: &'context mut Context, crate_id: CrateId, items: CollectedItems) -> Vec<(CompilationError, FileId)> { + pub fn elaborate( + context: &'context mut Context, + crate_id: CrateId, + items: CollectedItems, + ) -> Vec<(CompilationError, FileId)> { let mut this = Self::new(context, crate_id); // the resolver filters literal globals first - for global in items.globals { + for global in items.globals {} - } + for alias in items.type_aliases {} - for alias in items.type_aliases { + for trait_ in items.traits {} - } - - for trait_ in items.traits { - - } - - for struct_ in items.types { - - } + for struct_ in items.types {} for trait_impl in &items.trait_impls { // only collect now @@ -195,19 +217,16 @@ impl<'context> Elaborator<'context> { for functions in items.functions { this.file = functions.file_id; + this.trait_id = functions.trait_id; // TODO: Resolve? for (local_module, id, func) in functions.functions { this.local_module = local_module; this.elaborate_function(func, id); } } - for impl_ in items.impls { - - } - - for trait_impl in items.trait_impls { + for impl_ in items.impls {} - } + for trait_impl in items.trait_impls {} let cycle_errors = this.interner.check_for_dependency_cycles(); this.errors.extend(cycle_errors); @@ -215,22 +234,549 @@ impl<'context> Elaborator<'context> { this.errors } - fn elaborate_function(&mut self, function: NoirFunction, id: FuncId) { + fn elaborate_function(&mut self, mut function: NoirFunction, id: FuncId) { self.current_function = Some(id); - // This is a stub until the elaborator is connected to dc_crate - match function.kind { - FunctionKind::LowLevel => todo!(), - FunctionKind::Builtin => todo!(), - FunctionKind::Oracle => todo!(), - FunctionKind::Recursive => todo!(), - FunctionKind::Normal => { - let _body = self.elaborate_block(function.def.body); + self.resolve_where_clause(&mut function.def.where_clause); + + // Without this, impl methods can accidentally be placed in contracts. See #3254 + if self.self_type.is_some() { + self.in_contract = false; + } + + self.scopes.start_function(); + self.current_item = Some(DependencyId::Function(id)); + + // Check whether the function has globals in the local module and add them to the scope + self.resolve_local_globals(); + self.add_generics(&function.def.generics); + + self.desugar_impl_trait_args(&mut function, id); + self.trait_bounds = function.def.where_clause.clone(); + + let is_low_level_or_oracle = function + .attributes() + .function + .as_ref() + .map_or(false, |func| func.is_low_level() || func.is_oracle()); + + if function.def.is_unconstrained { + self.in_unconstrained_fn = true; + } + + let func_meta = self.extract_meta(&function, id); + + self.add_trait_constraints_to_scope(&func_meta); + + let (hir_func, body_type) = match function.kind { + FunctionKind::Builtin | FunctionKind::LowLevel | FunctionKind::Oracle => { + (HirFunction::empty(), Type::Error) + } + FunctionKind::Normal | FunctionKind::Recursive => { + let block_span = function.def.span; + let (block, body_type) = self.elaborate_block(function.def.body); + let expr_id = self.intern_expr(block, block_span); + self.interner.push_expr_type(expr_id, body_type.clone()); + (HirFunction::unchecked_from_expr(expr_id), body_type) + } + }; + + if !func_meta.can_ignore_return_type() { + self.type_check_function_body(body_type, &func_meta, hir_func.as_expr()); + } + + // Default any type variables that still need defaulting. + // This is done before trait impl search since leaving them bindable can lead to errors + // when multiple impls are available. Instead we default first to choose the Field or u64 impl. + for typ in &self.type_variables { + if let Type::TypeVariable(variable, kind) = typ.follow_bindings() { + let msg = "TypeChecker should only track defaultable type vars"; + variable.bind(kind.default_type().expect(msg)); } } + + // Verify any remaining trait constraints arising from the function body + for (constraint, expr_id) in std::mem::take(&mut self.trait_constraints) { + let span = self.interner.expr_span(&expr_id); + self.verify_trait_constraint( + &constraint.typ, + constraint.trait_id, + &constraint.trait_generics, + expr_id, + span, + ); + } + + // Now remove all the `where` clause constraints we added + for constraint in &func_meta.trait_constraints { + self.interner.remove_assumed_trait_implementations_for_trait(constraint.trait_id); + } + + let func_scope_tree = self.scopes.end_function(); + + // The arguments to low-level and oracle functions are always unused so we do not produce warnings for them. + if !is_low_level_or_oracle { + self.check_for_unused_variables_in_scope_tree(func_scope_tree); + } + + self.trait_bounds.clear(); + + self.interner.push_fn_meta(func_meta, id); + self.interner.update_fn(id, hir_func); self.current_function = None; } + /// This turns function parameters of the form: + /// fn foo(x: impl Bar) + /// + /// into + /// fn foo(x: T0_impl_Bar) where T0_impl_Bar: Bar + fn desugar_impl_trait_args(&mut self, func: &mut NoirFunction, func_id: FuncId) { + let mut impl_trait_generics = HashSet::default(); + let mut counter: usize = 0; + for parameter in func.def.parameters.iter_mut() { + if let UnresolvedTypeData::TraitAsType(path, args) = ¶meter.typ.typ { + let mut new_generic_ident: Ident = + format!("T{}_impl_{}", func_id, path.as_string()).into(); + let mut new_generic_path = Path::from_ident(new_generic_ident.clone()); + while impl_trait_generics.contains(&new_generic_ident) + || self.lookup_generic_or_global_type(&new_generic_path).is_some() + { + new_generic_ident = + format!("T{}_impl_{}_{}", func_id, path.as_string(), counter).into(); + new_generic_path = Path::from_ident(new_generic_ident.clone()); + counter += 1; + } + impl_trait_generics.insert(new_generic_ident.clone()); + + let is_synthesized = true; + let new_generic_type_data = + UnresolvedTypeData::Named(new_generic_path, vec![], is_synthesized); + let new_generic_type = + UnresolvedType { typ: new_generic_type_data.clone(), span: None }; + let new_trait_bound = TraitBound { + trait_path: path.clone(), + trait_id: None, + trait_generics: args.to_vec(), + }; + let new_trait_constraint = UnresolvedTraitConstraint { + typ: new_generic_type, + trait_bound: new_trait_bound, + }; + + parameter.typ.typ = new_generic_type_data; + func.def.generics.push(new_generic_ident); + func.def.where_clause.push(new_trait_constraint); + } + } + self.add_generics(&impl_trait_generics.into_iter().collect()); + } + + /// Add the given generics to scope. + /// Each generic will have a fresh Shared associated with it. + pub fn add_generics(&mut self, generics: &UnresolvedGenerics) -> Generics { + vecmap(generics, |generic| { + // Map the generic to a fresh type variable + let id = self.interner.next_type_variable_id(); + let typevar = TypeVariable::unbound(id); + let span = generic.0.span(); + + // Check for name collisions of this generic + let name = Rc::new(generic.0.contents.clone()); + + if let Some((_, _, first_span)) = self.find_generic(&name) { + self.push_err(ResolverError::DuplicateDefinition { + name: generic.0.contents.clone(), + first_span: *first_span, + second_span: span, + }); + } else { + self.generics.push((name, typevar.clone(), span)); + } + + typevar + }) + } + fn push_err(&mut self, error: impl Into) { self.errors.push((error.into(), self.file)); } + + fn resolve_where_clause(&mut self, clause: &mut [UnresolvedTraitConstraint]) { + for bound in clause { + if let Some(trait_id) = self.resolve_trait_by_path(bound.trait_bound.trait_path.clone()) + { + bound.trait_bound.trait_id = Some(trait_id); + } + } + } + + fn resolve_trait_by_path(&mut self, path: Path) -> Option { + let path_resolver = StandardPathResolver::new(self.module_id()); + + let error = match path_resolver.resolve(&self.def_maps, path.clone()) { + Ok(PathResolution { module_def_id: ModuleDefId::TraitId(trait_id), error }) => { + if let Some(error) = error { + self.push_err(error); + } + return Some(trait_id); + } + Ok(_) => DefCollectorErrorKind::NotATrait { not_a_trait_name: path }, + Err(_) => DefCollectorErrorKind::TraitNotFound { trait_path: path }, + }; + self.push_err(error); + None + } + + fn resolve_local_globals(&mut self) { + let globals = vecmap(self.interner.get_all_globals(), |global| { + (global.id, global.local_id, global.ident.clone()) + }); + for (id, local_module_id, name) in globals { + if local_module_id == self.local_module { + let definition = DefinitionKind::Global(id); + self.add_global_variable_decl(name, definition); + } + } + } + + /// TODO: This is currently only respected for generic free functions + /// there's a bunch of other places where trait constraints can pop up + fn resolve_trait_constraints( + &mut self, + where_clause: &[UnresolvedTraitConstraint], + ) -> Vec { + where_clause + .iter() + .cloned() + .filter_map(|constraint| self.resolve_trait_constraint(constraint)) + .collect() + } + + pub fn resolve_trait_constraint( + &mut self, + constraint: UnresolvedTraitConstraint, + ) -> Option { + let typ = self.resolve_type(constraint.typ); + let trait_generics = + vecmap(constraint.trait_bound.trait_generics, |typ| self.resolve_type(typ)); + + let span = constraint.trait_bound.trait_path.span(); + let the_trait = self.lookup_trait_or_error(constraint.trait_bound.trait_path)?; + let trait_id = the_trait.id; + + let expected_generics = the_trait.generics.len(); + let actual_generics = trait_generics.len(); + + if actual_generics != expected_generics { + let item_name = the_trait.name.to_string(); + self.push_err(ResolverError::IncorrectGenericCount { + span, + item_name, + actual: actual_generics, + expected: expected_generics, + }); + } + + Some(TraitConstraint { typ, trait_id, trait_generics }) + } + + /// Extract metadata from a NoirFunction + /// to be used in analysis and intern the function parameters + /// Prerequisite: self.add_generics() has already been called with the given + /// function's generics, including any generics from the impl, if any. + fn extract_meta(&mut self, func: &NoirFunction, func_id: FuncId) -> FuncMeta { + let location = Location::new(func.name_ident().span(), self.file); + let id = self.interner.function_definition_id(func_id); + let name_ident = HirIdent::non_trait_method(id, location); + + let attributes = func.attributes().clone(); + let has_no_predicates_attribute = attributes.is_no_predicates(); + let should_fold = attributes.is_foldable(); + if !self.inline_attribute_allowed(func) { + if has_no_predicates_attribute { + self.push_err(ResolverError::NoPredicatesAttributeOnUnconstrained { + ident: func.name_ident().clone(), + }); + } else if should_fold { + self.push_err(ResolverError::FoldAttributeOnUnconstrained { + ident: func.name_ident().clone(), + }); + } + } + // Both the #[fold] and #[no_predicates] alter a function's inline type and code generation in similar ways. + // In certain cases such as type checking (for which the following flag will be used) both attributes + // indicate we should code generate in the same way. Thus, we unify the attributes into one flag here. + let has_inline_attribute = has_no_predicates_attribute || should_fold; + let is_entry_point = self.is_entry_point_function(func); + + let mut generics = vecmap(&self.generics, |(_, typevar, _)| typevar.clone()); + let mut parameters = vec![]; + let mut parameter_types = vec![]; + + for Param { visibility, pattern, typ, span: _ } in func.parameters().iter().cloned() { + if visibility == Visibility::Public && !self.pub_allowed(func) { + self.push_err(ResolverError::UnnecessaryPub { + ident: func.name_ident().clone(), + position: PubPosition::Parameter, + }); + } + + let type_span = typ.span.unwrap_or_else(|| pattern.span()); + let typ = self.resolve_type_inner(typ, &mut generics); + self.check_if_type_is_valid_for_program_input( + &typ, + is_entry_point, + has_inline_attribute, + type_span, + ); + let pattern = self.elaborate_pattern(pattern, typ.clone(), DefinitionKind::Local(None)); + + parameters.push((pattern, typ.clone(), visibility)); + parameter_types.push(typ); + } + + let return_type = Box::new(self.resolve_type(func.return_type())); + + self.declare_numeric_generics(¶meter_types, &return_type); + + if !self.pub_allowed(func) && func.def.return_visibility == Visibility::Public { + self.push_err(ResolverError::UnnecessaryPub { + ident: func.name_ident().clone(), + position: PubPosition::ReturnType, + }); + } + + let is_low_level_function = + attributes.function.as_ref().map_or(false, |func| func.is_low_level()); + + if !self.crate_id.is_stdlib() && is_low_level_function { + let error = + ResolverError::LowLevelFunctionOutsideOfStdlib { ident: func.name_ident().clone() }; + self.push_err(error); + } + + // 'pub' is required on return types for entry point functions + if is_entry_point + && return_type.as_ref() != &Type::Unit + && func.def.return_visibility == Visibility::Private + { + self.push_err(ResolverError::NecessaryPub { ident: func.name_ident().clone() }); + } + // '#[recursive]' attribute is only allowed for entry point functions + if !is_entry_point && func.kind == FunctionKind::Recursive { + self.push_err(ResolverError::MisplacedRecursiveAttribute { + ident: func.name_ident().clone(), + }); + } + + if matches!(attributes.function, Some(FunctionAttribute::Test { .. })) + && !parameters.is_empty() + { + self.push_err(ResolverError::TestFunctionHasParameters { + span: func.name_ident().span(), + }); + } + + let mut typ = Type::Function(parameter_types, return_type, Box::new(Type::Unit)); + + if !generics.is_empty() { + typ = Type::Forall(generics, Box::new(typ)); + } + + self.interner.push_definition_type(name_ident.id, typ.clone()); + + let direct_generics = func.def.generics.iter(); + let direct_generics = direct_generics + .filter_map(|generic| self.find_generic(&generic.0.contents)) + .map(|(name, typevar, _span)| (name.clone(), typevar.clone())) + .collect(); + + FuncMeta { + name: name_ident, + kind: func.kind, + location, + typ, + direct_generics, + trait_impl: self.current_trait_impl, + parameters: parameters.into(), + return_type: func.def.return_type.clone(), + return_visibility: func.def.return_visibility, + has_body: !func.def.body.is_empty(), + trait_constraints: self.resolve_trait_constraints(&func.def.where_clause), + is_entry_point, + has_inline_attribute, + } + } + + /// Only sized types are valid to be used as main's parameters or the parameters to a contract + /// function. If the given type is not sized (e.g. contains a slice or NamedGeneric type), an + /// error is issued. + fn check_if_type_is_valid_for_program_input( + &mut self, + typ: &Type, + is_entry_point: bool, + has_inline_attribute: bool, + span: Span, + ) { + if (is_entry_point && !typ.is_valid_for_program_input()) + || (has_inline_attribute && !typ.is_valid_non_inlined_function_input()) + { + self.push_err(TypeCheckError::InvalidTypeForEntryPoint { span }); + } + } + + fn inline_attribute_allowed(&self, func: &NoirFunction) -> bool { + // Inline attributes are only relevant for constrained functions + // as all unconstrained functions are not inlined + !func.def.is_unconstrained + } + + /// True if the 'pub' keyword is allowed on parameters in this function + /// 'pub' on function parameters is only allowed for entry point functions + fn pub_allowed(&self, func: &NoirFunction) -> bool { + self.is_entry_point_function(func) || func.attributes().is_foldable() + } + + fn is_entry_point_function(&self, func: &NoirFunction) -> bool { + if self.in_contract { + func.attributes().is_contract_entry_point() + } else { + func.name() == MAIN_FUNCTION + } + } + + fn declare_numeric_generics(&mut self, params: &[Type], return_type: &Type) { + if self.generics.is_empty() { + return; + } + + for (name_to_find, type_variable) in Self::find_numeric_generics(params, return_type) { + // Declare any generics to let users use numeric generics in scope. + // Don't issue a warning if these are unused + // + // We can fail to find the generic in self.generics if it is an implicit one created + // by the compiler. This can happen when, e.g. eliding array lengths using the slice + // syntax [T]. + if let Some((name, _, span)) = + self.generics.iter().find(|(name, _, _)| name.as_ref() == &name_to_find) + { + let ident = Ident::new(name.to_string(), *span); + let definition = DefinitionKind::GenericType(type_variable); + self.add_variable_decl_inner(ident, false, false, false, definition); + } + } + } + + fn find_numeric_generics( + parameters: &[Type], + return_type: &Type, + ) -> Vec<(String, TypeVariable)> { + let mut found = BTreeMap::new(); + for parameter in parameters { + Self::find_numeric_generics_in_type(parameter, &mut found); + } + Self::find_numeric_generics_in_type(return_type, &mut found); + found.into_iter().collect() + } + + fn find_numeric_generics_in_type(typ: &Type, found: &mut BTreeMap) { + match typ { + Type::FieldElement + | Type::Integer(_, _) + | Type::Bool + | Type::Unit + | Type::Error + | Type::TypeVariable(_, _) + | Type::Constant(_) + | Type::NamedGeneric(_, _) + | Type::Code + | Type::Forall(_, _) => (), + + Type::TraitAsType(_, _, args) => { + for arg in args { + Self::find_numeric_generics_in_type(arg, found); + } + } + + Type::Array(length, element_type) => { + if let Type::NamedGeneric(type_variable, name) = length.as_ref() { + found.insert(name.to_string(), type_variable.clone()); + } + Self::find_numeric_generics_in_type(element_type, found); + } + + Type::Slice(element_type) => { + Self::find_numeric_generics_in_type(element_type, found); + } + + Type::Tuple(fields) => { + for field in fields { + Self::find_numeric_generics_in_type(field, found); + } + } + + Type::Function(parameters, return_type, _env) => { + for parameter in parameters { + Self::find_numeric_generics_in_type(parameter, found); + } + Self::find_numeric_generics_in_type(return_type, found); + } + + Type::Struct(struct_type, generics) => { + for (i, generic) in generics.iter().enumerate() { + if let Type::NamedGeneric(type_variable, name) = generic { + if struct_type.borrow().generic_is_numeric(i) { + found.insert(name.to_string(), type_variable.clone()); + } + } else { + Self::find_numeric_generics_in_type(generic, found); + } + } + } + Type::Alias(alias, generics) => { + for (i, generic) in generics.iter().enumerate() { + if let Type::NamedGeneric(type_variable, name) = generic { + if alias.borrow().generic_is_numeric(i) { + found.insert(name.to_string(), type_variable.clone()); + } + } else { + Self::find_numeric_generics_in_type(generic, found); + } + } + } + Type::MutableReference(element) => Self::find_numeric_generics_in_type(element, found), + Type::String(length) => { + if let Type::NamedGeneric(type_variable, name) = length.as_ref() { + found.insert(name.to_string(), type_variable.clone()); + } + } + Type::FmtString(length, fields) => { + if let Type::NamedGeneric(type_variable, name) = length.as_ref() { + found.insert(name.to_string(), type_variable.clone()); + } + Self::find_numeric_generics_in_type(fields, found); + } + } + } + + fn add_trait_constraints_to_scope(&mut self, func_meta: &FuncMeta) { + for constraint in &func_meta.trait_constraints { + let object = constraint.typ.clone(); + let trait_id = constraint.trait_id; + let generics = constraint.trait_generics.clone(); + + if !self.interner.add_assumed_trait_implementation(object, trait_id, generics) { + if let Some(the_trait) = self.interner.try_get_trait(trait_id) { + let trait_name = the_trait.name.to_string(); + let typ = constraint.typ.clone(); + let span = func_meta.location.span; + self.push_err(TypeCheckError::UnneededTraitConstraint { + trait_name, + typ, + span, + }); + } + } + } + } } diff --git a/compiler/noirc_frontend/src/elaborator/patterns.rs b/compiler/noirc_frontend/src/elaborator/patterns.rs index f414fc4c67d..cf9acab7712 100644 --- a/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -203,7 +203,7 @@ impl<'context> Elaborator<'context> { self.add_variable_decl_inner(name, mutable, allow_shadowing, true, definition) } - fn add_variable_decl_inner( + pub fn add_variable_decl_inner( &mut self, name: Ident, mutable: bool, @@ -238,7 +238,11 @@ impl<'context> Elaborator<'context> { ident } - fn add_global_variable_decl(&mut self, name: Ident, definition: DefinitionKind) -> HirIdent { + pub fn add_global_variable_decl( + &mut self, + name: Ident, + definition: DefinitionKind, + ) -> HirIdent { let scope = self.scopes.get_mut_scope(); // This check is necessary to maintain the same definition ids in the interner. Currently, each function uses a new resolver that has its own ScopeForest and thus global scope. @@ -247,9 +251,7 @@ impl<'context> Elaborator<'context> { let mut global_id = None; let global = self.interner.get_all_globals(); for global_info in global { - if global_info.ident == name - && global_info.local_id == self.local_module - { + if global_info.ident == name && global_info.local_id == self.local_module { global_id = Some(global_info.id); } } diff --git a/compiler/noirc_frontend/src/elaborator/scope.rs b/compiler/noirc_frontend/src/elaborator/scope.rs index 393843f15eb..ad9ee466878 100644 --- a/compiler/noirc_frontend/src/elaborator/scope.rs +++ b/compiler/noirc_frontend/src/elaborator/scope.rs @@ -4,8 +4,9 @@ use rustc_hash::FxHashMap as HashMap; use crate::ast::ERROR_IDENT; use crate::hir::comptime::Value; use crate::hir::def_map::{LocalModuleId, ModuleId}; -use crate::hir::resolution::path_resolver::{StandardPathResolver, PathResolver}; -use crate::hir::scope::{ScopeTree as GenericScopeTree, Scope as GenericScope}; +use crate::hir::resolution::path_resolver::{PathResolver, StandardPathResolver}; +use crate::hir::resolution::resolver::SELF_TYPE_NAME; +use crate::hir::scope::{Scope as GenericScope, ScopeTree as GenericScopeTree}; use crate::macros_api::Ident; use crate::{ hir::{ @@ -20,6 +21,7 @@ use crate::{ node_interner::{DefinitionId, TraitId, TypeAliasId}, Shared, StructType, }; +use crate::{Type, TypeAlias}; use super::{Elaborator, ResolverMeta}; @@ -122,7 +124,7 @@ impl<'context> Elaborator<'context> { self.check_for_unused_variables_in_scope_tree(scope.into()); } - fn check_for_unused_variables_in_scope_tree(&mut self, scope_decls: ScopeTree) { + pub fn check_for_unused_variables_in_scope_tree(&mut self, scope_decls: ScopeTree) { let mut unused_vars = Vec::new(); for scope in scope_decls.0.into_iter() { Self::check_for_unused_variables_in_local_scope(scope, &mut unused_vars); @@ -146,4 +148,53 @@ impl<'context> Elaborator<'context> { }); unused_vars.extend(unused_variables.map(|(_, meta)| meta.ident.clone())); } + + /// Lookup a given trait by name/path. + pub fn lookup_trait_or_error(&mut self, path: Path) -> Option<&mut Trait> { + match self.lookup(path) { + Ok(trait_id) => Some(self.get_trait_mut(trait_id)), + Err(error) => { + self.push_err(error); + None + } + } + } + + /// Lookup a given struct type by name. + pub fn lookup_struct_or_error(&mut self, path: Path) -> Option> { + match self.lookup(path) { + Ok(struct_id) => Some(self.get_struct(struct_id)), + Err(error) => { + self.push_err(error); + None + } + } + } + + /// Looks up a given type by name. + /// This will also instantiate any struct types found. + pub(super) fn lookup_type_or_error(&mut self, path: Path) -> Option { + let ident = path.as_ident(); + if ident.map_or(false, |i| i == SELF_TYPE_NAME) { + if let Some(typ) = &self.self_type { + return Some(typ.clone()); + } + } + + match self.lookup(path) { + Ok(struct_id) => { + let struct_type = self.get_struct(struct_id); + let generics = struct_type.borrow().instantiate(&mut self.interner); + Some(Type::Struct(struct_type, generics)) + } + Err(error) => { + self.push_err(error); + None + } + } + } + + pub fn lookup_type_alias(&mut self, path: Path) -> Option> { + self.lookup(path).ok().map(|id| self.interner.get_type_alias(id)) + } } diff --git a/compiler/noirc_frontend/src/elaborator/types.rs b/compiler/noirc_frontend/src/elaborator/types.rs index 1d768aeaf08..ba246fd24e0 100644 --- a/compiler/noirc_frontend/src/elaborator/types.rs +++ b/compiler/noirc_frontend/src/elaborator/types.rs @@ -19,13 +19,14 @@ use crate::{ HirBinaryOp, HirCallExpression, HirIdent, HirMemberAccess, HirMethodReference, HirPrefixExpression, }, + function::FuncMeta, traits::{Trait, TraitConstraint}, }, macros_api::{ - HirExpression, HirLiteral, Path, PathKind, SecondaryAttribute, Signedness, UnaryOp, - UnresolvedType, UnresolvedTypeData, + HirExpression, HirLiteral, HirStatement, Path, PathKind, SecondaryAttribute, Signedness, + UnaryOp, UnresolvedType, UnresolvedTypeData, }, - node_interner::{DefinitionKind, ExprId, GlobalId, TraitImplKind, TraitMethodId}, + node_interner::{DefinitionKind, ExprId, GlobalId, TraitId, TraitImplKind, TraitMethodId}, Generics, Shared, StructType, Type, TypeAlias, TypeBinding, TypeVariable, TypeVariableKind, }; @@ -45,7 +46,11 @@ impl<'context> Elaborator<'context> { /// Translates an UnresolvedType into a Type and appends any /// freshly created TypeVariables created to new_variables. - fn resolve_type_inner(&mut self, typ: UnresolvedType, new_variables: &mut Generics) -> Type { + pub fn resolve_type_inner( + &mut self, + typ: UnresolvedType, + new_variables: &mut Generics, + ) -> Type { use crate::ast::UnresolvedTypeData::*; let resolved_type = match typ.typ { @@ -124,7 +129,7 @@ impl<'context> Elaborator<'context> { resolved_type } - fn find_generic(&self, target_name: &str) -> Option<&(Rc, TypeVariable, Span)> { + pub fn find_generic(&self, target_name: &str) -> Option<&(Rc, TypeVariable, Span)> { self.generics.iter().find(|(name, _, _)| name.as_ref() == target_name) } @@ -257,7 +262,7 @@ impl<'context> Elaborator<'context> { } } - fn lookup_generic_or_global_type(&mut self, path: &Path) -> Option { + pub fn lookup_generic_or_global_type(&mut self, path: &Path) -> Option { if path.segments.len() == 1 { let name = &path.last_segment().0.contents; if let Some((name, var, _)) = self.find_generic(name) { @@ -327,55 +332,6 @@ impl<'context> Elaborator<'context> { } } - /// Lookup a given struct type by name. - fn lookup_struct_or_error(&mut self, path: Path) -> Option> { - match self.lookup(path) { - Ok(struct_id) => Some(self.get_struct(struct_id)), - Err(error) => { - self.push_err(error); - None - } - } - } - - /// Lookup a given trait by name/path. - fn lookup_trait_or_error(&mut self, path: Path) -> Option<&mut Trait> { - match self.lookup(path) { - Ok(trait_id) => Some(self.get_trait_mut(trait_id)), - Err(error) => { - self.push_err(error); - None - } - } - } - - /// Looks up a given type by name. - /// This will also instantiate any struct types found. - pub(super) fn lookup_type_or_error(&mut self, path: Path) -> Option { - let ident = path.as_ident(); - if ident.map_or(false, |i| i == SELF_TYPE_NAME) { - if let Some(typ) = &self.self_type { - return Some(typ.clone()); - } - } - - match self.lookup(path) { - Ok(struct_id) => { - let struct_type = self.get_struct(struct_id); - let generics = struct_type.borrow().instantiate(&mut self.interner); - Some(Type::Struct(struct_type, generics)) - } - Err(error) => { - self.push_err(error); - None - } - } - } - - fn lookup_type_alias(&mut self, path: Path) -> Option> { - self.lookup(path).ok().map(|id| self.interner.get_type_alias(id)) - } - // this resolves Self::some_static_method, inside an impl block (where we don't have a concrete self_type) // // Returns the trait method, trait constraint, and whether the impl is assumed to exist by a where clause or not @@ -626,9 +582,7 @@ impl<'context> Elaborator<'context> { ) { let mut errors = Vec::new(); actual.unify(expected, &mut errors, make_error); - self.errors.extend(errors.into_iter().map(|error| { - (error.into(), self.file) - })); + self.errors.extend(errors.into_iter().map(|error| (error.into(), self.file))); } /// Wrapper of Type::unify_with_coercions using self.errors @@ -647,9 +601,7 @@ impl<'context> Elaborator<'context> { &mut errors, make_error, ); - self.errors.extend(errors.into_iter().map(|error| { - (error.into(), self.file) - })); + self.errors.extend(errors.into_iter().map(|error| (error.into(), self.file))); } /// Return a fresh integer or field type variable and log it @@ -1397,4 +1349,96 @@ impl<'context> Elaborator<'context> { } } } + + pub fn type_check_function_body(&mut self, body_type: Type, meta: &FuncMeta, body_id: ExprId) { + let (expr_span, empty_function) = self.function_info(body_id); + let declared_return_type = meta.return_type(); + + let func_span = self.interner.expr_span(&body_id); // XXX: We could be more specific and return the span of the last stmt, however stmts do not have spans yet + if let Type::TraitAsType(trait_id, _, generics) = declared_return_type { + if self.interner.lookup_trait_implementation(&body_type, *trait_id, generics).is_err() { + self.push_err(TypeCheckError::TypeMismatchWithSource { + expected: declared_return_type.clone(), + actual: body_type, + span: func_span, + source: Source::Return(meta.return_type.clone(), expr_span), + }); + } + } else { + self.unify_with_coercions(&body_type, declared_return_type, body_id, || { + let mut error = TypeCheckError::TypeMismatchWithSource { + expected: declared_return_type.clone(), + actual: body_type.clone(), + span: func_span, + source: Source::Return(meta.return_type.clone(), expr_span), + }; + + if empty_function { + error = error.add_context( + "implicitly returns `()` as its body has no tail or `return` expression", + ); + } + error + }); + } + } + + fn function_info(&self, function_body_id: ExprId) -> (noirc_errors::Span, bool) { + let (expr_span, empty_function) = + if let HirExpression::Block(block) = self.interner.expression(&function_body_id) { + let last_stmt = block.statements().last(); + let mut span = self.interner.expr_span(&function_body_id); + + if let Some(last_stmt) = last_stmt { + if let HirStatement::Expression(expr) = self.interner.statement(last_stmt) { + span = self.interner.expr_span(&expr); + } + } + + (span, last_stmt.is_none()) + } else { + (self.interner.expr_span(&function_body_id), false) + }; + (expr_span, empty_function) + } + + pub fn verify_trait_constraint( + &mut self, + object_type: &Type, + trait_id: TraitId, + trait_generics: &[Type], + function_ident_id: ExprId, + span: Span, + ) { + match self.interner.lookup_trait_implementation(object_type, trait_id, trait_generics) { + Ok(impl_kind) => { + self.interner.select_impl_for_expression(function_ident_id, impl_kind); + } + Err(erroring_constraints) => { + if erroring_constraints.is_empty() { + self.push_err(TypeCheckError::TypeAnnotationsNeeded { span }); + } else { + // Don't show any errors where try_get_trait returns None. + // This can happen if a trait is used that was never declared. + let constraints = erroring_constraints + .into_iter() + .map(|constraint| { + let r#trait = self.interner.try_get_trait(constraint.trait_id)?; + let mut name = r#trait.name.to_string(); + if !constraint.trait_generics.is_empty() { + let generics = + vecmap(&constraint.trait_generics, ToString::to_string); + name += &format!("<{}>", generics.join(", ")); + } + Some((constraint.typ, name)) + }) + .collect::>>(); + + if let Some(constraints) = constraints { + self.push_err(TypeCheckError::NoMatchingImplFound { constraints, span }); + } + } + } + } + } } diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index 62e539cfe0c..4aac0fec9c3 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -250,7 +250,12 @@ impl DefCollector { let crate_graph = &context.crate_graph[crate_id]; for dep in crate_graph.dependencies.clone() { - errors.extend(CrateDefMap::collect_defs(dep.crate_id, context, use_elaborator, macro_processors)); + errors.extend(CrateDefMap::collect_defs( + dep.crate_id, + context, + use_elaborator, + macro_processors, + )); let dep_def_root = context.def_map(&dep.crate_id).expect("ice: def map was just created").root; @@ -285,12 +290,7 @@ impl DefCollector { inject_prelude(crate_id, context, crate_root, &mut def_collector.imports); for submodule in submodules { - inject_prelude( - crate_id, - context, - LocalModuleId(submodule), - &mut def_collector.imports, - ); + inject_prelude(crate_id, context, LocalModuleId(submodule), &mut def_collector.imports); } // Resolve unresolved imports collected from the crate, one by one. @@ -344,8 +344,7 @@ impl DefCollector { // // Additionally, we must resolve integer globals before structs since structs may refer to // the values of integer globals as numeric generics. - let (literal_globals, other_globals) = - filter_literal_globals(def_collector.items.globals); + let (literal_globals, other_globals) = filter_literal_globals(def_collector.items.globals); resolved_module.resolve_globals(context, literal_globals, crate_id); @@ -382,11 +381,7 @@ impl DefCollector { // // These are resolved after trait impls so that struct methods are chosen // over trait methods if there are name conflicts. - resolved_module.errors.extend(collect_impls( - context, - crate_id, - &def_collector.items.impls, - )); + resolved_module.errors.extend(collect_impls(context, crate_id, &def_collector.items.impls)); // We must wait to resolve non-integer globals until after we resolve structs since struct // globals will need to reference the struct type they're initialized to to ensure they are valid. @@ -451,8 +446,11 @@ fn inject_prelude( .map(|segment| crate::ast::Ident::new(segment.into(), Span::default())) .collect(); - let path = - Path { segments: segments.clone(), kind: crate::ast::PathKind::Dep, span: Span::default() }; + let path = Path { + segments: segments.clone(), + kind: crate::ast::PathKind::Dep, + span: Span::default(), + }; if let Ok(PathResolution { module_def_id, error }) = path_resolver::resolve_path( &context.def_maps, diff --git a/compiler/noirc_frontend/src/hir/def_map/mod.rs b/compiler/noirc_frontend/src/hir/def_map/mod.rs index 976a359bcd7..19e06387d43 100644 --- a/compiler/noirc_frontend/src/hir/def_map/mod.rs +++ b/compiler/noirc_frontend/src/hir/def_map/mod.rs @@ -117,7 +117,14 @@ impl CrateDefMap { }; // Now we want to populate the CrateDefMap using the DefCollector - errors.extend(DefCollector::collect(def_map, context, ast, root_file_id, use_elaborator, macro_processors)); + errors.extend(DefCollector::collect( + def_map, + context, + ast, + root_file_id, + use_elaborator, + macro_processors, + )); errors.extend( parsing_errors.iter().map(|e| (e.clone().into(), root_file_id)).collect::>(), diff --git a/compiler/noirc_frontend/src/hir_def/function.rs b/compiler/noirc_frontend/src/hir_def/function.rs index c38dd41fd3d..ceec9ad8580 100644 --- a/compiler/noirc_frontend/src/hir_def/function.rs +++ b/compiler/noirc_frontend/src/hir_def/function.rs @@ -135,10 +135,7 @@ impl FuncMeta { /// So this method tells the type checker to ignore the return /// of the empty function, which is unit pub fn can_ignore_return_type(&self) -> bool { - match self.kind { - FunctionKind::LowLevel | FunctionKind::Builtin | FunctionKind::Oracle => true, - FunctionKind::Normal | FunctionKind::Recursive => false, - } + self.kind.can_ignore_return_type() } pub fn function_signature(&self) -> FunctionSignature { From b44c98a8efe66f9bb8111cfecd592efa0f95da6a Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Wed, 8 May 2024 15:51:01 -0500 Subject: [PATCH 10/38] Add should_panic --- tooling/nargo_cli/tests/stdlib-tests.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tooling/nargo_cli/tests/stdlib-tests.rs b/tooling/nargo_cli/tests/stdlib-tests.rs index 7e168dfe7ba..70a9354f50a 100644 --- a/tooling/nargo_cli/tests/stdlib-tests.rs +++ b/tooling/nargo_cli/tests/stdlib-tests.rs @@ -65,7 +65,9 @@ fn stdlib_noir_tests() { run_stdlib_tests(false) } +// Once this no longer panics we can use the elaborator by default and remove the old passes #[test] +#[should_panic] fn stdlib_elaborator_tests() { run_stdlib_tests(true) } From 95796466979f3f75ba33d573f2e63fa7c7d05082 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Wed, 8 May 2024 15:59:43 -0500 Subject: [PATCH 11/38] clippy --- .../src/elaborator/expressions.rs | 2 +- compiler/noirc_frontend/src/elaborator/mod.rs | 2 +- .../noirc_frontend/src/elaborator/patterns.rs | 4 ++-- .../noirc_frontend/src/elaborator/scope.rs | 4 ++-- .../src/elaborator/statements.rs | 2 +- .../noirc_frontend/src/elaborator/types.rs | 20 +++++++------------ 6 files changed, 14 insertions(+), 20 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/expressions.rs b/compiler/noirc_frontend/src/elaborator/expressions.rs index 47c03ad50a4..ed8ed5305d1 100644 --- a/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -323,7 +323,7 @@ impl<'context> Elaborator<'context> { &method_ref, object_type, location, - &mut self.interner, + self.interner, ); let func_type = self.type_check_variable(function_name, function_id); diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index c4d7efbbff6..446e5b62ead 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -413,7 +413,7 @@ impl<'context> Elaborator<'context> { fn resolve_trait_by_path(&mut self, path: Path) -> Option { let path_resolver = StandardPathResolver::new(self.module_id()); - let error = match path_resolver.resolve(&self.def_maps, path.clone()) { + let error = match path_resolver.resolve(self.def_maps, path.clone()) { Ok(PathResolution { module_def_id: ModuleDefId::TraitId(trait_id), error }) => { if let Some(error) = error { self.push_err(error); diff --git a/compiler/noirc_frontend/src/elaborator/patterns.rs b/compiler/noirc_frontend/src/elaborator/patterns.rs index cf9acab7712..195d37878f1 100644 --- a/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -365,7 +365,7 @@ impl<'context> Elaborator<'context> { // does not check definition kinds and otherwise expects parameters to // already be typed. if self.interner.definition_type(hir_ident.id) == Type::Error { - let typ = Type::polymorphic_integer_or_field(&mut self.interner); + let typ = Type::polymorphic_integer_or_field(self.interner); self.interner.push_definition_type(hir_ident.id, typ); } } @@ -408,7 +408,7 @@ impl<'context> Elaborator<'context> { // This instantiates a trait's generics as well which need to be set // when the constraint below is later solved for when the function is // finished. How to link the two? - let (typ, bindings) = t.instantiate_with_bindings(bindings, &self.interner); + let (typ, bindings) = t.instantiate_with_bindings(bindings, self.interner); // Push any trait constraints required by this definition to the context // to be checked later when the type of this variable is further constrained. diff --git a/compiler/noirc_frontend/src/elaborator/scope.rs b/compiler/noirc_frontend/src/elaborator/scope.rs index ad9ee466878..cf10dbbc2b2 100644 --- a/compiler/noirc_frontend/src/elaborator/scope.rs +++ b/compiler/noirc_frontend/src/elaborator/scope.rs @@ -46,7 +46,7 @@ impl<'context> Elaborator<'context> { pub(super) fn resolve_path(&mut self, path: Path) -> Result { let resolver = StandardPathResolver::new(self.module_id()); - let path_resolution = resolver.resolve(&self.def_maps, path)?; + let path_resolution = resolver.resolve(self.def_maps, path)?; if let Some(error) = path_resolution.error { self.push_err(error); @@ -184,7 +184,7 @@ impl<'context> Elaborator<'context> { match self.lookup(path) { Ok(struct_id) => { let struct_type = self.get_struct(struct_id); - let generics = struct_type.borrow().instantiate(&mut self.interner); + let generics = struct_type.borrow().instantiate(self.interner); Some(Type::Struct(struct_type, generics)) } Err(error) => { diff --git a/compiler/noirc_frontend/src/elaborator/statements.rs b/compiler/noirc_frontend/src/elaborator/statements.rs index e8f5639620b..a7a2df4041e 100644 --- a/compiler/noirc_frontend/src/elaborator/statements.rs +++ b/compiler/noirc_frontend/src/elaborator/statements.rs @@ -224,7 +224,7 @@ impl<'context> Elaborator<'context> { mutable = definition.mutable; } - let typ = self.interner.definition_type(ident.id).instantiate(&self.interner).0; + let typ = self.interner.definition_type(ident.id).instantiate(self.interner).0; typ.follow_bindings() }; diff --git a/compiler/noirc_frontend/src/elaborator/types.rs b/compiler/noirc_frontend/src/elaborator/types.rs index ba246fd24e0..4c8364b6dda 100644 --- a/compiler/noirc_frontend/src/elaborator/types.rs +++ b/compiler/noirc_frontend/src/elaborator/types.rs @@ -594,20 +594,14 @@ impl<'context> Elaborator<'context> { make_error: impl FnOnce() -> TypeCheckError, ) { let mut errors = Vec::new(); - actual.unify_with_coercions( - expected, - expression, - &mut self.interner, - &mut errors, - make_error, - ); + actual.unify_with_coercions(expected, expression, self.interner, &mut errors, make_error); self.errors.extend(errors.into_iter().map(|error| (error.into(), self.file))); } /// Return a fresh integer or field type variable and log it /// in self.type_variables to default it later. pub(super) fn polymorphic_integer_or_field(&mut self) -> Type { - let typ = Type::polymorphic_integer_or_field(&mut self.interner); + let typ = Type::polymorphic_integer_or_field(self.interner); self.type_variables.push(typ.clone()); typ } @@ -615,7 +609,7 @@ impl<'context> Elaborator<'context> { /// Return a fresh integer type variable and log it /// in self.type_variables to default it later. pub(super) fn polymorphic_integer(&mut self) -> Type { - let typ = Type::polymorphic_integer(&mut self.interner); + let typ = Type::polymorphic_integer(self.interner); self.type_variables.push(typ.clone()); typ } @@ -915,7 +909,7 @@ impl<'context> Elaborator<'context> { // Doing so also ensures a type error if Field is used. // The is_numeric check is to allow impls for custom types to bypass this. if !op.kind.is_valid_for_field_type() && lhs_type.is_numeric() { - let target = Type::polymorphic_integer(&mut self.interner); + let target = Type::polymorphic_integer(self.interner); use crate::ast::BinaryOpKind::*; use TypeCheckError::*; @@ -967,7 +961,7 @@ impl<'context> Elaborator<'context> { || TypeCheckError::InvalidShiftSize { span }, ); let use_impl = if lhs_type.is_numeric() { - let integer_type = Type::polymorphic_integer(&mut self.interner); + let integer_type = Type::polymorphic_integer(self.interner); self.bind_type_variables_for_infix(lhs_type, op, &integer_type, span) } else { true @@ -1048,7 +1042,7 @@ impl<'context> Elaborator<'context> { let the_trait = self.interner.get_trait(trait_method_id.trait_id); let method = &the_trait.methods[trait_method_id.method_index]; - let (method_type, mut bindings) = method.typ.clone().instantiate(&self.interner); + let (method_type, mut bindings) = method.typ.clone().instantiate(self.interner); match method_type { Type::Function(args, _, _) => { @@ -1315,7 +1309,7 @@ impl<'context> Elaborator<'context> { if matches!(expected_object_type.follow_bindings(), Type::MutableReference(_)) { if !matches!(actual_type, Type::MutableReference(_)) { - if let Err(error) = verify_mutable_reference(&self.interner, *object) { + if let Err(error) = verify_mutable_reference(self.interner, *object) { self.push_err(TypeCheckError::ResolverError(error)); } From e4d5f610780b4cf673f74b7a90d4e1dc223def9e Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Wed, 8 May 2024 16:01:06 -0500 Subject: [PATCH 12/38] Fix test --- compiler/noirc_frontend/src/tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/noirc_frontend/src/tests.rs b/compiler/noirc_frontend/src/tests.rs index 5f99e9e347a..4a38309780b 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -81,6 +81,7 @@ mod test { &mut context, program.clone().into_sorted(), root_file_id, + false, &[], // No macro processors )); } From 556e6ef4dacb7b97a748688c870873f635960147 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Thu, 9 May 2024 11:38:50 -0500 Subject: [PATCH 13/38] Fix test --- compiler/noirc_driver/tests/stdlib_warnings.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/noirc_driver/tests/stdlib_warnings.rs b/compiler/noirc_driver/tests/stdlib_warnings.rs index 6f437621123..327c8daad06 100644 --- a/compiler/noirc_driver/tests/stdlib_warnings.rs +++ b/compiler/noirc_driver/tests/stdlib_warnings.rs @@ -24,7 +24,8 @@ fn stdlib_does_not_produce_constant_warnings() -> Result<(), ErrorsAndWarnings> let mut context = Context::new(file_manager, parsed_files); let root_crate_id = prepare_crate(&mut context, file_name); - let ((), warnings) = noirc_driver::check_crate(&mut context, root_crate_id, false, false)?; + let ((), warnings) = + noirc_driver::check_crate(&mut context, root_crate_id, false, false, false)?; assert_eq!(warnings, Vec::new(), "stdlib is producing warnings"); From 7487b03b47b086e956183266f04f2d0e8ec49818 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Thu, 9 May 2024 12:56:11 -0500 Subject: [PATCH 14/38] Elaborate impls --- compiler/noirc_frontend/src/elaborator/mod.rs | 357 +++++++++++++++++- .../src/hir/def_collector/dc_crate.rs | 4 + 2 files changed, 344 insertions(+), 17 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 446e5b62ead..b5f17db2cd9 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -4,14 +4,13 @@ use std::{ rc::Rc, }; -use crate::hir::def_map::CrateDefMap; use crate::{ ast::{ ArrayLiteral, ConstructorExpression, FunctionKind, IfExpression, InfixExpression, Lambda, UnresolvedTraitConstraint, UnresolvedTypeExpression, }, hir::{ - def_collector::dc_crate::CompilationError, + def_collector::{dc_crate::CompilationError, errors::DuplicateType}, resolution::{errors::ResolverError, path_resolver::PathResolver, resolver::LambdaContext}, scope::ScopeForest as GenericScopeForest, type_check::TypeCheckError, @@ -56,6 +55,14 @@ use crate::{ token::FunctionAttribute, Generics, }; +use crate::{ + hir::{ + def_collector::dc_crate::{UnresolvedFunctions, UnresolvedTraitImpl}, + def_map::{CrateDefMap, ModuleData}, + }, + hir_def::traits::TraitImpl, + macros_api::ItemVisibility, +}; mod expressions; mod patterns; @@ -86,7 +93,7 @@ pub struct Elaborator<'context> { interner: &'context mut NodeInterner, - def_maps: &'context BTreeMap, + def_maps: &'context mut BTreeMap, file: FileId, @@ -168,7 +175,7 @@ impl<'context> Elaborator<'context> { scopes: ScopeForest::default(), errors: Vec::new(), interner: &mut context.def_interner, - def_maps: &context.def_maps, + def_maps: &mut context.def_maps, file: FileId::dummy(), in_unconstrained_fn: false, nested_loops: 0, @@ -192,7 +199,7 @@ impl<'context> Elaborator<'context> { pub fn elaborate( context: &'context mut Context, crate_id: CrateId, - items: CollectedItems, + mut items: CollectedItems, ) -> Vec<(CompilationError, FileId)> { let mut this = Self::new(context, crate_id); @@ -205,28 +212,27 @@ impl<'context> Elaborator<'context> { for struct_ in items.types {} - for trait_impl in &items.trait_impls { - // only collect now + for trait_impl in &mut items.trait_impls { + this.collect_trait_impl(trait_impl); } - for impl_ in &items.impls { - // only collect now + for ((typ, module), impls) in &items.impls { + this.collect_impls(typ, *module, impls); } // resolver resolves non-literal globals here for functions in items.functions { - this.file = functions.file_id; - this.trait_id = functions.trait_id; // TODO: Resolve? - for (local_module, id, func) in functions.functions { - this.local_module = local_module; - this.elaborate_function(func, id); - } + this.elaborate_functions(functions); } - for impl_ in items.impls {} + for ((typ, module), impls) in items.impls { + this.elaborate_impls(typ, module, impls); + } - for trait_impl in items.trait_impls {} + for trait_impl in items.trait_impls { + this.elaborate_trait_impl(trait_impl); + } let cycle_errors = this.interner.check_for_dependency_cycles(); this.errors.extend(cycle_errors); @@ -234,6 +240,17 @@ impl<'context> Elaborator<'context> { this.errors } + fn elaborate_functions(&mut self, functions: UnresolvedFunctions) { + self.file = functions.file_id; + self.trait_id = functions.trait_id; // TODO: Resolve? + for (local_module, id, func) in functions.functions { + self.local_module = local_module; + let generics_count = self.generics.len(); + self.elaborate_function(func, id); + self.generics.truncate(generics_count); + } + } + fn elaborate_function(&mut self, mut function: NoirFunction, id: FuncId) { self.current_function = Some(id); self.resolve_where_clause(&mut function.def.where_clause); @@ -779,4 +796,310 @@ impl<'context> Elaborator<'context> { } } } + + fn elaborate_impls( + &mut self, + typ: UnresolvedType, + module: LocalModuleId, + impls: Vec<(Vec, Span, UnresolvedFunctions)>, + ) { + self.generics.clear(); + + for (generics, _, functions) in impls { + self.file = functions.file_id; + self.add_generics(&generics); + let self_type = self.resolve_type(typ.clone()); + self.self_type = Some(self_type.clone()); + + let function_ids = vecmap(&functions.functions, |(_, id, _)| *id); + self.elaborate_functions(functions); + + if self_type != Type::Error { + for method_id in function_ids { + let method_name = self.interner.function_name(&method_id).to_owned(); + + if let Some(first_fn) = + self.interner.add_method(&self_type, method_name.clone(), method_id, false) + { + let error = ResolverError::DuplicateDefinition { + name: method_name, + first_span: self.interner.function_ident(&first_fn).span(), + second_span: self.interner.function_ident(&method_id).span(), + }; + self.push_err(error); + } + } + } + } + } + + fn elaborate_trait_impl(&mut self, trait_impl: UnresolvedTraitImpl) { + self.file = trait_impl.file_id; + self.local_module = trait_impl.module_id; + + let unresolved_type = trait_impl.object_type; + let self_type_span = unresolved_type.span; + self.add_generics(&trait_impl.generics); + + let trait_generics = + vecmap(&trait_impl.trait_generics, |generic| self.resolve_type(generic.clone())); + + let self_type = self.resolve_type(unresolved_type.clone()); + let impl_id = self.interner.next_trait_impl_id(); + + self.self_type = Some(self_type.clone()); + self.current_trait_impl = Some(impl_id); + + let mut methods = trait_impl.methods.function_ids(); + + self.elaborate_functions(trait_impl.methods); + + let maybe_trait_id = trait_impl.trait_id; + if let Some(trait_id) = maybe_trait_id { + for func_id in &methods { + self.interner.set_function_trait(*func_id, self_type.clone(), trait_id); + } + } + + if matches!(self_type, Type::MutableReference(_)) { + let span = self_type_span.unwrap_or_else(|| trait_impl.trait_path.span()); + self.push_err(DefCollectorErrorKind::MutableReferenceInTraitImpl { span }); + } + + if let Some(trait_id) = maybe_trait_id { + let where_clause = trait_impl + .where_clause + .into_iter() + .flat_map(|item| self.resolve_trait_constraint(item)) + .collect(); + + let resolved_trait_impl = Shared::new(TraitImpl { + ident: trait_impl.trait_path.last_segment().clone(), + typ: self_type.clone(), + trait_id, + trait_generics: trait_generics.clone(), + file: trait_impl.file_id, + where_clause, + methods, + }); + + let generics = vecmap(&self.generics, |(_, type_variable, _)| type_variable.clone()); + + if let Err((prev_span, prev_file)) = self.interner.add_trait_implementation( + self_type.clone(), + trait_id, + trait_generics, + impl_id, + generics, + resolved_trait_impl, + ) { + self.push_err(DefCollectorErrorKind::OverlappingImpl { + typ: self_type.clone(), + span: self_type_span.unwrap_or_else(|| trait_impl.trait_path.span()), + }); + + // The 'previous impl defined here' note must be a separate error currently + // since it may be in a different file and all errors have the same file id. + self.file = prev_file; + self.push_err(DefCollectorErrorKind::OverlappingImplNote { span: prev_span }); + self.file = trait_impl.file_id; + } + } + + self.self_type = None; + self.current_trait_impl = None; + self.generics.clear(); + } + + fn collect_impls( + &mut self, + self_type: &UnresolvedType, + module: LocalModuleId, + impls: &[(Vec, Span, UnresolvedFunctions)], + ) { + self.local_module = module; + + for (generics, span, unresolved) in impls { + self.file = unresolved.file_id; + self.declare_method_on_struct(self_type, generics, false, unresolved, *span); + } + } + + fn collect_trait_impl(&mut self, trait_impl: &mut UnresolvedTraitImpl) { + self.local_module = trait_impl.module_id; + self.file = trait_impl.file_id; + trait_impl.trait_id = self.resolve_trait_by_path(trait_impl.trait_path.clone()); + + if let Some(trait_id) = trait_impl.trait_id { + self.collect_trait_impl_methods(trait_id, trait_impl); + + let span = trait_impl.object_type.span.expect("All trait self types should have spans"); + let object_type = &trait_impl.object_type; + let generics = &trait_impl.generics; + self.declare_method_on_struct(object_type, generics, true, &trait_impl.methods, span); + } + } + + fn get_module_mut(&mut self, module: ModuleId) -> &mut ModuleData { + &mut self.def_maps.get_mut(&module.krate).unwrap().modules[module.local_id.0] + } + + fn declare_method_on_struct( + &mut self, + self_type: &UnresolvedType, + generics: &UnresolvedGenerics, + is_trait_impl: bool, + functions: &UnresolvedFunctions, + span: Span, + ) { + let generic_count = self.generics.len(); + self.add_generics(generics); + let typ = self.resolve_type(self_type.clone()); + + if let Type::Struct(struct_type, _generics) = typ { + let struct_type = struct_type.borrow(); + + // `impl`s are only allowed on types defined within the current crate + if !is_trait_impl && struct_type.id.krate() != self.crate_id { + let type_name = struct_type.name.to_string(); + self.push_err(DefCollectorErrorKind::ForeignImpl { span, type_name }); + self.generics.truncate(generic_count); + return; + } + + // Grab the module defined by the struct type. Note that impls are a case + // where the module the methods are added to is not the same as the module + // they are resolved in. + let module = self.get_module_mut(struct_type.id.module_id()); + + for (_, method_id, method) in &functions.functions { + // If this method was already declared, remove it from the module so it cannot + // be accessed with the `TypeName::method` syntax. We'll check later whether the + // object types in each method overlap or not. If they do, we issue an error. + // If not, that is specialization which is allowed. + let name = method.name_ident().clone(); + if module.declare_function(name, ItemVisibility::Public, *method_id).is_err() { + module.remove_function(method.name_ident()); + } + } + // Prohibit defining impls for primitive types if we're not in the stdlib + } else if !is_trait_impl && typ != Type::Error && !self.crate_id.is_stdlib() { + self.push_err(DefCollectorErrorKind::NonStructTypeInImpl { span }); + } + self.generics.truncate(generic_count); + } + + fn collect_trait_impl_methods( + &mut self, + trait_id: TraitId, + trait_impl: &mut UnresolvedTraitImpl, + ) { + self.local_module = trait_impl.module_id; + self.file = trait_impl.file_id; + + // In this Vec methods[i] corresponds to trait.methods[i]. If the impl has no implementation + // for a particular method, the default implementation will be added at that slot. + let mut ordered_methods = Vec::new(); + + // check whether the trait implementation is in the same crate as either the trait or the type + self.check_trait_impl_crate_coherence(trait_id, trait_impl); + + // set of function ids that have a corresponding method in the trait + let mut func_ids_in_trait = HashSet::default(); + + // Temporarily take ownership of the trait's methods so we can iterate over them + // while also mutating the interner + let the_trait = self.interner.get_trait_mut(trait_id); + let methods = std::mem::take(&mut the_trait.methods); + + for method in &methods { + let overrides: Vec<_> = trait_impl + .methods + .functions + .iter() + .filter(|(_, _, f)| f.name() == method.name.0.contents) + .collect(); + + if overrides.is_empty() { + if let Some(default_impl) = &method.default_impl { + // copy 'where' clause from unresolved trait impl + let mut default_impl_clone = default_impl.clone(); + default_impl_clone.def.where_clause.extend(trait_impl.where_clause.clone()); + + let func_id = self.interner.push_empty_fn(); + let module = self.module_id(); + let location = Location::new(default_impl.def.span, trait_impl.file_id); + self.interner.push_function(func_id, &default_impl.def, module, location); + func_ids_in_trait.insert(func_id); + ordered_methods.push(( + method.default_impl_module_id, + func_id, + *default_impl_clone, + )); + } else { + self.push_err(DefCollectorErrorKind::TraitMissingMethod { + trait_name: self.interner.get_trait(trait_id).name.clone(), + method_name: method.name.clone(), + trait_impl_span: trait_impl + .object_type + .span + .expect("type must have a span"), + }); + } + } else { + for (_, func_id, _) in &overrides { + func_ids_in_trait.insert(*func_id); + } + + if overrides.len() > 1 { + self.push_err(DefCollectorErrorKind::Duplicate { + typ: DuplicateType::TraitAssociatedFunction, + first_def: overrides[0].2.name_ident().clone(), + second_def: overrides[1].2.name_ident().clone(), + }); + } + + ordered_methods.push(overrides[0].clone()); + } + } + + // Restore the methods that were taken before the for loop + let the_trait = self.interner.get_trait_mut(trait_id); + the_trait.set_methods(methods); + + // Emit MethodNotInTrait error for methods in the impl block that + // don't have a corresponding method signature defined in the trait + for (_, func_id, func) in &trait_impl.methods.functions { + if !func_ids_in_trait.contains(func_id) { + let trait_name = the_trait.name.clone(); + let impl_method = func.name_ident().clone(); + let error = DefCollectorErrorKind::MethodNotInTrait { trait_name, impl_method }; + self.errors.push((error.into(), self.file)); + } + } + + trait_impl.methods.functions = ordered_methods; + trait_impl.methods.trait_id = Some(trait_id); + } + + fn check_trait_impl_crate_coherence( + &mut self, + trait_id: TraitId, + trait_impl: &UnresolvedTraitImpl, + ) { + self.local_module = trait_impl.module_id; + self.file = trait_impl.file_id; + + let object_crate = match self.resolve_type(trait_impl.object_type.clone()) { + Type::Struct(struct_type, _) => struct_type.borrow().id.krate(), + _ => CrateId::Dummy, + }; + + let the_trait = self.interner.get_trait(trait_id); + if self.crate_id != the_trait.crate_id && self.crate_id != object_crate { + self.push_err(DefCollectorErrorKind::TraitImplOrphaned { + span: trait_impl.object_type.span.expect("object type must have a span"), + }); + } + } } diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index 4aac0fec9c3..d2eaf79b0f0 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -54,6 +54,10 @@ impl UnresolvedFunctions { self.functions.push((mod_id, func_id, func)); } + pub fn function_ids(&self) -> Vec { + vecmap(&self.functions, |(_, id, _)| *id) + } + pub fn resolve_trait_bounds_trait_ids( &mut self, def_maps: &BTreeMap, From 82d58714670ae4a95ddea54d0b399cf93e74e29c Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Thu, 9 May 2024 12:57:22 -0500 Subject: [PATCH 15/38] Fix yet another missed line --- tooling/lsp/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/lsp/src/lib.rs b/tooling/lsp/src/lib.rs index be9b83e02f6..05345b96c80 100644 --- a/tooling/lsp/src/lib.rs +++ b/tooling/lsp/src/lib.rs @@ -345,7 +345,7 @@ fn prepare_package_from_source_string() { let mut state = LspState::new(&client, acvm::blackbox_solver::StubbedBlackBoxSolver); let (mut context, crate_id) = crate::prepare_source(source.to_string(), &mut state); - let _check_result = noirc_driver::check_crate(&mut context, crate_id, false, false); + let _check_result = noirc_driver::check_crate(&mut context, crate_id, false, false, false); let main_func_id = context.get_main_function(&crate_id); assert!(main_func_id.is_some()); } From e9e69f4ce9698563f3b81d5b188e8dc1e2595704 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Thu, 9 May 2024 14:21:11 -0500 Subject: [PATCH 16/38] Resolve weird git merge --- compiler/noirc_frontend/src/tests.rs | 151 --------------------------- 1 file changed, 151 deletions(-) diff --git a/compiler/noirc_frontend/src/tests.rs b/compiler/noirc_frontend/src/tests.rs index bc04e4505e6..b9ea41a4fe5 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -586,157 +586,6 @@ fn check_trait_impl_for_non_type() { } } - pub(crate) fn remove_experimental_warnings(errors: &mut Vec<(CompilationError, FileId)>) { - errors.retain(|(error, _)| match error { - CompilationError::ParseError(error) => { - !matches!(error.reason(), Some(ParserErrorReason::ExperimentalFeature(..))) - } - _ => true, - }); - } - - pub(crate) fn get_program( - src: &str, - ) -> (ParsedModule, Context, Vec<(CompilationError, FileId)>) { - let root = std::path::Path::new("/"); - let fm = FileManager::new(root); - - let mut context = Context::new(fm, Default::default()); - context.def_interner.populate_dummy_operator_traits(); - let root_file_id = FileId::dummy(); - let root_crate_id = context.crate_graph.add_crate_root(root_file_id); - - let (program, parser_errors) = parse_program(src); - let mut errors = vecmap(parser_errors, |e| (e.into(), root_file_id)); - remove_experimental_warnings(&mut errors); - - if !has_parser_error(&errors) { - // Allocate a default Module for the root, giving it a ModuleId - let mut modules: Arena = Arena::default(); - let location = Location::new(Default::default(), root_file_id); - let root = modules.insert(ModuleData::new(None, location, false)); - - let def_map = CrateDefMap { - root: LocalModuleId(root), - modules, - krate: root_crate_id, - extern_prelude: BTreeMap::new(), - }; - - // Now we want to populate the CrateDefMap using the DefCollector - errors.extend(DefCollector::collect( - def_map, - &mut context, - program.clone().into_sorted(), - root_file_id, - false, - &[], // No macro processors - )); - } - (program, context, errors) - } - - pub(crate) fn get_program_errors(src: &str) -> Vec<(CompilationError, FileId)> { - get_program(src).2 - } - - #[test] - fn check_trait_implemented_for_all_t() { - let src = " - trait Default { - fn default() -> Self; - } - - trait Eq { - fn eq(self, other: Self) -> bool; - } - - trait IsDefault { - fn is_default(self) -> bool; - } - - impl IsDefault for T where T: Default + Eq { - fn is_default(self) -> bool { - self.eq(T::default()) - } - } - - struct Foo { - a: u64, - } - - impl Eq for Foo { - fn eq(self, other: Foo) -> bool { self.a == other.a } - } - - impl Default for u64 { - fn default() -> Self { - 0 - } - } - - impl Default for Foo { - fn default() -> Self { - Foo { a: Default::default() } - } - } - - fn main(a: Foo) -> pub bool { - a.is_default() - }"; - - let errors = get_program_errors(src); - errors.iter().for_each(|err| println!("{:?}", err)); - assert!(errors.is_empty()); - } - - #[test] - fn check_trait_implementation_duplicate_method() { - let src = " - trait Default { - fn default(x: Field, y: Field) -> Field; - } - - struct Foo { - bar: Field, - array: [Field; 2], - } - - impl Default for Foo { - // Duplicate trait methods should not compile - fn default(x: Field, y: Field) -> Field { - y + 2 * x - } - // Duplicate trait methods should not compile - fn default(x: Field, y: Field) -> Field { - x + 2 * y - } - } - - fn main() {}"; - - let errors = get_program_errors(src); - assert!(!has_parser_error(&errors)); - assert!(errors.len() == 1, "Expected 1 error, got: {:?}", errors); - - for (err, _file_id) in errors { - match &err { - CompilationError::DefinitionError(DefCollectorErrorKind::Duplicate { - typ, - first_def, - second_def, - }) => { - assert_eq!(typ, &DuplicateType::TraitAssociatedFunction); - assert_eq!(first_def, "default"); - assert_eq!(second_def, "default"); - } - _ => { - panic!("No other errors are expected! Found = {:?}", err); - } - }; - } - } - #[test] fn check_impl_struct_not_trait() { let src = " From d7d91ae32259f83036a9915a82e11e9da9f86457 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Thu, 9 May 2024 14:22:38 -0500 Subject: [PATCH 17/38] Wrong arg order --- compiler/noirc_frontend/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/noirc_frontend/src/tests.rs b/compiler/noirc_frontend/src/tests.rs index b9ea41a4fe5..fb80a7d8018 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -81,8 +81,8 @@ pub(crate) fn get_program(src: &str) -> (ParsedModule, Context, Vec<(Compilation &mut context, program.clone().into_sorted(), root_file_id, - &[], // No macro processors false, + &[], // No macro processors )); } (program, context, errors) From 75c6451a09bc188b19eb4ea857f1fe50df39c8af Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Fri, 10 May 2024 11:32:43 -0500 Subject: [PATCH 18/38] Code review --- compiler/noirc_frontend/src/elaborator/mod.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index b5f17db2cd9..0f9d22ca9b5 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -854,19 +854,16 @@ impl<'context> Elaborator<'context> { self.elaborate_functions(trait_impl.methods); - let maybe_trait_id = trait_impl.trait_id; - if let Some(trait_id) = maybe_trait_id { - for func_id in &methods { - self.interner.set_function_trait(*func_id, self_type.clone(), trait_id); - } - } - if matches!(self_type, Type::MutableReference(_)) { let span = self_type_span.unwrap_or_else(|| trait_impl.trait_path.span()); self.push_err(DefCollectorErrorKind::MutableReferenceInTraitImpl { span }); } - if let Some(trait_id) = maybe_trait_id { + if let Some(trait_id) = trait_impl.trait_id { + for func_id in &methods { + self.interner.set_function_trait(*func_id, self_type.clone(), trait_id); + } + let where_clause = trait_impl .where_clause .into_iter() @@ -941,7 +938,8 @@ impl<'context> Elaborator<'context> { } fn get_module_mut(&mut self, module: ModuleId) -> &mut ModuleData { - &mut self.def_maps.get_mut(&module.krate).unwrap().modules[module.local_id.0] + let message = "A crate should always be present for a given crate id"; + &mut self.def_maps.get_mut(&module.krate).expect(message).modules[module.local_id.0] } fn declare_method_on_struct( From bbecf4199e42b8203c159a9398df2f9abb2ac226 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Fri, 10 May 2024 14:42:46 -0500 Subject: [PATCH 19/38] Start work on traits + types (+ globals)? --- compiler/noirc_frontend/src/elaborator/mod.rs | 58 +++++++++++++++++-- .../noirc_frontend/src/elaborator/traits.rs | 45 ++++++++++++++ 2 files changed, 97 insertions(+), 6 deletions(-) create mode 100644 compiler/noirc_frontend/src/elaborator/traits.rs diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 0f9d22ca9b5..6b6fe48c9f7 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -10,7 +10,7 @@ use crate::{ UnresolvedTraitConstraint, UnresolvedTypeExpression, }, hir::{ - def_collector::{dc_crate::CompilationError, errors::DuplicateType}, + def_collector::{dc_crate::{CompilationError, UnresolvedTypeAlias, UnresolvedTrait, UnresolvedStruct}, errors::DuplicateType}, resolution::{errors::ResolverError, path_resolver::PathResolver, resolver::LambdaContext}, scope::ScopeForest as GenericScopeForest, type_check::TypeCheckError, @@ -30,7 +30,7 @@ use crate::{ MethodCallExpression, NodeInterner, NoirFunction, PrefixExpression, Statement, StatementKind, StructId, }, - node_interner::{DefinitionKind, DependencyId, ExprId, FuncId, StmtId, TraitId}, + node_interner::{DefinitionKind, DependencyId, ExprId, FuncId, StmtId, TraitId, TypeAliasId}, Shared, StructType, Type, TypeVariable, }; use crate::{ @@ -68,6 +68,7 @@ mod expressions; mod patterns; mod scope; mod statements; +mod traits; mod types; use fm::FileId; @@ -206,11 +207,12 @@ impl<'context> Elaborator<'context> { // the resolver filters literal globals first for global in items.globals {} - for alias in items.type_aliases {} - - for trait_ in items.traits {} + for (alias_id, alias) in items.type_aliases { + this.define_type_alias(alias_id, alias); + } - for struct_ in items.types {} + this.collect_traits(items.traits); + this.collect_struct_definitions(items.types); for trait_impl in &mut items.trait_impls { this.collect_trait_impl(trait_impl); @@ -1100,4 +1102,48 @@ impl<'context> Elaborator<'context> { }); } } + + fn define_type_alias(&self, alias_id: TypeAliasId, alias: UnresolvedTypeAlias) { + self.file = alias.file_id; + self.local_module = alias.module_id; + let (typ, generics, resolver_errors) = self.resolve_type_alias(alias.type_alias_def, alias_id); + self.interner.set_type_alias(alias_id, typ, generics); + } + + fn collect_struct_definitions(&mut self, structs: BTreeMap) { + // This is necessary to avoid cloning the entire struct map + // when adding checks after each struct field is resolved. + let struct_ids = structs.keys().copied().collect::>(); + + // Resolve each field in each struct. + // Each struct should already be present in the NodeInterner after def collection. + for (type_id, typ) in structs { + self.file = typ.file_id; + let (generics, fields) = self.resolve_struct_fields(type_id, typ); + + self.interner.update_struct(type_id, |struct_def| { + struct_def.set_fields(fields); + struct_def.generics = generics; + }); + } + + // Check whether the struct fields have nested slices + // We need to check after all structs are resolved to + // make sure every struct's fields is accurately set. + for id in struct_ids { + let struct_type = self.interner.get_struct(id); + // Only handle structs without generics as any generics args will be checked + // after monomorphization when performing SSA codegen + if struct_type.borrow().generics.is_empty() { + let fields = struct_type.borrow().get_fields(&[]); + for (_, field_type) in fields.iter() { + if field_type.is_nested_slice() { + let location = struct_type.borrow().location; + self.file = location.file; + self.push_err(ResolverError::NestedSlices { span: location.span }); + } + } + } + } + } } diff --git a/compiler/noirc_frontend/src/elaborator/traits.rs b/compiler/noirc_frontend/src/elaborator/traits.rs new file mode 100644 index 00000000000..6ab62880979 --- /dev/null +++ b/compiler/noirc_frontend/src/elaborator/traits.rs @@ -0,0 +1,45 @@ +use std::collections::BTreeMap; + +use crate::{node_interner::TraitId, hir::def_collector::dc_crate::UnresolvedTrait}; + +use super::Elaborator; + + +impl<'context> Elaborator<'context> { + pub fn collect_trait(&mut self, traits: BTreeMap) { + for (trait_id, unresolved_trait) in &traits { + self.interner.push_empty_trait(*trait_id, unresolved_trait); + } + let mut all_errors = Vec::new(); + + for (trait_id, unresolved_trait) in traits { + let generics = vecmap(&unresolved_trait.trait_def.generics, |_| { + TypeVariable::unbound(self.interner.next_type_variable_id()) + }); + + // Resolve order + // 1. Trait Types ( Trait constants can have a trait type, therefore types before constants) + let _ = self.resolve_trait_types(&unresolved_trait); + // 2. Trait Constants ( Trait's methods can use trait types & constants, therefore they should be after) + let _ = self.resolve_trait_constants(&unresolved_trait); + // 3. Trait Methods + let (methods, errors) = + self.resolve_trait_methods(trait_id, &unresolved_trait, &generics); + + all_errors.extend(errors); + + self.interner.update_trait(trait_id, |trait_def| { + trait_def.set_methods(methods); + trait_def.generics = generics; + }); + + // This check needs to be after the trait's methods are set since + // the interner may set `interner.ordering_type` based on the result type + // of the Cmp trait, if this is it. + if self.crate_id.is_stdlib() { + self.interner.try_add_operator_trait(trait_id); + } + } + all_errors + } +} From 7a37bf330ed2cf6c2635348da06ecddf39783be2 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Tue, 21 May 2024 15:12:21 -0500 Subject: [PATCH 20/38] Add globals --- compiler/noirc_frontend/src/elaborator/mod.rs | 52 +++++++++++++++++-- .../noirc_frontend/src/elaborator/traits.rs | 31 +++++++---- .../noirc_frontend/src/elaborator/types.rs | 10 +++- .../src/hir/def_collector/dc_crate.rs | 2 +- compiler/noirc_frontend/src/node_interner.rs | 12 +++++ 5 files changed, 90 insertions(+), 17 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 81fe63b7463..6764137235e 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -10,7 +10,13 @@ use crate::{ UnresolvedTraitConstraint, UnresolvedTypeExpression, }, hir::{ - def_collector::{dc_crate::{CompilationError, UnresolvedTypeAlias, UnresolvedTrait, UnresolvedStruct}, errors::DuplicateType}, + def_collector::{ + dc_crate::{ + filter_literal_globals, CompilationError, UnresolvedGlobal, UnresolvedStruct, + UnresolvedTrait, UnresolvedTypeAlias, + }, + errors::DuplicateType, + }, resolution::{errors::ResolverError, path_resolver::PathResolver, resolver::LambdaContext}, scope::ScopeForest as GenericScopeForest, type_check::TypeCheckError, @@ -22,13 +28,14 @@ use crate::{ HirInfixExpression, HirLambda, HirMemberAccess, HirMethodCallExpression, HirMethodReference, HirPrefixExpression, }, + stmt::HirLetStatement, traits::TraitConstraint, }, macros_api::{ BlockExpression, CallExpression, CastExpression, Expression, ExpressionKind, HirExpression, HirLiteral, HirStatement, Ident, IndexExpression, Literal, MemberAccessExpression, - MethodCallExpression, NodeInterner, NoirFunction, PrefixExpression, Statement, - StatementKind, StructId, NoirStruct, + MethodCallExpression, NodeInterner, NoirFunction, NoirStruct, Pattern, PrefixExpression, + SecondaryAttribute, Statement, StatementKind, StructId, }, node_interner::{DefinitionKind, DependencyId, ExprId, FuncId, StmtId, TraitId, TypeAliasId}, Shared, StructType, Type, TypeVariable, @@ -204,8 +211,12 @@ impl<'context> Elaborator<'context> { ) -> Vec<(CompilationError, FileId)> { let mut this = Self::new(context, crate_id); + let (literal_globals, other_globals) = filter_literal_globals(items.globals); + // the resolver filters literal globals first - for global in items.globals {} + for global in literal_globals { + this.elaborate_global(global); + } for (alias_id, alias) in items.type_aliases { this.define_type_alias(alias_id, alias); @@ -222,7 +233,9 @@ impl<'context> Elaborator<'context> { this.collect_impls(typ, *module, impls); } - // resolver resolves non-literal globals here + for global in other_globals { + this.elaborate_global(global); + } for functions in items.functions { this.elaborate_functions(functions); @@ -1170,4 +1183,33 @@ impl<'context> Elaborator<'context> { (generics, fields) } + + fn elaborate_global(&mut self, global: UnresolvedGlobal) { + self.local_module = global.module_id; + self.file = global.file_id; + + let global_id = global.global_id; + self.current_item = Some(DependencyId::Global(global_id)); + + let definition_kind = DefinitionKind::Global(global_id); + let let_stmt = global.stmt_def; + + if !self.in_contract + && let_stmt.attributes.iter().any(|attr| matches!(attr, SecondaryAttribute::Abi(_))) + { + let span = let_stmt.pattern.span(); + self.push_err(ResolverError::AbiAttributeOutsideContract { span }); + } + + if !let_stmt.comptime && matches!(let_stmt.pattern, Pattern::Mutable(..)) { + let span = let_stmt.pattern.span(); + self.push_err(ResolverError::MutableGlobal { span }); + } + + let (let_statement, _typ) = self.elaborate_let(let_stmt); + + let statement_id = self.interner.get_global(global_id).let_statement; + self.interner.get_global_definition_mut(global_id).kind = definition_kind; + self.interner.replace_statement(statement_id, let_statement); + } } diff --git a/compiler/noirc_frontend/src/elaborator/traits.rs b/compiler/noirc_frontend/src/elaborator/traits.rs index 84e8e6aca8e..e7018d900d8 100644 --- a/compiler/noirc_frontend/src/elaborator/traits.rs +++ b/compiler/noirc_frontend/src/elaborator/traits.rs @@ -3,11 +3,27 @@ use std::collections::BTreeMap; use iter_extended::vecmap; use noirc_errors::Location; -use crate::{node_interner::{TraitId, FuncId}, hir::{def_collector::dc_crate::UnresolvedTrait, resolution::path_resolver::StandardPathResolver, def_map::ModuleId}, TypeVariable, hir_def::{traits::{TraitType, TraitConstant, TraitFunction}, function::{HirFunction, FuncMeta}}, Generics, ast::{TraitItem, UnresolvedGenerics, UnresolvedTraitConstraint, FunctionKind}, Type, TypeVariableKind, macros_api::{Ident, UnresolvedType, FunctionReturnType, FunctionDefinition, ItemVisibility, Visibility, Pattern, BlockExpression, NoirFunction, Param}, token::Attributes}; +use crate::{ + ast::{FunctionKind, TraitItem, UnresolvedGenerics, UnresolvedTraitConstraint}, + hir::{ + def_collector::dc_crate::UnresolvedTrait, def_map::ModuleId, + resolution::path_resolver::StandardPathResolver, + }, + hir_def::{ + function::{FuncMeta, HirFunction}, + traits::{TraitConstant, TraitFunction, TraitType}, + }, + macros_api::{ + BlockExpression, FunctionDefinition, FunctionReturnType, Ident, ItemVisibility, + NoirFunction, Param, Pattern, UnresolvedType, Visibility, + }, + node_interner::{FuncId, TraitId}, + token::Attributes, + Generics, Type, TypeVariable, TypeVariableKind, +}; use super::Elaborator; - impl<'context> Elaborator<'context> { pub fn collect_traits(&mut self, traits: BTreeMap) { for (trait_id, unresolved_trait) in &traits { @@ -25,8 +41,7 @@ impl<'context> Elaborator<'context> { // 2. Trait Constants ( Trait's methods can use trait types & constants, therefore they should be after) let _ = self.resolve_trait_constants(&unresolved_trait); // 3. Trait Methods - let methods = - self.resolve_trait_methods(trait_id, &unresolved_trait, &generics); + let methods = self.resolve_trait_methods(trait_id, &unresolved_trait, &generics); self.interner.update_trait(trait_id, |trait_def| { trait_def.set_methods(methods); @@ -42,10 +57,7 @@ impl<'context> Elaborator<'context> { } } - fn resolve_trait_types( - &mut self, - _unresolved_trait: &UnresolvedTrait, - ) -> Vec { + fn resolve_trait_types(&mut self, _unresolved_trait: &UnresolvedTrait) -> Vec { // TODO vec![] } @@ -120,7 +132,8 @@ impl<'context> Elaborator<'context> { }; let no_environment = Box::new(Type::Unit); - let function_type = Type::Function(arguments, Box::new(return_type), no_environment); + let function_type = + Type::Function(arguments, Box::new(return_type), no_environment); functions.push(TraitFunction { name: name.clone(), diff --git a/compiler/noirc_frontend/src/elaborator/types.rs b/compiler/noirc_frontend/src/elaborator/types.rs index bd66fe99e2a..b0d29cffde7 100644 --- a/compiler/noirc_frontend/src/elaborator/types.rs +++ b/compiler/noirc_frontend/src/elaborator/types.rs @@ -4,7 +4,10 @@ use iter_extended::vecmap; use noirc_errors::{Location, Span}; use crate::{ - ast::{BinaryOpKind, IntegerBitSize, UnresolvedTraitConstraint, UnresolvedTypeExpression, NoirTypeAlias, UnresolvedGenerics}, + ast::{ + BinaryOpKind, IntegerBitSize, NoirTypeAlias, UnresolvedGenerics, UnresolvedTraitConstraint, + UnresolvedTypeExpression, + }, hir::{ def_map::ModuleDefId, resolution::{ @@ -26,7 +29,10 @@ use crate::{ HirExpression, HirLiteral, HirStatement, Path, PathKind, SecondaryAttribute, Signedness, UnaryOp, UnresolvedType, UnresolvedTypeData, }, - node_interner::{DefinitionKind, ExprId, GlobalId, TraitId, TraitImplKind, TraitMethodId, TypeAliasId, DependencyId}, + node_interner::{ + DefinitionKind, DependencyId, ExprId, GlobalId, TraitId, TraitImplKind, TraitMethodId, + TypeAliasId, + }, Generics, Shared, StructType, Type, TypeAlias, TypeBinding, TypeVariable, TypeVariableKind, }; diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index d2eaf79b0f0..afec3839599 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -486,7 +486,7 @@ fn inject_prelude( /// Separate the globals Vec into two. The first element in the tuple will be the /// literal globals, except for arrays, and the second will be all other globals. /// We exclude array literals as they can contain complex types -fn filter_literal_globals( +pub fn filter_literal_globals( globals: Vec, ) -> (Vec, Vec) { globals.into_iter().partition(|global| match &global.stmt_def.expression.kind { diff --git a/compiler/noirc_frontend/src/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs index faf89016f96..abe6d4144ec 100644 --- a/compiler/noirc_frontend/src/node_interner.rs +++ b/compiler/noirc_frontend/src/node_interner.rs @@ -912,6 +912,13 @@ impl NodeInterner { &self.definitions[id.0] } + /// Retrieves the definition where the given id was defined. + /// This will panic if given DefinitionId::dummy_id. Use try_definition for + /// any call with a possibly undefined variable. + pub fn definition_mut(&mut self, id: DefinitionId) -> &mut DefinitionInfo { + &mut self.definitions[id.0] + } + /// Tries to retrieve the given id's definition. /// This function should be used during name resolution or type checking when we cannot be sure /// all variables have corresponding definitions (in case of an error in the user's code). @@ -993,6 +1000,11 @@ impl NodeInterner { self.definition(global.definition_id) } + pub fn get_global_definition_mut(&mut self, global_id: GlobalId) -> &mut DefinitionInfo { + let global = self.get_global(global_id); + self.definition_mut(global.definition_id) + } + pub fn get_all_globals(&self) -> &[GlobalInfo] { &self.globals } From 464d58ca23a553577e28432823028608aa52ce9a Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Tue, 21 May 2024 15:13:55 -0500 Subject: [PATCH 21/38] Format --- compiler/noirc_frontend/src/elaborator/mod.rs | 9 ++++-- .../noirc_frontend/src/elaborator/traits.rs | 31 +++++++++++++------ .../noirc_frontend/src/elaborator/types.rs | 10 ++++-- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 81fe63b7463..50b39757fda 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -10,7 +10,10 @@ use crate::{ UnresolvedTraitConstraint, UnresolvedTypeExpression, }, hir::{ - def_collector::{dc_crate::{CompilationError, UnresolvedTypeAlias, UnresolvedTrait, UnresolvedStruct}, errors::DuplicateType}, + def_collector::{ + dc_crate::{CompilationError, UnresolvedStruct, UnresolvedTrait, UnresolvedTypeAlias}, + errors::DuplicateType, + }, resolution::{errors::ResolverError, path_resolver::PathResolver, resolver::LambdaContext}, scope::ScopeForest as GenericScopeForest, type_check::TypeCheckError, @@ -27,8 +30,8 @@ use crate::{ macros_api::{ BlockExpression, CallExpression, CastExpression, Expression, ExpressionKind, HirExpression, HirLiteral, HirStatement, Ident, IndexExpression, Literal, MemberAccessExpression, - MethodCallExpression, NodeInterner, NoirFunction, PrefixExpression, Statement, - StatementKind, StructId, NoirStruct, + MethodCallExpression, NodeInterner, NoirFunction, NoirStruct, PrefixExpression, Statement, + StatementKind, StructId, }, node_interner::{DefinitionKind, DependencyId, ExprId, FuncId, StmtId, TraitId, TypeAliasId}, Shared, StructType, Type, TypeVariable, diff --git a/compiler/noirc_frontend/src/elaborator/traits.rs b/compiler/noirc_frontend/src/elaborator/traits.rs index 84e8e6aca8e..e7018d900d8 100644 --- a/compiler/noirc_frontend/src/elaborator/traits.rs +++ b/compiler/noirc_frontend/src/elaborator/traits.rs @@ -3,11 +3,27 @@ use std::collections::BTreeMap; use iter_extended::vecmap; use noirc_errors::Location; -use crate::{node_interner::{TraitId, FuncId}, hir::{def_collector::dc_crate::UnresolvedTrait, resolution::path_resolver::StandardPathResolver, def_map::ModuleId}, TypeVariable, hir_def::{traits::{TraitType, TraitConstant, TraitFunction}, function::{HirFunction, FuncMeta}}, Generics, ast::{TraitItem, UnresolvedGenerics, UnresolvedTraitConstraint, FunctionKind}, Type, TypeVariableKind, macros_api::{Ident, UnresolvedType, FunctionReturnType, FunctionDefinition, ItemVisibility, Visibility, Pattern, BlockExpression, NoirFunction, Param}, token::Attributes}; +use crate::{ + ast::{FunctionKind, TraitItem, UnresolvedGenerics, UnresolvedTraitConstraint}, + hir::{ + def_collector::dc_crate::UnresolvedTrait, def_map::ModuleId, + resolution::path_resolver::StandardPathResolver, + }, + hir_def::{ + function::{FuncMeta, HirFunction}, + traits::{TraitConstant, TraitFunction, TraitType}, + }, + macros_api::{ + BlockExpression, FunctionDefinition, FunctionReturnType, Ident, ItemVisibility, + NoirFunction, Param, Pattern, UnresolvedType, Visibility, + }, + node_interner::{FuncId, TraitId}, + token::Attributes, + Generics, Type, TypeVariable, TypeVariableKind, +}; use super::Elaborator; - impl<'context> Elaborator<'context> { pub fn collect_traits(&mut self, traits: BTreeMap) { for (trait_id, unresolved_trait) in &traits { @@ -25,8 +41,7 @@ impl<'context> Elaborator<'context> { // 2. Trait Constants ( Trait's methods can use trait types & constants, therefore they should be after) let _ = self.resolve_trait_constants(&unresolved_trait); // 3. Trait Methods - let methods = - self.resolve_trait_methods(trait_id, &unresolved_trait, &generics); + let methods = self.resolve_trait_methods(trait_id, &unresolved_trait, &generics); self.interner.update_trait(trait_id, |trait_def| { trait_def.set_methods(methods); @@ -42,10 +57,7 @@ impl<'context> Elaborator<'context> { } } - fn resolve_trait_types( - &mut self, - _unresolved_trait: &UnresolvedTrait, - ) -> Vec { + fn resolve_trait_types(&mut self, _unresolved_trait: &UnresolvedTrait) -> Vec { // TODO vec![] } @@ -120,7 +132,8 @@ impl<'context> Elaborator<'context> { }; let no_environment = Box::new(Type::Unit); - let function_type = Type::Function(arguments, Box::new(return_type), no_environment); + let function_type = + Type::Function(arguments, Box::new(return_type), no_environment); functions.push(TraitFunction { name: name.clone(), diff --git a/compiler/noirc_frontend/src/elaborator/types.rs b/compiler/noirc_frontend/src/elaborator/types.rs index bd66fe99e2a..b0d29cffde7 100644 --- a/compiler/noirc_frontend/src/elaborator/types.rs +++ b/compiler/noirc_frontend/src/elaborator/types.rs @@ -4,7 +4,10 @@ use iter_extended::vecmap; use noirc_errors::{Location, Span}; use crate::{ - ast::{BinaryOpKind, IntegerBitSize, UnresolvedTraitConstraint, UnresolvedTypeExpression, NoirTypeAlias, UnresolvedGenerics}, + ast::{ + BinaryOpKind, IntegerBitSize, NoirTypeAlias, UnresolvedGenerics, UnresolvedTraitConstraint, + UnresolvedTypeExpression, + }, hir::{ def_map::ModuleDefId, resolution::{ @@ -26,7 +29,10 @@ use crate::{ HirExpression, HirLiteral, HirStatement, Path, PathKind, SecondaryAttribute, Signedness, UnaryOp, UnresolvedType, UnresolvedTypeData, }, - node_interner::{DefinitionKind, ExprId, GlobalId, TraitId, TraitImplKind, TraitMethodId, TypeAliasId, DependencyId}, + node_interner::{ + DefinitionKind, DependencyId, ExprId, GlobalId, TraitId, TraitImplKind, TraitMethodId, + TypeAliasId, + }, Generics, Shared, StructType, Type, TypeAlias, TypeBinding, TypeVariable, TypeVariableKind, }; From 397fe78339553e093c79b8f87c770a0ece76e918 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Wed, 22 May 2024 10:07:29 -0500 Subject: [PATCH 22/38] Copy over comments from dc_crate --- compiler/noirc_frontend/src/elaborator/mod.rs | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 6764137235e..a3d06ad9ab2 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -211,7 +211,12 @@ impl<'context> Elaborator<'context> { ) -> Vec<(CompilationError, FileId)> { let mut this = Self::new(context, crate_id); - let (literal_globals, other_globals) = filter_literal_globals(items.globals); + // We must first resolve and intern the globals before we can resolve any stmts inside each function. + // Each function uses its own resolver with a newly created ScopeForest, and must be resolved again to be within a function's scope + // + // Additionally, we must resolve integer globals before structs since structs may refer to + // the values of integer globals as numeric generics. + let (literal_globals, non_literal_globals) = filter_literal_globals(items.globals); // the resolver filters literal globals first for global in literal_globals { @@ -223,17 +228,30 @@ impl<'context> Elaborator<'context> { } this.collect_traits(items.traits); + + // Must resolve structs before we resolve globals. this.collect_struct_definitions(items.types); + // Bind trait impls to their trait. Collect trait functions, that have a + // default implementation, which hasn't been overridden. for trait_impl in &mut items.trait_impls { this.collect_trait_impl(trait_impl); } + // Before we resolve any function symbols we must go through our impls and + // re-collect the methods within into their proper module. This cannot be + // done during def collection since we need to be able to resolve the type of + // the impl since that determines the module we should collect into. + // + // These are resolved after trait impls so that struct methods are chosen + // over trait methods if there are name conflicts. for ((typ, module), impls) in &items.impls { this.collect_impls(typ, *module, impls); } - for global in other_globals { + // We must wait to resolve non-literal globals until after we resolve structs since struct + // globals will need to reference the struct type they're initialized to to ensure they are valid. + for global in non_literal_globals { this.elaborate_global(global); } From afcf26eea4e91bc02c19197bfe973b5862a31c55 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Wed, 22 May 2024 10:08:11 -0500 Subject: [PATCH 23/38] Remove unneeded comment --- compiler/noirc_frontend/src/elaborator/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index a3d06ad9ab2..0581e7900f8 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -218,7 +218,6 @@ impl<'context> Elaborator<'context> { // the values of integer globals as numeric generics. let (literal_globals, non_literal_globals) = filter_literal_globals(items.globals); - // the resolver filters literal globals first for global in literal_globals { this.elaborate_global(global); } From 8522936d3ecf5813787a36b2936e0c6fc3f96d63 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Wed, 22 May 2024 10:24:35 -0500 Subject: [PATCH 24/38] Re-add accidentally removed collect_traits call --- compiler/noirc_frontend/src/elaborator/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index a57bb8c57ad..0581e7900f8 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -226,6 +226,8 @@ impl<'context> Elaborator<'context> { this.define_type_alias(alias_id, alias); } + this.collect_traits(items.traits); + // Must resolve structs before we resolve globals. this.collect_struct_definitions(items.types); From bbb72284ea748ecf89546dbfeb66a37c800afe45 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Wed, 22 May 2024 13:07:39 -0500 Subject: [PATCH 25/38] Fix panic in the elaborator --- .../src/elaborator/expressions.rs | 6 +- compiler/noirc_frontend/src/elaborator/mod.rs | 151 ++++++++++++++---- .../noirc_frontend/src/elaborator/patterns.rs | 2 +- .../noirc_frontend/src/elaborator/traits.rs | 8 +- .../src/hir/def_collector/dc_crate.rs | 11 +- .../src/hir/def_collector/dc_mod.rs | 7 +- compiler/noirc_frontend/src/node_interner.rs | 19 ++- .../noirc_frontend/src/resolve_locations.rs | 4 +- 8 files changed, 152 insertions(+), 56 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/expressions.rs b/compiler/noirc_frontend/src/elaborator/expressions.rs index 75c95c06d09..b33bca33225 100644 --- a/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -84,10 +84,10 @@ impl<'context> Elaborator<'context> { expr_type: inner_expr_type.clone(), expr_span: span, }); + } - if i + 1 == statements.len() { - block_type = stmt_type; - } + if i + 1 == statements.len() { + block_type = stmt_type; } } diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 0581e7900f8..33102b1becb 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -1,19 +1,19 @@ #![allow(unused)] use std::{ - collections::{BTreeMap, BTreeSet}, + collections::{BTreeMap, BTreeSet, HashMap}, rc::Rc, }; use crate::{ ast::{ ArrayLiteral, ConstructorExpression, FunctionKind, IfExpression, InfixExpression, Lambda, - UnresolvedTraitConstraint, UnresolvedTypeExpression, + UnresolvedTraitConstraint, UnresolvedTypeExpression, TraitItem, }, hir::{ def_collector::{ dc_crate::{ filter_literal_globals, CompilationError, UnresolvedGlobal, UnresolvedStruct, - UnresolvedTrait, UnresolvedTypeAlias, + UnresolvedTrait, UnresolvedTypeAlias, ImplMap, }, errors::DuplicateType, }, @@ -29,7 +29,7 @@ use crate::{ HirMethodReference, HirPrefixExpression, }, stmt::HirLetStatement, - traits::TraitConstraint, + traits::TraitConstraint, function::Parameters, }, macros_api::{ BlockExpression, CallExpression, CastExpression, Expression, ExpressionKind, HirExpression, @@ -80,6 +80,7 @@ mod types; use fm::FileId; use iter_extended::vecmap; +use noirc_arena::Index; use noirc_errors::{Location, Span}; use regex::Regex; use rustc_hash::FxHashSet as HashSet; @@ -226,10 +227,12 @@ impl<'context> Elaborator<'context> { this.define_type_alias(alias_id, alias); } + this.define_function_metas(&mut items.functions, &mut items.impls, &mut items.trait_impls); + this.collect_traits(items.traits); // Must resolve structs before we resolve globals. - this.collect_struct_definitions(items.types); + this.collect_struct_definitions(&items.types); // Bind trait impls to their trait. Collect trait functions, that have a // default implementation, which hasn't been overridden. @@ -268,7 +271,6 @@ impl<'context> Elaborator<'context> { let cycle_errors = this.interner.check_for_dependency_cycles(); this.errors.extend(cycle_errors); - this.errors } @@ -285,7 +287,6 @@ impl<'context> Elaborator<'context> { fn elaborate_function(&mut self, mut function: NoirFunction, id: FuncId) { self.current_function = Some(id); - self.resolve_where_clause(&mut function.def.where_clause); // Without this, impl methods can accidentally be placed in contracts. See #3254 if self.self_type.is_some() { @@ -297,9 +298,6 @@ impl<'context> Elaborator<'context> { // Check whether the function has globals in the local module and add them to the scope self.resolve_local_globals(); - self.add_generics(&function.def.generics); - - self.desugar_impl_trait_args(&mut function, id); self.trait_bounds = function.def.where_clause.clone(); let is_low_level_or_oracle = function @@ -312,8 +310,18 @@ impl<'context> Elaborator<'context> { self.in_unconstrained_fn = true; } - let func_meta = self.extract_meta(&function, id); + let func_meta = self.interner.func_meta.get(&id) + .expect("FuncMetas should be declared before a function is elaborated") + .clone(); + + for (parameter, param2) in function.def.parameters.iter().zip(&func_meta.parameters.0) { + let definition_kind = DefinitionKind::Local(None); + self.elaborate_pattern(parameter.pattern.clone(), param2.1.clone(), definition_kind); + } + self.add_generics(&function.def.generics); + self.desugar_impl_trait_args(&mut function, id); + self.declare_numeric_generics(&func_meta.parameters, func_meta.return_type()); self.add_trait_constraints_to_scope(&func_meta); let (hir_func, body_type) = match function.kind { @@ -369,7 +377,6 @@ impl<'context> Elaborator<'context> { self.trait_bounds.clear(); - self.interner.push_fn_meta(func_meta, id); self.interner.update_fn(id, hir_func); self.current_function = None; } @@ -531,9 +538,25 @@ impl<'context> Elaborator<'context> { /// Extract metadata from a NoirFunction /// to be used in analysis and intern the function parameters - /// Prerequisite: self.add_generics() has already been called with the given - /// function's generics, including any generics from the impl, if any. - fn extract_meta(&mut self, func: &NoirFunction, func_id: FuncId) -> FuncMeta { + /// Prerequisite: any implicit generics, including any generics from the impl, + /// have already been added to scope via `self.add_generics`. + fn define_function_meta(&mut self, func: &mut NoirFunction, func_id: FuncId) { + self.current_function = Some(func_id); + self.resolve_where_clause(&mut func.def.where_clause); + + // Without this, impl methods can accidentally be placed in contracts. See #3254 + if self.self_type.is_some() { + self.in_contract = false; + } + + self.scopes.start_function(); + self.current_item = Some(DependencyId::Function(func_id)); + + // Check whether the function has globals in the local module and add them to the scope + self.resolve_local_globals(); + + + let location = Location::new(func.name_ident().span(), self.file); let id = self.interner.function_definition_id(func_id); let name_ident = HirIdent::non_trait_method(id, location); @@ -558,6 +581,8 @@ impl<'context> Elaborator<'context> { let has_inline_attribute = has_no_predicates_attribute || should_fold; let is_entry_point = self.is_entry_point_function(func); + self.add_generics(&func.def.generics); + let mut generics = vecmap(&self.generics, |(_, typevar, _)| typevar.clone()); let mut parameters = vec![]; let mut parameter_types = vec![]; @@ -586,8 +611,6 @@ impl<'context> Elaborator<'context> { let return_type = Box::new(self.resolve_type(func.return_type())); - self.declare_numeric_generics(¶meter_types, &return_type); - if !self.pub_allowed(func) && func.def.return_visibility == Visibility::Public { self.push_err(ResolverError::UnnecessaryPub { ident: func.name_ident().clone(), @@ -640,7 +663,7 @@ impl<'context> Elaborator<'context> { .map(|(name, typevar, _span)| (name.clone(), typevar.clone())) .collect(); - FuncMeta { + let meta = FuncMeta { name: name_ident, kind: func.kind, location, @@ -654,7 +677,12 @@ impl<'context> Elaborator<'context> { trait_constraints: self.resolve_trait_constraints(&func.def.where_clause), is_entry_point, has_inline_attribute, - } + }; + + self.interner.push_fn_meta(meta, func_id); + self.current_function = None; + self.scopes.end_function(); + self.current_item = None; } /// Only sized types are valid to be used as main's parameters or the parameters to a contract @@ -694,7 +722,7 @@ impl<'context> Elaborator<'context> { } } - fn declare_numeric_generics(&mut self, params: &[Type], return_type: &Type) { + fn declare_numeric_generics(&mut self, params: &Parameters, return_type: &Type) { if self.generics.is_empty() { return; } @@ -717,11 +745,11 @@ impl<'context> Elaborator<'context> { } fn find_numeric_generics( - parameters: &[Type], + parameters: &Parameters, return_type: &Type, ) -> Vec<(String, TypeVariable)> { let mut found = BTreeMap::new(); - for parameter in parameters { + for (_, parameter, _) in ¶meters.0 { Self::find_numeric_generics_in_type(parameter, &mut found); } Self::find_numeric_generics_in_type(return_type, &mut found); @@ -835,10 +863,9 @@ impl<'context> Elaborator<'context> { module: LocalModuleId, impls: Vec<(Vec, Span, UnresolvedFunctions)>, ) { - self.generics.clear(); - for (generics, _, functions) in impls { self.file = functions.file_id; + let old_generics_length = self.generics.len(); self.add_generics(&generics); let self_type = self.resolve_type(typ.clone()); self.self_type = Some(self_type.clone()); @@ -862,6 +889,8 @@ impl<'context> Elaborator<'context> { } } } + + self.generics.truncate(old_generics_length); } } @@ -871,16 +900,17 @@ impl<'context> Elaborator<'context> { let unresolved_type = trait_impl.object_type; let self_type_span = unresolved_type.span; + let old_generics_length = self.generics.len(); self.add_generics(&trait_impl.generics); let trait_generics = vecmap(&trait_impl.trait_generics, |generic| self.resolve_type(generic.clone())); - let self_type = self.resolve_type(unresolved_type.clone()); - let impl_id = self.interner.next_trait_impl_id(); + let self_type = trait_impl.resolved_object_type.unwrap_or(Type::Error); + let impl_id = trait_impl.impl_id.expect("An impls' id should be set during define_function_metas"); self.self_type = Some(self_type.clone()); - self.current_trait_impl = Some(impl_id); + self.current_trait_impl = trait_impl.impl_id; let mut methods = trait_impl.methods.function_ids(); @@ -937,7 +967,7 @@ impl<'context> Elaborator<'context> { self.self_type = None; self.current_trait_impl = None; - self.generics.clear(); + self.generics.truncate(old_generics_length); } fn collect_impls( @@ -1144,7 +1174,7 @@ impl<'context> Elaborator<'context> { self.interner.set_type_alias(alias_id, typ, generics); } - fn collect_struct_definitions(&mut self, structs: BTreeMap) { + fn collect_struct_definitions(&mut self, structs: &BTreeMap) { // This is necessary to avoid cloning the entire struct map // when adding checks after each struct field is resolved. let struct_ids = structs.keys().copied().collect::>(); @@ -1154,9 +1184,9 @@ impl<'context> Elaborator<'context> { for (type_id, typ) in structs { self.file = typ.file_id; self.local_module = typ.module_id; - let (generics, fields) = self.resolve_struct_fields(typ.struct_def, type_id); + let (generics, fields) = self.resolve_struct_fields(&typ.struct_def, *type_id); - self.interner.update_struct(type_id, |struct_def| { + self.interner.update_struct(*type_id, |struct_def| { struct_def.set_fields(fields); struct_def.generics = generics; }); @@ -1184,7 +1214,7 @@ impl<'context> Elaborator<'context> { pub fn resolve_struct_fields( &mut self, - unresolved: NoirStruct, + unresolved: &NoirStruct, struct_id: StructId, ) -> (Generics, Vec<(Ident, Type)>) { let generics = self.add_generics(&unresolved.generics); @@ -1195,7 +1225,7 @@ impl<'context> Elaborator<'context> { self.current_item = Some(DependencyId::Struct(struct_id)); self.resolving_ids.insert(struct_id); - let fields = vecmap(unresolved.fields, |(ident, typ)| (ident, self.resolve_type(typ))); + let fields = vecmap(&unresolved.fields, |(ident, typ)| (ident.clone(), self.resolve_type(typ.clone()))); self.resolving_ids.remove(&struct_id); (generics, fields) @@ -1229,4 +1259,59 @@ impl<'context> Elaborator<'context> { self.interner.get_global_definition_mut(global_id).kind = definition_kind; self.interner.replace_statement(statement_id, let_statement); } + + fn define_function_metas(&mut self, + functions: &mut [UnresolvedFunctions], + impls: &mut ImplMap, + trait_impls: &mut [UnresolvedTraitImpl], + ) { + for function_set in functions { + self.define_function_metas_for_functions(function_set); + } + + for ((_typ, local_module), function_sets) in impls { + self.local_module = *local_module; + + for (_generics, _, function_set) in function_sets { + self.define_function_metas_for_functions(function_set); + } + } + + for trait_impl in trait_impls { + self.file = trait_impl.file_id; + self.local_module = trait_impl.module_id; + + let unresolved_type = &trait_impl.object_type; + let self_type_span = unresolved_type.span; + let old_generics_length = self.generics.len(); + self.add_generics(&trait_impl.generics); + + let trait_generics = + vecmap(&trait_impl.trait_generics, |generic| self.resolve_type(generic.clone())); + + let self_type = self.resolve_type(unresolved_type.clone()); + let impl_id = self.interner.next_trait_impl_id(); + + self.self_type = Some(self_type.clone()); + self.current_trait_impl = Some(impl_id); + + let mut methods = trait_impl.methods.function_ids(); + self.define_function_metas_for_functions(&mut trait_impl.methods); + + trait_impl.resolved_object_type = self.self_type.take(); + trait_impl.impl_id = self.current_trait_impl.take(); + self.generics.truncate(old_generics_length); + } + } + + fn define_function_metas_for_functions(&mut self, function_set: &mut UnresolvedFunctions) { + self.file = function_set.file_id; + + for (local_module, id, func) in &mut function_set.functions { + self.local_module = *local_module; + let old_generics_length = self.generics.len(); + self.define_function_meta(func, *id); + self.generics.truncate(old_generics_length); + } + } } diff --git a/compiler/noirc_frontend/src/elaborator/patterns.rs b/compiler/noirc_frontend/src/elaborator/patterns.rs index 810e1b90743..53f4a21088c 100644 --- a/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -10,7 +10,7 @@ use crate::{ }, hir_def::{ expr::{HirIdent, ImplKind}, - stmt::HirPattern, + stmt::HirPattern, function::FuncMeta, }, macros_api::{HirExpression, Ident, Path, Pattern}, node_interner::{DefinitionId, DefinitionKind, ExprId, TraitImplKind}, diff --git a/compiler/noirc_frontend/src/elaborator/traits.rs b/compiler/noirc_frontend/src/elaborator/traits.rs index e7018d900d8..1f2b0d92229 100644 --- a/compiler/noirc_frontend/src/elaborator/traits.rs +++ b/compiler/noirc_frontend/src/elaborator/traits.rs @@ -26,10 +26,6 @@ use super::Elaborator; impl<'context> Elaborator<'context> { pub fn collect_traits(&mut self, traits: BTreeMap) { - for (trait_id, unresolved_trait) in &traits { - self.interner.push_empty_trait(*trait_id, unresolved_trait); - } - for (trait_id, unresolved_trait) in traits { let generics = vecmap(&unresolved_trait.trait_def.generics, |_| { TypeVariable::unbound(self.interner.next_type_variable_id()) @@ -187,7 +183,9 @@ impl<'context> Elaborator<'context> { return_visibility: Visibility::Private, }; - self.elaborate_function(NoirFunction { kind, def }, func_id); + let mut function = NoirFunction { kind, def }; + self.define_function_meta(&mut function, func_id); + self.elaborate_function(function, func_id); let _ = self.scopes.end_function(); // Don't check the scope tree for unused variables, they can't be used in a declaration anyway. self.trait_bounds.clear(); diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index afec3839599..b42f2f2c379 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -1,5 +1,6 @@ use super::dc_mod::collect_defs; use super::errors::{DefCollectorErrorKind, DuplicateType}; +use crate::Type; use crate::elaborator::Elaborator; use crate::graph::CrateId; use crate::hir::comptime::{Interpreter, InterpreterError}; @@ -18,7 +19,7 @@ use crate::hir::type_check::{ use crate::hir::Context; use crate::macros_api::{MacroError, MacroProcessor}; -use crate::node_interner::{FuncId, GlobalId, NodeInterner, StructId, TraitId, TypeAliasId}; +use crate::node_interner::{FuncId, GlobalId, NodeInterner, StructId, TraitId, TypeAliasId, TraitImplId}; use crate::ast::{ ExpressionKind, Ident, LetStatement, Literal, NoirFunction, NoirStruct, NoirTrait, @@ -107,13 +108,17 @@ pub struct UnresolvedTrait { pub struct UnresolvedTraitImpl { pub file_id: FileId, pub module_id: LocalModuleId, - pub trait_id: Option, pub trait_generics: Vec, pub trait_path: Path, pub object_type: UnresolvedType, pub methods: UnresolvedFunctions, pub generics: UnresolvedGenerics, pub where_clause: Vec, + + // These fields are filled in later + pub trait_id: Option, + pub impl_id: Option, + pub resolved_object_type: Option, } #[derive(Clone)] @@ -337,7 +342,7 @@ impl DefCollector { if use_elaborator { let mut more_errors = Elaborator::elaborate(context, crate_id, def_collector.items); - more_errors.append(&mut errors); + errors.append(&mut more_errors); return errors; } diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs index 3d0ffdb0155..c218dfc4227 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -187,8 +187,12 @@ impl<'a> ModCollector<'a> { object_type: trait_impl.object_type, generics: trait_impl.impl_generics, where_clause: trait_impl.where_clause, - trait_id: None, // will be filled later trait_generics: trait_impl.trait_generics, + + // These last fields are filled later on + trait_id: None, + impl_id: None, + resolved_object_type: None, }; self.def_collector.items.trait_impls.push(unresolved_trait_impl); @@ -507,6 +511,7 @@ impl<'a> ModCollector<'a> { method_ids, fns_with_default_impl: unresolved_functions, }; + context.def_interner.push_empty_trait(trait_id, &unresolved); self.def_collector.items.traits.insert(trait_id, unresolved); } errors diff --git a/compiler/noirc_frontend/src/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs index d4145ef6a1d..6a11d928f5f 100644 --- a/compiler/noirc_frontend/src/node_interner.rs +++ b/compiler/noirc_frontend/src/node_interner.rs @@ -111,7 +111,9 @@ pub struct NodeInterner { // The purpose for this hashmap is to detect duplication of trait implementations ( if any ) // // Indexed by TraitImplIds - pub(crate) trait_implementations: Vec>, + pub(crate) trait_implementations: HashMap>, + + next_trait_implementation_id: usize, /// Trait implementations on each type. This is expected to always have the same length as /// `self.trait_implementations`. @@ -485,7 +487,8 @@ impl Default for NodeInterner { struct_attributes: HashMap::new(), type_aliases: Vec::new(), traits: HashMap::new(), - trait_implementations: Vec::new(), + trait_implementations: HashMap::new(), + next_trait_implementation_id: 0, trait_implementation_map: HashMap::new(), selected_trait_implementations: HashMap::new(), operator_traits: HashMap::new(), @@ -1143,7 +1146,7 @@ impl NodeInterner { } pub fn get_trait_implementation(&self, id: TraitImplId) -> Shared { - self.trait_implementations[id.0].clone() + self.trait_implementations[&id].clone() } /// Given a `ObjectType: TraitId` pair, try to find an existing impl that satisfies the @@ -1380,7 +1383,7 @@ impl NodeInterner { ) -> Result<(), (Span, FileId)> { assert_eq!(impl_id.0, self.trait_implementations.len(), "trait impl defined out of order"); - self.trait_implementations.push(trait_impl.clone()); + self.trait_implementations.insert(impl_id, trait_impl.clone()); // Replace each generic with a fresh type variable let substitutions = impl_generics @@ -1483,10 +1486,10 @@ impl NodeInterner { } /// Returns what the next trait impl id is expected to be. - /// Note that this does not actually reserve the slot so care should - /// be taken that the next trait impl added matches this ID. - pub fn next_trait_impl_id(&self) -> TraitImplId { - TraitImplId(self.trait_implementations.len()) + pub fn next_trait_impl_id(&mut self) -> TraitImplId { + let next_id = self.next_trait_implementation_id; + self.next_trait_implementation_id += 1; + TraitImplId(next_id) } /// Removes all TraitImplKind::Assumed from the list of known impls for the given trait diff --git a/compiler/noirc_frontend/src/resolve_locations.rs b/compiler/noirc_frontend/src/resolve_locations.rs index 2fa7c8adbf8..5efe2e4a041 100644 --- a/compiler/noirc_frontend/src/resolve_locations.rs +++ b/compiler/noirc_frontend/src/resolve_locations.rs @@ -157,11 +157,11 @@ impl NodeInterner { self.trait_implementations .iter() .find(|shared_trait_impl| { - let trait_impl = shared_trait_impl.borrow(); + let trait_impl = shared_trait_impl.1.borrow(); trait_impl.file == location.file && trait_impl.ident.span().contains(&location.span) }) .and_then(|shared_trait_impl| { - let trait_impl = shared_trait_impl.borrow(); + let trait_impl = shared_trait_impl.1.borrow(); self.traits.get(&trait_impl.trait_id).map(|trait_| trait_.location) }) } From 78e6a275c2a8e25f8ad37a3f9c699f5f37806dbc Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Wed, 22 May 2024 13:18:40 -0500 Subject: [PATCH 26/38] Undo unnecessary change --- compiler/noirc_frontend/src/elaborator/mod.rs | 34 +++++++++++-------- .../noirc_frontend/src/elaborator/patterns.rs | 3 +- .../src/hir/def_collector/dc_crate.rs | 6 ++-- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 33102b1becb..d73049ddf82 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -7,13 +7,13 @@ use std::{ use crate::{ ast::{ ArrayLiteral, ConstructorExpression, FunctionKind, IfExpression, InfixExpression, Lambda, - UnresolvedTraitConstraint, UnresolvedTypeExpression, TraitItem, + TraitItem, UnresolvedTraitConstraint, UnresolvedTypeExpression, }, hir::{ def_collector::{ dc_crate::{ - filter_literal_globals, CompilationError, UnresolvedGlobal, UnresolvedStruct, - UnresolvedTrait, UnresolvedTypeAlias, ImplMap, + filter_literal_globals, CompilationError, ImplMap, UnresolvedGlobal, + UnresolvedStruct, UnresolvedTrait, UnresolvedTypeAlias, }, errors::DuplicateType, }, @@ -28,8 +28,9 @@ use crate::{ HirInfixExpression, HirLambda, HirMemberAccess, HirMethodCallExpression, HirMethodReference, HirPrefixExpression, }, + function::Parameters, stmt::HirLetStatement, - traits::TraitConstraint, function::Parameters, + traits::TraitConstraint, }, macros_api::{ BlockExpression, CallExpression, CastExpression, Expression, ExpressionKind, HirExpression, @@ -232,7 +233,7 @@ impl<'context> Elaborator<'context> { this.collect_traits(items.traits); // Must resolve structs before we resolve globals. - this.collect_struct_definitions(&items.types); + this.collect_struct_definitions(items.types); // Bind trait impls to their trait. Collect trait functions, that have a // default implementation, which hasn't been overridden. @@ -310,7 +311,10 @@ impl<'context> Elaborator<'context> { self.in_unconstrained_fn = true; } - let func_meta = self.interner.func_meta.get(&id) + let func_meta = self + .interner + .func_meta + .get(&id) .expect("FuncMetas should be declared before a function is elaborated") .clone(); @@ -555,8 +559,6 @@ impl<'context> Elaborator<'context> { // Check whether the function has globals in the local module and add them to the scope self.resolve_local_globals(); - - let location = Location::new(func.name_ident().span(), self.file); let id = self.interner.function_definition_id(func_id); let name_ident = HirIdent::non_trait_method(id, location); @@ -907,7 +909,8 @@ impl<'context> Elaborator<'context> { vecmap(&trait_impl.trait_generics, |generic| self.resolve_type(generic.clone())); let self_type = trait_impl.resolved_object_type.unwrap_or(Type::Error); - let impl_id = trait_impl.impl_id.expect("An impls' id should be set during define_function_metas"); + let impl_id = + trait_impl.impl_id.expect("An impls' id should be set during define_function_metas"); self.self_type = Some(self_type.clone()); self.current_trait_impl = trait_impl.impl_id; @@ -1174,7 +1177,7 @@ impl<'context> Elaborator<'context> { self.interner.set_type_alias(alias_id, typ, generics); } - fn collect_struct_definitions(&mut self, structs: &BTreeMap) { + fn collect_struct_definitions(&mut self, structs: BTreeMap) { // This is necessary to avoid cloning the entire struct map // when adding checks after each struct field is resolved. let struct_ids = structs.keys().copied().collect::>(); @@ -1184,9 +1187,9 @@ impl<'context> Elaborator<'context> { for (type_id, typ) in structs { self.file = typ.file_id; self.local_module = typ.module_id; - let (generics, fields) = self.resolve_struct_fields(&typ.struct_def, *type_id); + let (generics, fields) = self.resolve_struct_fields(typ.struct_def, type_id); - self.interner.update_struct(*type_id, |struct_def| { + self.interner.update_struct(type_id, |struct_def| { struct_def.set_fields(fields); struct_def.generics = generics; }); @@ -1214,7 +1217,7 @@ impl<'context> Elaborator<'context> { pub fn resolve_struct_fields( &mut self, - unresolved: &NoirStruct, + unresolved: NoirStruct, struct_id: StructId, ) -> (Generics, Vec<(Ident, Type)>) { let generics = self.add_generics(&unresolved.generics); @@ -1225,7 +1228,7 @@ impl<'context> Elaborator<'context> { self.current_item = Some(DependencyId::Struct(struct_id)); self.resolving_ids.insert(struct_id); - let fields = vecmap(&unresolved.fields, |(ident, typ)| (ident.clone(), self.resolve_type(typ.clone()))); + let fields = vecmap(unresolved.fields, |(ident, typ)| (ident, self.resolve_type(typ))); self.resolving_ids.remove(&struct_id); (generics, fields) @@ -1260,7 +1263,8 @@ impl<'context> Elaborator<'context> { self.interner.replace_statement(statement_id, let_statement); } - fn define_function_metas(&mut self, + fn define_function_metas( + &mut self, functions: &mut [UnresolvedFunctions], impls: &mut ImplMap, trait_impls: &mut [UnresolvedTraitImpl], diff --git a/compiler/noirc_frontend/src/elaborator/patterns.rs b/compiler/noirc_frontend/src/elaborator/patterns.rs index 53f4a21088c..602d5e4ec1c 100644 --- a/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -10,7 +10,8 @@ use crate::{ }, hir_def::{ expr::{HirIdent, ImplKind}, - stmt::HirPattern, function::FuncMeta, + function::FuncMeta, + stmt::HirPattern, }, macros_api::{HirExpression, Ident, Path, Pattern}, node_interner::{DefinitionId, DefinitionKind, ExprId, TraitImplKind}, diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index b42f2f2c379..597536bdd48 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -1,11 +1,11 @@ use super::dc_mod::collect_defs; use super::errors::{DefCollectorErrorKind, DuplicateType}; -use crate::Type; use crate::elaborator::Elaborator; use crate::graph::CrateId; use crate::hir::comptime::{Interpreter, InterpreterError}; use crate::hir::def_map::{CrateDefMap, LocalModuleId, ModuleId}; use crate::hir::resolution::errors::ResolverError; +use crate::Type; use crate::hir::resolution::import::{resolve_import, ImportDirective, PathResolution}; use crate::hir::resolution::{ @@ -19,7 +19,9 @@ use crate::hir::type_check::{ use crate::hir::Context; use crate::macros_api::{MacroError, MacroProcessor}; -use crate::node_interner::{FuncId, GlobalId, NodeInterner, StructId, TraitId, TypeAliasId, TraitImplId}; +use crate::node_interner::{ + FuncId, GlobalId, NodeInterner, StructId, TraitId, TraitImplId, TypeAliasId, +}; use crate::ast::{ ExpressionKind, Ident, LetStatement, Literal, NoirFunction, NoirStruct, NoirTrait, From 3b59749abb0aec7c65f941d773f04f8aea8d58b1 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Wed, 22 May 2024 13:24:54 -0500 Subject: [PATCH 27/38] Remove more unnecessary code --- compiler/noirc_frontend/src/elaborator/mod.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index d73049ddf82..e1882f17c24 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -311,10 +311,8 @@ impl<'context> Elaborator<'context> { self.in_unconstrained_fn = true; } - let func_meta = self - .interner - .func_meta - .get(&id) + let func_meta = self.interner.func_meta.get(&id); + let func_meta = func_meta .expect("FuncMetas should be declared before a function is elaborated") .clone(); @@ -1290,16 +1288,12 @@ impl<'context> Elaborator<'context> { let old_generics_length = self.generics.len(); self.add_generics(&trait_impl.generics); - let trait_generics = - vecmap(&trait_impl.trait_generics, |generic| self.resolve_type(generic.clone())); - let self_type = self.resolve_type(unresolved_type.clone()); - let impl_id = self.interner.next_trait_impl_id(); - self.self_type = Some(self_type.clone()); + + let impl_id = self.interner.next_trait_impl_id(); self.current_trait_impl = Some(impl_id); - let mut methods = trait_impl.methods.function_ids(); self.define_function_metas_for_functions(&mut trait_impl.methods); trait_impl.resolved_object_type = self.self_type.take(); From bf999d94dc78ec240537791e4fe2d1cf7ee7af13 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Wed, 22 May 2024 13:25:58 -0500 Subject: [PATCH 28/38] Remove unnecessary import --- compiler/noirc_frontend/src/elaborator/patterns.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/noirc_frontend/src/elaborator/patterns.rs b/compiler/noirc_frontend/src/elaborator/patterns.rs index 602d5e4ec1c..810e1b90743 100644 --- a/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -10,7 +10,6 @@ use crate::{ }, hir_def::{ expr::{HirIdent, ImplKind}, - function::FuncMeta, stmt::HirPattern, }, macros_api::{HirExpression, Ident, Path, Pattern}, From 7be1561dce266ff8777e2d7f5388d90e6ad5bc52 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Wed, 22 May 2024 14:43:18 -0500 Subject: [PATCH 29/38] Remove outdated assert --- compiler/noirc_frontend/src/node_interner.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/compiler/noirc_frontend/src/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs index 6a11d928f5f..60cc2580bb8 100644 --- a/compiler/noirc_frontend/src/node_interner.rs +++ b/compiler/noirc_frontend/src/node_interner.rs @@ -1381,8 +1381,6 @@ impl NodeInterner { impl_generics: Generics, trait_impl: Shared, ) -> Result<(), (Span, FileId)> { - assert_eq!(impl_id.0, self.trait_implementations.len(), "trait impl defined out of order"); - self.trait_implementations.insert(impl_id, trait_impl.clone()); // Replace each generic with a fresh type variable From fadc08516c2ec1a10309165449451135f9bc769b Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Thu, 23 May 2024 12:52:12 -0500 Subject: [PATCH 30/38] Fix issue with function generics --- compiler/noirc_frontend/src/elaborator/mod.rs | 109 ++++++++++-------- .../noirc_frontend/src/elaborator/types.rs | 4 + .../src/hir/def_collector/dc_crate.rs | 7 +- .../src/hir/def_collector/dc_mod.rs | 19 ++- .../src/hir/resolution/resolver.rs | 3 + .../noirc_frontend/src/hir_def/function.rs | 6 + tooling/nargo_cli/src/cli/info_cmd.rs | 3 +- 7 files changed, 97 insertions(+), 54 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index e1882f17c24..7cf07cbc86c 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -248,7 +248,7 @@ impl<'context> Elaborator<'context> { // // These are resolved after trait impls so that struct methods are chosen // over trait methods if there are name conflicts. - for ((typ, module), impls) in &items.impls { + for ((typ, module), impls) in &mut items.impls { this.collect_impls(typ, *module, impls); } @@ -278,6 +278,8 @@ impl<'context> Elaborator<'context> { fn elaborate_functions(&mut self, functions: UnresolvedFunctions) { self.file = functions.file_id; self.trait_id = functions.trait_id; // TODO: Resolve? + self.self_type = functions.self_type; + for (local_module, id, func) in functions.functions { self.local_module = local_module; let generics_count = self.generics.len(); @@ -321,7 +323,7 @@ impl<'context> Elaborator<'context> { self.elaborate_pattern(parameter.pattern.clone(), param2.1.clone(), definition_kind); } - self.add_generics(&function.def.generics); + self.generics = func_meta.all_generics.clone(); self.desugar_impl_trait_args(&mut function, id); self.declare_numeric_generics(&func_meta.parameters, func_meta.return_type()); self.add_trait_constraints_to_scope(&func_meta); @@ -669,6 +671,7 @@ impl<'context> Elaborator<'context> { location, typ, direct_generics, + all_generics: self.generics.clone(), trait_impl: self.current_trait_impl, parameters: parameters.into(), return_type: func.def.return_type.clone(), @@ -866,30 +869,7 @@ impl<'context> Elaborator<'context> { for (generics, _, functions) in impls { self.file = functions.file_id; let old_generics_length = self.generics.len(); - self.add_generics(&generics); - let self_type = self.resolve_type(typ.clone()); - self.self_type = Some(self_type.clone()); - - let function_ids = vecmap(&functions.functions, |(_, id, _)| *id); self.elaborate_functions(functions); - - if self_type != Type::Error { - for method_id in function_ids { - let method_name = self.interner.function_name(&method_id).to_owned(); - - if let Some(first_fn) = - self.interner.add_method(&self_type, method_name.clone(), method_id, false) - { - let error = ResolverError::DuplicateDefinition { - name: method_name, - first_span: self.interner.function_ident(&first_fn).span(), - second_span: self.interner.function_ident(&method_id).span(), - }; - self.push_err(error); - } - } - } - self.generics.truncate(old_generics_length); } } @@ -901,7 +881,7 @@ impl<'context> Elaborator<'context> { let unresolved_type = trait_impl.object_type; let self_type_span = unresolved_type.span; let old_generics_length = self.generics.len(); - self.add_generics(&trait_impl.generics); + self.generics = trait_impl.resolved_generics; let trait_generics = vecmap(&trait_impl.trait_generics, |generic| self.resolve_type(generic.clone())); @@ -975,13 +955,16 @@ impl<'context> Elaborator<'context> { &mut self, self_type: &UnresolvedType, module: LocalModuleId, - impls: &[(Vec, Span, UnresolvedFunctions)], + impls: &mut [(Vec, Span, UnresolvedFunctions)], ) { self.local_module = module; for (generics, span, unresolved) in impls { self.file = unresolved.file_id; - self.declare_method_on_struct(self_type, generics, false, unresolved, *span); + let old_generic_count = self.generics.len(); + self.add_generics(generics); + self.declare_methods_on_struct(self_type, false, unresolved, *span); + self.generics.truncate(old_generic_count); } } @@ -995,8 +978,11 @@ impl<'context> Elaborator<'context> { let span = trait_impl.object_type.span.expect("All trait self types should have spans"); let object_type = &trait_impl.object_type; - let generics = &trait_impl.generics; - self.declare_method_on_struct(object_type, generics, true, &trait_impl.methods, span); + let old_generic_count = self.generics.len(); + self.add_generics(&trait_impl.generics); + trait_impl.resolved_generics = self.generics.clone(); + self.declare_methods_on_struct(object_type, true, &mut trait_impl.methods, span); + self.generics.truncate(old_generic_count); } } @@ -1005,33 +991,33 @@ impl<'context> Elaborator<'context> { &mut self.def_maps.get_mut(&module.krate).expect(message).modules[module.local_id.0] } - fn declare_method_on_struct( + fn declare_methods_on_struct( &mut self, self_type: &UnresolvedType, - generics: &UnresolvedGenerics, is_trait_impl: bool, - functions: &UnresolvedFunctions, + functions: &mut UnresolvedFunctions, span: Span, ) { - let generic_count = self.generics.len(); - self.add_generics(generics); - let typ = self.resolve_type(self_type.clone()); + let self_type = self.resolve_type(self_type.clone()); + + functions.self_type = Some(self_type.clone()); + + let function_ids = functions.function_ids(); - if let Type::Struct(struct_type, _generics) = typ { - let struct_type = struct_type.borrow(); + if let Type::Struct(struct_type, generics) = &self_type { + let struct_ref = struct_type.borrow(); // `impl`s are only allowed on types defined within the current crate - if !is_trait_impl && struct_type.id.krate() != self.crate_id { - let type_name = struct_type.name.to_string(); + if !is_trait_impl && struct_ref.id.krate() != self.crate_id { + let type_name = struct_ref.name.to_string(); self.push_err(DefCollectorErrorKind::ForeignImpl { span, type_name }); - self.generics.truncate(generic_count); return; } // Grab the module defined by the struct type. Note that impls are a case // where the module the methods are added to is not the same as the module // they are resolved in. - let module = self.get_module_mut(struct_type.id.module_id()); + let module = self.get_module_mut(struct_ref.id.module_id()); for (_, method_id, method) in &functions.functions { // If this method was already declared, remove it from the module so it cannot @@ -1043,11 +1029,33 @@ impl<'context> Elaborator<'context> { module.remove_function(method.name_ident()); } } - // Prohibit defining impls for primitive types if we're not in the stdlib - } else if !is_trait_impl && typ != Type::Error && !self.crate_id.is_stdlib() { - self.push_err(DefCollectorErrorKind::NonStructTypeInImpl { span }); + + self.declare_struct_methods(&self_type, &function_ids); + // We can define methods on primitive types only if we're in the stdlib + } else if !is_trait_impl && self_type != Type::Error { + if self.crate_id.is_stdlib() { + self.declare_struct_methods(&self_type, &function_ids); + } else { + self.push_err(DefCollectorErrorKind::NonStructTypeInImpl { span }); + } + } + } + + fn declare_struct_methods(&mut self, self_type: &Type, function_ids: &[FuncId]) { + for method_id in function_ids { + let method_name = self.interner.function_name(&method_id).to_owned(); + + if let Some(first_fn) = + self.interner.add_method(self_type, method_name.clone(), *method_id, false) + { + let error = ResolverError::DuplicateDefinition { + name: method_name, + first_span: self.interner.function_ident(&first_fn).span(), + second_span: self.interner.function_ident(&method_id).span(), + }; + self.push_err(error); + } } - self.generics.truncate(generic_count); } fn collect_trait_impl_methods( @@ -1271,10 +1279,14 @@ impl<'context> Elaborator<'context> { self.define_function_metas_for_functions(function_set); } - for ((_typ, local_module), function_sets) in impls { + for ((self_type, local_module), function_sets) in impls { self.local_module = *local_module; - for (_generics, _, function_set) in function_sets { + for (generics, _, function_set) in function_sets { + self.add_generics(generics); + let self_type = self.resolve_type(self_type.clone()); + function_set.self_type = Some(self_type.clone()); + self.self_type = Some(self_type); self.define_function_metas_for_functions(function_set); } } @@ -1290,6 +1302,7 @@ impl<'context> Elaborator<'context> { let self_type = self.resolve_type(unresolved_type.clone()); self.self_type = Some(self_type.clone()); + trait_impl.methods.self_type = Some(self_type); let impl_id = self.interner.next_trait_impl_id(); self.current_trait_impl = Some(impl_id); diff --git a/compiler/noirc_frontend/src/elaborator/types.rs b/compiler/noirc_frontend/src/elaborator/types.rs index 3c8d805d802..cadf7dacf78 100644 --- a/compiler/noirc_frontend/src/elaborator/types.rs +++ b/compiler/noirc_frontend/src/elaborator/types.rs @@ -1204,6 +1204,10 @@ impl<'context> Elaborator<'context> { other => match self.interner.lookup_primitive_method(&other, method_name) { Some(method_id) => Some(HirMethodReference::FuncId(method_id)), None => { + panic!( + "Unresolved method call on other/primitive! {:?}::{}", + object_type, method_name + ); self.push_err(TypeCheckError::UnresolvedMethodCall { method_name: method_name.to_string(), object_type: object_type.clone(), diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index 597536bdd48..838aac3f067 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -5,7 +5,7 @@ use crate::graph::CrateId; use crate::hir::comptime::{Interpreter, InterpreterError}; use crate::hir::def_map::{CrateDefMap, LocalModuleId, ModuleId}; use crate::hir::resolution::errors::ResolverError; -use crate::Type; +use crate::{Type, TypeVariable}; use crate::hir::resolution::import::{resolve_import, ImportDirective, PathResolution}; use crate::hir::resolution::{ @@ -33,6 +33,7 @@ use iter_extended::vecmap; use noirc_errors::{CustomDiagnostic, Span}; use std::collections::{BTreeMap, HashMap}; +use std::rc::Rc; use std::vec; #[derive(Default)] @@ -50,6 +51,9 @@ pub struct UnresolvedFunctions { pub file_id: FileId, pub functions: Vec<(LocalModuleId, FuncId, NoirFunction)>, pub trait_id: Option, + + // The object type this set of functions was declared on, if there is one. + pub self_type: Option, } impl UnresolvedFunctions { @@ -121,6 +125,7 @@ pub struct UnresolvedTraitImpl { pub trait_id: Option, pub impl_id: Option, pub resolved_object_type: Option, + pub resolved_generics: Vec<(Rc, TypeVariable, Span)>, } #[derive(Clone)] diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs index c218dfc4227..baea00ad23d 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -144,6 +144,7 @@ impl<'a> ModCollector<'a> { file_id: self.file_id, functions: Vec::new(), trait_id: None, + self_type: None, }; for (method, _) in r#impl.methods { @@ -193,6 +194,7 @@ impl<'a> ModCollector<'a> { trait_id: None, impl_id: None, resolved_object_type: None, + resolved_generics: Vec::new(), }; self.def_collector.items.trait_impls.push(unresolved_trait_impl); @@ -205,8 +207,12 @@ impl<'a> ModCollector<'a> { trait_impl: &NoirTraitImpl, krate: CrateId, ) -> UnresolvedFunctions { - let mut unresolved_functions = - UnresolvedFunctions { file_id: self.file_id, functions: Vec::new(), trait_id: None }; + let mut unresolved_functions = UnresolvedFunctions { + file_id: self.file_id, + functions: Vec::new(), + trait_id: None, + self_type: None, + }; let module = ModuleId { krate, local_id: self.module_id }; @@ -228,8 +234,12 @@ impl<'a> ModCollector<'a> { functions: Vec, krate: CrateId, ) -> Vec<(CompilationError, FileId)> { - let mut unresolved_functions = - UnresolvedFunctions { file_id: self.file_id, functions: Vec::new(), trait_id: None }; + let mut unresolved_functions = UnresolvedFunctions { + file_id: self.file_id, + functions: Vec::new(), + trait_id: None, + self_type: None, + }; let mut errors = vec![]; let module = ModuleId { krate, local_id: self.module_id }; @@ -402,6 +412,7 @@ impl<'a> ModCollector<'a> { file_id: self.file_id, functions: Vec::new(), trait_id: None, + self_type: None, }; let mut method_ids = HashMap::new(); diff --git a/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/compiler/noirc_frontend/src/hir/resolution/resolver.rs index 1f006697359..a982cd1b611 100644 --- a/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -1104,6 +1104,9 @@ impl<'a> Resolver<'a> { trait_constraints: self.resolve_trait_constraints(&func.def.where_clause), is_entry_point: self.is_entry_point_function(func), has_inline_attribute, + + // This is only used by the elaborator + all_generics: Vec::new(), } } diff --git a/compiler/noirc_frontend/src/hir_def/function.rs b/compiler/noirc_frontend/src/hir_def/function.rs index ceec9ad8580..dcfded2912f 100644 --- a/compiler/noirc_frontend/src/hir_def/function.rs +++ b/compiler/noirc_frontend/src/hir_def/function.rs @@ -109,6 +109,12 @@ pub struct FuncMeta { /// such as a trait's `Self` type variable. pub direct_generics: Vec<(Rc, TypeVariable)>, + /// All the generics used by this function, which includes any implicit generics or generics + /// from outer scopes, such as those introduced by an impl. + /// This is stored when the FuncMeta is first created to later be used to set the current + /// generics when the function's body is later resolved. + pub all_generics: Vec<(Rc, TypeVariable, Span)>, + pub location: Location, // This flag is needed for the attribute check pass diff --git a/tooling/nargo_cli/src/cli/info_cmd.rs b/tooling/nargo_cli/src/cli/info_cmd.rs index 6c0709e7611..7c50e907dc9 100644 --- a/tooling/nargo_cli/src/cli/info_cmd.rs +++ b/tooling/nargo_cli/src/cli/info_cmd.rs @@ -102,7 +102,8 @@ pub(crate) fn run(args: InfoCommand, config: NargoConfig) -> Result<(), CliError } else { // Otherwise print human-readable table. if !info_report.programs.is_empty() { - let mut program_table = table!([Fm->"Package", Fm->"Function", Fm->"Expression Width", Fm->"ACIR Opcodes"]); + let mut program_table = + table!([Fm->"Package", Fm->"Function", Fm->"Expression Width", Fm->"ACIR Opcodes"]); for program_info in info_report.programs { let program_rows: Vec = program_info.into(); From f0d249a98dd2ac28fe8d809a9b3456421c0b0def Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Thu, 23 May 2024 12:58:15 -0500 Subject: [PATCH 31/38] Clippy --- compiler/noirc_frontend/src/elaborator/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 7cf07cbc86c..4dc47a90f50 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -1043,7 +1043,7 @@ impl<'context> Elaborator<'context> { fn declare_struct_methods(&mut self, self_type: &Type, function_ids: &[FuncId]) { for method_id in function_ids { - let method_name = self.interner.function_name(&method_id).to_owned(); + let method_name = self.interner.function_name(method_id).to_owned(); if let Some(first_fn) = self.interner.add_method(self_type, method_name.clone(), *method_id, false) @@ -1051,7 +1051,7 @@ impl<'context> Elaborator<'context> { let error = ResolverError::DuplicateDefinition { name: method_name, first_span: self.interner.function_ident(&first_fn).span(), - second_span: self.interner.function_ident(&method_id).span(), + second_span: self.interner.function_ident(method_id).span(), }; self.push_err(error); } From 68ccf9c9ff7e7a75df98b2179585b16becdaa60c Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Thu, 23 May 2024 14:06:33 -0500 Subject: [PATCH 32/38] Add missed field in test --- compiler/noirc_frontend/src/hir/type_check/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/noirc_frontend/src/hir/type_check/mod.rs b/compiler/noirc_frontend/src/hir/type_check/mod.rs index b2a76828c88..2e1077148d9 100644 --- a/compiler/noirc_frontend/src/hir/type_check/mod.rs +++ b/compiler/noirc_frontend/src/hir/type_check/mod.rs @@ -550,6 +550,7 @@ pub mod test { direct_generics: Vec::new(), is_entry_point: true, has_inline_attribute: false, + all_generics: Vec::new(), }; interner.push_fn_meta(func_meta, func_id); From aa6d581eb615f5b6e57039f5197c403fa05cab24 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Thu, 23 May 2024 15:33:39 -0500 Subject: [PATCH 33/38] Fix structs not recovering generics length --- compiler/noirc_frontend/src/elaborator/mod.rs | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 4dc47a90f50..127bb526523 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -229,7 +229,6 @@ impl<'context> Elaborator<'context> { } this.define_function_metas(&mut items.functions, &mut items.impls, &mut items.trait_impls); - this.collect_traits(items.traits); // Must resolve structs before we resolve globals. @@ -275,6 +274,15 @@ impl<'context> Elaborator<'context> { this.errors } + /// Runs `f` and if it modifies `self.generics`, `self.generics` is truncated + /// back to the previous length. + fn recover_generics(&mut self, f: impl FnOnce(&mut Self) -> T) -> T { + let generics_count = self.generics.len(); + let ret = f(self); + self.generics.truncate(generics_count); + ret + } + fn elaborate_functions(&mut self, functions: UnresolvedFunctions) { self.file = functions.file_id; self.trait_id = functions.trait_id; // TODO: Resolve? @@ -282,9 +290,7 @@ impl<'context> Elaborator<'context> { for (local_module, id, func) in functions.functions { self.local_module = local_module; - let generics_count = self.generics.len(); - self.elaborate_function(func, id); - self.generics.truncate(generics_count); + self.recover_generics(|this| this.elaborate_function(func, id)); } } @@ -890,7 +896,6 @@ impl<'context> Elaborator<'context> { let impl_id = trait_impl.impl_id.expect("An impls' id should be set during define_function_metas"); - self.self_type = Some(self_type.clone()); self.current_trait_impl = trait_impl.impl_id; let mut methods = trait_impl.methods.function_ids(); @@ -1226,18 +1231,20 @@ impl<'context> Elaborator<'context> { unresolved: NoirStruct, struct_id: StructId, ) -> (Generics, Vec<(Ident, Type)>) { - let generics = self.add_generics(&unresolved.generics); + self.recover_generics(|this| { + let generics = this.add_generics(&unresolved.generics); - // Check whether the struct definition has globals in the local module and add them to the scope - self.resolve_local_globals(); + // Check whether the struct definition has globals in the local module and add them to the scope + this.resolve_local_globals(); - self.current_item = Some(DependencyId::Struct(struct_id)); + this.current_item = Some(DependencyId::Struct(struct_id)); - self.resolving_ids.insert(struct_id); - let fields = vecmap(unresolved.fields, |(ident, typ)| (ident, self.resolve_type(typ))); - self.resolving_ids.remove(&struct_id); + this.resolving_ids.insert(struct_id); + let fields = vecmap(unresolved.fields, |(ident, typ)| (ident, this.resolve_type(typ))); + this.resolving_ids.remove(&struct_id); - (generics, fields) + (generics, fields) + }) } fn elaborate_global(&mut self, global: UnresolvedGlobal) { @@ -1288,6 +1295,7 @@ impl<'context> Elaborator<'context> { function_set.self_type = Some(self_type.clone()); self.self_type = Some(self_type); self.define_function_metas_for_functions(function_set); + self.generics.clear(); } } @@ -1297,7 +1305,6 @@ impl<'context> Elaborator<'context> { let unresolved_type = &trait_impl.object_type; let self_type_span = unresolved_type.span; - let old_generics_length = self.generics.len(); self.add_generics(&trait_impl.generics); let self_type = self.resolve_type(unresolved_type.clone()); @@ -1311,7 +1318,7 @@ impl<'context> Elaborator<'context> { trait_impl.resolved_object_type = self.self_type.take(); trait_impl.impl_id = self.current_trait_impl.take(); - self.generics.truncate(old_generics_length); + self.generics.clear(); } } @@ -1320,9 +1327,9 @@ impl<'context> Elaborator<'context> { for (local_module, id, func) in &mut function_set.functions { self.local_module = *local_module; - let old_generics_length = self.generics.len(); - self.define_function_meta(func, *id); - self.generics.truncate(old_generics_length); + self.recover_generics(|this| { + this.define_function_meta(func, *id); + }); } } } From f519ced120e419e2ee385dc496d48405ebdb0e3a Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Thu, 23 May 2024 16:15:46 -0500 Subject: [PATCH 34/38] Don't check return type for trait methods --- compiler/noirc_frontend/src/elaborator/mod.rs | 30 +++++++++++++------ .../noirc_frontend/src/elaborator/traits.rs | 2 +- .../src/hir/resolution/resolver.rs | 1 + .../noirc_frontend/src/hir/type_check/mod.rs | 3 +- .../noirc_frontend/src/hir_def/function.rs | 18 +++++++---- 5 files changed, 37 insertions(+), 17 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 127bb526523..85d67f0cb83 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -292,6 +292,9 @@ impl<'context> Elaborator<'context> { self.local_module = local_module; self.recover_generics(|this| this.elaborate_function(func, id)); } + + self.self_type = None; + self.trait_id = None; } fn elaborate_function(&mut self, mut function: NoirFunction, id: FuncId) { @@ -347,7 +350,8 @@ impl<'context> Elaborator<'context> { } }; - if !func_meta.can_ignore_return_type() { + // Don't verify the return type for builtin functions & trait function declarations + if !func_meta.is_stub() { self.type_check_function_body(body_type, &func_meta, hir_func.as_expr()); } @@ -381,7 +385,7 @@ impl<'context> Elaborator<'context> { let func_scope_tree = self.scopes.end_function(); // The arguments to low-level and oracle functions are always unused so we do not produce warnings for them. - if !is_low_level_or_oracle { + if !func_meta.is_stub() { self.check_for_unused_variables_in_scope_tree(func_scope_tree); } @@ -550,7 +554,12 @@ impl<'context> Elaborator<'context> { /// to be used in analysis and intern the function parameters /// Prerequisite: any implicit generics, including any generics from the impl, /// have already been added to scope via `self.add_generics`. - fn define_function_meta(&mut self, func: &mut NoirFunction, func_id: FuncId) { + fn define_function_meta( + &mut self, + func: &mut NoirFunction, + func_id: FuncId, + is_trait_function: bool, + ) { self.current_function = Some(func_id); self.resolve_where_clause(&mut func.def.where_clause); @@ -685,6 +694,7 @@ impl<'context> Elaborator<'context> { has_body: !func.def.body.is_empty(), trait_constraints: self.resolve_trait_constraints(&func.def.where_clause), is_entry_point, + is_trait_function, has_inline_attribute, }; @@ -983,11 +993,12 @@ impl<'context> Elaborator<'context> { let span = trait_impl.object_type.span.expect("All trait self types should have spans"); let object_type = &trait_impl.object_type; - let old_generic_count = self.generics.len(); - self.add_generics(&trait_impl.generics); - trait_impl.resolved_generics = self.generics.clone(); - self.declare_methods_on_struct(object_type, true, &mut trait_impl.methods, span); - self.generics.truncate(old_generic_count); + + self.recover_generics(|this| { + this.add_generics(&trait_impl.generics); + trait_impl.resolved_generics = this.generics.clone(); + this.declare_methods_on_struct(object_type, true, &mut trait_impl.methods, span); + }); } } @@ -1308,6 +1319,7 @@ impl<'context> Elaborator<'context> { self.add_generics(&trait_impl.generics); let self_type = self.resolve_type(unresolved_type.clone()); + self.self_type = Some(self_type.clone()); trait_impl.methods.self_type = Some(self_type); @@ -1328,7 +1340,7 @@ impl<'context> Elaborator<'context> { for (local_module, id, func) in &mut function_set.functions { self.local_module = *local_module; self.recover_generics(|this| { - this.define_function_meta(func, *id); + this.define_function_meta(func, *id, false); }); } } diff --git a/compiler/noirc_frontend/src/elaborator/traits.rs b/compiler/noirc_frontend/src/elaborator/traits.rs index 1f2b0d92229..52b86ce896b 100644 --- a/compiler/noirc_frontend/src/elaborator/traits.rs +++ b/compiler/noirc_frontend/src/elaborator/traits.rs @@ -184,7 +184,7 @@ impl<'context> Elaborator<'context> { }; let mut function = NoirFunction { kind, def }; - self.define_function_meta(&mut function, func_id); + self.define_function_meta(&mut function, func_id, true); self.elaborate_function(function, func_id); let _ = self.scopes.end_function(); // Don't check the scope tree for unused variables, they can't be used in a declaration anyway. diff --git a/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/compiler/noirc_frontend/src/hir/resolution/resolver.rs index a982cd1b611..00d2c8b4da5 100644 --- a/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -1107,6 +1107,7 @@ impl<'a> Resolver<'a> { // This is only used by the elaborator all_generics: Vec::new(), + is_trait_function: false, } } diff --git a/compiler/noirc_frontend/src/hir/type_check/mod.rs b/compiler/noirc_frontend/src/hir/type_check/mod.rs index 2e1077148d9..70d7c4021ed 100644 --- a/compiler/noirc_frontend/src/hir/type_check/mod.rs +++ b/compiler/noirc_frontend/src/hir/type_check/mod.rs @@ -49,7 +49,7 @@ pub struct TypeChecker<'interner> { pub fn type_check_func(interner: &mut NodeInterner, func_id: FuncId) -> Vec { let meta = interner.function_meta(&func_id); let declared_return_type = meta.return_type().clone(); - let can_ignore_ret = meta.can_ignore_return_type(); + let can_ignore_ret = meta.is_stub(); let function_body_id = &interner.function(&func_id).as_expr(); @@ -549,6 +549,7 @@ pub mod test { trait_constraints: Vec::new(), direct_generics: Vec::new(), is_entry_point: true, + is_trait_function: false, has_inline_attribute: false, all_generics: Vec::new(), }; diff --git a/compiler/noirc_frontend/src/hir_def/function.rs b/compiler/noirc_frontend/src/hir_def/function.rs index dcfded2912f..9e03f074ffe 100644 --- a/compiler/noirc_frontend/src/hir_def/function.rs +++ b/compiler/noirc_frontend/src/hir_def/function.rs @@ -129,6 +129,11 @@ pub struct FuncMeta { /// For non-contracts, this means the function is `main`. pub is_entry_point: bool, + /// True if this function was defined within a trait (not a trait impl!). + /// Trait functions are just stubs and shouldn't have their return type checked + /// against their body type, nor should unused variables be checked. + pub is_trait_function: bool, + /// True if this function is marked with an attribute /// that indicates it should be inlined differently than the default (inline everything). /// For example, such as `fold` (never inlined) or `no_predicates` (inlined after flattening) @@ -136,12 +141,13 @@ pub struct FuncMeta { } impl FuncMeta { - /// Builtin, LowLevel and Oracle functions usually have the return type - /// declared, however their function bodies will be empty - /// So this method tells the type checker to ignore the return - /// of the empty function, which is unit - pub fn can_ignore_return_type(&self) -> bool { - self.kind.can_ignore_return_type() + /// A stub function does not have a body. This includes Builtin, LowLevel, + /// and Oracle functions in addition to method declarations within a trait. + /// + /// We don't check the return type of these functions since it will always have + /// an empty body, and we don't check for unused parameters. + pub fn is_stub(&self) -> bool { + self.kind.can_ignore_return_type() || self.is_trait_function } pub fn function_signature(&self) -> FunctionSignature { From 4e6490ca2db96c5c0c7ce3ba77ebd097bdfcc5f4 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Fri, 24 May 2024 10:21:27 -0500 Subject: [PATCH 35/38] Fix more errors --- compiler/noirc_frontend/src/elaborator/mod.rs | 35 +++-- .../noirc_frontend/src/elaborator/traits.rs | 133 +++++++++--------- 2 files changed, 81 insertions(+), 87 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 85d67f0cb83..3e05251b982 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -247,8 +247,8 @@ impl<'context> Elaborator<'context> { // // These are resolved after trait impls so that struct methods are chosen // over trait methods if there are name conflicts. - for ((typ, module), impls) in &mut items.impls { - this.collect_impls(typ, *module, impls); + for ((_self_type, module), impls) in &mut items.impls { + this.collect_impls(*module, impls); } // We must wait to resolve non-literal globals until after we resolve structs since struct @@ -903,8 +903,9 @@ impl<'context> Elaborator<'context> { vecmap(&trait_impl.trait_generics, |generic| self.resolve_type(generic.clone())); let self_type = trait_impl.resolved_object_type.unwrap_or(Type::Error); + let impl_id = - trait_impl.impl_id.expect("An impls' id should be set during define_function_metas"); + trait_impl.impl_id.expect("An impl's id should be set during define_function_metas"); self.current_trait_impl = trait_impl.impl_id; @@ -968,7 +969,6 @@ impl<'context> Elaborator<'context> { fn collect_impls( &mut self, - self_type: &UnresolvedType, module: LocalModuleId, impls: &mut [(Vec, Span, UnresolvedFunctions)], ) { @@ -978,7 +978,7 @@ impl<'context> Elaborator<'context> { self.file = unresolved.file_id; let old_generic_count = self.generics.len(); self.add_generics(generics); - self.declare_methods_on_struct(self_type, false, unresolved, *span); + self.declare_methods_on_struct(false, unresolved, *span); self.generics.truncate(old_generic_count); } } @@ -992,13 +992,9 @@ impl<'context> Elaborator<'context> { self.collect_trait_impl_methods(trait_id, trait_impl); let span = trait_impl.object_type.span.expect("All trait self types should have spans"); - let object_type = &trait_impl.object_type; - - self.recover_generics(|this| { - this.add_generics(&trait_impl.generics); - trait_impl.resolved_generics = this.generics.clone(); - this.declare_methods_on_struct(object_type, true, &mut trait_impl.methods, span); - }); + self.generics = trait_impl.resolved_generics.clone(); + self.declare_methods_on_struct(true, &mut trait_impl.methods, span); + self.generics.clear(); } } @@ -1009,14 +1005,14 @@ impl<'context> Elaborator<'context> { fn declare_methods_on_struct( &mut self, - self_type: &UnresolvedType, is_trait_impl: bool, functions: &mut UnresolvedFunctions, span: Span, ) { - let self_type = self.resolve_type(self_type.clone()); - - functions.self_type = Some(self_type.clone()); + let self_type = functions + .self_type + .as_ref() + .expect("Expected struct type to be set before declare_methods_on_struct"); let function_ids = functions.function_ids(); @@ -1048,7 +1044,7 @@ impl<'context> Elaborator<'context> { self.declare_struct_methods(&self_type, &function_ids); // We can define methods on primitive types only if we're in the stdlib - } else if !is_trait_impl && self_type != Type::Error { + } else if !is_trait_impl && *self_type != Type::Error { if self.crate_id.is_stdlib() { self.declare_struct_methods(&self_type, &function_ids); } else { @@ -1175,8 +1171,8 @@ impl<'context> Elaborator<'context> { self.local_module = trait_impl.module_id; self.file = trait_impl.file_id; - let object_crate = match self.resolve_type(trait_impl.object_type.clone()) { - Type::Struct(struct_type, _) => struct_type.borrow().id.krate(), + let object_crate = match &trait_impl.resolved_object_type { + Some(Type::Struct(struct_type, _)) => struct_type.borrow().id.krate(), _ => CrateId::Dummy, }; @@ -1317,6 +1313,7 @@ impl<'context> Elaborator<'context> { let unresolved_type = &trait_impl.object_type; let self_type_span = unresolved_type.span; self.add_generics(&trait_impl.generics); + trait_impl.resolved_generics = self.generics.clone(); let self_type = self.resolve_type(unresolved_type.clone()); diff --git a/compiler/noirc_frontend/src/elaborator/traits.rs b/compiler/noirc_frontend/src/elaborator/traits.rs index 52b86ce896b..b5713e175be 100644 --- a/compiler/noirc_frontend/src/elaborator/traits.rs +++ b/compiler/noirc_frontend/src/elaborator/traits.rs @@ -27,21 +27,21 @@ use super::Elaborator; impl<'context> Elaborator<'context> { pub fn collect_traits(&mut self, traits: BTreeMap) { for (trait_id, unresolved_trait) in traits { - let generics = vecmap(&unresolved_trait.trait_def.generics, |_| { - TypeVariable::unbound(self.interner.next_type_variable_id()) - }); - - // Resolve order - // 1. Trait Types ( Trait constants can have a trait type, therefore types before constants) - let _ = self.resolve_trait_types(&unresolved_trait); - // 2. Trait Constants ( Trait's methods can use trait types & constants, therefore they should be after) - let _ = self.resolve_trait_constants(&unresolved_trait); - // 3. Trait Methods - let methods = self.resolve_trait_methods(trait_id, &unresolved_trait, &generics); - - self.interner.update_trait(trait_id, |trait_def| { - trait_def.set_methods(methods); - trait_def.generics = generics; + self.recover_generics(|this| { + this.add_generics(&unresolved_trait.trait_def.generics); + + // Resolve order + // 1. Trait Types ( Trait constants can have a trait type, therefore types before constants) + let _ = this.resolve_trait_types(&unresolved_trait); + // 2. Trait Constants ( Trait's methods can use trait types & constants, therefore they should be after) + let _ = this.resolve_trait_constants(&unresolved_trait); + // 3. Trait Methods + let methods = this.resolve_trait_methods(trait_id, &unresolved_trait); + + this.interner.update_trait(trait_id, |trait_def| { + trait_def.set_methods(methods); + trait_def.generics = vecmap(&this.generics, |(_, generic, _)| generic.clone()); + }); }); // This check needs to be after the trait's methods are set since @@ -70,7 +70,6 @@ impl<'context> Elaborator<'context> { &mut self, trait_id: TraitId, unresolved_trait: &UnresolvedTrait, - trait_generics: &Generics, ) -> Vec { self.local_module = unresolved_trait.module_id; self.file = self.def_maps[&self.crate_id].file_id(unresolved_trait.module_id); @@ -87,59 +86,57 @@ impl<'context> Elaborator<'context> { body: _, } = item { - let old_generic_count = self.generics.len(); - - let the_trait = self.interner.get_trait(trait_id); - let self_typevar = the_trait.self_type_typevar.clone(); - let self_type = Type::TypeVariable(self_typevar.clone(), TypeVariableKind::Normal); - let name_span = the_trait.name.span(); - - self.add_generics(generics); - self.add_existing_generics(&unresolved_trait.trait_def.generics, trait_generics); - self.add_existing_generic("Self", name_span, self_typevar); - self.self_type = Some(self_type.clone()); - - let func_id = unresolved_trait.method_ids[&name.0.contents]; - self.resolve_trait_function( - name, - generics, - parameters, - return_type, - where_clause, - func_id, - ); - - let arguments = vecmap(parameters, |param| self.resolve_type(param.1.clone())); - let return_type = self.resolve_type(return_type.get_type().into_owned()); - - let generics = vecmap(&self.generics, |(_, type_var, _)| type_var.clone()); - - let default_impl_list: Vec<_> = unresolved_trait - .fns_with_default_impl - .functions - .iter() - .filter(|(_, _, q)| q.name() == name.0.contents) - .collect(); - - let default_impl = if default_impl_list.len() == 1 { - Some(Box::new(default_impl_list[0].2.clone())) - } else { - None - }; - - let no_environment = Box::new(Type::Unit); - let function_type = - Type::Function(arguments, Box::new(return_type), no_environment); - - functions.push(TraitFunction { - name: name.clone(), - typ: Type::Forall(generics, Box::new(function_type)), - location: Location::new(name.span(), unresolved_trait.file_id), - default_impl, - default_impl_module_id: unresolved_trait.module_id, + self.recover_generics(|this| { + let the_trait = this.interner.get_trait(trait_id); + let self_typevar = the_trait.self_type_typevar.clone(); + let self_type = + Type::TypeVariable(self_typevar.clone(), TypeVariableKind::Normal); + let name_span = the_trait.name.span(); + + this.add_existing_generic("Self", name_span, self_typevar); + this.add_generics(generics); + this.self_type = Some(self_type.clone()); + + let func_id = unresolved_trait.method_ids[&name.0.contents]; + this.resolve_trait_function( + name, + generics, + parameters, + return_type, + where_clause, + func_id, + ); + + let arguments = vecmap(parameters, |param| this.resolve_type(param.1.clone())); + let return_type = this.resolve_type(return_type.get_type().into_owned()); + + let generics = vecmap(&this.generics, |(_, type_var, _)| type_var.clone()); + + let default_impl_list: Vec<_> = unresolved_trait + .fns_with_default_impl + .functions + .iter() + .filter(|(_, _, q)| q.name() == name.0.contents) + .collect(); + + let default_impl = if default_impl_list.len() == 1 { + Some(Box::new(default_impl_list[0].2.clone())) + } else { + None + }; + + let no_environment = Box::new(Type::Unit); + let function_type = + Type::Function(arguments, Box::new(return_type), no_environment); + + functions.push(TraitFunction { + name: name.clone(), + typ: Type::Forall(generics, Box::new(function_type)), + location: Location::new(name.span(), unresolved_trait.file_id), + default_impl, + default_impl_module_id: unresolved_trait.module_id, + }); }); - - self.generics.truncate(old_generic_count); } } functions From 38f20e99d3337a879d7f296032afda340839acb4 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Fri, 24 May 2024 10:27:37 -0500 Subject: [PATCH 36/38] Clippy --- compiler/noirc_frontend/src/elaborator/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 3e05251b982..e91009de2fb 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -1042,11 +1042,11 @@ impl<'context> Elaborator<'context> { } } - self.declare_struct_methods(&self_type, &function_ids); + self.declare_struct_methods(self_type, &function_ids); // We can define methods on primitive types only if we're in the stdlib } else if !is_trait_impl && *self_type != Type::Error { if self.crate_id.is_stdlib() { - self.declare_struct_methods(&self_type, &function_ids); + self.declare_struct_methods(self_type, &function_ids); } else { self.push_err(DefCollectorErrorKind::NonStructTypeInImpl { span }); } From 31e422803aa5726505f0d79f8062d04f97659560 Mon Sep 17 00:00:00 2001 From: jfecher Date: Tue, 28 May 2024 13:42:45 -0500 Subject: [PATCH 37/38] fix(experimental elaborator): Avoid defining globals twice (#5103) # Description ## Problem\* Fixes the "Duplicate definitions of found" error by removing `resolve_local_globals`. ## Summary\* ## Additional Context We don't need to re-add globals to scope anymore via `resolve_local_globals` since we're not creating a new Elaborator for each function like we do for NameResolvers. The flip side of this is that I think we'll need to change how globals are stored eventually so that they're not all always visible. I think how it is now may violate imports but I'll leave that for a later fix to keep these PRs small. (and wait until I have a test case that shows it). Down to 61 errors after this PR. The errors are: - "Duplicate definitions of \ found" - looks to be only one case where there is a generic directly on a trait method. `fn hash(self, state: &mut H) where H: Hasher;` - "Expression type is ambiguous" - from the two turbofish cases that haven't been merged in this branch yet - "Expected type &mut _, found type H" - issues with auto-deref and trait methods possibly - "No matching impl found for ..." - trait solver issues ## Documentation\* Check one: - [x] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --- compiler/noirc_frontend/src/elaborator/mod.rs | 24 ++----------------- .../noirc_frontend/src/elaborator/traits.rs | 3 --- 2 files changed, 2 insertions(+), 25 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index e91009de2fb..0b9fc39df88 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -308,8 +308,6 @@ impl<'context> Elaborator<'context> { self.scopes.start_function(); self.current_item = Some(DependencyId::Function(id)); - // Check whether the function has globals in the local module and add them to the scope - self.resolve_local_globals(); self.trait_bounds = function.def.where_clause.clone(); let is_low_level_or_oracle = function @@ -497,18 +495,6 @@ impl<'context> Elaborator<'context> { None } - fn resolve_local_globals(&mut self) { - let globals = vecmap(self.interner.get_all_globals(), |global| { - (global.id, global.local_id, global.ident.clone()) - }); - for (id, local_module_id, name) in globals { - if local_module_id == self.local_module { - let definition = DefinitionKind::Global(id); - self.add_global_variable_decl(name, definition); - } - } - } - /// TODO: This is currently only respected for generic free functions /// there's a bunch of other places where trait constraints can pop up fn resolve_trait_constraints( @@ -571,9 +557,6 @@ impl<'context> Elaborator<'context> { self.scopes.start_function(); self.current_item = Some(DependencyId::Function(func_id)); - // Check whether the function has globals in the local module and add them to the scope - self.resolve_local_globals(); - let location = Location::new(func.name_ident().span(), self.file); let id = self.interner.function_definition_id(func_id); let name_ident = HirIdent::non_trait_method(id, location); @@ -1189,7 +1172,6 @@ impl<'context> Elaborator<'context> { self.local_module = alias.module_id; let generics = self.add_generics(&alias.type_alias_def.generics); - self.resolve_local_globals(); self.current_item = Some(DependencyId::Alias(alias_id)); let typ = self.resolve_type(alias.type_alias_def.typ); self.interner.set_type_alias(alias_id, typ, generics); @@ -1241,9 +1223,6 @@ impl<'context> Elaborator<'context> { self.recover_generics(|this| { let generics = this.add_generics(&unresolved.generics); - // Check whether the struct definition has globals in the local module and add them to the scope - this.resolve_local_globals(); - this.current_item = Some(DependencyId::Struct(struct_id)); this.resolving_ids.insert(struct_id); @@ -1276,10 +1255,11 @@ impl<'context> Elaborator<'context> { self.push_err(ResolverError::MutableGlobal { span }); } + let name = let_stmt.pattern.name_ident().clone(); let (let_statement, _typ) = self.elaborate_let(let_stmt); let statement_id = self.interner.get_global(global_id).let_statement; - self.interner.get_global_definition_mut(global_id).kind = definition_kind; + self.interner.get_global_definition_mut(global_id).kind = definition_kind.clone(); self.interner.replace_statement(statement_id, let_statement); } diff --git a/compiler/noirc_frontend/src/elaborator/traits.rs b/compiler/noirc_frontend/src/elaborator/traits.rs index b5713e175be..c29a6871ccc 100644 --- a/compiler/noirc_frontend/src/elaborator/traits.rs +++ b/compiler/noirc_frontend/src/elaborator/traits.rs @@ -154,9 +154,6 @@ impl<'context> Elaborator<'context> { let old_generic_count = self.generics.len(); self.scopes.start_function(); - // Check whether the function has globals in the local module and add them to the scope - self.resolve_local_globals(); - self.trait_bounds = where_clause.to_vec(); let kind = FunctionKind::Normal; From 0fed42c45aefdcb2c290c189bbc98b8f65157be5 Mon Sep 17 00:00:00 2001 From: jfecher Date: Tue, 28 May 2024 13:52:47 -0500 Subject: [PATCH 38/38] fix(experimental elaborator): Move code to declare trait impls earlier (#5105) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description ## Problem\* Resolves "No matching impl found for ..." trait solver issues in the elaborator. ## Summary\* These issues were just because we'd add a trait impl to the interner after elaborating the impl. At that point, we'd already have elaborated all other functions & methods so it wasn't of much use. I've moved it to add the trait impl to the interner during `collect_trait_impl` instead. ## Additional Context Down to 6 errors after this PR. The errors are: - "Duplicate definitions of \ found" - looks to be only one case where there is a generic directly on a trait method. `fn hash(self, state: &mut H) where H: Hasher;` (1 error) - "Expected type &mut _, found type H" - issues with auto-deref and trait methods possibly (5 errors) (merging master has fixed the 2 "expression is ambiguous" errors from the turbofish operator changes) ## Documentation\* Check one: - [x] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --------- Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com> Co-authored-by: Maxim Vezenov Co-authored-by: Álvaro Rodríguez Co-authored-by: guipublic <47281315+guipublic@users.noreply.github.com> --- .github/workflows/gates_report.yml | 180 ++++++++--------- .../acvm/src/compiler/transformers/csat.rs | 4 +- compiler/noirc_evaluator/src/ssa.rs | 1 + .../src/ssa/acir_gen/acir_ir/acir_variable.rs | 41 +++- .../noirc_evaluator/src/ssa/ir/instruction.rs | 6 +- .../src/ssa/ir/instruction/call.rs | 1 + compiler/noirc_evaluator/src/ssa/opt/mod.rs | 1 + .../src/ssa/opt/remove_enable_side_effects.rs | 3 +- .../src/ssa/opt/remove_if_else.rs | 3 +- .../src/ssa/opt/resolve_is_unconstrained.rs | 56 ++++++ .../src/elaborator/expressions.rs | 33 ++-- compiler/noirc_frontend/src/elaborator/mod.rs | 110 +++++------ .../noirc_frontend/src/elaborator/patterns.rs | 70 ++++++- .../src/hir/def_collector/dc_crate.rs | 6 +- .../src/hir/def_collector/dc_mod.rs | 1 + .../noirc_frontend/src/hir/type_check/expr.rs | 15 +- compiler/noirc_frontend/src/tests.rs | 4 +- .../noir/standard_library/is_unconstrained.md | 59 ++++++ noir_stdlib/src/eddsa.nr | 2 +- noir_stdlib/src/field/bn254.nr | 183 +++++++++++------- noir_stdlib/src/lib.nr | 2 + noir_stdlib/src/runtime.nr | 2 + .../brillig_slice_input/Nargo.toml | 0 .../brillig_slice_input/src/main.nr | 0 .../regression_4383/Nargo.toml | 0 .../regression_4383/src/main.nr | 0 .../regression_4436/Nargo.toml | 0 .../regression_4436/src/main.nr | 0 .../slice_init_with_complex_type/Nargo.toml | 0 .../slice_init_with_complex_type/Prover.toml | 0 .../slice_init_with_complex_type/src/main.nr | 0 .../is_unconstrained/Nargo.toml | 6 + .../is_unconstrained/Prover.toml | 1 + .../is_unconstrained/src/main.nr | 14 ++ .../src/main.nr | 4 +- test_programs/gates_report.sh | 39 ++-- test_programs/rebuild.sh | 8 + tooling/debugger/ignored-tests.txt | 1 + 38 files changed, 584 insertions(+), 272 deletions(-) create mode 100644 compiler/noirc_evaluator/src/ssa/opt/resolve_is_unconstrained.rs create mode 100644 docs/docs/noir/standard_library/is_unconstrained.md create mode 100644 noir_stdlib/src/runtime.nr rename test_programs/{execution_success => compile_success_empty}/brillig_slice_input/Nargo.toml (100%) rename test_programs/{execution_success => compile_success_empty}/brillig_slice_input/src/main.nr (100%) rename test_programs/{execution_success => compile_success_empty}/regression_4383/Nargo.toml (100%) rename test_programs/{execution_success => compile_success_empty}/regression_4383/src/main.nr (100%) rename test_programs/{execution_success => compile_success_empty}/regression_4436/Nargo.toml (100%) rename test_programs/{execution_success => compile_success_empty}/regression_4436/src/main.nr (100%) rename test_programs/{execution_success => compile_success_empty}/slice_init_with_complex_type/Nargo.toml (100%) rename test_programs/{execution_success => compile_success_empty}/slice_init_with_complex_type/Prover.toml (100%) rename test_programs/{execution_success => compile_success_empty}/slice_init_with_complex_type/src/main.nr (100%) create mode 100644 test_programs/execution_success/is_unconstrained/Nargo.toml create mode 100644 test_programs/execution_success/is_unconstrained/Prover.toml create mode 100644 test_programs/execution_success/is_unconstrained/src/main.nr diff --git a/.github/workflows/gates_report.yml b/.github/workflows/gates_report.yml index 3d4bef1940e..0cc94a1a04d 100644 --- a/.github/workflows/gates_report.yml +++ b/.github/workflows/gates_report.yml @@ -1,88 +1,94 @@ -# name: Report gates diff - -# on: -# push: -# branches: -# - master -# pull_request: - -# jobs: -# build-nargo: -# runs-on: ubuntu-latest -# strategy: -# matrix: -# target: [x86_64-unknown-linux-gnu] - -# steps: -# - name: Checkout Noir repo -# uses: actions/checkout@v4 - -# - name: Setup toolchain -# uses: dtolnay/rust-toolchain@1.74.1 - -# - uses: Swatinem/rust-cache@v2 -# with: -# key: ${{ matrix.target }} -# cache-on-failure: true -# save-if: ${{ github.event_name != 'merge_group' }} - -# - name: Build Nargo -# run: cargo build --package nargo_cli --release - -# - name: Package artifacts -# run: | -# mkdir dist -# cp ./target/release/nargo ./dist/nargo -# 7z a -ttar -so -an ./dist/* | 7z a -si ./nargo-x86_64-unknown-linux-gnu.tar.gz - -# - name: Upload artifact -# uses: actions/upload-artifact@v4 -# with: -# name: nargo -# path: ./dist/* -# retention-days: 3 - - -# compare_gas_reports: -# needs: [build-nargo] -# runs-on: ubuntu-latest -# permissions: -# pull-requests: write - -# steps: -# - uses: actions/checkout@v4 - -# - name: Download nargo binary -# uses: actions/download-artifact@v4 -# with: -# name: nargo -# path: ./nargo - -# - name: Set nargo on PATH -# run: | -# nargo_binary="${{ github.workspace }}/nargo/nargo" -# chmod +x $nargo_binary -# echo "$(dirname $nargo_binary)" >> $GITHUB_PATH -# export PATH="$PATH:$(dirname $nargo_binary)" -# nargo -V - -# - name: Generate gates report -# working-directory: ./test_programs -# run: | -# ./gates_report.sh -# mv gates_report.json ../gates_report.json +name: Report gates diff + +on: + push: + branches: + - master + pull_request: + +jobs: + build-nargo: + runs-on: ubuntu-latest + strategy: + matrix: + target: [x86_64-unknown-linux-gnu] + + steps: + - name: Checkout Noir repo + uses: actions/checkout@v4 + + - name: Setup toolchain + uses: dtolnay/rust-toolchain@1.74.1 + + - uses: Swatinem/rust-cache@v2 + with: + key: ${{ matrix.target }} + cache-on-failure: true + save-if: ${{ github.event_name != 'merge_group' }} + + - name: Build Nargo + run: cargo build --package nargo_cli --release + + - name: Package artifacts + run: | + mkdir dist + cp ./target/release/nargo ./dist/nargo + 7z a -ttar -so -an ./dist/* | 7z a -si ./nargo-x86_64-unknown-linux-gnu.tar.gz + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: nargo + path: ./dist/* + retention-days: 3 + + + compare_gates_reports: + needs: [build-nargo] + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + - uses: actions/checkout@v4 + + - name: Install `bb` + run: | + ./scripts/install_bb.sh + echo "$HOME/.bb/" >> $GITHUB_PATH + + - name: Download nargo binary + uses: actions/download-artifact@v4 + with: + name: nargo + path: ./nargo + + - name: Set nargo on PATH + run: | + nargo_binary="${{ github.workspace }}/nargo/nargo" + chmod +x $nargo_binary + echo "$(dirname $nargo_binary)" >> $GITHUB_PATH + export PATH="$PATH:$(dirname $nargo_binary)" + nargo -V + + - name: Generate gates report + working-directory: ./test_programs + run: | + ./rebuild.sh + ./gates_report.sh + mv gates_report.json ../gates_report.json -# - name: Compare gates reports -# id: gates_diff -# uses: vezenovm/noir-gates-diff@acf12797860f237117e15c0d6e08d64253af52b6 -# with: -# report: gates_report.json -# summaryQuantile: 0.9 # only display the 10% most significant circuit size diffs in the summary (defaults to 20%) - -# - name: Add gates diff to sticky comment -# if: github.event_name == 'pull_request' || github.event_name == 'pull_request_target' -# uses: marocchino/sticky-pull-request-comment@v2 -# with: -# # delete the comment in case changes no longer impact circuit sizes -# delete: ${{ !steps.gates_diff.outputs.markdown }} -# message: ${{ steps.gates_diff.outputs.markdown }} + - name: Compare gates reports + id: gates_diff + uses: vezenovm/noir-gates-diff@acf12797860f237117e15c0d6e08d64253af52b6 + with: + report: gates_report.json + summaryQuantile: 0.9 # only display the 10% most significant circuit size diffs in the summary (defaults to 20%) + + - name: Add gates diff to sticky comment + if: github.event_name == 'pull_request' || github.event_name == 'pull_request_target' + uses: marocchino/sticky-pull-request-comment@v2 + with: + # delete the comment in case changes no longer impact circuit sizes + delete: ${{ !steps.gates_diff.outputs.markdown }} + message: ${{ steps.gates_diff.outputs.markdown }} diff --git a/acvm-repo/acvm/src/compiler/transformers/csat.rs b/acvm-repo/acvm/src/compiler/transformers/csat.rs index 9e2e3091c74..12a37e3e37a 100644 --- a/acvm-repo/acvm/src/compiler/transformers/csat.rs +++ b/acvm-repo/acvm/src/compiler/transformers/csat.rs @@ -210,16 +210,16 @@ impl CSatTransformer { } } else { // No more usable elements left in the old opcode - opcode.linear_combinations = remaining_linear_terms; break; } } + opcode.linear_combinations.extend(remaining_linear_terms); + // Constraint this intermediate_opcode to be equal to the temp variable by adding it into the IndexMap // We need a unique name for our intermediate variable // XXX: Another optimization, which could be applied in another algorithm // If two opcodes have a large fan-in/out and they share a few common terms, then we should create intermediate variables for them // Do some sort of subset matching algorithm for this on the terms of the polynomial - let inter_var = Self::get_or_create_intermediate_vars( intermediate_variables, intermediate_opcode, diff --git a/compiler/noirc_evaluator/src/ssa.rs b/compiler/noirc_evaluator/src/ssa.rs index 69e5f6ddfcc..c2fe7878bf8 100644 --- a/compiler/noirc_evaluator/src/ssa.rs +++ b/compiler/noirc_evaluator/src/ssa.rs @@ -56,6 +56,7 @@ pub(crate) fn optimize_into_acir( .run_pass(Ssa::defunctionalize, "After Defunctionalization:") .run_pass(Ssa::remove_paired_rc, "After Removing Paired rc_inc & rc_decs:") .run_pass(Ssa::inline_functions, "After Inlining:") + .run_pass(Ssa::resolve_is_unconstrained, "After Resolving IsUnconstrained:") // Run mem2reg with the CFG separated into blocks .run_pass(Ssa::mem2reg, "After Mem2Reg:") .run_pass(Ssa::as_slice_optimization, "After `as_slice` optimization") diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs index 90cdbb650c2..b6e88a8d4b0 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs @@ -614,13 +614,44 @@ impl AcirContext { expr.push_multiplication_term(FieldElement::one(), lhs_witness, rhs_witness); self.add_data(AcirVarData::Expr(expr)) } - ( - AcirVarData::Expr(_) | AcirVarData::Witness(_), - AcirVarData::Expr(_) | AcirVarData::Witness(_), - ) => { + (AcirVarData::Expr(expression), AcirVarData::Witness(witness)) + | (AcirVarData::Witness(witness), AcirVarData::Expr(expression)) + if expression.is_linear() => + { + let mut expr = Expression::default(); + for term in expression.linear_combinations.iter() { + expr.push_multiplication_term(term.0, term.1, witness); + } + expr.push_addition_term(expression.q_c, witness); + self.add_data(AcirVarData::Expr(expr)) + } + (AcirVarData::Expr(lhs_expr), AcirVarData::Expr(rhs_expr)) => { + let degree_one = if lhs_expr.is_linear() && rhs_expr.is_degree_one_univariate() { + Some((lhs_expr, rhs_expr)) + } else if rhs_expr.is_linear() && lhs_expr.is_degree_one_univariate() { + Some((rhs_expr, lhs_expr)) + } else { + None + }; + if let Some((lin, univariate)) = degree_one { + let mut expr = Expression::default(); + let rhs_term = univariate.linear_combinations[0]; + for term in lin.linear_combinations.iter() { + expr.push_multiplication_term(term.0 * rhs_term.0, term.1, rhs_term.1); + } + expr.push_addition_term(lin.q_c * rhs_term.0, rhs_term.1); + expr.sort(); + expr = expr.add_mul(univariate.q_c, &lin); + self.add_data(AcirVarData::Expr(expr)) + } else { + let lhs = self.get_or_create_witness_var(lhs)?; + let rhs = self.get_or_create_witness_var(rhs)?; + self.mul_var(lhs, rhs)? + } + } + _ => { let lhs = self.get_or_create_witness_var(lhs)?; let rhs = self.get_or_create_witness_var(rhs)?; - self.mul_var(lhs, rhs)? } }; diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index f136d3c5fb2..93ea703721f 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -65,6 +65,7 @@ pub(crate) enum Intrinsic { FromField, AsField, AsWitness, + IsUnconstrained, } impl std::fmt::Display for Intrinsic { @@ -89,6 +90,7 @@ impl std::fmt::Display for Intrinsic { Intrinsic::FromField => write!(f, "from_field"), Intrinsic::AsField => write!(f, "as_field"), Intrinsic::AsWitness => write!(f, "as_witness"), + Intrinsic::IsUnconstrained => write!(f, "is_unconstrained"), } } } @@ -116,7 +118,8 @@ impl Intrinsic { | Intrinsic::SliceRemove | Intrinsic::StrAsBytes | Intrinsic::FromField - | Intrinsic::AsField => false, + | Intrinsic::AsField + | Intrinsic::IsUnconstrained => false, // Some black box functions have side-effects Intrinsic::BlackBox(func) => matches!(func, BlackBoxFunc::RecursiveAggregation), @@ -145,6 +148,7 @@ impl Intrinsic { "from_field" => Some(Intrinsic::FromField), "as_field" => Some(Intrinsic::AsField), "as_witness" => Some(Intrinsic::AsWitness), + "is_unconstrained" => Some(Intrinsic::IsUnconstrained), other => BlackBoxFunc::lookup(other).map(Intrinsic::BlackBox), } } diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs index 8e320de9337..8f57d9de368 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -294,6 +294,7 @@ pub(super) fn simplify_call( SimplifyResult::SimplifiedToInstruction(instruction) } Intrinsic::AsWitness => SimplifyResult::None, + Intrinsic::IsUnconstrained => SimplifyResult::None, } } diff --git a/compiler/noirc_evaluator/src/ssa/opt/mod.rs b/compiler/noirc_evaluator/src/ssa/opt/mod.rs index 27536d59ea5..f6c3f022bfc 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/mod.rs @@ -17,5 +17,6 @@ mod rc; mod remove_bit_shifts; mod remove_enable_side_effects; mod remove_if_else; +mod resolve_is_unconstrained; mod simplify_cfg; mod unrolling; diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs index 9309652d508..464faa57323 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs @@ -158,7 +158,8 @@ impl Context { | Intrinsic::FromField | Intrinsic::AsField | Intrinsic::AsSlice - | Intrinsic::AsWitness => false, + | Intrinsic::AsWitness + | Intrinsic::IsUnconstrained => false, }, // We must assume that functions contain a side effect as we cannot inspect more deeply. diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs index 3d2b5142219..91b455dbf29 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs @@ -232,6 +232,7 @@ fn slice_capacity_change( | Intrinsic::BlackBox(_) | Intrinsic::FromField | Intrinsic::AsField - | Intrinsic::AsWitness => SizeChange::None, + | Intrinsic::AsWitness + | Intrinsic::IsUnconstrained => SizeChange::None, } } diff --git a/compiler/noirc_evaluator/src/ssa/opt/resolve_is_unconstrained.rs b/compiler/noirc_evaluator/src/ssa/opt/resolve_is_unconstrained.rs new file mode 100644 index 00000000000..2c9e33ae528 --- /dev/null +++ b/compiler/noirc_evaluator/src/ssa/opt/resolve_is_unconstrained.rs @@ -0,0 +1,56 @@ +use crate::ssa::{ + ir::{ + function::{Function, RuntimeType}, + instruction::{Instruction, Intrinsic}, + types::Type, + value::Value, + }, + ssa_gen::Ssa, +}; +use acvm::FieldElement; +use fxhash::FxHashSet as HashSet; + +impl Ssa { + /// An SSA pass to find any calls to `Intrinsic::IsUnconstrained` and replacing any uses of the result of the intrinsic + /// with the resolved boolean value. + /// Note that this pass must run after the pass that does runtime separation, since in SSA generation an ACIR function can end up targeting brillig. + #[tracing::instrument(level = "trace", skip(self))] + pub(crate) fn resolve_is_unconstrained(mut self) -> Self { + for func in self.functions.values_mut() { + replace_is_unconstrained_result(func); + } + self + } +} + +fn replace_is_unconstrained_result(func: &mut Function) { + let mut is_unconstrained_calls = HashSet::default(); + // Collect all calls to is_unconstrained + for block_id in func.reachable_blocks() { + for &instruction_id in func.dfg[block_id].instructions() { + let target_func = match &func.dfg[instruction_id] { + Instruction::Call { func, .. } => *func, + _ => continue, + }; + + if let Value::Intrinsic(Intrinsic::IsUnconstrained) = &func.dfg[target_func] { + is_unconstrained_calls.insert(instruction_id); + } + } + } + + for instruction_id in is_unconstrained_calls { + let call_returns = func.dfg.instruction_results(instruction_id); + let original_return_id = call_returns[0]; + + // We replace the result with a fresh id. This will be unused, so the DIE pass will remove the leftover intrinsic call. + func.dfg.replace_result(instruction_id, original_return_id); + + let is_within_unconstrained = func.dfg.make_constant( + FieldElement::from(matches!(func.runtime(), RuntimeType::Brillig)), + Type::bool(), + ); + // Replace all uses of the original return value with the constant + func.dfg.set_value_from_id(original_return_id, is_within_unconstrained); + } +} diff --git a/compiler/noirc_frontend/src/elaborator/expressions.rs b/compiler/noirc_frontend/src/elaborator/expressions.rs index b33bca33225..3502565c264 100644 --- a/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -194,7 +194,7 @@ impl<'context> Elaborator<'context> { let expr_id = self.interner.push_expr(ident); self.interner.push_expr_location(expr_id, call_expr_span, self.file); let ident = old_value.ident.clone(); - let typ = self.type_check_variable(ident, expr_id); + let typ = self.type_check_variable(ident, expr_id, None); self.interner.push_expr_type(expr_id, typ.clone()); capture_types.push(typ); fmt_str_idents.push(expr_id); @@ -291,16 +291,25 @@ impl<'context> Elaborator<'context> { Some(method_ref) => { // Automatically add `&mut` if the method expects a mutable reference and // the object is not already one. - if let HirMethodReference::FuncId(func_id) = &method_ref { - if *func_id != FuncId::dummy_id() { - let function_type = self.interner.function_meta(func_id).typ.clone(); - - self.try_add_mutable_reference_to_object( - &function_type, - &mut object_type, - &mut object, - ); + let func_id = match &method_ref { + HirMethodReference::FuncId(func_id) => *func_id, + HirMethodReference::TraitMethodId(method_id, _) => { + let id = self.interner.trait_method_id(*method_id); + let definition = self.interner.definition(id); + let DefinitionKind::Function(func_id) = definition.kind else { + unreachable!("Expected trait function to be a DefinitionKind::Function") + }; + func_id } + }; + + if func_id != FuncId::dummy_id() { + let function_type = self.interner.function_meta(&func_id).typ.clone(); + self.try_add_mutable_reference_to_object( + &function_type, + &mut object_type, + &mut object, + ); } // These arguments will be given to the desugared function call. @@ -322,6 +331,7 @@ impl<'context> Elaborator<'context> { let generics = method_call.generics.map(|option_inner| { option_inner.into_iter().map(|generic| self.resolve_type(generic)).collect() }); + let turbofish_generics = generics.clone(); let method_call = HirMethodCallExpression { method, object, arguments, location, generics }; @@ -335,7 +345,8 @@ impl<'context> Elaborator<'context> { self.interner, ); - let func_type = self.type_check_variable(function_name, function_id); + let func_type = + self.type_check_variable(function_name, function_id, turbofish_generics); // Type check the new call now that it has been changed from a method call // to a function call. This way we avoid duplicating code. diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 0b9fc39df88..370c5c50764 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -503,21 +503,20 @@ impl<'context> Elaborator<'context> { ) -> Vec { where_clause .iter() - .cloned() .filter_map(|constraint| self.resolve_trait_constraint(constraint)) .collect() } pub fn resolve_trait_constraint( &mut self, - constraint: UnresolvedTraitConstraint, + constraint: &UnresolvedTraitConstraint, ) -> Option { - let typ = self.resolve_type(constraint.typ); + let typ = self.resolve_type(constraint.typ.clone()); let trait_generics = - vecmap(constraint.trait_bound.trait_generics, |typ| self.resolve_type(typ)); + vecmap(&constraint.trait_bound.trait_generics, |typ| self.resolve_type(typ.clone())); let span = constraint.trait_bound.trait_path.span(); - let the_trait = self.lookup_trait_or_error(constraint.trait_bound.trait_path)?; + let the_trait = self.lookup_trait_or_error(constraint.trait_bound.trait_path.clone())?; let trait_id = the_trait.id; let expected_generics = the_trait.generics.len(); @@ -877,41 +876,69 @@ impl<'context> Elaborator<'context> { self.file = trait_impl.file_id; self.local_module = trait_impl.module_id; - let unresolved_type = trait_impl.object_type; - let self_type_span = unresolved_type.span; - let old_generics_length = self.generics.len(); self.generics = trait_impl.resolved_generics; + self.current_trait_impl = trait_impl.impl_id; - let trait_generics = - vecmap(&trait_impl.trait_generics, |generic| self.resolve_type(generic.clone())); + self.elaborate_functions(trait_impl.methods); - let self_type = trait_impl.resolved_object_type.unwrap_or(Type::Error); + self.self_type = None; + self.current_trait_impl = None; + self.generics.clear(); + } - let impl_id = - trait_impl.impl_id.expect("An impl's id should be set during define_function_metas"); + fn collect_impls( + &mut self, + module: LocalModuleId, + impls: &mut [(Vec, Span, UnresolvedFunctions)], + ) { + self.local_module = module; - self.current_trait_impl = trait_impl.impl_id; + for (generics, span, unresolved) in impls { + self.file = unresolved.file_id; + let old_generic_count = self.generics.len(); + self.add_generics(generics); + self.declare_methods_on_struct(false, unresolved, *span); + self.generics.truncate(old_generic_count); + } + } - let mut methods = trait_impl.methods.function_ids(); + fn collect_trait_impl(&mut self, trait_impl: &mut UnresolvedTraitImpl) { + self.local_module = trait_impl.module_id; + self.file = trait_impl.file_id; + trait_impl.trait_id = self.resolve_trait_by_path(trait_impl.trait_path.clone()); - self.elaborate_functions(trait_impl.methods); + let self_type = trait_impl.methods.self_type.clone(); + let self_type = + self_type.expect("Expected struct type to be set before collect_trait_impl"); + + let self_type_span = trait_impl.object_type.span; if matches!(self_type, Type::MutableReference(_)) { let span = self_type_span.unwrap_or_else(|| trait_impl.trait_path.span()); self.push_err(DefCollectorErrorKind::MutableReferenceInTraitImpl { span }); } + assert!(trait_impl.trait_id.is_some()); if let Some(trait_id) = trait_impl.trait_id { + self.collect_trait_impl_methods(trait_id, trait_impl); + + let span = trait_impl.object_type.span.expect("All trait self types should have spans"); + self.generics = trait_impl.resolved_generics.clone(); + self.declare_methods_on_struct(true, &mut trait_impl.methods, span); + + let methods = trait_impl.methods.function_ids(); for func_id in &methods { self.interner.set_function_trait(*func_id, self_type.clone(), trait_id); } let where_clause = trait_impl .where_clause - .into_iter() + .iter() .flat_map(|item| self.resolve_trait_constraint(item)) .collect(); + let trait_generics = trait_impl.resolved_trait_generics.clone(); + let resolved_trait_impl = Shared::new(TraitImpl { ident: trait_impl.trait_path.last_segment().clone(), typ: self_type.clone(), @@ -928,7 +955,7 @@ impl<'context> Elaborator<'context> { self_type.clone(), trait_id, trait_generics, - impl_id, + trait_impl.impl_id.expect("impl_id should be set in define_function_metas"), generics, resolved_trait_impl, ) { @@ -945,40 +972,7 @@ impl<'context> Elaborator<'context> { } } - self.self_type = None; - self.current_trait_impl = None; - self.generics.truncate(old_generics_length); - } - - fn collect_impls( - &mut self, - module: LocalModuleId, - impls: &mut [(Vec, Span, UnresolvedFunctions)], - ) { - self.local_module = module; - - for (generics, span, unresolved) in impls { - self.file = unresolved.file_id; - let old_generic_count = self.generics.len(); - self.add_generics(generics); - self.declare_methods_on_struct(false, unresolved, *span); - self.generics.truncate(old_generic_count); - } - } - - fn collect_trait_impl(&mut self, trait_impl: &mut UnresolvedTraitImpl) { - self.local_module = trait_impl.module_id; - self.file = trait_impl.file_id; - trait_impl.trait_id = self.resolve_trait_by_path(trait_impl.trait_path.clone()); - - if let Some(trait_id) = trait_impl.trait_id { - self.collect_trait_impl_methods(trait_id, trait_impl); - - let span = trait_impl.object_type.span.expect("All trait self types should have spans"); - self.generics = trait_impl.resolved_generics.clone(); - self.declare_methods_on_struct(true, &mut trait_impl.methods, span); - self.generics.clear(); - } + self.generics.clear(); } fn get_module_mut(&mut self, module: ModuleId) -> &mut ModuleData { @@ -992,10 +986,9 @@ impl<'context> Elaborator<'context> { functions: &mut UnresolvedFunctions, span: Span, ) { - let self_type = functions - .self_type - .as_ref() - .expect("Expected struct type to be set before declare_methods_on_struct"); + let self_type = functions.self_type.as_ref(); + let self_type = + self_type.expect("Expected struct type to be set before declare_methods_on_struct"); let function_ids = functions.function_ids(); @@ -1291,10 +1284,13 @@ impl<'context> Elaborator<'context> { self.local_module = trait_impl.module_id; let unresolved_type = &trait_impl.object_type; - let self_type_span = unresolved_type.span; self.add_generics(&trait_impl.generics); trait_impl.resolved_generics = self.generics.clone(); + let trait_generics = + vecmap(&trait_impl.trait_generics, |generic| self.resolve_type(generic.clone())); + trait_impl.resolved_trait_generics = trait_generics; + let self_type = self.resolve_type(unresolved_type.clone()); self.self_type = Some(self_type.clone()); diff --git a/compiler/noirc_frontend/src/elaborator/patterns.rs b/compiler/noirc_frontend/src/elaborator/patterns.rs index 810e1b90743..17c11b88f4a 100644 --- a/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -5,6 +5,7 @@ use rustc_hash::FxHashSet as HashSet; use crate::{ ast::ERROR_IDENT, hir::{ + def_collector::dc_crate::CompilationError, resolution::errors::ResolverError, type_check::{Source, TypeCheckError}, }, @@ -330,9 +331,9 @@ impl<'context> Elaborator<'context> { ) -> (ExprId, Type) { let span = variable.span; let expr = self.resolve_variable(variable); - let id = self.interner.push_expr(HirExpression::Ident(expr.clone(), generics)); + let id = self.interner.push_expr(HirExpression::Ident(expr.clone(), generics.clone())); self.interner.push_expr_location(id, span, self.file); - let typ = self.type_check_variable(expr, id); + let typ = self.type_check_variable(expr, id, generics); self.interner.push_expr_type(id, typ.clone()); (id, typ) } @@ -384,14 +385,19 @@ impl<'context> Elaborator<'context> { } } - pub(super) fn type_check_variable(&mut self, ident: HirIdent, expr_id: ExprId) -> Type { + pub(super) fn type_check_variable( + &mut self, + ident: HirIdent, + expr_id: ExprId, + generics: Option>, + ) -> Type { let mut bindings = TypeBindings::new(); // Add type bindings from any constraints that were used. // We need to do this first since otherwise instantiating the type below // will replace each trait generic with a fresh type variable, rather than // the type used in the trait constraint (if it exists). See #4088. - if let ImplKind::TraitMethod(_, constraint, _) = &ident.impl_kind { + if let ImplKind::TraitMethod(_, constraint, assumed) = &ident.impl_kind { let the_trait = self.interner.get_trait(constraint.trait_id); assert_eq!(the_trait.generics.len(), constraint.trait_generics.len()); @@ -401,6 +407,16 @@ impl<'context> Elaborator<'context> { bindings.insert(param.id(), (param.clone(), arg.clone())); } } + + // If the trait impl is already assumed to exist we should add any type bindings for `Self`. + // Otherwise `self` will be replaced with a fresh type variable, which will require the user + // to specify a redundant type annotation. + if *assumed { + bindings.insert( + the_trait.self_type_typevar_id, + (the_trait.self_type_typevar.clone(), constraint.typ.clone()), + ); + } } // An identifiers type may be forall-quantified in the case of generic functions. @@ -409,10 +425,21 @@ impl<'context> Elaborator<'context> { // variable to handle generic functions. let t = self.interner.id_type_substitute_trait_as_type(ident.id); + let definition = self.interner.try_definition(ident.id); + let function_generic_count = definition.map_or(0, |definition| match &definition.kind { + DefinitionKind::Function(function) => { + self.interner.function_modifiers(function).generic_count + } + _ => 0, + }); + + let span = self.interner.expr_span(&expr_id); + let location = self.interner.expr_location(&expr_id); // This instantiates a trait's generics as well which need to be set // when the constraint below is later solved for when the function is // finished. How to link the two? - let (typ, bindings) = t.instantiate_with_bindings(bindings, self.interner); + let (typ, bindings) = + self.instantiate(t, bindings, generics, function_generic_count, span, location); // Push any trait constraints required by this definition to the context // to be checked later when the type of this variable is further constrained. @@ -447,6 +474,39 @@ impl<'context> Elaborator<'context> { typ } + fn instantiate( + &mut self, + typ: Type, + bindings: TypeBindings, + turbofish_generics: Option>, + function_generic_count: usize, + span: Span, + location: Location, + ) -> (Type, TypeBindings) { + match turbofish_generics { + Some(turbofish_generics) => { + if turbofish_generics.len() != function_generic_count { + let type_check_err = TypeCheckError::IncorrectTurbofishGenericCount { + expected_count: function_generic_count, + actual_count: turbofish_generics.len(), + span, + }; + self.errors.push((CompilationError::TypeError(type_check_err), location.file)); + typ.instantiate_with_bindings(bindings, self.interner) + } else { + // Fetch the count of any implicit generics on the function, such as + // for a method within a generic impl. + let implicit_generic_count = match &typ { + Type::Forall(generics, _) => generics.len() - function_generic_count, + _ => 0, + }; + typ.instantiate_with(turbofish_generics, self.interner, implicit_generic_count) + } + } + None => typ.instantiate_with_bindings(bindings, self.interner), + } + } + fn get_ident_from_path(&mut self, path: Path) -> (HirIdent, usize) { let location = Location::new(path.span(), self.file); diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index 838aac3f067..05147af5459 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -121,11 +121,15 @@ pub struct UnresolvedTraitImpl { pub generics: UnresolvedGenerics, pub where_clause: Vec, - // These fields are filled in later + // Every field after this line is filled in later in the elaborator pub trait_id: Option, pub impl_id: Option, pub resolved_object_type: Option, pub resolved_generics: Vec<(Rc, TypeVariable, Span)>, + + // The resolved generic on the trait itself. E.g. it is the `` in + // `impl Foo for Bar { ... }` + pub resolved_trait_generics: Vec, } #[derive(Clone)] diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs index baea00ad23d..df8fe6dc878 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -195,6 +195,7 @@ impl<'a> ModCollector<'a> { impl_id: None, resolved_object_type: None, resolved_generics: Vec::new(), + resolved_trait_generics: Vec::new(), }; self.def_collector.items.trait_impls.push(unresolved_trait_impl); diff --git a/compiler/noirc_frontend/src/hir/type_check/expr.rs b/compiler/noirc_frontend/src/hir/type_check/expr.rs index abff466e1d5..336c1cedbb6 100644 --- a/compiler/noirc_frontend/src/hir/type_check/expr.rs +++ b/compiler/noirc_frontend/src/hir/type_check/expr.rs @@ -371,7 +371,7 @@ impl<'interner> TypeChecker<'interner> { // We need to do this first since otherwise instantiating the type below // will replace each trait generic with a fresh type variable, rather than // the type used in the trait constraint (if it exists). See #4088. - if let ImplKind::TraitMethod(_, constraint, _) = &ident.impl_kind { + if let ImplKind::TraitMethod(_, constraint, assumed) = &ident.impl_kind { let the_trait = self.interner.get_trait(constraint.trait_id); assert_eq!(the_trait.generics.len(), constraint.trait_generics.len()); @@ -381,6 +381,16 @@ impl<'interner> TypeChecker<'interner> { bindings.insert(param.id(), (param.clone(), arg.clone())); } } + + // If the trait impl is already assumed to exist we should add any type bindings for `Self`. + // Otherwise `self` will be replaced with a fresh type variable, which will require the user + // to specify a redundant type annotation. + if *assumed { + bindings.insert( + the_trait.self_type_typevar_id, + (the_trait.self_type_typevar.clone(), constraint.typ.clone()), + ); + } } // An identifiers type may be forall-quantified in the case of generic functions. @@ -389,8 +399,6 @@ impl<'interner> TypeChecker<'interner> { // variable to handle generic functions. let t = self.interner.id_type_substitute_trait_as_type(ident.id); - let span = self.interner.expr_span(expr_id); - let definition = self.interner.try_definition(ident.id); let function_generic_count = definition.map_or(0, |definition| match &definition.kind { DefinitionKind::Function(function) => { @@ -399,6 +407,7 @@ impl<'interner> TypeChecker<'interner> { _ => 0, }); + let span = self.interner.expr_span(expr_id); // This instantiates a trait's generics as well which need to be set // when the constraint below is later solved for when the function is // finished. How to link the two? diff --git a/compiler/noirc_frontend/src/tests.rs b/compiler/noirc_frontend/src/tests.rs index 7bf5655486b..bd81752c046 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -1430,14 +1430,14 @@ fn specify_method_types_with_turbofish() { } impl Foo { - fn generic_method(_self: Self) where U: Default { + fn generic_method(_self: Self) -> U where U: Default { U::default() } } fn main() { let foo: Foo = Foo { inner: 1 }; - foo.generic_method::(); + let _ = foo.generic_method::(); } "#; let errors = get_program_errors(src); diff --git a/docs/docs/noir/standard_library/is_unconstrained.md b/docs/docs/noir/standard_library/is_unconstrained.md new file mode 100644 index 00000000000..bb157e719dc --- /dev/null +++ b/docs/docs/noir/standard_library/is_unconstrained.md @@ -0,0 +1,59 @@ +--- +title: Is Unconstrained Function +description: + The is_unconstrained function returns wether the context at that point of the program is unconstrained or not. +keywords: + [ + unconstrained + ] +--- + +It's very common for functions in circuits to take unconstrained hints of an expensive computation and then verify it. This is done by running the hint in an unconstrained context and then verifying the result in a constrained context. + +When a function is marked as unconstrained, any subsequent functions that it calls will also be run in an unconstrained context. However, if we are implementing a library function, other users might call it within an unconstrained context or a constrained one. Generally, in an unconstrained context we prefer just computing the result instead of taking a hint of it and verifying it, since that'd mean doing the same computation twice: + +```rust + +fn my_expensive_computation(){ + ... +} + +unconstrained fn my_expensive_computation_hint(){ + my_expensive_computation() +} + +pub fn external_interface(){ + my_expensive_computation_hint(); + // verify my_expensive_computation: If external_interface is called from unconstrained, this is redundant + ... +} + +``` + +In order to improve the performance in an unconstrained context you can use the function at `std::runtime::is_unconstrained() -> bool`: + + +```rust +use dep::std::runtime::is_unconstrained; + +fn my_expensive_computation(){ + ... +} + +unconstrained fn my_expensive_computation_hint(){ + my_expensive_computation() +} + +pub fn external_interface(){ + if is_unconstrained() { + my_expensive_computation(); + } else { + my_expensive_computation_hint(); + // verify my_expensive_computation + ... + } +} + +``` + +The is_unconstrained result is resolved at compile time, so in unconstrained contexts the compiler removes the else branch, and in constrained contexts the compiler removes the if branch, reducing the amount of compute necessary to run external_interface. \ No newline at end of file diff --git a/noir_stdlib/src/eddsa.nr b/noir_stdlib/src/eddsa.nr index 1ab0158af8f..337969be90e 100644 --- a/noir_stdlib/src/eddsa.nr +++ b/noir_stdlib/src/eddsa.nr @@ -45,7 +45,7 @@ where H: Hasher + Default { // Ensure S < Subgroup Order assert(signature_s.lt(bjj.suborder)); // Calculate the h = H(R, A, msg) - let mut hasher: H = H::default(); + let mut hasher = H::default(); hasher.write(signature_r8_x); hasher.write(signature_r8_y); hasher.write(pub_key_x); diff --git a/noir_stdlib/src/field/bn254.nr b/noir_stdlib/src/field/bn254.nr index 2e82d9e7c23..bcdc23f80dc 100644 --- a/noir_stdlib/src/field/bn254.nr +++ b/noir_stdlib/src/field/bn254.nr @@ -1,11 +1,13 @@ +use crate::runtime::is_unconstrained; + // The low and high decomposition of the field modulus global PLO: Field = 53438638232309528389504892708671455233; global PHI: Field = 64323764613183177041862057485226039389; global TWO_POW_128: Field = 0x100000000000000000000000000000000; -/// A hint for decomposing a single field into two 16 byte fields. -unconstrained fn decompose_unsafe(x: Field) -> (Field, Field) { +// Decomposes a single field into two 16 byte fields. +fn compute_decomposition(x: Field) -> (Field, Field) { let x_bytes = x.to_le_bytes(32); let mut low: Field = 0; @@ -21,37 +23,11 @@ unconstrained fn decompose_unsafe(x: Field) -> (Field, Field) { (low, high) } -// Assert that (alo > blo && ahi >= bhi) || (alo <= blo && ahi > bhi) -fn assert_gt_limbs(a: (Field, Field), b: (Field, Field)) { - let (alo, ahi) = a; - let (blo, bhi) = b; - let borrow = lte_unsafe_16(alo, blo); - - let rlo = alo - blo - 1 + (borrow as Field) * TWO_POW_128; - let rhi = ahi - bhi - (borrow as Field); - - rlo.assert_max_bit_size(128); - rhi.assert_max_bit_size(128); -} - -/// Decompose a single field into two 16 byte fields. -pub fn decompose(x: Field) -> (Field, Field) { - // Take hints of the decomposition - let (xlo, xhi) = decompose_unsafe(x); - - // Range check the limbs - xlo.assert_max_bit_size(128); - xhi.assert_max_bit_size(128); - - // Check that the decomposition is correct - assert_eq(x, xlo + TWO_POW_128 * xhi); - - // Assert that the decomposition of P is greater than the decomposition of x - assert_gt_limbs((PLO, PHI), (xlo, xhi)); - (xlo, xhi) +unconstrained fn decompose_hint(x: Field) -> (Field, Field) { + compute_decomposition(x) } -fn lt_unsafe_internal(x: Field, y: Field, num_bytes: u32) -> bool { +fn compute_lt(x: Field, y: Field, num_bytes: u32) -> bool { let x_bytes = x.to_le_radix(256, num_bytes); let y_bytes = y.to_le_radix(256, num_bytes); let mut x_is_lt = false; @@ -70,29 +46,67 @@ fn lt_unsafe_internal(x: Field, y: Field, num_bytes: u32) -> bool { x_is_lt } -fn lte_unsafe_internal(x: Field, y: Field, num_bytes: u32) -> bool { +fn compute_lte(x: Field, y: Field, num_bytes: u32) -> bool { if x == y { true } else { - lt_unsafe_internal(x, y, num_bytes) + compute_lt(x, y, num_bytes) } } -unconstrained fn lt_unsafe_32(x: Field, y: Field) -> bool { - lt_unsafe_internal(x, y, 32) +unconstrained fn lt_32_hint(x: Field, y: Field) -> bool { + compute_lt(x, y, 32) } -unconstrained fn lte_unsafe_16(x: Field, y: Field) -> bool { - lte_unsafe_internal(x, y, 16) +unconstrained fn lte_16_hint(x: Field, y: Field) -> bool { + compute_lte(x, y, 16) +} + +// Assert that (alo > blo && ahi >= bhi) || (alo <= blo && ahi > bhi) +fn assert_gt_limbs(a: (Field, Field), b: (Field, Field)) { + let (alo, ahi) = a; + let (blo, bhi) = b; + let borrow = lte_16_hint(alo, blo); + + let rlo = alo - blo - 1 + (borrow as Field) * TWO_POW_128; + let rhi = ahi - bhi - (borrow as Field); + + rlo.assert_max_bit_size(128); + rhi.assert_max_bit_size(128); +} + +/// Decompose a single field into two 16 byte fields. +pub fn decompose(x: Field) -> (Field, Field) { + if is_unconstrained() { + compute_decomposition(x) + } else { + // Take hints of the decomposition + let (xlo, xhi) = decompose_hint(x); + + // Range check the limbs + xlo.assert_max_bit_size(128); + xhi.assert_max_bit_size(128); + + // Check that the decomposition is correct + assert_eq(x, xlo + TWO_POW_128 * xhi); + + // Assert that the decomposition of P is greater than the decomposition of x + assert_gt_limbs((PLO, PHI), (xlo, xhi)); + (xlo, xhi) + } } pub fn assert_gt(a: Field, b: Field) { - // Decompose a and b - let a_limbs = decompose(a); - let b_limbs = decompose(b); + if is_unconstrained() { + assert(compute_lt(b, a, 32)); + } else { + // Decompose a and b + let a_limbs = decompose(a); + let b_limbs = decompose(b); - // Assert that a_limbs is greater than b_limbs - assert_gt_limbs(a_limbs, b_limbs) + // Assert that a_limbs is greater than b_limbs + assert_gt_limbs(a_limbs, b_limbs) + } } pub fn assert_lt(a: Field, b: Field) { @@ -100,14 +114,19 @@ pub fn assert_lt(a: Field, b: Field) { } pub fn gt(a: Field, b: Field) -> bool { - if a == b { - false - } else if lt_unsafe_32(a, b) { - assert_gt(b, a); + if is_unconstrained() { + compute_lt(b, a, 32) + } else if a == b { false - } else { - assert_gt(a, b); - true + } else { + // Take a hint of the comparison and verify it + if lt_32_hint(a, b) { + assert_gt(b, a); + false + } else { + assert_gt(a, b); + true + } } } @@ -117,44 +136,41 @@ pub fn lt(a: Field, b: Field) -> bool { mod tests { // TODO: Allow imports from "super" - use crate::field::bn254::{ - decompose_unsafe, decompose, lt_unsafe_internal, assert_gt, gt, lt, TWO_POW_128, - lte_unsafe_internal, PLO, PHI - }; + use crate::field::bn254::{decompose_hint, decompose, compute_lt, assert_gt, gt, lt, TWO_POW_128, compute_lte, PLO, PHI}; #[test] - fn check_decompose_unsafe() { - assert_eq(decompose_unsafe(TWO_POW_128), (0, 1)); - assert_eq(decompose_unsafe(TWO_POW_128 + 0x1234567890), (0x1234567890, 1)); - assert_eq(decompose_unsafe(0x1234567890), (0x1234567890, 0)); + fn check_decompose() { + assert_eq(decompose(TWO_POW_128), (0, 1)); + assert_eq(decompose(TWO_POW_128 + 0x1234567890), (0x1234567890, 1)); + assert_eq(decompose(0x1234567890), (0x1234567890, 0)); } #[test] - fn check_decompose() { + unconstrained fn check_decompose_unconstrained() { assert_eq(decompose(TWO_POW_128), (0, 1)); assert_eq(decompose(TWO_POW_128 + 0x1234567890), (0x1234567890, 1)); assert_eq(decompose(0x1234567890), (0x1234567890, 0)); } #[test] - fn check_lt_unsafe() { - assert(lt_unsafe_internal(0, 1, 16)); - assert(lt_unsafe_internal(0, 0x100, 16)); - assert(lt_unsafe_internal(0x100, TWO_POW_128 - 1, 16)); - assert(!lt_unsafe_internal(0, TWO_POW_128, 16)); + fn check_compute_lt() { + assert(compute_lt(0, 1, 16)); + assert(compute_lt(0, 0x100, 16)); + assert(compute_lt(0x100, TWO_POW_128 - 1, 16)); + assert(!compute_lt(0, TWO_POW_128, 16)); } #[test] - fn check_lte_unsafe() { - assert(lte_unsafe_internal(0, 1, 16)); - assert(lte_unsafe_internal(0, 0x100, 16)); - assert(lte_unsafe_internal(0x100, TWO_POW_128 - 1, 16)); - assert(!lte_unsafe_internal(0, TWO_POW_128, 16)); - - assert(lte_unsafe_internal(0, 0, 16)); - assert(lte_unsafe_internal(0x100, 0x100, 16)); - assert(lte_unsafe_internal(TWO_POW_128 - 1, TWO_POW_128 - 1, 16)); - assert(lte_unsafe_internal(TWO_POW_128, TWO_POW_128, 16)); + fn check_compute_lte() { + assert(compute_lte(0, 1, 16)); + assert(compute_lte(0, 0x100, 16)); + assert(compute_lte(0x100, TWO_POW_128 - 1, 16)); + assert(!compute_lte(0, TWO_POW_128, 16)); + + assert(compute_lte(0, 0, 16)); + assert(compute_lte(0x100, 0x100, 16)); + assert(compute_lte(TWO_POW_128 - 1, TWO_POW_128 - 1, 16)); + assert(compute_lte(TWO_POW_128, TWO_POW_128, 16)); } #[test] @@ -166,6 +182,15 @@ mod tests { assert_gt(0 - 1, 0); } + #[test] + unconstrained fn check_assert_gt_unconstrained() { + assert_gt(1, 0); + assert_gt(0x100, 0); + assert_gt((0 - 1), (0 - 2)); + assert_gt(TWO_POW_128, 0); + assert_gt(0 - 1, 0); + } + #[test] fn check_gt() { assert(gt(1, 0)); @@ -178,6 +203,18 @@ mod tests { assert(!gt(0 - 2, 0 - 1)); } + #[test] + unconstrained fn check_gt_unconstrained() { + assert(gt(1, 0)); + assert(gt(0x100, 0)); + assert(gt((0 - 1), (0 - 2))); + assert(gt(TWO_POW_128, 0)); + assert(!gt(0, 0)); + assert(!gt(0, 0x100)); + assert(gt(0 - 1, 0 - 2)); + assert(!gt(0 - 2, 0 - 1)); + } + #[test] fn check_plo_phi() { assert_eq(PLO + PHI * TWO_POW_128, 0); diff --git a/noir_stdlib/src/lib.nr b/noir_stdlib/src/lib.nr index f11b21003ad..ad47171fa46 100644 --- a/noir_stdlib/src/lib.nr +++ b/noir_stdlib/src/lib.nr @@ -25,6 +25,7 @@ mod default; mod prelude; mod uint128; mod bigint; +mod runtime; // Oracle calls are required to be wrapped in an unconstrained function // Thus, the only argument to the `println` oracle is expected to always be an ident @@ -70,3 +71,4 @@ pub fn wrapping_mul(x: T, y: T) -> T { #[builtin(as_witness)] pub fn as_witness(x: Field) {} + diff --git a/noir_stdlib/src/runtime.nr b/noir_stdlib/src/runtime.nr new file mode 100644 index 00000000000..c075107cd52 --- /dev/null +++ b/noir_stdlib/src/runtime.nr @@ -0,0 +1,2 @@ +#[builtin(is_unconstrained)] +pub fn is_unconstrained() -> bool {} diff --git a/test_programs/execution_success/brillig_slice_input/Nargo.toml b/test_programs/compile_success_empty/brillig_slice_input/Nargo.toml similarity index 100% rename from test_programs/execution_success/brillig_slice_input/Nargo.toml rename to test_programs/compile_success_empty/brillig_slice_input/Nargo.toml diff --git a/test_programs/execution_success/brillig_slice_input/src/main.nr b/test_programs/compile_success_empty/brillig_slice_input/src/main.nr similarity index 100% rename from test_programs/execution_success/brillig_slice_input/src/main.nr rename to test_programs/compile_success_empty/brillig_slice_input/src/main.nr diff --git a/test_programs/execution_success/regression_4383/Nargo.toml b/test_programs/compile_success_empty/regression_4383/Nargo.toml similarity index 100% rename from test_programs/execution_success/regression_4383/Nargo.toml rename to test_programs/compile_success_empty/regression_4383/Nargo.toml diff --git a/test_programs/execution_success/regression_4383/src/main.nr b/test_programs/compile_success_empty/regression_4383/src/main.nr similarity index 100% rename from test_programs/execution_success/regression_4383/src/main.nr rename to test_programs/compile_success_empty/regression_4383/src/main.nr diff --git a/test_programs/execution_success/regression_4436/Nargo.toml b/test_programs/compile_success_empty/regression_4436/Nargo.toml similarity index 100% rename from test_programs/execution_success/regression_4436/Nargo.toml rename to test_programs/compile_success_empty/regression_4436/Nargo.toml diff --git a/test_programs/execution_success/regression_4436/src/main.nr b/test_programs/compile_success_empty/regression_4436/src/main.nr similarity index 100% rename from test_programs/execution_success/regression_4436/src/main.nr rename to test_programs/compile_success_empty/regression_4436/src/main.nr diff --git a/test_programs/execution_success/slice_init_with_complex_type/Nargo.toml b/test_programs/compile_success_empty/slice_init_with_complex_type/Nargo.toml similarity index 100% rename from test_programs/execution_success/slice_init_with_complex_type/Nargo.toml rename to test_programs/compile_success_empty/slice_init_with_complex_type/Nargo.toml diff --git a/test_programs/execution_success/slice_init_with_complex_type/Prover.toml b/test_programs/compile_success_empty/slice_init_with_complex_type/Prover.toml similarity index 100% rename from test_programs/execution_success/slice_init_with_complex_type/Prover.toml rename to test_programs/compile_success_empty/slice_init_with_complex_type/Prover.toml diff --git a/test_programs/execution_success/slice_init_with_complex_type/src/main.nr b/test_programs/compile_success_empty/slice_init_with_complex_type/src/main.nr similarity index 100% rename from test_programs/execution_success/slice_init_with_complex_type/src/main.nr rename to test_programs/compile_success_empty/slice_init_with_complex_type/src/main.nr diff --git a/test_programs/execution_success/is_unconstrained/Nargo.toml b/test_programs/execution_success/is_unconstrained/Nargo.toml new file mode 100644 index 00000000000..deef68c7f72 --- /dev/null +++ b/test_programs/execution_success/is_unconstrained/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "is_unconstrained" +type = "bin" +authors = [""] + +[dependencies] diff --git a/test_programs/execution_success/is_unconstrained/Prover.toml b/test_programs/execution_success/is_unconstrained/Prover.toml new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/test_programs/execution_success/is_unconstrained/Prover.toml @@ -0,0 +1 @@ + diff --git a/test_programs/execution_success/is_unconstrained/src/main.nr b/test_programs/execution_success/is_unconstrained/src/main.nr new file mode 100644 index 00000000000..af40af1029f --- /dev/null +++ b/test_programs/execution_success/is_unconstrained/src/main.nr @@ -0,0 +1,14 @@ +use dep::std::runtime::is_unconstrained; + +fn check(should_be_unconstrained: bool) { + assert_eq(should_be_unconstrained, is_unconstrained()); +} + +unconstrained fn unconstrained_intermediate() { + check(true); +} + +fn main() { + unconstrained_intermediate(); + check(false); +} diff --git a/test_programs/execution_success/turbofish_call_func_diff_types/src/main.nr b/test_programs/execution_success/turbofish_call_func_diff_types/src/main.nr index 709a694e77b..b880d3ae047 100644 --- a/test_programs/execution_success/turbofish_call_func_diff_types/src/main.nr +++ b/test_programs/execution_success/turbofish_call_func_diff_types/src/main.nr @@ -23,9 +23,7 @@ fn main(x: Field, y: pub Field) { fn hash_simple_array(input: [Field; 2]) -> Field where H: Hasher + Default { // Check that we can call a trait method instead of a trait implementation - // TODO(https://github.com/noir-lang/noir/issues/5063): Need to remove the need for this type annotation - // Curently, without the annotation we will get `Expression type is ambiguous` when trying to use the `hasher` - let mut hasher: H = H::default(); + let mut hasher = H::default(); // Regression that the object is converted to a mutable reference type `&mut _`. // Otherwise will see `Expected type &mut _, found type H`. // Then we need to make sure to also auto dereference later in the type checking process diff --git a/test_programs/gates_report.sh b/test_programs/gates_report.sh index 3b0b4d9e148..b33e81b037c 100755 --- a/test_programs/gates_report.sh +++ b/test_programs/gates_report.sh @@ -1,36 +1,37 @@ #!/usr/bin/env bash set -e +BACKEND=${BACKEND:-bb} + # These tests are incompatible with gas reporting excluded_dirs=("workspace" "workspace_default_member" "double_verify_nested_proof") -# These tests cause failures in CI with a stack overflow for some reason. -ci_excluded_dirs=("eddsa") - current_dir=$(pwd) -base_path="$current_dir/execution_success" -test_dirs=$(ls $base_path) +artifacts_path="$current_dir/acir_artifacts" +test_dirs=$(ls $artifacts_path) -# We generate a Noir workspace which contains all of the test cases -# This allows us to generate a gates report using `nargo info` for all of them at once. +echo "{\"programs\": [" > gates_report.json -echo "[workspace]" > Nargo.toml -echo "members = [" >> Nargo.toml +# Bound for checking where to place last parentheses +NUM_ARTIFACTS=$(ls -1q "$artifacts_path" | wc -l) -for dir in $test_dirs; do - if [[ " ${excluded_dirs[@]} " =~ " ${dir} " ]]; then - continue - fi +ITER="1" +for pathname in $test_dirs; do + ARTIFACT_NAME=$(basename "$pathname") + + GATES_INFO=$($BACKEND gates -b "$artifacts_path/$ARTIFACT_NAME/target/program.json") + MAIN_FUNCTION_INFO=$(echo $GATES_INFO | jq -r '.functions[0] | .name = "main"') + echo "{\"package_name\": \"$ARTIFACT_NAME\", \"functions\": [$MAIN_FUNCTION_INFO]" >> gates_report.json - if [[ ${CI-false} = "true" ]] && [[ " ${ci_excluded_dirs[@]} " =~ " ${dir} " ]]; then - continue + if (($ITER == $NUM_ARTIFACTS)); then + echo "}" >> gates_report.json + else + echo "}, " >> gates_report.json fi - echo " \"execution_success/$dir\"," >> Nargo.toml + ITER=$(( $ITER + 1 )) done -echo "]" >> Nargo.toml +echo "]}" >> gates_report.json -nargo info --json > gates_report.json -rm Nargo.toml diff --git a/test_programs/rebuild.sh b/test_programs/rebuild.sh index 4733bad10c3..cfc6479febf 100755 --- a/test_programs/rebuild.sh +++ b/test_programs/rebuild.sh @@ -8,6 +8,14 @@ process_dir() { local current_dir=$2 local dir_name=$(basename "$dir") + if [[ ! -f "$dir/Nargo.toml" ]]; then + # This directory isn't a proper test but just hold some stale build artifacts + # We then delete it and carry on. + rm -rf $dir + return 0 + fi + + if [[ ! -d "$current_dir/acir_artifacts/$dir_name" ]]; then mkdir -p $current_dir/acir_artifacts/$dir_name fi diff --git a/tooling/debugger/ignored-tests.txt b/tooling/debugger/ignored-tests.txt index cda26169421..a6d3c9a3a94 100644 --- a/tooling/debugger/ignored-tests.txt +++ b/tooling/debugger/ignored-tests.txt @@ -25,3 +25,4 @@ fold_fibonacci fold_complex_outputs slice_init_with_complex_type hashmap +is_unconstrained \ No newline at end of file