Make 'UnexpectedLabelArg' errors more helpful

## 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)
  ```
This commit is contained in:
KtorZ 2022-12-22 21:43:55 +01:00
parent 0682781460
commit 666761efef
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
4 changed files with 60 additions and 8 deletions

View File

@ -796,7 +796,7 @@ impl<'comments> Formatter<'comments> {
} }
} }
fn pattern_constructor<'a>( pub fn pattern_constructor<'a>(
&mut self, &mut self,
name: &'a str, name: &'a str,
args: &'a [CallArg<UntypedPattern>], args: &'a [CallArg<UntypedPattern>],
@ -1478,7 +1478,7 @@ impl<'comments> Formatter<'comments> {
list(elements_document, elements.len(), tail) 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 comments = self.pop_comments(pattern.location().start);
let doc = match pattern { let doc = match pattern {
Pattern::Int { value, .. } => value.to_doc(), Pattern::Int { value, .. } => value.to_doc(),

View File

@ -10,9 +10,10 @@ use crate::{
ast::{ ast::{
Annotation, CallArg, DataType, Definition, Function, ModuleConstant, Pattern, Annotation, CallArg, DataType, Definition, Function, ModuleConstant, Pattern,
RecordConstructor, RecordConstructorArg, Span, TypeAlias, TypedDefinition, RecordConstructor, RecordConstructorArg, Span, TypeAlias, TypedDefinition,
UnqualifiedImport, UntypedDefinition, Use, PIPE_VARIABLE, UnqualifiedImport, UntypedDefinition, UntypedPattern, Use, PIPE_VARIABLE,
}, },
builtins::{self, function, generic_var, tuple, unbound_var}, builtins::{self, function, generic_var, tuple, unbound_var},
format::Formatter,
tipo::fields::FieldMap, tipo::fields::FieldMap,
IdGenerator, 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<UntypedPattern>],
module: &'a Option<String>,
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::<Vec<_>>();
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<A>(args: &[CallArg<A>]) -> Result<(), Error> { pub(super) fn assert_no_labeled_arguments<A>(args: &[CallArg<A>]) -> Result<(), Error> {
for arg in args { for arg in args {
if let Some(label) = &arg.label { if let Some(label) = &arg.label {
return Err(Error::UnexpectedLabeledArg { return Err(Error::UnexpectedLabeledArg {
location: arg.location, location: arg.location,
label: label.to_string(), label: label.to_string(),
hint: None,
}); });
} }
} }

View File

@ -144,11 +144,14 @@ pub enum Error {
#[error("{name} is a reserved module name\n")] #[error("{name} is a reserved module name\n")]
ReservedModuleName { name: String }, ReservedModuleName { name: String },
#[error("Unexpected labeled argument\n\n{label}\n")] #[error("Unexpected labeled argument '{label}'\n")]
#[diagnostic()]
UnexpectedLabeledArg { UnexpectedLabeledArg {
#[label] #[label]
location: Span, location: Span,
label: String, label: String,
#[help]
hint: Option<String>,
}, },
#[error("Unexpected type hole\n")] #[error("Unexpected type hole\n")]
@ -229,8 +232,8 @@ pub enum Error {
#[diagnostic(help( #[diagnostic(help(
r#"Did you forget to import it? r#"Did you forget to import it?
Data-type constructors are not automatically imported, even if their type is Data-type constructors are not automatically imported, even if their type
imported. So, if a module `aiken/pet` defines the following type: is imported. So, if a module `aiken/pet` defines the following type:
aiken/pet.ak aiken/pet.ak
pub type Pet {{ pub type Pet {{

View File

@ -9,7 +9,9 @@ use std::{
use itertools::Itertools; use itertools::Itertools;
use super::{ use super::{
environment::{assert_no_labeled_arguments, collapse_links, EntityKind, Environment}, environment::{
assert_no_labeled_arguments_in_pattern, collapse_links, EntityKind, Environment,
},
error::Error, error::Error,
hydrator::Hydrator, hydrator::Hydrator,
PatternConstructor, Type, ValueConstructor, ValueConstructorVariant, 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 // 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(); let constructor_typ = cons.tipo.clone();