Change:
If you consider the following program: It will print "2,3,4,1," four times. The issue occurs on recent nightly as well as all the way back to 1.0.0. This is a bit weird IMO, and makes && and chains non-associative. IMO it's a bug that should be fixed to print "1,2,3,4,". IDK how much we can do about this at the current point, given backwards compatibility concerns. It might not stop programs from compiling, but programs might differ in their runtime output. There is with the name "Stabilize drop order" but if you read the text, it only concerns the drop order of structs, vecs, tuples, etc. Also, after the merge, the RFC author themself made a PR to add regression tests in , and mentioned that that PR was enough for getting the RFC tested. The RFC didn't include anything about && chains. I think the language should not be interpreted too generally. It took a long time until in which we we got some further drop order tests for "established" language constructs, while there have been many PRs with drop order tests for let-else. Originally I've discovered this in: , but given that it's so visible, I'm sure that someone else has already complained about this. Maybe one can just implement the change to "1,2,3,4," and make a crater run for it? cc label T-lang

Discussion:
// scopes, meaning that temporaries cannot outlive them. // This ensures fixed size stacks. hir::ExprKind::Binary( <del> source_map::Spanned { node: hir::BinOpKind::And, .. }, _, ref r, ) | hir::ExprKind::Binary( source_map::Spanned { node: hir::BinOpKind::Or, .. }, _, </del> <ins> source_map::Spanned { node: hir::BinOpKind::And | hir::BinOpKind::Or, .. }, ref l, </ins> ref r, ) => { <del> // For shortcircuiting operators, mark the RHS as a terminating // scope since it only executes conditionally. </del> <ins> // expr is a short circuiting operator (|| or &&). As its // functionality can't be overridden by traits, it always // processes bool sub-expressions. bools are Copy and thus we // can drop any temporaries in evaluation (read) order // (with the exception of potentially failing let expressions). // We achieve this by enclosing the operands in a terminating // scope, both the LHS and the RHS. // We optimize this a little in the presence of chains. // Chains like a && b && c get lowered to AND(AND(a, b), c). // In here, b and c are RHS, while a is the only LHS operand in // that chain. This holds true for longer chains as well: the // leading operand is always the only LHS operand that is not a // binop itself. Putting a binop like AND(a, b) into a // terminating scope is not useful, thus we only put the LHS // into a terminating scope if it is not a binop. let terminate_lhs = match l.kind { // let expressions can create temporaries that live on hir::ExprKind::Let(_) => false, // binops already drop their temporaries, so there is no // need to put them into a terminating scope. // This is purely an optimization to reduce the number of // terminating scopes. hir::ExprKind::Binary( source_map::Spanned { node: hir::BinOpKind::And | hir::BinOpKind::Or, .. }, .., ) => false, // otherwise: mark it as terminating _ => true, }; if terminate_lhs { terminating(l.hir_id.local_id); } </ins> // `Let` expressions (in a let-chain) shouldn't be terminating, as their temporaries // should live beyond the immediate expression

} if { <del> if self.option_loud_drop(7).is_some() && self.option_loud_drop(6).is_some() { </del> <ins> if self.option_loud_drop(6).is_some() && self.option_loud_drop(7).is_some() { </ins> self.loud_drop(8); true } else {

} } <ins> fn and_chain(&self) { // issue-103107 if self.option_loud_drop(1).is_some() // 1 && self.option_loud_drop(2).is_some() // 2 && self.option_loud_drop(3).is_some() // 3 && self.option_loud_drop(4).is_some() // 4 && self.option_loud_drop(5).is_some() // 5 { self.print(6); // 6 } let _ = self.option_loud_drop(7).is_some() // 1 && self.option_loud_drop(8).is_some() // 2 && self.option_loud_drop(9).is_some(); // 3 self.print(10); // 4 // Test associativity if self.option_loud_drop(11).is_some() // 1 && (self.option_loud_drop(12).is_some() // 2 && self.option_loud_drop(13).is_some() // 3 && self.option_loud_drop(14).is_some()) // 4 && self.option_loud_drop(15).is_some() // 5 { self.print(16); // 6 } } fn or_chain(&self) { // issue-103107 if self.option_loud_drop(1).is_none() // 1 || self.option_loud_drop(2).is_none() // 2 || self.option_loud_drop(3).is_none() // 3 || self.option_loud_drop(4).is_none() // 4 || self.option_loud_drop(5).is_some() // 5 { self.print(6); // 6 } let _ = self.option_loud_drop(7).is_none() // 1 || self.option_loud_drop(8).is_none() // 2 || self.option_loud_drop(9).is_none(); // 3 self.print(10); // 4 // Test associativity if self.option_loud_drop(11).is_none() // 1 || (self.option_loud_drop(12).is_none() // 2 || self.option_loud_drop(13).is_none() // 3 || self.option_loud_drop(14).is_none()) // 4 || self.option_loud_drop(15).is_some() // 5 { self.print(16); // 6 } } fn mixed_and_or_chain(&self) { // issue-103107 if self.option_loud_drop(1).is_none() // 1 || self.option_loud_drop(2).is_none() // 2 || self.option_loud_drop(3).is_some() // 3 && self.option_loud_drop(4).is_some() // 4 && self.option_loud_drop(5).is_none() // 5 || self.option_loud_drop(6).is_none() // 6 || self.option_loud_drop(7).is_some() // 7 { self.print(8); // 8 } } </ins> fn let_chain(&self) { // take the "then" branch <del> if self.option_loud_drop(2).is_some() // 2 && self.option_loud_drop(1).is_some() // 1 </del> <ins> if self.option_loud_drop(1).is_some() // 1 && self.option_loud_drop(2).is_some() // 2 </ins> && let Some(_d) = self.option_loud_drop(4) { // 4 self.print(3); // 3 } // take the "else" branch <del> if self.option_loud_drop(6).is_some() // 2 && self.option_loud_drop(5).is_some() // 1 </del> <ins> if self.option_loud_drop(5).is_some() // 1 && self.option_loud_drop(6).is_some() // 2 </ins> && let None = self.option_loud_drop(8) { // 4 unreachable!(); } else {

} // let exprs last <del> if self.option_loud_drop(20).is_some() // 2 && self.option_loud_drop(19).is_some() // 1 </del> <ins> if self.option_loud_drop(19).is_some() // 1 && self.option_loud_drop(20).is_some() // 2 </ins> && let Some(_d) = self.option_loud_drop(23) // 5 && let Some(_e) = self.option_loud_drop(22) { // 4 self.print(21); // 3

collector.if_(); collector.assert_sorted(); <ins> println!("-- and chain --"); let collector = DropOrderCollector::default(); collector.and_chain(); collector.assert_sorted(); println!("-- or chain --"); let collector = DropOrderCollector::default(); collector.or_chain(); collector.assert_sorted(); println!("-- mixed and/or chain --"); let collector = DropOrderCollector::default(); collector.mixed_and_or_chain(); collector.assert_sorted(); </ins> println!("-- if let --"); let collector = DropOrderCollector::default(); collector.if_let();

<ins> // check-pass // compile-flags: -Z validate-mir struct Foo<'a>(&'a mut u32); impl<'a> Drop for Foo<'a> { fn drop(&mut self) { *self.0 = 0; } } fn and() { let mut foo = 0; // This used to compile also before the fix if true && *Foo(&mut foo).0 == 0 && ({ foo = 0; true}) {} // This used to fail before the fix if *Foo(&mut foo).0 == 0 && ({ foo = 0; true}) {} println!("{foo}"); } fn or() { let mut foo = 0; // This used to compile also before the fix if false || *Foo(&mut foo).0 == 1 || ({ foo = 0; true}) {} // This used to fail before the fix if *Foo(&mut foo).0 == 1 || ({ foo = 0; true}) {} println!("{foo}"); } fn main() { and(); or(); } </ins>