Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
61 changes: 61 additions & 0 deletions Notes/MIR-Match_OrPatterns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
Or patterns, Match guards, and if-let

Problem: or-patterns mean that there are multiple inputs and outputs of a guarded match arm
It's possible for or-patterns to overlap, and thus the guard run twice

e.g.
```rust
fn test(a: Option<u32>, b: Option<u32>) {
match (a, b)
{
(Some(v), _) | (_, Some(v)) => if v == 0 {},
_ => {},
}
}
```
In this case, the guard has to run twice, if, `(Some(1), Some(0))` is passed


# Background
Logically, a `match` evaluates as if each arm is tried in sequence (including or patterns).
This would intuitively mean that guards and body code is duplicated for each or-pattern, but
in practice the body can be shared between or-pattern duplications (although the guard needs
to be duplicated in order for the first or to fall through to the second one).

E.g.
```rust
match foo {
Ok(_) if bar() => 1,
Ok(v) => v,
Err(_) => panic(),
}
```
expands as if it's the following
```rust
loop {
let _value = foo;
if let Ok(_) = _value {
if bar() {
break { 1 };
}
}
if let Ok(v) = _value {
if true {
break v;
}
}
if let Err(_) = _value {
if true {
break panic();
}
}
diverge()
}
```

# Scope stacks
To get the right variable state tracking, MIR match lowering needs to follow the logical pattern of the match arms:
- A loop scope wrapping the entire block, early-terminated in each arm.
- split scope with empty arm for each pattern
- Early exited
- same (split with empty arm) for each guard rule
24 changes: 23 additions & 1 deletion samples/test/scoping_rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,31 @@ impl<'a> ::std::ops::Drop for DropFlag<'a>
fn temporaries_in_yielded_expr()
{
let drop_count = ::std::cell::Cell::new(0);
let _foo = ({ DropFlag(&drop_count).0 }, assert_eq!(drop_count.get(), 0) );
// NOTE: This is edition specific! in 2024 this is what happens, but in pre 24 the drop happens the `let`
let _foo = ({ DropFlag(&drop_count).0 }, assert_eq!(drop_count.get(), 1) );
drop(_foo);
assert_eq!(drop_count.get(), 1);
}


#[test]
fn temp_in_if()
{
let drop_count = ::std::cell::Cell::new(0);
if (DropFlag(&drop_count), true).1 {
assert_eq!(drop_count.get(), 1);
}
if let true = (DropFlag(&drop_count), true).1 {
// Temporaries in a `let` get dropped after the body
assert_eq!(drop_count.get(), 1);
}
assert_eq!(drop_count.get(), 2);

if let true = true && (DropFlag(&drop_count), true).1 {
// Expect the non-let to have its temporaries dropped before the body
assert_eq!(drop_count.get(), 3);
}
assert_eq!(drop_count.get(), 3);

{}
}
117 changes: 51 additions & 66 deletions src/ast/dump.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ class RustPrinter:
WRAPIF( n.m_val
, AST::ExprNode_Deref, AST::ExprNode_UniOp
, AST::ExprNode_Cast, AST::ExprNode_BinOp, AST::ExprNode_Assign
, AST::ExprNode_Match, AST::ExprNode_If, AST::ExprNode_IfLet, AST::ExprNode_Match
, AST::ExprNode_Match, AST::ExprNode_If, AST::ExprNode_Match
);
m_os << "." << n.m_method;
m_os << "(";
Expand Down Expand Up @@ -303,30 +303,35 @@ class RustPrinter:
m_os << "'" << n.m_label << ": ";
}

