Repository: gleam-lang/gleam (rust)

Issue:
() is accept after a type name that is not generic
The following code

```gleam
pub fn main() {
  let a: Int() = 0
}
```

compiles fine.

I'm using gleam 1.5.1

Pull request:
Fix 3796
This PR fixes #3796 and a similar bug where `pub type Wibble() { ... }` would be accepted (but rewritten as `pub type Wibble { ... }` by the formatter)

Patch:
diff --git a/.editorconfig b/.editorconfig
index 0c6dbd519..441c0da4e 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -15,7 +15,7 @@ insert_final_newline = true
 indent_style = space
 indent_size = 2
 
-[*.{rs, gleam, erl, hrl, ex, mjs, js, ts, sh}]
+[*.{gleam, erl, hrl, ex, mjs, js, ts, sh}]
 max_line_length = 80
 
 [*.{rs, .erl, .hrl}]
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 46f59bad2..0792b024c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -378,6 +378,14 @@
   no arguments.
   ([Louis Pilfold](https://github.com/lpil))
 
+- Fixed a bug where the parser would incorrectly parse a generic type
+  constructor with no arguments.
+  ([Giacomo Cavalieri](https://github.com/giacomocavalieri))
+
+- Fixed a bug where the parser would incorrectly parse a generic type definition
+  with no arguments.
+  ([Giacomo Cavalieri](https://github.com/giacomocavalieri))
+
 - Fixed a bug where the language server wouldn't show hints when hovering over
   the tail of a list.
   ([Giacomo Cavalieri](https://github.com/giacomocavalieri))
diff --git a/compiler-core/src/parse.rs b/compiler-core/src/parse.rs
index 17cad973f..9b7f6b4a3 100644
--- a/compiler-core/src/parse.rs
+++ b/compiler-core/src/parse.rs
@@ -2215,10 +2215,16 @@ where
         &mut self,
     ) -> Result<(u32, EcoString, Vec<SpannedString>, u32, u32), ParseError> {
         let (start, upname, end) = self.expect_upname()?;
-        if self.maybe_one(&Token::LeftParen).is_some() {
+        if let Some((par_s, _)) = self.maybe_one(&Token::LeftParen) {
             let args =
                 Parser::series_of(self, &|p| Ok(Parser::maybe_name(p)), Some(&Token::Comma))?;
             let (_, par_e) = self.expect_one_following_series(&Token::RightParen, "a name")?;
+            if args.is_empty() {
+                return parse_error(
+                    ParseErrorType::TypeDefinitionNoArguments,
+                    SrcSpan::new(par_s, par_e),
+                );
+            }
             let args2 = args
                 .into_iter()
                 .map(|(start, name, end)| (SrcSpan { start, end }, name))
@@ -2408,9 +2414,15 @@ where
         name: EcoString,
         end: u32,
     ) -> Result<Option<TypeAst>, ParseError> {
-        if self.maybe_one(&Token::LeftParen).is_some() {
+        if let Some((par_s, _)) = self.maybe_one(&Token::LeftParen) {
             let args = self.parse_types()?;
             let (_, par_e) = self.expect_one(&Token::RightParen)?;
+            if args.is_empty() {
+                return parse_error(
+                    ParseErrorType::TypeConstructorNoArguments,
+                    SrcSpan::new(par_s, par_e),
+                );
+            }
             Ok(Some(TypeAst::Constructor(TypeAstConstructor {
                 location: SrcSpan { start, end: par_e },
                 module,
diff --git a/compiler-core/src/parse/error.rs b/compiler-core/src/parse/error.rs
index c973a8e71..e99e49eeb 100644
--- a/compiler-core/src/parse/error.rs
+++ b/compiler-core/src/parse/error.rs
@@ -316,6 +316,17 @@ utf16_codepoint, utf32_codepoint, signed, unsigned, big, little, native, size, u
                 "I was expecting arguments here",
                 vec!["A record must be passed arguments when constructed.".into()],
             ),
+            ParseErrorType::TypeConstructorNoArguments => (
+                "I was expecting arguments here",
+                vec!["A type constructor must be passed arguments.".into()],
+            ),
+            ParseErrorType::TypeDefinitionNoArguments => (
+                "I was expecting generic parameters here",
+                vec![
+                    "A generic type must have at least a generic parameter.".into(),
+                    "Hint: If a type is not generic you should omit the `()`.".into(),
+                ],
+            ),
         }
     }
 }
@@ -385,6 +396,8 @@ pub enum ParseErrorType {
     CallInClauseGuard, // case x { _ if f() -> 1 }
     IfExpression,
     ConstantRecordConstructorNoArguments, // const x = Record()
+    TypeConstructorNoArguments,           // let a : Int()
+    TypeDefinitionNoArguments,            // pub type Wibble() { ... }
 }
 
 impl LexicalError {


Test patch:
diff --git a/compiler-core/src/parse/snapshots/gleam_core__parse__tests__missing_type_constructor_arguments_in_type_annotation_1.snap b/compiler-core/src/parse/snapshots/gleam_core__parse__tests__missing_type_constructor_arguments_in_type_annotation_1.snap
new file mode 100644
index 000000000..dde98fe3f
--- /dev/null
+++ b/compiler-core/src/parse/snapshots/gleam_core__parse__tests__missing_type_constructor_arguments_in_type_annotation_1.snap
@@ -0,0 +1,15 @@
+---
+source: compiler-core/src/parse/tests.rs
+expression: "pub fn main() -> Int() {}"
+---
+----- SOURCE CODE
+pub fn main() -> Int() {}
+
+----- ERROR
+error: Syntax error
+  ┌─ /src/parse/error.gleam:1:21
+  │
+1 │ pub fn main() -> Int() {}
+  │                     ^^ I was expecting arguments here
+
+A type constructor must be passed arguments.
diff --git a/compiler-core/src/parse/snapshots/gleam_core__parse__tests__missing_type_constructor_arguments_in_type_annotation_2.snap b/compiler-core/src/parse/snapshots/gleam_core__parse__tests__missing_type_constructor_arguments_in_type_annotation_2.snap
new file mode 100644
index 000000000..857fc1211
--- /dev/null
+++ b/compiler-core/src/parse/snapshots/gleam_core__parse__tests__missing_type_constructor_arguments_in_type_annotation_2.snap
@@ -0,0 +1,17 @@
+---
+source: compiler-core/src/parse/tests.rs
+expression: "pub fn main() {\n  let a: Int() = todo\n}"
+---
+----- SOURCE CODE
+pub fn main() {
+  let a: Int() = todo
+}
+
+----- ERROR
+error: Syntax error
+  ┌─ /src/parse/error.gleam:2:13
+  │
+2 │   let a: Int() = todo
+  │             ^^ I was expecting arguments here
+
+A type constructor must be passed arguments.
diff --git a/compiler-core/src/parse/snapshots/gleam_core__parse__tests__missing_type_constructor_arguments_in_type_definition.snap b/compiler-core/src/parse/snapshots/gleam_core__parse__tests__missing_type_constructor_arguments_in_type_definition.snap
new file mode 100644
index 000000000..1385e3466
--- /dev/null
+++ b/compiler-core/src/parse/snapshots/gleam_core__parse__tests__missing_type_constructor_arguments_in_type_definition.snap
@@ -0,0 +1,20 @@
+---
+source: compiler-core/src/parse/tests.rs
+expression: "\npub type A() {\n  A(Int)\n}\n"
+---
+----- SOURCE CODE
+
+pub type A() {
+  A(Int)
+}
+
+
+----- ERROR
+error: Syntax error
+  ┌─ /src/parse/error.gleam:2:11
+  │
+2 │ pub type A() {
+  │           ^^ I was expecting generic parameters here
+
+A generic type must have at least a generic parameter.
+Hint: If a type is not generic you should omit the `()`.
diff --git a/compiler-core/src/parse/tests.rs b/compiler-core/src/parse/tests.rs
index ed674910a..c68c778ee 100644
--- a/compiler-core/src/parse/tests.rs
+++ b/compiler-core/src/parse/tests.rs
@@ -1548,3 +1548,29 @@ const a = A()
 "
     );
 }
+
+// https://github.com/gleam-lang/gleam/issues/3796
+#[test]
+fn missing_type_constructor_arguments_in_type_definition() {
+    assert_module_error!(
+        "
+pub type A() {
+  A(Int)
+}
+"
+    );
+}
+
+#[test]
+fn missing_type_constructor_arguments_in_type_annotation_1() {
+    assert_module_error!("pub fn main() -> Int() {}");
+}
+
+#[test]
+fn missing_type_constructor_arguments_in_type_annotation_2() {
+    assert_module_error!(
+        "pub fn main() {
+  let a: Int() = todo
+}"
+    );
+}