Skip to content

Commit 80be608

Browse files
authored
Implements Iterator trait and for loops. (#5557)
## Description This implements an Iterator trait in std-lib, and adds iter() to Vec. This also adds parsing and desugaring of for loops. ``` for pattern in iterator { code_block } ``` is desugared 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 } ``` This also adds for loops documentation to the control flow docs. We still have to fix this issues: - #5567 - #5568 - #5570 - #5571 Closes #145 ## Checklist - [x] I have linked to any relevant issues. - [x] I have commented my code, particularly in hard-to-understand areas. - [x] I have updated the documentation where relevant (API docs, the reference, and the Sway book). - [x] I have added tests that prove my fix is effective or that my feature works. - [x] I have added (or requested a maintainer to add) the necessary `Breaking*` or `New Feature` labels where relevant. - [x] I have done my best to ensure that my PR adheres to [the Fuel Labs Code Review Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md). - [x] I have requested a review from the relevant team or maintainers.
1 parent 51e8c9d commit 80be608

File tree

42 files changed

+1101
-301
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1101
-301
lines changed

docs/book/src/basics/control_flow.md

+14-2
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ Some examples of how you can use a `match` expression:
6262

6363
### `while`
6464

65-
Loops in Sway are currently limited to `while` loops. This is what they look like:
65+
This is what a `while` loop looks like:
6666

6767
```sway
6868
while counter < 10 {
@@ -72,9 +72,21 @@ while counter < 10 {
7272

7373
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.
7474

75+
### `for`
76+
77+
This is what a `for` loop that computes the sum of a vector of numbers looks like:
78+
79+
```sway
80+
for element in vector.iter() {
81+
sum += element;
82+
}
83+
```
84+
85+
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.
86+
7587
### `break` and `continue`
7688

77-
`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:
89+
`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:
7890

7991
```sway
8092
{{#include ../../../../examples/break_and_continue/src/main.sw:break_example}}

sway-ast/src/expr/mod.rs

+56-7
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ pub enum Expr {
4141
condition: Box<Expr>,
4242
block: Braces<CodeBlockContents>,
4343
},
44+
For {
45+
for_token: ForToken,
46+
in_token: InToken,
47+
value_pattern: Pattern,
48+
iterator: Box<Expr>,
49+
block: Braces<CodeBlockContents>,
50+
},
4451
FuncApp {
4552
func: Box<Expr>,
4653
args: Parens<Punctuated<Expr, CommaToken>>,
@@ -220,6 +227,9 @@ impl Spanned for Expr {
220227
Expr::While {
221228
while_token, block, ..
222229
} => Span::join(while_token.span(), block.span()),
230+
Expr::For {
231+
for_token, block, ..
232+
} => Span::join(for_token.span(), block.span()),
223233
Expr::FuncApp { func, args } => Span::join(func.span(), args.span()),
224234
Expr::Index { target, arg } => Span::join(target.span(), arg.span()),
225235
Expr::MethodCall { target, args, .. } => Span::join(target.span(), args.span()),
@@ -490,13 +500,52 @@ impl Expr {
490500
}
491501

492502
pub fn is_control_flow(&self) -> bool {
493-
matches!(
494-
self,
503+
match self {
495504
Expr::Block(..)
496-
| Expr::Asm(..)
497-
| Expr::If(..)
498-
| Expr::Match { .. }
499-
| Expr::While { .. },
500-
)
505+
| Expr::Asm(..)
506+
| Expr::If(..)
507+
| Expr::Match { .. }
508+
| Expr::While { .. }
509+
| Expr::For { .. } => true,
510+
Expr::Error(..)
511+
| Expr::Path(..)
512+
| Expr::Literal(..)
513+
| Expr::AbiCast { .. }
514+
| Expr::Struct { .. }
515+
| Expr::Tuple(..)
516+
| Expr::Parens(..)
517+
| Expr::Array(..)
518+
| Expr::Return { .. }
519+
| Expr::FuncApp { .. }
520+
| Expr::Index { .. }
521+
| Expr::MethodCall { .. }
522+
| Expr::FieldProjection { .. }
523+
| Expr::TupleFieldProjection { .. }
524+
| Expr::Ref { .. }
525+
| Expr::Deref { .. }
526+
| Expr::Not { .. }
527+
| Expr::Mul { .. }
528+
| Expr::Div { .. }
529+
| Expr::Pow { .. }
530+
| Expr::Modulo { .. }
531+
| Expr::Add { .. }
532+
| Expr::Sub { .. }
533+
| Expr::Shl { .. }
534+
| Expr::Shr { .. }
535+
| Expr::BitAnd { .. }
536+
| Expr::BitXor { .. }
537+
| Expr::BitOr { .. }
538+
| Expr::Equal { .. }
539+
| Expr::NotEqual { .. }
540+
| Expr::LessThan { .. }
541+
| Expr::GreaterThan { .. }
542+
| Expr::LessThanEq { .. }
543+
| Expr::GreaterThanEq { .. }
544+
| Expr::LogicalAnd { .. }
545+
| Expr::LogicalOr { .. }
546+
| Expr::Reassignment { .. }
547+
| Expr::Break { .. }
548+
| Expr::Continue { .. } => false,
549+
}
501550
}
502551
}

sway-ast/src/keywords.rs

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ define_keyword!(FnToken, "fn");
6161
define_keyword!(TraitToken, "trait");
6262
define_keyword!(ImplToken, "impl");
6363
define_keyword!(ForToken, "for");
64+
define_keyword!(InToken, "in");
6465
define_keyword!(AbiToken, "abi");
6566
define_keyword!(ConstToken, "const");
6667
define_keyword!(StorageToken, "storage");

sway-core/src/control_flow_analysis/dead_code_analysis.rs

+11
Original file line numberDiff line numberDiff line change
@@ -1772,6 +1772,17 @@ fn connect_expression<'eng: 'cfg, 'cfg>(
17721772
}
17731773
Ok(vec![while_loop_exit])
17741774
}
1775+
ForLoop { desugared, .. } => connect_expression(
1776+
engines,
1777+
&desugared.expression,
1778+
graph,
1779+
leaves,
1780+
exit_node,
1781+
label,
1782+
tree_type,
1783+
expression_span,
1784+
options,
1785+
),
17751786
Break => {
17761787
let break_node = graph.add_node("break".to_string().into());
17771788
for leaf in leaves {

sway-core/src/ir_generation/const_eval.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -673,7 +673,8 @@ fn const_eval_typed_expr(
673673
| ty::TyExpressionVariant::UnsafeDowncast { .. }
674674
| ty::TyExpressionVariant::Break
675675
| ty::TyExpressionVariant::Continue
676-
| ty::TyExpressionVariant::WhileLoop { .. } => {
676+
| ty::TyExpressionVariant::WhileLoop { .. }
677+
| ty::TyExpressionVariant::ForLoop { .. } => {
677678
return Err(ConstEvalError::CannotBeEvaluatedToConst {
678679
span: expr.span.clone(),
679680
})

sway-core/src/ir_generation/function.rs

+3
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,9 @@ impl<'eng> FnCompiler<'eng> {
587587
ty::TyExpressionVariant::WhileLoop { body, condition } => {
588588
self.compile_while_loop(context, md_mgr, body, condition, span_md_idx)
589589
}
590+
ty::TyExpressionVariant::ForLoop { desugared } => {
591+
self.compile_expression(context, md_mgr, desugared)
592+
}
590593
ty::TyExpressionVariant::Break => {
591594
match self.block_to_break_to {
592595
// If `self.block_to_break_to` is not None, then it has been set inside

sway-core/src/language/parsed/expression/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,11 @@ pub struct WhileLoopExpression {
235235
pub body: CodeBlock,
236236
}
237237

238+
#[derive(Debug, Clone)]
239+
pub struct ForLoopExpression {
240+
pub desugared: Box<Expression>,
241+
}
242+
238243
#[derive(Debug, Clone)]
239244
pub struct ReassignmentExpression {
240245
pub lhs: ReassignmentTarget,
@@ -304,6 +309,8 @@ pub enum ExpressionKind {
304309
/// A control flow element which loops continually until some boolean expression evaluates as
305310
/// `false`.
306311
WhileLoop(WhileLoopExpression),
312+
/// A control flow element which loops between values of an iterator.
313+
ForLoop(ForLoopExpression),
307314
Break,
308315
Continue,
309316
Reassignment(ReassignmentExpression),

sway-core/src/language/ty/expression/expression.rs

+6
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,9 @@ impl CollectTypesMetadata for TyExpression {
291291
res.append(&mut content.collect_types_metadata(handler, ctx)?);
292292
}
293293
}
294+
ForLoop { desugared } => {
295+
res.append(&mut desugared.collect_types_metadata(handler, ctx)?);
296+
}
294297
ImplicitReturn(exp) | Return(exp) => {
295298
res.append(&mut exp.collect_types_metadata(handler, ctx)?)
296299
}
@@ -394,6 +397,9 @@ impl DeterministicallyAborts for TyExpression {
394397
condition.deterministically_aborts(decl_engine, check_call_body)
395398
|| body.deterministically_aborts(decl_engine, check_call_body)
396399
}
400+
ForLoop { desugared } => {
401+
desugared.deterministically_aborts(decl_engine, check_call_body)
402+
}
397403
Break => false,
398404
Continue => false,
399405
Reassignment(reassignment) => reassignment

sway-core/src/language/ty/expression/expression_variant.rs

+22
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@ pub enum TyExpressionVariant {
153153
condition: Box<TyExpression>,
154154
body: TyCodeBlock,
155155
},
156+
ForLoop {
157+
desugared: Box<TyExpression>,
158+
},
156159
Break,
157160
Continue,
158161
Reassignment(Box<TyReassignment>),
@@ -611,6 +614,9 @@ impl HashWithEngines for TyExpressionVariant {
611614
condition.hash(state, engines);
612615
body.hash(state, engines);
613616
}
617+
Self::ForLoop { desugared } => {
618+
desugared.hash(state, engines);
619+
}
614620
Self::Break | Self::Continue | Self::FunctionParameter => {}
615621
Self::Reassignment(exp) => {
616622
exp.hash(state, engines);
@@ -765,6 +771,9 @@ impl SubstTypes for TyExpressionVariant {
765771
condition.subst(type_mapping, engines);
766772
body.subst(type_mapping, engines);
767773
}
774+
ForLoop { ref mut desugared } => {
775+
desugared.subst(type_mapping, engines);
776+
}
768777
Break => (),
769778
Continue => (),
770779
Reassignment(reassignment) => reassignment.subst(type_mapping, engines),
@@ -903,6 +912,9 @@ impl ReplaceDecls for TyExpressionVariant {
903912
condition.replace_decls(decl_mapping, handler, ctx).ok();
904913
body.replace_decls(decl_mapping, handler, ctx)?;
905914
}
915+
ForLoop { ref mut desugared } => {
916+
desugared.replace_decls(decl_mapping, handler, ctx).ok();
917+
}
906918
Break => (),
907919
Continue => (),
908920
Reassignment(reassignment) => {
@@ -1018,6 +1030,9 @@ impl TypeCheckAnalysis for TyExpressionVariant {
10181030
condition.type_check_analyze(handler, ctx)?;
10191031
body.type_check_analyze(handler, ctx)?;
10201032
}
1033+
TyExpressionVariant::ForLoop { desugared } => {
1034+
desugared.type_check_analyze(handler, ctx)?;
1035+
}
10211036
TyExpressionVariant::Break => {}
10221037
TyExpressionVariant::Continue => {}
10231038
TyExpressionVariant::Reassignment(node) => {
@@ -1143,6 +1158,9 @@ impl TypeCheckFinalization for TyExpressionVariant {
11431158
condition.type_check_finalize(handler, ctx)?;
11441159
body.type_check_finalize(handler, ctx)?;
11451160
}
1161+
TyExpressionVariant::ForLoop { desugared } => {
1162+
desugared.type_check_finalize(handler, ctx)?;
1163+
}
11461164
TyExpressionVariant::Break => {}
11471165
TyExpressionVariant::Continue => {}
11481166
TyExpressionVariant::Reassignment(node) => {
@@ -1263,6 +1281,9 @@ impl UpdateConstantExpression for TyExpressionVariant {
12631281
condition.update_constant_expression(engines, implementing_type);
12641282
body.update_constant_expression(engines, implementing_type);
12651283
}
1284+
ForLoop { ref mut desugared } => {
1285+
desugared.update_constant_expression(engines, implementing_type);
1286+
}
12661287
Break => (),
12671288
Continue => (),
12681289
Reassignment(reassignment) => {
@@ -1413,6 +1434,7 @@ impl DebugWithEngines for TyExpressionVariant {
14131434
TyExpressionVariant::WhileLoop { condition, .. } => {
14141435
format!("while loop on {:?}", engines.help_out(&**condition))
14151436
}
1437+
TyExpressionVariant::ForLoop { .. } => "for loop".to_string(),
14161438
TyExpressionVariant::Break => "break".to_string(),
14171439
TyExpressionVariant::Continue => "continue".to_string(),
14181440
TyExpressionVariant::Reassignment(reassignment) => {

sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs

+11
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,9 @@ impl ty::TyExpression {
358358
ExpressionKind::WhileLoop(WhileLoopExpression { condition, body }) => {
359359
Self::type_check_while_loop(handler, ctx.by_ref(), *condition, body, span)
360360
}
361+
ExpressionKind::ForLoop(ForLoopExpression { desugared }) => {
362+
Self::type_check_for_loop(handler, ctx.by_ref(), *desugared)
363+
}
361364
ExpressionKind::Break => {
362365
let expr = ty::TyExpression {
363366
expression: ty::TyExpressionVariant::Break,
@@ -1941,6 +1944,14 @@ impl ty::TyExpression {
19411944
Ok(exp)
19421945
}
19431946

1947+
fn type_check_for_loop(
1948+
handler: &Handler,
1949+
ctx: TypeCheckContext,
1950+
desugared: Expression,
1951+
) -> Result<Self, ErrorEmitted> {
1952+
Self::type_check(handler, ctx, desugared)
1953+
}
1954+
19441955
fn type_check_reassignment(
19451956
handler: &Handler,
19461957
ctx: TypeCheckContext,

sway-core/src/semantic_analysis/cei_pattern_analysis.rs

+2
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ fn analyze_expression(
343343
}
344344
res_effs
345345
}
346+
ForLoop { desugared } => analyze_expression(engines, desugared, block_name, warnings),
346347
AsmExpression {
347348
registers, body, ..
348349
} => {
@@ -572,6 +573,7 @@ fn effects_of_expression(engines: &Engines, expr: &ty::TyExpression) -> HashSet<
572573
.union(&effects_of_codeblock(engines, body))
573574
.cloned()
574575
.collect(),
576+
ForLoop { desugared } => effects_of_expression(engines, desugared),
575577
FunctionApplication {
576578
fn_ref,
577579
arguments,

sway-core/src/semantic_analysis/coins_analysis.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ pub fn possibly_nonzero_u64_expression(
6464
| StructFieldAccess { .. }
6565
| TupleElemAccess { .. }
6666
| StorageAccess(_)
67-
| WhileLoop { .. } => true,
67+
| WhileLoop { .. }
68+
| ForLoop { .. } => true,
6869
// The following expression variants are unreachable, because of the type system
6970
// but we still consider these as non-zero to be on the safe side
7071
LazyOperator { .. }

sway-core/src/semantic_analysis/node_dependencies.rs

+3
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,9 @@ impl Dependencies {
656656
}) => self
657657
.gather_from_expr(engines, condition)
658658
.gather_from_block(engines, body),
659+
ExpressionKind::ForLoop(ForLoopExpression { desugared, .. }) => {
660+
self.gather_from_expr(engines, desugared)
661+
}
659662
ExpressionKind::Reassignment(reassignment) => {
660663
self.gather_from_expr(engines, &reassignment.rhs)
661664
}

0 commit comments

Comments
 (0)