switch(n.m_type)
{
case AST::ExprNode_Loop::LOOP:
m_os << "loop";
break;
case AST::ExprNode_Loop::WHILE:
m_os << "while ";
AST::NodeVisitor::visit(n.m_cond);
break;
case AST::ExprNode_Loop::FOR:
m_os << "for ";
print_pattern(n.m_pattern, true);
m_os << " in ";
AST::NodeVisitor::visit(n.m_cond);
break;
m_os << "loop";

if( expr_root ) {
m_os << "\n";
m_os << indent();
}
else {
m_os << " ";
}

if( expr_root )
{
AST::NodeVisitor::visit(n.m_code);
}
virtual void visit(AST::ExprNode_For& n) override {
bool expr_root = m_expr_root;
m_expr_root = false;

if( n.m_label.name != "" ) {
m_os << "'" << n.m_label << ": ";
}
m_os << "for ";
print_pattern(n.m_pattern, true);
m_os << " in ";
AST::NodeVisitor::visit(n.m_value);

if( expr_root ) {
m_os << "\n";
m_os << indent();
}
else
{
else {
m_os << " ";
}

Expand All @@ -345,7 +350,7 @@ class RustPrinter:
m_os << ")";
}
}
void visit(AST::ExprNode_WhileLet& n) override {
void visit(AST::ExprNode_While& n) override {
bool expr_root = m_expr_root;
m_expr_root = false;

Expand Down Expand Up @@ -419,54 +424,34 @@ class RustPrinter:
virtual void visit(AST::ExprNode_If& n) override {
bool expr_root = m_expr_root;
m_expr_root = false;
m_os << "if ";
AST::NodeVisitor::visit(n.m_cond);

visit_if_common(expr_root, n.m_true, n.m_false);
}
virtual void visit(AST::ExprNode_IfLet& n) override {
bool expr_root = m_expr_root;
m_expr_root = false;
m_os << "if ";
visit_iflet_conditions(n.m_conditions);
for(auto& arm : n.m_arms) {
if( &arm != n.m_arms.data() ) {
if( expr_root ) {
m_os << indent();
}
m_os << "else ";
}

visit_if_common(expr_root, n.m_true, n.m_false);
}
void visit_if_common(bool expr_root, ::AST::ExprNodeP& tv, ::AST::ExprNodeP& fv)
{
if( expr_root )
{
m_os << "\n";
m_os << indent();
}
else
{
m_os << " ";
}
m_os << "if ";
visit_iflet_conditions(arm.m_conditions);

bool is_block = (dynamic_cast<const AST::ExprNode_Block*>(&*tv) != nullptr);
if( !is_block ) m_os << "{ ";
AST::NodeVisitor::visit(tv);
if( !is_block ) m_os << " }";
if(fv.get())
{
if( expr_root )
{
bool is_block = (dynamic_cast<const AST::ExprNode_Block*>(&*arm.m_body) != nullptr);
if( !is_block ) m_os << "{ ";
AST::NodeVisitor::visit(arm.m_body);
if( !is_block ) m_os << " }";
if( expr_root ) {
m_os << "\n";
m_os << indent() << "else";
// handle chained if statements nicely
if( IS(*fv, AST::ExprNode_If) || IS(*fv, AST::ExprNode_IfLet) ) {
m_expr_root = true;
m_os << " ";
}
else
m_os << "\n" << indent();
}
else
{
m_os << " else ";
}
if( n.m_else ) {
if( expr_root ) {
m_os << indent();
}
AST::NodeVisitor::visit(fv);
m_os << "else";
bool is_block = (dynamic_cast<const AST::ExprNode_Block*>(&*n.m_else) != nullptr);
if( !is_block ) m_os << "{ ";
AST::NodeVisitor::visit(n.m_else);
if( !is_block ) m_os << " }";
}
}
virtual void visit(AST::ExprNode_Closure& n) override {
Expand Down Expand Up @@ -666,7 +651,7 @@ class RustPrinter:
WRAPIF( n.m_obj
, AST::ExprNode_Deref, AST::ExprNode_UniOp
, AST::ExprNode_Cast, AST::ExprNode_BinOp, AST::ExprNode_Assign
, AST::ExprNode_Match, AST::ExprNode_If, AST::ExprNode_IfLet, AST::ExprNode_Match
, AST::ExprNode_Match, AST::ExprNode_If, AST::ExprNode_Match
);
m_os << "." << n.m_name;
}
Expand All @@ -675,7 +660,7 @@ class RustPrinter:
WRAPIF( n.m_obj
, AST::ExprNode_Deref, AST::ExprNode_UniOp
, AST::ExprNode_Cast, AST::ExprNode_BinOp, AST::ExprNode_Assign
, AST::ExprNode_Match, AST::ExprNode_If, AST::ExprNode_IfLet, AST::ExprNode_Match
, AST::ExprNode_Match, AST::ExprNode_If, AST::ExprNode_Match
);
m_os << "[";
AST::NodeVisitor::visit(n.m_idx);
Expand Down
Loading