diff --git a/Cargo.lock b/Cargo.lock index 7874d48f866..3c26d7307a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3580,6 +3580,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "in_definite" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5bd47a3c8188d842aa7ff4dd65a9732f740fcbe04689ecfbfef43c465b381de" + [[package]] name = "include_dir" version = "0.7.3" @@ -6665,6 +6671,7 @@ name = "sway-error" version = "0.56.0" dependencies = [ "either", + "in_definite", "num-traits", "smallvec", "sway-types", diff --git a/docs/book/src/basics/variables.md b/docs/book/src/basics/variables.md index a590552daca..9c409b9ff33 100644 --- a/docs/book/src/basics/variables.md +++ b/docs/book/src/basics/variables.md @@ -32,6 +32,18 @@ foo = 6; Now, `foo` is mutable, and the reassignment to the number `6` is valid. That is, we are allowed to _mutate_ the variable `foo` to change its value. +When assigning to a mutable variable, the right-hand side of the assignment is evaluated before the left-hand side. In the below example, the mutable variable `i` will first be increased and the resulting value of `1` will be stored to `array[1]`, thus resulting in `array` being changed to `[0, 1, 0]`. + +```sway +let mut array = [0, 0, 0]; +let mut i = 0; + +array[i] = { + i += 1; + i +}; +``` + ## Type Annotations diff --git a/docs/reference/src/code/language/variables/src/lib.sw b/docs/reference/src/code/language/variables/src/lib.sw index e4f90f68777..47a12293d28 100644 --- a/docs/reference/src/code/language/variables/src/lib.sw +++ b/docs/reference/src/code/language/variables/src/lib.sw @@ -7,6 +7,18 @@ fn mutable() { // ANCHOR_END: mutable } +fn mutable_evaluation_order() { + // ANCHOR: mutable_evaluation_order + let mut array = [0, 0, 0]; + let mut i = 0; + + array[i] = { + i += 1; + i + }; + // ANCHOR_END: mutable_evaluation_order +} + fn immutable() { // ANCHOR: immutable let foo = 5; diff --git a/docs/reference/src/documentation/language/variables/let.md b/docs/reference/src/documentation/language/variables/let.md index add7a224191..5853356f115 100644 --- a/docs/reference/src/documentation/language/variables/let.md +++ b/docs/reference/src/documentation/language/variables/let.md @@ -19,3 +19,9 @@ We can declare a variable that can have its value changed through the use of the ```sway {{#include ../../../code/language/variables/src/lib.sw:mutable}} ``` + +When assigning to a mutable variable, the right-hand side of the assignment is evaluated before the left-hand side. In the below example, the mutable variable `i` will first be increased and the resulting value of `1` will be stored to `array[1]`, thus resulting in `array` being changed to `[0, 1, 0]`. + +```sway +{{#include ../../../code/language/variables/src/lib.sw:mutable_evaluation_order}} +``` diff --git a/sway-ast/src/assignable.rs b/sway-ast/src/assignable.rs index 91a0051a418..a9c4f7931fe 100644 --- a/sway-ast/src/assignable.rs +++ b/sway-ast/src/assignable.rs @@ -1,19 +1,42 @@ use crate::priv_prelude::*; +/// Left-hand side of an assignment. #[derive(Clone, Debug, Serialize)] pub enum Assignable { + /// A single variable or a path to a part of an aggregate. + /// E.g.: + /// - `my_variable` + /// - `array[0].field.x.1` + /// Note that within the path, we cannot have dereferencing + /// (except, of course, in expressions inside of array index operator). + /// This is guaranteed by the grammar. + /// E.g., an expression like this is not allowed by the grammar: + /// `my_struct.*expr` + ElementAccess(ElementAccess), + /// Dereferencing of an arbitrary reference expression. + /// E.g.: + /// - *my_ref + /// - **if x > 0 { &mut &mut a } else { &mut &mut b } + Deref { + star_token: StarToken, + expr: Box, + }, +} + +#[derive(Clone, Debug, Serialize)] +pub enum ElementAccess { Var(Ident), Index { - target: Box, + target: Box, arg: SquareBrackets>, }, FieldProjection { - target: Box, + target: Box, dot_token: DotToken, name: Ident, }, TupleFieldProjection { - target: Box, + target: Box, dot_token: DotToken, field: BigUint, field_span: Span, @@ -23,12 +46,21 @@ pub enum Assignable { impl Spanned for Assignable { fn span(&self) -> Span { match self { - Assignable::Var(name) => name.span(), - Assignable::Index { target, arg } => Span::join(target.span(), arg.span()), - Assignable::FieldProjection { target, name, .. } => { + Assignable::ElementAccess(element_access) => element_access.span(), + Assignable::Deref { star_token, expr } => Span::join(star_token.span(), expr.span()), + } + } +} + +impl Spanned for ElementAccess { + fn span(&self) -> Span { + match self { + ElementAccess::Var(name) => name.span(), + ElementAccess::Index { target, arg } => Span::join(target.span(), arg.span()), + ElementAccess::FieldProjection { target, name, .. } => { Span::join(target.span(), name.span()) } - Assignable::TupleFieldProjection { + ElementAccess::TupleFieldProjection { target, field_span, .. } => Span::join(target.span(), field_span.clone()), } diff --git a/sway-ast/src/expr/mod.rs b/sway-ast/src/expr/mod.rs index 880e1de2632..49296ac127e 100644 --- a/sway-ast/src/expr/mod.rs +++ b/sway-ast/src/expr/mod.rs @@ -1,6 +1,6 @@ use sway_error::handler::ErrorEmitted; -use crate::{priv_prelude::*, PathExprSegment}; +use crate::{assignable::ElementAccess, priv_prelude::*, PathExprSegment}; pub mod asm; pub mod op_code; @@ -446,56 +446,59 @@ impl Spanned for ExprStructField { } impl Expr { + /// Returns the resulting [Assignable] if the `self` is a + /// valid [Assignable], or an error containing the [Expr] + /// which causes the `self` to be an invalid [Assignable]. + /// + /// In case of an error, the returned [Expr] can be `self` + /// or any subexpression of `self` that is not allowed + /// in assignment targets. pub fn try_into_assignable(self) -> Result { + if let Expr::Deref { star_token, expr } = self { + Ok(Assignable::Deref { star_token, expr }) + } else { + Ok(Assignable::ElementAccess(self.try_into_element_access()?)) + } + } + + fn try_into_element_access(self) -> Result { match self { Expr::Path(path_expr) => match path_expr.try_into_ident() { - Ok(name) => Ok(Assignable::Var(name)), + Ok(name) => Ok(ElementAccess::Var(name)), Err(path_expr) => Err(Expr::Path(path_expr)), }, - Expr::Index { target, arg } => match target.try_into_assignable() { - Ok(target) => Ok(Assignable::Index { - target: Box::new(target), - arg, - }), - Err(target) => Err(Expr::Index { + Expr::Index { target, arg } => match target.try_into_element_access() { + Ok(target) => Ok(ElementAccess::Index { target: Box::new(target), arg, }), + error => error, }, Expr::FieldProjection { target, dot_token, name, - } => match target.try_into_assignable() { - Ok(target) => Ok(Assignable::FieldProjection { - target: Box::new(target), - dot_token, - name, - }), - Err(target) => Err(Expr::FieldProjection { + } => match target.try_into_element_access() { + Ok(target) => Ok(ElementAccess::FieldProjection { target: Box::new(target), dot_token, name, }), + error => error, }, Expr::TupleFieldProjection { target, dot_token, field, field_span, - } => match target.try_into_assignable() { - Ok(target) => Ok(Assignable::TupleFieldProjection { - target: Box::new(target), - dot_token, - field, - field_span, - }), - Err(target) => Err(Expr::TupleFieldProjection { + } => match target.try_into_element_access() { + Ok(target) => Ok(ElementAccess::TupleFieldProjection { target: Box::new(target), dot_token, field, field_span, }), + error => error, }, expr => Err(expr), } @@ -550,4 +553,55 @@ impl Expr { | Expr::Continue { .. } => false, } } + + /// Friendly [Expr] name string used for error reporting, + pub fn friendly_name(&self) -> &'static str { + match self { + Expr::Error(_, _) => "error", + Expr::Path(_) => "path", + Expr::Literal(_) => "literal", + Expr::AbiCast { .. } => "ABI cast", + Expr::Struct { .. } => "struct instantiation", + Expr::Tuple(_) => "tuple", + Expr::Parens(_) => "parentheses", // Note the plural! + Expr::Block(_) => "block", + Expr::Array(_) => "array", + Expr::Asm(_) => "assembly block", + Expr::Return { .. } => "return", + Expr::If(_) => "if expression", + Expr::Match { .. } => "match expression", + Expr::While { .. } => "while loop", + Expr::For { .. } => "for loop", + Expr::FuncApp { .. } => "function call", + Expr::Index { .. } => "array element access", + Expr::MethodCall { .. } => "method call", + Expr::FieldProjection { .. } => "struct field access", + Expr::TupleFieldProjection { .. } => "tuple element access", + Expr::Ref { .. } => "referencing", + Expr::Deref { .. } => "dereferencing", + Expr::Not { .. } => "negation", + Expr::Mul { .. } => "multiplication", + Expr::Div { .. } => "division", + Expr::Pow { .. } => "power operation", + Expr::Modulo { .. } => "modulo operation", + Expr::Add { .. } => "addition", + Expr::Sub { .. } => "subtraction", + Expr::Shl { .. } => "left shift", + Expr::Shr { .. } => "right shift", + Expr::BitAnd { .. } => "bitwise and", + Expr::BitXor { .. } => "bitwise xor", + Expr::BitOr { .. } => "bitwise or", + Expr::Equal { .. } => "equality", + Expr::NotEqual { .. } => "non equality", + Expr::LessThan { .. } => "less than operation", + Expr::GreaterThan { .. } => "greater than operation", + Expr::LessThanEq { .. } => "less than or equal operation", + Expr::GreaterThanEq { .. } => "greater than or equal operation", + Expr::LogicalAnd { .. } => "logical and", + Expr::LogicalOr { .. } => "logical or", + Expr::Reassignment { .. } => "reassignment", + Expr::Break { .. } => "break", + Expr::Continue { .. } => "continue", + } + } } diff --git a/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs b/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs index 3c18eb42f90..644aba30022 100644 --- a/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs +++ b/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs @@ -1788,7 +1788,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { // --------------------------------------------------------------------------------------------- - // XXX reassess all the places we use this + // TODO-IG: Reassess all the places we use `is_copy_type`. pub(crate) fn is_copy_type(&self, ty: &Type) -> bool { ty.is_unit(self.context) || ty.is_never(self.context) diff --git a/sway-core/src/control_flow_analysis/dead_code_analysis.rs b/sway-core/src/control_flow_analysis/dead_code_analysis.rs index bfc75ab41dc..64ef4f02154 100644 --- a/sway-core/src/control_flow_analysis/dead_code_analysis.rs +++ b/sway-core/src/control_flow_analysis/dead_code_analysis.rs @@ -4,8 +4,8 @@ use crate::{ language::{ parsed::TreeType, ty::{ - self, ConstantDecl, FunctionDecl, StructDecl, TraitDecl, TyAstNode, TyAstNodeContent, - TyDecl, TyImplItem, TypeAliasDecl, + self, ConstantDecl, FunctionDecl, ProjectionKind, StructDecl, TraitDecl, TyAstNode, + TyAstNodeContent, TyDecl, TyImplItem, TypeAliasDecl, }, CallPath, Visibility, }, @@ -1956,23 +1956,60 @@ fn connect_expression<'eng: 'cfg, 'cfg>( Ok(vec![]) } Reassignment(typed_reassignment) => { - if let Some(variable_entry) = graph - .namespace - .get_variable(&typed_reassignment.lhs_base_name) - { - for leaf in leaves { - graph.add_edge(*leaf, variable_entry.variable_decl_ix, "".into()); + match &typed_reassignment.lhs { + ty::TyReassignmentTarget::ElementAccess { + base_name, indices, .. + } => { + if let Some(variable_entry) = graph.namespace.get_variable(base_name) { + for leaf in leaves { + graph.add_edge( + *leaf, + variable_entry.variable_decl_ix, + "variable reassignment LHS".into(), + ); + } + }; + + for projection in indices { + if let ProjectionKind::ArrayIndex { index, index_span } = projection { + connect_expression( + engines, + &index.expression, + graph, + leaves, + exit_node, + "variable reassignment LHS array index", + tree_type, + index_span.clone(), + options, + )?; + } + } } - } + ty::TyReassignmentTarget::Deref(exp) => { + connect_expression( + engines, + &exp.expression, + graph, + leaves, + exit_node, + "variable reassignment LHS dereferencing", + tree_type, + exp.span.clone(), + options, + )?; + } + }; + connect_expression( engines, &typed_reassignment.rhs.expression, graph, leaves, exit_node, - "variable reassignment", + "variable reassignment RHS", tree_type, - typed_reassignment.rhs.clone().span, + typed_reassignment.rhs.span.clone(), options, ) } diff --git a/sway-core/src/ir_generation/function.rs b/sway-core/src/ir_generation/function.rs index c1b3de35b71..beb577f389c 100644 --- a/sway-core/src/ir_generation/function.rs +++ b/sway-core/src/ir_generation/function.rs @@ -18,6 +18,7 @@ use crate::{ type_system::*, types::*, }; + use indexmap::IndexMap; use sway_ast::intrinsics::Intrinsic; use sway_error::error::CompileError; @@ -73,8 +74,8 @@ impl TerminatorValue { } } -// If the provided TerminatorValue::is_terminator is true, then return from the current function -// immediately. Otherwise extract the embedded Value. +/// If the provided [TerminatorValue::is_terminator] is true, then return from the current function +/// immediately. Otherwise extract the embedded [Value]. macro_rules! return_on_termination_or_extract { ($value:expr) => {{ let val = $value; @@ -1413,8 +1414,51 @@ impl<'eng> FnCompiler<'eng> { ast_expr: &ty::TyExpression, span_md_idx: Option, ) -> Result { - let ref_value = - return_on_termination_or_extract!(self.compile_expression(context, md_mgr, ast_expr)?); + let (ptr, referenced_ast_type) = + self.compile_deref_up_to_ptr(context, md_mgr, ast_expr, span_md_idx)?; + + let ptr = return_on_termination_or_extract!(ptr); + + let referenced_type = self.engines.te().get_unaliased(referenced_ast_type); + + let result = if referenced_type.is_copy_type() || referenced_type.is_reference() { + // For non aggregates, we need to return the value. + // This means, loading the value the `ptr` is pointing to. + self.current_block.append(context).load(ptr) + } else { + // For aggregates, we access them via pointer, so we just + // need to return the `ptr`. + ptr + }; + + Ok(TerminatorValue::new(result, context)) + } + + /// Compiles a [ty::TyExpression] of the variant [TyExpressionVariant::Deref] + /// up to the pointer to the referenced value. + /// The referenced value can afterwards be accessed from the returned pointer, + /// and either read from or written to. + /// Writing to is happening in reassignments. + /// + /// Returns the compiled pointer and the [TypeId] of the + /// type of the referenced value. + /// + /// If the returned [TerminatorValue::is_terminator] is true, + /// the returned [TypeId] does not represent any existing type and + /// is assumed to be never used by callers. + fn compile_deref_up_to_ptr( + &mut self, + context: &mut Context, + md_mgr: &mut MetadataManager, + ast_expr: &ty::TyExpression, + span_md_idx: Option, + ) -> Result<(TerminatorValue, TypeId), CompileError> { + let ref_value = self.compile_expression(context, md_mgr, ast_expr)?; + let ref_value = if ref_value.is_terminator { + return Ok((ref_value, 0usize.into())); + } else { + ref_value.value + }; let ptr_as_int = if ref_value .get_type(context) @@ -1456,19 +1500,7 @@ impl<'eng> FnCompiler<'eng> { .int_to_ptr(ptr_as_int, ptr_type) .add_metadatum(context, span_md_idx); - let referenced_type = self.engines.te().get_unaliased(referenced_ast_type); - - let result = if referenced_type.is_copy_type() || referenced_type.is_reference() { - // For non aggregates, we need to return the value. - // This means, loading the value the `ptr` is pointing to. - self.current_block.append(context).load(ptr) - } else { - // For aggregates, we access them via pointer, so we just - // need to return the `ptr`. - ptr - }; - - Ok(TerminatorValue::new(result, context)) + Ok((TerminatorValue::new(ptr, context), referenced_ast_type)) } fn compile_lazy_op( @@ -2430,114 +2462,143 @@ impl<'eng> FnCompiler<'eng> { ast_reassignment: &ty::TyReassignment, span_md_idx: Option, ) -> Result { - let name = self - .lexical_map - .get(ast_reassignment.lhs_base_name.as_str()) - .expect("All local symbols must be in the lexical symbol map."); - - // First look for a local variable with the required name - let lhs_val = self - .function - .get_local_var(context, name) - .map(|var| { - self.current_block - .append(context) - .get_local(var) - .add_metadatum(context, span_md_idx) - }) - .or_else(|| - // Now look for an argument with the required name - self.function - .args_iter(context) - .find_map(|(arg_name, arg_val)| (arg_name == name).then_some(*arg_val))) - .ok_or_else(|| { - CompileError::InternalOwned( - format!("variable not found: {name}"), - ast_reassignment.lhs_base_name.span(), - ) - })?; - - let reassign_val = return_on_termination_or_extract!(self.compile_expression_to_value( + let rhs = return_on_termination_or_extract!(self.compile_expression_to_value( context, md_mgr, &ast_reassignment.rhs )?); - let lhs_ptr = if ast_reassignment.lhs_indices.is_empty() { - // A non-aggregate; use a direct `store`. - lhs_val - } else { - // Create a GEP by following the chain of LHS indices. We use a scan which is - // essentially a map with context, which is the parent type id for the current field. - let mut gep_indices = Vec::::new(); - let mut cur_type_id = ast_reassignment.lhs_type; - for idx_kind in ast_reassignment.lhs_indices.iter() { - let cur_type_info_arc = self.engines.te().get_unaliased(cur_type_id); - let cur_type_info = &*cur_type_info_arc; - match (idx_kind, cur_type_info) { - ( - ProjectionKind::StructField { name: idx_name }, - TypeInfo::Struct(decl_ref), - ) => { - let struct_decl = self.engines.de().get_struct(decl_ref); - - match struct_decl.get_field_index_and_type(idx_name) { - None => { - return Err(CompileError::InternalOwned( - format!( - "Unknown field name '{idx_name}' for struct '{}' \ - in reassignment.", - struct_decl.call_path.suffix.as_str(), - ), - ast_reassignment.lhs_base_name.span(), - )) + let lhs_ptr = match &ast_reassignment.lhs { + ty::TyReassignmentTarget::ElementAccess { + base_name, + base_type, + indices, + } => { + let name = self + .lexical_map + .get(base_name.as_str()) + .expect("All local symbols must be in the lexical symbol map."); + + // First look for a local variable with the required name + let lhs_val = self + .function + .get_local_var(context, name) + .map(|var| { + self.current_block + .append(context) + .get_local(var) + .add_metadatum(context, span_md_idx) + }) + .or_else(|| + // Now look for an argument with the required name + self.function + .args_iter(context) + .find_map(|(arg_name, arg_val)| (arg_name == name).then_some(*arg_val))) + .ok_or_else(|| { + CompileError::InternalOwned( + format!("Variable not found: {name}."), + base_name.span(), + ) + })?; + + if indices.is_empty() { + // A non-aggregate; use a direct `store`. + lhs_val + } else { + // Create a GEP by following the chain of LHS indices. We use a scan which is + // essentially a map with context, which is the parent type id for the current field. + let mut gep_indices = Vec::::new(); + let mut cur_type_id = *base_type; + // TODO-IG: Add support for projections being references themselves. + for idx_kind in indices.iter() { + let cur_type_info_arc = self.engines.te().get_unaliased(cur_type_id); + let cur_type_info = &*cur_type_info_arc; + match (idx_kind, cur_type_info) { + ( + ProjectionKind::StructField { name: idx_name }, + TypeInfo::Struct(decl_ref), + ) => { + let struct_decl = self.engines.de().get_struct(decl_ref); + + match struct_decl.get_field_index_and_type(idx_name) { + None => { + return Err(CompileError::InternalOwned( + format!( + "Unknown field name \"{idx_name}\" for struct \"{}\" \ + in reassignment.", + struct_decl.call_path.suffix.as_str(), + ), + idx_name.span(), + )) + } + Some((field_idx, field_type_id)) => { + cur_type_id = field_type_id; + gep_indices + .push(Constant::get_uint(context, 64, field_idx)); + } + } + } + ( + ProjectionKind::TupleField { index, .. }, + TypeInfo::Tuple(field_tys), + ) => { + cur_type_id = field_tys[*index].type_id; + gep_indices.push(Constant::get_uint(context, 64, *index as u64)); } - Some((field_idx, field_type_id)) => { - cur_type_id = field_type_id; - gep_indices.push(Constant::get_uint(context, 64, field_idx)); + ( + ProjectionKind::ArrayIndex { index, .. }, + TypeInfo::Array(elem_ty, _), + ) => { + cur_type_id = elem_ty.type_id; + let val = return_on_termination_or_extract!( + self.compile_expression_to_value(context, md_mgr, index)? + ); + gep_indices.push(val); + } + _ => { + return Err(CompileError::Internal( + "Unknown field in reassignment.", + idx_kind.span(), + )) } } } - (ProjectionKind::TupleField { index, .. }, TypeInfo::Tuple(field_tys)) => { - cur_type_id = field_tys[*index].type_id; - gep_indices.push(Constant::get_uint(context, 64, *index as u64)); - } - (ProjectionKind::ArrayIndex { index, .. }, TypeInfo::Array(elem_ty, _)) => { - cur_type_id = elem_ty.type_id; - let val = return_on_termination_or_extract!( - self.compile_expression_to_value(context, md_mgr, index)? - ); - gep_indices.push(val); - } - _ => { - return Err(CompileError::Internal( - "Unknown field in reassignment.", - idx_kind.span(), - )) - } + + // Using the type of the RHS for the GEP, rather than the final inner type of the + // aggregate, but getting the latter is a bit of a pain, though the `scan` above knew it. + // The program is type checked and the IR types on the LHS and RHS are the same. + let field_type = rhs.get_type(context).ok_or_else(|| { + CompileError::Internal( + "Failed to determine type of reassignment.", + base_name.span(), + ) + })?; + + // Create the GEP. + self.current_block + .append(context) + .get_elem_ptr(lhs_val, field_type, gep_indices) + .add_metadatum(context, span_md_idx) } } + ty::TyReassignmentTarget::Deref(dereference_exp) => { + let TyExpressionVariant::Deref(reference_exp) = &dereference_exp.expression else { + return Err(CompileError::Internal( + "Left-hand side of the reassignment must be dereferencing.", + dereference_exp.span.clone(), + )); + }; - // Using the type of the RHS for the GEP, rather than the final inner type of the - // aggregate, but getting the later is a bit of a pain, though the `scan` above knew it. - // Realistically the program is type checked and they should be the same. - let field_type = reassign_val.get_type(context).ok_or_else(|| { - CompileError::Internal( - "Failed to determine type of reassignment.", - ast_reassignment.lhs_base_name.span(), - ) - })?; + let (ptr, _) = + self.compile_deref_up_to_ptr(context, md_mgr, reference_exp, span_md_idx)?; - // Create the GEP. - self.current_block - .append(context) - .get_elem_ptr(lhs_val, field_type, gep_indices) - .add_metadatum(context, span_md_idx) + return_on_termination_or_extract!(ptr) + } }; self.current_block .append(context) - .store(lhs_ptr, reassign_val) + .store(lhs_ptr, rhs) .add_metadatum(context, span_md_idx); let val = Constant::get_unit(context).add_metadatum(context, span_md_idx); @@ -2583,7 +2644,7 @@ impl<'eng> FnCompiler<'eng> { .iter() .all(|elm| matches!(elm.expression, TyExpressionVariant::Literal(..))); - // We only do the optimization for suffiently large arrays, so that + // We only do the optimization for sufficiently large arrays, so that // overhead due to the loop doesn't make it worse than the unrolled version. if all_consts && contents.len() > 5 { // We can compile all elements ahead of time without affecting register pressure. @@ -2753,7 +2814,7 @@ impl<'eng> FnCompiler<'eng> { let elem_type = array_type.get_array_elem_type(context).ok_or_else(|| { CompileError::Internal( - "Array type has alread confirmed to be an array. Getting elem type can't fail.", + "Array type is already confirmed as an array. Getting the element type can't fail.", array_expr.span.clone(), ) })?; diff --git a/sway-core/src/language/parsed/expression/mod.rs b/sway-core/src/language/parsed/expression/mod.rs index 2a5952bc1a3..89b98db40b9 100644 --- a/sway-core/src/language/parsed/expression/mod.rs +++ b/sway-core/src/language/parsed/expression/mod.rs @@ -335,7 +335,18 @@ pub struct RefExpression { #[derive(Debug, Clone)] pub enum ReassignmentTarget { - VariableExpression(Box), + /// An [Expression] representing a single variable or a path + /// to a part of an aggregate. + /// E.g.: + /// - `my_variable` + /// - `array[0].field.x.1` + ElementAccess(Box), + /// An dereferencing [Expression] representing dereferencing + /// of an arbitrary reference expression. + /// E.g.: + /// - *my_ref + /// - **if x > 0 { &mut &mut a } else { &mut &mut b } + Deref(Box), } #[derive(Debug, Clone)] diff --git a/sway-core/src/language/ty/declaration/declaration.rs b/sway-core/src/language/ty/declaration/declaration.rs index 475af10ed61..d6c3d2045d7 100644 --- a/sway-core/src/language/ty/declaration/declaration.rs +++ b/sway-core/src/language/ty/declaration/declaration.rs @@ -729,7 +729,7 @@ impl TyDecl { } } - /// friendly name string used for error reporting, + /// Friendly name string used for error reporting, /// which consists of the identifier for the declaration. pub fn friendly_name(&self, engines: &Engines) -> String { let decl_engine = engines.de(); @@ -752,8 +752,14 @@ impl TyDecl { } } - /// friendly type name string used for error reporting, + /// Friendly type name string used for various reporting, /// which consists of the type name of the declaration AST node. + /// + /// Note that all friendly type names are lowercase. + /// This is also the case for acronyms like ABI. + /// For contexts in which acronyms need to be uppercase, like + /// e.g., error reporting, use `friendly_type_name_with_acronym` + /// instead. pub fn friendly_type_name(&self) -> &'static str { use TyDecl::*; match self { @@ -774,7 +780,14 @@ impl TyDecl { } } - /// name string used in `forc doc` file path generation that mirrors `cargo doc`. + pub fn friendly_type_name_with_acronym(&self) -> &'static str { + match self.friendly_type_name() { + "abi" => "ABI", + friendly_name => friendly_name, + } + } + + /// Name string used in `forc doc` file path generation that mirrors `cargo doc`. pub fn doc_name(&self) -> &'static str { use TyDecl::*; match self { diff --git a/sway-core/src/language/ty/expression/expression_variant.rs b/sway-core/src/language/ty/expression/expression_variant.rs index fd79a9989b9..fc5ad0e9ffc 100644 --- a/sway-core/src/language/ty/expression/expression_variant.rs +++ b/sway-core/src/language/ty/expression/expression_variant.rs @@ -1210,19 +1210,6 @@ impl TypeCheckFinalization for TyExpressionVariant { TyExpressionVariant::Break => {} TyExpressionVariant::Continue => {} TyExpressionVariant::Reassignment(node) => { - for lhs_index in node.lhs_indices.iter_mut() { - match lhs_index { - ProjectionKind::StructField { name: _ } => {} - ProjectionKind::TupleField { - index: _, - index_span: _, - } => {} - ProjectionKind::ArrayIndex { - index, - index_span: _, - } => index.expression.type_check_finalize(handler, ctx)?, - } - } node.type_check_finalize(handler, ctx)?; } TyExpressionVariant::ImplicitReturn(node) | TyExpressionVariant::Return(node) => { @@ -1484,20 +1471,37 @@ impl DebugWithEngines for TyExpressionVariant { TyExpressionVariant::Break => "break".to_string(), TyExpressionVariant::Continue => "continue".to_string(), TyExpressionVariant::Reassignment(reassignment) => { - let mut place = reassignment.lhs_base_name.to_string(); - for index in &reassignment.lhs_indices { - place.push('.'); - match index { - ProjectionKind::StructField { name } => place.push_str(name.as_str()), - ProjectionKind::TupleField { index, .. } => { - write!(&mut place, "{index}").unwrap(); - } - ProjectionKind::ArrayIndex { index, .. } => { - write!(&mut place, "{index:#?}").unwrap(); + let target = match &reassignment.lhs { + TyReassignmentTarget::Deref(exp) => format!("{:?}", engines.help_out(exp)), + TyReassignmentTarget::ElementAccess { + base_name, + base_type: _, + indices, + } => { + let mut target = base_name.to_string(); + for index in indices { + match index { + ProjectionKind::StructField { name } => { + target.push('.'); + target.push_str(name.as_str()); + } + ProjectionKind::TupleField { index, .. } => { + target.push('.'); + target.push_str(index.to_string().as_str()); + } + ProjectionKind::ArrayIndex { index, .. } => { + write!(&mut target, "[{:?}]", engines.help_out(index)).unwrap(); + } + } } + target } - } - format!("reassignment to {place}") + }; + + format!( + "reassignment to {target} = {:?}", + engines.help_out(&reassignment.rhs) + ) } TyExpressionVariant::ImplicitReturn(exp) => { format!("implicit return {:?}", engines.help_out(&**exp)) diff --git a/sway-core/src/language/ty/expression/reassignment.rs b/sway-core/src/language/ty/expression/reassignment.rs index e118f7d284f..d643ef63227 100644 --- a/sway-core/src/language/ty/expression/reassignment.rs +++ b/sway-core/src/language/ty/expression/reassignment.rs @@ -20,52 +20,143 @@ use crate::{ #[derive(Clone, Debug)] pub struct TyReassignment { - // either a direct variable, so length of 1, or - // at series of struct fields/array indices (array syntax) - pub lhs_base_name: Ident, - pub lhs_type: TypeId, - pub lhs_indices: Vec, + pub lhs: TyReassignmentTarget, pub rhs: TyExpression, } +#[derive(Clone, Debug)] +pub enum TyReassignmentTarget { + /// An [TyExpression] representing a single variable or a path + /// to a part of an aggregate. + /// E.g.: + /// - `my_variable` + /// - `array[0].field.x.1` + ElementAccess { + /// [Ident] of the single variable, or the starting variable + /// of the path to a part of an aggregate. + base_name: Ident, + /// [TypeId] of the variable behind the `base_name`. + base_type: TypeId, + /// Indices representing the path from the `base_name` to the + /// final part of an aggregate. + /// Empty if the LHS of the reassignment is a single variable. + indices: Vec, + }, + /// An dereferencing [TyExpression] representing dereferencing + /// of an arbitrary reference expression. + /// E.g.: + /// - *my_ref + /// - **if x > 0 { &mut &mut a } else { &mut &mut b } + /// The [TyExpression] is guaranteed to be of [TyExpressionVariant::Deref]. + Deref(Box), +} + +impl EqWithEngines for TyReassignmentTarget {} +impl PartialEqWithEngines for TyReassignmentTarget { + fn eq(&self, other: &Self, ctx: &PartialEqWithEnginesContext) -> bool { + let type_engine = ctx.engines().te(); + match (self, other) { + (TyReassignmentTarget::Deref(l), TyReassignmentTarget::Deref(r)) => (*l).eq(r, ctx), + ( + TyReassignmentTarget::ElementAccess { + base_name: l_name, + base_type: l_type, + indices: l_indices, + }, + TyReassignmentTarget::ElementAccess { + base_name: r_name, + base_type: r_type, + indices: r_indices, + }, + ) => { + l_name == r_name + && (l_type == r_type + || type_engine.get(*l_type).eq(&type_engine.get(*r_type), ctx)) + && l_indices.eq(r_indices, ctx) + } + _ => false, + } + } +} + impl EqWithEngines for TyReassignment {} impl PartialEqWithEngines for TyReassignment { fn eq(&self, other: &Self, ctx: &PartialEqWithEnginesContext) -> bool { - let type_engine = ctx.engines().te(); - self.lhs_base_name == other.lhs_base_name - && type_engine - .get(self.lhs_type) - .eq(&type_engine.get(other.lhs_type), ctx) - && self.lhs_indices.eq(&other.lhs_indices, ctx) - && self.rhs.eq(&other.rhs, ctx) + self.lhs.eq(&other.lhs, ctx) && self.rhs.eq(&other.rhs, ctx) } } -impl HashWithEngines for TyReassignment { +impl HashWithEngines for TyReassignmentTarget { fn hash(&self, state: &mut H, engines: &Engines) { - let TyReassignment { - lhs_base_name, - lhs_type, - lhs_indices, - rhs, - } = self; let type_engine = engines.te(); - lhs_base_name.hash(state); - type_engine.get(*lhs_type).hash(state, engines); - lhs_indices.hash(state, engines); + match self { + TyReassignmentTarget::Deref(exp) => exp.hash(state, engines), + TyReassignmentTarget::ElementAccess { + base_name, + base_type, + indices, + } => { + base_name.hash(state); + type_engine.get(*base_type).hash(state, engines); + indices.hash(state, engines); + } + }; + } +} + +impl HashWithEngines for TyReassignment { + fn hash(&self, state: &mut H, engines: &Engines) { + let TyReassignment { lhs, rhs } = self; + + lhs.hash(state, engines); rhs.hash(state, engines); } } +impl SubstTypes for TyReassignmentTarget { + fn subst_inner(&mut self, type_mapping: &TypeSubstMap, engines: &Engines) -> HasChanges { + has_changes! { + match self { + TyReassignmentTarget::Deref(exp) => exp.subst(type_mapping, engines), + TyReassignmentTarget::ElementAccess { base_type, indices, .. } => { + has_changes! { + base_type.subst(type_mapping, engines); + indices.subst(type_mapping, engines); + } + } + }; + } + } +} + impl SubstTypes for TyReassignment { fn subst_inner(&mut self, type_mapping: &TypeSubstMap, engines: &Engines) -> HasChanges { has_changes! { + self.lhs.subst(type_mapping, engines); self.rhs.subst(type_mapping, engines); - self.lhs_type.subst(type_mapping, engines); } } } +impl ReplaceDecls for TyReassignmentTarget { + fn replace_decls_inner( + &mut self, + decl_mapping: &DeclMapping, + handler: &Handler, + ctx: &mut TypeCheckContext, + ) -> Result { + Ok(match self { + TyReassignmentTarget::Deref(exp) => exp.replace_decls(decl_mapping, handler, ctx)?, + TyReassignmentTarget::ElementAccess { indices, .. } => indices + .iter_mut() + .map(|i| i.replace_decls(decl_mapping, handler, ctx)) + .collect::, _>>()? + .iter() + .any(|is_changed| *is_changed), + }) + } +} + impl ReplaceDecls for TyReassignment { fn replace_decls_inner( &mut self, @@ -73,7 +164,28 @@ impl ReplaceDecls for TyReassignment { handler: &Handler, ctx: &mut TypeCheckContext, ) -> Result { - self.rhs.replace_decls(decl_mapping, handler, ctx) + let lhs_changed = self.lhs.replace_decls(decl_mapping, handler, ctx)?; + let rhs_changed = self.rhs.replace_decls(decl_mapping, handler, ctx)?; + + Ok(lhs_changed || rhs_changed) + } +} + +impl TypeCheckAnalysis for TyReassignmentTarget { + fn type_check_analyze( + &self, + handler: &Handler, + ctx: &mut TypeCheckAnalysisContext, + ) -> Result<(), ErrorEmitted> { + match self { + TyReassignmentTarget::Deref(exp) => exp.type_check_analyze(handler, ctx)?, + TyReassignmentTarget::ElementAccess { indices, .. } => indices + .iter() + .map(|i| i.type_check_analyze(handler, ctx)) + .collect::, _>>() + .map(|_| ())?, + }; + Ok(()) } } @@ -83,7 +195,28 @@ impl TypeCheckAnalysis for TyReassignment { handler: &Handler, ctx: &mut TypeCheckAnalysisContext, ) -> Result<(), ErrorEmitted> { - self.rhs.type_check_analyze(handler, ctx) + self.lhs.type_check_analyze(handler, ctx)?; + self.rhs.type_check_analyze(handler, ctx)?; + + Ok(()) + } +} + +impl TypeCheckFinalization for TyReassignmentTarget { + fn type_check_finalize( + &mut self, + handler: &Handler, + ctx: &mut TypeCheckFinalizationContext, + ) -> Result<(), ErrorEmitted> { + match self { + TyReassignmentTarget::Deref(exp) => exp.type_check_finalize(handler, ctx)?, + TyReassignmentTarget::ElementAccess { indices, .. } => indices + .iter_mut() + .map(|i| i.type_check_finalize(handler, ctx)) + .collect::, _>>() + .map(|_| ())?, + }; + Ok(()) } } @@ -93,14 +226,34 @@ impl TypeCheckFinalization for TyReassignment { handler: &Handler, ctx: &mut TypeCheckFinalizationContext, ) -> Result<(), ErrorEmitted> { - self.rhs.type_check_finalize(handler, ctx) + self.lhs.type_check_finalize(handler, ctx)?; + self.rhs.type_check_finalize(handler, ctx)?; + + Ok(()) + } +} + +impl UpdateConstantExpression for TyReassignmentTarget { + fn update_constant_expression(&mut self, engines: &Engines, implementing_type: &TyDecl) { + match self { + TyReassignmentTarget::Deref(exp) => { + exp.update_constant_expression(engines, implementing_type) + } + TyReassignmentTarget::ElementAccess { indices, .. } => { + indices + .iter_mut() + .for_each(|i| i.update_constant_expression(engines, implementing_type)); + } + }; } } impl UpdateConstantExpression for TyReassignment { fn update_constant_expression(&mut self, engines: &Engines, implementing_type: &TyDecl) { + self.lhs + .update_constant_expression(engines, implementing_type); self.rhs - .update_constant_expression(engines, implementing_type) + .update_constant_expression(engines, implementing_type); } } @@ -176,6 +329,73 @@ impl HashWithEngines for ProjectionKind { } } +impl SubstTypes for ProjectionKind { + fn subst_inner(&mut self, type_mapping: &TypeSubstMap, engines: &Engines) -> HasChanges { + use ProjectionKind::*; + match self { + ArrayIndex { index, .. } => index.subst(type_mapping, engines), + _ => HasChanges::No, + } + } +} + +impl ReplaceDecls for ProjectionKind { + fn replace_decls_inner( + &mut self, + decl_mapping: &DeclMapping, + handler: &Handler, + ctx: &mut TypeCheckContext, + ) -> Result { + use ProjectionKind::*; + match self { + ArrayIndex { index, .. } => index.replace_decls(decl_mapping, handler, ctx), + _ => Ok(false), + } + } +} + +impl TypeCheckAnalysis for ProjectionKind { + fn type_check_analyze( + &self, + handler: &Handler, + ctx: &mut TypeCheckAnalysisContext, + ) -> Result<(), ErrorEmitted> { + use ProjectionKind::*; + match self { + ArrayIndex { index, .. } => index.type_check_analyze(handler, ctx), + _ => Ok(()), + } + } +} + +impl TypeCheckFinalization for ProjectionKind { + fn type_check_finalize( + &mut self, + handler: &Handler, + ctx: &mut TypeCheckFinalizationContext, + ) -> Result<(), ErrorEmitted> { + use ProjectionKind::*; + match self { + ArrayIndex { index, .. } => index.type_check_finalize(handler, ctx), + _ => Ok(()), + } + } +} + +impl UpdateConstantExpression for ProjectionKind { + fn update_constant_expression(&mut self, engines: &Engines, implementing_type: &TyDecl) { + use ProjectionKind::*; + #[allow(clippy::single_match)] + // To keep it consistent and same looking as the above implementations. + match self { + ArrayIndex { index, .. } => { + index.update_constant_expression(engines, implementing_type) + } + _ => (), + } + } +} + impl Spanned for ProjectionKind { fn span(&self) -> Span { match self { diff --git a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs index 0bf1e68b051..7b4ecfb2b58 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs @@ -22,7 +22,10 @@ use crate::{ decl_engine::*, language::{ parsed::*, - ty::{self, TyCodeBlock, TyImplItem, VariableMutability}, + ty::{ + self, GetDeclIdent, TyCodeBlock, TyDecl, TyExpression, TyExpressionVariant, TyImplItem, + TyReassignmentTarget, VariableMutability, + }, *, }, namespace::{IsExtendingExistingImpl, IsImplSelf}, @@ -157,7 +160,6 @@ impl ty::TyExpression { suffix: name.clone(), is_absolute: false, }; - if matches!( ctx.namespace() .resolve_call_path_typed( @@ -548,7 +550,7 @@ impl ty::TyExpression { Some(a) => { let err = handler.emit_err(CompileError::NotAVariable { name: name.clone(), - what_it_is: a.friendly_type_name(), + what_it_is: a.friendly_type_name_with_acronym(), span, }); ty::TyExpression::error(err, name.span(), engines) @@ -1639,7 +1641,7 @@ impl ty::TyExpression { _ => { return Err(handler.emit_err(CompileError::NotAnAbi { span: abi_name.span(), - actually_is: abi.friendly_type_name(), + actually_is: abi.friendly_type_name_with_acronym(), })); } }; @@ -1673,7 +1675,7 @@ impl ty::TyExpression { a => { return Err(handler.emit_err(CompileError::NotAnAbi { span: abi_name.span(), - actually_is: a.friendly_type_name(), + actually_is: a.friendly_type_name_with_acronym(), })); } }; @@ -1903,7 +1905,7 @@ impl ty::TyExpression { let index_te = { let type_info_u64 = TypeInfo::UnsignedInteger(IntegerBits::SixtyFour); let ctx = ctx - .with_help_text("") + .with_help_text("Array index must be of type \"u64\".") .with_type_annotation(type_engine.insert(engines, type_info_u64, None)); ty::TyExpression::type_check(handler, ctx, index)? @@ -1999,12 +2001,113 @@ impl ty::TyExpression { let mut ctx = ctx .with_type_annotation(type_engine.insert(engines, TypeInfo::Unknown, None)) .with_help_text(""); - // ensure that the lhs is a supported expression kind - match lhs { - ReassignmentTarget::VariableExpression(var) => { - let mut expr = var; - let mut names_vec = Vec::new(); - let (base_name, final_return_type) = loop { + + let (lhs, expected_rhs_type) = match lhs { + ReassignmentTarget::Deref(dereference_exp) => { + let internal_compiler_error = || { + Result::::Err(handler.emit_err(CompileError::Internal( + "Left-hand side of the reassignment must be dereferencing.", + dereference_exp.span.clone(), + ))) + }; + + let Expression { + kind: ExpressionKind::Deref(reference_exp), + .. + } = *dereference_exp + else { + return internal_compiler_error(); + }; + + let reference_exp_span = reference_exp.span(); + let deref_exp = Self::type_check_deref( + handler, + ctx.by_ref(), + reference_exp, + reference_exp_span.clone(), + )?; + + let TyExpression { + expression: TyExpressionVariant::Deref(reference_exp), + .. + } = &deref_exp + else { + return internal_compiler_error(); + }; + + let TypeInfo::Ref { + to_mutable_value, .. + } = *type_engine.get(reference_exp.return_type) + else { + return internal_compiler_error(); + }; + + if !to_mutable_value { + let (decl_reference_name, decl_reference_rhs, decl_reference_type) = + match &reference_exp.expression { + TyExpressionVariant::VariableExpression { name, .. } => { + let var_decl = ctx.namespace().resolve_symbol_typed( + handler, + engines, + name, + ctx.self_type(), + )?; + + let TyDecl::VariableDecl(var_decl) = var_decl else { + return Err(handler.emit_err(CompileError::Internal( + "Dereferenced expression must be a variable.", + reference_exp_span, + ))); + }; + + let reference_type = engines + .help_out( + type_engine.get_unaliased_type_id(var_decl.return_type), + ) + .to_string(); + + ( + Some(var_decl.name), + Some(var_decl.body.span), + reference_type, + ) + } + _ => ( + None, + None, + engines + .help_out( + type_engine + .get_unaliased_type_id(reference_exp.return_type), + ) + .to_string(), + ), + }; + + return Err( + handler.emit_err(CompileError::AssignmentViaNonMutableReference { + decl_reference_name, + decl_reference_rhs, + decl_reference_type, + span: reference_exp_span, + }), + ); + } + + let expected_rhs_type = deref_exp.return_type; + ( + TyReassignmentTarget::Deref(Box::new(deref_exp)), + expected_rhs_type, + ) + } + ReassignmentTarget::ElementAccess(path) => { + let lhs_span = path.span.clone(); + let mut expr = path; + let mut indices = Vec::new(); + // Loop through the LHS "backwards" starting from the outermost expression + // (the whole LHS) and moving towards the first identifier that must + // be a mutable variable. + let (base_name, base_type) = loop { match expr.kind { ExpressionKind::Variable(name) => { // check that the reassigned name exists @@ -2014,20 +2117,49 @@ impl ty::TyExpression { &name, ctx.self_type(), )?; - let variable_decl = unknown_decl.expect_variable(handler).cloned()?; - if !variable_decl.mutability.is_mutable() { - return Err(handler.emit_err( - CompileError::AssignmentToNonMutable { name, span }, - )); + + match unknown_decl { + TyDecl::VariableDecl(variable_decl) => { + if !variable_decl.mutability.is_mutable() { + return Err(handler.emit_err( + CompileError::AssignmentToNonMutableVariable { + decl_name: variable_decl.name.clone(), + lhs_span, + }, + )); + } + + break (name, variable_decl.body.return_type); + } + TyDecl::ConstantDecl(constant_decl) => { + let constant_decl = + engines.de().get_constant(&constant_decl.decl_id); + return Err(handler.emit_err( + CompileError::AssignmentToConstantOrConfigurable { + decl_name: constant_decl.name().clone(), + is_configurable: constant_decl.is_configurable, + lhs_span, + }, + )); + } + decl => { + return Err(handler.emit_err( + CompileError::DeclAssignmentTargetCannotBeAssignedTo { + decl_name: decl.get_decl_ident(), + decl_friendly_type_name: decl + .friendly_type_name_with_acronym(), + lhs_span, + }, + )); + } } - break (name, variable_decl.body.return_type); } ExpressionKind::Subfield(SubfieldExpression { prefix, field_to_access, .. }) => { - names_vec.push(ty::ProjectionKind::StructField { + indices.push(ty::ProjectionKind::StructField { name: field_to_access, }); expr = prefix; @@ -2038,17 +2170,25 @@ impl ty::TyExpression { index_span, .. }) => { - names_vec.push(ty::ProjectionKind::TupleField { index, index_span }); + indices.push(ty::ProjectionKind::TupleField { index, index_span }); expr = prefix; } ExpressionKind::ArrayIndex(ArrayIndexExpression { prefix, index }) => { - let ctx = ctx.by_ref().with_help_text(""); + let type_info_u64 = TypeInfo::UnsignedInteger(IntegerBits::SixtyFour); + let ctx = ctx + .by_ref() + .with_help_text("Array index must be of type \"u64\".") + .with_type_annotation(type_engine.insert( + engines, + type_info_u64, + None, + )); let typed_index = ty::TyExpression::type_check(handler, ctx, index.as_ref().clone()) .unwrap_or_else(|err| { ty::TyExpression::error(err, span.clone(), engines) }); - names_vec.push(ty::ProjectionKind::ArrayIndex { + indices.push(ty::ProjectionKind::ArrayIndex { index: Box::new(typed_index), index_span: index.span(), }); @@ -2061,7 +2201,8 @@ impl ty::TyExpression { } } }; - let names_vec = names_vec.into_iter().rev().collect::>(); + + let indices = indices.into_iter().rev().collect::>(); let (ty_of_field, _ty_of_parent) = ctx.namespace().module_id(engines).read(engines, |m| { m.current_items().find_subfield_type( @@ -2069,29 +2210,36 @@ impl ty::TyExpression { ctx.engines(), ctx.namespace(), &base_name, - &names_vec, + &indices, ) })?; - // type check the reassignment - let ctx = ctx.with_type_annotation(ty_of_field).with_help_text(""); - let rhs_span = rhs.span(); - let rhs = ty::TyExpression::type_check(handler, ctx, rhs) - .unwrap_or_else(|err| ty::TyExpression::error(err, rhs_span, engines)); - - Ok(ty::TyExpression { - expression: ty::TyExpressionVariant::Reassignment(Box::new( - ty::TyReassignment { - lhs_base_name: base_name, - lhs_type: final_return_type, - lhs_indices: names_vec, - rhs, - }, - )), - return_type: type_engine.insert(engines, TypeInfo::Tuple(Vec::new()), None), - span, - }) + + ( + TyReassignmentTarget::ElementAccess { + base_name, + base_type, + indices, + }, + ty_of_field, + ) } - } + }; + + let ctx = ctx + .with_type_annotation(expected_rhs_type) + .with_help_text(""); + let rhs_span = rhs.span(); + let rhs = ty::TyExpression::type_check(handler, ctx, rhs) + .unwrap_or_else(|err| ty::TyExpression::error(err, rhs_span, engines)); + + Ok(ty::TyExpression { + expression: ty::TyExpressionVariant::Reassignment(Box::new(ty::TyReassignment { + lhs, + rhs, + })), + return_type: type_engine.insert(engines, TypeInfo::Tuple(Vec::new()), None), + span, + }) } fn type_check_ref( @@ -2183,7 +2331,7 @@ impl ty::TyExpression { // Otherwise, we pass a new `TypeInfo::Unknown` as the annotation, to allow the `expr` // to be evaluated without any expectations. // Since `&mut T` coerces into `&T` we always go with a lesser expectation, `&T`. - // Thus, `to_mutable_value` is set to false. + // Thus, `to_mutable_vale` is set to false. let type_annotation = match &*type_engine.get(ctx.type_annotation()) { TypeInfo::Unknown => type_engine.insert(engines, TypeInfo::Unknown, None), _ => type_engine.insert( diff --git a/sway-core/src/semantic_analysis/type_check_analysis.rs b/sway-core/src/semantic_analysis/type_check_analysis.rs index e62a316726a..8180ccce2c8 100644 --- a/sway-core/src/semantic_analysis/type_check_analysis.rs +++ b/sway-core/src/semantic_analysis/type_check_analysis.rs @@ -1,5 +1,5 @@ //! This module handles the process of iterating through the typed AST and doing an analysis. -//! At the moment we compute an dependency graph between typed nodes. +//! At the moment, we compute a dependency graph between typed nodes. use std::collections::{HashMap, HashSet}; use std::fmt::Display; @@ -86,7 +86,7 @@ impl TypeCheckAnalysisContext<'_> { /// This functions either gets an existing node in the graph, or creates a new /// node corresponding to the passed function declaration node. /// The function will try to find a non-monomorphized declaration node id so that - /// future acesses always normalize to the same node id. + /// future accesses always normalize to the same node id. #[allow(clippy::map_entry)] pub fn get_or_create_node_for_fn_decl( &mut self, diff --git a/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs b/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs index ce90e82a841..7f5cf814781 100644 --- a/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs +++ b/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs @@ -13,6 +13,7 @@ use crate::{ use indexmap::IndexMap; use itertools::Itertools; use sway_ast::{ + assignable::ElementAccess, attribute::Annotated, expr::{LoopControlFlow, ReassignmentOp, ReassignmentOpVariant}, ty::TyTupleDescriptor, @@ -4270,90 +4271,129 @@ fn assignable_to_expression( ) -> Result { let span = assignable.span(); let expression = match assignable { - Assignable::Var(name) => Expression { - kind: ExpressionKind::Variable(name), - span, - }, - Assignable::Index { target, arg } => Expression { - kind: ExpressionKind::ArrayIndex(ArrayIndexExpression { - prefix: Box::new(assignable_to_expression( - context, handler, engines, *target, - )?), - index: Box::new(expr_to_expression( - context, - handler, - engines, - *arg.into_inner(), - )?), - }), + Assignable::ElementAccess(element_access) => { + element_access_to_expression(context, handler, engines, element_access, span)? + } + Assignable::Deref { + star_token: _, + expr, + } => Expression { + kind: ExpressionKind::Deref(Box::new(expr_to_expression( + context, handler, engines, *expr, + )?)), span, }, - Assignable::FieldProjection { target, name, .. } => { - let mut idents = vec![&name]; - let mut base = &*target; - let (storage_access_field_names_opt, storage_name_opt) = loop { - match base { - Assignable::FieldProjection { target, name, .. } => { - idents.push(name); - base = target; + }; + + return Ok(expression); + + fn element_access_to_expression( + context: &mut Context, + handler: &Handler, + engines: &Engines, + element_access: ElementAccess, + span: Span, + ) -> Result { + let expression = match element_access { + ElementAccess::Var(name) => Expression { + kind: ExpressionKind::Variable(name), + span, + }, + ElementAccess::Index { target, arg } => Expression { + kind: ExpressionKind::ArrayIndex(ArrayIndexExpression { + prefix: Box::new(element_access_to_expression( + context, + handler, + engines, + *target, + span.clone(), + )?), + index: Box::new(expr_to_expression( + context, + handler, + engines, + *arg.into_inner(), + )?), + }), + span, + }, + ElementAccess::FieldProjection { target, name, .. } => { + let mut idents = vec![&name]; + let mut base = &*target; + let (storage_access_field_names_opt, storage_name_opt) = loop { + match base { + ElementAccess::FieldProjection { target, name, .. } => { + idents.push(name); + base = target; + } + ElementAccess::Var(name) => { + if name.as_str() == "storage" { + break (Some(idents), Some(name.clone())); + } + break (None, None); + } + _ => break (None, None), } - Assignable::Var(name) => { - if name.as_str() == "storage" { - break (Some(idents), Some(name.clone())); + }; + match (storage_access_field_names_opt, storage_name_opt) { + (Some(field_names), Some(storage_name)) => { + let field_names = field_names.into_iter().rev().cloned().collect(); + Expression { + kind: ExpressionKind::StorageAccess(StorageAccessExpression { + field_names, + storage_keyword_span: storage_name.span(), + }), + span, } - break (None, None); } - _ => break (None, None), - } - }; - match (storage_access_field_names_opt, storage_name_opt) { - (Some(field_names), Some(storage_name)) => { - let field_names = field_names.into_iter().rev().cloned().collect(); - Expression { - kind: ExpressionKind::StorageAccess(StorageAccessExpression { - field_names, - storage_keyword_span: storage_name.span(), + _ => Expression { + kind: ExpressionKind::Subfield(SubfieldExpression { + prefix: Box::new(element_access_to_expression( + context, + handler, + engines, + *target, + span.clone(), + )?), + field_to_access: name, }), span, - } + }, } - _ => Expression { - kind: ExpressionKind::Subfield(SubfieldExpression { - prefix: Box::new(assignable_to_expression( - context, handler, engines, *target, + } + ElementAccess::TupleFieldProjection { + target, + field, + field_span, + .. + } => { + let index = match usize::try_from(field) { + Ok(index) => index, + Err(..) => { + let error = + ConvertParseTreeError::TupleIndexOutOfRange { span: field_span }; + return Err(handler.emit_err(error.into())); + } + }; + Expression { + kind: ExpressionKind::TupleIndex(TupleIndexExpression { + prefix: Box::new(element_access_to_expression( + context, + handler, + engines, + *target, + span.clone(), )?), - field_to_access: name, + index, + index_span: field_span, }), span, - }, - } - } - Assignable::TupleFieldProjection { - target, - field, - field_span, - .. - } => { - let index = match usize::try_from(field) { - Ok(index) => index, - Err(..) => { - let error = ConvertParseTreeError::TupleIndexOutOfRange { span: field_span }; - return Err(handler.emit_err(error.into())); } - }; - Expression { - kind: ExpressionKind::TupleIndex(TupleIndexExpression { - prefix: Box::new(assignable_to_expression( - context, handler, engines, *target, - )?), - index, - index_span: field_span, - }), - span, } - } - }; - Ok(expression) + }; + + Ok(expression) + } } fn assignable_to_reassignment_target( @@ -4362,22 +4402,11 @@ fn assignable_to_reassignment_target( engines: &Engines, assignable: Assignable, ) -> Result { - let mut idents = Vec::new(); - let mut base = &assignable; - - loop { - match base { - Assignable::FieldProjection { target, name, .. } => { - idents.push(name); - base = target; - } - Assignable::Var(_) => break, - Assignable::Index { .. } => break, - Assignable::TupleFieldProjection { .. } => break, - } - } let expression = assignable_to_expression(context, handler, engines, assignable)?; - Ok(ReassignmentTarget::VariableExpression(Box::new(expression))) + Ok(match expression.kind { + ExpressionKind::Deref(_) => ReassignmentTarget::Deref(Box::new(expression)), + _ => ReassignmentTarget::ElementAccess(Box::new(expression)), + }) } fn generic_args_to_type_arguments( diff --git a/sway-core/src/type_system/info.rs b/sway-core/src/type_system/info.rs index aab9c217b72..aa7bcbca088 100644 --- a/sway-core/src/type_system/info.rs +++ b/sway-core/src/type_system/info.rs @@ -1138,6 +1138,7 @@ impl TypeInfo { | TypeInfo::UnsignedInteger(IntegerBits::SixtyFour) | TypeInfo::RawUntypedPtr | TypeInfo::Numeric // TODO-IG: Should Ptr and Ref also be a copy type? + | TypeInfo::Never ) || self.is_unit() } diff --git a/sway-core/src/type_system/substitute/subst_types.rs b/sway-core/src/type_system/substitute/subst_types.rs index 16f1bbaffee..ba7716590a8 100644 --- a/sway-core/src/type_system/substitute/subst_types.rs +++ b/sway-core/src/type_system/substitute/subst_types.rs @@ -66,11 +66,11 @@ impl SubstTypes for Vec { #[macro_export] macro_rules! has_changes { - ($($stmts:expr);* ;) => {{ - let mut has_change = $crate::type_system::HasChanges::No; + ($($stmt:expr);* ;) => {{ + let mut has_changes = $crate::type_system::HasChanges::No; $( - has_change = $stmts | has_change; + has_changes = $stmt | has_changes; )* - has_change + has_changes }}; } diff --git a/sway-error/Cargo.toml b/sway-error/Cargo.toml index cb15d315596..8cc2acc06ef 100644 --- a/sway-error/Cargo.toml +++ b/sway-error/Cargo.toml @@ -10,6 +10,7 @@ repository.workspace = true [dependencies] either = "1.9.0" +in_definite = "1.0.0" num-traits = "0.2.14" smallvec = "1.7" sway-types = { version = "0.56.0", path = "../sway-types" } diff --git a/sway-error/src/error.rs b/sway-error/src/error.rs index 26505b3e8bc..2bca8c40661 100644 --- a/sway-error/src/error.rs +++ b/sway-error/src/error.rs @@ -2,7 +2,7 @@ use crate::convert_parse_tree_error::ConvertParseTreeError; use crate::diagnostic::{Code, Diagnostic, Hint, Issue, Reason, ToDiagnostic}; use crate::formatting::*; use crate::lex_error::LexError; -use crate::parser_error::ParseError; +use crate::parser_error::{ParseError, ParseErrorKind}; use crate::type_error::TypeError; use core::fmt; @@ -121,8 +121,67 @@ pub enum CompileError { duplicate: Span, duplicate_is_struct_field: bool, }, - #[error("Assignment to immutable variable. Variable {name} is not declared as mutable.")] - AssignmentToNonMutable { name: Ident, span: Span }, + #[error( + "Assignment to an immutable variable. Variable \"{decl_name} is not declared as mutable." + )] + AssignmentToNonMutableVariable { + /// Variable name pointing to the name in the variable declaration. + decl_name: Ident, + /// The complete left-hand side of the assignment. + lhs_span: Span, + }, + #[error( + "Assignment to a {}. {} cannot be assigned to.", + if *is_configurable { + "configurable" + } else { + "constant" + }, + if *is_configurable { + "Configurables" + } else { + "Constants" + } + )] + AssignmentToConstantOrConfigurable { + /// Constant or configurable name pointing to the name in the constant declaration. + decl_name: Ident, + is_configurable: bool, + /// The complete left-hand side of the assignment. + lhs_span: Span, + }, + #[error( + "This assignment target cannot be assigned to, because {} is {}{decl_friendly_type_name} and not a mutable variable.", + if let Some(decl_name) = decl_name { + format!("\"{decl_name}\"") + } else { + "this".to_string() + }, + a_or_an(decl_friendly_type_name) + )] + DeclAssignmentTargetCannotBeAssignedTo { + /// Name of the declared variant, pointing to the name in the declaration. + decl_name: Option, + /// Friendly name of the type of the declaration. E.g., "function", or "struct". + decl_friendly_type_name: &'static str, + /// The complete left-hand side of the assignment. + lhs_span: Span, + }, + #[error("This reference is not a reference to a mutable value (`&mut`).")] + AssignmentViaNonMutableReference { + /// Name of the reference, if the left-hand side of the assignment is a reference variable, + /// pointing to the name in the reference variable declaration. + /// + /// `None` if the assignment LHS is an arbitrary expression and not a variable. + decl_reference_name: Option, + /// [Span] of the right-hand side of the reference variable definition, + /// if the left-hand side of the assignment is a reference variable. + decl_reference_rhs: Option, + /// The type of the reference, if the left-hand side of the assignment is a reference variable, + /// expected to start with `&`. + decl_reference_type: String, + span: Span, + }, #[error( "Cannot call method \"{method_name}\" on variable \"{variable_name}\" because \ \"{variable_name}\" is not declared as mutable." @@ -140,7 +199,7 @@ pub enum CompileError { ImmutableArgumentToMutableParameter { span: Span }, #[error("ref mut or mut parameter is not allowed for contract ABI function.")] RefMutableNotAllowedInContractAbi { param_name: Ident, span: Span }, - #[error("Reference to mutable value cannot reference a constant.")] + #[error("Reference to a mutable value cannot reference a constant.")] RefMutCannotReferenceConstant { /// Constant, as accessed in code. E.g.: /// - `MY_CONST` @@ -149,7 +208,7 @@ pub enum CompileError { constant: String, span: Span, }, - #[error("Reference to mutable value cannot reference an immutable variable.")] + #[error("Reference to a mutable value cannot reference an immutable variable.")] RefMutCannotReferenceImmutableVariable { /// Variable name pointing to the name in the variable declaration. decl_name: Ident, @@ -923,7 +982,10 @@ impl Spanned for CompileError { MultipleDefinitionsOfType { span, .. } => span.clone(), MultipleDefinitionsOfMatchArmVariable { duplicate, .. } => duplicate.clone(), MultipleDefinitionsOfFallbackFunction { span, .. } => span.clone(), - AssignmentToNonMutable { span, .. } => span.clone(), + AssignmentToNonMutableVariable { lhs_span, .. } => lhs_span.clone(), + AssignmentToConstantOrConfigurable { lhs_span, .. } => lhs_span.clone(), + DeclAssignmentTargetCannotBeAssignedTo { lhs_span, .. } => lhs_span.clone(), + AssignmentViaNonMutableReference { span, .. } => span.clone(), MutableParameterNotSupported { span, .. } => span.clone(), ImmutableArgumentToMutableParameter { span } => span.clone(), RefMutableNotAllowedInContractAbi { span, .. } => span.clone(), @@ -1944,7 +2006,7 @@ impl ToDiagnostic for CompileError { Hint::info( source_engine, decl_name.span(), - format!("\"{decl_name}\" is declared here as immutable.") + format!("Variable \"{decl_name}\" is declared here as immutable.") ), ], help: vec![ @@ -1976,6 +2038,187 @@ impl ToDiagnostic for CompileError { "This property is called \"trait coherence\".".to_string(), ], }, + AssignmentToNonMutableVariable { lhs_span, decl_name } => Diagnostic { + reason: Some(Reason::new(code(1), "Immutable variables cannot be assigned to".to_string())), + issue: Issue::error( + source_engine, + lhs_span.clone(), + // "x" cannot be assigned to, because it is an immutable variable. + // or + // This expression cannot be assigned to, because "x" is an immutable variable. + format!("{} cannot be assigned to, because {} is an immutable variable.", + if decl_name.as_str() == lhs_span.as_str() { // We have just a single variable in the expression. + format!("\"{decl_name}\"") + } else { + "This expression".to_string() + }, + if decl_name.as_str() == lhs_span.as_str() { + "it".to_string() + } else { + format!("\"{decl_name}\"") + } + ) + ), + hints: vec![ + Hint::info( + source_engine, + decl_name.span(), + format!("Variable \"{decl_name}\" is declared here as immutable.") + ), + ], + help: vec![ + // TODO-IG: Once desugaring information becomes available, do not show this suggestion if declaring variable as mutable is not possible. + format!("Consider declaring \"{decl_name}\" as mutable."), + ], + }, + AssignmentToConstantOrConfigurable { lhs_span, is_configurable, decl_name } => Diagnostic { + reason: Some(Reason::new(code(1), format!("{} cannot be assigned to", + if *is_configurable { + "Configurables" + } else { + "Constants" + } + ))), + issue: Issue::error( + source_engine, + lhs_span.clone(), + // "x" cannot be assigned to, because it is a constant/configurable. + // or + // This expression cannot be assigned to, because "x" is a constant/configurable. + format!("{} cannot be assigned to, because {} is a {}.", + if decl_name.as_str() == lhs_span.as_str() { // We have just the constant in the expression. + format!("\"{decl_name}\"") + } else { + "This expression".to_string() + }, + if decl_name.as_str() == lhs_span.as_str() { + "it".to_string() + } else { + format!("\"{decl_name}\"") + }, + if *is_configurable { + "configurable" + } else { + "constant" + } + ) + ), + hints: vec![ + Hint::info( + source_engine, + decl_name.span(), + format!("{} \"{decl_name}\" is declared here.", + if *is_configurable { + "Configurable" + } else { + "Constant" + } + ) + ), + ], + help: vec![], + }, + DeclAssignmentTargetCannotBeAssignedTo { decl_name, decl_friendly_type_name, lhs_span } => Diagnostic { + reason: Some(Reason::new(code(1), "Assignment target cannot be assigned to".to_string())), + issue: Issue::error( + source_engine, + lhs_span.clone(), + // "x" cannot be assigned to, because it is a trait/function/ etc and not a mutable variable. + // or + // This cannot be assigned to, because "x" is a trait/function/ etc and not a mutable variable. + format!("{} cannot be assigned to, because {} is {}{decl_friendly_type_name} and not a mutable variable.", + match decl_name { + Some(decl_name) if decl_name.as_str() == lhs_span.as_str() => // We have just the decl name in the expression. + format!("\"{decl_name}\""), + _ => "This".to_string(), + }, + match decl_name { + Some(decl_name) if decl_name.as_str() == lhs_span.as_str() => + "it".to_string(), + Some(decl_name) => format!("\"{}\"", decl_name.as_str()), + _ => "it".to_string(), + }, + a_or_an(decl_friendly_type_name) + ) + ), + hints: vec![ + match decl_name { + Some(decl_name) => Hint::info( + source_engine, + decl_name.span(), + format!("{} \"{decl_name}\" is declared here.", ascii_sentence_case(&decl_friendly_type_name.to_string())) + ), + _ => Hint::none(), + } + ], + help: vec![], + }, + AssignmentViaNonMutableReference { decl_reference_name, decl_reference_rhs, decl_reference_type, span } => Diagnostic { + reason: Some(Reason::new(code(1), "Reference is not a reference to a mutable value (`&mut`)".to_string())), + issue: Issue::error( + source_engine, + span.clone(), + // This reference expression is not a reference to a mutable value (`&mut`). + // or + // Reference "ref_xyz" is not a reference to a mutable value (`&mut`). + format!("{} is not a reference to a mutable value (`&mut`).", + match decl_reference_name { + Some(decl_reference_name) => format!("Reference \"{decl_reference_name}\""), + _ => "This reference expression".to_string(), + } + ) + ), + hints: vec![ + match decl_reference_name { + Some(decl_reference_name) => Hint::info( + source_engine, + decl_reference_name.span(), + format!("Reference \"{decl_reference_name}\" is declared here as a reference to immutable value.") + ), + _ => Hint::none(), + }, + match decl_reference_rhs { + Some(decl_reference_rhs) => Hint::info( + source_engine, + decl_reference_rhs.clone(), + format!("This expression has type \"{decl_reference_type}\" instead of \"&mut {}\".", + &decl_reference_type[1..] + ) + ), + _ => Hint::info( + source_engine, + span.clone(), + format!("It has type \"{decl_reference_type}\" instead of \"&mut {}\".", + &decl_reference_type[1..] + ) + ), + }, + match decl_reference_rhs { + Some(decl_reference_rhs) if decl_reference_rhs.as_str().starts_with('&') => Hint::help( + source_engine, + decl_reference_rhs.clone(), + format!("Consider taking here a reference to a mutable value: `&mut {}`.", + first_line(decl_reference_rhs.as_str()[1..].trim(), true) + ) + ), + _ => Hint::none(), + }, + ], + help: vec![ + format!("{} dereferenced in assignment targets must {} references to mutable values (`&mut`).", + if decl_reference_name.is_some() { + "References" + } else { + "Reference expressions" + }, + if decl_reference_name.is_some() { + "be" + } else { + "result in" + } + ), + ], + }, Unimplemented { feature, help, span } => Diagnostic { reason: Some(Reason::new(code(1), "Used feature is currently not implemented".to_string())), issue: Issue::error( @@ -2089,6 +2332,79 @@ impl ToDiagnostic for CompileError { format!("E.g., `{module_path}::SOME_CONSTANT` or `{module_path}::some_function()`."), ] }, + Parse { error } => { + match &error.kind { + ParseErrorKind::UnassignableExpression { erroneous_expression_kind, erroneous_expression_span } => Diagnostic { + reason: Some(Reason::new(code(1), "Expression cannot be assigned to".to_string())), + // A bit of a special handling for parentheses, because they are the only + // expression kind whose friendly name is in plural. Having it in singular + // or without this simple special handling gives very odd sounding sentences. + // Therefore, just a bit of a special handling. + issue: Issue::error( + source_engine, + error.span.clone(), + format!("This expression cannot be assigned to, because it {} {}{}.", + if &error.span == erroneous_expression_span { // If the whole expression is erroneous. + "is" + } else { + "contains" + }, + if *erroneous_expression_kind == "parentheses" { + "" + } else { + a_or_an(erroneous_expression_kind) + }, + erroneous_expression_kind + ) + ), + hints: vec![ + if &error.span != erroneous_expression_span { + Hint::info( + source_engine, + erroneous_expression_span.clone(), + format!("{} the contained {erroneous_expression_kind}.", + if *erroneous_expression_kind == "parentheses" { + "These are" + } else { + "This is" + } + ) + ) + } else { + Hint::none() + }, + ], + help: vec![ + format!("{} cannot be {}an assignment target.", + ascii_sentence_case(&erroneous_expression_kind.to_string()), + if &error.span == erroneous_expression_span { + "" + } else { + "a part of " + } + ), + Diagnostic::help_empty_line(), + "In Sway, assignment targets must be one of the following:".to_string(), + format!("{}- Expressions starting with a mutable variable, optionally having", Indent::Single), + format!("{} array or tuple element accesses, struct field accesses,", Indent::Single), + format!("{} or arbitrary combinations of those.", Indent::Single), + format!("{} E.g., `mut_var` or `mut_struct.field` or `mut_array[x + y].field.1`.", Indent::Single), + Diagnostic::help_empty_line(), + format!("{}- Dereferencing of an arbitrary expression that results", Indent::Single), + format!("{} in a reference to a mutable value.", Indent::Single), + format!("{} E.g., `*ref_to_mutable_value` or `*max_mut(&mut x, &mut y)`.", Indent::Single), + ] + }, + _ => Diagnostic { + // TODO: Temporary we use self here to achieve backward compatibility. + // In general, self must not be used and will not be used once we + // switch to our own #[error] macro. All the values for the formatting + // of a diagnostic must come from the enum variant parameters. + issue: Issue::error(source_engine, self.span(), format!("{}", self)), + ..Default::default() + }, + } + }, _ => Diagnostic { // TODO: Temporary we use self here to achieve backward compatibility. // In general, self must not be used and will not be used once we diff --git a/sway-error/src/formatting.rs b/sway-error/src/formatting.rs index dbfb59f6930..84adf117f4f 100644 --- a/sway-error/src/formatting.rs +++ b/sway-error/src/formatting.rs @@ -2,6 +2,7 @@ //! diagnostic messages. use std::{ + borrow::Cow, cmp, fmt::{self, Display}, }; @@ -230,9 +231,62 @@ pub(crate) fn singular_plural<'a>(count: usize, singular: &'a str, plural: &'a s /// SomeName -> SomeName /// std::ops::Eq -> Eq /// some_lib::Struct -> Struct -pub(crate) fn call_path_suffix_with_args(call_path: &str) -> String { +pub(crate) fn call_path_suffix_with_args(call_path: &String) -> Cow { match call_path.rfind(':') { - Some(index) if index < call_path.len() - 1 => call_path.split_at(index + 1).1.to_string(), - _ => call_path.to_string(), + Some(index) if index < call_path.len() - 1 => { + Cow::Owned(call_path.split_at(index + 1).1.to_string()) + } + _ => Cow::Borrowed(call_path), + } +} + +/// Returns indefinite article "a" or "an" that corresponds to the `word`, +/// or an empty string if the indefinite article do not fit to the word. +/// +/// Note that the function does not recognize plurals and assumes that the +/// `word` is in singular. +/// +/// If an article is returned, it is followed by a space, e.g. "a ". +pub(crate) fn a_or_an(word: &'static str) -> &'static str { + let is_a = in_definite::is_an(word); + match is_a { + in_definite::Is::An => "an ", + in_definite::Is::A => "a ", + in_definite::Is::None => "", + } +} + +/// Returns `text` with the first character turned into ASCII uppercase. +pub(crate) fn ascii_sentence_case(text: &String) -> Cow { + if text.is_empty() || text.chars().next().unwrap().is_uppercase() { + Cow::Borrowed(text) + } else { + let mut result = text.clone(); + result[0..1].make_ascii_uppercase(); + Cow::Owned(result.to_owned()) + } +} + +/// Returns the first line in `text`, up to the first `\n` if the `text` contains +/// multiple lines, and optionally adds ellipses "..." to the end of the line +/// if `with_ellipses` is true. +/// +/// If the `text` is a single-line string, returns the original `text`. +/// +/// Suitable for showing just the first line of a piece of code. +/// E.g., if `text` is: +/// if x { +/// 0 +/// } else { +/// 1 +/// } +/// the returned value, with ellipses, will be: +/// if x {... +pub(crate) fn first_line(text: &str, with_ellipses: bool) -> Cow { + if !text.contains('\n') { + Cow::Borrowed(text) + } else { + let index_of_new_line = text.find('\n').unwrap(); + Cow::Owned(text[..index_of_new_line].to_string() + if with_ellipses { "..." } else { "" }) } } diff --git a/sway-error/src/parser_error.rs b/sway-error/src/parser_error.rs index adf5d78fee2..170964db9c6 100644 --- a/sway-error/src/parser_error.rs +++ b/sway-error/src/parser_error.rs @@ -17,7 +17,17 @@ pub enum ParseErrorKind { #[error("Unexpected token in statement.")] UnexpectedTokenInStatement, #[error("This expression cannot be assigned to.")] - UnassignableExpression, + UnassignableExpression { + /// The friendly name of the kind of the expression + /// that makes the overall expression unassignable. + /// E.g., "function call", or "struct instantiation". + erroneous_expression_kind: &'static str, + /// [Span] that points to either the whole left-hand + /// side of the reassignment, or to a [Span] of an + /// erroneous nested expression, if only a part of + /// the assignment target expression is erroneous. + erroneous_expression_span: Span, + }, #[error("Unexpected token after array index.")] UnexpectedTokenAfterArrayIndex, #[error("Invalid literal to use as a field name.")] diff --git a/sway-ir/src/analysis/memory_utils.rs b/sway-ir/src/analysis/memory_utils.rs index 0b3a620e3f5..9304b29722d 100644 --- a/sway-ir/src/analysis/memory_utils.rs +++ b/sway-ir/src/analysis/memory_utils.rs @@ -1,5 +1,5 @@ //! An analysis to compute symbols that escape out from a function. -//! This could be into another function, or via ptr_to_int etc. +//! This could be into another function, or via `ptr_to_int` etc. //! Any transformations involving such symbols are unsafe. use indexmap::IndexSet; @@ -45,16 +45,119 @@ impl Symbol { } } -// A value may (indirectly) refer to one or more symbols. -pub fn get_symbols(context: &Context, val: Value) -> FxIndexSet { - let mut visited = FxHashSet::default(); - let mut symbols = IndexSet::default(); +/// Get [Symbol]s, both [Symbol::Local]s and [Symbol::Arg]s, reachable +/// from the `val` via chain of [InstOp::GetElemPtr] (GEP) instructions. +/// A `val` can, via GEP instructions, refer indirectly to none, or one +/// or more symbols. +/// +/// Note that this function does not return [Symbol]s potentially reachable +/// via referencing (`&`), dereferencing (`*`), and raw pointers (`__addr_of`) +/// and is thus suitable for all IR analysis and manipulation that deals +/// strictly with GEP access. +/// +/// To acquire all [Symbol]s reachable from the `val`, use [get_referred_symbols] instead. +pub fn get_gep_referred_symbols(context: &Context, val: Value) -> FxIndexSet { + match get_symbols(context, val, true) { + ReferredSymbols::Complete(symbols) => symbols, + _ => unreachable!( + "In the case of GEP access, the set of returned symbols is always complete." + ), + } +} + +/// Provides [Symbol]s, both [Symbol::Local]s and [Symbol::Arg]s, reachable +/// from a certain [Value] via chain of [InstOp::GetElemPtr] (GEP) instructions +/// or via [InstOp::IntToPtr] and [InstOp::PtrToInt] instruction patterns +/// specific to references, both referencing (`&`) and dereferencing (`*`), +/// and raw pointers, via `__addr_of`. +pub enum ReferredSymbols { + /// Guarantees that all [Symbol]s reachable from the particular [Value] + /// are collected, thus, that there are no escapes or pointer accesses + /// in the scope that _might_ result in symbols indirectly related to + /// the [Value] but not reachable only via GEP, or references, or + /// raw pointers only. + Complete(FxIndexSet), + /// Denotes that there _might_ be [Symbol]s out of returned ones that + /// are related to the particular [Value], but not reachable only via GEP, + /// or references, or raw pointers. + Incomplete(FxIndexSet), +} + +impl ReferredSymbols { + // TODO: Check all the usages of this method and replace it with the + // checked access to either complete or incomplete symbols. + // This is a temporary convenience method until + // we decide case by case how to deal with incomplete set of symbols. + // See: https://github.com/FuelLabs/sway/issues/5924 + pub fn any(self) -> FxIndexSet { + match self { + ReferredSymbols::Complete(symbols) | ReferredSymbols::Incomplete(symbols) => symbols, + } + } +} + +/// Get [Symbol]s, both [Symbol::Local]s and [Symbol::Arg]s, reachable +/// from the `val` via chain of [InstOp::GetElemPtr] (GEP) instructions +/// or via [InstOp::IntToPtr] and [InstOp::PtrToInt] instruction patterns +/// specific to references, both referencing (`&`) and dereferencing (`*`), +/// and raw pointers, via `__addr_of`. +/// A `val` can, via these instructions, refer indirectly to none, or one +/// or more symbols. +/// +/// Note that *this function does not perform any escape analysis*. E.g., if a +/// local symbol gets passed by `raw_ptr` or `&T` to a function and returned +/// back from the function via the same `raw_ptr` or `&T` the value returned +/// from the function will not be tracked back to the original symbol and the +/// symbol will not be collected as referred. +/// +/// This means that, even if the result contains [Symbol]s, it _might_ be that +/// there are still other [Symbol]s in scope related to the `val`. E.g., in case +/// of branching, where the first branch directly returns `& local_var_a` +/// and the second branch, indirectly over a function call as explained above, +/// `& local_var_b`, only the `local_var_a` will be returned as a result. +/// +/// Therefore, the function returns the [ReferredSymbols] enum to denote +/// if the returned set of symbols is guaranteed to be complete, or if it is +/// incomplete. +pub fn get_referred_symbols(context: &Context, val: Value) -> ReferredSymbols { + get_symbols(context, val, false) +} + +/// Get [Symbol]s, both [Symbol::Local]s and [Symbol::Arg]s, reachable +/// from the `val`. +/// +/// If `gep_only` is `true` only the [Symbol]s reachable via GEP instructions +/// are returned. Otherwise, the result also contains [Symbol]s reachable +/// via referencing (`&`) and dereferencing (`*`). +fn get_symbols(context: &Context, val: Value, gep_only: bool) -> ReferredSymbols { fn get_symbols_rec( context: &Context, symbols: &mut FxIndexSet, visited: &mut FxHashSet, val: Value, + gep_only: bool, + is_complete: &mut bool, ) { + fn get_argument_symbols( + context: &Context, + symbols: &mut FxIndexSet, + visited: &mut FxHashSet, + arg: BlockArgument, + gep_only: bool, + is_complete: &mut bool, + ) { + if arg.block.get_label(context) == "entry" { + symbols.insert(Symbol::Arg(arg)); + } else { + arg.block + .pred_iter(context) + .map(|pred| arg.get_val_coming_from(context, pred).unwrap()) + .for_each(|v| { + get_symbols_rec(context, symbols, visited, v, gep_only, is_complete) + }) + } + } + if visited.contains(&val) { return; } @@ -69,26 +172,96 @@ pub fn get_symbols(context: &Context, val: Value) -> FxIndexSet { ValueDatum::Instruction(Instruction { op: InstOp::GetElemPtr { base, .. }, .. - }) => get_symbols_rec(context, symbols, visited, base), - ValueDatum::Argument(b) => { - if b.block.get_label(context) == "entry" { - symbols.insert(Symbol::Arg(b)); - } else { - b.block - .pred_iter(context) - .map(|pred| b.get_val_coming_from(context, pred).unwrap()) - .for_each(|v| get_symbols_rec(context, symbols, visited, v)) + }) => get_symbols_rec(context, symbols, visited, base, gep_only, is_complete), + // The below chain of instructions are specific to + // referencing, dereferencing, and `__addr_of` and do not occur + // in other kinds of IR generation. E.g., `IntToPtr` could be emitted when + // GTF intrinsic is compiled, but do not produce + // the below patterns which are specific to references and raw pointers. + ValueDatum::Instruction(Instruction { + op: InstOp::IntToPtr(int_value, _), + .. + }) if !gep_only => { + // Ignore this path if only GEP chain is requested. + match context.values[int_value.0].value { + ValueDatum::Instruction(Instruction { + op: InstOp::Load(loaded_from), + .. + }) => get_symbols_rec( + context, + symbols, + visited, + loaded_from, + gep_only, + is_complete, + ), + ValueDatum::Instruction(Instruction { + op: InstOp::PtrToInt(ptr_value, _), + .. + }) => { + get_symbols_rec(context, symbols, visited, ptr_value, gep_only, is_complete) + } + ValueDatum::Argument(arg) => { + get_argument_symbols(context, symbols, visited, arg, gep_only, is_complete) + } + // In other cases, e.g., getting the integer address from an unsafe pointer + // arithmetic, or as a function result, etc. we bail out and mark the + // collection as not being guaranteed to be a complete set of all referred symbols. + _ => { + *is_complete = false; + } } } + // In case of converting pointer to int for references and raw pointers, + // we consider the pointed symbols to be reachable from the `ptr_value`. + ValueDatum::Instruction(Instruction { + op: InstOp::PtrToInt(ptr_value, _), + .. + }) if !gep_only => { + get_symbols_rec(context, symbols, visited, ptr_value, gep_only, is_complete) + } + ValueDatum::Argument(arg) => { + get_argument_symbols(context, symbols, visited, arg, gep_only, is_complete) + } + _ if !gep_only => { + // Same as above, we cannot track the value up the chain and cannot guarantee + // that the value is not coming from some of the symbols. + *is_complete = false; + } + // In the case of GEP only access, the returned set is always complete. _ => (), } } - get_symbols_rec(context, &mut symbols, &mut visited, val); - symbols + + let mut visited = FxHashSet::default(); + let mut symbols = IndexSet::default(); + let mut is_complete = true; + + get_symbols_rec( + context, + &mut symbols, + &mut visited, + val, + gep_only, + &mut is_complete, + ); + + if is_complete { + ReferredSymbols::Complete(symbols) + } else { + ReferredSymbols::Incomplete(symbols) + } +} + +pub fn get_gep_symbol(context: &Context, val: Value) -> Option { + let syms = get_gep_referred_symbols(context, val); + (syms.len() == 1) + .then(|| syms.iter().next().cloned()) + .flatten() } -pub fn get_symbol(context: &Context, val: Value) -> Option { - let syms = get_symbols(context, val); +pub fn get_referred_symbol(context: &Context, val: Value) -> Option { + let syms = get_referred_symbols(context, val).any(); (syms.len() == 1) .then(|| syms.iter().next().cloned()) .flatten() @@ -109,9 +282,12 @@ pub fn compute_escaped_symbols(context: &Context, function: &Function) -> Escape let mut result = FxHashSet::default(); let add_from_val = |result: &mut FxHashSet, val: &Value| { - get_symbols(context, *val).iter().for_each(|s| { - result.insert(*s); - }); + get_referred_symbols(context, *val) + .any() + .iter() + .for_each(|s| { + result.insert(*s); + }); }; for (_block, inst) in function.instruction_iter(context) { @@ -218,7 +394,7 @@ pub fn get_loaded_ptr_values(context: &Context, val: Value) -> Vec { pub fn get_loaded_symbols(context: &Context, val: Value) -> FxIndexSet { let mut res = IndexSet::default(); for val in get_loaded_ptr_values(context, val) { - for sym in get_symbols(context, val) { + for sym in get_referred_symbols(context, val).any() { res.insert(sym); } } @@ -274,7 +450,7 @@ pub fn get_stored_ptr_values(context: &Context, val: Value) -> Vec { pub fn get_stored_symbols(context: &Context, val: Value) -> FxIndexSet { let mut res = IndexSet::default(); for val in get_stored_ptr_values(context, val) { - for sym in get_symbols(context, val) { + for sym in get_referred_symbols(context, val).any() { res.insert(sym); } } @@ -309,7 +485,7 @@ pub fn combine_indices(context: &Context, val: Value) -> Option> { /// Given a memory pointer instruction, compute the offset of indexed element, /// for each symbol that this may alias to. pub fn get_memory_offsets(context: &Context, val: Value) -> FxIndexMap { - get_symbols(context, val) + get_gep_referred_symbols(context, val) .into_iter() .filter_map(|sym| { let offset = sym diff --git a/sway-ir/src/block.rs b/sway-ir/src/block.rs index a92b60a5c45..2c47022ee59 100644 --- a/sway-ir/src/block.rs +++ b/sway-ir/src/block.rs @@ -51,7 +51,7 @@ pub struct BlockArgument { } impl BlockArgument { - /// Get the actual parameter passed to this block argument from `from_block` + /// Get the actual parameter passed to this block argument from `from_block`. pub fn get_val_coming_from(&self, context: &Context, from_block: &Block) -> Option { for BranchToWithArgs { block: succ_block, diff --git a/sway-ir/src/optimize/constants.rs b/sway-ir/src/optimize/constants.rs index 52d2bc93cce..646560633db 100644 --- a/sway-ir/src/optimize/constants.rs +++ b/sway-ir/src/optimize/constants.rs @@ -25,7 +25,7 @@ pub fn create_const_combine_pass() -> Pass { } } -/// Find constant expressions which can be reduced to fewer opterations. +/// Find constant expressions which can be reduced to fewer operations. pub fn combine_constants( context: &mut Context, _: &AnalysisResults, diff --git a/sway-ir/src/optimize/dce.rs b/sway-ir/src/optimize/dce.rs index 7fe0ce7f1fc..49b411c3f2c 100644 --- a/sway-ir/src/optimize/dce.rs +++ b/sway-ir/src/optimize/dce.rs @@ -8,9 +8,9 @@ use rustc_hash::FxHashSet; use crate::{ - get_symbols, memory_utils, AnalysisResults, Context, EscapedSymbols, Function, InstOp, - Instruction, IrError, LocalVar, Module, Pass, PassMutability, ScopedPass, Symbol, Value, - ValueDatum, ESCAPED_SYMBOLS_NAME, + get_gep_referred_symbols, get_referred_symbols, memory_utils, AnalysisResults, Context, + EscapedSymbols, Function, InstOp, Instruction, IrError, LocalVar, Module, Pass, PassMutability, + ReferredSymbols, ScopedPass, Symbol, Value, ValueDatum, ESCAPED_SYMBOLS_NAME, }; use std::collections::{HashMap, HashSet}; @@ -58,17 +58,21 @@ fn is_removable_store( InstOp::MemCopyBytes { dst_val_ptr, .. } | InstOp::MemCopyVal { dst_val_ptr, .. } | InstOp::Store { dst_val_ptr, .. } => { - let syms = get_symbols(context, dst_val_ptr); - syms.iter().all(|sym| { - !escaped_symbols.contains(sym) - && num_symbol_uses.get(sym).map_or(0, |uses| *uses) == 0 - }) + let syms = get_referred_symbols(context, dst_val_ptr); + match syms { + ReferredSymbols::Complete(syms) => syms.iter().all(|sym| { + !escaped_symbols.contains(sym) + && num_symbol_uses.get(sym).map_or(0, |uses| *uses) == 0 + }), + // We cannot guarantee that the destination is not used. + ReferredSymbols::Incomplete(_) => false, + } } _ => false, } } -/// Perform dead code (if any) elimination and return true if function modified. +/// Perform dead code (if any) elimination and return true if function is modified. pub fn dce( context: &mut Context, analyses: &AnalysisResults, @@ -83,10 +87,10 @@ pub fn dce( let mut stores_of_sym: HashMap> = HashMap::new(); // Every argument is assumed to be loaded from (from the caller), - // so stores to it shouldn't be deliminated. + // so stores to it shouldn't be eliminated. for sym in function .args_iter(context) - .flat_map(|arg| get_symbols(context, arg.1)) + .flat_map(|arg| get_gep_referred_symbols(context, arg.1)) { num_symbol_uses .entry(sym) diff --git a/sway-ir/src/optimize/memcpyopt.rs b/sway-ir/src/optimize/memcpyopt.rs index a6a6c082972..dba87774e39 100644 --- a/sway-ir/src/optimize/memcpyopt.rs +++ b/sway-ir/src/optimize/memcpyopt.rs @@ -6,9 +6,10 @@ use rustc_hash::{FxHashMap, FxHashSet}; use sway_types::{FxIndexMap, FxIndexSet}; use crate::{ - get_symbol, get_symbols, memory_utils, AnalysisResults, Block, Context, EscapedSymbols, - Function, InstOp, Instruction, IrError, LocalVar, Pass, PassMutability, ScopedPass, Symbol, - Type, Value, ValueDatum, ESCAPED_SYMBOLS_NAME, + get_gep_referred_symbols, get_gep_symbol, get_referred_symbol, get_referred_symbols, + memory_utils, AnalysisResults, Block, Context, EscapedSymbols, Function, InstOp, Instruction, + IrError, LocalVar, Pass, PassMutability, ScopedPass, Symbol, Type, Value, ValueDatum, + ESCAPED_SYMBOLS_NAME, }; pub const MEMCPYOPT_NAME: &str = "memcpyopt"; @@ -35,17 +36,27 @@ pub fn mem_copy_opt( Ok(modified) } -struct InstInfo { - // The block in which an instruction is - block: Block, - // Relative (use only for comparison) position of instruction in `block`. - pos: usize, -} - fn local_copy_prop_prememcpy(context: &mut Context, function: Function) -> Result { + struct InstInfo { + // The block containing the instruction. + block: Block, + // Relative (use only for comparison) position of instruction in `block`. + pos: usize, + } + + // All instructions that load from the `Symbol`. let mut loads_map = FxHashMap::>::default(); + // All instructions that store to the `Symbol`. let mut stores_map = FxHashMap::>::default(); + // All load and store instructions. let mut instr_info_map = FxHashMap::::default(); + // Symbols that escape. + // TODO: The below code does its own logic to calculate escaping symbols. + // It does not cover all escaping cases, though. E.g., contract calls, etc. + // In general, the question is why it does not use `memory_utils::compute_escaped_symbols`. + // My assumption is that it was written before `memory_utils::compute_escaped_symbols` + // got available. + // See: https://github.com/FuelLabs/sway/issues/5924 let mut escaping_uses = FxHashSet::::default(); for (pos, (block, inst)) in function.instruction_iter(context).enumerate() { @@ -56,7 +67,7 @@ fn local_copy_prop_prememcpy(context: &mut Context, function: Function) -> Resul op: InstOp::Load(src_val_ptr), .. } => { - if let Some(local) = get_symbol(context, *src_val_ptr) { + if let Some(local) = get_referred_symbol(context, *src_val_ptr) { loads_map .entry(local) .and_modify(|loads| loads.push(inst)) @@ -68,7 +79,7 @@ fn local_copy_prop_prememcpy(context: &mut Context, function: Function) -> Resul op: InstOp::Store { dst_val_ptr, .. }, .. } => { - if let Some(local) = get_symbol(context, *dst_val_ptr) { + if let Some(local) = get_referred_symbol(context, *dst_val_ptr) { stores_map .entry(local) .and_modify(|stores| stores.push(inst)) @@ -80,7 +91,7 @@ fn local_copy_prop_prememcpy(context: &mut Context, function: Function) -> Resul op: InstOp::PtrToInt(value, _), .. } => { - if let Some(local) = get_symbol(context, *value) { + if let Some(local) = get_referred_symbol(context, *value) { escaping_uses.insert(local); } } @@ -90,7 +101,7 @@ fn local_copy_prop_prememcpy(context: &mut Context, function: Function) -> Resul } => { for arg in args { if let Some(arg) = arg.initializer { - if let Some(local) = get_symbol(context, arg) { + if let Some(local) = get_referred_symbol(context, arg) { escaping_uses.insert(local); } } @@ -101,7 +112,7 @@ fn local_copy_prop_prememcpy(context: &mut Context, function: Function) -> Resul .. } => { for arg in args { - if let Some(local) = get_symbol(context, *arg) { + if let Some(local) = get_referred_symbol(context, *arg) { escaping_uses.insert(local); } } @@ -111,10 +122,17 @@ fn local_copy_prop_prememcpy(context: &mut Context, function: Function) -> Resul } let mut to_delete = FxHashSet::::default(); + // Candidates for replacements. The map's key `Symbol` is the + // destination `Symbol` that can be replaced with the + // map's value `Symbol`, the source. + // Replacement is possible (among other criteria explained below) + // only if the Store of the source is the only storing to the destination. let candidates: FxHashMap = function .instruction_iter(context) .enumerate() .filter_map(|(pos, (block, instr_val))| { + // 1. Go through all the Store instructions whose source is + // a Load instruction... instr_val .get_instruction(context) .and_then(|instr| { @@ -128,7 +146,7 @@ fn local_copy_prop_prememcpy(context: &mut Context, function: Function) -> Resul .. } = instr { - get_symbol(context, *dst_val_ptr).and_then(|dst_local| { + get_gep_symbol(context, *dst_val_ptr).and_then(|dst_local| { stored_val .get_instruction(context) .map(|src_instr| (src_instr, stored_val, dst_local)) @@ -144,13 +162,16 @@ fn local_copy_prop_prememcpy(context: &mut Context, function: Function) -> Resul .. } = src_instr { - get_symbol(context, *src_val_ptr) + get_gep_symbol(context, *src_val_ptr) .map(|src_local| (stored_val, dst_local, src_local)) } else { None } }) .and_then(|(src_load, dst_local, src_local)| { + // 2. ... and pick the (dest_local, src_local) pairs that fulfill the + // below criteria, in other words, where `dest_local` can be + // replaced with `src_local`. let (temp_empty1, temp_empty2, temp_empty3) = (vec![], vec![], vec![]); let dst_local_stores = stores_map.get(&dst_local).unwrap_or(&temp_empty1); let src_local_stores = stores_map.get(&src_local).unwrap_or(&temp_empty2); @@ -170,7 +191,7 @@ fn local_copy_prop_prememcpy(context: &mut Context, function: Function) -> Resul let instr_info = instr_info_map.get(load_val).unwrap(); instr_info.block == block && instr_info.pos > pos }) - // We don't deal with ASM blocks. + // We don't deal with ASM blocks and function calls. || escaping_uses.contains(&dst_local) // We don't deal part copies. || dst_local.get_type(context) != src_local.get_type(context) @@ -186,28 +207,32 @@ fn local_copy_prop_prememcpy(context: &mut Context, function: Function) -> Resul }) .collect(); - // if we have A replaces B and B replaces C, then A must replace C also. - fn closure(candidates: &FxHashMap, src_local: &Symbol) -> Option { + // If we have A replaces B and B replaces C, then A must replace C also. + // Recursively searches for the final replacement for the `local`. + // Returns `None` if the `local` cannot be replaced. + fn get_replace_with(candidates: &FxHashMap, local: &Symbol) -> Option { candidates - .get(src_local) - .map(|replace_with| closure(candidates, replace_with).unwrap_or(*replace_with)) + .get(local) + .map(|replace_with| get_replace_with(candidates, replace_with).unwrap_or(*replace_with)) } // If the source is an Arg, we replace uses of destination with Arg. - // otherwise (`get_local`), we replace the local symbol in-place. + // Otherwise (`get_local`), we replace the local symbol in-place. enum ReplaceWith { InPlaceLocal(LocalVar), Value(Value), } // Because we can't borrow context for both iterating and replacing, do it in 2 steps. + // `replaces` are the original GetLocal instructions with the corresponding replacements + // of their arguments. let replaces: Vec<_> = function .instruction_iter(context) .filter_map(|(_block, value)| match value.get_instruction(context) { Some(Instruction { op: InstOp::GetLocal(local), .. - }) => closure(&candidates, &Symbol::Local(*local)).map(|replace_with| { + }) => get_replace_with(&candidates, &Symbol::Local(*local)).map(|replace_with| { ( value, match replace_with { @@ -269,10 +294,10 @@ fn local_copy_prop( // Currently (as we scan a block) available `memcpy`s. let mut available_copies: FxHashSet; - // Map a symbol to the available `memcpy`s of which its a source. + // Map a symbol to the available `memcpy`s of which it's a source. let mut src_to_copies: FxIndexMap>; - // Map a symbol to the available `memcpy`s of which its a destination. - // (multiple memcpys for the same destination may be available when + // Map a symbol to the available `memcpy`s of which it's a destination. + // (multiple `memcpy`s for the same destination may be available when // they are partial / field writes, and don't alias). let mut dest_to_copies: FxIndexMap>; @@ -285,8 +310,8 @@ fn local_copy_prop( src_to_copies: &mut FxIndexMap>, dest_to_copies: &mut FxIndexMap>, ) { - let syms = get_symbols(context, value); - for sym in syms { + let rs = get_referred_symbols(context, value); + for sym in rs.any() { if let Some(copies) = src_to_copies.get_mut(&sym) { for copy in &*copies { let (_, src_ptr, copy_size) = deconstruct_memcpy(context, *copy); @@ -342,8 +367,8 @@ fn local_copy_prop( dest_to_copies: &mut FxIndexMap>, ) { if let (Some(dst_sym), Some(src_sym)) = ( - get_symbol(context, dst_val_ptr), - get_symbol(context, src_val_ptr), + get_gep_symbol(context, dst_val_ptr), + get_gep_symbol(context, src_val_ptr), ) { if escaped_symbols.contains(&dst_sym) || escaped_symbols.contains(&src_sym) { return; @@ -412,7 +437,7 @@ fn local_copy_prop( ) -> bool { // For every `memcpy` that src_val_ptr is a destination of, // check if we can do the load from the source of that memcpy. - if let Some(src_sym) = get_symbol(context, src_val_ptr) { + if let Some(src_sym) = get_referred_symbol(context, src_val_ptr) { if escaped_symbols.contains(&src_sym) { return false; } @@ -445,8 +470,8 @@ fn local_copy_prop( // if the memcpy copies the entire symbol, we could // insert a new GEP from the source of the memcpy. if let (Some(memcpy_src_sym), Some(memcpy_dst_sym), Some(new_indices)) = ( - get_symbol(context, src_ptr_memcpy), - get_symbol(context, dst_ptr_memcpy), + get_gep_symbol(context, src_ptr_memcpy), + get_gep_symbol(context, dst_ptr_memcpy), memory_utils::combine_indices(context, src_val_ptr), ) { let memcpy_src_sym_type = memcpy_src_sym @@ -502,7 +527,8 @@ fn local_copy_prop( dest_to_copies: &mut FxIndexMap>, ) { for arg in args { - let max_size = get_symbols(context, *arg) + let max_size = get_referred_symbols(context, *arg) + .any() .iter() .filter_map(|sym| { sym.get_type(context) @@ -721,7 +747,7 @@ fn is_clobbered( .skip_while(|i| i != &store_val); assert!(iter.next().unwrap() == store_val); - let src_symbols = get_symbols(context, src_ptr); + let src_symbols = get_gep_referred_symbols(context, src_ptr); // Scan backwards till we encounter load_val, checking if // any store aliases with src_ptr. @@ -744,7 +770,7 @@ fn is_clobbered( .. }) = inst.get_instruction(context) { - if get_symbols(context, *dst_val_ptr) + if get_gep_referred_symbols(context, *dst_val_ptr) .iter() .any(|sym| src_symbols.contains(sym)) { diff --git a/sway-ir/src/optimize/sroa.rs b/sway-ir/src/optimize/sroa.rs index 9fb856c7ce4..5c20a8a278f 100644 --- a/sway-ir/src/optimize/sroa.rs +++ b/sway-ir/src/optimize/sroa.rs @@ -3,9 +3,9 @@ use rustc_hash::{FxHashMap, FxHashSet}; use crate::{ - combine_indices, compute_escaped_symbols, get_loaded_ptr_values, get_stored_ptr_values, - get_symbols, pointee_size, AnalysisResults, Constant, ConstantValue, Context, Function, InstOp, - IrError, LocalVar, Pass, PassMutability, ScopedPass, Symbol, Type, Value, + combine_indices, compute_escaped_symbols, get_gep_referred_symbols, get_loaded_ptr_values, + get_stored_ptr_values, pointee_size, AnalysisResults, Constant, ConstantValue, Context, + Function, InstOp, IrError, LocalVar, Pass, PassMutability, ScopedPass, Symbol, Type, Value, }; pub const SROA_NAME: &str = "sroa"; @@ -131,8 +131,8 @@ pub fn sroa( src_val_ptr, } = inst.get_instruction(context).unwrap().op { - let src_syms = get_symbols(context, src_val_ptr); - let dst_syms = get_symbols(context, dst_val_ptr); + let src_syms = get_gep_referred_symbols(context, src_val_ptr); + let dst_syms = get_gep_referred_symbols(context, dst_val_ptr); // If neither source nor dest needs rewriting, we skip. let src_sym = src_syms @@ -353,7 +353,7 @@ pub fn sroa( let stored_pointers = get_stored_ptr_values(context, inst); for ptr in loaded_pointers.iter().chain(stored_pointers.iter()) { - let syms = get_symbols(context, *ptr); + let syms = get_gep_referred_symbols(context, *ptr); if let Some(sym) = syms .iter() .next() @@ -424,8 +424,8 @@ fn profitability(context: &Context, function: Function, candidates: &mut FxHashS } = inst.get_instruction(context).unwrap().op { if pointee_size(context, dst_val_ptr) > 200 { - for sym in - get_symbols(context, dst_val_ptr).union(&get_symbols(context, src_val_ptr)) + for sym in get_gep_referred_symbols(context, dst_val_ptr) + .union(&get_gep_referred_symbols(context, src_val_ptr)) { candidates.remove(sym); } @@ -466,7 +466,7 @@ fn candidate_symbols(context: &Context, function: Function) -> FxHashSet let inst = inst.get_instruction(context).unwrap(); for ptr in loaded_pointers.iter().chain(stored_pointers.iter()) { - let syms = get_symbols(context, *ptr); + let syms = get_gep_referred_symbols(context, *ptr); if syms.len() != 1 { for sym in &syms { candidates.remove(sym); diff --git a/sway-ir/src/printer.rs b/sway-ir/src/printer.rs index f10ca5ab3d9..6c041d70179 100644 --- a/sway-ir/src/printer.rs +++ b/sway-ir/src/printer.rs @@ -88,7 +88,7 @@ impl Doc { /// Pretty-print a whole [`Context`] to a string. /// -/// The ouput from this function must always be suitable for [`crate::parser::parse`]. +/// The output from this function must always be suitable for [crate::parser::parse]. pub fn to_string(context: &Context) -> String { let mut md_namer = MetadataNamer::default(); context diff --git a/sway-lsp/src/traverse/lexed_tree.rs b/sway-lsp/src/traverse/lexed_tree.rs index 3f30fd41caa..9f0af530d56 100644 --- a/sway-lsp/src/traverse/lexed_tree.rs +++ b/sway-lsp/src/traverse/lexed_tree.rs @@ -4,12 +4,12 @@ use crate::{ }; use rayon::iter::{ParallelBridge, ParallelIterator}; use sway_ast::{ - expr::LoopControlFlow, ty::TyTupleDescriptor, Assignable, CodeBlockContents, ConfigurableField, - Expr, ExprArrayDescriptor, ExprStructField, ExprTupleDescriptor, FnArg, FnArgs, FnSignature, - IfCondition, IfExpr, ItemAbi, ItemConfigurable, ItemConst, ItemEnum, ItemFn, ItemImpl, - ItemImplItem, ItemKind, ItemStorage, ItemStruct, ItemTrait, ItemTypeAlias, ItemUse, - MatchBranchKind, ModuleKind, Pattern, PatternStructField, Statement, StatementLet, - StorageField, TraitType, Ty, TypeField, UseTree, + assignable::ElementAccess, expr::LoopControlFlow, ty::TyTupleDescriptor, Assignable, + CodeBlockContents, ConfigurableField, Expr, ExprArrayDescriptor, ExprStructField, + ExprTupleDescriptor, FnArg, FnArgs, FnSignature, IfCondition, IfExpr, ItemAbi, + ItemConfigurable, ItemConst, ItemEnum, ItemFn, ItemImpl, ItemImplItem, ItemKind, ItemStorage, + ItemStruct, ItemTrait, ItemTypeAlias, ItemUse, MatchBranchKind, ModuleKind, Pattern, + PatternStructField, Statement, StatementLet, StorageField, TraitType, Ty, TypeField, UseTree, }; use sway_core::language::{lexed::LexedProgram, HasSubmodules}; use sway_types::{Ident, Span, Spanned}; @@ -735,18 +735,27 @@ impl Parse for TyTupleDescriptor { } } -impl Parse for Assignable { +impl Parse for ElementAccess { fn parse(&self, ctx: &ParseContext) { match self { - Assignable::Index { target, arg } => { + ElementAccess::Index { target, arg } => { target.parse(ctx); arg.get().parse(ctx) } - Assignable::FieldProjection { target, .. } - | Assignable::TupleFieldProjection { target, .. } => { + ElementAccess::FieldProjection { target, .. } + | ElementAccess::TupleFieldProjection { target, .. } => { target.parse(ctx); } _ => {} } } } + +impl Parse for Assignable { + fn parse(&self, ctx: &ParseContext) { + match self { + Assignable::ElementAccess(element_access) => element_access.parse(ctx), + Assignable::Deref { expr, .. } => expr.parse(ctx), + } + } +} diff --git a/sway-lsp/src/traverse/parsed_tree.rs b/sway-lsp/src/traverse/parsed_tree.rs index 0089cffef90..855f7ce1905 100644 --- a/sway-lsp/src/traverse/parsed_tree.rs +++ b/sway-lsp/src/traverse/parsed_tree.rs @@ -357,7 +357,10 @@ impl Parse for ReassignmentExpression { fn parse(&self, ctx: &ParseContext) { self.rhs.parse(ctx); match &self.lhs { - ReassignmentTarget::VariableExpression(exp) => { + ReassignmentTarget::ElementAccess(exp) => { + exp.parse(ctx); + } + ReassignmentTarget::Deref(exp) => { exp.parse(ctx); } } diff --git a/sway-lsp/src/traverse/typed_tree.rs b/sway-lsp/src/traverse/typed_tree.rs index 76f9bd40599..def8db25ee1 100644 --- a/sway-lsp/src/traverse/typed_tree.rs +++ b/sway-lsp/src/traverse/typed_tree.rs @@ -11,7 +11,7 @@ use sway_core::{ decl_engine::{id::DeclId, InterfaceDeclId}, language::{ parsed::{ImportType, QualifiedPathType, Supertrait}, - ty::{self, GetDeclIdent, TyModule, TyProgram, TySubmodule}, + ty::{self, GetDeclIdent, TyModule, TyProgram, TyReassignmentTarget, TySubmodule}, CallPathTree, }, type_system::TypeArgument, @@ -1048,30 +1048,37 @@ impl Parse for ty::TyStructScrutineeField { impl Parse for ty::TyReassignment { fn parse(&self, ctx: &ParseContext) { self.rhs.parse(ctx); - if let Some(mut token) = ctx - .tokens - .try_get_mut_with_retry(&ctx.ident(&self.lhs_base_name)) - { - token.typed = Some(TypedAstToken::TypedReassignment(self.clone())); - } - adaptive_iter(&self.lhs_indices, |proj_kind| { - if let ty::ProjectionKind::StructField { name } = proj_kind { - if let Some(mut token) = ctx.tokens.try_get_mut_with_retry(&ctx.ident(name)) { + match &self.lhs { + TyReassignmentTarget::Deref(exp) => exp.parse(ctx), + TyReassignmentTarget::ElementAccess { + base_name, + base_type, + indices, + } => { + if let Some(mut token) = ctx.tokens.try_get_mut_with_retry(&ctx.ident(base_name)) { token.typed = Some(TypedAstToken::TypedReassignment(self.clone())); - if let Some(struct_decl) = &ctx - .tokens - .struct_declaration_of_type_id(ctx.engines, &self.lhs_type) - { - struct_decl.fields.iter().for_each(|decl_field| { - if &decl_field.name == name { - token.type_def = - Some(TypeDefinition::Ident(decl_field.name.clone())); + } + adaptive_iter(indices, |proj_kind| { + if let ty::ProjectionKind::StructField { name } = proj_kind { + if let Some(mut token) = ctx.tokens.try_get_mut_with_retry(&ctx.ident(name)) + { + token.typed = Some(TypedAstToken::TypedReassignment(self.clone())); + if let Some(struct_decl) = &ctx + .tokens + .struct_declaration_of_type_id(ctx.engines, base_type) + { + struct_decl.fields.iter().for_each(|decl_field| { + if &decl_field.name == name { + token.type_def = + Some(TypeDefinition::Ident(decl_field.name.clone())); + } + }); } - }); + } } - } + }); } - }); + } } } diff --git a/sway-parse/src/expr/mod.rs b/sway-parse/src/expr/mod.rs index dab96d304aa..93954bb777a 100644 --- a/sway-parse/src/expr/mod.rs +++ b/sway-parse/src/expr/mod.rs @@ -281,15 +281,20 @@ fn take_reassignment_op(parser: &mut Parser) -> Option { fn parse_reassignment(parser: &mut Parser, ctx: ParseExprCtx) -> ParseResult { let expr = parse_logical_or(parser, ctx)?; + let expr_span = expr.span(); if let Some(reassignment_op) = take_reassignment_op(parser) { let assignable = match expr.try_into_assignable() { Ok(assignable) => assignable, Err(expr) => { let span = expr.span(); - return Err( - parser.emit_error_with_span(ParseErrorKind::UnassignableExpression, span) - ); + return Err(parser.emit_error_with_span( + ParseErrorKind::UnassignableExpression { + erroneous_expression_kind: expr.friendly_name(), + erroneous_expression_span: span, + }, + expr_span, + )); } }; let expr = Box::new(parse_reassignment(parser, ctx.not_statement())?); diff --git a/swayfmt/src/utils/language/expr/assignable.rs b/swayfmt/src/utils/language/expr/assignable.rs index c3911525cb7..b5e278464dd 100644 --- a/swayfmt/src/utils/language/expr/assignable.rs +++ b/swayfmt/src/utils/language/expr/assignable.rs @@ -6,26 +6,26 @@ use crate::{ }, }; use std::fmt::Write; -use sway_ast::{expr::ReassignmentOp, Assignable, Expr}; +use sway_ast::{assignable::ElementAccess, expr::ReassignmentOp, Assignable, Expr}; use sway_types::Spanned; -impl Format for Assignable { +impl Format for ElementAccess { fn format( &self, formatted_code: &mut FormattedCode, formatter: &mut Formatter, ) -> Result<(), FormatterError> { match self { - Assignable::Var(name) => { + ElementAccess::Var(name) => { name.format(formatted_code, formatter)?; } - Assignable::Index { target, arg } => { + ElementAccess::Index { target, arg } => { target.format(formatted_code, formatter)?; Expr::open_square_bracket(formatted_code, formatter)?; arg.get().format(formatted_code, formatter)?; Expr::close_square_bracket(formatted_code, formatter)?; } - Assignable::FieldProjection { + ElementAccess::FieldProjection { target, dot_token, name, @@ -34,7 +34,7 @@ impl Format for Assignable { write!(formatted_code, "{}", dot_token.span().as_str())?; name.format(formatted_code, formatter)?; } - Assignable::TupleFieldProjection { + ElementAccess::TupleFieldProjection { target, dot_token, field: _, @@ -53,6 +53,25 @@ impl Format for Assignable { } } +impl Format for Assignable { + fn format( + &self, + formatted_code: &mut FormattedCode, + formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + match self { + Assignable::ElementAccess(element_access) => { + element_access.format(formatted_code, formatter)? + } + Assignable::Deref { star_token, expr } => { + write!(formatted_code, "{}", star_token.span().as_str())?; + expr.format(formatted_code, formatter)?; + } + } + Ok(()) + } +} + impl Format for ReassignmentOp { fn format( &self, @@ -64,16 +83,16 @@ impl Format for ReassignmentOp { } } -impl LeafSpans for Assignable { +impl LeafSpans for ElementAccess { fn leaf_spans(&self) -> Vec { let mut collected_spans = Vec::new(); match self { - Assignable::Var(var) => collected_spans.push(ByteSpan::from(var.span())), - Assignable::Index { target, arg } => { + ElementAccess::Var(var) => collected_spans.push(ByteSpan::from(var.span())), + ElementAccess::Index { target, arg } => { collected_spans.append(&mut target.leaf_spans()); collected_spans.append(&mut arg.leaf_spans()); } - Assignable::FieldProjection { + ElementAccess::FieldProjection { target, dot_token, name, @@ -82,7 +101,7 @@ impl LeafSpans for Assignable { collected_spans.push(ByteSpan::from(dot_token.span())); collected_spans.push(ByteSpan::from(name.span())); } - Assignable::TupleFieldProjection { + ElementAccess::TupleFieldProjection { target, dot_token, field: _field, @@ -96,3 +115,17 @@ impl LeafSpans for Assignable { collected_spans } } + +impl LeafSpans for Assignable { + fn leaf_spans(&self) -> Vec { + match self { + Assignable::ElementAccess(element_access) => element_access.leaf_spans(), + Assignable::Deref { star_token, expr } => { + let mut collected_spans = Vec::new(); + collected_spans.push(ByteSpan::from(star_token.span())); + collected_spans.append(&mut expr.leaf_spans()); + collected_spans + } + } + } +} diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/array_bad_index/json_abi_oracle.json b/test/src/e2e_vm_tests/test_programs/should_fail/array_bad_index/json_abi_oracle.json deleted file mode 100644 index 0637a088a01..00000000000 --- a/test/src/e2e_vm_tests/test_programs/should_fail/array_bad_index/json_abi_oracle.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/array_bad_index/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/array_bad_index/src/main.sw index c85ebea1d74..ba663ccee32 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/array_bad_index/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_fail/array_bad_index/src/main.sw @@ -1,6 +1,57 @@ +// This test proves that https://github.com/FuelLabs/sway/issues/5922 is fixed. script; -fn main() -> u64 { - let ary = [0, 1, 2, 3]; - ary[false] +struct S { + x: u64, } + +enum E { + X: u64 +} + +fn main() { + let mut array = [1, 2, 3]; + + array[0u8] = 0; + + array[0u16] = 0; + + array[0u32] = 0; + + // Enough vertical space, so that the below line + // does not appear in the output of the above error. + + array[0u64] = 0; + + + array[true] = 0; + + array[()] = 0; + + array["test"] = 0; + + array[S { x: 0 }] = 0; + + array[E::X(0)] = 0; + + poke(array[0u8]); + poke(array[0u16]); + poke(array[0u32]); + + + + poke(array[0u64]); + + + + poke(array[true]); + poke(array[()]); + poke(array["test"]); + poke(array[S { x: 0 }]); + poke(array[E::X(0)]); + + poke(S { x: 0}.x); +} + +#[inline(never)] +fn poke(_x: T) { } \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/array_bad_index/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/array_bad_index/test.toml index 366684b2f51..504efe9ff7c 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/array_bad_index/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/array_bad_index/test.toml @@ -1,6 +1,119 @@ category = "fail" -# check: ary[false] -# nextln: $()Mismatched types. -# nextln: $()expected: u64 -# nextln: $()found: bool. +#check: $()error +#check: $()array[0u8] = 0; +#nextln: $()Mismatched types. +#nextln: $()expected: u64 +#nextln: $()found: u8. +#nextln: $()help: Array index must be of type "u64". + +#check: $()error +#check: $()array[0u16] = 0; +#nextln: $()Mismatched types. +#nextln: $()expected: u64 +#nextln: $()found: u16. +#nextln: $()help: Array index must be of type "u64". + +#check: $()error +#check: $()array[0u32] = 0; +#nextln: $()Mismatched types. +#nextln: $()expected: u64 +#nextln: $()found: u32. +#nextln: $()help: Array index must be of type "u64". + +#not: $()array[0u64] = 0; + +#check: $()error +#check: $()array[true] = 0; +#nextln: $()Mismatched types. +#nextln: $()expected: u64 +#nextln: $()found: bool. +#nextln: $()help: Array index must be of type "u64". + +#check: $()error +#check: $()array[()] = 0; +#nextln: $()Mismatched types. +#nextln: $()expected: u64 +#nextln: $()found: (). +#nextln: $()help: Array index must be of type "u64". + +#check: $()error +#check: $()array["test"] = 0; +#nextln: $()Mismatched types. +#nextln: $()expected: u64 +#nextln: $()found: str. +#nextln: $()help: Array index must be of type "u64". + +#check: $()error +#check: $()array[S { x: 0 }] = 0; +#nextln: $()Mismatched types. +#nextln: $()expected: u64 +#nextln: $()found: S. +#nextln: $()help: Array index must be of type "u64". + +#check: $()error +#check: $()array[E::X(0)] = 0; +#nextln: $()Mismatched types. +#nextln: $()expected: u64 +#nextln: $()found: E. +#nextln: $()help: Array index must be of type "u64". + +#--------------------------------------------------- + +#check: $()error +#check: $()poke(array[0u8]); +#nextln: $()Mismatched types. +#nextln: $()expected: u64 +#nextln: $()found: u8. +#nextln: $()help: Array index must be of type "u64". + +#check: $()error +#check: $()poke(array[0u16]); +#nextln: $()Mismatched types. +#nextln: $()expected: u64 +#nextln: $()found: u16. +#nextln: $()help: Array index must be of type "u64". + +#check: $()error +#check: $()poke(array[0u32]); +#nextln: $()Mismatched types. +#nextln: $()expected: u64 +#nextln: $()found: u32. +#nextln: $()help: Array index must be of type "u64". + +#not: $()poke(array[0u64]); + +#check: $()error +#check: $()poke(array[true]); +#nextln: $()Mismatched types. +#nextln: $()expected: u64 +#nextln: $()found: bool. +#nextln: $()help: Array index must be of type "u64". + +#check: $()error +#check: $()poke(array[()]); +#nextln: $()Mismatched types. +#nextln: $()expected: u64 +#nextln: $()found: (). +#nextln: $()help: Array index must be of type "u64". + +#check: $()error +#check: $()poke(array["test"]); +#nextln: $()Mismatched types. +#nextln: $()expected: u64 +#nextln: $()found: str. +#nextln: $()help: Array index must be of type "u64". + +#check: $()error +#check: $()poke(array[S { x: 0 }]); +#nextln: $()Mismatched types. +#nextln: $()expected: u64 +#nextln: $()found: S. +#nextln: $()help: Array index must be of type "u64". + +#check: $()error +#check: $()poke(array[E::X(0)]); +#nextln: $()Mismatched types. +#nextln: $()expected: u64 +#nextln: $()found: E. +#nextln: $()help: Array index must be of type "u64". \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/assign_to_field_of_non_mutable_struct/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/assign_to_field_of_non_mutable_struct/src/main.sw index b63445270a1..822f4c2815e 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/assign_to_field_of_non_mutable_struct/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_fail/assign_to_field_of_non_mutable_struct/src/main.sw @@ -4,7 +4,7 @@ struct S { x: u64, } -fn wow() { +fn main() { let thing: S = S { x: 0 }; thing.x = 23; } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/assign_to_field_of_non_mutable_struct/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/assign_to_field_of_non_mutable_struct/test.toml index 2cdba2605b9..33a4b19ea30 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/assign_to_field_of_non_mutable_struct/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/assign_to_field_of_non_mutable_struct/test.toml @@ -1,3 +1,9 @@ category = "fail" -# check: $()Assignment to immutable variable. Variable thing is not declared as mutable. +#check: $()Immutable variables cannot be assigned to +#check: $()let thing: S = S { x: 0 }; +#nextln: $()Variable "thing" is declared here as immutable. +#check: $()thing.x = 23; +#nextln: $()This expression cannot be assigned to, because "thing" is an immutable variable. +#check: $()Consider declaring "thing" as mutable. + diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/configurables_and_consts_are_immutable/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/configurables_and_consts_are_immutable/Forc.lock new file mode 100644 index 00000000000..d140027eee3 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/configurables_and_consts_are_immutable/Forc.lock @@ -0,0 +1,3 @@ +[[package]] +name = "configurables_and_consts_are_immutable" +source = "member" diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/configurables_and_consts_are_immutable/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/configurables_and_consts_are_immutable/Forc.toml new file mode 100644 index 00000000000..e5f2c65ad38 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/configurables_and_consts_are_immutable/Forc.toml @@ -0,0 +1,6 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +implicit-std = false +license = "Apache-2.0" +name = "configurables_and_consts_are_immutable" diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/configurables_and_consts_are_immutable/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/configurables_and_consts_are_immutable/src/main.sw new file mode 100644 index 00000000000..6db72ae1173 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/configurables_and_consts_are_immutable/src/main.sw @@ -0,0 +1,21 @@ +script; + +struct S { + x: u8, +} + +const CONST: u64 = 0; +const CONST_S: S = S { x: 0 }; + +configurable { + C: u64 = 0, + C_S: S = S { x: 0 }, +} + +fn main() { + C = 1; + C_S.x = 1; + + CONST = 1; + CONST_S.x = 1; +} diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/configurables_and_consts_are_immutable/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/configurables_and_consts_are_immutable/test.toml new file mode 100644 index 00000000000..521dc2cc06d --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/configurables_and_consts_are_immutable/test.toml @@ -0,0 +1,25 @@ +category = "fail" + +#check: $()Configurables cannot be assigned to +#check: $()C: u64 = 0, +#nextln: $()Configurable "C" is declared here. +#check: $()C = 1; +#nextln: $()"C" cannot be assigned to, because it is a configurable. + +#check: $()Configurables cannot be assigned to +#check: $()C_S: S = S { x: 0 }, +#nextln: $()Configurable "C_S" is declared here. +#check: $()C_S.x = 1; +#nextln: $()This expression cannot be assigned to, because "C_S" is a configurable. + +#check: $()Constants cannot be assigned to +#check: $()const CONST: u64 = 0; +#nextln: $()Constant "CONST" is declared here. +#check: $()CONST = 1; +#nextln: $()"CONST" cannot be assigned to, because it is a constant. + +#check: $()Constants cannot be assigned to +#check: $()const CONST_S: S = S { x: 0 }; +#nextln: $()Constant "CONST_S" is declared here. +#check: $()CONST_S.x = 1; +#nextln: $()This expression cannot be assigned to, because "CONST_S" is a constant. diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/configurables_are_immutable/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/configurables_are_immutable/Forc.lock deleted file mode 100644 index 694497fff4b..00000000000 --- a/test/src/e2e_vm_tests/test_programs/should_fail/configurables_are_immutable/Forc.lock +++ /dev/null @@ -1,3 +0,0 @@ -[[package]] -name = 'configurables_are_immutable' -source = 'member' diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/configurables_are_immutable/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/configurables_are_immutable/src/main.sw deleted file mode 100644 index 0cdf4192579..00000000000 --- a/test/src/e2e_vm_tests/test_programs/should_fail/configurables_are_immutable/src/main.sw +++ /dev/null @@ -1,10 +0,0 @@ -script; - -configurable { - C1: u64 = 5, -} - -fn main() -> u64 { - C1 = 6; - C1 -} diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/configurables_are_immutable/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/configurables_are_immutable/test.toml deleted file mode 100644 index 6c329619810..00000000000 --- a/test/src/e2e_vm_tests/test_programs/should_fail/configurables_are_immutable/test.toml +++ /dev/null @@ -1,4 +0,0 @@ -category = "fail" - -#check: $()C1: u64 = 5, -#nextln: $()This is a constant, not a variable. diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/decl_assignment_target_cannot_be_assigned_to/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/decl_assignment_target_cannot_be_assigned_to/Forc.lock new file mode 100644 index 00000000000..2d5009d7a37 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/decl_assignment_target_cannot_be_assigned_to/Forc.lock @@ -0,0 +1,3 @@ +[[package]] +name = "decl_assignment_target_cannot_be_assigned_to" +source = "member" diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/decl_assignment_target_cannot_be_assigned_to/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/decl_assignment_target_cannot_be_assigned_to/Forc.toml new file mode 100644 index 00000000000..9145535e5ce --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/decl_assignment_target_cannot_be_assigned_to/Forc.toml @@ -0,0 +1,6 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "decl_assignment_target_cannot_be_assigned_to" +implicit-std = false diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/decl_assignment_target_cannot_be_assigned_to/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/decl_assignment_target_cannot_be_assigned_to/src/main.sw new file mode 100644 index 00000000000..c237dca61bc --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/decl_assignment_target_cannot_be_assigned_to/src/main.sw @@ -0,0 +1,43 @@ +script; + +abi Abi { } + +struct S { + x: u8, +} + +trait Trait {} + +fn function() { } + +enum E { + A: (), +} + +type Type = u64; + +pub fn play() { + Abi = 0; + + Abi.x = 0; + + S = 0; + + S.x = 0; + + Trait = 0; + + Trait.x = 0; + + function = 0; + + function.x = 0; + + E = 0; + + E.x = 0; + + Type = 0; + + Type.x = 0; +} diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/decl_assignment_target_cannot_be_assigned_to/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/decl_assignment_target_cannot_be_assigned_to/test.toml new file mode 100644 index 00000000000..a9db795f755 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/decl_assignment_target_cannot_be_assigned_to/test.toml @@ -0,0 +1,73 @@ +category = "fail" + +#check: $()Assignment target cannot be assigned to +#check: $()abi Abi { } +#nextln: $()ABI "Abi" is declared here. +#check: $()Abi = 0; +#check: $()"Abi" cannot be assigned to, because it is an ABI and not a mutable variable. + +#check: $()Assignment target cannot be assigned to +#check: $()abi Abi { } +#nextln: $()ABI "Abi" is declared here. +#check: $()Abi.x = 0; +#check: $()This cannot be assigned to, because "Abi" is an ABI and not a mutable variable. + +#check: $()Assignment target cannot be assigned to +#check: $()struct S { +#nextln: $()Struct "S" is declared here. +#check: $()S = 0; +#check: $()"S" cannot be assigned to, because it is a struct and not a mutable variable. + +#check: $()Assignment target cannot be assigned to +#check: $()struct S { +#nextln: $()Struct "S" is declared here. +#check: $()S.x = 0; +#check: $()This cannot be assigned to, because "S" is a struct and not a mutable variable. + +#check: $()Assignment target cannot be assigned to +#check: $()trait Trait {} +#nextln: $()Trait "Trait" is declared here. +#check: $()Trait = 0; +#check: $()"Trait" cannot be assigned to, because it is a trait and not a mutable variable. + +#check: $()Assignment target cannot be assigned to +#check: $()trait Trait {} +#nextln: $()Trait "Trait" is declared here. +#check: $()Trait.x = 0; +#check: $()This cannot be assigned to, because "Trait" is a trait and not a mutable variable. + +#check: $()Assignment target cannot be assigned to +#check: $()fn function() { } +#nextln: $()Function "function" is declared here. +#check: $()function = 0; +#check: $()"function" cannot be assigned to, because it is a function and not a mutable variable. + +#check: $()Assignment target cannot be assigned to +#check: $()fn function() { } +#nextln: $()Function "function" is declared here. +#check: $()function.x = 0; +#check: $()This cannot be assigned to, because "function" is a function and not a mutable variable. + +#check: $()Assignment target cannot be assigned to +#check: $()enum E { +#nextln: $()Enum "E" is declared here. +#check: $()E = 0; +#check: $()"E" cannot be assigned to, because it is an enum and not a mutable variable. + +#check: $()Assignment target cannot be assigned to +#check: $()enum E { +#nextln: $()Enum "E" is declared here. +#check: $()E.x = 0; +#check: $()This cannot be assigned to, because "E" is an enum and not a mutable variable. + +#check: $()Assignment target cannot be assigned to +#check: $()type Type = u64; +#nextln: $()Type alias "Type" is declared here. +#check: $()Type = 0; +#check: $()"Type" cannot be assigned to, because it is a type alias and not a mutable variable. + +#check: $()Assignment target cannot be assigned to +#check: $()type Type = u64; +#nextln: $()Type alias "Type" is declared here. +#check: $()Type.x = 0; +#check: $()This cannot be assigned to, because "Type" is a type alias and not a mutable variable. diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/expression_cannot_be_assigned_to/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/expression_cannot_be_assigned_to/Forc.lock new file mode 100644 index 00000000000..638fb9a5613 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/expression_cannot_be_assigned_to/Forc.lock @@ -0,0 +1,3 @@ +[[package]] +name = "expression_cannot_be_assigned_to" +source = "member" diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/configurables_are_immutable/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/expression_cannot_be_assigned_to/Forc.toml similarity index 73% rename from test/src/e2e_vm_tests/test_programs/should_fail/configurables_are_immutable/Forc.toml rename to test/src/e2e_vm_tests/test_programs/should_fail/expression_cannot_be_assigned_to/Forc.toml index f443dcce452..f6fec165134 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/configurables_are_immutable/Forc.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/expression_cannot_be_assigned_to/Forc.toml @@ -1,6 +1,6 @@ [project] authors = ["Fuel Labs "] entry = "main.sw" -implicit-std = false license = "Apache-2.0" -name = "configurables_are_immutable" +name = "expression_cannot_be_assigned_to" +implicit-std = false diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/expression_cannot_be_assigned_to/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/expression_cannot_be_assigned_to/src/main.sw new file mode 100644 index 00000000000..f2122fb589a --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/expression_cannot_be_assigned_to/src/main.sw @@ -0,0 +1,41 @@ +script; + +struct S { + x: (u8, u8), +} + +impl S { + fn method(self) {} +} + +fn main() { + S { x: 0 } = 0; + S { x: 0 }.x = 0; + + return_array() = 1; + return_array()[0].x.1 = 1; + + let s = S { x: (0, 0) }; + + s.method() = 2; + s.method().x = 2; + + return = 3; + + break = 4; + + continue = 5; + + (2 + 2) = 6; + (2 + 2).x = 6; + + 2 + 2 = 4; + + { } = 7; + { s }.x = 7; +} + +fn return_array() -> [S;2] { + let s = S { x: (0, 0) }; + [s, s] +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/expression_cannot_be_assigned_to/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/expression_cannot_be_assigned_to/test.toml new file mode 100644 index 00000000000..5bf3e38a21e --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/expression_cannot_be_assigned_to/test.toml @@ -0,0 +1,75 @@ +category = "fail" + +#check: $()Expression cannot be assigned to +#check: $()S { x: 0 } = 0; +#nextln: $()This expression cannot be assigned to, because it is a struct instantiation. +#check: $()Struct instantiation cannot be an assignment target. + +#check: $()Expression cannot be assigned to +#check: $()S { x: 0 }.x = 0; +#nextln: $()This expression cannot be assigned to, because it contains a struct instantiation. +#nextln: $()This is the contained struct instantiation. +#check: $()Struct instantiation cannot be a part of an assignment target. + +#check: $()Expression cannot be assigned to +#check: $()return_array() = 1; +#nextln: $()This expression cannot be assigned to, because it is a function call. +#check: $()Function call cannot be an assignment target. + +#check: $()Expression cannot be assigned to +#check: $()return_array()[0].x.1 = 1; +#nextln: $()This expression cannot be assigned to, because it contains a function call. +#nextln: $()This is the contained function call. +#check: $()Function call cannot be a part of an assignment target. + +#check: $()Expression cannot be assigned to +#check: $()s.method() = 2; +#nextln: $()This expression cannot be assigned to, because it is a method call. +#check: $()Method call cannot be an assignment target. + +#check: $()Expression cannot be assigned to +#check: $()s.method().x = 2; +#nextln: $()This expression cannot be assigned to, because it contains a method call. +#nextln: $()This is the contained method call. +#check: $()Method call cannot be a part of an assignment target. + +#check: $()error +#check: $()return = 3; +#nextln: $()Expected an expression. + +#check: $()Expression cannot be assigned to +#check: $()break = 4; +#nextln: $()This expression cannot be assigned to, because it is a break. +#check: $()Break cannot be an assignment target. + +#check: $()Expression cannot be assigned to +#check: $()continue = 5; +#nextln: $()This expression cannot be assigned to, because it is a continue. +#check: $()Continue cannot be an assignment target. + +#check: $()Expression cannot be assigned to +#check: $()(2 + 2) = 6; +#nextln: $()This expression cannot be assigned to, because it is parentheses. +#check: $()Parentheses cannot be an assignment target. + +#check: $()Expression cannot be assigned to +#check: $()(2 + 2).x = 6; +#nextln: $()This expression cannot be assigned to, because it contains parentheses. +#nextln: $()These are the contained parentheses. +#check: $()Parentheses cannot be a part of an assignment target. + +#check: $()Expression cannot be assigned to +#check: $()2 + 2 = 4; +#nextln: $()This expression cannot be assigned to, because it is an addition. +#check: $()Addition cannot be an assignment target. + +#check: $()Expression cannot be assigned to +#check: $(){ } = 7; +#nextln: $()This expression cannot be assigned to, because it is a block. +#check: $()Block cannot be an assignment target. + +#check: $()Expression cannot be assigned to +#check: $(){ s }.x = 7; +#nextln: $()This expression cannot be assigned to, because it contains a block. +#nextln: $()This is the contained block. +#check: $()Block cannot be a part of an assignment target. \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/language/references/references_to_mutable_values/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/language/references/references_to_mutable_values/test.toml index 8d9445d64a6..e7881b96223 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/language/references/references_to_mutable_values/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/language/references/references_to_mutable_values/test.toml @@ -2,7 +2,7 @@ category = "fail" #check: $()References to mutable values cannot reference immutable variables #check: $()fn function(f_x: u8) { -#nextln: $()"f_x" is declared here as immutable. +#nextln: $()Variable "f_x" is declared here as immutable. #nextln: $()let _ = &mut f_x; #nextln: $()"f_x" is an immutable variable. `&mut` cannot reference immutable variables. #check: $()- referencing a mutable copy of "f_x", by returning it from a block: `&mut { f_x }`. @@ -31,7 +31,7 @@ category = "fail" #check: $()References to mutable values cannot reference immutable variables #check: $()let a = 123; -#nextln: $()"a" is declared here as immutable. +#nextln: $()Variable "a" is declared here as immutable. #check: $()let _ = &mut a; #nextln: $()"a" is an immutable variable. `&mut` cannot reference immutable variables. #check: $()- referencing a mutable copy of "a", by returning it from a block: `&mut { a }`. @@ -40,56 +40,56 @@ category = "fail" #check: $()References to mutable values cannot reference immutable variables #check: $()let S { x } = S { x: 0 }; -#nextln: $()"x" is declared here as immutable. +#nextln: $()Variable "x" is declared here as immutable. #check: $()let _ = &mut x; #nextln: $()"x" is an immutable variable. `&mut` cannot reference immutable variables. #check: $()- referencing a mutable copy of "x", by returning it from a block: `&mut { x }`. #check: $()References to mutable values cannot reference immutable variables #check: $()let S { x: x } = S { x: 0 }; -#nextln: $()"x" is declared here as immutable. +#nextln: $()Variable "x" is declared here as immutable. #check: $()let _ = &mut x; #nextln: $()"x" is an immutable variable. `&mut` cannot reference immutable variables. #check: $()- referencing a mutable copy of "x", by returning it from a block: `&mut { x }`. #check: $()References to mutable values cannot reference immutable variables #check: $()let S { x: x_1 } = S { x: 0 }; -#nextln: $()"x_1" is declared here as immutable. +#nextln: $()Variable "x_1" is declared here as immutable. #check: $()let _ = &mut x_1; #nextln: $()"x_1" is an immutable variable. `&mut` cannot reference immutable variables. #check: $()- referencing a mutable copy of "x_1", by returning it from a block: `&mut { x_1 }`. #check: $()References to mutable values cannot reference immutable variables #check: $()S { x } => { -#nextln: $()"x" is declared here as immutable. +#nextln: $()Variable "x" is declared here as immutable. #check: $()let _ = &mut x; #nextln: $()"x" is an immutable variable. `&mut` cannot reference immutable variables. #check: $()- referencing a mutable copy of "x", by returning it from a block: `&mut { x }`. #check: $()References to mutable values cannot reference immutable variables #check: $()S { x: x } => { -#nextln: $()"x" is declared here as immutable. +#nextln: $()Variable "x" is declared here as immutable. #check: $()let _ = &mut x; #nextln: $()"x" is an immutable variable. `&mut` cannot reference immutable variables. #check: $()- referencing a mutable copy of "x", by returning it from a block: `&mut { x }`. #check: $()References to mutable values cannot reference immutable variables #check: $()S { x: x_1 } => { -#nextln: $()"x_1" is declared here as immutable. +#nextln: $()Variable "x_1" is declared here as immutable. #check: $()let _ = &mut x_1; #nextln: $()"x_1" is an immutable variable. `&mut` cannot reference immutable variables. #check: $()- referencing a mutable copy of "x_1", by returning it from a block: `&mut { x_1 }`. #check: $()References to mutable values cannot reference immutable variables #check: $()if let S { x } = s { -#nextln: $()"x" is declared here as immutable. +#nextln: $()Variable "x" is declared here as immutable. #check: $()let _ = &mut x; #nextln: $()"x" is an immutable variable. `&mut` cannot reference immutable variables. #check: $()- referencing a mutable copy of "x", by returning it from a block: `&mut { x }`. #check: $()References to mutable values cannot reference immutable variables #check: $()for n in vec.iter() { -#nextln: $()"n" is declared here as immutable. +#nextln: $()Variable "n" is declared here as immutable. #check: $()let _ = &mut n; #nextln: $()"n" is an immutable variable. `&mut` cannot reference immutable variables. #check: $()- referencing a mutable copy of "n", by returning it from a block: `&mut { n }`. diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/mutable_arrays/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/mutable_arrays/src/main.sw index 95064daf407..0b834a7f401 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/mutable_arrays/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_fail/mutable_arrays/src/main.sw @@ -16,6 +16,4 @@ fn main() -> bool { false } -fn takes_ref_mut_arr(ref mut arr: [u64; 1]) { - -} +fn takes_ref_mut_arr(ref mut _arr: [u64; 1]) { } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/mutable_arrays/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/mutable_arrays/test.toml index 65f59b26cfc..4f7253ba004 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/mutable_arrays/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/mutable_arrays/test.toml @@ -4,7 +4,12 @@ category = "fail" # check: $()b[0] = true; # nextln: $()This expression has type "bool", which is not an indexable type. -# check: $()Assignment to immutable variable. Variable my_array is not declared as mutable. +# check: $()Immutable variables cannot be assigned to +# check: $()let my_array: [u64; 1] = [1]; +# nextln: $()"my_array" is declared here as immutable. +# check: $()my_array[0] = 0; +# nextln: $()This expression cannot be assigned to, because "my_array" is an immutable variable. +# check: $()Consider declaring "my_array" as mutable. # check: $()Cannot pass immutable argument to mutable parameter. diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/reference_is_not_reference_to_mutable_value/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/reference_is_not_reference_to_mutable_value/Forc.lock new file mode 100644 index 00000000000..679481c7dfb --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/reference_is_not_reference_to_mutable_value/Forc.lock @@ -0,0 +1,3 @@ +[[package]] +name = "reference_is_not_reference_to_mutable_value" +source = "member" diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/reference_is_not_reference_to_mutable_value/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/reference_is_not_reference_to_mutable_value/Forc.toml new file mode 100644 index 00000000000..1a46c3ed0ae --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/reference_is_not_reference_to_mutable_value/Forc.toml @@ -0,0 +1,6 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "reference_is_not_reference_to_mutable_value" +implicit-std = false diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/reference_is_not_reference_to_mutable_value/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/reference_is_not_reference_to_mutable_value/src/main.sw new file mode 100644 index 00000000000..36bbc24a5ff --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/reference_is_not_reference_to_mutable_value/src/main.sw @@ -0,0 +1,27 @@ +script; + +fn main() { + let mut array = [1u64, 2, 3]; + + let r_array_1 = &array; + + *r_array_1 = [2, 3, 4]; + + + let r_array_2 = r_array_1; + + *r_array_2 = [2, 3, 4]; + + + let r_array_3 = &get_array( + 1, 2 + ); + + *r_array_3 = [2, 3, 4]; + + *&array = [2, 3, 4]; +} + +fn get_array(_x: u64, _y: u64) -> [u64;3] { + [0, 0, 0] +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/reference_is_not_reference_to_mutable_value/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/reference_is_not_reference_to_mutable_value/test.toml new file mode 100644 index 00000000000..a079b5714b5 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/reference_is_not_reference_to_mutable_value/test.toml @@ -0,0 +1,34 @@ +category = "fail" + +#check: $()Reference is not a reference to a mutable value (`&mut`) +#check: $()let r_array_1 = &array; +#nextln: $()Reference "r_array_1" is declared here as a reference to immutable value. +#nextln: $()This expression has type "&[u64; 3]" instead of "&mut [u64; 3]". +#nextln: $()Consider taking here a reference to a mutable value: `&mut array`. +#check: $()*r_array_1 = [2, 3, 4]; +#nextln: $()Reference "r_array_1" is not a reference to a mutable value (`&mut`). +#check: $()References dereferenced in assignment targets must be references to mutable values (`&mut`). + +#check: $()Reference is not a reference to a mutable value (`&mut`) +#check: $()let r_array_2 = r_array_1; +#nextln: $()Reference "r_array_2" is declared here as a reference to immutable value. +#nextln: $()This expression has type "&[u64; 3]" instead of "&mut [u64; 3]". +#not: $()Consider taking here a reference to a mutable value +#check: $()*r_array_2 = [2, 3, 4]; +#nextln: $()Reference "r_array_2" is not a reference to a mutable value (`&mut`). +#check: $()References dereferenced in assignment targets must be references to mutable values (`&mut`). + +#check: $()Reference is not a reference to a mutable value (`&mut`) +#check: $()let r_array_3 = &get_array( +#nextln: $()Reference "r_array_3" is declared here as a reference to immutable value. +#check: $()This expression has type "&[u64; 3]" instead of "&mut [u64; 3]". +#nextln: $()Consider taking here a reference to a mutable value: `&mut get_array(...`. +#check: $()*r_array_3 = [2, 3, 4]; +#nextln: $()Reference "r_array_3" is not a reference to a mutable value (`&mut`). +#check: $()References dereferenced in assignment targets must be references to mutable values (`&mut`). + +#check: $()Reference is not a reference to a mutable value (`&mut`) +#check: $()*&array = [2, 3, 4]; +#nextln: $()This reference expression is not a reference to a mutable value (`&mut`). +#nextln: $()It has type "&[u64; 3]" instead of "&mut [u64; 3]". +#check: $()Reference expressions dereferenced in assignment targets must result in references to mutable values (`&mut`). diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/dca/reassignment_lhs/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/dca/reassignment_lhs/Forc.lock new file mode 100644 index 00000000000..12e2016f1ed --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/dca/reassignment_lhs/Forc.lock @@ -0,0 +1,8 @@ +[[package]] +name = "core" +source = "path+from-root-565B0B583CE96EA8" + +[[package]] +name = "reassignment_lhs" +source = "member" +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/dca/reassignment_lhs/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/dca/reassignment_lhs/Forc.toml new file mode 100644 index 00000000000..c926215fcf6 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/dca/reassignment_lhs/Forc.toml @@ -0,0 +1,9 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "reassignment_lhs" +implicit-std = false + +[dependencies] +core = { path = "../../../../../../../sway-lib-core" } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/dca/reassignment_lhs/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/dca/reassignment_lhs/src/main.sw new file mode 100644 index 00000000000..50bdb37fddd --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/dca/reassignment_lhs/src/main.sw @@ -0,0 +1,50 @@ +script; + +const CONST: u64 = 0; + +fn function_1() -> u64 { + 0 +} + +struct S1 {} + +impl S1 { + fn method(self) -> u64 { + 0 + } +} + +fn function_2() -> u64 { + 0 +} + +struct S2 {} + +impl S2 { + fn method(self) -> u64 { + 0 + } +} + +fn main() { + // This test proves that https://github.com/FuelLabs/sway/issues/5920 is fixed. + let mut array = [1, 2, 3]; + + let i = 0; + array[i] = 0; + + array[CONST] = 0; + + array[function_1()] = 0; + + array[S1 {}.method()] = 0; + + // This proves that LHS is properly analyzed in DCA also for dereferencing. + let mut x = 0; + + *&mut x = 0; + + *&mut function_2() = 0; + + *&mut S2 {}.method() = 0; +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/dca/reassignment_lhs/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/dca/reassignment_lhs/test.toml new file mode 100644 index 00000000000..555fafa187b --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/dca/reassignment_lhs/test.toml @@ -0,0 +1,3 @@ +category = "compile" + +expected_warnings = 0 diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/reassignment_rhs_lhs_evaluation_order/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/language/reassignment_rhs_lhs_evaluation_order/Forc.lock new file mode 100644 index 00000000000..f95f5383aa8 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/reassignment_rhs_lhs_evaluation_order/Forc.lock @@ -0,0 +1,13 @@ +[[package]] +name = "core" +source = "path+from-root-DB0AC437AEFB8078" + +[[package]] +name = "reassignment_rhs_lhs_evaluation_order" +source = "member" +dependencies = ["std"] + +[[package]] +name = "std" +source = "path+from-root-DB0AC437AEFB8078" +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/reassignment_rhs_lhs_evaluation_order/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/reassignment_rhs_lhs_evaluation_order/Forc.toml new file mode 100644 index 00000000000..61c26555832 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/reassignment_rhs_lhs_evaluation_order/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "reassignment_rhs_lhs_evaluation_order" + +[dependencies] +std = { path = "../../../../reduced_std_libs/sway-lib-std-assert" } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/reassignment_rhs_lhs_evaluation_order/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/reassignment_rhs_lhs_evaluation_order/src/main.sw new file mode 100644 index 00000000000..a2eacf74934 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/reassignment_rhs_lhs_evaluation_order/src/main.sw @@ -0,0 +1,21 @@ +script; + +// In Sway, like in Rust, we first evaluate the RHS. + +fn inc_i(ref mut i: u64) -> u64 { + i += 1; + i +} + +fn main() -> u64 { + let mut array = [0, 0, 0]; + let mut i = 0; + + array[inc_i(i)] = inc_i(i); + + assert_eq(array[0], 0); + assert_eq(array[1], 0); + assert_eq(array[2], 1); + + 1 +} diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/reassignment_rhs_lhs_evaluation_order/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/reassignment_rhs_lhs_evaluation_order/test.toml new file mode 100644 index 00000000000..96bce34f094 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/reassignment_rhs_lhs_evaluation_order/test.toml @@ -0,0 +1,4 @@ +category = "run" +expected_result = { action = "return", value = 1 } +expected_result_new_encoding = { action = "return_data", value = "0000000000000001" } +validate_abi = false diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_dot_on_structs/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_dot_on_structs/test.toml index 933d916e3e4..10272c8b101 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_dot_on_structs/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_dot_on_structs/test.toml @@ -2,4 +2,4 @@ category = "run" expected_result = { action = "return", value = 42 } expected_result_new_encoding = { action = "return_data", value = "000000000000002A" } validate_abi = false -expected_warnings = 50 +expected_warnings = 2 # TODO-DCA: Set to zero once https://github.com/FuelLabs/sway/issues/5921 is fixed. diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_dot_on_tuples/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_dot_on_tuples/test.toml index 933d916e3e4..c080b869f36 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_dot_on_tuples/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_dot_on_tuples/test.toml @@ -2,4 +2,4 @@ category = "run" expected_result = { action = "return", value = 42 } expected_result_new_encoding = { action = "return_data", value = "000000000000002A" } validate_abi = false -expected_warnings = 50 +expected_warnings = 0 diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_index/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_index/test.toml index 933d916e3e4..f466a63df11 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_index/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_index/test.toml @@ -2,4 +2,4 @@ category = "run" expected_result = { action = "return", value = 42 } expected_result_new_encoding = { action = "return_data", value = "000000000000002A" } validate_abi = false -expected_warnings = 50 +expected_warnings = 50 # TODO-DCA: Set to zero once https://github.com/FuelLabs/sway/issues/5921 is fixed. diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_star/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_star/test.toml index ad0971b22ec..c080b869f36 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_star/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_star/test.toml @@ -1,6 +1,5 @@ category = "run" expected_result = { action = "return", value = 42 } expected_result_new_encoding = { action = "return_data", value = "000000000000002A" } - validate_abi = false -expected_warnings = 9 +expected_warnings = 0 diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/impl_reference_types/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/impl_reference_types/test.toml index 7141af3908c..c7f97d68a1a 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/impl_reference_types/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/impl_reference_types/test.toml @@ -2,4 +2,4 @@ category = "run" expected_result = { action = "return", value = 42 } expected_result_new_encoding = { action = "return_data", value = "000000000000002A" } validate_abi = false -expected_warnings = 31 #TODO-DCA: Set to zero once DCA issues are solved. +expected_warnings = 26 # TODO-DCA: Set to zero once https://github.com/FuelLabs/sway/issues/5921 is fixed. diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/passing_and_returning_references_to_and_from_functions/json_abi_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/passing_and_returning_references_to_and_from_functions/json_abi_oracle.json deleted file mode 100644 index ad50b55d54c..00000000000 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/passing_and_returning_references_to_and_from_functions/json_abi_oracle.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "configurables": [], - "functions": [ - { - "attributes": null, - "inputs": [], - "name": "main", - "output": { - "name": "", - "type": 0, - "typeArguments": null - } - } - ], - "loggedTypes": [], - "messagesTypes": [], - "types": [ - { - "components": null, - "type": "u64", - "typeId": 0, - "typeParameters": null - } - ] -} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/passing_and_returning_references_to_and_from_functions/json_abi_oracle_new_encoding.json b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/passing_and_returning_references_to_and_from_functions/json_abi_oracle_new_encoding.json deleted file mode 100644 index 068da3305ab..00000000000 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/passing_and_returning_references_to_and_from_functions/json_abi_oracle_new_encoding.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "configurables": [], - "encoding": "1", - "functions": [ - { - "attributes": null, - "inputs": [], - "name": "main", - "output": { - "name": "", - "type": 0, - "typeArguments": null - } - } - ], - "loggedTypes": [], - "messagesTypes": [], - "types": [ - { - "components": null, - "type": "u64", - "typeId": 0, - "typeParameters": null - } - ] -} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/passing_and_returning_references_to_and_from_functions/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/passing_and_returning_references_to_and_from_functions/test.toml index 0a63779e717..c080b869f36 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/passing_and_returning_references_to_and_from_functions/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/passing_and_returning_references_to_and_from_functions/test.toml @@ -1,5 +1,5 @@ category = "run" expected_result = { action = "return", value = 42 } expected_result_new_encoding = { action = "return_data", value = "000000000000002A" } -validate_abi = true +validate_abi = false expected_warnings = 0 diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_in_aggregates/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_in_aggregates/Forc.lock new file mode 100644 index 00000000000..42269fa9372 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_in_aggregates/Forc.lock @@ -0,0 +1,13 @@ +[[package]] +name = "core" +source = "path+from-root-05D2D390AACBA529" + +[[package]] +name = "reassigning_via_references_in_aggregates" +source = "member" +dependencies = ["std"] + +[[package]] +name = "std" +source = "path+from-root-05D2D390AACBA529" +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_in_aggregates/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_in_aggregates/Forc.toml new file mode 100644 index 00000000000..861b11cdac3 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_in_aggregates/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "reassigning_via_references_in_aggregates" + +[dependencies] +std = { path = "../../../../../reduced_std_libs/sway-lib-std-assert" } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_in_aggregates/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_in_aggregates/src/main.sw new file mode 100644 index 00000000000..c8139b1560b --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_in_aggregates/src/main.sw @@ -0,0 +1,208 @@ +script; + +struct A { + r_u8: &mut u8, + r_array: &mut [u64;3], +} + +impl A { + fn new() -> Self { + Self { r_u8: &mut 0, r_array: &mut [0, 0, 0] } + } + + fn use_me(self) { + poke(self.r_u8); + poke(self.r_array); + } +} + +struct B { + r_a: &A, + r_array: &[&A;3], +} + +impl B { + fn new() -> Self { + let r_a = &A::new(); + Self { r_a: r_a, r_array: &[r_a, r_a, r_a] } + } + + fn use_me(self) { + poke(self.r_a); + poke(self.r_array); + } +} + +impl core::ops::Eq for [u64;3] { + fn eq(self, other: Self) -> bool { + self[0] == other[0] && self[1] == other[1] && self[2] == other[2] + } +} + +#[inline(always)] +fn in_structs() { + let mut x = 11u8; + let mut array: [u64;3] = [11, 11, 11]; + + let a = A { r_u8: &mut x, r_array: &mut array }; + let b = B { r_a: &a, r_array: &[&a, &a, &a] }; + + *a.r_u8 = 22; + assert_eq(x, 22); + + *a.r_array = [22, 22, 22]; + assert_eq(array, [22, 22, 22]); + + *b.r_a.r_u8 = 33; + assert_eq(x, 33); + + *b.r_array[0].r_u8 = 44; + assert_eq(x, 44); + + *b.r_array[0].r_array = [33, 33, 33]; + assert_eq(array, [33, 33, 33]); +} + +#[inline(never)] +fn in_structs_not_inlined() { + in_structs() +} + +enum E { + R_A: &A, + R_B: &B, +} + +#[inline(always)] +fn in_enums() { + let mut x = 11u8; + let mut array: [u64;3] = [11, 11, 11]; + + let a = A { r_u8: &mut x, r_array: &mut array }; + let b = B { r_a: &a, r_array: &[&a, &a, &a] }; + + let e_r_a = E::R_A(&a); + let e_r_b = E::R_B(&b); + + match e_r_a { + E::R_A(r_a) => { + *r_a.r_u8 = 22; + assert_eq(x, 22); + + *r_a.r_array = [22, 22, 22]; + assert_eq(array, [22, 22, 22]); + } + _ => assert(false), + } + + match e_r_b { + E::R_B(r_b) => { + *r_b.r_a.r_u8 = 33; + assert_eq(x, 33); + + *r_b.r_array[0].r_u8 = 44; + assert_eq(x, 44); + + *r_b.r_array[0].r_array = [33, 33, 33]; + assert_eq(array, [33, 33, 33]); + } + _ => assert(false), + } +} + +#[inline(never)] +fn in_enums_not_inlined() { + in_enums() +} + +#[inline(always)] +fn in_arrays() { + let mut x = 11u8; + let mut array: [u64;3] = [11, 11, 11]; + + let a = A { r_u8: &mut x, r_array: &mut array }; + let b = B { r_a: &a, r_array: &[&a, &a, &a] }; + + let arr_a = [&a, &a, &a]; + let arr_b = [&b, &b, &b]; + + *arr_a[0].r_u8 = 22; + assert_eq(x, 22); + + *arr_a[1].r_array = [22, 22, 22]; + assert_eq(array, [22, 22, 22]); + + *arr_b[0].r_a.r_u8 = 33; + assert_eq(x, 33); + + *arr_b[1].r_array[0].r_u8 = 44; + assert_eq(x, 44); + + *arr_b[2].r_array[0].r_array = [33, 33, 33]; + assert_eq(array, [33, 33, 33]); +} + +#[inline(never)] +fn in_arrays_not_inlined() { + in_arrays() +} + +#[inline(always)] +fn in_tuples() { + let mut x = 11u8; + let mut array: [u64;3] = [11, 11, 11]; + + let a = A { r_u8: &mut x, r_array: &mut array }; + let b = B { r_a: &a, r_array: &[&a, &a, &a] }; + + let tuple_a = (&a, &a, &a); + let tuple_b = (&b, &b, &b); + + *tuple_a.0.r_u8 = 22; + assert_eq(x, 22); + + *tuple_a.1.r_array = [22, 22, 22]; + assert_eq(array, [22, 22, 22]); + + *tuple_b.0.r_a.r_u8 = 33; + assert_eq(x, 33); + + *tuple_b.1.r_array[0].r_u8 = 44; + assert_eq(x, 44); + + *tuple_b.2.r_array[0].r_array = [33, 33, 33]; + assert_eq(array, [33, 33, 33]); +} + +#[inline(never)] +fn in_tuples_not_inlined() { + in_tuples() +} + +#[inline(never)] +fn test_all_inlined() { + in_structs(); + in_enums(); + in_arrays(); + in_tuples(); +} + +#[inline(never)] +fn test_not_inlined() { + in_structs_not_inlined(); + in_enums_not_inlined(); + in_arrays_not_inlined(); + in_tuples_not_inlined(); +} + +fn main() -> u64 { + test_all_inlined(); + test_not_inlined(); + + A::new().use_me(); + B::new().use_me(); + + 42 +} + +fn poke(_x: T) { } \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_in_aggregates/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_in_aggregates/test.toml new file mode 100644 index 00000000000..10272c8b101 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_in_aggregates/test.toml @@ -0,0 +1,5 @@ +category = "run" +expected_result = { action = "return", value = 42 } +expected_result_new_encoding = { action = "return_data", value = "000000000000002A" } +validate_abi = false +expected_warnings = 2 # TODO-DCA: Set to zero once https://github.com/FuelLabs/sway/issues/5921 is fixed. diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_passed_and_returned_to_and_from_functions/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_passed_and_returned_to_and_from_functions/Forc.lock new file mode 100644 index 00000000000..187112e14e2 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_passed_and_returned_to_and_from_functions/Forc.lock @@ -0,0 +1,13 @@ +[[package]] +name = "core" +source = "path+from-root-789914A5CBC0F1A9" + +[[package]] +name = "reassigning_via_references_passed_and_returned_to_and_from_functions" +source = "member" +dependencies = ["std"] + +[[package]] +name = "std" +source = "path+from-root-789914A5CBC0F1A9" +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_passed_and_returned_to_and_from_functions/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_passed_and_returned_to_and_from_functions/Forc.toml new file mode 100644 index 00000000000..35be5586dc9 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_passed_and_returned_to_and_from_functions/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "reassigning_via_references_passed_and_returned_to_and_from_functions" + +[dependencies] +std = { path = "../../../../../reduced_std_libs/sway-lib-std-assert" } \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_passed_and_returned_to_and_from_functions/src/impls.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_passed_and_returned_to_and_from_functions/src/impls.sw new file mode 100644 index 00000000000..c9de1b3d7c2 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_passed_and_returned_to_and_from_functions/src/impls.sw @@ -0,0 +1,262 @@ +library; + +use core::ops::Eq; + +pub trait TestInstance { + fn new() -> Self; + fn different() -> Self; +} + +impl TestInstance for bool { + fn new() -> Self { + true + } + fn different() -> Self { + false + } +} + +impl TestInstance for u8 { + fn new() -> Self { + 123 + } + fn different() -> Self { + 223 + } +} + +impl TestInstance for u16 { + fn new() -> Self { + 1234 + } + fn different() -> Self { + 4321 + } +} + +impl TestInstance for u32 { + fn new() -> Self { + 12345 + } + fn different() -> Self { + 54321 + } +} + +impl TestInstance for u64 { + fn new() -> Self { + 123456 + } + fn different() -> Self { + 654321 + } +} + +impl TestInstance for u256 { + fn new() -> Self { + 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20u256 + } + fn different() -> Self { + 0x0203040405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1f1a20u256 + } +} + +impl TestInstance for str { + fn new() -> Self { + "1a2B3c" + } + fn different() -> Self { + "3A2b1C" + } +} + +impl Eq for str[6] { + fn eq(self, other: Self) -> bool { + let mut i = 0; + while i < 6 { + let ptr_self = __addr_of(self).add::(i); + let ptr_other = __addr_of(other).add::(i); + + if ptr_self.read::() != ptr_other.read::() { + return false; + } + + i = i + 1; + }; + + true + } +} + +impl TestInstance for str[6] { + fn new() -> Self { + __to_str_array("1a2B3c") + } + fn different() -> Self { + __to_str_array("3A2b1C") + } +} + +impl Eq for [u64;2] { + fn eq(self, other: Self) -> bool { + self[0] == other[0] && self[1] == other[1] + } +} + +impl TestInstance for [u64;2] { + fn new() -> Self { + [123456, 654321] + } + fn different() -> Self { + [654321, 123456] + } +} + +pub struct Struct { + pub x: u64, +} + +impl Eq for Struct { + fn eq(self, other: Self) -> bool { + self.x == other.x + } +} + +impl TestInstance for Struct { + fn new() -> Self { + Self { x: 98765 } + } + fn different() -> Self { + Self { x: 56789 } + } +} + +pub struct EmptyStruct { } + +impl Eq for EmptyStruct { + fn eq(self, other: Self) -> bool { + true + } +} + +impl TestInstance for EmptyStruct { + fn new() -> Self { + EmptyStruct { } + } + fn different() -> Self { + EmptyStruct { } + } +} + +pub enum Enum { + A: u64, +} + +impl Eq for Enum { + fn eq(self, other: Self) -> bool { + match (self, other) { + (Enum::A(l), Enum::A(r)) => l == r, + } + } +} + +impl TestInstance for Enum { + fn new() -> Self { + Self::A(123456) + } + fn different() -> Self { + Self::A(654321) + } +} + +impl Eq for (u8, u32) { + fn eq(self, other: Self) -> bool { + self.0 == other.0 && self.1 == other.1 + } +} + +impl TestInstance for (u8, u32) { + fn new() -> Self { + (123, 12345) + } + fn different() -> Self { + (223, 54321) + } +} + +impl TestInstance for b256 { + fn new() -> Self { + 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 + } + fn different() -> Self { + 0x0202020405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1f1a20 + } +} + +impl TestInstance for raw_ptr { + fn new() -> Self { + let null_ptr = asm() { zero: raw_ptr }; + + null_ptr.add::(42) + } + fn different() -> Self { + let null_ptr = asm() { zero: raw_ptr }; + + null_ptr.add::(42*2) + } +} + +impl AbiEncode for raw_ptr { + fn abi_encode(self, ref mut buffer: Buffer) { + buffer.push_u64(asm(p: self) { p: u64 }); + } +} + +impl TestInstance for raw_slice { + fn new() -> Self { + let null_ptr = asm() { zero: raw_ptr }; + + std::raw_slice::from_parts::(null_ptr, 42) + } + fn different() -> Self { + let null_ptr = asm() { zero: raw_ptr }; + + std::raw_slice::from_parts::(null_ptr, 42*2) + } +} + +impl Eq for raw_slice { + fn eq(self, other: Self) -> bool { + self.ptr() == other.ptr() && self.number_of_bytes() == other.number_of_bytes() + } +} + +impl TestInstance for () { + fn new() -> Self { + () + } + fn different() -> Self { + () + } +} + +impl Eq for () { + fn eq(self, other: Self) -> bool { + true + } +} + +impl TestInstance for [u64;0] { + fn new() -> Self { + [] + } + fn different() -> Self { + [] + } +} + +impl Eq for [u64;0] { + fn eq(self, other: Self) -> bool { + true + } +} diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_passed_and_returned_to_and_from_functions/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_passed_and_returned_to_and_from_functions/src/main.sw new file mode 100644 index 00000000000..7a81f22db09 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_passed_and_returned_to_and_from_functions/src/main.sw @@ -0,0 +1,225 @@ +script; + +mod impls; +use impls::*; + +#[inline(always)] +fn reference_to_copy_type() { + let mut x = 11u8; + let mut y = 11u8; + let mut z = 11u8; + + let r_mut_x = &mut x; + let r_r_mut_y = & &mut y; + let r_r_r_mut_z = &mut & &mut z; + + let ret = copy_type_ref(&mut x, & &mut y, &mut & &mut z, 11, 22); + assert_eq(x, 22); + assert_eq(y, 22); + assert_eq(z, 22); + + *ret.0 = 33; + **ret.1 = 33; + ***ret.2 = 33; + + assert_eq(x, 33); + assert_eq(y, 33); + assert_eq(z, 33); + + let ret = copy_type_ref(r_mut_x, r_r_mut_y, r_r_r_mut_z, 33, 44); + assert_eq(x, 44); + assert_eq(y, 44); + assert_eq(z, 44); + + *ret.0 = 55; + **ret.1 = 55; + ***ret.2 = 55; + + assert_eq(x, 55); + assert_eq(y, 55); + assert_eq(z, 55); +} + +#[inline(never)] +fn reference_to_copy_type_not_inlined() { + reference_to_copy_type() +} + +fn copy_type_ref(r_mut: &mut u8, r_r_mut: & &mut u8, r_r_r_mut: &mut & & mut u8, old_value: u8, new_value: u8) -> (&mut u8, & &mut u8, &mut & &mut u8) { + assert_eq(*r_mut, old_value); + assert_eq(**r_r_mut, old_value); + assert_eq(***r_r_r_mut, old_value); + + *r_mut = new_value; + **r_r_mut = new_value; + ***r_r_r_mut = new_value; + + assert_eq(*r_mut, new_value); + assert_eq(**r_r_mut, new_value); + assert_eq(***r_r_r_mut, new_value); + + (r_mut, r_r_mut, r_r_r_mut) +} + +#[inline(always)] +fn reference_to_aggregate() { + let mut x = Struct { x: 11u64 }; + let mut y = Struct { x: 11u64 }; + let mut z = Struct { x: 11u64 }; + + let r_mut_x = &mut x; + let r_r_mut_y = & &mut y; + let r_r_r_mut_z = &mut & &mut z; + + let ret = aggregate_ref(&mut x, & &mut y, &mut & &mut z, Struct { x: 11 }, Struct { x: 22 }); + assert_eq(x, Struct { x: 22 }); + assert_eq(y, Struct { x: 22 }); + assert_eq(z, Struct { x: 22 }); + + *ret.0 = Struct { x: 33 }; + **ret.1 = Struct { x: 33 }; + ***ret.2 = Struct { x: 33 }; + + assert_eq(x, Struct { x: 33 }); + assert_eq(y, Struct { x: 33 }); + assert_eq(z, Struct { x: 33 }); + + let ret = aggregate_ref(r_mut_x, r_r_mut_y, r_r_r_mut_z, Struct { x: 33 }, Struct { x: 44 }); + assert_eq(x, Struct { x: 44 }); + assert_eq(y, Struct { x: 44 }); + assert_eq(z, Struct { x: 44 }); + + *ret.0 = Struct { x: 55 }; + **ret.1 = Struct { x: 55 }; + ***ret.2 = Struct { x: 55 }; + + assert_eq(x, Struct { x: 55 }); + assert_eq(y, Struct { x: 55 }); + assert_eq(z, Struct { x: 55 }); +} + +#[inline(never)] +fn reference_to_aggregate_not_inlined() { + reference_to_aggregate() +} + +fn aggregate_ref(r_mut: &mut Struct, r_r_mut: & &mut Struct, r_r_r_mut: &mut & & mut Struct, old_value: Struct, new_value: Struct) -> (&mut Struct, & &mut Struct, &mut & &mut Struct) { + assert_eq(*r_mut, old_value); + assert_eq(**r_r_mut, old_value); + assert_eq(***r_r_r_mut, old_value); + + *r_mut = new_value; + **r_r_mut = new_value; + ***r_r_r_mut = new_value; + + assert_eq(*r_mut, new_value); + assert_eq(**r_r_mut, new_value); + assert_eq(***r_r_r_mut, new_value); + + (r_mut, r_r_mut, r_r_r_mut) +} + +#[inline(always)] +fn reference_to_generic() { + reference_to_generic_test::<()>(); + reference_to_generic_test::(); + reference_to_generic_test::(); + reference_to_generic_test::(); + reference_to_generic_test::(); + reference_to_generic_test::(); + reference_to_generic_test::(); + reference_to_generic_test::<[u64;2]>(); + reference_to_generic_test::<[u64;0]>(); + reference_to_generic_test::(); + reference_to_generic_test::(); + reference_to_generic_test::(); + reference_to_generic_test::(); + reference_to_generic_test::(); + reference_to_generic_test::<(u8, u32)>(); + reference_to_generic_test::(); + reference_to_generic_test::(); + reference_to_generic_test::(); +} + +#[inline(always)] +fn reference_to_generic_test() + where T: AbiEncode + TestInstance + Eq +{ + let mut x = T::new(); + let mut y = T::new(); + let mut z = T::new(); + + let r_mut_x = &mut x; + let r_r_mut_y = & &mut y; + let r_r_r_mut_z = &mut & &mut z; + + let ret = generic_ref(&mut x, & &mut y, &mut & &mut z, T::new(), T::different()); + assert_eq(x, T::different()); + assert_eq(y, T::different()); + assert_eq(z, T::different()); + + *ret.0 = T::new(); + **ret.1 = T::new(); + ***ret.2 = T::new(); + + assert_eq(x, T::new()); + assert_eq(y, T::new()); + assert_eq(z, T::new()); + + let ret = generic_ref(r_mut_x, r_r_mut_y, r_r_r_mut_z, T::new(), T::different()); + assert_eq(x, T::different()); + assert_eq(y, T::different()); + assert_eq(z, T::different()); + + *ret.0 = T::new(); + **ret.1 = T::new(); + ***ret.2 = T::new(); + + assert_eq(x, T::new()); + assert_eq(y, T::new()); + assert_eq(z, T::new()); +} + +#[inline(never)] +fn reference_to_generic_not_inlined() { + reference_to_generic() +} + +fn generic_ref(r_mut: &mut T, r_r_mut: & &mut T, r_r_r_mut: &mut & & mut T, old_value: T, new_value: T) -> (&mut T, & &mut T, &mut & &mut T) + where T: AbiEncode + Eq +{ + assert_eq(*r_mut, old_value); + assert_eq(**r_r_mut, old_value); + assert_eq(***r_r_r_mut, old_value); + + *r_mut = new_value; + **r_r_mut = new_value; + ***r_r_r_mut = new_value; + + assert_eq(*r_mut, new_value); + assert_eq(**r_r_mut, new_value); + assert_eq(***r_r_r_mut, new_value); + + (r_mut, r_r_mut, r_r_r_mut) +} + +#[inline(never)] +fn test_all_inlined() { + reference_to_copy_type(); + reference_to_aggregate(); + reference_to_generic(); +} + +#[inline(never)] +fn test_not_inlined() { + reference_to_copy_type_not_inlined(); + reference_to_aggregate_not_inlined(); + reference_to_generic_not_inlined(); +} + +fn main() -> u64 { + test_all_inlined(); + test_not_inlined(); + + 42 +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_passed_and_returned_to_and_from_functions/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_passed_and_returned_to_and_from_functions/test.toml new file mode 100644 index 00000000000..1e215f41047 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_passed_and_returned_to_and_from_functions/test.toml @@ -0,0 +1,5 @@ +category = "run" +expected_result = { action = "return", value = 42 } +expected_result_new_encoding = { action = "return_data", value = "000000000000002A" } +validate_abi = false +expected_warnings = 11 # TODO-DCA: Set to zero once https://github.com/FuelLabs/sway/issues/5921 is fixed. diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_expressions/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_expressions/Forc.lock new file mode 100644 index 00000000000..49c6cbed1a6 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_expressions/Forc.lock @@ -0,0 +1,13 @@ +[[package]] +name = "core" +source = "path+from-root-A0FF837F10263FC2" + +[[package]] +name = "reassigning_via_references_to_expressions" +source = "member" +dependencies = ["std"] + +[[package]] +name = "std" +source = "path+from-root-A0FF837F10263FC2" +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_expressions/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_expressions/Forc.toml new file mode 100644 index 00000000000..0455da1e0f4 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_expressions/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "reassigning_via_references_to_expressions" + +[dependencies] +std = { path = "../../../../../reduced_std_libs/sway-lib-std-conversions" } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_expressions/src/impls.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_expressions/src/impls.sw new file mode 100644 index 00000000000..5d96382c54a --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_expressions/src/impls.sw @@ -0,0 +1,264 @@ +library; + +use core::ops::Eq; +use std::bytes_conversions::u256::*; +use std::bytes_conversions::b256::*; + +pub trait TestInstance { + fn new() -> Self; + fn different() -> Self; +} + +impl TestInstance for bool { + fn new() -> Self { + true + } + fn different() -> Self { + false + } +} + +impl TestInstance for u8 { + fn new() -> Self { + 123 + } + fn different() -> Self { + 223 + } +} + +impl TestInstance for u16 { + fn new() -> Self { + 1234 + } + fn different() -> Self { + 4321 + } +} + +impl TestInstance for u32 { + fn new() -> Self { + 12345 + } + fn different() -> Self { + 54321 + } +} + +impl TestInstance for u64 { + fn new() -> Self { + 123456 + } + fn different() -> Self { + 654321 + } +} + +impl TestInstance for u256 { + fn new() -> Self { + 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20u256 + } + fn different() -> Self { + 0x0203040405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1f1a20u256 + } +} + +impl TestInstance for str { + fn new() -> Self { + "1a2B3c" + } + fn different() -> Self { + "3A2b1C" + } +} + +impl Eq for str[6] { + fn eq(self, other: Self) -> bool { + let mut i = 0; + while i < 6 { + let ptr_self = __addr_of(self).add::(i); + let ptr_other = __addr_of(other).add::(i); + + if ptr_self.read::() != ptr_other.read::() { + return false; + } + + i = i + 1; + }; + + true + } +} + +impl TestInstance for str[6] { + fn new() -> Self { + __to_str_array("1a2B3c") + } + fn different() -> Self { + __to_str_array("3A2b1C") + } +} + +impl Eq for [u64;2] { + fn eq(self, other: Self) -> bool { + self[0] == other[0] && self[1] == other[1] + } +} + +impl TestInstance for [u64;2] { + fn new() -> Self { + [123456, 654321] + } + fn different() -> Self { + [654321, 123456] + } +} + +pub struct Struct { + pub x: u64, +} + +impl Eq for Struct { + fn eq(self, other: Self) -> bool { + self.x == other.x + } +} + +impl TestInstance for Struct { + fn new() -> Self { + Self { x: 98765 } + } + fn different() -> Self { + Self { x: 56789 } + } +} + +pub struct EmptyStruct { } + +impl Eq for EmptyStruct { + fn eq(self, other: Self) -> bool { + true + } +} + +impl TestInstance for EmptyStruct { + fn new() -> Self { + EmptyStruct { } + } + fn different() -> Self { + EmptyStruct { } + } +} + +pub enum Enum { + A: u64, +} + +impl Eq for Enum { + fn eq(self, other: Self) -> bool { + match (self, other) { + (Enum::A(l), Enum::A(r)) => l == r, + } + } +} + +impl TestInstance for Enum { + fn new() -> Self { + Self::A(123456) + } + fn different() -> Self { + Self::A(654321) + } +} + +impl Eq for (u8, u32) { + fn eq(self, other: Self) -> bool { + self.0 == other.0 && self.1 == other.1 + } +} + +impl TestInstance for (u8, u32) { + fn new() -> Self { + (123, 12345) + } + fn different() -> Self { + (223, 54321) + } +} + +impl TestInstance for b256 { + fn new() -> Self { + 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 + } + fn different() -> Self { + 0x0202020405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1f1a20 + } +} + +impl TestInstance for raw_ptr { + fn new() -> Self { + let null_ptr = asm() { zero: raw_ptr }; + + null_ptr.add::(42) + } + fn different() -> Self { + let null_ptr = asm() { zero: raw_ptr }; + + null_ptr.add::(42*2) + } +} + +impl AbiEncode for raw_ptr { + fn abi_encode(self, ref mut buffer: Buffer) { + buffer.push_u64(asm(p: self) { p: u64 }); + } +} + +impl TestInstance for raw_slice { + fn new() -> Self { + let null_ptr = asm() { zero: raw_ptr }; + + std::raw_slice::from_parts::(null_ptr, 42) + } + fn different() -> Self { + let null_ptr = asm() { zero: raw_ptr }; + + std::raw_slice::from_parts::(null_ptr, 42*2) + } +} + +impl Eq for raw_slice { + fn eq(self, other: Self) -> bool { + self.ptr() == other.ptr() && self.number_of_bytes() == other.number_of_bytes() + } +} + +impl TestInstance for () { + fn new() -> Self { + () + } + fn different() -> Self { + () + } +} + +impl Eq for () { + fn eq(self, other: Self) -> bool { + true + } +} + +impl TestInstance for [u64;0] { + fn new() -> Self { + [] + } + fn different() -> Self { + [] + } +} + +impl Eq for [u64;0] { + fn eq(self, other: Self) -> bool { + true + } +} diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_expressions/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_expressions/src/main.sw new file mode 100644 index 00000000000..b4f8d48fffe --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_expressions/src/main.sw @@ -0,0 +1,217 @@ +script; + +mod impls; +use impls::*; +use core::ops::Eq; + +// TODO-IG: Add tests for other expressions. + +const USE_LOCAL_VARIABLE: u8 = 0; +const USE_TEMPORARY_VALUE: u8 = 1; +const USE_NON_MUT_REF_MUT_PARAMETER: u8 = 2; +// TODO-IG: Once implemented, add tests for `mut` parameters. +// const USE_MUT_PARAMETER: u8 = 3; + +// All tests are arranged in a way that the value requested via `to_use` +// parameter is changed from `T::new()` to `T::different()`. +// This function asserts that only the requested change is properly done. +fn check_changes(to_use: u8, local: T, non_mut_ref_mut: T) where T: AbiEncode + TestInstance + Eq { + if to_use == USE_LOCAL_VARIABLE { + assert_eq(local, T::different()); + assert_eq(non_mut_ref_mut, T::new()); + } else if to_use == USE_TEMPORARY_VALUE { + assert_eq(local, T::new()); + assert_eq(non_mut_ref_mut, T::new()); + } else if to_use == USE_NON_MUT_REF_MUT_PARAMETER { + assert_eq(local, T::new()); + assert_eq(non_mut_ref_mut, T::different()); + } +} + +#[inline(always)] +fn if_expr(to_use: u8, r_m: &mut T) where T: AbiEncode + TestInstance + Eq { + let mut x = T::new(); + + assert_eq(*r_m, T::new()); + + *if to_use == USE_LOCAL_VARIABLE { + &mut x + } else if to_use == USE_TEMPORARY_VALUE { + &mut T::new() + } else if to_use == USE_NON_MUT_REF_MUT_PARAMETER { + r_m + } else { + revert(1122334455) + } = T::different(); + + check_changes(to_use, x, *r_m); +} + +#[inline(always)] +fn test_if_expr(to_use: u8) where T: AbiEncode + TestInstance + Eq { + let mut t = T::new(); + if_expr(to_use, &mut t); + + if to_use == USE_NON_MUT_REF_MUT_PARAMETER { + assert_eq(t, T::different()); + } +} + +#[inline(never)] +fn test_if_expr_not_inlined(to_use: u8) where T: AbiEncode + TestInstance + Eq { + test_if_expr::(to_use) +} + +#[inline(always)] +fn inlined_function(r_m: &mut T) -> &mut T where T: AbiEncode + TestInstance + Eq { + r_m +} + +#[inline(never)] +fn non_inlined_function(r_m: &mut T) -> &mut T where T: AbiEncode + TestInstance + Eq { + r_m +} + +#[inline(always)] +fn function_call(to_use: u8, r_m: &mut T) where T: AbiEncode + TestInstance + Eq { + let mut x = T::new(); + + assert_eq(*r_m, T::new()); + + if to_use == USE_LOCAL_VARIABLE { + *inlined_function(&mut x) = T::different(); + } else if to_use == USE_TEMPORARY_VALUE { + *inlined_function(&mut T::new()) = T::different(); + } else if to_use == USE_NON_MUT_REF_MUT_PARAMETER { + *inlined_function(r_m) = T::different(); + } else { + revert(1122334455); + } + + check_changes(to_use, x, *r_m); + + // Reset the values. + x = T::new(); + *r_m = T::new(); + + if to_use == USE_LOCAL_VARIABLE { + *non_inlined_function(&mut x) = T::different(); + } else if to_use == USE_TEMPORARY_VALUE { + *non_inlined_function(&mut T::new()) = T::different(); + } else if to_use == USE_NON_MUT_REF_MUT_PARAMETER { + *non_inlined_function(r_m) = T::different(); + } else { + revert(1122334455); + } + + check_changes(to_use, x, *r_m); +} + +#[inline(always)] +fn test_function_call(to_use: u8) where T: AbiEncode + TestInstance + Eq { + let mut t = T::new(); + function_call(to_use, &mut t); + + if to_use == USE_NON_MUT_REF_MUT_PARAMETER { + assert_eq(t, T::different()); + } +} + +#[inline(never)] +fn test_function_call_not_inlined(to_use: u8) where T: AbiEncode + TestInstance + Eq { + test_function_call::(to_use) +} + +#[inline(never)] +fn test_all_inlined(to_use: u8) { + test_if_expr::<()>(to_use); + test_if_expr::(to_use); + test_if_expr::(to_use); + test_if_expr::(to_use); + test_if_expr::(to_use); + test_if_expr::(to_use); + test_if_expr::(to_use); + test_if_expr::<[u64;2]>(to_use); + test_if_expr::<[u64;0]>(to_use); + test_if_expr::(to_use); + test_if_expr::(to_use); + test_if_expr::(to_use); + test_if_expr::(to_use); + test_if_expr::(to_use); + test_if_expr::<(u8, u32)>(to_use); + test_if_expr::(to_use); + test_if_expr::(to_use); + test_if_expr::(to_use); + + test_function_call::<()>(to_use); + test_function_call::(to_use); + test_function_call::(to_use); + test_function_call::(to_use); + test_function_call::(to_use); + test_function_call::(to_use); + test_function_call::(to_use); + test_function_call::<[u64;2]>(to_use); + test_function_call::<[u64;0]>(to_use); + test_function_call::(to_use); + test_function_call::(to_use); + test_function_call::(to_use); + test_function_call::(to_use); + test_function_call::(to_use); + test_function_call::<(u8, u32)>(to_use); + test_function_call::(to_use); + test_function_call::(to_use); + test_function_call::(to_use); +} + +#[inline(never)] +fn test_not_inlined(to_use: u8) { + test_if_expr_not_inlined::<()>(to_use); + test_if_expr_not_inlined::(to_use); + test_if_expr_not_inlined::(to_use); + test_if_expr_not_inlined::(to_use); + test_if_expr_not_inlined::(to_use); + test_if_expr_not_inlined::(to_use); + test_if_expr_not_inlined::(to_use); + test_if_expr_not_inlined::<[u64;2]>(to_use); + test_if_expr_not_inlined::<[u64;0]>(to_use); + test_if_expr_not_inlined::(to_use); + test_if_expr_not_inlined::(to_use); + test_if_expr_not_inlined::(to_use); + test_if_expr_not_inlined::(to_use); + test_if_expr_not_inlined::(to_use); + test_if_expr_not_inlined::<(u8, u32)>(to_use); + test_if_expr_not_inlined::(to_use); + test_if_expr_not_inlined::(to_use); + test_if_expr_not_inlined::(to_use); + + test_function_call_not_inlined::<()>(to_use); + test_function_call_not_inlined::(to_use); + test_function_call_not_inlined::(to_use); + test_function_call_not_inlined::(to_use); + test_function_call_not_inlined::(to_use); + test_function_call_not_inlined::(to_use); + test_function_call_not_inlined::(to_use); + test_function_call_not_inlined::<[u64;2]>(to_use); + test_function_call_not_inlined::<[u64;0]>(to_use); + test_function_call_not_inlined::(to_use); + test_function_call_not_inlined::(to_use); + test_function_call_not_inlined::(to_use); + test_function_call_not_inlined::(to_use); + test_function_call_not_inlined::(to_use); + test_function_call_not_inlined::<(u8, u32)>(to_use); + test_function_call_not_inlined::(to_use); + test_function_call_not_inlined::(to_use); + test_function_call_not_inlined::(to_use); +} + +fn main() -> u64 { + test_all_inlined(USE_LOCAL_VARIABLE); + test_all_inlined(USE_TEMPORARY_VALUE); + test_all_inlined(USE_NON_MUT_REF_MUT_PARAMETER); + + test_not_inlined(USE_LOCAL_VARIABLE); + test_not_inlined(USE_TEMPORARY_VALUE); + test_not_inlined(USE_NON_MUT_REF_MUT_PARAMETER); + + 42 +} diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_expressions/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_expressions/test.toml new file mode 100644 index 00000000000..a63b9cde7b3 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_expressions/test.toml @@ -0,0 +1,5 @@ +category = "run" +expected_result = { action = "return", value = 42 } +expected_result_new_encoding = { action = "return_data", value = "000000000000002A" } +validate_abi = false +expected_warnings = 1 diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_values/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_values/Forc.lock new file mode 100644 index 00000000000..7140fdf4d1d --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_values/Forc.lock @@ -0,0 +1,13 @@ +[[package]] +name = "core" +source = "path+from-root-4D30E5ABBEE9EB9E" + +[[package]] +name = "reassigning_via_references_to_values" +source = "member" +dependencies = ["std"] + +[[package]] +name = "std" +source = "path+from-root-4D30E5ABBEE9EB9E" +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_values/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_values/Forc.toml new file mode 100644 index 00000000000..0cfe00b4781 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_values/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "reassigning_via_references_to_values" + +[dependencies] +std = { path = "../../../../../reduced_std_libs/sway-lib-std-conversions" } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_values/src/impls.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_values/src/impls.sw new file mode 100644 index 00000000000..dad85dd3649 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_values/src/impls.sw @@ -0,0 +1,258 @@ +library; + +use core::ops::Eq; +use std::bytes_conversions::u256::*; +use std::bytes_conversions::b256::*; + +pub trait TestInstance { + fn new() -> Self; + fn different() -> Self; +} + +impl TestInstance for bool { + fn new() -> Self { + true + } + fn different() -> Self { + false + } +} + +impl TestInstance for u8 { + fn new() -> Self { + 123 + } + fn different() -> Self { + 223 + } +} + +impl TestInstance for u16 { + fn new() -> Self { + 1234 + } + fn different() -> Self { + 4321 + } +} + +impl TestInstance for u32 { + fn new() -> Self { + 12345 + } + fn different() -> Self { + 54321 + } +} + +impl TestInstance for u64 { + fn new() -> Self { + 123456 + } + fn different() -> Self { + 654321 + } +} + +impl TestInstance for u256 { + fn new() -> Self { + 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20u256 + } + fn different() -> Self { + 0x0203040405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1f1a20u256 + } +} + +impl TestInstance for str { + fn new() -> Self { + "1a2B3c" + } + fn different() -> Self { + "3A2b1C" + } +} + +impl Eq for str[6] { + fn eq(self, other: Self) -> bool { + let mut i = 0; + while i < 6 { + let ptr_self = __addr_of(self).add::(i); + let ptr_other = __addr_of(other).add::(i); + + if ptr_self.read::() != ptr_other.read::() { + return false; + } + + i = i + 1; + }; + + true + } +} + +impl TestInstance for str[6] { + fn new() -> Self { + __to_str_array("1a2B3c") + } + fn different() -> Self { + __to_str_array("3A2b1C") + } +} + +impl Eq for [u64;2] { + fn eq(self, other: Self) -> bool { + self[0] == other[0] && self[1] == other[1] + } +} + +impl TestInstance for [u64;2] { + fn new() -> Self { + [123456, 654321] + } + fn different() -> Self { + [654321, 123456] + } +} + +pub struct Struct { + x: u64, +} + +impl Eq for Struct { + fn eq(self, other: Self) -> bool { + self.x == other.x + } +} + +impl TestInstance for Struct { + fn new() -> Self { + Self { x: 98765 } + } + fn different() -> Self { + Self { x: 56789 } + } +} + +pub struct EmptyStruct { } + +impl Eq for EmptyStruct { + fn eq(self, other: Self) -> bool { + true + } +} + +impl TestInstance for EmptyStruct { + fn new() -> Self { + EmptyStruct { } + } + fn different() -> Self { + EmptyStruct { } + } +} + +pub enum Enum { + A: u64, +} + +impl Eq for Enum { + fn eq(self, other: Self) -> bool { + match (self, other) { + (Enum::A(l), Enum::A(r)) => l == r, + } + } +} + +impl TestInstance for Enum { + fn new() -> Self { + Self::A(123456) + } + fn different() -> Self { + Self::A(654321) + } +} + +impl Eq for (u8, u32) { + fn eq(self, other: Self) -> bool { + self.0 == other.0 && self.1 == other.1 + } +} + +impl TestInstance for (u8, u32) { + fn new() -> Self { + (123, 12345) + } + fn different() -> Self { + (223, 54321) + } +} + +impl TestInstance for b256 { + fn new() -> Self { + 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 + } + fn different() -> Self { + 0x0202020405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1f1a20 + } +} + +impl TestInstance for raw_ptr { + fn new() -> Self { + let null_ptr = asm() { zero: raw_ptr }; + + null_ptr.add::(42) + } + fn different() -> Self { + let null_ptr = asm() { zero: raw_ptr }; + + null_ptr.add::(42*2) + } +} + +impl TestInstance for raw_slice { + fn new() -> Self { + let null_ptr = asm() { zero: raw_ptr }; + + std::raw_slice::from_parts::(null_ptr, 42) + } + fn different() -> Self { + let null_ptr = asm() { zero: raw_ptr }; + + std::raw_slice::from_parts::(null_ptr, 42*2) + } +} + +impl Eq for raw_slice { + fn eq(self, other: Self) -> bool { + self.ptr() == other.ptr() && self.number_of_bytes() == other.number_of_bytes() + } +} + +impl TestInstance for () { + fn new() -> Self { + () + } + fn different() -> Self { + () + } +} + +impl Eq for () { + fn eq(self, other: Self) -> bool { + true + } +} + +impl TestInstance for [u64;0] { + fn new() -> Self { + [] + } + fn different() -> Self { + [] + } +} + +impl Eq for [u64;0] { + fn eq(self, other: Self) -> bool { + true + } +} diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_values/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_values/src/main.sw new file mode 100644 index 00000000000..c5cf9849fd9 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_values/src/main.sw @@ -0,0 +1,274 @@ +script; + +mod impls; +use impls::*; +use core::ops::Eq; + +#[inline(always)] +fn assign_built_in_value_u8() { + let mut x = 11u8; + + let mut r_mut_x = &mut x; + let mut r_mut_r_mut_x = &mut r_mut_x; + let r_mut_r_mut_r_mut_x = &mut r_mut_r_mut_x; + + *r_mut_x = 22u8; + assert(x == 22u8); + + **r_mut_r_mut_x = 33u8; + assert(x == 33u8); + + ***r_mut_r_mut_r_mut_x = 44u8; + assert(x == 44u8); + + let r = & &mut & &mut x; + ****r = 11; + assert(x == 11); +} + +#[inline(never)] +fn assign_built_in_value_u8_not_inlined() { + assign_built_in_value_u8() +} + +struct S { + x: bool, + y: u64, +} + +impl Eq for S { + fn eq(self, other: Self) -> bool { + self.x == other.x && self.y == other.y + } +} + +#[inline(always)] +fn assign_struct_value() { + let mut x = S { x: true, y: 111 }; + + let mut r_mut_x = &mut x; + let mut r_mut_r_mut_x = &mut r_mut_x; + let r_mut_r_mut_r_mut_x = &mut r_mut_r_mut_x; + + *r_mut_x = S { x: false, y: 222 }; + assert(x == S { x: false, y: 222 }); + + **r_mut_r_mut_x = S { x: true, y: 333 }; + assert(x == S { x: true, y: 333 }); + + ***r_mut_r_mut_r_mut_x = S { x: false, y: 444 }; + assert(x == S { x: false, y: 444 }); + + let r = & &mut & &mut x; + ****r = S { x: true, y: 111 }; + assert(x == S { x: true, y: 111 }); +} + +#[inline(never)] +fn assign_struct_value_not_inlined() { + assign_struct_value() +} + +impl Eq for (bool, u64) { + fn eq(self, other: Self) -> bool { + self.0 == other.0 && self.1 == other.1 + } +} + +#[inline(always)] +fn assign_tuple_value() { + let mut x = (true, 111); + + let mut r_mut_x = &mut x; + let mut r_mut_r_mut_x = &mut r_mut_x; + let r_mut_r_mut_r_mut_x = &mut r_mut_r_mut_x; + + *r_mut_x = (false, 222); + assert(x == (false, 222)); + + **r_mut_r_mut_x = (true, 333); + assert(x == (true, 333)); + + ***r_mut_r_mut_r_mut_x = (false, 444); + assert(x == (false, 444)); + + let r = & &mut & &mut x; + ****r = (true, 111); + assert(x == (true, 111)); +} + +#[inline(never)] +fn assign_tuple_value_not_inlined() { + assign_tuple_value() +} + +enum E { + A: u64, + B: bool, + C: u8, + D: u32, +} + +impl Eq for E { + fn eq(self, other: Self) -> bool { + match (self, other) { + (E::A(l), E::A(r)) => l == r, + (E::B(l), E::B(r)) => l == r, + (E::C(l), E::C(r)) => l == r, + (E::D(l), E::D(r)) => l == r, + _ => false, + } + } +} + +#[inline(always)] +fn assign_enum_value() { + let mut x = E::A(111); + + let mut r_mut_x = &mut x; + let mut r_mut_r_mut_x = &mut r_mut_x; + let r_mut_r_mut_r_mut_x = &mut r_mut_r_mut_x; + + *r_mut_x = E::B(true); + assert(x == E::B(true)); + + **r_mut_r_mut_x = E::C(222); + assert(x == E::C(222)); + + ***r_mut_r_mut_r_mut_x = E::D(333); + assert(x == E::D(333)); + + let r = & &mut & &mut x; + ****r = E::A(111); + assert(x == E::A(111)); +} + +#[inline(never)] +fn assign_enum_value_not_inlined() { + assign_enum_value() +} + +#[inline(always)] +fn assign_array_value() { + let mut x = [111, 222]; + + let mut r_mut_x = &mut x; + let mut r_mut_r_mut_x = &mut r_mut_x; + let r_mut_r_mut_r_mut_x = &mut r_mut_r_mut_x; + + *r_mut_x = [333, 444]; + assert(x == [333, 444]); + + **r_mut_r_mut_x = [555, 666]; + assert(x == [555, 666]); + + ***r_mut_r_mut_r_mut_x = [777, 888]; + assert(x == [777, 888]); + + let r = & &mut & &mut x; + ****r = [111, 222]; + assert(x == [111, 222]); +} + +#[inline(never)] +fn assign_array_value_not_inlined() { + assign_array_value() +} + +#[inline(always)] +fn assign_value() + where T: TestInstance + Eq +{ + let mut x = T::new(); + + let mut r_mut_x = &mut x; + let mut r_mut_r_mut_x = &mut r_mut_x; + let r_mut_r_mut_r_mut_x = &mut r_mut_r_mut_x; + + *r_mut_x = T::different(); + assert(x == T::different()); + + **r_mut_r_mut_x = T::new(); + assert(x == T::new()); + + ***r_mut_r_mut_r_mut_x = T::different(); + assert(x == T::different()); + + let r = & &mut & &mut x; + ****r = T::new(); + assert(x == T::new()); +} + +#[inline(never)] +fn assign_value_not_inlined() + where T: TestInstance + Eq +{ + assign_value::() +} + +#[inline(never)] +fn test_all_inlined() { + assign_built_in_value_u8(); + assign_struct_value(); + assign_array_value(); + assign_tuple_value(); + assign_enum_value(); + + assign_value::<()>(); + assign_value::(); + assign_value::(); + assign_value::(); + assign_value::(); + assign_value::(); + // TODO: Enable once https://github.com/FuelLabs/sway/issues/5833 get solved. + // assign_value::(); + assign_value::<[u64;2]>(); + assign_value::<[u64;0]>(); + assign_value::(); + assign_value::(); + assign_value::(); + assign_value::(); + assign_value::(); + assign_value::<(u8, u32)>(); + // TODO: Enable once https://github.com/FuelLabs/sway/issues/5833 get solved. + // assign_value::(); + assign_value::(); + assign_value::(); +} + +#[inline(never)] +fn test_not_inlined() { + assign_built_in_value_u8_not_inlined(); + assign_struct_value_not_inlined(); + assign_array_value_not_inlined(); + assign_tuple_value_not_inlined(); + assign_enum_value_not_inlined(); + + assign_value_not_inlined::<()>(); + assign_value_not_inlined::(); + assign_value_not_inlined::(); + assign_value_not_inlined::(); + assign_value_not_inlined::(); + assign_value_not_inlined::(); + // TODO: Enable once https://github.com/FuelLabs/sway/issues/5833 get solved. + // assign_value_not_inlined::(); + assign_value_not_inlined::<[u64;2]>(); + assign_value_not_inlined::<[u64;0]>(); + assign_value_not_inlined::(); + assign_value_not_inlined::(); + assign_value_not_inlined::(); + assign_value_not_inlined::(); + assign_value_not_inlined::(); + assign_value_not_inlined::<(u8, u32)>(); + // TODO: Enable once https://github.com/FuelLabs/sway/issues/5833 get solved. + // assign_value_not_inlined::(); + assign_value_not_inlined::(); + assign_value_not_inlined::(); +} + +fn main() -> u64 { + test_all_inlined(); + test_not_inlined(); + + 42 +} diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_values/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_values/test.toml new file mode 100644 index 00000000000..c080b869f36 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/reassigning_via_references_to_values/test.toml @@ -0,0 +1,5 @@ +category = "run" +expected_result = { action = "return", value = 42 } +expected_result_new_encoding = { action = "return_data", value = "000000000000002A" } +validate_abi = false +expected_warnings = 0 diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/references_and_type_aliases/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/references_and_type_aliases/test.toml index 7f8ab6e788d..e6bc49e8232 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/references_and_type_aliases/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/references_and_type_aliases/test.toml @@ -2,4 +2,4 @@ category = "run" expected_result = { action = "return", value = 42 } expected_result_new_encoding = { action = "return_data", value = "000000000000002A" } validate_abi = false -expected_warnings = 1 #TODO-DCA: Set to zero once DCA bug is solved. +expected_warnings = 1 # TODO-DCA: Set to zero once https://github.com/FuelLabs/sway/issues/5921 is fixed. diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_control_flow_expressions/json_abi_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_control_flow_expressions/json_abi_oracle.json deleted file mode 100644 index ad50b55d54c..00000000000 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_control_flow_expressions/json_abi_oracle.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "configurables": [], - "functions": [ - { - "attributes": null, - "inputs": [], - "name": "main", - "output": { - "name": "", - "type": 0, - "typeArguments": null - } - } - ], - "loggedTypes": [], - "messagesTypes": [], - "types": [ - { - "components": null, - "type": "u64", - "typeId": 0, - "typeParameters": null - } - ] -} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_control_flow_expressions/json_abi_oracle_new_encoding.json b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_control_flow_expressions/json_abi_oracle_new_encoding.json deleted file mode 100644 index 068da3305ab..00000000000 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_control_flow_expressions/json_abi_oracle_new_encoding.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "configurables": [], - "encoding": "1", - "functions": [ - { - "attributes": null, - "inputs": [], - "name": "main", - "output": { - "name": "", - "type": 0, - "typeArguments": null - } - } - ], - "loggedTypes": [], - "messagesTypes": [], - "types": [ - { - "components": null, - "type": "u64", - "typeId": 0, - "typeParameters": null - } - ] -} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_control_flow_expressions/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_control_flow_expressions/test.toml index ab49189d439..ce0064ae069 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_control_flow_expressions/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_control_flow_expressions/test.toml @@ -1,5 +1,5 @@ category = "run" expected_result = { action = "return", value = 42 } expected_result_new_encoding = { action = "return_data", value = "000000000000002A" } -validate_abi = true +validate_abi = false expected_warnings = 40 diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_expressions/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_expressions/src/main.sw index 24d477c43e4..ce1a42d8c09 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_expressions/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_expressions/src/main.sw @@ -21,7 +21,7 @@ impl Eq for [Struct; 3] { // TODO-IG: Add tests for other expressions. #[inline(always)] -fn if_expr(input: u64, left: T, right: T) where T: Eq { +fn if_expr(input: u64, left: T, right: T) where T: AbiEncode + Eq { let mut x = if input > 42 { left } else { @@ -45,6 +45,28 @@ fn if_expr(input: u64, left: T, right: T) where T: Eq { }; assert_references(r_x, r_val, r_mut_x, r_mut_val, x); + + if *r_mut_x == left { + assert_eq(x, left); + *r_mut_x = right; + assert_eq(x, right); + } else { + assert_eq(x, right); + *r_mut_x = left; + assert_eq(x, left); + } + + if *r_mut_val == left { + let current_x = x; + *r_mut_val = right; + assert_eq(x, current_x); + assert_eq(*r_mut_val, right); + } else { + let current_x = x; + *r_mut_val = left; + assert_eq(x, current_x); + assert_eq(*r_mut_val, left); + } } fn assert_references(r_x: &T, r_val: &T, r_mut_x: &mut T, r_mut_val: &mut T, x: T) where T: Eq { @@ -75,7 +97,7 @@ fn assert_references(r_x: &T, r_val: &T, r_mut_x: &mut T, r_mut_val: &mut T, } #[inline(never)] -fn if_expr_not_inlined(input: u64, left: T, right: T) where T: Eq { +fn if_expr_not_inlined(input: u64, left: T, right: T) where T: AbiEncode + Eq { if_expr(input, left, right) } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_function_parameters/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_function_parameters/src/main.sw index e1a59b5f62b..541489651cf 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_function_parameters/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_function_parameters/src/main.sw @@ -24,26 +24,28 @@ impl Eq for S { // TODO-IG: Extend with `mut` parameters once declaring `mut` parameters is implemented. +// TODO-IG: Extend with `&` and `&mut` parameters once proper referencing of copy type parameters is implemented. + // TODO-IG: Uncomment once proper referencing of copy type parameters is implemented. -//#[inline(always)] -//Fn u8_parameter(p: u8) { +// #[inline(always)] +// fn u8_parameter(p: u8) { // let r_p_1 = &p; // let r_p_2 = &p; -// + // let p_ptr = asm(r: &p) { r: raw_ptr }; // let r_p_1_ptr = asm(r: r_p_1) { r: raw_ptr }; // let r_p_2_ptr = asm(r: r_p_2) { r: raw_ptr }; -// + // assert(p_ptr == r_p_1_ptr); // assert(p_ptr == r_p_2_ptr); -// + // assert(p_ptr.read::() == p); -//} -// -//#[inline(never)] -//Fn u8_parameter_not_inlined(p: u8) { +// } + +// #[inline(never)] +// fn u8_parameter_not_inlined(p: u8) { // u8_parameter(p) -//} +// } impl Eq for [u64;2] { fn eq(self, other: Self) -> bool { @@ -244,7 +246,7 @@ fn generic_parameter_not_inlined() { #[inline(never)] fn test_all_inlined() { - //u8_parameter(123u8); + // u8_parameter(123u8); array_parameter([111u64, 222u64]); empty_struct_parameter(EmptyStruct { }); struct_parameter(S { x: 123u8 }); @@ -255,7 +257,7 @@ fn test_all_inlined() { #[inline(never)] fn test_not_inlined() { - //u8_parameter_not_inlined(123u8); + // u8_parameter_not_inlined(123u8); array_parameter_not_inlined([111u64, 222u64]); empty_struct_parameter_not_inlined(EmptyStruct { }); struct_parameter_not_inlined(S { x: 123u8 }); diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_local_vars_and_values/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_local_vars_and_values/test.toml index 8ae760b9cc5..c080b869f36 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_local_vars_and_values/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_local_vars_and_values/test.toml @@ -2,4 +2,4 @@ category = "run" expected_result = { action = "return", value = 42 } expected_result_new_encoding = { action = "return_data", value = "000000000000002A" } validate_abi = false -expected_warnings = 9 # TODO-DCA: Should be zero. DCA problem? Investigate why there are "This method is never called." errors for some of the types. +expected_warnings = 0 diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/type_unification_of_references/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/type_unification_of_references/test.toml index 4fe77773c8d..c080b869f36 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/type_unification_of_references/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/type_unification_of_references/test.toml @@ -2,4 +2,4 @@ category = "run" expected_result = { action = "return", value = 42 } expected_result_new_encoding = { action = "return_data", value = "000000000000002A" } validate_abi = false -expected_warnings = 20 +expected_warnings = 0 diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/array_of_structs_caller/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/array_of_structs_caller/src/main.sw index a5411c7625f..3aae9dd814a 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/array_of_structs_caller/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/array_of_structs_caller/src/main.sw @@ -6,7 +6,7 @@ use std::hash::*; #[cfg(experimental_new_encoding = false)] const CONTRACT_ID = 0x7fae96947a8cad59cc2a25239f9f80897955d4c1b10d31510681f15842b93265; #[cfg(experimental_new_encoding = true)] -const CONTRACT_ID = 0xffebbe5dec39b68b5e0b27b734b223ad2c58e5310f67c6afa890a9a171638b2d; +const CONTRACT_ID = 0x51106f3df741291f2aaef8a246ab6311e23abbafd3a7b3e10623e088fcc37451; fn main() -> u64 { let addr = abi(TestContract, CONTRACT_ID); diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_basic_storage/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_basic_storage/src/main.sw index e7c66e25874..89438789f8f 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_basic_storage/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_basic_storage/src/main.sw @@ -2,9 +2,9 @@ script; use basic_storage_abi::{BasicStorage, Quad}; #[cfg(experimental_new_encoding = false)] -const CONTRACT_ID = 0xbb236cbc9f99b66bb8b8daea5702fd58b740163b0629a042b73f3a8063329ffa; +const CONTRACT_ID = 0x5c0aef0c1af7601aa6b9fa0fc9efff0e956dcb93f855788222e172e67e717072; #[cfg(experimental_new_encoding = true)] -const CONTRACT_ID = 0x3dd787f1603818f0f2ad850817379fdb0b32bd87dc401cde73bd67473d867eea; +const CONTRACT_ID = 0x0ebd782b27bfa57a7525afd472e4cee41c9c7dfaaf0afb70df4a378da0025e23; fn main() -> u64 { let addr = abi(BasicStorage, CONTRACT_ID); diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_contract_with_type_aliases/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_contract_with_type_aliases/src/main.sw index 8279412810c..2c1cf6dfd38 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_contract_with_type_aliases/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_contract_with_type_aliases/src/main.sw @@ -5,7 +5,7 @@ use contract_with_type_aliases_abi::*; #[cfg(experimental_new_encoding = false)] const CONTRACT_ID = 0x9d76ecbf446c30ef659efd1157d67d156de02b1e6c2ac2f9c744125571efa229; #[cfg(experimental_new_encoding = true)] -const CONTRACT_ID = 0x954194e367501d0cc8f9a6c812552bb72bc273d625a392fda904e6968c5697a1; +const CONTRACT_ID = 0xbd2ab800338002ff56708e5ca457e678a714b25b93031bcd0e7a352212c0affc; fn main() { let caller = abi(MyContract, CONTRACT_ID); diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_increment_contract/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_increment_contract/src/main.sw index d82fdf239a0..cafe8ca239c 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_increment_contract/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_increment_contract/src/main.sw @@ -4,9 +4,9 @@ use increment_abi::Incrementor; use dynamic_contract_call::*; #[cfg(experimental_new_encoding = false)] -const CONTRACT_ID = 0x4440ac68a7f88e414ae29425ab22c6aed0434cf6632d0ee1d41ab82607923493; +const CONTRACT_ID = 0x080ca4b6a4661d3cc2138f733cbe54095ce8b910eee73d913c1f43ecad6bf0d2; #[cfg(experimental_new_encoding = true)] -const CONTRACT_ID = 0x3e74f971fb1f8e3076af8f343dd4f5191b2363f3606091e5e0cbe86e6109dd30; +const CONTRACT_ID = 0x41166689b47e0f03434ba72957e66afdbf02f23518a0f2c9e48f887fbd4e67d8; fn main() -> bool { let the_abi = abi(Incrementor, CONTRACT_ID); diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_storage_enum/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_storage_enum/src/main.sw index 7688da431a6..f271aca5b4b 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_storage_enum/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_storage_enum/src/main.sw @@ -3,9 +3,9 @@ script; use storage_enum_abi::*; #[cfg(experimental_new_encoding = false)] -const CONTRACT_ID = 0x1ce765336fbb4c4558c7f5753fad01a8549521b03e82bc94048fa512750b9554; +const CONTRACT_ID = 0x0436d54f976e2dee0d77c81abc0d32cc7be985d8e0c97eeba27acd1caffdcea1; #[cfg(experimental_new_encoding = true)] -const CONTRACT_ID = 0x2126d89a1de460b674af6d1d956051272f5b54bef7472be87c34f53b88ecd8d3; +const CONTRACT_ID = 0x028e88f07ba7bf51d0b5d2e3cda7ccea35f8bdf788defa74ddb34841f074fcfd; fn main() -> u64 { let caller = abi(StorageEnum, CONTRACT_ID); diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/storage_access_caller/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/storage_access_caller/src/main.sw index b2e92c7a0b3..6f4044ac113 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/storage_access_caller/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/storage_access_caller/src/main.sw @@ -4,9 +4,9 @@ use storage_access_abi::*; use std::hash::*; #[cfg(experimental_new_encoding = false)] -const CONTRACT_ID = 0x060a673b757bf47ce307548322586ec68b94a11ef330da149a7000435e3a294b; +const CONTRACT_ID = 0x0a58692bee60559887f0ac181c8a3b14ffb7a3a66256eec3f08e3135bfbecac9; #[cfg(experimental_new_encoding = true)] -const CONTRACT_ID = 0xd258b6303f783a49a8c9370bc824b0824a06edf05a3299b8c9e69d6bf26d2149; +const CONTRACT_ID = 0x061235f2d1470151789ff3df04bd61b7034084b0dc22298c7167c4e0d38e29e0; fn main() -> bool { let caller = abi(StorageAccess, CONTRACT_ID);