diff --git a/compiler/noirc_frontend/src/elaborator/comptime.rs b/compiler/noirc_frontend/src/elaborator/comptime.rs index 69705b7cfb8..20d75a704d3 100644 --- a/compiler/noirc_frontend/src/elaborator/comptime.rs +++ b/compiler/noirc_frontend/src/elaborator/comptime.rs @@ -59,13 +59,11 @@ impl<'context> Elaborator<'context> { elaborator.introduce_generics_into_scope(meta.all_generics.clone()); } - elaborator.comptime_scopes = std::mem::take(&mut self.comptime_scopes); elaborator.populate_scope_from_comptime_scopes(); let result = f(&mut elaborator); elaborator.check_and_pop_function_context(); - self.comptime_scopes = elaborator.comptime_scopes; self.errors.append(&mut elaborator.errors); result } @@ -74,7 +72,7 @@ impl<'context> Elaborator<'context> { // Take the comptime scope to be our runtime scope. // Iterate from global scope to the most local scope so that the // later definitions will naturally shadow the former. - for scope in &self.comptime_scopes { + for scope in &self.interner.comptime_scopes { for definition_id in scope.keys() { let definition = self.interner.definition(*definition_id); let name = definition.name.clone(); @@ -156,7 +154,7 @@ impl<'context> Elaborator<'context> { if value != Value::Unit { let items = value - .into_top_level_items(location) + .into_top_level_items(location, self.interner) .map_err(|error| error.into_compilation_error_pair())?; self.add_items(items, generated_items, location); diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index eadc4169882..0d7eeac9790 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -6,7 +6,6 @@ use std::{ use crate::{ ast::{FunctionKind, UnresolvedTraitConstraint}, hir::{ - comptime::Value, def_collector::dc_crate::{ filter_literal_globals, CompilationError, ImplMap, UnresolvedGlobal, UnresolvedStruct, UnresolvedTypeAlias, @@ -27,8 +26,7 @@ use crate::{ SecondaryAttribute, StructId, }, node_interner::{ - DefinitionId, DefinitionKind, DependencyId, ExprId, FuncId, GlobalId, ReferenceId, TraitId, - TypeAliasId, + DefinitionKind, DependencyId, ExprId, FuncId, GlobalId, ReferenceId, TraitId, TypeAliasId, }, Shared, Type, TypeVariable, }; @@ -68,7 +66,6 @@ mod unquote; use fm::FileId; use iter_extended::vecmap; use noirc_errors::{Location, Span}; -use rustc_hash::FxHashMap as HashMap; use self::traits::check_trait_impl_method_matches_declaration; @@ -157,11 +154,6 @@ pub struct Elaborator<'context> { crate_id: CrateId, - /// Each value currently in scope in the comptime interpreter. - /// Each element of the Vec represents a scope with every scope together making - /// up all currently visible definitions. The first scope is always the global scope. - pub(crate) comptime_scopes: Vec>, - /// The scope of --debug-comptime, or None if unset debug_comptime_in_file: Option, @@ -209,7 +201,6 @@ impl<'context> Elaborator<'context> { trait_bounds: Vec::new(), function_context: vec![FunctionContext::default()], current_trait_impl: None, - comptime_scopes: vec![HashMap::default()], debug_comptime_in_file, unresolved_globals: BTreeMap::new(), } diff --git a/compiler/noirc_frontend/src/elaborator/scope.rs b/compiler/noirc_frontend/src/elaborator/scope.rs index 73aed9bf06c..e07a844405d 100644 --- a/compiler/noirc_frontend/src/elaborator/scope.rs +++ b/compiler/noirc_frontend/src/elaborator/scope.rs @@ -144,12 +144,12 @@ impl<'context> Elaborator<'context> { pub fn push_scope(&mut self) { self.scopes.start_scope(); - self.comptime_scopes.push(Default::default()); + self.interner.comptime_scopes.push(Default::default()); } pub fn pop_scope(&mut self) { let scope = self.scopes.end_scope(); - self.comptime_scopes.pop(); + self.interner.comptime_scopes.pop(); self.check_for_unused_variables_in_scope_tree(scope.into()); } diff --git a/compiler/noirc_frontend/src/hir/comptime/errors.rs b/compiler/noirc_frontend/src/hir/comptime/errors.rs index cd6370e4742..7898f13945f 100644 --- a/compiler/noirc_frontend/src/hir/comptime/errors.rs +++ b/compiler/noirc_frontend/src/hir/comptime/errors.rs @@ -5,7 +5,7 @@ use crate::{ ast::TraitBound, hir::{def_collector::dc_crate::CompilationError, type_check::NoMatchingImplFoundError}, parser::ParserError, - token::Tokens, + token::Token, Type, }; use acvm::{acir::AcirField, BlackBoxResolutionError, FieldElement}; @@ -13,55 +13,183 @@ use fm::FileId; use iter_extended::vecmap; use noirc_errors::{CustomDiagnostic, Location}; -use super::value::Value; - /// The possible errors that can halt the interpreter. #[derive(Debug, Clone, PartialEq, Eq)] pub enum InterpreterError { - ArgumentCountMismatch { expected: usize, actual: usize, location: Location }, - TypeMismatch { expected: Type, value: Value, location: Location }, - NonComptimeVarReferenced { name: String, location: Location }, - VariableNotInScope { location: Location }, - IntegerOutOfRangeForType { value: FieldElement, typ: Type, location: Location }, - ErrorNodeEncountered { location: Location }, - NonFunctionCalled { value: Value, location: Location }, - NonBoolUsedInIf { value: Value, location: Location }, - NonBoolUsedInConstrain { value: Value, location: Location }, - FailingConstraint { message: Option, location: Location }, - NoMethodFound { name: String, typ: Type, location: Location }, - NonIntegerUsedInLoop { value: Value, location: Location }, - NonPointerDereferenced { value: Value, location: Location }, - NonTupleOrStructInMemberAccess { value: Value, location: Location }, - NonArrayIndexed { value: Value, location: Location }, - NonIntegerUsedAsIndex { value: Value, location: Location }, - NonIntegerIntegerLiteral { typ: Type, location: Location }, - NonIntegerArrayLength { typ: Type, location: Location }, - NonNumericCasted { value: Value, location: Location }, - IndexOutOfBounds { index: usize, length: usize, location: Location }, - ExpectedStructToHaveField { value: Value, field_name: String, location: Location }, - TypeUnsupported { typ: Type, location: Location }, - InvalidValueForUnary { value: Value, operator: &'static str, location: Location }, - InvalidValuesForBinary { lhs: Value, rhs: Value, operator: &'static str, location: Location }, - CastToNonNumericType { typ: Type, location: Location }, - QuoteInRuntimeCode { location: Location }, - NonStructInConstructor { typ: Type, location: Location }, - CannotInlineMacro { value: Value, location: Location }, - UnquoteFoundDuringEvaluation { location: Location }, - DebugEvaluateComptime { diagnostic: CustomDiagnostic, location: Location }, - FailedToParseMacro { error: ParserError, tokens: Rc, rule: &'static str, file: FileId }, - UnsupportedTopLevelItemUnquote { item: String, location: Location }, - ComptimeDependencyCycle { function: String, location: Location }, - NoImpl { location: Location }, - NoMatchingImplFound { error: NoMatchingImplFoundError, file: FileId }, - ImplMethodTypeMismatch { expected: Type, actual: Type, location: Location }, - BreakNotInLoop { location: Location }, - ContinueNotInLoop { location: Location }, + ArgumentCountMismatch { + expected: usize, + actual: usize, + location: Location, + }, + TypeMismatch { + expected: Type, + actual: Type, + location: Location, + }, + NonComptimeVarReferenced { + name: String, + location: Location, + }, + VariableNotInScope { + location: Location, + }, + IntegerOutOfRangeForType { + value: FieldElement, + typ: Type, + location: Location, + }, + ErrorNodeEncountered { + location: Location, + }, + NonFunctionCalled { + typ: Type, + location: Location, + }, + NonBoolUsedInIf { + typ: Type, + location: Location, + }, + NonBoolUsedInConstrain { + typ: Type, + location: Location, + }, + FailingConstraint { + message: Option, + location: Location, + }, + NoMethodFound { + name: String, + typ: Type, + location: Location, + }, + NonIntegerUsedInLoop { + typ: Type, + location: Location, + }, + NonPointerDereferenced { + typ: Type, + location: Location, + }, + NonTupleOrStructInMemberAccess { + typ: Type, + location: Location, + }, + NonArrayIndexed { + typ: Type, + location: Location, + }, + NonIntegerUsedAsIndex { + typ: Type, + location: Location, + }, + NonIntegerIntegerLiteral { + typ: Type, + location: Location, + }, + NonIntegerArrayLength { + typ: Type, + location: Location, + }, + NonNumericCasted { + typ: Type, + location: Location, + }, + IndexOutOfBounds { + index: usize, + length: usize, + location: Location, + }, + ExpectedStructToHaveField { + typ: Type, + field_name: String, + location: Location, + }, + TypeUnsupported { + typ: Type, + location: Location, + }, + InvalidValueForUnary { + typ: Type, + operator: &'static str, + location: Location, + }, + InvalidValuesForBinary { + lhs: Type, + rhs: Type, + operator: &'static str, + location: Location, + }, + CastToNonNumericType { + typ: Type, + location: Location, + }, + QuoteInRuntimeCode { + location: Location, + }, + NonStructInConstructor { + typ: Type, + location: Location, + }, + CannotInlineMacro { + value: String, + typ: Type, + location: Location, + }, + UnquoteFoundDuringEvaluation { + location: Location, + }, + DebugEvaluateComptime { + diagnostic: CustomDiagnostic, + location: Location, + }, + FailedToParseMacro { + error: ParserError, + tokens: Rc>, + rule: &'static str, + file: FileId, + }, + UnsupportedTopLevelItemUnquote { + item: String, + location: Location, + }, + ComptimeDependencyCycle { + function: String, + location: Location, + }, + NoImpl { + location: Location, + }, + NoMatchingImplFound { + error: NoMatchingImplFoundError, + file: FileId, + }, + ImplMethodTypeMismatch { + expected: Type, + actual: Type, + location: Location, + }, + BreakNotInLoop { + location: Location, + }, + ContinueNotInLoop { + location: Location, + }, BlackBoxError(BlackBoxResolutionError, Location), - FailedToResolveTraitBound { trait_bound: TraitBound, location: Location }, - TraitDefinitionMustBeAPath { location: Location }, - FailedToResolveTraitDefinition { location: Location }, + FailedToResolveTraitBound { + trait_bound: TraitBound, + location: Location, + }, + TraitDefinitionMustBeAPath { + location: Location, + }, + FailedToResolveTraitDefinition { + location: Location, + }, - Unimplemented { item: String, location: Location }, + Unimplemented { + item: String, + location: Location, + }, // These cases are not errors, they are just used to prevent us from running more code // until the loop can be resumed properly. These cases will never be displayed to users. @@ -170,9 +298,8 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { let secondary = format!("Too {few_many} arguments"); CustomDiagnostic::simple_error(msg, secondary, location.span) } - InterpreterError::TypeMismatch { expected, value, location } => { - let typ = value.get_type(); - let msg = format!("Expected `{expected}` but a value of type `{typ}` was given"); + InterpreterError::TypeMismatch { expected, actual, location } => { + let msg = format!("Expected `{expected}` but a value of type `{actual}` was given"); CustomDiagnostic::simple_error(msg, String::new(), location.span) } InterpreterError::NonComptimeVarReferenced { name, location } => { @@ -198,23 +325,23 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { let secondary = "This is a bug, please report this if found!".to_string(); CustomDiagnostic::simple_error(msg, secondary, location.span) } - InterpreterError::NonFunctionCalled { value, location } => { + InterpreterError::NonFunctionCalled { typ, location } => { let msg = "Only functions may be called".to_string(); - let secondary = format!("Expression has type {}", value.get_type()); + let secondary = format!("Expression has type {typ}"); CustomDiagnostic::simple_error(msg, secondary, location.span) } - InterpreterError::NonBoolUsedInIf { value, location } => { - let msg = format!("Expected a `bool` but found `{}`", value.get_type()); + InterpreterError::NonBoolUsedInIf { typ, location } => { + let msg = format!("Expected a `bool` but found `{typ}`"); let secondary = "If conditions must be a boolean value".to_string(); CustomDiagnostic::simple_error(msg, secondary, location.span) } - InterpreterError::NonBoolUsedInConstrain { value, location } => { - let msg = format!("Expected a `bool` but found `{}`", value.get_type()); + InterpreterError::NonBoolUsedInConstrain { typ, location } => { + let msg = format!("Expected a `bool` but found `{typ}`"); CustomDiagnostic::simple_error(msg, String::new(), location.span) } InterpreterError::FailingConstraint { message, location } => { let (primary, secondary) = match message { - Some(msg) => (format!("{msg}"), "Assertion failed".into()), + Some(msg) => (msg.clone(), "Assertion failed".into()), None => ("Assertion failed".into(), String::new()), }; CustomDiagnostic::simple_error(primary, secondary, location.span) @@ -223,32 +350,30 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { let msg = format!("No method named `{name}` found for type `{typ}`"); CustomDiagnostic::simple_error(msg, String::new(), location.span) } - InterpreterError::NonIntegerUsedInLoop { value, location } => { - let typ = value.get_type(); + InterpreterError::NonIntegerUsedInLoop { typ, location } => { let msg = format!("Non-integer type `{typ}` used in for loop"); - let secondary = if matches!(typ.as_ref(), &Type::FieldElement) { + let secondary = if matches!(typ, Type::FieldElement) { "`field` is not an integer type, try `u32` instead".to_string() } else { String::new() }; CustomDiagnostic::simple_error(msg, secondary, location.span) } - InterpreterError::NonPointerDereferenced { value, location } => { - let typ = value.get_type(); + InterpreterError::NonPointerDereferenced { typ, location } => { let msg = format!("Only references may be dereferenced, but found `{typ}`"); CustomDiagnostic::simple_error(msg, String::new(), location.span) } - InterpreterError::NonTupleOrStructInMemberAccess { value, location } => { - let msg = format!("The type `{}` has no fields to access", value.get_type()); + InterpreterError::NonTupleOrStructInMemberAccess { typ, location } => { + let msg = format!("The type `{typ}` has no fields to access"); CustomDiagnostic::simple_error(msg, String::new(), location.span) } - InterpreterError::NonArrayIndexed { value, location } => { - let msg = format!("Expected an array or slice but found a(n) {}", value.get_type()); + InterpreterError::NonArrayIndexed { typ, location } => { + let msg = format!("Expected an array or slice but found a(n) {typ}"); let secondary = "Only arrays or slices may be indexed".into(); CustomDiagnostic::simple_error(msg, secondary, location.span) } - InterpreterError::NonIntegerUsedAsIndex { value, location } => { - let msg = format!("Expected an integer but found a(n) {}", value.get_type()); + InterpreterError::NonIntegerUsedAsIndex { typ, location } => { + let msg = format!("Expected an integer but found a(n) {typ}"); let secondary = "Only integers may be indexed. Note that this excludes `field`s".into(); CustomDiagnostic::simple_error(msg, secondary, location.span) @@ -263,17 +388,16 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { let secondary = "Array lengths must be integers".into(); CustomDiagnostic::simple_error(msg, secondary, location.span) } - InterpreterError::NonNumericCasted { value, location } => { + InterpreterError::NonNumericCasted { typ, location } => { let msg = "Only numeric types may be casted".into(); - let secondary = format!("`{}` is non-numeric", value.get_type()); + let secondary = format!("`{typ}` is non-numeric"); CustomDiagnostic::simple_error(msg, secondary, location.span) } InterpreterError::IndexOutOfBounds { index, length, location } => { let msg = format!("{index} is out of bounds for the array of length {length}"); CustomDiagnostic::simple_error(msg, String::new(), location.span) } - InterpreterError::ExpectedStructToHaveField { value, field_name, location } => { - let typ = value.get_type(); + InterpreterError::ExpectedStructToHaveField { typ, field_name, location } => { let msg = format!("The type `{typ}` has no field named `{field_name}`"); CustomDiagnostic::simple_error(msg, String::new(), location.span) } @@ -282,13 +406,11 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { format!("The type `{typ}` is currently unsupported in comptime expressions"); CustomDiagnostic::simple_error(msg, String::new(), location.span) } - InterpreterError::InvalidValueForUnary { value, operator, location } => { - let msg = format!("`{}` cannot be used with unary {operator}", value.get_type()); + InterpreterError::InvalidValueForUnary { typ, operator, location } => { + let msg = format!("`{typ}` cannot be used with unary {operator}"); CustomDiagnostic::simple_error(msg, String::new(), location.span) } InterpreterError::InvalidValuesForBinary { lhs, rhs, operator, location } => { - let lhs = lhs.get_type(); - let rhs = rhs.get_type(); let msg = format!("No implementation for `{lhs}` {operator} `{rhs}`",); CustomDiagnostic::simple_error(msg, String::new(), location.span) } @@ -304,10 +426,9 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { let msg = format!("`{typ}` is not a struct type"); CustomDiagnostic::simple_error(msg, String::new(), location.span) } - InterpreterError::CannotInlineMacro { value, location } => { - let typ = value.get_type(); + InterpreterError::CannotInlineMacro { value, typ, location } => { let msg = format!("Cannot inline values of type `{typ}` into this position"); - let secondary = format!("Cannot inline value {value:?}"); + let secondary = format!("Cannot inline value `{value}`"); CustomDiagnostic::simple_error(msg, secondary, location.span) } InterpreterError::UnquoteFoundDuringEvaluation { location } => { @@ -318,7 +439,7 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { InterpreterError::DebugEvaluateComptime { diagnostic, .. } => diagnostic.clone(), InterpreterError::FailedToParseMacro { error, tokens, rule, file: _ } => { let message = format!("Failed to parse macro's token stream into {rule}"); - let tokens = vecmap(&tokens.0, ToString::to_string).join(" "); + let tokens = vecmap(tokens.iter(), ToString::to_string).join(" "); // 10 is an aribtrary number of tokens here chosen to fit roughly onto one line let token_stream = if tokens.len() > 10 { diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs index 8f3f1295cac..888fa17d0af 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs @@ -258,8 +258,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { pub(super) fn enter_function(&mut self) -> (bool, Vec>) { // Drain every scope except the global scope let mut scope = Vec::new(); - if self.elaborator.comptime_scopes.len() > 1 { - scope = self.elaborator.comptime_scopes.drain(1..).collect(); + if self.elaborator.interner.comptime_scopes.len() > 1 { + scope = self.elaborator.interner.comptime_scopes.drain(1..).collect(); } self.push_scope(); (std::mem::take(&mut self.in_loop), scope) @@ -269,21 +269,21 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { self.in_loop = state.0; // Keep only the global scope - self.elaborator.comptime_scopes.truncate(1); - self.elaborator.comptime_scopes.append(&mut state.1); + self.elaborator.interner.comptime_scopes.truncate(1); + self.elaborator.interner.comptime_scopes.append(&mut state.1); } pub(super) fn push_scope(&mut self) { - self.elaborator.comptime_scopes.push(HashMap::default()); + self.elaborator.interner.comptime_scopes.push(HashMap::default()); } pub(super) fn pop_scope(&mut self) { - self.elaborator.comptime_scopes.pop(); + self.elaborator.interner.comptime_scopes.pop(); } fn current_scope_mut(&mut self) -> &mut HashMap { // the global scope is always at index zero, so this is always Some - self.elaborator.comptime_scopes.last_mut().unwrap() + self.elaborator.interner.comptime_scopes.last_mut().unwrap() } fn unbind_generics_from_previous_function(&mut self) { @@ -351,7 +351,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Ok(()) } (value, _) => { - Err(InterpreterError::TypeMismatch { expected: typ.clone(), value, location }) + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { expected: typ.clone(), actual, location }) } }, HirPattern::Struct(struct_type, pattern_fields, _) => { @@ -362,7 +363,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { for (field_name, field_pattern) in pattern_fields { let field = fields.get(&field_name.0.contents).ok_or_else(|| { InterpreterError::ExpectedStructToHaveField { - value: Value::Struct(fields.clone(), struct_type.clone()), + typ: struct_type.clone(), field_name: field_name.0.contents.clone(), location, } @@ -380,7 +381,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { } value => Err(InterpreterError::TypeMismatch { expected: typ.clone(), - value, + actual: value.get_type().into_owned(), location, }), }; @@ -402,7 +403,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { return Ok(()); } - for scope in self.elaborator.comptime_scopes.iter_mut().rev() { + for scope in self.elaborator.interner.comptime_scopes.iter_mut().rev() { if let Entry::Occupied(mut entry) = scope.entry(id) { entry.insert(argument); return Ok(()); @@ -416,7 +417,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { } pub fn lookup_id(&self, id: DefinitionId, location: Location) -> IResult { - for scope in self.elaborator.comptime_scopes.iter().rev() { + for scope in self.elaborator.interner.comptime_scopes.iter().rev() { if let Some(value) = scope.get(&id) { return Ok(value.clone()); } @@ -569,7 +570,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { consuming = false; if let Some(value) = values.pop_front() { - result.push_str(&value.to_string()); + result.push_str(&value.display(self.elaborator.interner).to_string()); } } other if !consuming => { @@ -769,7 +770,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { value => { let location = self.elaborator.interner.expr_location(&id); let operator = "minus"; - Err(InterpreterError::InvalidValueForUnary { value, location, operator }) + let typ = value.get_type().into_owned(); + Err(InterpreterError::InvalidValueForUnary { typ, location, operator }) } }, UnaryOp::Not => match rhs { @@ -784,7 +786,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Value::U64(value) => Ok(Value::U64(!value)), value => { let location = self.elaborator.interner.expr_location(&id); - Err(InterpreterError::InvalidValueForUnary { value, location, operator: "not" }) + let typ = value.get_type().into_owned(); + Err(InterpreterError::InvalidValueForUnary { typ, location, operator: "not" }) } }, UnaryOp::MutableReference => { @@ -800,7 +803,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Value::Pointer(element, _) => Ok(element.borrow().clone()), value => { let location = self.elaborator.interner.expr_location(&id); - Err(InterpreterError::NonPointerDereferenced { value, location }) + let typ = value.get_type().into_owned(); + Err(InterpreterError::NonPointerDereferenced { typ, location }) } }, } @@ -814,6 +818,13 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { return self.evaluate_overloaded_infix(infix, lhs, rhs, id); } + let make_error = |this: &mut Self, lhs: Value, rhs: Value, operator| { + let location = this.elaborator.interner.expr_location(&id); + let lhs = lhs.get_type().into_owned(); + let rhs = rhs.get_type().into_owned(); + Err(InvalidValuesForBinary { lhs, rhs, location, operator }) + }; + use InterpreterError::InvalidValuesForBinary; match infix.operator.kind { BinaryOpKind::Add => match (lhs, rhs) { @@ -826,10 +837,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs + rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs + rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs + rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: "+" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, "+"), }, BinaryOpKind::Subtract => match (lhs, rhs) { (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Field(lhs - rhs)), @@ -841,10 +849,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs - rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs - rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs - rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: "-" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, "-"), }, BinaryOpKind::Multiply => match (lhs, rhs) { (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Field(lhs * rhs)), @@ -856,10 +861,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs * rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs * rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs * rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: "*" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, "*"), }, BinaryOpKind::Divide => match (lhs, rhs) { (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Field(lhs / rhs)), @@ -871,10 +873,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs / rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs / rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs / rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: "/" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, "/"), }, BinaryOpKind::Equal => match (lhs, rhs) { (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Bool(lhs == rhs)), @@ -886,10 +885,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::Bool(lhs == rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::Bool(lhs == rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::Bool(lhs == rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: "==" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, "=="), }, BinaryOpKind::NotEqual => match (lhs, rhs) { (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Bool(lhs != rhs)), @@ -901,10 +897,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::Bool(lhs != rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::Bool(lhs != rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::Bool(lhs != rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: "!=" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, "!="), }, BinaryOpKind::Less => match (lhs, rhs) { (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Bool(lhs < rhs)), @@ -916,10 +909,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::Bool(lhs < rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::Bool(lhs < rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::Bool(lhs < rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: "<" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, "<"), }, BinaryOpKind::LessEqual => match (lhs, rhs) { (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Bool(lhs <= rhs)), @@ -931,10 +921,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::Bool(lhs <= rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::Bool(lhs <= rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::Bool(lhs <= rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: "<=" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, "<="), }, BinaryOpKind::Greater => match (lhs, rhs) { (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Bool(lhs > rhs)), @@ -946,10 +933,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::Bool(lhs > rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::Bool(lhs > rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::Bool(lhs > rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: ">" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, ">"), }, BinaryOpKind::GreaterEqual => match (lhs, rhs) { (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Bool(lhs >= rhs)), @@ -961,10 +945,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::Bool(lhs >= rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::Bool(lhs >= rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::Bool(lhs >= rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: ">=" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, ">="), }, BinaryOpKind::And => match (lhs, rhs) { (Value::Bool(lhs), Value::Bool(rhs)) => Ok(Value::Bool(lhs & rhs)), @@ -976,10 +957,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs & rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs & rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs & rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: "&" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, "&"), }, BinaryOpKind::Or => match (lhs, rhs) { (Value::Bool(lhs), Value::Bool(rhs)) => Ok(Value::Bool(lhs | rhs)), @@ -991,10 +969,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs | rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs | rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs | rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: "|" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, "|"), }, BinaryOpKind::Xor => match (lhs, rhs) { (Value::Bool(lhs), Value::Bool(rhs)) => Ok(Value::Bool(lhs ^ rhs)), @@ -1006,10 +981,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs ^ rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs ^ rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs ^ rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: "^" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, "^"), }, BinaryOpKind::ShiftRight => match (lhs, rhs) { (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::I8(lhs >> rhs)), @@ -1020,10 +992,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs >> rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs >> rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs >> rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: ">>" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, ">>"), }, BinaryOpKind::ShiftLeft => match (lhs, rhs) { (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::I8(lhs << rhs)), @@ -1034,10 +1003,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs << rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs << rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs << rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: "<<" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, "<<"), }, BinaryOpKind::Modulo => match (lhs, rhs) { (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::I8(lhs % rhs)), @@ -1048,10 +1014,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (Value::U16(lhs), Value::U16(rhs)) => Ok(Value::U16(lhs % rhs)), (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs % rhs)), (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs % rhs)), - (lhs, rhs) => { - let location = self.elaborator.interner.expr_location(&id); - Err(InvalidValuesForBinary { lhs, rhs, location, operator: "%" }) - } + (lhs, rhs) => make_error(self, lhs, rhs, "%"), }, } } @@ -1155,7 +1118,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Value::Array(array, _) => array, Value::Slice(array, _) => array, value => { - return Err(InterpreterError::NonArrayIndexed { value, location }); + let typ = value.get_type().into_owned(); + return Err(InterpreterError::NonArrayIndexed { typ, location }); } }; @@ -1175,7 +1139,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Value::U32(value) => value as usize, Value::U64(value) => value as usize, value => { - return Err(InterpreterError::NonIntegerUsedAsIndex { value, location }); + let typ = value.get_type().into_owned(); + return Err(InterpreterError::NonIntegerUsedAsIndex { typ, location }); } }; @@ -1222,7 +1187,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { } value => { let location = self.elaborator.interner.expr_location(&id); - return Err(InterpreterError::NonTupleOrStructInMemberAccess { value, location }); + let typ = value.get_type().into_owned(); + return Err(InterpreterError::NonTupleOrStructInMemberAccess { typ, location }); } }; @@ -1230,7 +1196,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { let location = self.elaborator.interner.expr_location(&id); let value = Value::Struct(fields, struct_type); let field_name = access.rhs.0.contents; - InterpreterError::ExpectedStructToHaveField { value, field_name, location } + let typ = value.get_type().into_owned(); + InterpreterError::ExpectedStructToHaveField { typ, field_name, location } }) } @@ -1255,7 +1222,10 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Ok(result) } Value::Closure(closure, env, _) => self.call_closure(closure, env, arguments, location), - value => Err(InterpreterError::NonFunctionCalled { value, location }), + value => { + let typ = value.get_type().into_owned(); + Err(InterpreterError::NonFunctionCalled { typ, location }) + } } } @@ -1331,7 +1301,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { } value => { let location = interner.expr_location(&id); - return Err(InterpreterError::NonNumericCasted { value, location }); + let typ = value.get_type().into_owned(); + return Err(InterpreterError::NonNumericCasted { typ, location }); } }; @@ -1396,7 +1367,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Value::Bool(value) => value, value => { let location = self.elaborator.interner.expr_location(&id); - return Err(InterpreterError::NonBoolUsedInIf { value, location }); + let typ = value.get_type().into_owned(); + return Err(InterpreterError::NonBoolUsedInIf { typ, location }); } }; @@ -1436,8 +1408,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { fn evaluate_quote(&mut self, mut tokens: Tokens, expr_id: ExprId) -> IResult { let location = self.elaborator.interner.expr_location(&expr_id); - tokens = self.substitute_unquoted_values_into_tokens(tokens, location)?; - Ok(Value::Code(Rc::new(tokens))) + let tokens = self.substitute_unquoted_values_into_tokens(tokens, location)?; + Ok(Value::Quoted(Rc::new(tokens))) } pub fn evaluate_statement(&mut self, statement: StmtId) -> IResult { @@ -1474,11 +1446,14 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Value::Bool(false) => { let location = self.elaborator.interner.expr_location(&constrain.0); let message = constrain.2.and_then(|expr| self.evaluate(expr).ok()); + let message = + message.map(|value| value.display(self.elaborator.interner).to_string()); Err(InterpreterError::FailingConstraint { location, message }) } value => { let location = self.elaborator.interner.expr_location(&constrain.0); - Err(InterpreterError::NonBoolUsedInConstrain { value, location }) + let typ = value.get_type().into_owned(); + Err(InterpreterError::NonBoolUsedInConstrain { typ, location }) } } } @@ -1498,7 +1473,10 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { *value.borrow_mut() = rhs; Ok(()) } - value => Err(InterpreterError::NonPointerDereferenced { value, location }), + value => { + let typ = value.get_type().into_owned(); + Err(InterpreterError::NonPointerDereferenced { typ, location }) + } } } HirLValue::MemberAccess { object, field_name, field_index, typ: _, location } => { @@ -1507,7 +1485,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { let index = field_index.ok_or_else(|| { let value = object_value.clone(); let field_name = field_name.to_string(); - InterpreterError::ExpectedStructToHaveField { value, field_name, location } + let typ = value.get_type().into_owned(); + InterpreterError::ExpectedStructToHaveField { typ, field_name, location } })?; match object_value { @@ -1520,7 +1499,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { self.store_lvalue(*object, Value::Struct(fields, typ.follow_bindings())) } value => { - Err(InterpreterError::NonTupleOrStructInMemberAccess { value, location }) + let typ = value.get_type().into_owned(); + Err(InterpreterError::NonTupleOrStructInMemberAccess { typ, location }) } } } @@ -1552,7 +1532,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { match self.evaluate_lvalue(lvalue)? { Value::Pointer(value, _) => Ok(value.borrow().clone()), value => { - Err(InterpreterError::NonPointerDereferenced { value, location: *location }) + let typ = value.get_type().into_owned(); + Err(InterpreterError::NonPointerDereferenced { typ, location: *location }) } } } @@ -1563,14 +1544,15 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { let value = object_value.clone(); let field_name = field_name.to_string(); let location = *location; - InterpreterError::ExpectedStructToHaveField { value, field_name, location } + let typ = value.get_type().into_owned(); + InterpreterError::ExpectedStructToHaveField { typ, field_name, location } })?; match object_value { Value::Tuple(mut values) => Ok(values.swap_remove(index)), Value::Struct(fields, _) => Ok(fields[&field_name.0.contents].clone()), value => Err(InterpreterError::NonTupleOrStructInMemberAccess { - value, + typ: value.get_type().into_owned(), location: *location, }), } @@ -1598,7 +1580,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Value::U64(value) => Ok((value as i128, |i| Value::U64(i as u64))), value => { let location = this.elaborator.interner.expr_location(&expr); - Err(InterpreterError::NonIntegerUsedInLoop { value, location }) + let typ = value.get_type().into_owned(); + Err(InterpreterError::NonIntegerUsedInLoop { typ, location }) } } }; @@ -1652,9 +1635,9 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { let print_newline = arguments[0].0 == Value::Bool(true); if print_newline { - println!("{}", arguments[1].0); + println!("{}", arguments[1].0.display(self.elaborator.interner)); } else { - print!("{}", arguments[1].0); + print!("{}", arguments[1].0.display(self.elaborator.interner)); } Ok(Value::Unit) diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index 2ea093ccb34..62959e4eda9 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -11,11 +11,11 @@ use rustc_hash::FxHashMap as HashMap; use crate::{ ast::IntegerBitSize, - hir::comptime::{errors::IResult, InterpreterError, Value}, + hir::comptime::{errors::IResult, value::add_token_spans, InterpreterError, Value}, macros_api::{NodeInterner, Signedness}, node_interner::TraitId, parser, - token::{SpannedToken, Token, Tokens}, + token::Token, QuotedType, Shared, Type, }; @@ -80,8 +80,7 @@ pub(super) fn check_argument_count( } fn failing_constraint(message: impl Into, location: Location) -> IResult { - let message = Some(Value::String(Rc::new(message.into()))); - Err(InterpreterError::FailingConstraint { message, location }) + Err(InterpreterError::FailingConstraint { message: Some(message.into()), location }) } pub(super) fn get_array( @@ -94,7 +93,8 @@ pub(super) fn get_array( value => { let type_var = Box::new(interner.next_type_variable()); let expected = Type::Array(type_var.clone(), type_var); - Err(InterpreterError::TypeMismatch { expected, value, location }) + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { expected, actual, location }) } } } @@ -109,7 +109,8 @@ fn get_slice( value => { let type_var = Box::new(interner.next_type_variable()); let expected = Type::Slice(type_var); - Err(InterpreterError::TypeMismatch { expected, value, location }) + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { expected, actual, location }) } } } @@ -118,7 +119,8 @@ pub(super) fn get_field(value: Value, location: Location) -> IResult Ok(value), value => { - Err(InterpreterError::TypeMismatch { expected: Type::FieldElement, value, location }) + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { expected: Type::FieldElement, actual, location }) } } } @@ -128,7 +130,8 @@ pub(super) fn get_u32(value: Value, location: Location) -> IResult { Value::U32(value) => Ok(value), value => { let expected = Type::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo); - Err(InterpreterError::TypeMismatch { expected, value, location }) + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { expected, actual, location }) } } } @@ -138,7 +141,8 @@ fn get_trait_constraint(value: Value, location: Location) -> IResult<(TraitId, V Value::TraitConstraint(trait_id, generics) => Ok((trait_id, generics)), value => { let expected = Type::Quoted(QuotedType::TraitConstraint); - Err(InterpreterError::TypeMismatch { expected, value, location }) + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { expected, actual, location }) } } } @@ -148,17 +152,19 @@ fn get_trait_def(value: Value, location: Location) -> IResult { Value::TraitDefinition(id) => Ok(id), value => { let expected = Type::Quoted(QuotedType::TraitDefinition); - Err(InterpreterError::TypeMismatch { expected, value, location }) + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { expected, actual, location }) } } } -fn get_quoted(value: Value, location: Location) -> IResult> { +fn get_quoted(value: Value, location: Location) -> IResult>> { match value { - Value::Code(tokens) => Ok(tokens), + Value::Quoted(tokens) => Ok(tokens), value => { let expected = Type::Quoted(QuotedType::Quoted); - Err(InterpreterError::TypeMismatch { expected, value, location }) + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { expected, actual, location }) } } } @@ -175,7 +181,8 @@ fn array_len( value => { let type_var = Box::new(interner.next_type_variable()); let expected = Type::Array(type_var.clone(), type_var); - Err(InterpreterError::TypeMismatch { expected, value, location }) + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { expected, actual, location }) } } } @@ -193,7 +200,8 @@ fn as_slice( value => { let type_var = Box::new(interner.next_type_variable()); let expected = Type::Array(type_var.clone(), type_var); - Err(InterpreterError::TypeMismatch { expected, value, location }) + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { expected, actual, location }) } } } @@ -223,7 +231,8 @@ fn struct_def_as_type( Value::StructDefinition(id) => id, value => { let expected = Type::Quoted(QuotedType::StructDefinition); - return Err(InterpreterError::TypeMismatch { expected, location, value }); + let actual = value.get_type().into_owned(); + return Err(InterpreterError::TypeMismatch { expected, location, actual }); } }; @@ -246,11 +255,12 @@ fn struct_def_generics( ) -> IResult { check_argument_count(1, &arguments, location)?; - let (struct_def, span) = match arguments.pop().unwrap() { - (Value::StructDefinition(id), location) => (id, location.span), + let struct_def = match arguments.pop().unwrap().0 { + Value::StructDefinition(id) => id, value => { let expected = Type::Quoted(QuotedType::StructDefinition); - return Err(InterpreterError::TypeMismatch { expected, location, value: value.0 }); + let actual = value.get_type().into_owned(); + return Err(InterpreterError::TypeMismatch { expected, location, actual }); } }; @@ -258,8 +268,8 @@ fn struct_def_generics( let struct_def = struct_def.borrow(); let generics = struct_def.generics.iter().map(|generic| { - let name = SpannedToken::new(Token::Ident(generic.type_var.borrow().to_string()), span); - Value::Code(Rc::new(Tokens(vec![name]))) + let name = Token::Ident(generic.type_var.borrow().to_string()); + Value::Quoted(Rc::new(vec![name])) }); let typ = Type::Slice(Box::new(Type::Quoted(QuotedType::Quoted))); @@ -275,24 +285,22 @@ fn struct_def_fields( ) -> IResult { check_argument_count(1, &arguments, location)?; - let (struct_def, span) = match arguments.pop().unwrap() { - (Value::StructDefinition(id), location) => (id, location.span), + let struct_def = match arguments.pop().unwrap().0 { + Value::StructDefinition(id) => id, value => { let expected = Type::Quoted(QuotedType::StructDefinition); - return Err(InterpreterError::TypeMismatch { expected, location, value: value.0 }); + let actual = value.get_type().into_owned(); + return Err(InterpreterError::TypeMismatch { expected, location, actual }); } }; let struct_def = interner.get_struct(struct_def); let struct_def = struct_def.borrow(); - let make_token = |name| SpannedToken::new(Token::Ident(name), span); - let make_quoted = |tokens| Value::Code(Rc::new(Tokens(tokens))); - let mut fields = im::Vector::new(); for (name, typ) in struct_def.get_fields_as_written() { - let name = make_quoted(vec![make_token(name)]); + let name = Value::Quoted(Rc::new(vec![Token::Ident(name)])); let typ = Value::Type(typ); fields.push_back(Value::Tuple(vec![name, typ])); } @@ -394,7 +402,7 @@ fn quoted_as_trait_constraint( check_argument_count(1, &arguments, location)?; let tokens = get_quoted(arguments.pop().unwrap().0, location)?; - let quoted = tokens.as_ref().clone(); + let quoted = add_token_spans(tokens.clone(), location.span); let trait_bound = parser::trait_bound().parse(quoted).map_err(|mut errors| { let error = errors.swap_remove(0); @@ -420,7 +428,7 @@ fn quoted_as_type( check_argument_count(1, &arguments, location)?; let tokens = get_quoted(arguments.pop().unwrap().0, location)?; - let quoted = tokens.as_ref().clone(); + let quoted = add_token_spans(tokens.clone(), location.span); let typ = parser::parse_type().parse(quoted).map_err(|mut errors| { let error = errors.swap_remove(0); diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/unquote.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/unquote.rs index c80d39f8df8..c7b1532c9b7 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/unquote.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/unquote.rs @@ -15,20 +15,20 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { &mut self, tokens: Tokens, location: Location, - ) -> IResult { + ) -> IResult> { let mut new_tokens = Vec::with_capacity(tokens.0.len()); for token in tokens.0 { - match token.token() { + match token.into_token() { Token::UnquoteMarker(id) => { - let value = self.evaluate(*id)?; + let value = self.evaluate(id)?; let tokens = value.into_tokens(self.elaborator.interner, location)?; - new_tokens.extend(tokens.0); + new_tokens.extend(tokens); } - _ => new_tokens.push(token), + token => new_tokens.push(token), } } - Ok(Tokens(new_tokens)) + Ok(new_tokens) } } diff --git a/compiler/noirc_frontend/src/hir/comptime/value.rs b/compiler/noirc_frontend/src/hir/comptime/value.rs index 0959e4c17ac..61475fa60e1 100644 --- a/compiler/noirc_frontend/src/hir/comptime/value.rs +++ b/compiler/noirc_frontend/src/hir/comptime/value.rs @@ -4,7 +4,7 @@ use acvm::{AcirField, FieldElement}; use chumsky::Parser; use im::Vector; use iter_extended::{try_vecmap, vecmap}; -use noirc_errors::Location; +use noirc_errors::{Location, Span}; use crate::{ ast::{ArrayLiteral, ConstructorExpression, Ident, IntegerBitSize, Signedness}, @@ -46,7 +46,10 @@ pub enum Value { Pointer(Shared, /* auto_deref */ bool), Array(Vector, Type), Slice(Vector, Type), - Code(Rc), + /// Quoted tokens don't have spans because otherwise inserting them in the middle of other + /// tokens can cause larger spans to be before lesser spans, causing an assert. They may also + /// be inserted into separate files entirely. + Quoted(Rc>), StructDefinition(StructId), TraitConstraint(TraitId, /* trait generics */ Vec), TraitDefinition(TraitId), @@ -84,7 +87,7 @@ impl Value { Value::Struct(_, typ) => return Cow::Borrowed(typ), Value::Array(_, typ) => return Cow::Borrowed(typ), Value::Slice(_, typ) => return Cow::Borrowed(typ), - Value::Code(_) => Type::Quoted(QuotedType::Quoted), + Value::Quoted(_) => Type::Quoted(QuotedType::Quoted), Value::StructDefinition(_) => Type::Quoted(QuotedType::StructDefinition), Value::Pointer(element, auto_deref) => { if *auto_deref { @@ -204,9 +207,9 @@ impl Value { try_vecmap(elements, |element| element.into_expression(interner, location))?; ExpressionKind::Literal(Literal::Slice(ArrayLiteral::Standard(elements))) } - Value::Code(tokens) => { + Value::Quoted(tokens) => { // Wrap the tokens in '{' and '}' so that we can parse statements as well. - let mut tokens_to_parse = tokens.as_ref().clone(); + let mut tokens_to_parse = add_token_spans(tokens.clone(), location.span); tokens_to_parse.0.insert(0, SpannedToken::new(Token::LeftBrace, location.span)); tokens_to_parse.0.push(SpannedToken::new(Token::RightBrace, location.span)); @@ -228,7 +231,9 @@ impl Value { | Value::Zeroed(_) | Value::Type(_) | Value::ModuleDefinition(_) => { - return Err(InterpreterError::CannotInlineMacro { value: self, location }) + let typ = self.get_type().into_owned(); + let value = self.display(interner).to_string(); + return Err(InterpreterError::CannotInlineMacro { typ, value, location }); } }; @@ -339,7 +344,7 @@ impl Value { })?; HirExpression::Literal(HirLiteral::Slice(HirArrayLiteral::Standard(elements))) } - Value::Code(block) => HirExpression::Unquote(unwrap_rc(block)), + Value::Quoted(tokens) => HirExpression::Unquote(add_token_spans(tokens, location.span)), Value::Pointer(..) | Value::StructDefinition(_) | Value::TraitConstraint(..) @@ -348,7 +353,9 @@ impl Value { | Value::Zeroed(_) | Value::Type(_) | Value::ModuleDefinition(_) => { - return Err(InterpreterError::CannotInlineMacro { value: self, location }) + let typ = self.get_type().into_owned(); + let value = self.display(interner).to_string(); + return Err(InterpreterError::CannotInlineMacro { value, typ, location }); } }; @@ -362,13 +369,13 @@ impl Value { self, interner: &mut NodeInterner, location: Location, - ) -> IResult { + ) -> IResult> { let token = match self { - Value::Code(tokens) => return Ok(unwrap_rc(tokens)), + Value::Quoted(tokens) => return Ok(unwrap_rc(tokens)), Value::Type(typ) => Token::QuotedType(interner.push_quoted_type(typ)), other => Token::UnquoteMarker(other.into_hir_expression(interner, location)?), }; - Ok(Tokens(vec![SpannedToken::new(token, location.span)])) + Ok(vec![token]) } /// Converts any unsigned `Value` into a `u128`. @@ -391,12 +398,24 @@ impl Value { pub(crate) fn into_top_level_items( self, location: Location, + interner: &NodeInterner, ) -> IResult> { match self { - Value::Code(tokens) => parse_tokens(tokens, parser::top_level_items(), location.file), - value => Err(InterpreterError::CannotInlineMacro { value, location }), + Value::Quoted(tokens) => parse_tokens(tokens, parser::top_level_items(), location), + _ => { + let typ = self.get_type().into_owned(); + let value = self.display(interner).to_string(); + Err(InterpreterError::CannotInlineMacro { value, typ, location }) + } } } + + pub fn display<'value, 'interner>( + &'value self, + interner: &'interner NodeInterner, + ) -> ValuePrinter<'value, 'interner> { + ValuePrinter { value: self, interner } + } } /// Unwraps an Rc value without cloning the inner value if the reference count is 1. Clones otherwise. @@ -404,20 +423,35 @@ pub(crate) fn unwrap_rc(rc: Rc) -> T { Rc::try_unwrap(rc).unwrap_or_else(|rc| (*rc).clone()) } -fn parse_tokens(tokens: Rc, parser: impl NoirParser, file: fm::FileId) -> IResult { - match parser.parse(tokens.as_ref().clone()) { +fn parse_tokens( + tokens: Rc>, + parser: impl NoirParser, + location: Location, +) -> IResult { + match parser.parse(add_token_spans(tokens.clone(), location.span)) { Ok(expr) => Ok(expr), Err(mut errors) => { let error = errors.swap_remove(0); let rule = "an expression"; + let file = location.file; Err(InterpreterError::FailedToParseMacro { error, file, tokens, rule }) } } } -impl Display for Value { +pub(crate) fn add_token_spans(tokens: Rc>, span: Span) -> Tokens { + let tokens = unwrap_rc(tokens); + Tokens(vecmap(tokens, |token| SpannedToken::new(token, span))) +} + +pub struct ValuePrinter<'value, 'interner> { + value: &'value Value, + interner: &'interner NodeInterner, +} + +impl<'value, 'interner> Display for ValuePrinter<'value, 'interner> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { + match self.value { Value::Unit => write!(f, "()"), Value::Bool(value) => { let msg = if *value { "true" } else { "false" }; @@ -438,7 +472,7 @@ impl Display for Value { Value::Function(..) => write!(f, "(function)"), Value::Closure(_, _, _) => write!(f, "(closure)"), Value::Tuple(fields) => { - let fields = vecmap(fields, ToString::to_string); + let fields = vecmap(fields, |field| field.display(self.interner).to_string()); write!(f, "({})", fields.join(", ")) } Value::Struct(fields, typ) => { @@ -446,29 +480,48 @@ impl Display for Value { Type::Struct(def, _) => def.borrow().name.to_string(), other => other.to_string(), }; - let fields = vecmap(fields, |(name, value)| format!("{}: {}", name, value)); + let fields = vecmap(fields, |(name, value)| { + format!("{}: {}", name, value.display(self.interner)) + }); write!(f, "{typename} {{ {} }}", fields.join(", ")) } - Value::Pointer(value, _) => write!(f, "&mut {}", value.borrow()), + Value::Pointer(value, _) => write!(f, "&mut {}", value.borrow().display(self.interner)), Value::Array(values, _) => { - let values = vecmap(values, ToString::to_string); + let values = vecmap(values, |value| value.display(self.interner).to_string()); write!(f, "[{}]", values.join(", ")) } Value::Slice(values, _) => { - let values = vecmap(values, ToString::to_string); + let values = vecmap(values, |value| value.display(self.interner).to_string()); write!(f, "&[{}]", values.join(", ")) } - Value::Code(tokens) => { + Value::Quoted(tokens) => { write!(f, "quote {{")?; - for token in tokens.0.iter() { + for token in tokens.iter() { write!(f, " {token}")?; } write!(f, " }}") } - Value::StructDefinition(_) => write!(f, "(struct definition)"), - Value::TraitConstraint { .. } => write!(f, "(trait constraint)"), - Value::TraitDefinition(_) => write!(f, "(trait definition)"), - Value::FunctionDefinition(_) => write!(f, "(function definition)"), + Value::StructDefinition(id) => { + let def = self.interner.get_struct(*id); + let def = def.borrow(); + write!(f, "{}", def.name) + } + Value::TraitConstraint(trait_id, generics) => { + let trait_ = self.interner.get_trait(*trait_id); + let generic_string = vecmap(generics, ToString::to_string).join(", "); + if generics.is_empty() { + write!(f, "{}", trait_.name) + } else { + write!(f, "{}<{generic_string}>", trait_.name) + } + } + Value::TraitDefinition(trait_id) => { + let trait_ = self.interner.get_trait(*trait_id); + write!(f, "{}", trait_.name) + } + Value::FunctionDefinition(function_id) => { + write!(f, "{}", self.interner.function_name(function_id)) + } Value::ModuleDefinition(_) => write!(f, "(module)"), Value::Zeroed(typ) => write!(f, "(zeroed {typ})"), Value::Type(typ) => write!(f, "{}", typ), diff --git a/compiler/noirc_frontend/src/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs index 87ff45f8f1a..2c8e884cf12 100644 --- a/compiler/noirc_frontend/src/node_interner.rs +++ b/compiler/noirc_frontend/src/node_interner.rs @@ -1,5 +1,4 @@ use std::borrow::Cow; -use std::collections::HashMap; use std::fmt; use std::hash::Hash; use std::marker::Copy; @@ -12,6 +11,7 @@ use noirc_errors::{Location, Span, Spanned}; use petgraph::algo::tarjan_scc; use petgraph::prelude::DiGraph; use petgraph::prelude::NodeIndex as PetGraphIndex; +use rustc_hash::FxHashMap as HashMap; use crate::ast::Ident; use crate::graph::CrateId; @@ -230,6 +230,14 @@ pub struct NodeInterner { // The module where each reference is // (ReferenceId::Reference and ReferenceId::Local aren't included here) pub(crate) reference_modules: HashMap, + + /// Each value currently in scope in the comptime interpreter. + /// Each element of the Vec represents a scope with every scope together making + /// up all currently visible definitions. The first scope is always the global scope. + /// + /// This is stored in the NodeInterner so that the Elaborator from each crate can + /// share the same global values. + pub(crate) comptime_scopes: Vec>, } /// A dependency in the dependency graph may be a type or a definition. @@ -556,44 +564,45 @@ impl Default for NodeInterner { fn default() -> Self { NodeInterner { nodes: Arena::default(), - func_meta: HashMap::new(), - function_definition_ids: HashMap::new(), - function_modifiers: HashMap::new(), - function_modules: HashMap::new(), - module_attributes: HashMap::new(), - func_id_to_trait: HashMap::new(), + func_meta: HashMap::default(), + function_definition_ids: HashMap::default(), + function_modifiers: HashMap::default(), + function_modules: HashMap::default(), + module_attributes: HashMap::default(), + func_id_to_trait: HashMap::default(), dependency_graph: petgraph::graph::DiGraph::new(), - dependency_graph_indices: HashMap::new(), - id_to_location: HashMap::new(), + dependency_graph_indices: HashMap::default(), + id_to_location: HashMap::default(), definitions: vec![], - id_to_type: HashMap::new(), - definition_to_type: HashMap::new(), - structs: HashMap::new(), - struct_attributes: HashMap::new(), + id_to_type: HashMap::default(), + definition_to_type: HashMap::default(), + structs: HashMap::default(), + struct_attributes: HashMap::default(), type_aliases: Vec::new(), - traits: HashMap::new(), - trait_implementations: HashMap::new(), + traits: HashMap::default(), + trait_implementations: HashMap::default(), next_trait_implementation_id: 0, - trait_implementation_map: HashMap::new(), - selected_trait_implementations: HashMap::new(), - infix_operator_traits: HashMap::new(), - prefix_operator_traits: HashMap::new(), + trait_implementation_map: HashMap::default(), + selected_trait_implementations: HashMap::default(), + infix_operator_traits: HashMap::default(), + prefix_operator_traits: HashMap::default(), ordering_type: None, - instantiation_bindings: HashMap::new(), - field_indices: HashMap::new(), + instantiation_bindings: HashMap::default(), + field_indices: HashMap::default(), next_type_variable_id: std::cell::Cell::new(0), globals: Vec::new(), - global_attributes: HashMap::new(), - struct_methods: HashMap::new(), - primitive_methods: HashMap::new(), + global_attributes: HashMap::default(), + struct_methods: HashMap::default(), + primitive_methods: HashMap::default(), type_alias_ref: Vec::new(), type_ref_locations: Vec::new(), quoted_types: Default::default(), track_references: false, location_indices: LocationIndices::default(), reference_graph: petgraph::graph::DiGraph::new(), - reference_graph_indices: HashMap::new(), - reference_modules: HashMap::new(), + reference_graph_indices: HashMap::default(), + reference_modules: HashMap::default(), + comptime_scopes: vec![HashMap::default()], } } } diff --git a/noir_stdlib/src/cmp.nr b/noir_stdlib/src/cmp.nr index bdd5e2bc5ec..d2cf6b3836a 100644 --- a/noir_stdlib/src/cmp.nr +++ b/noir_stdlib/src/cmp.nr @@ -1,9 +1,37 @@ +use crate::meta::derive_via; + +#[derive_via(derive_eq)] // docs:start:eq-trait trait Eq { fn eq(self, other: Self) -> bool; } // docs:end:eq-trait +comptime fn derive_eq(s: StructDefinition) -> Quoted { + let typ = s.as_type(); + + let impl_generics = s.generics().join(quote {,}); + + let where_clause = s.generics().map(|name| quote { $name: Default }).join(quote {,}); + + // `(self.a == other.a) & (self.b == other.b) & ...` + let equalities = s.fields().map( + |f: (Quoted, Type)| { + let name = f.0; + quote { (self.$name == other.$name) } + } + ); + let body = equalities.join(quote { & }); + + quote { + impl<$impl_generics> Eq for $typ where $where_clause { + fn eq(self, other: Self) -> bool { + $body + } + } + } +} + impl Eq for Field { fn eq(self, other: Field) -> bool { self == other } } impl Eq for u64 { fn eq(self, other: u64) -> bool { self == other } } diff --git a/noir_stdlib/src/default.nr b/noir_stdlib/src/default.nr index 0acb3966034..f0d98205a90 100644 --- a/noir_stdlib/src/default.nr +++ b/noir_stdlib/src/default.nr @@ -1,9 +1,37 @@ +use crate::meta::derive_via; + +#[derive_via(derive_default)] // docs:start:default-trait trait Default { fn default() -> Self; } // docs:end:default-trait +comptime fn derive_default(s: StructDefinition) -> Quoted { + let typ = s.as_type(); + + let impl_generics = s.generics().join(quote {,}); + + let where_clause = s.generics().map(|name| quote { $name: Default }).join(quote {,}); + + // `foo: Default::default(), bar: Default::default(), ...` + let fields = s.fields().map( + |f: (Quoted, Type)| { + let name = f.0; + quote { $name: Default::default() } + } + ); + let fields = fields.join(quote {,}); + + quote { + impl<$impl_generics> Default for $typ where $where_clause { + fn default() -> Self { + Self { $fields } + } + } + } +} + impl Default for Field { fn default() -> Field { 0 } } impl Default for u8 { fn default() -> u8 { 0 } } diff --git a/noir_stdlib/src/hash/poseidon2.nr b/noir_stdlib/src/hash/poseidon2.nr index 08cf68d1f82..9626da0cf97 100644 --- a/noir_stdlib/src/hash/poseidon2.nr +++ b/noir_stdlib/src/hash/poseidon2.nr @@ -1,7 +1,7 @@ use crate::hash::Hasher; use crate::default::Default; -global RATE: u32 = 3; +comptime global RATE: u32 = 3; struct Poseidon2 { cache: [Field;3], diff --git a/noir_stdlib/src/meta/mod.nr b/noir_stdlib/src/meta/mod.nr index 395f09a453e..21ba24972be 100644 --- a/noir_stdlib/src/meta/mod.nr +++ b/noir_stdlib/src/meta/mod.nr @@ -1,6 +1,10 @@ +use crate::collections::umap::UHashMap; +use crate::hash::BuildHasherDefault; +use crate::hash::poseidon2::Poseidon2Hasher; + +mod struct_def; mod trait_constraint; mod trait_def; -mod type_def; mod quoted; /// Calling unquote as a macro (via `unquote!(arg)`) will unquote @@ -9,3 +13,26 @@ mod quoted; pub comptime fn unquote(code: Quoted) -> Quoted { code } + +type DeriveFunction = fn(StructDefinition) -> Quoted; + +comptime mut global HANDLERS: UHashMap> = UHashMap::default(); + +#[varargs] +pub comptime fn derive(s: StructDefinition, traits: [TraitDefinition]) -> Quoted { + let mut result = quote {}; + + for trait_to_derive in traits { + let handler = HANDLERS.get(trait_to_derive); + assert(handler.is_some(), f"No derive function registered for `{trait_to_derive}`"); + + let trait_impl = handler.unwrap()(s); + result = quote { $result $trait_impl }; + } + + result +} + +unconstrained pub comptime fn derive_via(t: TraitDefinition, f: DeriveFunction) { + HANDLERS.insert(t, f); +} diff --git a/noir_stdlib/src/meta/type_def.nr b/noir_stdlib/src/meta/struct_def.nr similarity index 100% rename from noir_stdlib/src/meta/type_def.nr rename to noir_stdlib/src/meta/struct_def.nr diff --git a/noir_stdlib/src/prelude.nr b/noir_stdlib/src/prelude.nr index 3244329aa4b..0d423e3556d 100644 --- a/noir_stdlib/src/prelude.nr +++ b/noir_stdlib/src/prelude.nr @@ -6,3 +6,4 @@ use crate::uint128::U128; use crate::cmp::{Eq, Ord}; use crate::default::Default; use crate::convert::{From, Into}; +use crate::meta::{derive, derive_via}; diff --git a/test_programs/execution_success/derive/src/main.nr b/test_programs/execution_success/derive/src/main.nr index 135d6551b88..e4148f2c944 100644 --- a/test_programs/execution_success/derive/src/main.nr +++ b/test_programs/execution_success/derive/src/main.nr @@ -1,40 +1,9 @@ -// Put derive functions in a separate module to show that TraitConstraints -// on an attribute can be resolved in the caller's scope -mod derive { - use std::collections::umap::UHashMap; - use std::hash::BuildHasherDefault; - use std::hash::poseidon2::Poseidon2Hasher; - - type DeriveFunction = fn(StructDefinition) -> Quoted; - - comptime mut global HANDLERS: UHashMap> = UHashMap::default(); - - #[varargs] - comptime fn my_derive(s: StructDefinition, traits: [TraitDefinition]) -> Quoted { - let mut result = quote {}; - - for trait_to_derive in traits { - let handler = HANDLERS.get(trait_to_derive); - assert(handler.is_some(), f"No derive function registered for `{trait_to_derive}`"); - - let trait_impl = handler.unwrap()(s); - result = quote { $result $trait_impl }; - } - - result - } - - unconstrained comptime fn my_derive_via(t: TraitDefinition, f: DeriveFunction) { - HANDLERS.insert(t, f); - } -} - -#[derive::my_derive_via(derive_do_nothing)] +#[derive_via(derive_do_nothing)] trait DoNothing { fn do_nothing(self); } -#[derive::my_derive(DoNothing)] +#[derive(DoNothing)] struct MyStruct { my_field: u32 } comptime fn derive_do_nothing(s: StructDefinition) -> Quoted { @@ -50,7 +19,17 @@ comptime fn derive_do_nothing(s: StructDefinition) -> Quoted { } } +// Test stdlib derive fns & multiple traits +#[derive(Eq, Default)] +struct MyOtherStruct { + field1: u32, + field2: u64, +} + fn main() { let s = MyStruct { my_field: 1 }; s.do_nothing(); + + let o = MyOtherStruct::default(); + assert_eq(o, o); }