From 666761efeffc4b8181e8de6eb9c9a2dcecf69ece Mon Sep 17 00:00:00 2001 From: KtorZ Date: Thu, 22 Dec 2022 21:43:55 +0100 Subject: [PATCH] Make 'UnexpectedLabelArg' errors more helpful MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Before ``` × Checking ╰─▶ Unexpected labeled argument t ╭─[/Users/mati/Devel/OpenSource/time_lock_aiken/validators/time_lock.ak:13:1] 13 │ let now = when context.transaction.validity_range.lower_bound.bound_type is { 14 │ Finite { t } -> t · ─ 15 │ NegativeInfinity -> 0 ╰──── ``` ## After ``` × Type-checking ╰─▶ Unexpected labeled argument 't' ╭─[../stdlib/validators/tmp.ak:10:1] 10 │ let now = when context.transaction.validity_range.lower_bound.bound_type is { 11 │ interval.Finite { t } -> t · ─ 12 │ interval.NegativeInfinity -> 0 ╰──── help: The constructor 'Finite' does not have any labeled field. Its fields must therefore be matched only by position. Perhaps, try the following: ╰─▶ interval.Finite(t) ``` --- crates/aiken-lang/src/format.rs | 4 +-- crates/aiken-lang/src/tipo/environment.rs | 44 ++++++++++++++++++++++- crates/aiken-lang/src/tipo/error.rs | 9 +++-- crates/aiken-lang/src/tipo/pattern.rs | 11 ++++-- 4 files changed, 60 insertions(+), 8 deletions(-) diff --git a/crates/aiken-lang/src/format.rs b/crates/aiken-lang/src/format.rs index 91b7d0f1..795dfedf 100644 --- a/crates/aiken-lang/src/format.rs +++ b/crates/aiken-lang/src/format.rs @@ -796,7 +796,7 @@ impl<'comments> Formatter<'comments> { } } - fn pattern_constructor<'a>( + pub fn pattern_constructor<'a>( &mut self, name: &'a str, args: &'a [CallArg], @@ -1478,7 +1478,7 @@ impl<'comments> Formatter<'comments> { list(elements_document, elements.len(), tail) } - fn pattern<'a>(&mut self, pattern: &'a UntypedPattern) -> Document<'a> { + pub fn pattern<'a>(&mut self, pattern: &'a UntypedPattern) -> Document<'a> { let comments = self.pop_comments(pattern.location().start); let doc = match pattern { Pattern::Int { value, .. } => value.to_doc(), diff --git a/crates/aiken-lang/src/tipo/environment.rs b/crates/aiken-lang/src/tipo/environment.rs index d0992f4b..ce4992cb 100644 --- a/crates/aiken-lang/src/tipo/environment.rs +++ b/crates/aiken-lang/src/tipo/environment.rs @@ -10,9 +10,10 @@ use crate::{ ast::{ Annotation, CallArg, DataType, Definition, Function, ModuleConstant, Pattern, RecordConstructor, RecordConstructorArg, Span, TypeAlias, TypedDefinition, - UnqualifiedImport, UntypedDefinition, Use, PIPE_VARIABLE, + UnqualifiedImport, UntypedDefinition, UntypedPattern, Use, PIPE_VARIABLE, }, builtins::{self, function, generic_var, tuple, unbound_var}, + format::Formatter, tipo::fields::FieldMap, IdGenerator, }; @@ -1603,12 +1604,53 @@ fn assert_unique_const_name<'a>( } } +pub(super) fn assert_no_labeled_arguments_in_pattern<'a>( + name: &str, + args: &[CallArg], + module: &'a Option, + with_spread: bool, +) -> Result<(), Error> { + for arg in args { + if let Some(label) = &arg.label { + let fixed_args = args + .iter() + .map(|arg| CallArg { + label: None, + location: arg.location, + value: arg.value.clone(), + }) + .collect::>(); + + let suggestion = Formatter::new() + .pattern_constructor(name, &fixed_args, module, with_spread, false) + .to_pretty_string(70); + + let hint = format!( + r#"The constructor '{name}' does not have any labeled field. Its fields +must therefore be matched only by position. + +Perhaps, try the following: + +╰─▶ {suggestion}"# + ); + + return Err(Error::UnexpectedLabeledArg { + location: arg.location, + label: label.to_string(), + hint: Some(hint), + }); + } + } + Ok(()) +} + pub(super) fn assert_no_labeled_arguments(args: &[CallArg]) -> Result<(), Error> { for arg in args { if let Some(label) = &arg.label { return Err(Error::UnexpectedLabeledArg { location: arg.location, label: label.to_string(), + hint: None, }); } } diff --git a/crates/aiken-lang/src/tipo/error.rs b/crates/aiken-lang/src/tipo/error.rs index 19dd2189..92496ec0 100644 --- a/crates/aiken-lang/src/tipo/error.rs +++ b/crates/aiken-lang/src/tipo/error.rs @@ -144,11 +144,14 @@ pub enum Error { #[error("{name} is a reserved module name\n")] ReservedModuleName { name: String }, - #[error("Unexpected labeled argument\n\n{label}\n")] + #[error("Unexpected labeled argument '{label}'\n")] + #[diagnostic()] UnexpectedLabeledArg { #[label] location: Span, label: String, + #[help] + hint: Option, }, #[error("Unexpected type hole\n")] @@ -229,8 +232,8 @@ pub enum Error { #[diagnostic(help( r#"Did you forget to import it? -Data-type constructors are not automatically imported, even if their type is -imported. So, if a module `aiken/pet` defines the following type: +Data-type constructors are not automatically imported, even if their type +is imported. So, if a module `aiken/pet` defines the following type: ┍━ aiken/pet.ak ━━━━━━━━ │ pub type Pet {{ diff --git a/crates/aiken-lang/src/tipo/pattern.rs b/crates/aiken-lang/src/tipo/pattern.rs index 63f89df3..a15c3064 100644 --- a/crates/aiken-lang/src/tipo/pattern.rs +++ b/crates/aiken-lang/src/tipo/pattern.rs @@ -9,7 +9,9 @@ use std::{ use itertools::Itertools; use super::{ - environment::{assert_no_labeled_arguments, collapse_links, EntityKind, Environment}, + environment::{ + assert_no_labeled_arguments_in_pattern, collapse_links, EntityKind, Environment, + }, error::Error, hydrator::Hydrator, PatternConstructor, Type, ValueConstructor, ValueConstructorVariant, @@ -483,7 +485,12 @@ impl<'a, 'b> PatternTyper<'a, 'b> { } // The fun has no field map and so we error if arguments have been labelled - None => assert_no_labeled_arguments(&pattern_args)?, + None => assert_no_labeled_arguments_in_pattern( + &name, + &pattern_args, + &module, + with_spread, + )?, } let constructor_typ = cons.tipo.clone();