diff --git a/sway-core/Cargo.toml b/sway-core/Cargo.toml index 689b21acaf3..66f3da2a850 100644 --- a/sway-core/Cargo.toml +++ b/sway-core/Cargo.toml @@ -14,10 +14,12 @@ derivative = "2.2.0" dirs = "3.0" either = "1.6" ethabi = { package = "fuel-ethabi", version = "18.0.0" } -etk-asm = { package = "fuel-etk-asm", version = "0.3.1-dev", features = ["backtraces"] } +etk-asm = { package = "fuel-etk-asm", version = "0.3.1-dev", features = [ + "backtraces", +] } etk-dasm = { package = "fuel-etk-dasm", version = "0.3.1-dev" } etk-ops = { package = "fuel-etk-ops", version = "0.3.1-dev" } -fuel-abi-types = "0.1" +fuel-abi-types = "0.1" fuel-vm = { workspace = true, features = ["serde"] } hashbrown = "0.13.1" hex = { version = "0.4", optional = true } diff --git a/sway-core/src/ir_generation/const_eval.rs b/sway-core/src/ir_generation/const_eval.rs index e2df486611a..c976071774c 100644 --- a/sway-core/src/ir_generation/const_eval.rs +++ b/sway-core/src/ir_generation/const_eval.rs @@ -27,9 +27,20 @@ use sway_ir::{ value::Value, Instruction, Type, }; -use sway_types::{ident::Ident, span::Spanned}; +use sway_types::{ident::Ident, span::Spanned, Span}; use sway_utils::mapped_stack::MappedStack; +enum ConstEvalError { + CompileError(CompileError), + CannotBeEvaluatedToConst { + // This is not used at the moment because we do not give detailed description of why a + // const eval failed. + // Nonetheless, this is used in tests to help debug. + #[allow(dead_code)] + span: Span, + }, +} + pub(crate) struct LookupEnv<'a, 'eng> { pub(crate) engines: &'a Engines, pub(crate) context: &'a mut Context<'eng>, @@ -144,6 +155,7 @@ pub(crate) fn compile_const_decl( &value.unwrap(), is_configurable, )?; + if !is_configurable { env.module.add_global_constant( env.context, @@ -189,6 +201,7 @@ pub(super) fn compile_constant_expression( function_compiler, const_expr, )?; + if !is_configurable { Ok(Value::new_constant(context, constant_evaluated).add_metadatum(context, span_id_idx)) } else { @@ -233,7 +246,10 @@ pub(crate) fn compile_constant_expression_to_constant( }; let mut known_consts = MappedStack::::new(); - const_eval_typed_expr(lookup, &mut known_consts, const_expr)?.map_or(err, Ok) + match const_eval_typed_expr(lookup, &mut known_consts, const_expr) { + Ok(Some(constant)) => Ok(constant), + _ => err, + } } /// Given an environment mapping names to constants, @@ -242,36 +258,45 @@ fn const_eval_typed_expr( lookup: &mut LookupEnv, known_consts: &mut MappedStack, expr: &ty::TyExpression, -) -> Result, CompileError> { +) -> Result, ConstEvalError> { Ok(match &expr.expression { ty::TyExpressionVariant::Literal(l) => Some(convert_literal_to_constant(lookup.context, l)), ty::TyExpressionVariant::FunctionApplication { - arguments, fn_ref, .. + arguments, + fn_ref, + call_path, + .. } => { let mut actuals_const: Vec<_> = vec![]; + for arg in arguments { let (name, sub_expr) = arg; let eval_expr_opt = const_eval_typed_expr(lookup, known_consts, sub_expr)?; if let Some(sub_const) = eval_expr_opt { actuals_const.push((name, sub_const)); + } else { + // If all actual arguments don't evaluate a constant, bail out. + // TODO: Explore if we could continue here and if it'll be useful. + return Err(ConstEvalError::CannotBeEvaluatedToConst { + span: call_path.span(), + }); } } - // If all actual arguments don't evaluate a constant, bail out. - // TODO: Explore if we could continue here and if it'll be useful. - if actuals_const.len() < arguments.len() { - return Ok(None); - } + assert!(actuals_const.len() == arguments.len()); + for (name, cval) in actuals_const.into_iter() { known_consts.push(name.clone(), cval); } let function_decl = lookup.engines.de().get_function(fn_ref); - let res = const_eval_codeblock(lookup, known_consts, &function_decl.body)?; + let res = const_eval_codeblock(lookup, known_consts, &function_decl.body); + for (name, _) in arguments { known_consts.pop(name); } - res + + res? } ty::TyExpressionVariant::ConstantExpression { const_decl, .. } => { let call_path = &const_decl.call_path; @@ -306,21 +331,29 @@ fn const_eval_typed_expr( .and_then(|v| v.get_constant(lookup.context).cloned()) } }, - ty::TyExpressionVariant::StructExpression { fields, .. } => { + ty::TyExpressionVariant::StructExpression { + fields, + instantiation_span, + .. + } => { let (mut field_typs, mut field_vals): (Vec<_>, Vec<_>) = (vec![], vec![]); + for field in fields { let ty::TyStructExpressionField { name: _, value, .. } = field; let eval_expr_opt = const_eval_typed_expr(lookup, known_consts, value)?; if let Some(cv) = eval_expr_opt { field_typs.push(value.return_type); field_vals.push(cv); + } else { + return Err(ConstEvalError::CannotBeEvaluatedToConst { + span: instantiation_span.clone(), + }); } } - if field_vals.len() < fields.len() { - // We couldn't evaluate all fields to a constant. - return Ok(None); - } + assert!(field_typs.len() == fields.len()); + assert!(field_vals.len() == fields.len()); + get_struct_for_types( lookup.engines.te(), lookup.engines.de(), @@ -337,17 +370,22 @@ fn const_eval_typed_expr( } ty::TyExpressionVariant::Tuple { fields } => { let (mut field_typs, mut field_vals): (Vec<_>, Vec<_>) = (vec![], vec![]); + for value in fields { let eval_expr_opt = const_eval_typed_expr(lookup, known_consts, value)?; if let Some(cv) = eval_expr_opt { field_typs.push(value.return_type); field_vals.push(cv); + } else { + return Err(ConstEvalError::CannotBeEvaluatedToConst { + span: expr.span.clone(), + }); } } - if field_vals.len() < fields.len() { - // We couldn't evaluate all fields to a constant. - return Ok(None); - } + + assert!(field_typs.len() == fields.len()); + assert!(field_vals.len() == fields.len()); + create_tuple_aggregate( lookup.engines.te(), lookup.engines.de(), @@ -367,30 +405,37 @@ fn const_eval_typed_expr( contents, } => { let (mut element_typs, mut element_vals): (Vec<_>, Vec<_>) = (vec![], vec![]); + for value in contents { let eval_expr_opt = const_eval_typed_expr(lookup, known_consts, value)?; if let Some(cv) = eval_expr_opt { element_typs.push(value.return_type); element_vals.push(cv); + } else { + return Err(ConstEvalError::CannotBeEvaluatedToConst { + span: expr.span.clone(), + }); } } - if element_vals.len() < contents.len() || element_typs.is_empty() { - // We couldn't evaluate all fields to a constant or cannot determine element type. - return Ok(None); - } - let elem_type_info = lookup.engines.te().get(*elem_type); - if !element_typs.iter().all(|tid| { - lookup - .engines - .te() - .get(*tid) - .eq(&elem_type_info, lookup.engines) - }) { - // This shouldn't happen if the type checker did its job. - return Ok(None); - } + + assert!(element_typs.len() == contents.len()); + assert!(element_vals.len() == contents.len()); + + let te = lookup.engines.te(); + + assert!({ + let elem_type_info = te.get(*elem_type); + element_typs.iter().all(|tid| { + lookup + .engines + .te() + .get(*tid) + .eq(&elem_type_info, lookup.engines) + }) + }); + create_array_aggregate( - lookup.engines.te(), + te, lookup.engines.de(), lookup.context, *elem_type, @@ -408,6 +453,7 @@ fn const_eval_typed_expr( enum_ref, tag, contents, + variant_instantiation_span, .. } => { let enum_decl = lookup.engines.de().get_enum(enum_ref); @@ -417,25 +463,29 @@ fn const_eval_typed_expr( lookup.context, &enum_decl.variants, ); + if let Ok(enum_ty) = aggregate { let tag_value = Constant::new_uint(lookup.context, 64, *tag as u64); let mut fields: Vec = vec![tag_value]; + match contents { None => fields.push(Constant::new_unit(lookup.context)), - Some(subexpr) => { - let eval_expr = const_eval_typed_expr(lookup, known_consts, subexpr)?; - eval_expr.into_iter().for_each(|enum_val| { - fields.push(enum_val); - }) - } + Some(subexpr) => match const_eval_typed_expr(lookup, known_consts, subexpr)? { + Some(constant) => fields.push(constant), + None => { + return Err(ConstEvalError::CannotBeEvaluatedToConst { + span: variant_instantiation_span.clone(), + }) + } + }, } - Some(Constant::new_struct( - lookup.context, - enum_ty.get_field_types(lookup.context), - fields, - )) + + let fields_tys = enum_ty.get_field_types(lookup.context); + Some(Constant::new_struct(lookup.context, fields_tys, fields)) } else { - None + return Err(ConstEvalError::CannotBeEvaluatedToConst { + span: expr.span.clone(), + }); } } ty::TyExpressionVariant::StructFieldAccess { @@ -462,7 +512,11 @@ fn const_eval_typed_expr( }) .and_then(|field_idx| fields.get(field_idx as usize).cloned()) } - _ => None, + _ => { + return Err(ConstEvalError::CannotBeEvaluatedToConst { + span: expr.span.clone(), + }) + } }, ty::TyExpressionVariant::TupleElemAccess { prefix, @@ -473,13 +527,21 @@ fn const_eval_typed_expr( value: ConstantValue::Struct(fields), .. }) => fields.get(*elem_to_access_num).cloned(), - _ => None, + _ => { + return Err(ConstEvalError::CannotBeEvaluatedToConst { + span: expr.span.clone(), + }) + } }, // we could allow non-local control flow in pure functions, but it would // require some more work and at this point it's not clear if it is too useful // for constant initializers -- the user can always refactor their pure functions // to not use the return statement - ty::TyExpressionVariant::Return(_exp) => None, + ty::TyExpressionVariant::Return(exp) => { + return Err(ConstEvalError::CannotBeEvaluatedToConst { + span: exp.span.clone(), + }) + } ty::TyExpressionVariant::MatchExp { desugared, .. } => { const_eval_typed_expr(lookup, known_consts, desugared)? } @@ -507,14 +569,51 @@ fn const_eval_typed_expr( None } } - _ => None, + _ => { + return Err(ConstEvalError::CannotBeEvaluatedToConst { + span: expr.span.clone(), + }) + } } } ty::TyExpressionVariant::CodeBlock(codeblock) => { const_eval_codeblock(lookup, known_consts, codeblock)? } - ty::TyExpressionVariant::ArrayIndex { .. } - | ty::TyExpressionVariant::Reassignment(_) + ty::TyExpressionVariant::ArrayIndex { prefix, index } => { + let prefix = const_eval_typed_expr(lookup, known_consts, prefix)?; + let index = const_eval_typed_expr(lookup, known_consts, index)?; + match (prefix, index) { + ( + Some(Constant { + value: ConstantValue::Array(items), + .. + }), + Some(Constant { + value: ConstantValue::Uint(index), + .. + }), + ) => { + let count = items.len() as u64; + if index < count { + Some(items[index as usize].clone()) + } else { + return Err(ConstEvalError::CompileError( + CompileError::ArrayOutOfBounds { + index, + count, + span: expr.span.clone(), + }, + )); + } + } + _ => { + return Err(ConstEvalError::CannotBeEvaluatedToConst { + span: expr.span.clone(), + }) + } + } + } + ty::TyExpressionVariant::Reassignment(_) | ty::TyExpressionVariant::FunctionParameter | ty::TyExpressionVariant::AsmExpression { .. } | ty::TyExpressionVariant::LazyOperator { .. } @@ -525,7 +624,11 @@ fn const_eval_typed_expr( | ty::TyExpressionVariant::UnsafeDowncast { .. } | ty::TyExpressionVariant::Break | ty::TyExpressionVariant::Continue - | ty::TyExpressionVariant::WhileLoop { .. } => None, + | ty::TyExpressionVariant::WhileLoop { .. } => { + return Err(ConstEvalError::CannotBeEvaluatedToConst { + span: expr.span.clone(), + }) + } }) } @@ -536,64 +639,96 @@ fn const_eval_codeblock( lookup: &mut LookupEnv, known_consts: &mut MappedStack, codeblock: &ty::TyCodeBlock, -) -> Result, CompileError> { +) -> Result, ConstEvalError> { // the current result - let mut res_const = None; + let mut result: Result, ConstEvalError> = Ok(None); // keep track of new bindings for this codeblock let mut bindings: Vec<_> = vec![]; for ast_node in &codeblock.contents { - match &ast_node.content { - ty::TyAstNodeContent::Declaration(ty::TyDecl::VariableDecl(var_decl)) => { - let rhs_opt = const_eval_typed_expr(lookup, known_consts, &var_decl.body)?; - if let Some(rhs) = rhs_opt { + result = match &ast_node.content { + ty::TyAstNodeContent::Declaration(decl @ ty::TyDecl::VariableDecl(var_decl)) => { + if let Ok(Some(rhs)) = const_eval_typed_expr(lookup, known_consts, &var_decl.body) { known_consts.push(var_decl.name.clone(), rhs); bindings.push(var_decl.name.clone()); + Ok(None) + } else { + Err(ConstEvalError::CannotBeEvaluatedToConst { + span: decl.span().clone(), + }) } - res_const = None } ty::TyAstNodeContent::Declaration(ty::TyDecl::ConstantDecl(const_decl)) => { let ty_const_decl = lookup.engines.de().get_constant(&const_decl.decl_id); - if let Some(const_expr) = ty_const_decl.value { - if let Some(constant) = - const_eval_typed_expr(lookup, known_consts, &const_expr)? - { - known_consts.push(const_decl.name.clone(), constant); - bindings.push(const_decl.name.clone()); - } + if let Some(constant) = ty_const_decl + .value + .and_then(|expr| const_eval_typed_expr(lookup, known_consts, &expr).ok()) + .flatten() + { + known_consts.push(const_decl.name.clone(), constant); + bindings.push(const_decl.name.clone()); + Ok(None) + } else { + Err(ConstEvalError::CannotBeEvaluatedToConst { + span: const_decl.decl_span.clone(), + }) } - res_const = None } - ty::TyAstNodeContent::Declaration(_) => res_const = None, - ty::TyAstNodeContent::Expression(e) - | ty::TyAstNodeContent::ImplicitReturnExpression(e) => { - res_const = const_eval_typed_expr(lookup, known_consts, e)? + ty::TyAstNodeContent::Declaration(_) => Ok(None), + ty::TyAstNodeContent::Expression(e) => { + if const_eval_typed_expr(lookup, known_consts, e).is_err() { + Err(ConstEvalError::CannotBeEvaluatedToConst { + span: e.span.clone(), + }) + } else { + Ok(None) + } + } + ty::TyAstNodeContent::ImplicitReturnExpression(e) => { + if let Ok(Some(constant)) = const_eval_typed_expr(lookup, known_consts, e) { + Ok(Some(constant)) + } else { + Err(ConstEvalError::CannotBeEvaluatedToConst { + span: e.span.clone(), + }) + } } - ty::TyAstNodeContent::SideEffect(_) => res_const = None, + ty::TyAstNodeContent::SideEffect(_) => Err(ConstEvalError::CannotBeEvaluatedToConst { + span: ast_node.span.clone(), + }), + }; + + if result.is_err() { + break; } } + // remove introduced vars/consts from scope at the end of the codeblock for name in bindings { known_consts.pop(&name) } - Ok(res_const) + + result } fn const_eval_intrinsic( lookup: &mut LookupEnv, known_consts: &mut MappedStack, intrinsic: &TyIntrinsicFunctionKind, -) -> Result, CompileError> { - let args = intrinsic - .arguments - .iter() - .filter_map(|arg| const_eval_typed_expr(lookup, known_consts, arg).transpose()) - .collect::, CompileError>>()?; - - if args.len() != intrinsic.arguments.len() { - // We couldn't const-eval all arguments. - return Ok(None); +) -> Result, ConstEvalError> { + let mut args = vec![]; + for arg in intrinsic.arguments.iter() { + if let Ok(Some(constant)) = const_eval_typed_expr(lookup, known_consts, arg) { + args.push(constant); + } else { + return Err(ConstEvalError::CannotBeEvaluatedToConst { + span: arg.span.clone(), + }); + } } + + assert!(args.len() == intrinsic.arguments.len()); + match intrinsic.kind { sway_ast::Intrinsic::Add | sway_ast::Intrinsic::Sub @@ -611,6 +746,7 @@ fn const_eval_intrinsic( else { panic!("Type checker allowed incorrect args to binary op"); }; + // All arithmetic is done as if it were u64 let result = match intrinsic.kind { Intrinsic::Add => arg1.checked_add(*arg2), @@ -623,12 +759,15 @@ fn const_eval_intrinsic( Intrinsic::Mod => arg1.checked_rem(*arg2), _ => unreachable!(), }; + match result { Some(sum) => Ok(Some(Constant { ty, value: ConstantValue::Uint(sum), })), - None => Ok(None), + None => Err(ConstEvalError::CannotBeEvaluatedToConst { + span: intrinsic.span.clone(), + }), } } sway_ast::Intrinsic::Lsh | sway_ast::Intrinsic::Rsh => { @@ -638,10 +777,12 @@ fn const_eval_intrinsic( && ty.is_uint(lookup.context) && args[1].ty.is_uint64(lookup.context) ); + let (ConstantValue::Uint(arg1), ConstantValue::Uint(ref arg2)) = (&args[0].value, &args[1].value) else { panic!("Type checker allowed incorrect args to binary op"); }; + let result = match intrinsic.kind { Intrinsic::Lsh => u32::try_from(*arg2) .ok() @@ -651,12 +792,15 @@ fn const_eval_intrinsic( .and_then(|arg2| arg1.checked_shr(arg2)), _ => unreachable!(), }; + match result { Some(sum) => Ok(Some(Constant { ty, value: ConstantValue::Uint(sum), })), - None => Ok(None), + None => Err(ConstEvalError::CannotBeEvaluatedToConst { + span: intrinsic.span.clone(), + }), } } sway_ast::Intrinsic::SizeOfType => { @@ -667,7 +811,8 @@ fn const_eval_intrinsic( lookup.context, &targ.type_id, &targ.span, - )?; + ) + .map_err(ConstEvalError::CompileError)?; Ok(Some(Constant { ty: Type::get_uint64(lookup.context), value: ConstantValue::Uint(ir_type_size_in_bytes(lookup.context, &ir_type)), @@ -682,7 +827,8 @@ fn const_eval_intrinsic( lookup.context, &type_id, &val.span, - )?; + ) + .map_err(ConstEvalError::CompileError)?; Ok(Some(Constant { ty: Type::get_uint64(lookup.context), value: ConstantValue::Uint(ir_type_size_in_bytes(lookup.context, &ir_type)), @@ -696,7 +842,8 @@ fn const_eval_intrinsic( lookup.context, &targ.type_id, &targ.span, - )?; + ) + .map_err(ConstEvalError::CompileError)?; Ok(Some(Constant { ty: Type::get_uint64(lookup.context), value: ConstantValue::Uint(ir_type_str_size_in_bytes(lookup.context, &ir_type)), @@ -729,10 +876,10 @@ fn const_eval_intrinsic( value: ConstantValue::Bool(val1 < val2), })) } - sway_ast::Intrinsic::AddrOf => Ok(None), - sway_ast::Intrinsic::PtrAdd => Ok(None), - sway_ast::Intrinsic::PtrSub => Ok(None), - sway_ast::Intrinsic::IsReferenceType + sway_ast::Intrinsic::AddrOf + | sway_ast::Intrinsic::PtrAdd + | sway_ast::Intrinsic::PtrSub + | sway_ast::Intrinsic::IsReferenceType | sway_ast::Intrinsic::IsStrType | sway_ast::Intrinsic::Gtf | sway_ast::Intrinsic::StateClear @@ -742,7 +889,9 @@ fn const_eval_intrinsic( | sway_ast::Intrinsic::StateStoreQuad | sway_ast::Intrinsic::Log | sway_ast::Intrinsic::Revert - | sway_ast::Intrinsic::Smo => Ok(None), + | sway_ast::Intrinsic::Smo => Err(ConstEvalError::CannotBeEvaluatedToConst { + span: intrinsic.span.clone(), + }), sway_ast::Intrinsic::Not => { // Not works only with uint at the moment // `bool` ops::Not implementation uses `__eq`. @@ -772,3 +921,148 @@ fn const_eval_intrinsic( } } } + +#[cfg(test)] +mod tests { + use super::*; + use sway_ir::Kind; + + /// This function validates if an expression can be converted to [Constant]. + /// + /// The flag `is_constant` is used to define if the expression should be convertible or not. + /// `prefix` is any valid code at top level, useful to declare types. + /// + /// Example: + /// + /// ```rust,ignore + /// assert_is_constant(true, "enum Color { Blue: u64 }", "Color::Blue(1)"); + /// assert_is_constant(false, "", "{return 1; 1}"); + /// ``` + /// + /// It DOES NOT have access to the std lib, and constants, and other features that demand full compilation. + fn assert_is_constant(is_constant: bool, prefix: &str, expr: &str) { + let engines = Engines::default(); + let mut context = Context::new(engines.se()); + let mut md_mgr = MetadataManager::default(); + let core_lib = namespace::Module::default(); + + let mut performance_data = sway_utils::PerformanceData::default(); + + let r = crate::compile_to_ast( + &engines, + std::sync::Arc::from(format!("library; {prefix} fn f() -> u64 {{ {expr}; 0 }}")), + core_lib, + None, + "test", + &mut performance_data, + ); + + if !r.errors.is_empty() { + panic!("{:#?}", r.errors); + } + + let f = r.value.unwrap(); + let f = f.typed.unwrap(); + + let f = f + .declarations + .iter() + .find_map(|x| match x { + ty::TyDecl::FunctionDecl(x) if x.name.as_str() == "f" => Some(x), + _ => None, + }) + .expect("An function named `f` was not found."); + + let f = engines.de().get_function(&f.decl_id); + let expr_under_test = f.body.contents.first().unwrap(); + + let expr_under_test = match &expr_under_test.content { + ty::TyAstNodeContent::Expression(expr_under_test) => expr_under_test.clone(), + ty::TyAstNodeContent::Declaration(crate::language::ty::TyDecl::ConstantDecl(decl)) => { + let decl = engines.de().get_constant(&decl.decl_id); + decl.value.unwrap() + } + x => todo!("{x:?}"), + }; + + let module = Module::new(&mut context, Kind::Library); + let actual_constant = compile_constant_expression_to_constant( + &engines, + &mut context, + &mut md_mgr, + module, + None, + None, + &expr_under_test, + ); + + match (is_constant, actual_constant) { + (true, Ok(_)) => {} + (true, Err(err)) => { + panic!("Expression cannot be converted to constant: {expr:?}\nPrefix: {prefix:?}\nExpr:{expr_under_test:#?}\nError: {err:#?}"); + } + (false, Ok(constant)) => { + panic!("Expression unexpectedly can be converted to constant: {expr:?}\nPrefix: {prefix:?}\nExpr:{expr_under_test:#?}\nConstant: {constant:#?}"); + } + (false, Err(_)) => {} + } + } + + #[test] + fn const_eval_test() { + // Expressions that can be converted to constant + assert_is_constant(true, "", "1"); + assert_is_constant(true, "", "true"); + assert_is_constant(true, "fn one() -> u64 { 1 }", "one()"); + assert_is_constant(true, "fn id(x: u64) -> u64 { x }", "id(1)"); + assert_is_constant(true, "enum Color { Blue: () }", "Color::Blue"); + assert_is_constant(true, "enum Color { Blue: u64 }", "Color::Blue(1)"); + assert_is_constant(true, "struct Person { age: u64 }", "Person { age: 1 }"); + assert_is_constant(true, "struct Person { age: u64 }", "Person { age: 1 }.age"); + assert_is_constant(true, "", "if true { 1 } else { 0 }"); + assert_is_constant(true, "", "(0,1).0"); + assert_is_constant(true, "", "[0,1][0]"); + + // Expressions that cannot be converted to constant + assert_is_constant(false, "", "{ return 1; }"); + assert_is_constant(false, "", "{ return 1; 1}"); + assert_is_constant( + false, + "enum Color { Blue: u64 }", + "Color::Blue({ return 1; 1})", + ); + assert_is_constant( + false, + "struct Person { age: u64 }", + "Person { age: { return 1; 1} }", + ); + assert_is_constant( + false, + "struct Person { age: u64 }", + "Person { age: { let mut x = 0; x = 1; 1} }", + ); + // At the moment this is not constant because of the "return" + assert_is_constant(false, "fn id(x: u64) -> u64 { return x; }", "id(1)"); + assert_is_constant(false, "", "[0,1][2]"); + assert_is_constant( + false, + "enum Color { Blue: u64 }", + "Color::Blue({return 1;})", + ); + + // Code blocks that can be converted to constants + assert_is_constant(true, "", "{ 1 }"); + assert_is_constant(true, "", "{ let a = 1; a }"); + assert_is_constant(true, "", "{ const a = 1; a }"); + assert_is_constant(true, "", "{ struct A {} 1 }"); + assert_is_constant(true, "fn id(x: u64) -> u64 { { let x = 2; }; x }", "id(1)"); + + // Code blocks that cannot be converted to constants + assert_is_constant(false, "", "{ let a = 1; }"); + assert_is_constant(false, "", "{ const a = 1; }"); + assert_is_constant(false, "", "{ struct A {} }"); + assert_is_constant(false, "", "{ return 1; 1 }"); + assert_is_constant(false, "", "{ }"); + assert_is_constant(false, "fn id(x: u64) -> u64 { { return 1; }; x }", "id(1)"); + } +} diff --git a/sway-error/src/error.rs b/sway-error/src/error.rs index 5ed2fbdcb13..821120f4c39 100644 --- a/sway-error/src/error.rs +++ b/sway-error/src/error.rs @@ -355,6 +355,8 @@ pub enum CompileError { TraitNotFound { name: String, span: Span }, #[error("This expression is not valid on the left hand side of a reassignment.")] InvalidExpressionOnLhs { span: Span }, + #[error("This code cannot be evaluated to a constant")] + CannotBeEvaluatedToConst { span: Span }, #[error("{} \"{method_name}\" expects {expected} {} but you provided {received}.", if *dot_syntax_used { "Method" } else { "Function" }, if *expected == 1usize { "argument" } else {"arguments"}, @@ -821,6 +823,7 @@ impl Spanned for CompileError { ConfigurableInLibrary { span } => span.clone(), NameDefinedMultipleTimes { span, .. } => span.clone(), MultipleApplicableItemsInScope { span, .. } => span.clone(), + CannotBeEvaluatedToConst { span } => span.clone(), } } } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/const_eval_bad_struct_with_return/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/const_eval_bad_struct_with_return/Forc.lock new file mode 100644 index 00000000000..b2595d83cef --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/const_eval_bad_struct_with_return/Forc.lock @@ -0,0 +1,3 @@ +[[package]] +name = 'const_eval_bad_struct_with_return' +source = 'member' diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/const_eval_bad_struct_with_return/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/const_eval_bad_struct_with_return/Forc.toml new file mode 100644 index 00000000000..02811d183f4 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/const_eval_bad_struct_with_return/Forc.toml @@ -0,0 +1,6 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "const_eval_bad_struct_with_return" +implicit-std = false diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/const_eval_bad_struct_with_return/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/const_eval_bad_struct_with_return/src/main.sw new file mode 100644 index 00000000000..8d8712ffbd3 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/const_eval_bad_struct_with_return/src/main.sw @@ -0,0 +1,24 @@ +script; + +struct MyStruct { + x: u64 +} + +// OK +const A: MyStruct = MyStruct { + x: { + 1 + } +}; + +// NOK +const B: MyStruct = MyStruct { + x: { + return 1; + 1 + } +}; + +fn main() -> u64 { + 0 +} diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/const_eval_bad_struct_with_return/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/const_eval_bad_struct_with_return/test.toml new file mode 100644 index 00000000000..e30bd06cf45 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/const_eval_bad_struct_with_return/test.toml @@ -0,0 +1,4 @@ +category = "fail" + +# check: $()const B +# check: $()Could not evaluate initializer to a const declaration.