Skip to content

Commit

Permalink
Implements Iterator trait and for loops. (#5557)
Browse files Browse the repository at this point in the history
## 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.
  • Loading branch information
esdrubal authored and sdankel committed Feb 8, 2024
1 parent 37b4e6e commit 2f605d5
Show file tree
Hide file tree
Showing 42 changed files with 1,101 additions and 301 deletions.
16 changes: 14 additions & 2 deletions docs/book/src/basics/control_flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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}}
Expand Down
63 changes: 56 additions & 7 deletions sway-ast/src/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ pub enum Expr {
condition: Box<Expr>,
block: Braces<CodeBlockContents>,
},
For {
for_token: ForToken,
in_token: InToken,
value_pattern: Pattern,
iterator: Box<Expr>,
block: Braces<CodeBlockContents>,
},
FuncApp {
func: Box<Expr>,
args: Parens<Punctuated<Expr, CommaToken>>,
Expand Down Expand Up @@ -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()),
Expand Down Expand Up @@ -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,
}
}
}
1 change: 1 addition & 0 deletions sway-ast/src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
11 changes: 11 additions & 0 deletions sway-core/src/control_flow_analysis/dead_code_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 2 additions & 1 deletion sway-core/src/ir_generation/const_eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
})
Expand Down
3 changes: 3 additions & 0 deletions sway-core/src/ir_generation/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions sway-core/src/language/parsed/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,11 @@ pub struct WhileLoopExpression {
pub body: CodeBlock,
}

#[derive(Debug, Clone)]
pub struct ForLoopExpression {
pub desugared: Box<Expression>,
}

#[derive(Debug, Clone)]
pub struct ReassignmentExpression {
pub lhs: ReassignmentTarget,
Expand Down Expand Up @@ -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),
Expand Down
6 changes: 6 additions & 0 deletions sway-core/src/language/ty/expression/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?)
}
Expand Down Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions sway-core/src/language/ty/expression/expression_variant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ pub enum TyExpressionVariant {
condition: Box<TyExpression>,
body: TyCodeBlock,
},
ForLoop {
desugared: Box<TyExpression>,
},
Break,
Continue,
Reassignment(Box<TyReassignment>),
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -1941,6 +1944,14 @@ impl ty::TyExpression {
Ok(exp)
}

fn type_check_for_loop(
handler: &Handler,
ctx: TypeCheckContext,
desugared: Expression,
) -> Result<Self, ErrorEmitted> {
Self::type_check(handler, ctx, desugared)
}

fn type_check_reassignment(
handler: &Handler,
ctx: TypeCheckContext,
Expand Down
2 changes: 2 additions & 0 deletions sway-core/src/semantic_analysis/cei_pattern_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ fn analyze_expression(
}
res_effs
}
ForLoop { desugared } => analyze_expression(engines, desugared, block_name, warnings),
AsmExpression {
registers, body, ..
} => {
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion sway-core/src/semantic_analysis/coins_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 { .. }
Expand Down
3 changes: 3 additions & 0 deletions sway-core/src/semantic_analysis/node_dependencies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
Loading

0 comments on commit 2f605d5

Please sign in to comment.