Repository: jinxdash/prettier-plugin-rust (language: rust)

Issue:
The issue of semicolons affecting the lifecycle
### Input code

```rust
if let Some(left) = node.borrow().left.as_ref() {
                    deque.push_back(left.clone());
                };
                if let Some(right) = node.borrow().right.as_ref() {
                    deque.push_back(right.clone());
                };
```


### Output code

```rust
if let Some(left) = node.borrow().left.as_ref() {
                    deque.push_back(left.clone());
                }
                if let Some(right) = node.borrow().right.as_ref() {
                    deque.push_back(right.clone());
                }
```


### Additional context

![image](https://github.com/jinxdash/prettier-plugin-rust/assets/45996643/bc3e76ec-5ea0-48ae-84d7-3bb8cd9f660b)
![image](https://github.com/jinxdash/prettier-plugin-rust/assets/45996643/c13e85d0-1783-4224-8112-ec803a255d11)



Discussion / hints:
![image](https://github.com/jinxdash/prettier-plugin-rust/assets/45996643/22fa4b61-d02f-4e8f-a511-528ab3feec68)

Related: https://github.com/rust-lang/rust/issues/70844

Per Rust spec those IfLet blocks are statements, hence semicolons should be meaningless and unnecessary. 

My understanding is that this is a longstanding bug in the rust compiler whereby "if let" is syntax sugar that gets unrolled into some code that itself is missing a semicolon.

I'm inclined to say that the formatting is correct and that the issue is upstream. Maybe someone can correct me on that, otherwise I'd suggest adding `// prettier-ignore` above the `if let` blocks
![image](https://github.com/jinxdash/prettier-plugin-rust/assets/45996643/3cffcb75-63e7-42cf-b6e1-22ff4e8aa0a3)
![image](https://github.com/jinxdash/prettier-plugin-rust/assets/45996643/2d976818-5c43-4ddd-be19-1f1482477313)
I noticed that the last expression needed a semicolon

When the code is formatted, it doesn't compile properly. This may be unreasonable.


Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9b90c96..8c33d3a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,10 +2,10 @@
 
 ## Unreleased
 
-- feat: custom format for `cfg_if` macro
-- feat: '@' character handling in macro rules match
-- fix: format number trailing dot to float
-- fix: format number no-op exponent to float
+- feat: format `cfg_if!` macros
+- feat: format `@` character in `macro_rules`
+- fix: format `<number>.` and `<number>e0` to float instead of int ([#14](https://github.com/jinxdash/prettier-plugin-rust/issues/14), [#16](https://github.com/jinxdash/prettier-plugin-rust/issues/16))
+- fix: avoid removing optional semi in rare [rust compiler bug](https://github.com/rust-lang/rust/issues/70844) ([#22](https://github.com/jinxdash/prettier-plugin-rust/issues/22))
 
 ## 0.1.8
 
diff --git a/src/format/styling.ts b/src/format/styling.ts
index c3cd87a..9394df9 100644
--- a/src/format/styling.ts
+++ b/src/format/styling.ts
@@ -39,7 +39,6 @@ import {
 	is_Attribute,
 	is_AttributeOrDocComment,
 	is_AwaitExpression,
-	is_bitshiftOperator,
 	is_BitwiseOperator,
 	is_CallExpression,
 	is_ClosureFunctionExpression,
@@ -65,9 +64,9 @@ import {
 	is_LiteralNumberLike,
 	is_LogicalExpression,
 	is_LoopBlockExpression,
+	is_MatchExpression,
 	is_MatchExpressionCase,
 	is_MemberExpression,
-	is_multiplicativeOperator,
 	is_NodeWithBodyNoBody,
 	is_NodeWithMaybePatternNoUnionBody,
 	is_OperationExpression,
@@ -90,6 +89,8 @@ import {
 	is_UnionPattern,
 	is_WhileBlockExpression,
 	is_YieldExpression,
+	is_bitshiftOperator,
+	is_multiplicativeOperator,
 } from "jinx-rust/utils";
 import { exit, last_of } from "../utils/common";
 import { CF, hasBreaklineAfter, hasComment } from "./comments";
@@ -384,14 +385,32 @@ const NoNode = { nodeType: 0 } as MissingNode;
 
 export function needsSemi(parent: NodeWithBody, stmt: ExpressionStatement, disregardExprType = false) {
 	const expr = disregardExprType ? NoNode : stmt.expression!;
+	const hadSemi = !disregardExprType && stmt.semi;
+
 	return (
 		!!expr &&
-		(shouldNeverSemi()
-			? false //
+		(forcePreserveSemi()
+			? true
+			: shouldNeverSemi()
+			? false
 			: shouldPreserveSemi()
-			? (!disregardExprType && stmt.semi) || shouldAlwaysSemi() || canAutoCompleteSemi()
+			? hadSemi || shouldAlwaysSemi() || canAutoCompleteSemi()
 			: true)
 	);
+
+	function forcePreserveSemi() {
+		/** Rust Compiler bug (preserve optional semicolon) */
+		// rust-lang/rust#70844 https://github.com/rust-lang/rust/issues/70844
+		// issue#22 https://github.com/jinxdash/prettier-plugin-rust/issues/22
+		return (
+			hadSemi &&
+			stmt === last_of(parent.body!) &&
+			((is_IfBlockExpression(expr) &&
+				hasLetScrutineeCondition(expr) &&
+				!(is_LetScrutinee(expr.condition) && is_Identifier(expr.condition.expression))) ||
+				(is_MatchExpression(expr) && !is_Identifier(expr.expression)))
+		);
+	}
 	function shouldNeverSemi() {
 		return is_ExpressionWithBodyOrCases_or_BlockLikeMacroInvocation(expr);
 	}


Test patch:
diff --git a/tests/output-ext/expressions/flow_expr.f.rs b/tests/output-ext/expressions/flow_expr.f.rs
index 1301343..9fa909c 100644
--- a/tests/output-ext/expressions/flow_expr.f.rs
+++ b/tests/output-ext/expressions/flow_expr.f.rs
@@ -131,7 +131,7 @@ pub fn main() {
           _ => {
             return;
           }
-        }
+        };
       } else if (
         {
           return;
diff --git a/tests/output/issues/22.f.rs b/tests/output/issues/22.f.rs
new file mode 100644
index 0000000..b429fcf
--- /dev/null
+++ b/tests/output/issues/22.f.rs
@@ -0,0 +1,73 @@
+fn preserve_last_semicolon() {
+  if let Some(left) = node.borrow().left.as_ref() {
+    deque.push_back(left.clone());
+  }
+  if let Some(right) = node.borrow().right.as_ref() {
+    deque.push_back(right.clone());
+  };
+}
+
+fn a() {
+  if let Ok(_) = lock.try_lock() {
+  };
+}
+
+fn b() {
+  let lock = std::sync::Mutex::new(10);
+  match lock.try_lock() {
+    Ok(_) => {}
+    Err(_) => {}
+  }
+  match lock.try_lock() {
+    Ok(_) => {}
+    Err(_) => {}
+  };
+}
+
+fn c() {
+  if let Ok(_) = lock.try_lock() {
+  };
+  // comment
+}
+
+fn d() {
+  if let Ok(_) = lock.try_lock() {
+  }; // comment
+}
+
+fn e() {
+  if let Ok(_) = lock.try_lock() {
+  } /** comment */;
+}
+
+fn f() {
+  if let Ok(_) = lock.try_lock() {
+  };
+}
+
+fn g() {
+  if let Ok(_) = lock.try_lock() {
+  };
+  // comment
+}
+
+fn h() {
+  if let Ok(_) = lock {
+  }
+  if let Ok(_) = lock {
+  }
+}
+
+fn i() {
+  match lock {
+  }
+  match lock {
+  }
+}
+
+fn inner_attr() {
+  if let Ok(_) = lock.try_lock() {
+  }
+  #![attr]
+}
+// source: "../../samples/issues/22.rs"
\ No newline at end of file
diff --git a/tests/samples/issues/22.rs b/tests/samples/issues/22.rs
new file mode 100644
index 0000000..ce2cf46
--- /dev/null
+++ b/tests/samples/issues/22.rs
@@ -0,0 +1,67 @@
+fn preserve_last_semicolon() {
+	if let Some(left) = node.borrow().left.as_ref() {
+		deque.push_back(left.clone());
+	};
+	if let Some(right) = node.borrow().right.as_ref() {
+		deque.push_back(right.clone());
+	};
+}
+
+fn a() {
+    if let Ok(_) = lock.try_lock() {};
+}
+
+fn b() {
+    let lock = std::sync::Mutex::new(10);
+    match lock.try_lock() {
+		Ok(_) => {}
+		Err(_) => {}
+	};
+    match lock.try_lock() {
+		Ok(_) => {}
+		Err(_) => {}
+	};
+}
+
+fn c() {
+    if let Ok(_) = lock.try_lock() {};
+	// comment
+}
+
+fn d() {
+    if let Ok(_) = lock.try_lock() {}; // comment
+}
+
+fn e() {
+    if let Ok(_) = lock.try_lock() {}/** comment */;
+}
+
+fn f() {
+    if let Ok(_) = lock.try_lock() {
+
+	}
+	;
+}
+
+fn g() {
+    if let Ok(_) = lock.try_lock() {
+		
+	}
+	// comment
+	;
+}
+
+fn h() {
+	if let Ok(_) = lock {};
+	if let Ok(_) = lock {};
+}
+
+fn i() {
+	match lock {};
+	match lock {};
+}
+
+fn inner_attr() {
+    if let Ok(_) = lock.try_lock() {};
+	#![attr]
+}
\ No newline at end of file