Repository: gleam-lang/gleam (rust)

Issue:
Add hint to error message when trying to use a variable name starting with a `_`
<!--
Please include the following:
- Steps and code to reproduce the problem
- Versions for Gleam, OS, the runtime and any other software used
- Logs from running Gleam with the `GLEAM_LOG` environment variable set to `trace`
-->


### Example 1:

```gleam
import gleam/io

pub fn main() {
  let val = _with_underscore(1)
}

fn _with_underscore(value: Int) -> String {
  value.to_string()
}
```

Fails with:

```shell
➜ gleam run
error: Syntax error
  ┌─ /Users/monty/work/ashwch/learning/gleam/tutorial/src/tutorial.gleam:7:11
  │
7 │   let val = _with_underscore(1)
  │           ^ I was expecting a value after this
```

### Example 2:

```gleam
import gleam/io
import gleam/int

pub fn main() {
  case int.random(5) {
    1 -> _with_underscore(1)
  }
}


fn _with_underscore(value: Int) -> String {
  value.to_string()
}
```

Fails with:

```shell
✗  gleam run
error: Syntax error
  ┌─ /Users/monty/work/ashwch/learning/gleam/tutorial/src/tutorial.gleam:6:7
  │
6 │     1 -> _with_underscore(1)
  │       ^^ I was expecting an expression after this
```


----
[And as pointed out in Discord](https://discord.com/channels/768594524158427167/1272240110326382673/1272240110326382673), if I comment out the main function or move the `_with_underscore` definition above main(or anything calling it) then it shows the correct message:

```shell
error: Syntax error
  ┌─ /Users/monty/work/ashwch/learning/gleam/tutorial/src/tutorial.gleam:4:4
  │
4 │ fn _with_underscore(value: Int) -> String {
  │    ^^^^^^^^^^^^^^^^ I'm expecting a lowercase name here
```


### System details:

I'm on MacOS 14.6.1 (M1 Pro)

```
➜ gleam --version
gleam 1.4.1
➜ sw_vers -productVersion
14.6.1
➜ uname -m
arm64
```




Pull request:
Improve error message on discarded names in case patterns and assignments
Closes #3504 

Are there any extra cases to handle?

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cd066e872..6ea74b311 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -138,6 +138,11 @@
 - Fixed a bug which affected inference of function calls in pipe expressions.
   ([sobolevn](https://github.com/sobolevn))
 
+- Improved an error message when using variable names
+  starting with an underscore in constructs like:
+  `let some = _func()` or `case { 1 -> _func() }`
+  ([sobolevn](https://github.com/sobolevn))
+
 ## v1.4.1 - 2024-08-04
 
 ### Bug Fixes
diff --git a/compiler-core/src/parse.rs b/compiler-core/src/parse.rs
index 5b7873789..ebf73d26b 100644
--- a/compiler-core/src/parse.rs
+++ b/compiler-core/src/parse.rs
@@ -953,11 +953,18 @@ where
                 end: pattern.location().end,
             },
         })?;
-        let value = self.parse_expression_inner(true)?.ok_or(ParseError {
-            error: ParseErrorType::ExpectedValue,
-            location: SrcSpan {
-                start: eq_s,
-                end: eq_e,
+        let value = self.parse_expression_inner(true)?.ok_or(match self.tok0 {
+            Some((start, Token::DiscardName { .. }, end)) => ParseError {
+                error: ParseErrorType::IncorrectName,
+                location: SrcSpan { start, end },
+            },
+
+            _ => ParseError {
+                error: ParseErrorType::ExpectedValue,
+                location: SrcSpan {
+                    start: eq_s,
+                    end: eq_e,
+                },
             },
         })?;
         Ok(Statement::Assignment(Assignment {
@@ -1389,13 +1396,18 @@ where
                     then,
                 }))
             } else {
-                parse_error(
-                    ParseErrorType::ExpectedExpr,
-                    SrcSpan {
-                        start: arr_s,
-                        end: arr_e,
-                    },
-                )
+                match self.tok0 {
+                    Some((start, Token::DiscardName { .. }, end)) => {
+                        parse_error(ParseErrorType::IncorrectName, SrcSpan { start, end })
+                    }
+                    _ => parse_error(
+                        ParseErrorType::ExpectedExpr,
+                        SrcSpan {
+                            start: arr_s,
+                            end: arr_e,
+                        },
+                    ),
+                }
             }
         } else {
             Ok(None)


Test patch:
diff --git a/compiler-core/src/parse/snapshots/gleam_core__parse__tests__error_message_on_variable_starting_with_underscore.snap b/compiler-core/src/parse/snapshots/gleam_core__parse__tests__error_message_on_variable_starting_with_underscore.snap
new file mode 100644
index 000000000..910bd1f76
--- /dev/null
+++ b/compiler-core/src/parse/snapshots/gleam_core__parse__tests__error_message_on_variable_starting_with_underscore.snap
@@ -0,0 +1,12 @@
+---
+source: compiler-core/src/parse/tests.rs
+expression: "\n  pub fn main() {\n    let val = _func_starting_with_underscore(1)\n  }"
+---
+error: Syntax error
+  ┌─ /src/parse/error.gleam:3:15
+  │
+3 │     let val = _func_starting_with_underscore(1)
+  │               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ I'm expecting a lowercase name here
+
+Hint: Variable and module names start with a lowercase letter, and can
+contain a-z, 0-9, or _.
diff --git a/compiler-core/src/parse/snapshots/gleam_core__parse__tests__error_message_on_variable_starting_with_underscore2.snap b/compiler-core/src/parse/snapshots/gleam_core__parse__tests__error_message_on_variable_starting_with_underscore2.snap
new file mode 100644
index 000000000..0cb1f631c
--- /dev/null
+++ b/compiler-core/src/parse/snapshots/gleam_core__parse__tests__error_message_on_variable_starting_with_underscore2.snap
@@ -0,0 +1,12 @@
+---
+source: compiler-core/src/parse/tests.rs
+expression: "\n  pub fn main() {\n    case 1 {\n      1 -> _with_underscore(1)\n    }\n  }"
+---
+error: Syntax error
+  ┌─ /src/parse/error.gleam:4:12
+  │
+4 │       1 -> _with_underscore(1)
+  │            ^^^^^^^^^^^^^^^^ I'm expecting a lowercase name here
+
+Hint: Variable and module names start with a lowercase letter, and can
+contain a-z, 0-9, or _.
diff --git a/compiler-core/src/parse/tests.rs b/compiler-core/src/parse/tests.rs
index 2c10928a9..428a618d2 100644
--- a/compiler-core/src/parse/tests.rs
+++ b/compiler-core/src/parse/tests.rs
@@ -1426,6 +1426,17 @@ pub fn main() {
     );
 }
 
+#[test]
+fn error_message_on_variable_starting_with_underscore() {
+    // https://github.com/gleam-lang/gleam/issues/3504
+    assert_module_error!(
+        "
+  pub fn main() {
+    let val = _func_starting_with_underscore(1)
+  }"
+    );
+}
+
 #[test]
 fn non_module_level_function_with_not_a_name() {
     assert_module_error!(
@@ -1436,3 +1447,16 @@ pub fn main() {
 "#
     );
 }
+
+#[test]
+fn error_message_on_variable_starting_with_underscore2() {
+    // https://github.com/gleam-lang/gleam/issues/3504
+    assert_module_error!(
+        "
+  pub fn main() {
+    case 1 {
+      1 -> _with_underscore(1)
+    }
+  }"
+    );
+}