Repository: gleam-lang/gleam (rust)

Issue:
Slightly wrong error message for missing `main` function in test module
When running `gleam test`, if the `project_test` module doesn't have a `main` function, this error is printed:
```txt
error: Module does not have a main function

`project_test` does not have a main function so the module can not be run.
Hint: Add a public `main` function to to `src/project_test.gleam`.
```
Which isn't quite correct, as the file is `test/project_test.gleam` not `src/project_test.gleam`

Pull request description:
Fix slightly wrong error message for missing main function in test module
resolves #4420 


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 344b611e4..7ad91c10c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -73,6 +73,31 @@
   errors as soon as it finds one.
   ([Giacomo Cavalieri](https://github.com/giacomocavalieri))
 
+- The error message for types used with the wrong number of arguments has been
+  improved. For example, this piece of code:
+
+  ```gleam
+  type Wibble(a)
+
+  type Wobble {
+    Wobble(Wibble)
+  }
+  ```
+
+  Produces the following error:
+
+  ```txt
+    error: Incorrect arity
+    ┌─ /src/one/two.gleam:5:10
+    │
+  5 │   Wobble(Wibble)
+    │          ^^^^^^ Expected 1 type argument, got 0
+
+  `Wibble` requires 1 type argument but none where provided.
+  ```
+
+  ([Giacomo Cavalieri](https://github.com/giacomocavalieri))
+
 ### Build tool
 
 - The build tool now supports placing modules in a directory called `dev`,
@@ -235,3 +260,6 @@
 - Fixed a bug where updating the remote URL of a Git dependency would fail to
   update the remote in the local dependency, causing a caching issue.
   ([Surya Rose](https://github.com/GearsDatapacks))
+
+- Fix slightly wrong error message for missing main function in test module.
+  ([Samuel Cristobal](https://github.com/scristobal))
diff --git a/compiler-core/src/build.rs b/compiler-core/src/build.rs
index a0059aeac..d4fb197b1 100644
--- a/compiler-core/src/build.rs
+++ b/compiler-core/src/build.rs
@@ -35,7 +35,7 @@ use camino::Utf8PathBuf;
 use ecow::EcoString;
 use itertools::Itertools;
 use serde::{Deserialize, Serialize};
-use std::fmt::Debug;
+use std::fmt::{Debug, Display};
 use std::sync::Arc;
 use std::time::SystemTime;
 use std::{collections::HashMap, ffi::OsString, fs::DirEntry, iter::Peekable, process};
@@ -589,6 +589,16 @@ impl Origin {
     pub fn is_dev(&self) -> bool {
         matches!(self, Self::Dev)
     }
+
+    /// Name of the folder containing the origin.
+    #[must_use]
+    pub fn folder_name(&self) -> &str {
+        match self {
+            Origin::Src => "src",
+            Origin::Test => "test",
+            Origin::Dev => "dev",
+        }
+    }
 }
 
 fn doc_comments_before<'a>(
diff --git a/compiler-core/src/error.rs b/compiler-core/src/error.rs
index 187e13fc0..3c800e84f 100644
--- a/compiler-core/src/error.rs
+++ b/compiler-core/src/error.rs
@@ -1,5 +1,5 @@
 #![allow(clippy::unwrap_used, clippy::expect_used)]
-use crate::build::{Outcome, Runtime, Target};
+use crate::build::{Origin, Outcome, Runtime, Target};
 use crate::diagnostic::{Diagnostic, ExtraLabel, Label, Location};
 use crate::type_::collapse_links;
 use crate::type_::error::{
@@ -177,7 +177,7 @@ pub enum Error {
     },
 
     #[error("{module} does not have a main function")]
-    ModuleDoesNotHaveMainFunction { module: EcoString },
+    ModuleDoesNotHaveMainFunction { module: EcoString, origin: Origin },
 
     #[error("{module}'s main function has the wrong arity so it can not be run")]
     MainFunctionHasWrongArity { module: EcoString, arity: usize },
@@ -923,7 +923,7 @@ forward slash and must not end with a slash."
                 }]
             }
 
-            Error::ModuleDoesNotHaveMainFunction { module } => vec![Diagnostic {
+            Error::ModuleDoesNotHaveMainFunction { module, origin } => vec![Diagnostic {
                 title: "Module does not have a main function".into(),
                 text: format!(
                     "`{module}` does not have a main function so the module can not be run."
@@ -932,7 +932,7 @@ forward slash and must not end with a slash."
                 location: None,
                 hint: Some(format!(
                     "Add a public `main` function to \
-to `src/{module}.gleam`."
+to `{}/{module}.gleam`.", origin.folder_name()
                 )),
             }],
 
@@ -2163,16 +2163,20 @@ But function expects:
                 TypeError::IncorrectTypeArity {
                     location,
                     expected,
-                    given,
-                    ..
+                    given: given_number,
+                    name,
                 } => {
-                    let text = wrap("Functions and constructors have to be \
-called with their expected number of arguments.");
                     let expected = match expected {
-                        0 => "no arguments".into(),
-                        1 => "1 argument".into(),
-                        _ => format!("{expected} arguments"),
+                        0 => "no type arguments".into(),
+                        1 => "1 type argument".into(),
+                        _ => format!("{expected} type arguments"),
+                    };
+                    let given = match given_number {
+                        0 => "none",
+                        _ => &format!("{given_number}")
                     };
+                    let text = wrap_format!("`{name}` requires {expected} \
+but {given} where provided.");
                     Diagnostic {
                         title: "Incorrect arity".into(),
                         text,
@@ -2180,7 +2184,7 @@ called with their expected number of arguments.");
                         level: Level::Error,
                         location: Some(Location {
                             label: Label {
-                                text: Some(format!("Expected {expected}, got {given}")),
+                                text: Some(format!("Expected {expected}, got {given_number}")),
                                 span: *location,
                             },
                             path: path.clone(),
diff --git a/compiler-core/src/type_.rs b/compiler-core/src/type_.rs
index a46153b84..b8a5dbe4b 100644
--- a/compiler-core/src/type_.rs
+++ b/compiler-core/src/type_.rs
@@ -1051,6 +1051,7 @@ impl ModuleInterface {
     pub fn get_main_function(&self, target: Target) -> Result<ModuleFunction, crate::Error> {
         let not_found = || crate::Error::ModuleDoesNotHaveMainFunction {
             module: self.name.clone(),
+            origin: self.origin,
         };
 
         // Module must have a value with the name "main"
@@ -1059,7 +1060,7 @@ impl ModuleInterface {
             .get(&EcoString::from("main"))
             .ok_or_else(not_found)?;
 
-        assert_suitable_main_function(value, &self.name, target)?;
+        assert_suitable_main_function(value, &self.name, self.origin, target)?;
 
         Ok(ModuleFunction {
             package: self.package.clone(),
@@ -1565,10 +1566,12 @@ pub enum FieldAccessUsage {
 fn assert_suitable_main_function(
     value: &ValueConstructor,
     module_name: &EcoString,
+    origin: Origin,
     target: Target,
 ) -> Result<(), crate::Error> {
     let not_found = || crate::Error::ModuleDoesNotHaveMainFunction {
         module: module_name.clone(),
+        origin,
     };
 
     // The value must be a module function


Test patch:
diff --git a/compiler-core/src/type_/tests.rs b/compiler-core/src/type_/tests.rs
index 4d7b6a56c..44cf5eaa7 100644
--- a/compiler-core/src/type_/tests.rs
+++ b/compiler-core/src/type_/tests.rs
@@ -2678,7 +2678,10 @@ fn assert_suitable_main_function_not_module_function() {
             name: "main".into(),
         },
     };
-    assert!(assert_suitable_main_function(&value, &"module".into(), Target::Erlang).is_err(),);
+    assert!(
+        assert_suitable_main_function(&value, &"module".into(), Origin::Src, Target::Erlang)
+            .is_err(),
+    );
 }
 
 #[test]
@@ -2705,7 +2708,10 @@ fn assert_suitable_main_function_wrong_arity() {
             },
         },
     };
-    assert!(assert_suitable_main_function(&value, &"module".into(), Target::Erlang).is_err(),);
+    assert!(
+        assert_suitable_main_function(&value, &"module".into(), Origin::Src, Target::Erlang)
+            .is_err(),
+    );
 }
 
 #[test]
@@ -2732,7 +2738,10 @@ fn assert_suitable_main_function_ok() {
             },
         },
     };
-    assert!(assert_suitable_main_function(&value, &"module".into(), Target::Erlang).is_ok(),);
+    assert!(
+        assert_suitable_main_function(&value, &"module".into(), Origin::Src, Target::Erlang)
+            .is_ok(),
+    );
 }
 
 #[test]
@@ -2759,7 +2768,10 @@ fn assert_suitable_main_function_erlang_not_supported() {
             },
         },
     };
-    assert!(assert_suitable_main_function(&value, &"module".into(), Target::Erlang).is_err(),);
+    assert!(
+        assert_suitable_main_function(&value, &"module".into(), Origin::Src, Target::Erlang)
+            .is_err(),
+    );
 }
 
 #[test]
@@ -2786,7 +2798,10 @@ fn assert_suitable_main_function_javascript_not_supported() {
             },
         },
     };
-    assert!(assert_suitable_main_function(&value, &"module".into(), Target::JavaScript).is_err(),);
+    assert!(
+        assert_suitable_main_function(&value, &"module".into(), Origin::Src, Target::JavaScript)
+            .is_err(),
+    );
 }
 
 #[test]
diff --git a/compiler-core/src/type_/tests/errors.rs b/compiler-core/src/type_/tests/errors.rs
index d1589a31c..7c60f2442 100644
--- a/compiler-core/src/type_/tests/errors.rs
+++ b/compiler-core/src/type_/tests/errors.rs
@@ -3071,3 +3071,16 @@ pub fn main() {
 "#
     );
 }
+
+#[test]
+fn error_for_missing_type_parameters() {
+    assert_module_error!(
+        r#"
+type Wibble(a)
+
+type Wobble {
+  Wobble(Wibble)
+}
+"#
+    );
+}
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__error_for_missing_type_parameters.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__error_for_missing_type_parameters.snap
new file mode 100644
index 000000000..cdd4e02bd
--- /dev/null
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__error_for_missing_type_parameters.snap
@@ -0,0 +1,21 @@
+---
+source: compiler-core/src/type_/tests/errors.rs
+expression: "\ntype Wibble(a)\n\ntype Wobble {\n  Wobble(Wibble)\n}\n"
+---
+----- SOURCE CODE
+
+type Wibble(a)
+
+type Wobble {
+  Wobble(Wibble)
+}
+
+
+----- ERROR
+error: Incorrect arity
+  ┌─ /src/one/two.gleam:5:10
+  │
+5 │   Wobble(Wibble)
+  │          ^^^^^^ Expected 1 type argument, got 0
+
+`Wibble` requires 1 type argument but none where provided.
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__module_arity_error.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__module_arity_error.snap
index f2bae7269..2201dfd4a 100644
--- a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__module_arity_error.snap
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__module_arity_error.snap
@@ -10,7 +10,6 @@ error: Incorrect arity
   ┌─ /src/one/two.gleam:1:10
   │
 1 │ fn go(x: List(a, b)) -> Int { 1 }
-  │          ^^^^^^^^^^ Expected 1 argument, got 2
+  │          ^^^^^^^^^^ Expected 1 type argument, got 2
 
-Functions and constructors have to be called with their expected number of
-arguments.
+`List` requires 1 type argument but 2 where provided.