Skip to content

Commit

Permalink
Convert while loops to be an expression instead of a statement (FuelL…
Browse files Browse the repository at this point in the history
…abs#2517)

* Convert while loops to be handled as an expression instead of a statement.

This commit changes while loops to be handled as an expression instead
of as a statement.

This means while loops now return the unit type () as their return type.

And means the following code now compiles:

```rust
fn main() {
    let result = while true { break; };
}
```

Which is the same behavior and also allowed in Rust.

Closes FuelLabs#2455.
  • Loading branch information
tritao authored Aug 13, 2022
1 parent 91a2b43 commit d038ffc
Show file tree
Hide file tree
Showing 20 changed files with 205 additions and 208 deletions.
5 changes: 4 additions & 1 deletion sway-core/src/control_flow_analysis/analyze_return_paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,10 @@ fn connect_node(
}
Ok((NodeConnection::Return(this_index), vec![]))
}
TypedAstNodeContent::WhileLoop(TypedWhileLoop { body, .. }) => {
TypedAstNodeContent::Expression(TypedExpression {
expression: TypedExpressionVariant::WhileLoop { body, .. },
..
}) => {
// This is very similar to the dead code analysis for a while loop.
let entry = graph.add_node(node.into());
let while_loop_exit = graph.add_node("while loop exit".to_string().into());
Expand Down
67 changes: 32 additions & 35 deletions sway-core/src/control_flow_analysis/dead_code_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
TypedEnumDeclaration, TypedExpression, TypedExpressionVariant,
TypedFunctionDeclaration, TypedReassignment, TypedReturnStatement,
TypedStructDeclaration, TypedStructExpressionField, TypedTraitDeclaration,
TypedVariableDeclaration, TypedWhileLoop, VariableMutability,
TypedVariableDeclaration, VariableMutability,
},
TypeCheckedStorageReassignment, TypedAsmRegisterDeclaration, TypedAstNode,
TypedAstNodeContent, TypedImplTrait, TypedIntrinsicFunctionKind, TypedStorageDeclaration,
Expand Down Expand Up @@ -262,38 +262,6 @@ fn connect_node(
}
(return_contents, None)
}
TypedAstNodeContent::WhileLoop(TypedWhileLoop { body, .. }) => {
// a while loop can loop back to the beginning,
// or it can terminate.
// so we connect the _end_ of the while loop _both_ to its beginning and the next node.
// the loop could also be entirely skipped

let entry = graph.add_node(node.into());
let while_loop_exit = graph.add_node("while loop exit".to_string().into());
for leaf in leaves {
graph.add_edge(*leaf, entry, "".into());
}
// it is possible for a whole while loop to be skipped so add edge from
// beginning of while loop straight to exit
graph.add_edge(
entry,
while_loop_exit,
"condition is initially false".into(),
);
let mut leaves = vec![entry];
let (l_leaves, _l_exit_node) =
depth_first_insertion_code_block(body, graph, &leaves, exit_node, tree_type)?;
// insert edges from end of block back to beginning of it
for leaf in &l_leaves {
graph.add_edge(*leaf, entry, "loop repeats".into());
}

leaves = l_leaves;
for leaf in leaves {
graph.add_edge(leaf, while_loop_exit, "".into());
}
(vec![while_loop_exit], exit_node)
}
TypedAstNodeContent::Expression(TypedExpression {
expression: expr_variant,
span,
Expand Down Expand Up @@ -1103,6 +1071,36 @@ fn connect_expression(
tree_type,
exp.span.clone(),
),
WhileLoop { body, .. } => {
// a while loop can loop back to the beginning,
// or it can terminate.
// so we connect the _end_ of the while loop _both_ to its beginning and the next node.
// the loop could also be entirely skipped

let entry = leaves[0];
let while_loop_exit = graph.add_node("while loop exit".to_string().into());

// it is possible for a whole while loop to be skipped so add edge from
// beginning of while loop straight to exit
graph.add_edge(
entry,
while_loop_exit,
"condition is initially false".into(),
);
let mut leaves = vec![entry];
let (l_leaves, _l_exit_node) =
depth_first_insertion_code_block(body, graph, &leaves, exit_node, tree_type)?;
// insert edges from end of block back to beginning of it
for leaf in &l_leaves {
graph.add_edge(*leaf, entry, "loop repeats".into());
}

leaves = l_leaves;
for leaf in leaves {
graph.add_edge(leaf, while_loop_exit, "".into());
}
Ok(vec![while_loop_exit])
}
}
}

