Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Desugar for-loops in the AST #3392

Merged
merged 3 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions gcc/rust/Make-lang.in
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ GRS_OBJS = \
rust/rust-expand-format-args.o \
rust/rust-lang-item.o \
rust/rust-collect-lang-items.o \
rust/rust-desugar-for-loops.o \
$(END)
# removed object files from here

Expand Down
4 changes: 2 additions & 2 deletions gcc/rust/ast/rust-ast-builder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -306,8 +306,8 @@ Builder::return_expr (std::unique_ptr<Expr> &&to_return)
}

std::unique_ptr<Stmt>
Builder::let (std::unique_ptr<Pattern> pattern, std::unique_ptr<Type> type,
std::unique_ptr<Expr> init) const
Builder::let (std::unique_ptr<Pattern> &&pattern, std::unique_ptr<Type> &&type,
std::unique_ptr<Expr> &&init) const
{
return std::unique_ptr<Stmt> (new LetStmt (std::move (pattern),
std::move (init), std::move (type),
Expand Down
6 changes: 3 additions & 3 deletions gcc/rust/ast/rust-ast-builder.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,9 @@ class Builder

/* Create a let binding with an optional type and initializer (`let <name> :
* <type> = <init>`) */
std::unique_ptr<Stmt> let (std::unique_ptr<Pattern> pattern,
std::unique_ptr<Type> type = nullptr,
std::unique_ptr<Expr> init = nullptr) const;
std::unique_ptr<Stmt> let (std::unique_ptr<Pattern> &&pattern,
std::unique_ptr<Type> &&type = nullptr,
std::unique_ptr<Expr> &&init = nullptr) const;

/**
* Create a call expression to a function, struct or enum variant, given its
Expand Down
204 changes: 204 additions & 0 deletions gcc/rust/ast/rust-desugar-for-loops.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
// Copyright (C) 2025 Free Software Foundation, Inc.

// This file is part of GCC.

// GCC is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 3, or (at your option) any later
// version.

// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
// for more details.

// You should have received a copy of the GNU General Public License
// along with GCC; see the file COPYING3. If not see
// <http://www.gnu.org/licenses/>.

#include "rust-desugar-for-loops.h"
#include "rust-ast-visitor.h"
#include "rust-ast.h"
#include "rust-hir-map.h"
#include "rust-path.h"
#include "rust-pattern.h"
#include "rust-stmt.h"
#include "rust-expr.h"
#include "rust-ast-builder.h"

namespace Rust {
namespace AST {

DesugarForLoops::DesugarForLoops () {}

void
DesugarForLoops::go (AST::Crate &crate)
{
DefaultASTVisitor::visit (crate);
}

static void
replace_for_loop (std::unique_ptr<Expr> &for_loop,
CohenArthur marked this conversation as resolved.
Show resolved Hide resolved
std::unique_ptr<Expr> &&expanded)
{
for_loop = std::move (expanded);
}

MatchArm
DesugarForLoops::DesugarCtx::make_match_arm (std::unique_ptr<Pattern> &&path)
{
auto patterns = std::vector<std::unique_ptr<Pattern>> ();
patterns.emplace_back (std::move (path));

return MatchArm (std::move (patterns), loc);
}

MatchCase
DesugarForLoops::DesugarCtx::make_break_arm ()
{
auto arm = make_match_arm (std::unique_ptr<Pattern> (new PathInExpression (
builder.path_in_expression (LangItem::Kind::OPTION_NONE))));

auto break_expr = std::unique_ptr<Expr> (
new BreakExpr (Lifetime::error (), nullptr, {}, loc));

return MatchCase (std::move (arm), std::move (break_expr));
}

MatchCase
DesugarForLoops::DesugarCtx::make_continue_arm ()
{
auto val = builder.identifier_pattern (DesugarCtx::continue_pattern_id);

auto patterns = std::vector<std::unique_ptr<Pattern>> ();
patterns.emplace_back (std::move (val));

auto pattern_item = std::unique_ptr<TupleStructItems> (
new TupleStructItemsNoRange (std::move (patterns)));
auto pattern = std::unique_ptr<Pattern> (new TupleStructPattern (
builder.path_in_expression (LangItem::Kind::OPTION_SOME),
std::move (pattern_item)));

auto val_arm = make_match_arm (std::move (pattern));

auto next = builder.identifier (DesugarCtx::next_value_id);

auto assignment = std::unique_ptr<Expr> (
new AssignmentExpr (std::move (next),
builder.identifier (DesugarCtx::continue_pattern_id),
{}, loc));

return MatchCase (std::move (val_arm), std::move (assignment));
}

std::unique_ptr<Stmt>
DesugarForLoops::DesugarCtx::statementify (std::unique_ptr<Expr> &&expr)
{
return std::unique_ptr<Stmt> (new ExprStmt (std::move (expr), loc, true));
}

std::unique_ptr<Expr>
DesugarForLoops::desugar (AST::ForLoopExpr &expr)
{
auto ctx = DesugarCtx (expr.get_locus ());

auto into_iter = std::make_unique<PathInExpression> (
ctx.builder.path_in_expression (LangItem::Kind::INTOITER_INTOITER));
auto next = std::make_unique<PathInExpression> (
ctx.builder.path_in_expression (LangItem::Kind::ITERATOR_NEXT));

// IntoIterator::into_iter(<head>)
auto into_iter_call
= ctx.builder.call (std::move (into_iter),
expr.get_iterator_expr ().clone_expr ());

// Iterator::next(iter)
auto next_call = ctx.builder.call (
std::move (next),
ctx.builder.ref (ctx.builder.identifier (DesugarCtx::iter_id), true));

// None => break,
auto break_arm = ctx.make_break_arm ();
// Some(val) => __next = val; },
auto continue_arm = ctx.make_continue_arm ();

// match <next_call> {
// <continue_arm>
// <break_arm>
// }
auto match_next
= ctx.builder.match (std::move (next_call),
{std::move (continue_arm), std::move (break_arm)});

// let mut __next;
auto let_next = ctx.builder.let (
ctx.builder.identifier_pattern (DesugarCtx::next_value_id, true));
// let <pattern> = __next;
auto let_pat
= ctx.builder.let (expr.get_pattern ().clone_pattern (), nullptr,
ctx.builder.identifier (DesugarCtx::next_value_id));

auto loop_stmts = std::vector<std::unique_ptr<Stmt>> ();
loop_stmts.emplace_back (std::move (let_next));
loop_stmts.emplace_back (ctx.statementify (std::move (match_next)));
loop_stmts.emplace_back (std::move (let_pat));
loop_stmts.emplace_back (
ctx.statementify (expr.get_loop_block ().clone_expr ()));

// loop {
// <let_next>;
// <match_next>;
// <let_pat>;
//
// <body>;
// }
auto loop = ctx.builder.loop (std::move (loop_stmts));

auto mut_iter_pattern
= ctx.builder.identifier_pattern (DesugarCtx::iter_id, true);
auto match_iter
= ctx.builder.match (std::move (into_iter_call),
{ctx.builder.match_case (std::move (mut_iter_pattern),
std::move (loop))});

auto let_result
= ctx.builder.let (ctx.builder.identifier_pattern (DesugarCtx::result_id),
nullptr, std::move (match_iter));
auto result_return = ctx.builder.identifier (DesugarCtx::result_id);

return ctx.builder.block (std::move (let_result), std::move (result_return));
}

void
DesugarForLoops::maybe_desugar_expr (std::unique_ptr<Expr> &expr)
{
if (expr->get_expr_kind () == AST::Expr::Kind::Loop)
{
auto &loop = static_cast<AST::BaseLoopExpr &> (*expr);

if (loop.get_loop_kind () == AST::BaseLoopExpr::Kind::For)
{
auto &for_loop = static_cast<AST::ForLoopExpr &> (loop);

auto desugared = desugar (for_loop);

replace_for_loop (expr, std::move (desugared));
}
}
}

void
DesugarForLoops::visit (AST::BlockExpr &block)
{
for (auto &stmt : block.get_statements ())
if (stmt->get_stmt_kind () == AST::Stmt::Kind::Expr)
maybe_desugar_expr (static_cast<AST::ExprStmt &> (*stmt).get_expr_ptr ());

if (block.has_tail_expr ())
maybe_desugar_expr (block.get_tail_expr_ptr ());

DefaultASTVisitor::visit (block);
}

} // namespace AST
} // namespace Rust
108 changes: 108 additions & 0 deletions gcc/rust/ast/rust-desugar-for-loops.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright (C) 2025 Free Software Foundation, Inc.

// This file is part of GCC.

// GCC is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 3, or (at your option) any later
// version.

// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
// for more details.

// You should have received a copy of the GNU General Public License
// along with GCC; see the file COPYING3. If not see
// <http://www.gnu.org/licenses/>.

#ifndef RUST_DESUGAR_FOR_LOOPS_H
#define RUST_DESUGAR_FOR_LOOPS_H

#include "rust-ast-builder.h"
#include "rust-ast-visitor.h"
#include "rust-expr.h"

namespace Rust {
namespace AST {

// Desugar for-loops into a set of other AST nodes. The desugar is of the
// following form:
//
// ```
// for <pat> in <head> <body>
// ```
//
// becomes:
//
// ```
// {
// let result = match ::std::iter::IntoIterator::into_iter(<head>) {
// mut iter => {
// loop {
// let mut __next;
// match ::std::iter::Iterator::next(&mut iter) {
// ::std::option::Option::Some(val) => __next = val,
// ::std::option::Option::None => break
// };
// let <pat> = __next;
//
// <body>;
// }
// }
// };
// result
// }
// ```
//
// NOTE: In a perfect world, this would be an immutable visitor which would take
// ownership of the AST node and return a new one, instead of mutating this one
// in place. Nevertheless, this isn't Rust, and doing immutable visitors in C++
// sucks, and the world isn't perfect, so we are impure and sad.
//
// NOTE: This class could eventually be removed in favor of
// an HIR desugar. This would avoid mutating the AST and would be cleaner.
// However, it requires multiple changes in the way we do typechecking and name
// resolution, as this desugar creates new bindings. Because of this, these new
// bindings need to be inserted into the name-resolution context outside of the
// name resolution pass, which is difficult. Those bindings are needed because
// of the way the typechecker is currently structured, where it will fetch name
// resolution information in order to typecheck paths - which technically isn't
// necessary.
class DesugarForLoops : public DefaultASTVisitor
{
using DefaultASTVisitor::visit;

public:
DesugarForLoops ();
void go (AST::Crate &);

private:
struct DesugarCtx
{
DesugarCtx (location_t loc) : builder (Builder (loc)), loc (loc) {}

Builder builder;
location_t loc;

MatchArm make_match_arm (std::unique_ptr<Pattern> &&pattern);
MatchCase make_break_arm ();
MatchCase make_continue_arm ();
std::unique_ptr<Stmt> statementify (std::unique_ptr<Expr> &&expr);

constexpr static const char *continue_pattern_id = "#val";
constexpr static const char *next_value_id = "#__next";
constexpr static const char *iter_id = "#iter";
constexpr static const char *result_id = "#result";
P-E-P marked this conversation as resolved.
Show resolved Hide resolved
};

std::unique_ptr<Expr> desugar (AST::ForLoopExpr &expr);
void maybe_desugar_expr (std::unique_ptr<Expr> &expr);

void visit (AST::BlockExpr &) override;
};

} // namespace AST
} // namespace Rust

#endif // ! RUST_DESUGAR_FOR_LOOPS_H
2 changes: 1 addition & 1 deletion gcc/rust/hir/rust-ast-lower-expr.cc
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ ASTLoweringExpr::visit (AST::WhileLoopExpr &expr)
void
ASTLoweringExpr::visit (AST::ForLoopExpr &expr)
{
translated = ASTLoweringExprWithBlock::translate (expr, &terminated);
rust_unreachable ();
}

void
Expand Down
4 changes: 4 additions & 0 deletions gcc/rust/rust-session-manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#include "rust-session-manager.h"
#include "rust-collect-lang-items.h"
#include "rust-desugar-for-loops.h"
#include "rust-diagnostics.h"
#include "rust-hir-pattern-analysis.h"
#include "rust-immutable-name-resolution-context.h"
Expand Down Expand Up @@ -614,6 +615,9 @@ Session::compile_crate (const char *filename)
// expansion pipeline stage

expansion (parsed_crate, name_resolution_ctx);

AST::DesugarForLoops ().go (parsed_crate);

rust_debug ("\033[0;31mSUCCESSFULLY FINISHED EXPANSION \033[0m");
if (options.dump_option_enabled (CompileOptions::EXPANSION_DUMP))
{
Expand Down
Loading