diff --git a/CHANGELOG.md b/CHANGELOG.md index 2877ade616b..a020b410655 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/compiler-core/src/error.rs b/compiler-core/src/error.rs index 214a937b1ad..54d504623c9 100644 --- a/compiler-core/src/error.rs +++ b/compiler-core/src/error.rs @@ -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}; @@ -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(), diff --git a/compiler-core/src/type_/error.rs b/compiler-core/src/type_/error.rs index 5aee5a5bf0c..420c0abc4b5 100644 --- a/compiler-core/src/type_/error.rs +++ b/compiler-core/src/type_/error.rs @@ -224,6 +224,7 @@ pub enum Error { IncorrectArity { location: SrcSpan, expected: usize, + context: IncorrectArityContext, given: usize, labels: Vec, }, @@ -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, @@ -1431,6 +1438,7 @@ pub fn convert_not_fun_error( ) => Error::IncorrectArity { labels: vec![], location: call_location, + context: IncorrectArityContext::Function, expected, given, }, diff --git a/compiler-core/src/type_/expression.rs b/compiler-core/src/type_/expression.rs index 44b02431915..204f576ce1b 100644 --- a/compiler-core/src/type_/expression.rs +++ b/compiler-core/src/type_/expression.rs @@ -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)?, @@ -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. @@ -3995,6 +3999,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { Error::IncorrectArity { expected, given, + context, labels, location, } => { @@ -4002,6 +4007,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { self.problems.error(Error::IncorrectArity { expected, given, + context, labels, location, }); diff --git a/compiler-core/src/type_/fields.rs b/compiler-core/src/type_/fields.rs index 6939c3e3011..7d622ec1f00 100644 --- a/compiler-core/src/type_/fields.rs +++ b/compiler-core/src/type_/fields.rs @@ -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}; @@ -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(&self, args: &mut Vec>, location: SrcSpan) -> Result<(), Error> { + pub fn reorder( + &self, + args: &mut Vec>, + location: SrcSpan, + context: IncorrectArityContext, + ) -> Result<(), Error> { let mut labelled_arguments_given = false; let mut seen_labels = HashSet::new(); let mut unknown_labels = Vec::new(); @@ -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(), }); diff --git a/compiler-core/src/type_/pattern.rs b/compiler-core/src/type_/pattern.rs index 2892f93f895..3a8677ba970 100644 --- a/compiler-core/src/type_/pattern.rs +++ b/compiler-core/src/type_/pattern.rs @@ -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(), }); @@ -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) => { @@ -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; } } @@ -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(), }); @@ -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(), }); diff --git a/compiler-core/src/type_/tests.rs b/compiler-core/src/type_/tests.rs index 8fc33c1d013..520a35957dd 100644 --- a/compiler-core/src/type_/tests.rs +++ b/compiler-core/src/type_/tests.rs @@ -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); } } diff --git a/compiler-core/src/type_/tests/errors.rs b/compiler-core/src/type_/tests/errors.rs index 2524d137f96..89c4e10fe06 100644 --- a/compiler-core/src/type_/tests/errors.rs +++ b/compiler-core/src/type_/tests/errors.rs @@ -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 + } +} +" + ); +} diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__pattern_with_incorrect_arity.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__pattern_with_incorrect_arity.snap new file mode 100644 index 00000000000..31ab585f55b --- /dev/null +++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__pattern_with_incorrect_arity.snap @@ -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