Expand Down Expand Up @@ -1285,8 +1283,7 @@ fn construct_dead_code_warning_from_node(node: &TypedAstNode) -> Option<CompileW
TypedAstNodeContent::ReturnStatement(_)
| TypedAstNodeContent::ImplicitReturnExpression(_)
| TypedAstNodeContent::Expression(_)
| TypedAstNodeContent::SideEffect
| TypedAstNodeContent::WhileLoop(_),
| TypedAstNodeContent::SideEffect,
} => CompileWarning {
span: span.clone(),
warning_content: Warning::UnreachableCode,
Expand Down
34 changes: 14 additions & 20 deletions sway-core/src/convert_parse_tree.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::collections::HashSet;

use crate::type_system::{TraitConstraint, TypeArgument, TypeBinding, TypeParameter};
use crate::{
type_system::{TraitConstraint, TypeArgument, TypeBinding, TypeParameter},
WhileLoopExpression,
};

use {
crate::{
Expand All @@ -20,7 +23,7 @@ use {
StorageDeclaration, StorageField, StructDeclaration, StructExpression,
StructExpressionField, StructField, StructScrutineeField, SubfieldExpression, Supertrait,
TraitDeclaration, TraitFn, TreeType, TupleIndexExpression, TypeInfo, UseStatement,
VariableDeclaration, Visibility, WhileLoop,
VariableDeclaration, Visibility,
},
std::{
collections::HashMap,
Expand Down Expand Up @@ -100,8 +103,6 @@ pub enum ConvertParseTreeError {
PubUseNotSupported { span: Span },
#[error("return expressions are not allowed outside of blocks")]
ReturnOutsideOfBlock { span: Span },
#[error("while expressions are not allowed outside of blocks")]
WhileOutsideOfBlock { span: Span },
#[error("functions used in applications may not be arbitrary expressions")]
FunctionArbitraryExpression { span: Span },
#[error("generics are not supported here")]
Expand Down Expand Up @@ -193,7 +194,6 @@ impl Spanned for ConvertParseTreeError {
match self {
ConvertParseTreeError::PubUseNotSupported { span } => span.clone(),
ConvertParseTreeError::ReturnOutsideOfBlock { span } => span.clone(),
ConvertParseTreeError::WhileOutsideOfBlock { span } => span.clone(),
ConvertParseTreeError::FunctionArbitraryExpression { span } => span.clone(),
ConvertParseTreeError::GenericsNotSupportedHere { span } => span.clone(),
ConvertParseTreeError::FullyQualifiedPathsNotSupportedHere { span } => span.clone(),
Expand Down Expand Up @@ -1135,15 +1135,6 @@ fn expr_to_ast_node(
span,
}
}
Expr::While {
condition, block, ..
} => AstNode {
content: AstNodeContent::WhileLoop(WhileLoop {
condition: expr_to_expression(ec, *condition)?,
body: braced_code_block_contents_to_code_block(ec, block)?,
}),
span,
},
Expr::Reassignment {
assignable,
expr,
Expand Down Expand Up @@ -1623,12 +1614,15 @@ fn expr_to_expression(ec: &mut ErrorContext, expr: Expr) -> Result<Expression, E
span,
}
}
Expr::While { while_token, .. } => {
let error = ConvertParseTreeError::WhileOutsideOfBlock {
span: while_token.span(),
};
return Err(ec.error(error));
}
Expr::While {
condition, block, ..
} => Expression {
kind: ExpressionKind::WhileLoop(WhileLoopExpression {
condition: Box::new(expr_to_expression(ec, *condition)?),
body: braced_code_block_contents_to_code_block(ec, block)?,
}),
span,
},
Expr::FuncApp { func, args } => {
let kind = expr_func_app_to_expression_kind(ec, func, args)?;
Expression { kind, span }
Expand Down
5 changes: 3 additions & 2 deletions sway-core/src/ir_generation/const_eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,8 @@ fn const_eval_typed_expr(
| TypedExpressionVariant::StorageAccess(_)
| TypedExpressionVariant::AbiName(_)
| TypedExpressionVariant::EnumTag { .. }
| TypedExpressionVariant::UnsafeDowncast { .. } => None,
| TypedExpressionVariant::UnsafeDowncast { .. }
| TypedExpressionVariant::WhileLoop { .. } => None,
}
}

Expand All @@ -358,6 +359,6 @@ fn const_eval_typed_ast_node(
TypedAstNodeContent::Expression(e) | TypedAstNodeContent::ImplicitReturnExpression(e) => {
const_eval_typed_expr(lookup, known_consts, e)
}
TypedAstNodeContent::WhileLoop(_) | TypedAstNodeContent::SideEffect => None,
TypedAstNodeContent::SideEffect => None,
}
}
13 changes: 7 additions & 6 deletions sway-core/src/ir_generation/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,9 +220,6 @@ impl FnCompiler {
TypedAstNodeContent::ImplicitReturnExpression(te) => {
self.compile_expression(context, md_mgr, te)
}
TypedAstNodeContent::WhileLoop(twl) => {
self.compile_while_loop(context, md_mgr, twl, span_md_idx)
}
// a side effect can be () because it just impacts the type system/namespacing.
// There should be no new IR generated.
TypedAstNodeContent::SideEffect => Ok(Constant::get_unit(context)),
Expand Down Expand Up @@ -374,6 +371,9 @@ impl FnCompiler {
self.compile_unsafe_downcast(context, md_mgr, exp, variant)
}
TypedExpressionVariant::EnumTag { exp } => self.compile_enum_tag(context, md_mgr, exp),
TypedExpressionVariant::WhileLoop { body, condition } => {
self.compile_while_loop(context, md_mgr, body, *condition, span_md_idx)
}
}
}

Expand Down Expand Up @@ -970,7 +970,8 @@ impl FnCompiler {
&mut self,
context: &mut Context,
md_mgr: &mut MetadataManager,
ast_while_loop: TypedWhileLoop,
body: TypedCodeBlock,
condition: TypedExpression,
span_md_idx: Option<MetadataIndex>,
) -> Result<Value, CompileError> {
// We're dancing around a bit here to make the blocks sit in the right order. Ideally we
Expand Down Expand Up @@ -1007,7 +1008,7 @@ impl FnCompiler {
// Compile the body and a branch to the condition block if no branch is already present in
// the body block
self.current_block = body_block;
self.compile_code_block(context, md_mgr, ast_while_loop.body)?;
self.compile_code_block(context, md_mgr, body)?;
if !self.current_block.is_terminated(context) {
self.current_block.ins(context).branch(cond_block, None);
}
Expand All @@ -1018,7 +1019,7 @@ impl FnCompiler {

// Add the conditional which jumps into the body or out to the final block.
self.current_block = cond_block;
let cond_value = self.compile_expression(context, md_mgr, ast_while_loop.condition)?;
let cond_value = self.compile_expression(context, md_mgr, condition)?;
if !self.current_block.is_terminated(context) {
self.current_block.ins(context).conditional_branch(
cond_value,
Expand Down
2 changes: 1 addition & 1 deletion sway-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub use semantic_analysis::{
};
pub mod types;
pub use crate::parse_tree::{
Declaration, Expression, ParseModule, ParseProgram, TreeType, UseStatement, WhileLoop, *,
Declaration, Expression, ParseModule, ParseProgram, TreeType, UseStatement, *,
};

pub use error::{CompileError, CompileResult, CompileWarning};
Expand Down
6 changes: 1 addition & 5 deletions sway-core/src/parse_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ mod program;
mod return_statement;
mod use_statement;
mod visibility;
mod while_loop;

pub use call_path::*;
pub use code_block::*;
Expand All @@ -24,7 +23,6 @@ pub use return_statement::*;
use sway_types::span::Span;
pub use use_statement::{ImportType, UseStatement};
pub use visibility::Visibility;
pub use while_loop::WhileLoop;

/// Represents some exportable information that results from compiling some
/// Sway source code.
Expand All @@ -48,6 +46,7 @@ pub struct AstNode {
}

/// Represents the various structures that constitute a Sway program.
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone)]
pub enum AstNodeContent {
/// A statement of the form `use foo::bar;` or `use ::foo::bar;`
Expand All @@ -65,9 +64,6 @@ pub enum AstNodeContent {
/// An implicit return expression is an [Expression] at the end of a code block which has no
/// semicolon, denoting that it is the [Expression] to be returned from that block.
ImplicitReturnExpression(Expression),
/// A control flow element which loops continually until some boolean expression evaluates as
/// `false`.
WhileLoop(WhileLoop),
/// A statement of the form `dep foo::bar;` which imports/includes another source file.
IncludeStatement(IncludeStatement),
}
9 changes: 9 additions & 0 deletions sway-core/src/parse_tree/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@ pub struct IntrinsicFunctionExpression {
pub arguments: Vec<Expression>,
}

#[derive(Debug, Clone)]
pub struct WhileLoopExpression {
pub condition: Box<Expression>,
pub body: CodeBlock,
}

#[derive(Debug, Clone)]
pub enum ExpressionKind {
Literal(Literal),
Expand Down Expand Up @@ -152,6 +158,9 @@ pub enum ExpressionKind {
ArrayIndex(ArrayIndexExpression),
StorageAccess(StorageAccessExpression),
IntrinsicFunction(IntrinsicFunctionExpression),
/// A control flow element which loops continually until some boolean expression evaluates as
/// `false`.
WhileLoop(WhileLoopExpression),
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
Expand Down
8 changes: 0 additions & 8 deletions sway-core/src/parse_tree/while_loop.rs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
error::{err, ok},
semantic_analysis::{
Mode, TypeCheckContext, TypedAstNodeContent, TypedExpression, TypedExpressionVariant,
TypedIntrinsicFunctionKind, TypedReturnStatement, TypedWhileLoop,
TypedIntrinsicFunctionKind, TypedReturnStatement,
},
type_system::{
insert_type, look_up_type_id, resolve_type, set_type_as_storage_only, unify_with_self,
Expand Down Expand Up @@ -193,10 +193,6 @@ impl TypedImplTrait {
expr_contains_get_storage_index(expr)
}
TypedAstNodeContent::Declaration(decl) => decl_contains_get_storage_index(decl),
TypedAstNodeContent::WhileLoop(TypedWhileLoop { condition, body }) => {
expr_contains_get_storage_index(condition)
|| codeblock_contains_get_storage_index(body)
}
TypedAstNodeContent::SideEffect => false,
}
}
Expand Down Expand Up @@ -257,6 +253,10 @@ impl TypedImplTrait {
kind,
..
}) => matches!(kind, sway_ast::intrinsics::Intrinsic::GetStorageKey),
TypedExpressionVariant::WhileLoop { condition, body } => {
expr_contains_get_storage_index(condition)
|| codeblock_contains_get_storage_index(body)
}
}
}
fn decl_contains_get_storage_index(decl: &TypedDeclaration) -> bool {
Expand Down
Loading

0 comments on commit d038ffc

Please sign in to comment.