Repository: gleam-lang/gleam (rust)

Issue:
Unbound variable getting passed the compiler

```gleam
import gleam/list
import gleam/string

pub fn main() {
  let assert Ok(thing) =
    ["", ""] |> list.find(fn(item) { string.length(item) == 1 })
    as { "you should see this " <> thing }
}
```
the compiler should tell that `thing` is unbound in the `as` block, but it doesn't

Pull request:
Fix bug with scoping in let assert
Fixes #4195 
This PR fixes the scoping issue by analysing the panic message before bringing the variable binding into scope.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c6d79d612..b25bcdcd2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -278,3 +278,7 @@
 - Fixed a bug where the "Extract variable" code action would erroneously extract
   a pipeline step as a variable.
   ([Giacomo Cavalieri](https://github.com/giacomocavalieri))
+
+- Fixed a bug where variables bound in `let assert` assignments would be allowed
+  to be used in the custom panic message.
+  ([Surya Rose](https://github.com/GearsDatapacks))
diff --git a/compiler-core/src/type_/expression.rs b/compiler-core/src/type_/expression.rs
index 8df2ca3a2..d651572f6 100644
--- a/compiler-core/src/type_/expression.rs
+++ b/compiler-core/src/type_/expression.rs
@@ -1325,6 +1325,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
 
         let value_typ = value.type_();
 
+        let kind = self.infer_assignment_kind(kind.clone());
+
         // Ensure the pattern matches the type of the value
         let pattern_location = pattern.location();
         let mut pattern_typer = pattern::PatternTyper::new(
@@ -1381,14 +1383,6 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
             (AssignmentKind::Assert { .. }, _) => {}
         }
 
-        let kind = match self.infer_assignment_kind(kind) {
-            Ok(kind) => kind,
-            Err(error) => {
-                self.problems.error(error);
-                AssignmentKind::Generated
-            }
-        };
-
         Assignment {
             location,
             annotation,
@@ -1401,10 +1395,10 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
     fn infer_assignment_kind(
         &mut self,
         kind: AssignmentKind<UntypedExpr>,
-    ) -> Result<AssignmentKind<TypedExpr>, Error> {
+    ) -> AssignmentKind<TypedExpr> {
         match kind {
-            AssignmentKind::Let => Ok(AssignmentKind::Let),
-            AssignmentKind::Generated => Ok(AssignmentKind::Generated),
+            AssignmentKind::Let => AssignmentKind::Let,
+            AssignmentKind::Generated => AssignmentKind::Generated,
             AssignmentKind::Assert { location, message } => {
                 let message = match message {
                     Some(message) => {
@@ -1412,15 +1406,20 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
                             FeatureKind::LetAssertWithMessage,
                             message.location(),
                         );
-                        let message = self.infer(*message)?;
+                        let message = self.infer(*message).unwrap_or_else(|error| {
+                            self.problems.error(error);
+                            self.error_expr(location)
+                        });
 
-                        unify(string(), message.type_())
-                            .map_err(|e| convert_unify_error(e, message.location()))?;
+                        let _ = unify(string(), message.type_()).map_err(|e| {
+                            self.problems
+                                .error(convert_unify_error(e, message.location()))
+                        });
                         Some(Box::new(message))
                     }
                     None => None,
                 };
-                Ok(AssignmentKind::Assert { location, message })
+                AssignmentKind::Assert { location, message }
             }
         }
     }


Test patch:
diff --git a/compiler-core/src/type_/tests/errors.rs b/compiler-core/src/type_/tests/errors.rs
index 87f93b419..5dba0421d 100644
--- a/compiler-core/src/type_/tests/errors.rs
+++ b/compiler-core/src/type_/tests/errors.rs
@@ -2849,3 +2849,15 @@ pub fn want_result(wibble: fn() -> Result(Int, Bool)) {
 "
     );
 }
+
+#[test]
+// https://github.com/gleam-lang/gleam/issues/4195
+fn let_assert_binding_cannot_be_used_in_panic_message() {
+    assert_module_error!(
+        r#"
+pub fn main() {
+  let assert Ok(message) = Error("Not Message") as { "Uh oh: " <> message }
+}
+"#
+    );
+}
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__let_assert_binding_cannot_be_used_in_panic_message.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__let_assert_binding_cannot_be_used_in_panic_message.snap
new file mode 100644
index 000000000..586d930db
--- /dev/null
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__let_assert_binding_cannot_be_used_in_panic_message.snap
@@ -0,0 +1,19 @@
+---
+source: compiler-core/src/type_/tests/errors.rs
+expression: "\npub fn main() {\n  let assert Ok(message) = Error(\"Not Message\") as { \"Uh oh: \" <> message }\n}\n"
+---
+----- SOURCE CODE
+
+pub fn main() {
+  let assert Ok(message) = Error("Not Message") as { "Uh oh: " <> message }
+}
+
+
+----- ERROR
+error: Unknown variable
+  ┌─ /src/one/two.gleam:3:67
+  │
+3 │   let assert Ok(message) = Error("Not Message") as { "Uh oh: " <> message }
+  │                                                                   ^^^^^^^
+
+The name `message` is not in scope here.