diff --git a/docs/book/src/basics/control_flow.md b/docs/book/src/basics/control_flow.md index 2eaababac66..34b4f1a8022 100644 --- a/docs/book/src/basics/control_flow.md +++ b/docs/book/src/basics/control_flow.md @@ -62,7 +62,7 @@ Some examples of how you can use a `match` expression: ### `while` -Loops in Sway are currently limited to `while` loops. This is what they look like: +This is what a `while` loop looks like: ```sway while counter < 10 { @@ -72,9 +72,21 @@ while counter < 10 { You need the `while` keyword, some condition (`value < 10` in this case) which will be evaluated each iteration, and a block of code inside the curly braces (`{...}`) to execute each iteration. +### `for` + +This is what a `for` loop that computes the sum of a vector of numbers looks like: + +```sway +for element in vector.iter() { + sum += element; +} +``` + +You need the `for` keyword, some pattern that contains variable names such as `element` in this case, the `ìn` keyword followed by an iterator, and a block of code inside the curly braces (`{...}`) to execute each iteration. `vector.iter()` in the example above returns an iterator for the `vector`. In each iteration, the value of `element` is updated with the next value in the iterator until the end of the vector is reached and the `for` loop iteration ends. + ### `break` and `continue` -`break` and `continue` keywords are available to use inside the body of a `while` loop. The purpose of the `break` statement is to break out of a loop early: +`break` and `continue` keywords are available to use inside the body of a `while` or `for` loop. The purpose of the `break` statement is to break out of a loop early: ```sway {{#include ../../../../examples/break_and_continue/src/main.sw:break_example}} diff --git a/sway-ast/src/expr/mod.rs b/sway-ast/src/expr/mod.rs index 118da3d2909..91537e01ddb 100644 --- a/sway-ast/src/expr/mod.rs +++ b/sway-ast/src/expr/mod.rs @@ -41,6 +41,13 @@ pub enum Expr { condition: Box, block: Braces, }, + For { + for_token: ForToken, + in_token: InToken, + value_pattern: Pattern, + iterator: Box, + block: Braces, + }, FuncApp { func: Box, args: Parens>, @@ -220,6 +227,9 @@ impl Spanned for Expr { Expr::While { while_token, block, .. } => Span::join(while_token.span(), block.span()), + Expr::For { + for_token, block, .. + } => Span::join(for_token.span(), block.span()), Expr::FuncApp { func, args } => Span::join(func.span(), args.span()), Expr::Index { target, arg } => Span::join(target.span(), arg.span()), Expr::MethodCall { target, args, .. } => Span::join(target.span(), args.span()), @@ -490,13 +500,52 @@ impl Expr { } pub fn is_control_flow(&self) -> bool { - matches!( - self, + match self { Expr::Block(..) - | Expr::Asm(..) - | Expr::If(..) - | Expr::Match { .. } - | Expr::While { .. }, - ) + | Expr::Asm(..) + | Expr::If(..) + | Expr::Match { .. } + | Expr::While { .. } + | Expr::For { .. } => true, + Expr::Error(..) + | Expr::Path(..) + | Expr::Literal(..) + | Expr::AbiCast { .. } + | Expr::Struct { .. } + | Expr::Tuple(..) + | Expr::Parens(..) + | Expr::Array(..) + | Expr::Return { .. } + | Expr::FuncApp { .. } + | Expr::Index { .. } + | Expr::MethodCall { .. } + | Expr::FieldProjection { .. } + | Expr::TupleFieldProjection { .. } + | Expr::Ref { .. } + | Expr::Deref { .. } + | Expr::Not { .. } + | Expr::Mul { .. } + | Expr::Div { .. } + | Expr::Pow { .. } + | Expr::Modulo { .. } + | Expr::Add { .. } + | Expr::Sub { .. } + | Expr::Shl { .. } + | Expr::Shr { .. } + | Expr::BitAnd { .. } + | Expr::BitXor { .. } + | Expr::BitOr { .. } + | Expr::Equal { .. } + | Expr::NotEqual { .. } + | Expr::LessThan { .. } + | Expr::GreaterThan { .. } + | Expr::LessThanEq { .. } + | Expr::GreaterThanEq { .. } + | Expr::LogicalAnd { .. } + | Expr::LogicalOr { .. } + | Expr::Reassignment { .. } + | Expr::Break { .. } + | Expr::Continue { .. } => false, + } } } diff --git a/sway-ast/src/keywords.rs b/sway-ast/src/keywords.rs index 4ab0554bbb3..0e3ea802da0 100644 --- a/sway-ast/src/keywords.rs +++ b/sway-ast/src/keywords.rs @@ -61,6 +61,7 @@ define_keyword!(FnToken, "fn"); define_keyword!(TraitToken, "trait"); define_keyword!(ImplToken, "impl"); define_keyword!(ForToken, "for"); +define_keyword!(InToken, "in"); define_keyword!(AbiToken, "abi"); define_keyword!(ConstToken, "const"); define_keyword!(StorageToken, "storage"); 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 e0fc9f088f7..7da1e368138 100644 --- a/sway-core/src/control_flow_analysis/dead_code_analysis.rs +++ b/sway-core/src/control_flow_analysis/dead_code_analysis.rs @@ -1772,6 +1772,17 @@ fn connect_expression<'eng: 'cfg, 'cfg>( } Ok(vec![while_loop_exit]) } + ForLoop { desugared, .. } => connect_expression( + engines, + &desugared.expression, + graph, + leaves, + exit_node, + label, + tree_type, + expression_span, + options, + ), Break => { let break_node = graph.add_node("break".to_string().into()); for leaf in leaves { diff --git a/sway-core/src/ir_generation/const_eval.rs b/sway-core/src/ir_generation/const_eval.rs index acc06b846b7..dc49ddeeb32 100644 --- a/sway-core/src/ir_generation/const_eval.rs +++ b/sway-core/src/ir_generation/const_eval.rs @@ -673,7 +673,8 @@ fn const_eval_typed_expr( | ty::TyExpressionVariant::UnsafeDowncast { .. } | ty::TyExpressionVariant::Break | ty::TyExpressionVariant::Continue - | ty::TyExpressionVariant::WhileLoop { .. } => { + | ty::TyExpressionVariant::WhileLoop { .. } + | ty::TyExpressionVariant::ForLoop { .. } => { return Err(ConstEvalError::CannotBeEvaluatedToConst { span: expr.span.clone(), }) diff --git a/sway-core/src/ir_generation/function.rs b/sway-core/src/ir_generation/function.rs index 3102372ef32..25ba68ce6c5 100644 --- a/sway-core/src/ir_generation/function.rs +++ b/sway-core/src/ir_generation/function.rs @@ -587,6 +587,9 @@ impl<'eng> FnCompiler<'eng> { ty::TyExpressionVariant::WhileLoop { body, condition } => { self.compile_while_loop(context, md_mgr, body, condition, span_md_idx) } + ty::TyExpressionVariant::ForLoop { desugared } => { + self.compile_expression(context, md_mgr, desugared) + } ty::TyExpressionVariant::Break => { match self.block_to_break_to { // If `self.block_to_break_to` is not None, then it has been set inside diff --git a/sway-core/src/language/parsed/expression/mod.rs b/sway-core/src/language/parsed/expression/mod.rs index b9a691844d9..6cc1054775d 100644 --- a/sway-core/src/language/parsed/expression/mod.rs +++ b/sway-core/src/language/parsed/expression/mod.rs @@ -235,6 +235,11 @@ pub struct WhileLoopExpression { pub body: CodeBlock, } +#[derive(Debug, Clone)] +pub struct ForLoopExpression { + pub desugared: Box, +} + #[derive(Debug, Clone)] pub struct ReassignmentExpression { pub lhs: ReassignmentTarget, @@ -304,6 +309,8 @@ pub enum ExpressionKind { /// A control flow element which loops continually until some boolean expression evaluates as /// `false`. WhileLoop(WhileLoopExpression), + /// A control flow element which loops between values of an iterator. + ForLoop(ForLoopExpression), Break, Continue, Reassignment(ReassignmentExpression), diff --git a/sway-core/src/language/ty/expression/expression.rs b/sway-core/src/language/ty/expression/expression.rs index 4ef9d6a9b50..8af73292e6c 100644 --- a/sway-core/src/language/ty/expression/expression.rs +++ b/sway-core/src/language/ty/expression/expression.rs @@ -291,6 +291,9 @@ impl CollectTypesMetadata for TyExpression { res.append(&mut content.collect_types_metadata(handler, ctx)?); } } + ForLoop { desugared } => { + res.append(&mut desugared.collect_types_metadata(handler, ctx)?); + } ImplicitReturn(exp) | Return(exp) => { res.append(&mut exp.collect_types_metadata(handler, ctx)?) } @@ -394,6 +397,9 @@ impl DeterministicallyAborts for TyExpression { condition.deterministically_aborts(decl_engine, check_call_body) || body.deterministically_aborts(decl_engine, check_call_body) } + ForLoop { desugared } => { + desugared.deterministically_aborts(decl_engine, check_call_body) + } Break => false, Continue => false, Reassignment(reassignment) => reassignment diff --git a/sway-core/src/language/ty/expression/expression_variant.rs b/sway-core/src/language/ty/expression/expression_variant.rs index b6ccec4d4a5..ca78e919b11 100644 --- a/sway-core/src/language/ty/expression/expression_variant.rs +++ b/sway-core/src/language/ty/expression/expression_variant.rs @@ -153,6 +153,9 @@ pub enum TyExpressionVariant { condition: Box, body: TyCodeBlock, }, + ForLoop { + desugared: Box, + }, Break, Continue, Reassignment(Box), @@ -611,6 +614,9 @@ impl HashWithEngines for TyExpressionVariant { condition.hash(state, engines); body.hash(state, engines); } + Self::ForLoop { desugared } => { + desugared.hash(state, engines); + } Self::Break | Self::Continue | Self::FunctionParameter => {} Self::Reassignment(exp) => { exp.hash(state, engines); @@ -765,6 +771,9 @@ impl SubstTypes for TyExpressionVariant { condition.subst(type_mapping, engines); body.subst(type_mapping, engines); } + ForLoop { ref mut desugared } => { + desugared.subst(type_mapping, engines); + } Break => (), Continue => (), Reassignment(reassignment) => reassignment.subst(type_mapping, engines), @@ -903,6 +912,9 @@ impl ReplaceDecls for TyExpressionVariant { condition.replace_decls(decl_mapping, handler, ctx).ok(); body.replace_decls(decl_mapping, handler, ctx)?; } + ForLoop { ref mut desugared } => { + desugared.replace_decls(decl_mapping, handler, ctx).ok(); + } Break => (), Continue => (), Reassignment(reassignment) => { @@ -1018,6 +1030,9 @@ impl TypeCheckAnalysis for TyExpressionVariant { condition.type_check_analyze(handler, ctx)?; body.type_check_analyze(handler, ctx)?; } + TyExpressionVariant::ForLoop { desugared } => { + desugared.type_check_analyze(handler, ctx)?; + } TyExpressionVariant::Break => {} TyExpressionVariant::Continue => {} TyExpressionVariant::Reassignment(node) => { @@ -1143,6 +1158,9 @@ impl TypeCheckFinalization for TyExpressionVariant { condition.type_check_finalize(handler, ctx)?; body.type_check_finalize(handler, ctx)?; } + TyExpressionVariant::ForLoop { desugared } => { + desugared.type_check_finalize(handler, ctx)?; + } TyExpressionVariant::Break => {} TyExpressionVariant::Continue => {} TyExpressionVariant::Reassignment(node) => { @@ -1263,6 +1281,9 @@ impl UpdateConstantExpression for TyExpressionVariant { condition.update_constant_expression(engines, implementing_type); body.update_constant_expression(engines, implementing_type); } + ForLoop { ref mut desugared } => { + desugared.update_constant_expression(engines, implementing_type); + } Break => (), Continue => (), Reassignment(reassignment) => { @@ -1413,6 +1434,7 @@ impl DebugWithEngines for TyExpressionVariant { TyExpressionVariant::WhileLoop { condition, .. } => { format!("while loop on {:?}", engines.help_out(&**condition)) } + TyExpressionVariant::ForLoop { .. } => "for loop".to_string(), TyExpressionVariant::Break => "break".to_string(), TyExpressionVariant::Continue => "continue".to_string(), TyExpressionVariant::Reassignment(reassignment) => { 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 2f12f975c1b..cdcfad26a9a 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 @@ -358,6 +358,9 @@ impl ty::TyExpression { ExpressionKind::WhileLoop(WhileLoopExpression { condition, body }) => { Self::type_check_while_loop(handler, ctx.by_ref(), *condition, body, span) } + ExpressionKind::ForLoop(ForLoopExpression { desugared }) => { + Self::type_check_for_loop(handler, ctx.by_ref(), *desugared) + } ExpressionKind::Break => { let expr = ty::TyExpression { expression: ty::TyExpressionVariant::Break, @@ -1941,6 +1944,14 @@ impl ty::TyExpression { Ok(exp) } + fn type_check_for_loop( + handler: &Handler, + ctx: TypeCheckContext, + desugared: Expression, + ) -> Result { + Self::type_check(handler, ctx, desugared) + } + fn type_check_reassignment( handler: &Handler, ctx: TypeCheckContext, diff --git a/sway-core/src/semantic_analysis/cei_pattern_analysis.rs b/sway-core/src/semantic_analysis/cei_pattern_analysis.rs index fab9805e56d..6f75e37681f 100644 --- a/sway-core/src/semantic_analysis/cei_pattern_analysis.rs +++ b/sway-core/src/semantic_analysis/cei_pattern_analysis.rs @@ -343,6 +343,7 @@ fn analyze_expression( } res_effs } + ForLoop { desugared } => analyze_expression(engines, desugared, block_name, warnings), AsmExpression { registers, body, .. } => { @@ -572,6 +573,7 @@ fn effects_of_expression(engines: &Engines, expr: &ty::TyExpression) -> HashSet< .union(&effects_of_codeblock(engines, body)) .cloned() .collect(), + ForLoop { desugared } => effects_of_expression(engines, desugared), FunctionApplication { fn_ref, arguments, diff --git a/sway-core/src/semantic_analysis/coins_analysis.rs b/sway-core/src/semantic_analysis/coins_analysis.rs index 65473a099e7..be39eecc8f8 100644 --- a/sway-core/src/semantic_analysis/coins_analysis.rs +++ b/sway-core/src/semantic_analysis/coins_analysis.rs @@ -64,7 +64,8 @@ pub fn possibly_nonzero_u64_expression( | StructFieldAccess { .. } | TupleElemAccess { .. } | StorageAccess(_) - | WhileLoop { .. } => true, + | WhileLoop { .. } + | ForLoop { .. } => true, // The following expression variants are unreachable, because of the type system // but we still consider these as non-zero to be on the safe side LazyOperator { .. } diff --git a/sway-core/src/semantic_analysis/node_dependencies.rs b/sway-core/src/semantic_analysis/node_dependencies.rs index d6728ac363e..f566f510b3d 100644 --- a/sway-core/src/semantic_analysis/node_dependencies.rs +++ b/sway-core/src/semantic_analysis/node_dependencies.rs @@ -656,6 +656,9 @@ impl Dependencies { }) => self .gather_from_expr(engines, condition) .gather_from_block(engines, body), + ExpressionKind::ForLoop(ForLoopExpression { desugared, .. }) => { + self.gather_from_expr(engines, desugared) + } ExpressionKind::Reassignment(reassignment) => { self.gather_from_expr(engines, &reassignment.rhs) } 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 9164b4fa098..23e43d61125 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 @@ -2047,6 +2047,220 @@ fn expr_to_expression( }), span, }, + Expr::For { + value_pattern, + iterator, + block, + .. + } => { + // Desugar for loop into: + // let mut iterable = iterator; + // while true { + // let value_opt = iterable.next(); + // if value_opt.is_none() { + // break; + // } + // let value = value_opt.unwrap(); + // code_block + // } + let value_opt_ident = Ident::new_no_span("__for_value_opt".into()); + let value_opt_expr = Expression { + kind: ExpressionKind::Variable(value_opt_ident.clone()), + span: Span::dummy(), + }; + + let iterable_ident = Ident::new_no_span("__for_iterable".into()); + let iterable_expr = Expression { + kind: ExpressionKind::Variable(iterable_ident.clone()), + span: Span::dummy(), + }; + + let iterator_expr = expr_to_expression(context, handler, engines, *iterator.clone())?; + + // Declare iterable with iterator return + let iterable_decl = engines.pe().insert(VariableDeclaration { + type_ascription: { + let type_id = engines.te().insert(engines, TypeInfo::Unknown, None); + TypeArgument { + type_id, + initial_type_id: type_id, + span: iterable_ident.clone().span(), + call_path_tree: None, + } + }, + name: iterable_ident, + is_mutable: true, + body: iterator_expr.clone(), + }); + + // iterable.next() expression + // We use iterator.span() so errors can point to it. + let set_value_opt_to_next_body_expr = Expression { + kind: ExpressionKind::MethodApplication(Box::new(MethodApplicationExpression { + arguments: vec![iterable_expr], + method_name_binding: TypeBinding { + inner: MethodName::FromModule { + method_name: Ident::new_with_override("next".into(), iterator.span()), + }, + type_arguments: TypeArgs::Regular(vec![]), + span: iterator.span(), + }, + contract_call_params: vec![], + })), + span: iterator.span(), + }; + + // Declare value_opt = iterable.next() + let value_opt_to_next_decl = engines.pe().insert(VariableDeclaration { + type_ascription: { + let type_id = engines.te().insert(engines, TypeInfo::Unknown, None); + TypeArgument { + type_id, + initial_type_id: type_id, + span: value_opt_ident.clone().span(), + call_path_tree: None, + } + }, + name: value_opt_ident, + is_mutable: true, + body: set_value_opt_to_next_body_expr.clone(), + }); + + // Call value_opt.is_none() + let value_opt_is_none = Expression { + kind: ExpressionKind::MethodApplication(Box::new(MethodApplicationExpression { + arguments: vec![value_opt_expr.clone()], + method_name_binding: TypeBinding { + inner: MethodName::FromModule { + method_name: Ident::new_no_span("is_none".into()), + }, + type_arguments: TypeArgs::Regular(vec![]), + span: Span::dummy(), + }, + contract_call_params: vec![], + })), + span: Span::dummy(), + }; + + // Call value_opt.unwrap() + // We use iterator.span() so mismatched types errors can point to it. + let value_opt_unwarp = Expression { + kind: ExpressionKind::MethodApplication(Box::new(MethodApplicationExpression { + arguments: vec![value_opt_expr], + method_name_binding: TypeBinding { + inner: MethodName::FromModule { + method_name: Ident::new_with_override("unwrap".into(), iterator.span()), + }, + type_arguments: TypeArgs::Regular(vec![]), + span: iterator.span(), + }, + contract_call_params: vec![], + })), + span: iterator.span(), + }; + + let pattern_ast_nodes = statement_let_to_ast_nodes_unfold( + context, + handler, + engines, + value_pattern.clone(), + None, + value_opt_unwarp, + value_pattern.span(), + )?; + + let mut while_body = + braced_code_block_contents_to_code_block(context, handler, engines, block)?; + + //At the beginning of while block do: + // let value_opt = iterable.next(); + // if value_opt.is_none() { + // break; + // } + // let value = value_opt.unwrap(); + // Note: Inserting in reverse order + + // let value = value_opt.unwrap(); + for node in pattern_ast_nodes.iter().rev() { + while_body.contents.insert(0, node.clone()); + } + + // if value_opt.is_none() { + // break; + // } + while_body.contents.insert( + 0, + AstNode { + content: AstNodeContent::Expression(Expression { + kind: ExpressionKind::If(IfExpression { + condition: Box::new(value_opt_is_none), + then: Box::new(Expression { + kind: ExpressionKind::CodeBlock(CodeBlock { + contents: vec![AstNode { + content: AstNodeContent::Expression(Expression { + kind: ExpressionKind::Break, + span: Span::dummy(), + }), + span: Span::dummy(), + }], + whole_block_span: Span::dummy(), + }), + span: Span::dummy(), + }), + r#else: None, + }), + span: Span::dummy(), + }), + span: Span::dummy(), + }, + ); + + // let value_opt = iterable.next(); + while_body.contents.insert( + 0, + AstNode { + content: AstNodeContent::Declaration(Declaration::VariableDeclaration( + value_opt_to_next_decl, + )), + span: Span::dummy(), + }, + ); + + let desugared = Expression { + kind: ExpressionKind::CodeBlock(CodeBlock { + contents: vec![ + AstNode { + content: AstNodeContent::Declaration(Declaration::VariableDeclaration( + iterable_decl, + )), + span: Span::dummy(), + }, + AstNode { + content: AstNodeContent::Expression(Expression { + kind: ExpressionKind::WhileLoop(WhileLoopExpression { + condition: Box::new(Expression { + kind: ExpressionKind::Literal(Literal::Boolean(true)), + span: Span::dummy(), + }), + body: while_body, + }), + span: Span::dummy(), + }), + span: Span::dummy(), + }, + ], + whole_block_span: Span::dummy(), + }), + span: span.clone(), + }; + + Expression { + kind: ExpressionKind::ForLoop(ForLoopExpression { + desugared: Box::new(desugared), + }), + span, + } + } Expr::FuncApp { func, args } => { let kind = expr_func_app_to_expression_kind(context, handler, engines, func, args)?; Expression { kind, span } @@ -3348,314 +3562,310 @@ fn statement_let_to_ast_nodes( engines: &Engines, statement_let: StatementLet, ) -> Result, ErrorEmitted> { - fn unfold( - context: &mut Context, - handler: &Handler, - engines: &Engines, - pattern: Pattern, - ty_opt: Option, - expression: Expression, - span: Span, - ) -> Result, ErrorEmitted> { - let ast_nodes = match pattern { - Pattern::Wildcard { .. } | Pattern::Var { .. } | Pattern::AmbiguousSingleIdent(..) => { - let (reference, mutable, name) = match pattern { - Pattern::Var { - reference, - mutable, - name, - } => (reference, mutable, name), - Pattern::Wildcard { .. } => (None, None, Ident::new_no_span("_".into())), - Pattern::AmbiguousSingleIdent(ident) => (None, None, ident), - _ => unreachable!(), - }; - if reference.is_some() { - let error = ConvertParseTreeError::RefVariablesNotSupported { span }; - return Err(handler.emit_err(error.into())); - } - let type_ascription = match ty_opt { - Some(ty) => ty_to_type_argument(context, handler, engines, ty)?, - None => { - let type_id = engines.te().insert(engines, TypeInfo::Unknown, None); - TypeArgument { - type_id, - initial_type_id: type_id, - span: name.span(), - call_path_tree: None, - } - } - }; - let var_decl = engines.pe().insert(VariableDeclaration { + let span = statement_let.span(); + let initial_expression = expr_to_expression(context, handler, engines, statement_let.expr)?; + statement_let_to_ast_nodes_unfold( + context, + handler, + engines, + statement_let.pattern, + statement_let.ty_opt.map(|(_colon_token, ty)| ty), + initial_expression, + span, + ) +} + +fn statement_let_to_ast_nodes_unfold( + context: &mut Context, + handler: &Handler, + engines: &Engines, + pattern: Pattern, + ty_opt: Option, + expression: Expression, + span: Span, +) -> Result, ErrorEmitted> { + let ast_nodes = match pattern { + Pattern::Wildcard { .. } | Pattern::Var { .. } | Pattern::AmbiguousSingleIdent(..) => { + let (reference, mutable, name) = match pattern { + Pattern::Var { + reference, + mutable, name, - type_ascription, - body: expression, - is_mutable: mutable.is_some(), - }); - let ast_node = AstNode { - content: AstNodeContent::Declaration(Declaration::VariableDeclaration( - var_decl, - )), - span, - }; - vec![ast_node] - } - Pattern::Literal(..) => { - let error = ConvertParseTreeError::LiteralPatternsNotSupportedHere { span }; - return Err(handler.emit_err(error.into())); - } - Pattern::Constant(..) => { - let error = ConvertParseTreeError::ConstantPatternsNotSupportedHere { span }; - return Err(handler.emit_err(error.into())); - } - Pattern::Constructor { .. } | Pattern::Error(..) => { - let error = ConvertParseTreeError::ConstructorPatternsNotSupportedHere { span }; + } => (reference, mutable, name), + Pattern::Wildcard { .. } => (None, None, Ident::new_no_span("_".into())), + Pattern::AmbiguousSingleIdent(ident) => (None, None, ident), + _ => unreachable!(), + }; + if reference.is_some() { + let error = ConvertParseTreeError::RefVariablesNotSupported { span }; return Err(handler.emit_err(error.into())); } - Pattern::Struct { path, fields, .. } => { - let mut ast_nodes = Vec::new(); - - // Generate a deterministic name for the destructured struct variable. - let destructured_struct_name = generate_destructured_struct_var_name( - context.next_destructured_struct_unique_suffix(), - ); - - let destructured_struct_name = - Ident::new_with_override(destructured_struct_name, path.prefix.name.span()); - - // Parse the type ascription and the type ascription span. - // In the event that the user did not provide a type ascription, - // it is set to TypeInfo::Unknown and the span to None. - let type_ascription = match &ty_opt { - Some(ty) => ty_to_type_argument(context, handler, engines, ty.clone())?, - None => { - let type_id = engines.te().insert(engines, TypeInfo::Unknown, None); - TypeArgument { - type_id, - initial_type_id: type_id, - span: destructured_struct_name.span(), - call_path_tree: None, - } + let type_ascription = match ty_opt { + Some(ty) => ty_to_type_argument(context, handler, engines, ty)?, + None => { + let type_id = engines.te().insert(engines, TypeInfo::Unknown, None); + TypeArgument { + type_id, + initial_type_id: type_id, + span: name.span(), + call_path_tree: None, } - }; - - // Save the destructure to the new name as a new variable declaration - let save_body_first = engines.pe().insert(VariableDeclaration { - name: destructured_struct_name.clone(), - type_ascription, - body: expression, - is_mutable: false, - }); - ast_nodes.push(AstNode { - content: AstNodeContent::Declaration(Declaration::VariableDeclaration( - save_body_first, - )), - span: span.clone(), - }); - - // create a new variable expression that points to the new destructured struct name that we just created - let new_expr = Expression { - kind: ExpressionKind::Variable(destructured_struct_name), - span: span.clone(), - }; - - // for all of the fields of the struct destructuring on the LHS, - // recursively create variable declarations - for pattern_struct_field in fields.into_inner().into_iter() { - let (field, recursive_pattern) = match pattern_struct_field { - PatternStructField::Field { - field_name, - pattern_opt, - } => { - let recursive_pattern = match pattern_opt { - Some((_colon_token, box_pattern)) => *box_pattern, - None => Pattern::Var { - reference: None, - mutable: None, - name: field_name.clone(), - }, - }; - (field_name, recursive_pattern) - } - PatternStructField::Rest { .. } => { - continue; - } - }; - - // recursively create variable declarations for the subpatterns on the LHS - // and add them to the ast nodes - ast_nodes.extend(unfold( - context, - handler, - engines, - recursive_pattern, - None, - Expression { - kind: ExpressionKind::Subfield(SubfieldExpression { - prefix: Box::new(new_expr.clone()), - field_to_access: field, - }), - span: span.clone(), - }, - span.clone(), - )?); } - ast_nodes - } - Pattern::Or { .. } => { - let error = ConvertParseTreeError::OrPatternsNotSupportedHere { span }; - return Err(handler.emit_err(error.into())); - } - Pattern::Tuple(pat_tuple) => { - let mut ast_nodes = Vec::new(); + }; + let var_decl = engines.pe().insert(VariableDeclaration { + name, + type_ascription, + body: expression, + is_mutable: mutable.is_some(), + }); + let ast_node = AstNode { + content: AstNodeContent::Declaration(Declaration::VariableDeclaration(var_decl)), + span, + }; + vec![ast_node] + } + Pattern::Literal(..) => { + let error = ConvertParseTreeError::LiteralPatternsNotSupportedHere { span }; + return Err(handler.emit_err(error.into())); + } + Pattern::Constant(..) => { + let error = ConvertParseTreeError::ConstantPatternsNotSupportedHere { span }; + return Err(handler.emit_err(error.into())); + } + Pattern::Constructor { .. } | Pattern::Error(..) => { + let error = ConvertParseTreeError::ConstructorPatternsNotSupportedHere { span }; + return Err(handler.emit_err(error.into())); + } + Pattern::Struct { path, fields, .. } => { + let mut ast_nodes = Vec::new(); - // Generate a deterministic name for the tuple. - let tuple_name = - generate_tuple_var_name(context.next_destructured_tuple_unique_suffix()); + // Generate a deterministic name for the destructured struct variable. + let destructured_struct_name = generate_destructured_struct_var_name( + context.next_destructured_struct_unique_suffix(), + ); - let tuple_name = Ident::new_with_override(tuple_name, span.clone()); + let destructured_struct_name = + Ident::new_with_override(destructured_struct_name, path.prefix.name.span()); - // Acript a second declaration to a tuple of placeholders to check that the tuple - // is properly sized to the pattern - let placeholders_type_ascription = { - let type_id = engines.te().insert( - engines, - TypeInfo::Tuple( - pat_tuple - .clone() - .into_inner() - .into_iter() - .map(|_| { - let initial_type_id = - engines.te().insert(engines, TypeInfo::Unknown, None); - let dummy_type_param = TypeParameter { - type_id: initial_type_id, - initial_type_id, - name_ident: Ident::new_with_override( - "_".into(), - span.clone(), - ), - trait_constraints: vec![], - trait_constraints_span: Span::dummy(), - is_from_parent: false, - }; - let initial_type_id = engines.te().insert( - engines, - TypeInfo::Placeholder(dummy_type_param), - None, - ); - TypeArgument { - type_id: initial_type_id, - initial_type_id, - call_path_tree: None, - span: Span::dummy(), - } - }) - .collect(), - ), - tuple_name.span().source_id(), - ); + // Parse the type ascription and the type ascription span. + // In the event that the user did not provide a type ascription, + // it is set to TypeInfo::Unknown and the span to None. + let type_ascription = match &ty_opt { + Some(ty) => ty_to_type_argument(context, handler, engines, ty.clone())?, + None => { + let type_id = engines.te().insert(engines, TypeInfo::Unknown, None); TypeArgument { type_id, initial_type_id: type_id, - span: tuple_name.span(), + span: destructured_struct_name.span(), call_path_tree: None, } - }; + } + }; - // Parse the type ascription and the type ascription span. - // In the event that the user did not provide a type ascription, - // it is set to TypeInfo::Unknown and the span to None. - let type_ascription = match &ty_opt { - Some(ty) => ty_to_type_argument(context, handler, engines, ty.clone())?, - None => placeholders_type_ascription.clone(), - }; + // Save the destructure to the new name as a new variable declaration + let save_body_first = engines.pe().insert(VariableDeclaration { + name: destructured_struct_name.clone(), + type_ascription, + body: expression, + is_mutable: false, + }); + ast_nodes.push(AstNode { + content: AstNodeContent::Declaration(Declaration::VariableDeclaration( + save_body_first, + )), + span: span.clone(), + }); - // Save the tuple to the new name as a new variable declaration. - let save_body_first = engines.pe().insert(VariableDeclaration { - name: tuple_name.clone(), - type_ascription, - body: expression, - is_mutable: false, - }); - ast_nodes.push(AstNode { - content: AstNodeContent::Declaration(Declaration::VariableDeclaration( - save_body_first, - )), - span: span.clone(), - }); + // create a new variable expression that points to the new destructured struct name that we just created + let new_expr = Expression { + kind: ExpressionKind::Variable(destructured_struct_name), + span: span.clone(), + }; - // create a variable expression that points to the new tuple name that we just created - let new_expr = Expression { - kind: ExpressionKind::Variable(tuple_name.clone()), - span: span.clone(), + // for all of the fields of the struct destructuring on the LHS, + // recursively create variable declarations + for pattern_struct_field in fields.into_inner().into_iter() { + let (field, recursive_pattern) = match pattern_struct_field { + PatternStructField::Field { + field_name, + pattern_opt, + } => { + let recursive_pattern = match pattern_opt { + Some((_colon_token, box_pattern)) => *box_pattern, + None => Pattern::Var { + reference: None, + mutable: None, + name: field_name.clone(), + }, + }; + (field_name, recursive_pattern) + } + PatternStructField::Rest { .. } => { + continue; + } }; - // Override the previous declaration with a tuple of placeholders to check the - // shape of the tuple - let check_tuple_shape_second = engines.pe().insert(VariableDeclaration { - name: tuple_name, - type_ascription: placeholders_type_ascription, - body: new_expr.clone(), - is_mutable: false, - }); - ast_nodes.push(AstNode { - content: AstNodeContent::Declaration(Declaration::VariableDeclaration( - check_tuple_shape_second, - )), - span: span.clone(), - }); + // recursively create variable declarations for the subpatterns on the LHS + // and add them to the ast nodes + ast_nodes.extend(statement_let_to_ast_nodes_unfold( + context, + handler, + engines, + recursive_pattern, + None, + Expression { + kind: ExpressionKind::Subfield(SubfieldExpression { + prefix: Box::new(new_expr.clone()), + field_to_access: field, + }), + span: span.clone(), + }, + span.clone(), + )?); + } + ast_nodes + } + Pattern::Or { .. } => { + let error = ConvertParseTreeError::OrPatternsNotSupportedHere { span }; + return Err(handler.emit_err(error.into())); + } + Pattern::Tuple(pat_tuple) => { + let mut ast_nodes = Vec::new(); - // from the possible type annotation, if the annotation was a tuple annotation, - // extract the internal types of the annotation - let tuple_tys_opt = match ty_opt { - Some(Ty::Tuple(tys)) => Some(tys.into_inner().to_tys()), - _ => None, - }; + // Generate a deterministic name for the tuple. + let tuple_name = + generate_tuple_var_name(context.next_destructured_tuple_unique_suffix()); - // for all of the elements in the tuple destructuring on the LHS, - // recursively create variable declarations - for (index, pattern) in pat_tuple.into_inner().into_iter().enumerate() { - // from the possible type annotation, grab the type at the index of the current element - // we are processing - let ty_opt = tuple_tys_opt - .as_ref() - .and_then(|tys| tys.get(index).cloned()); - - // recursively create variable declarations for the subpatterns on the LHS - // and add them to the ast nodes - ast_nodes.extend(unfold( - context, - handler, - engines, - pattern, - ty_opt, - Expression { - kind: ExpressionKind::TupleIndex(TupleIndexExpression { - prefix: Box::new(new_expr.clone()), - index, - index_span: span.clone(), - }), - span: span.clone(), - }, - span.clone(), - )?); + let tuple_name = Ident::new_with_override(tuple_name, span.clone()); + + // Acript a second declaration to a tuple of placeholders to check that the tuple + // is properly sized to the pattern + let placeholders_type_ascription = { + let type_id = engines.te().insert( + engines, + TypeInfo::Tuple( + pat_tuple + .clone() + .into_inner() + .into_iter() + .map(|_| { + let initial_type_id = + engines.te().insert(engines, TypeInfo::Unknown, None); + let dummy_type_param = TypeParameter { + type_id: initial_type_id, + initial_type_id, + name_ident: Ident::new_with_override("_".into(), span.clone()), + trait_constraints: vec![], + trait_constraints_span: Span::dummy(), + is_from_parent: false, + }; + let initial_type_id = engines.te().insert( + engines, + TypeInfo::Placeholder(dummy_type_param), + None, + ); + TypeArgument { + type_id: initial_type_id, + initial_type_id, + call_path_tree: None, + span: Span::dummy(), + } + }) + .collect(), + ), + tuple_name.span().source_id(), + ); + TypeArgument { + type_id, + initial_type_id: type_id, + span: tuple_name.span(), + call_path_tree: None, } - ast_nodes + }; + + // Parse the type ascription and the type ascription span. + // In the event that the user did not provide a type ascription, + // it is set to TypeInfo::Unknown and the span to None. + let type_ascription = match &ty_opt { + Some(ty) => ty_to_type_argument(context, handler, engines, ty.clone())?, + None => placeholders_type_ascription.clone(), + }; + + // Save the tuple to the new name as a new variable declaration. + let save_body_first = engines.pe().insert(VariableDeclaration { + name: tuple_name.clone(), + type_ascription, + body: expression, + is_mutable: false, + }); + ast_nodes.push(AstNode { + content: AstNodeContent::Declaration(Declaration::VariableDeclaration( + save_body_first, + )), + span: span.clone(), + }); + + // create a variable expression that points to the new tuple name that we just created + let new_expr = Expression { + kind: ExpressionKind::Variable(tuple_name.clone()), + span: span.clone(), + }; + + // Override the previous declaration with a tuple of placeholders to check the + // shape of the tuple + let check_tuple_shape_second = engines.pe().insert(VariableDeclaration { + name: tuple_name, + type_ascription: placeholders_type_ascription, + body: new_expr.clone(), + is_mutable: false, + }); + ast_nodes.push(AstNode { + content: AstNodeContent::Declaration(Declaration::VariableDeclaration( + check_tuple_shape_second, + )), + span: span.clone(), + }); + + // from the possible type annotation, if the annotation was a tuple annotation, + // extract the internal types of the annotation + let tuple_tys_opt = match ty_opt { + Some(Ty::Tuple(tys)) => Some(tys.into_inner().to_tys()), + _ => None, + }; + + // for all of the elements in the tuple destructuring on the LHS, + // recursively create variable declarations + for (index, pattern) in pat_tuple.into_inner().into_iter().enumerate() { + // from the possible type annotation, grab the type at the index of the current element + // we are processing + let ty_opt = tuple_tys_opt + .as_ref() + .and_then(|tys| tys.get(index).cloned()); + + // recursively create variable declarations for the subpatterns on the LHS + // and add them to the ast nodes + ast_nodes.extend(statement_let_to_ast_nodes_unfold( + context, + handler, + engines, + pattern, + ty_opt, + Expression { + kind: ExpressionKind::TupleIndex(TupleIndexExpression { + prefix: Box::new(new_expr.clone()), + index, + index_span: span.clone(), + }), + span: span.clone(), + }, + span.clone(), + )?); } - }; - Ok(ast_nodes) - } - let span = statement_let.span(); - let initial_expression = expr_to_expression(context, handler, engines, statement_let.expr)?; - unfold( - context, - handler, - engines, - statement_let.pattern, - statement_let.ty_opt.map(|(_colon_token, ty)| ty), - initial_expression, - span, - ) + ast_nodes + } + }; + Ok(ast_nodes) } fn submodule_to_include_statement(dependency: &Submodule) -> IncludeStatement { diff --git a/sway-lib-std/src/iterator.sw b/sway-lib-std/src/iterator.sw new file mode 100644 index 00000000000..f53165647d6 --- /dev/null +++ b/sway-lib-std/src/iterator.sw @@ -0,0 +1,41 @@ +library; + +use ::option::Option::{self, *}; + +pub trait Iterator { + /// The type of the elements being iterated over. + type Item; + /// Advances the iterator and returns the next value. + /// + /// Returns [`None`] when iteration is finished. Individual iterator + /// implementations may choose to resume iteration, and so calling `next()` + /// again may or may not eventually start returning [`Some(Item)`] again at some + /// point. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// let mut a = Vec::new(); + /// + /// a.push(1); + /// a.push(2); + /// a.push(3); + /// + /// let mut iter = a.iter(); + /// + /// // A call to next() returns the next value... + /// assert_eq!(Some(1), iter.next()); + /// assert_eq!(Some(2), iter.next()); + /// assert_eq!(Some(3), iter.next()); + /// + /// // ... and then None once it's over. + /// assert_eq!(None, iter.next()); + /// + /// // More calls may or may not return `None`. Here, they always will. + /// assert_eq!(None, iter.next()); + /// assert_eq!(None, iter.next()); + /// ``` + fn next(ref mut self) -> Option; +} diff --git a/sway-lib-std/src/lib.sw b/sway-lib-std/src/lib.sw index 04173d5ef33..1f6da4b9726 100644 --- a/sway-lib-std/src/lib.sw +++ b/sway-lib-std/src/lib.sw @@ -10,6 +10,7 @@ pub mod convert; pub mod intrinsics; pub mod alloc; pub mod registers; +pub mod iterator; pub mod vec; pub mod bytes; pub mod primitive_conversions; diff --git a/sway-lib-std/src/prelude.sw b/sway-lib-std/src/prelude.sw index ee79680264d..7fa5e6995d4 100644 --- a/sway-lib-std/src/prelude.sw +++ b/sway-lib-std/src/prelude.sw @@ -15,7 +15,7 @@ use ::storage::storage_key::*; // Collections use ::storage::storage_map::*; -use ::vec::Vec; +use ::vec::{Vec, VecIter}; // Error handling use ::assert::{assert, assert_eq, assert_ne}; diff --git a/sway-lib-std/src/vec.sw b/sway-lib-std/src/vec.sw index 2985768518a..31b46f049c8 100644 --- a/sway-lib-std/src/vec.sw +++ b/sway-lib-std/src/vec.sw @@ -5,6 +5,7 @@ use ::alloc::{alloc, realloc}; use ::assert::assert; use ::option::Option::{self, *}; use ::convert::From; +use ::iterator::*; struct RawVec { pub ptr: raw_ptr, @@ -587,6 +588,13 @@ impl Vec { index_ptr.write::(value); } + + pub fn iter(self) -> VecIter { + VecIter { + values: self, + index: 0, + } + } } impl AsRawSlice for Vec { @@ -633,6 +641,23 @@ where } } +pub struct VecIter { + values: Vec, + index: u64, +} + +impl Iterator for VecIter { + type Item = T; + fn next(ref mut self) -> Option { + if self.index >= self.values.len() { + return None + } + + self.index += 1; + self.values.get(self.index - 1) + } +} + #[test()] fn test_vec_with_len_1() { let mut ve: Vec = Vec::new(); diff --git a/sway-lsp/src/traverse/parsed_tree.rs b/sway-lsp/src/traverse/parsed_tree.rs index 1339ce3c94c..a678218ba14 100644 --- a/sway-lsp/src/traverse/parsed_tree.rs +++ b/sway-lsp/src/traverse/parsed_tree.rs @@ -21,9 +21,9 @@ use sway_core::{ AbiCastExpression, AbiDeclaration, AmbiguousPathExpression, ArrayExpression, ArrayIndexExpression, AstNode, AstNodeContent, ConstantDeclaration, Declaration, DelineatedPathExpression, EnumDeclaration, EnumVariant, Expression, ExpressionKind, - FunctionApplicationExpression, FunctionDeclaration, FunctionParameter, IfExpression, - ImplItem, ImplSelf, ImplTrait, ImportType, IncludeStatement, - IntrinsicFunctionExpression, LazyOperatorExpression, MatchExpression, + ForLoopExpression, FunctionApplicationExpression, FunctionDeclaration, + FunctionParameter, IfExpression, ImplItem, ImplSelf, ImplTrait, ImportType, + IncludeStatement, IntrinsicFunctionExpression, LazyOperatorExpression, MatchExpression, MethodApplicationExpression, MethodName, ParseModule, ParseProgram, ParseSubmodule, QualifiedPathRootTypes, ReassignmentExpression, ReassignmentTarget, Scrutinee, StorageAccessExpression, StorageDeclaration, StorageField, StructDeclaration, @@ -338,6 +338,9 @@ impl Parse for Expression { body.contents.par_iter().for_each(|node| node.parse(ctx)); condition.parse(ctx); } + ExpressionKind::ForLoop(ForLoopExpression { desugared }) => { + desugared.parse(ctx); + } ExpressionKind::Reassignment(reassignment) => { reassignment.parse(ctx); } diff --git a/sway-lsp/src/traverse/typed_tree.rs b/sway-lsp/src/traverse/typed_tree.rs index 13c17bbbd7b..5eaf5460227 100644 --- a/sway-lsp/src/traverse/typed_tree.rs +++ b/sway-lsp/src/traverse/typed_tree.rs @@ -570,6 +570,9 @@ impl Parse for ty::TyExpression { condition.parse(ctx); body.contents.par_iter().for_each(|node| node.parse(ctx)); } + ty::TyExpressionVariant::ForLoop { desugared, .. } => { + desugared.parse(ctx); + } ty::TyExpressionVariant::Break => (), ty::TyExpressionVariant::Continue => (), ty::TyExpressionVariant::Reassignment(reassignment) => { diff --git a/sway-parse/src/expr/mod.rs b/sway-parse/src/expr/mod.rs index b23962c7e76..d679cdafe39 100644 --- a/sway-parse/src/expr/mod.rs +++ b/sway-parse/src/expr/mod.rs @@ -720,6 +720,19 @@ fn parse_atom(parser: &mut Parser, ctx: ParseExprCtx) -> ParseResult { block, }); } + if let Some(for_token) = parser.take() { + let value_pattern = parser.parse()?; + let in_token = parser.parse()?; + let iterator = Box::new(parse_condition(parser)?); + let block = parser.parse()?; + return Ok(Expr::For { + for_token, + value_pattern, + in_token, + iterator, + block, + }); + } if parser.peek::().is_some() || parser.peek::().is_some() || parser.peek::().is_some() diff --git a/sway-parse/src/keywords.rs b/sway-parse/src/keywords.rs index cd5bdb25a61..0c952d0a52e 100644 --- a/sway-parse/src/keywords.rs +++ b/sway-parse/src/keywords.rs @@ -51,6 +51,7 @@ keyword_impls! { TraitToken, ImplToken, ForToken, + InToken, AbiToken, ConstToken, StorageToken, diff --git a/swayfmt/src/utils/language/expr/mod.rs b/swayfmt/src/utils/language/expr/mod.rs index 7138a32daf5..a46d696c720 100644 --- a/swayfmt/src/utils/language/expr/mod.rs +++ b/swayfmt/src/utils/language/expr/mod.rs @@ -268,6 +268,29 @@ impl Format for Expr { }, )?; } + Self::For { + for_token, + in_token, + value_pattern, + iterator, + block, + } => { + formatter.with_shape( + formatter + .shape + .with_code_line_from(LineStyle::Normal, ExprKind::Function), + |formatter| -> Result<(), FormatterError> { + write!(formatted_code, "{} ", for_token.span().as_str())?; + value_pattern.format(formatted_code, formatter)?; + write!(formatted_code, "{} ", in_token.span().as_str())?; + iterator.format(formatted_code, formatter)?; + IfExpr::open_curly_brace(formatted_code, formatter)?; + block.get().format(formatted_code, formatter)?; + IfExpr::close_curly_brace(formatted_code, formatter)?; + Ok(()) + }, + )?; + } Self::FuncApp { func, args } => { formatter.with_shape( formatter @@ -1014,6 +1037,20 @@ fn expr_leaf_spans(expr: &Expr) -> Vec { collected_spans.append(&mut block.leaf_spans()); collected_spans } + Expr::For { + for_token, + in_token, + value_pattern, + iterator, + block, + } => { + let mut collected_spans = vec![ByteSpan::from(for_token.span())]; + collected_spans.append(&mut value_pattern.leaf_spans()); + collected_spans.append(&mut vec![ByteSpan::from(in_token.span())]); + collected_spans.append(&mut iterator.leaf_spans()); + collected_spans.append(&mut block.leaf_spans()); + collected_spans + } Expr::FuncApp { func, args } => { let mut collected_spans = Vec::new(); collected_spans.append(&mut func.leaf_spans()); diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/for_bad_iterator/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/for_bad_iterator/Forc.lock new file mode 100644 index 00000000000..b6d023ece3b --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/for_bad_iterator/Forc.lock @@ -0,0 +1,13 @@ +[[package]] +name = "core" +source = "path+from-root-D551181B5FCE5911" + +[[package]] +name = "for_bad_iterator" +source = "member" +dependencies = ["std"] + +[[package]] +name = "std" +source = "path+from-root-D551181B5FCE5911" +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/for_bad_iterator/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/for_bad_iterator/Forc.toml new file mode 100644 index 00000000000..f51a3847861 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/for_bad_iterator/Forc.toml @@ -0,0 +1,9 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +implicit-std = false +license = "Apache-2.0" +name = "for_bad_iterator" + +[dependencies] +std = { path = "../../../../../../sway-lib-std" } \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/for_bad_iterator/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/for_bad_iterator/src/main.sw new file mode 100644 index 00000000000..fc8fadabb45 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/for_bad_iterator/src/main.sw @@ -0,0 +1,21 @@ +script; + +fn main() -> u64 { + let mut vector = Vec::new(); + + vector.push(0); + vector.push(1); + vector.push(2); + vector.push(3); + vector.push(4); + + let mut i = 0; + + for _n in vector { + i += 1; + } + + assert(i == 5); + + 0 +} diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/for_bad_iterator/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/for_bad_iterator/test.toml new file mode 100644 index 00000000000..b406de3973a --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/for_bad_iterator/test.toml @@ -0,0 +1,4 @@ +category = "fail" + +# check: $()for _n in vector +# nextln: $()No method named "next" found for type "Vec". diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/for_mismatch_pattern_type/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/for_mismatch_pattern_type/Forc.lock new file mode 100644 index 00000000000..f3c13aef042 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/for_mismatch_pattern_type/Forc.lock @@ -0,0 +1,13 @@ +[[package]] +name = "core" +source = "path+from-root-ED9C2DBB478FF28E" + +[[package]] +name = "for_mismatch_pattern_type" +source = "member" +dependencies = ["std"] + +[[package]] +name = "std" +source = "path+from-root-ED9C2DBB478FF28E" +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/for_mismatch_pattern_type/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/for_mismatch_pattern_type/Forc.toml new file mode 100644 index 00000000000..7c28da46bea --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/for_mismatch_pattern_type/Forc.toml @@ -0,0 +1,9 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +implicit-std = false +license = "Apache-2.0" +name = "for_mismatch_pattern_type" + +[dependencies] +std = { path = "../../../../../../sway-lib-std" } \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/for_mismatch_pattern_type/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/for_mismatch_pattern_type/src/main.sw new file mode 100644 index 00000000000..8207abd9df3 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/for_mismatch_pattern_type/src/main.sw @@ -0,0 +1,21 @@ +script; + +fn main() -> u64 { + let mut vector = Vec::new(); + + vector.push(0); + vector.push(1); + vector.push(2); + vector.push(3); + vector.push(4); + + let mut i = 0; + + for (_n,_m) in vector.iter() { + i += 1; + } + + assert(i == 5); + + 0 +} diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/for_mismatch_pattern_type/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/for_mismatch_pattern_type/test.toml new file mode 100644 index 00000000000..210f9990ae8 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/for_mismatch_pattern_type/test.toml @@ -0,0 +1,7 @@ +category = "fail" + +# check: $()for (_n,_m) in vector.iter() +# nextln: $()Mismatched types. +# nextln: $()expected: (_, _) +# nextln: $()found: u64. +# nextln: $()help: Function return type does not match up with local type annotation. diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/for_loops/.gitignore b/test/src/e2e_vm_tests/test_programs/should_pass/language/for_loops/.gitignore new file mode 100644 index 00000000000..77d3844f58c --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/for_loops/.gitignore @@ -0,0 +1,2 @@ +out +target diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/for_loops/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/language/for_loops/Forc.lock new file mode 100644 index 00000000000..2375dcbedbd --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/for_loops/Forc.lock @@ -0,0 +1,13 @@ +[[package]] +name = "core" +source = "path+from-root-39A1897B6E883C90" + +[[package]] +name = "for_loops" +source = "member" +dependencies = ["std"] + +[[package]] +name = "std" +source = "path+from-root-39A1897B6E883C90" +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/for_loops/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/for_loops/Forc.toml new file mode 100644 index 00000000000..f760879fa9c --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/for_loops/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "for_loops" + +[dependencies] +std = { path = "../../../../../../../sway-lib-std" } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/for_loops/json_abi_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/language/for_loops/json_abi_oracle.json new file mode 100644 index 00000000000..03b2f150939 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/for_loops/json_abi_oracle.json @@ -0,0 +1,25 @@ +{ + "configurables": [], + "functions": [ + { + "attributes": null, + "inputs": [], + "name": "main", + "output": { + "name": "", + "type": 0, + "typeArguments": null + } + } + ], + "loggedTypes": [], + "messagesTypes": [], + "types": [ + { + "components": null, + "type": "bool", + "typeId": 0, + "typeParameters": null + } + ] +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/for_loops/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/for_loops/src/main.sw new file mode 100644 index 00000000000..3b738c5c2a5 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/for_loops/src/main.sw @@ -0,0 +1,119 @@ +script; + +fn test_simple_for() { + let mut vector = Vec::new(); + + vector.push(0); + vector.push(1); + vector.push(2); + vector.push(3); + vector.push(4); + + let mut i = 0; + + for n in vector.iter() { + assert(n == i); + i += 1; + } + + assert(i == 5); +} + +fn test_for_pattern_tuple() { + let mut vector = Vec::new(); + + vector.push((0, 0)); + vector.push((1, 0)); + vector.push((2, 0)); + vector.push((3, 0)); + vector.push((4, 0)); + + let mut i = 0; + + for (n, m) in vector.iter() { + assert(n == i); + assert(m == 0); + i += 1; + } + + assert(i == 5); +} + +fn test_for_nested() { + let mut vector = Vec::new(); + + vector.push(0); + vector.push(1); + vector.push(2); + vector.push(3); + vector.push(4); + + let mut i = 0; + + for n in vector.iter() { + let mut j = 0; + for m in vector.iter() { + assert(m == j); + j += 1; + } + assert(j == 5); + assert(n == i); + i += 1; + } + + assert(i == 5); +} + +fn test_for_break() { + let mut vector = Vec::new(); + + vector.push(0); + vector.push(1); + vector.push(2); + vector.push(3); + vector.push(4); + + let mut i = 0; + + for n in vector.iter() { + if n == 2 { + break; + } + assert(n == i); + i += 1; + } + + assert(i == 2); +} + +fn test_for_continue() { + let mut vector = Vec::new(); + + vector.push(0); + vector.push(1); + vector.push(2); + vector.push(3); + vector.push(4); + + let mut i = 0; + + for n in vector.iter() { + if n == 0 { + continue; + } + assert(n-1 == i); + i += 1; + } + + assert(i == 4); +} + +fn main() -> bool { + test_simple_for(); + test_for_pattern_tuple(); + test_for_nested(); + test_for_break(); + test_for_continue(); + + true +} diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/for_loops/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/for_loops/test.toml new file mode 100644 index 00000000000..ace9e6f3186 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/for_loops/test.toml @@ -0,0 +1,3 @@ +category = "run" +expected_result = { action = "return", value = 1 } +validate_abi = true diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/iterator/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/iterator/Forc.lock new file mode 100644 index 00000000000..96bcb5bc39f --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/iterator/Forc.lock @@ -0,0 +1,13 @@ +[[package]] +name = "core" +source = "path+from-root-E9A3C0D015679961" + +[[package]] +name = "iterator" +source = "member" +dependencies = ["std"] + +[[package]] +name = "std" +source = "path+from-root-E9A3C0D015679961" +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/iterator/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/iterator/Forc.toml new file mode 100644 index 00000000000..443ca5923cd --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/iterator/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +license = "Apache-2.0" +name = "iterator" +entry = "main.sw" + +[dependencies] +std = { path = "../../../../../../../sway-lib-std" } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/iterator/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/iterator/src/main.sw new file mode 100644 index 00000000000..a9ced30c93a --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/iterator/src/main.sw @@ -0,0 +1,19 @@ +script; + +fn main() -> bool { + let mut vector = Vec::new(); + vector.push(1); + vector.push(2); + vector.push(3); + vector.push(4); + + let mut iter = vector.iter(); + assert_eq(Some(1), iter.next()); + assert_eq(Some(2), iter.next()); + assert_eq(Some(3), iter.next()); + assert_eq(Some(4), iter.next()); + assert_eq(None, iter.next()); + assert_eq(None, iter.next()); + + true +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/iterator/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/iterator/test.toml new file mode 100644 index 00000000000..78a2c7303f6 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/iterator/test.toml @@ -0,0 +1,3 @@ +category = "run" +expected_result = { action = "return", value = 1 } +validate_abi = false diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/vec/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/vec/src/main.sw index 28079a5599c..eb80bc51725 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/vec/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/vec/src/main.sw @@ -23,6 +23,7 @@ fn main() -> bool { test_vector_new_string(); test_vector_new_array(); test_vector_with_capacity_u64(); + test_vector_iter(); true } @@ -2583,3 +2584,29 @@ fn test_vector_with_capacity_u64() { Some(_val) => revert(0), None => (), } } + +fn test_vector_iter() { + let mut vector = Vec::new(); + + let number0 = 0; + let number1 = 1; + let number2 = 2; + let number3 = 3; + let number4 = 4; + + vector.push(number0); + vector.push(number1); + vector.push(number2); + vector.push(number3); + vector.push(number4); + + let mut iter = vector.iter(); + + assert(iter.next() == Some(number0)); + assert(iter.next() == Some(number1)); + assert(iter.next() == Some(number2)); + assert(iter.next() == Some(number3)); + assert(iter.next() == Some(number4)); + assert(iter.next() == None); + assert(iter.next() == None); +}