Skip to content

Stop emitting two incorrect arity errors for wrong patterns #4694

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@
correctly to variables using label shorthand syntax.
([Surya Rose](https://github.com/GearsDatapacks))

- Fixed a bug where the compiler would emit the same error twice for patterns
with the wrong number of labels.
([Giacomo Cavalieri](https://github.com/giacomocavalieri))

## v1.11.1 - 2025-06-05

### Compiler
Expand Down
11 changes: 8 additions & 3 deletions compiler-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use crate::diagnostic::{Diagnostic, ExtraLabel, Label, Location};
use crate::strings::{to_snake_case, to_upper_camel_case};
use crate::type_::collapse_links;
use crate::type_::error::{
InvalidImportKind, MissingAnnotation, ModuleValueUsageContext, Named, UnknownField,
UnknownTypeHint, UnsafeRecordUpdateReason,
IncorrectArityContext, InvalidImportKind, MissingAnnotation, ModuleValueUsageContext, Named,
UnknownField, UnknownTypeHint, UnsafeRecordUpdateReason,
};
use crate::type_::printer::{Names, Printer};
use crate::type_::{FieldAccessUsage, error::PatternMatchKind};
Expand Down Expand Up @@ -2208,18 +2208,23 @@ but {given} where provided.");
TypeError::IncorrectArity {
labels,
location,
context,
expected,
given,
} => {
let text = if labels.is_empty() {
"".into()
} else {
let subject = match context {
IncorrectArityContext::Pattern => "pattern",
IncorrectArityContext::Function => "call",
};
let labels = labels
.iter()
.map(|p| format!(" - {p}"))
.sorted()
.join("\n");
format!("This call accepts these additional labelled arguments:\n\n{labels}",)
format!("This {subject} accepts these additional labelled arguments:\n\n{labels}",)
};
let expected = match expected {
0 => "no arguments".into(),
Expand Down
8 changes: 8 additions & 0 deletions compiler-core/src/type_/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ pub enum Error {
IncorrectArity {
location: SrcSpan,
expected: usize,
context: IncorrectArityContext,
given: usize,
labels: Vec<EcoString>,
},
Expand Down Expand Up @@ -673,6 +674,12 @@ pub enum LiteralCollectionKind {
Record,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IncorrectArityContext {
Pattern,
Function,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InvalidImportKind {
SrcImportingTest,
Expand Down Expand Up @@ -1431,6 +1438,7 @@ pub fn convert_not_fun_error(
) => Error::IncorrectArity {
labels: vec![],
location: call_location,
context: IncorrectArityContext::Function,
expected,
given,
},
Expand Down
10 changes: 8 additions & 2 deletions compiler-core/src/type_/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3659,7 +3659,9 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
.map_err(|e| convert_get_value_constructor_error(e, location, None))?
{
// The fun has a field map so labelled arguments may be present and need to be reordered.
Some(field_map) => field_map.reorder(&mut args, location)?,
Some(field_map) => {
field_map.reorder(&mut args, location, IncorrectArityContext::Function)?
}

// The fun has no field map and so we error if arguments have been labelled
None => assert_no_labelled_arguments(&args)?,
Expand Down Expand Up @@ -3975,7 +3977,9 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
match field_map {
// The fun has a field map so labelled arguments may be
// present and need to be reordered.
Some(field_map) => field_map.reorder(&mut args, location),
Some(field_map) => {
field_map.reorder(&mut args, location, IncorrectArityContext::Function)
}

// The fun has no field map and so we error if arguments
// have been labelled.
Expand All @@ -3995,13 +3999,15 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
Error::IncorrectArity {
expected,
given,
context,
labels,
location,
} => {
labelled_arity_error = true;
self.problems.error(Error::IncorrectArity {
expected,
given,
context,
labels,
location,
});
Expand Down
13 changes: 11 additions & 2 deletions compiler-core/src/type_/fields.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use super::Error;
use crate::ast::{CallArg, SrcSpan};
use crate::{
ast::{CallArg, SrcSpan},
type_::error::IncorrectArityContext,
};
use ecow::EcoString;
use itertools::Itertools;
use std::collections::{HashMap, HashSet};
Expand Down Expand Up @@ -39,7 +42,12 @@ impl FieldMap {
/// Reorder an argument list so that labelled fields supplied out-of-order are
/// in the correct order.
///
pub fn reorder<A>(&self, args: &mut Vec<CallArg<A>>, location: SrcSpan) -> Result<(), Error> {
pub fn reorder<A>(
&self,
args: &mut Vec<CallArg<A>>,
location: SrcSpan,
context: IncorrectArityContext,
) -> Result<(), Error> {
let mut labelled_arguments_given = false;
let mut seen_labels = HashSet::new();
let mut unknown_labels = Vec::new();
Expand All @@ -49,6 +57,7 @@ impl FieldMap {
return Err(Error::IncorrectArity {
labels: self.incorrect_arity_labels(args),
location,
context,
expected: self.arity as usize,
given: args.len(),
});
Expand Down
22 changes: 16 additions & 6 deletions compiler-core/src/type_/pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
self.error(Error::IncorrectArity {
labels: vec![],
location,
context: IncorrectArityContext::Pattern,
expected: type_elements.len(),
given: elements.len(),
});
Expand Down Expand Up @@ -925,6 +926,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
}
};

let mut incorrect_arity_error = false;
match constructor.field_map() {
// The fun has a field map so labelled arguments may be present and need to be reordered.
Some(field_map) => {
Expand Down Expand Up @@ -1014,11 +1016,14 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
}
}

if let Err(error) = field_map.reorder(&mut pattern_args, location) {
{
self.problems.error(error);
self.error_encountered = true;
};
if let Err(error) = field_map.reorder(
&mut pattern_args,
location,
IncorrectArityContext::Pattern,
) {
incorrect_arity_error = true;
self.problems.error(error);
self.error_encountered = true;
}
}

Expand Down Expand Up @@ -1127,10 +1132,14 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
self.set_subject_variable_variant(variable_to_infer, inferred_variant);
}

if args.len() != pattern_args.len() {
// We're emitting the incorrect arity error only if we haven't emitted
// one already. This might happen when we can't reorder the field map
// of a constructor because there's not enough labels.
if args.len() != pattern_args.len() && !incorrect_arity_error {
self.error(Error::IncorrectArity {
labels: vec![],
location,
context: IncorrectArityContext::Pattern,
expected: args.len(),
given: pattern_args.len(),
});
Expand Down Expand Up @@ -1165,6 +1174,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
self.error(Error::IncorrectArity {
labels: vec![],
location,
context: IncorrectArityContext::Pattern,
expected: 0,
given: pattern_args.len(),
});
Expand Down
5 changes: 4 additions & 1 deletion compiler-core/src/type_/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,10 @@ fn field_map_reorder_test() {
fields: self.fields,
};
let location = SrcSpan { start: 0, end: 0 };
assert_eq!(self.expected_result, fm.reorder(&mut args, location));
assert_eq!(
self.expected_result,
fm.reorder(&mut args, location, IncorrectArityContext::Function)
);
assert_eq!(self.expected_args, args);
}
}
Expand Down
16 changes: 16 additions & 0 deletions compiler-core/src/type_/tests/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3192,3 +3192,19 @@ pub fn go() {
"
);
}

// https://github.com/gleam-lang/gleam/issues/4693
#[test]
fn pattern_with_incorrect_arity() {
assert_module_error!(
"
pub type Pokemon { Pokemon(name: String, id: Int) }

pub fn main() {
case todo {
Pokemon(name:) -> todo
}
}
"
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
source: compiler-core/src/type_/tests/errors.rs
expression: "\npub type Pokemon { Pokemon(name: String, id: Int) }\n\npub fn main() {\n case todo {\n Pokemon(name:) -> todo\n }\n}\n"
---
----- SOURCE CODE

pub type Pokemon { Pokemon(name: String, id: Int) }

pub fn main() {
case todo {
Pokemon(name:) -> todo
}
}


----- ERROR
error: Incorrect arity
┌─ /src/one/two.gleam:6:5
6 │ Pokemon(name:) -> todo
│ ^^^^^^^^^^^^^^ Expected 2 arguments, got 1

This pattern accepts these additional labelled arguments:

- id
Loading