aiken_lang/tipo/
error.rs

1use super::Type;
2use crate::{
3    ast::{
4        Annotation, BinOp, CallArg, LogicalOpChainKind, Namespace, Span, UntypedFunction,
5        UntypedPattern,
6    },
7    error::ExtraData,
8    expr::{self, AssignmentPattern, UntypedAssignmentKind, UntypedExpr},
9    format::Formatter,
10    levenshtein,
11    pretty::Documentable,
12};
13use indoc::formatdoc;
14use itertools::Itertools;
15use miette::{Diagnostic, LabeledSpan};
16use ordinal::Ordinal;
17use owo_colors::{
18    OwoColorize,
19    Stream::{Stderr, Stdout},
20};
21use std::{collections::HashMap, fmt::Display, rc::Rc};
22use vec1::Vec1;
23
24#[derive(Debug, Clone, thiserror::Error)]
25#[error(
26    "I don't know some of the labels used in this expression. I've highlighted them just below."
27)]
28pub struct UnknownLabels {
29    pub unknown: Vec<Span>,
30    pub valid: Vec<String>,
31    pub supplied: Vec<String>,
32}
33
34impl Diagnostic for UnknownLabels {
35    fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
36        Some(Box::new(formatdoc! {
37            r#"Here's a list of all the (valid) labels that I know of:
38
39               {known_labels}"#
40            , known_labels = self.valid
41                .iter()
42                .map(|s| format!("─▶ {}", s.if_supports_color(Stdout, |s| s.yellow())))
43                .collect::<Vec<_>>()
44                .join("\n")
45        }))
46    }
47
48    fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
49        Some(Box::new(self.unknown.iter().map(|l| {
50            LabeledSpan::new_with_span(Some("?".to_string()), *l)
51        })))
52    }
53}
54
55#[derive(Debug, thiserror::Error, Diagnostic, Clone)]
56pub enum Error {
57    #[error("I discovered an {} chain with less than 2 expressions.", op.if_supports_color(Stdout, |s| s.purple()))]
58    #[diagnostic(code("illegal::logical_op_chain"))]
59    #[diagnostic(help(
60        "Logical {}/{} chains require at least 2 expressions. You are missing {}.",
61        "and".if_supports_color(Stdout, |s| s.purple()),
62        "or".if_supports_color(Stdout, |s| s.purple()),
63        missing
64    ))]
65    LogicalOpChainMissingExpr {
66        op: LogicalOpChainKind,
67        #[label("not enough operands")]
68        location: Span,
69        missing: u8,
70    },
71
72    #[error("I discovered a type cast from Data without an annotation.")]
73    #[diagnostic(code("illegal::type_cast"))]
74    #[diagnostic(help("Try adding an annotation...\n\n{}", format_suggestion(value)))]
75    CastDataNoAnn {
76        #[label("missing annotation")]
77        location: Span,
78        value: UntypedExpr,
79    },
80
81    #[error("I struggled to unify the types of two expressions.\n")]
82    #[diagnostic(url("https://aiken-lang.org/language-tour/primitive-types"))]
83    #[diagnostic(code("type_mismatch"))]
84    #[diagnostic(help("{}", suggest_unify(expected, given, situation, rigid_type_names)))]
85    CouldNotUnify {
86        #[label(
87            "expected type '{}'",
88            expected.to_pretty_with_names(rigid_type_names.clone(), 0),
89        )]
90        location: Span,
91        expected: Rc<Type>,
92        given: Rc<Type>,
93        situation: Option<UnifyErrorSituation>,
94        rigid_type_names: HashMap<u64, String>,
95    },
96
97    #[error("I almost got caught in an infinite cycle of type definitions.\n")]
98    #[diagnostic(url("https://aiken-lang.org/language-tour/custom-types#type-aliases"))]
99    #[diagnostic(code("cycle"))]
100    CyclicTypeDefinitions {
101        #[label(collection, "part of a cycle")]
102        cycle: Vec<Span>,
103    },
104
105    #[error("I found an incorrect usage of decorators.\n")]
106    #[diagnostic(code("decorators::validation"))]
107    #[diagnostic(help("{message}"))]
108    DecoratorValidation {
109        #[label("found here")]
110        location: Span,
111        message: String,
112    },
113
114    #[error("I found an incorrect usage of decorators.\n")]
115    #[diagnostic(code("decorators::validation"))]
116    #[diagnostic(help(
117        "All tags for a type must be unique. Pay attention to the order of the constructors.\nBy default a constructor's tag is it's order of appearance at the definition site, starting at 0."
118    ))]
119    DecoratorTagOverlap {
120        tag: usize,
121        #[label("found \"{tag}\" here")]
122        first: Span,
123        #[label("conflicts here")]
124        second: Span,
125    },
126
127    #[error("I found an incorrect usage of decorators.\n")]
128    #[diagnostic(code("decorators::conflict"))]
129    #[diagnostic(help("You cannot use these two decorators together"))]
130    ConflictingDecorators {
131        #[label("here")]
132        location: Span,
133        #[label("conflicts with")]
134        conflicting_location: Span,
135    },
136
137    #[error(
138        "I found two function arguments both called '{}'.\n",
139        label.if_supports_color(Stdout, |s| s.purple())
140    )]
141    #[diagnostic(code("duplicate::argument"))]
142    #[diagnostic(help(
143        "Function arguments cannot have the same name. You can use '{discard}' and numbers to distinguish between similar names.",
144        discard = "_".if_supports_color(Stdout, |s| s.yellow())
145    ))]
146    DuplicateArgument {
147        #[label("found here")]
148        location: Span,
149        #[label("found here again")]
150        duplicate_location: Span,
151        label: String,
152    },
153
154    #[error("I found two declarations for the constant '{}'.\n", name.purple())]
155    #[diagnostic(code("duplicate::constant"))]
156    #[diagnostic(help(
157        "Top-level constants of a same module cannot have the same name. You can use '{discard}' and numbers to distinguish between similar names.",
158        discard = "_".if_supports_color(Stdout, |s| s.yellow())
159    ))]
160    DuplicateConstName {
161        #[label("declared again here")]
162        location: Span,
163        #[label("declared here")]
164        previous_location: Span,
165        name: String,
166    },
167
168    #[error(
169        "I stumbled upon the field '{}' twice in a data-type definition.\n",
170        label.if_supports_color(Stdout, |s| s.purple())
171    )]
172    #[diagnostic(code("duplicate::field"))]
173    #[diagnostic(help(r#"Data-types must have fields with strictly different names. You can use '{discard}' and numbers to distinguish between similar names.
174Note that it is also possible to declare data-types with positional (nameless) fields only.
175
176For example:
177
178  ┍━━━━━━━━━━━━━━━━━━━━━━━
179  │ {keyword_pub} {keyword_type} {type_Point} {{
180  │   {variant_Point}({type_Int}, {type_Int}, {type_Int})
181  │ }}
182"#
183        , discard = "_".if_supports_color(Stdout, |s| s.yellow())
184        , keyword_pub = "pub".if_supports_color(Stdout, |s| s.bright_blue())
185        , keyword_type = "type".if_supports_color(Stdout, |s| s.yellow())
186        , type_Int = "Int".if_supports_color(Stdout, |s| s.green())
187        , type_Point = "Point".if_supports_color(Stdout, |s| s.green())
188        , variant_Point = "Point".if_supports_color(Stdout, |s| s.green())
189    ))]
190    DuplicateField {
191        #[label("found here")]
192        location: Span,
193        #[label("found here again")]
194        duplicate_location: Span,
195        label: String,
196    },
197
198    #[error(
199        "I noticed you were importing '{}' twice.\n",
200        name.if_supports_color(Stdout, |s| s.purple())
201    )]
202    #[diagnostic(code("duplicate::import"))]
203    #[diagnostic(help(r#"If you're trying to import two modules with identical names but from different packages, you'll need to use a named import.
204For example:
205
206╰─▶ {keyword_use} {import} {keyword_as} {named}
207
208Otherwise, just remove the redundant import."#
209        , keyword_use = "use".if_supports_color(Stdout, |s| s.bright_blue())
210        , keyword_as = "as".if_supports_color(Stdout, |s| s.bright_blue())
211        , import = module
212            .iter()
213            .map(|x| x.if_supports_color(Stdout, |s| s.purple()).to_string())
214            .collect::<Vec<_>>()
215            .join("/".if_supports_color(Stdout, |s| s.bold()).to_string().as_ref())
216        , named = module.join("_")
217    ))]
218    DuplicateImport {
219        #[label("also imported here as '{name}'")]
220        location: Span,
221        name: String,
222        module: Vec<String>,
223        #[label("imported here as '{name}'")]
224        previous_location: Span,
225    },
226
227    #[error(
228        "I discovered two top-level objects referred to as '{}'.\n",
229        name.if_supports_color(Stdout, |s| s.purple())
230    )]
231    #[diagnostic(code("duplicate::name"))]
232    #[diagnostic(help(
233        r#"Top-level definitions cannot have the same name, even if they refer to objects with different natures (e.g. function and test).
234
235You can use '{discard}' and numbers to distinguish between similar names.
236"#,
237        discard = "_".if_supports_color(Stdout, |s| s.yellow())
238    ))]
239    DuplicateName {
240        #[label("also defined here")]
241        location: Span,
242        #[label("originally defined here")]
243        previous_location: Span,
244        name: String,
245    },
246
247    #[error(
248        "I found two types declared with the same name: '{}'.\n",
249        name.if_supports_color(Stdout, |s| s.purple())
250    )]
251    #[diagnostic(code("duplicate::type"))]
252    #[diagnostic(help(
253        "Types cannot have the same top-level name. You {cannot} use '_' in types name, but you can use numbers to distinguish between similar names.",
254        cannot = "cannot".if_supports_color(Stdout, |s| s.red())
255    ))]
256    DuplicateTypeName {
257        #[label("also defined here")]
258        location: Span,
259        #[label("originally defined here")]
260        previous_location: Span,
261        name: String,
262    },
263
264    #[error(
265        "I realized the variable '{}' was mentioned more than once in an alternative pattern.\n",
266        name.if_supports_color(Stdout, |s| s.purple())
267    )]
268    #[diagnostic(url(
269        "https://aiken-lang.org/language-tour/control-flow#alternative-clause-patterns"
270    ))]
271    #[diagnostic(code("duplicate::pattern"))]
272    DuplicateVarInPattern {
273        #[label("duplicate identifier")]
274        location: Span,
275        name: String,
276    },
277
278    #[error(
279        "I tripped over an extra variable in an alternative pattern: {}.\n",
280        name.if_supports_color(Stdout, |s| s.purple())
281    )]
282    #[diagnostic(url(
283        "https://aiken-lang.org/language-tour/control-flow#alternative-clause-patterns"
284    ))]
285    #[diagnostic(code("unexpected::variable"))]
286    ExtraVarInAlternativePattern {
287        #[label("unexpected variable")]
288        location: Span,
289        name: String,
290    },
291
292    #[error("I caught an opaque type possibly breaking its abstraction boundary.\n")]
293    #[diagnostic(code("illegal::expect_on_opaque"))]
294    #[diagnostic(url("https://aiken-lang.org/language-tour/modules#opaque-types"))]
295    #[diagnostic(help(
296        "This expression is trying to convert something unknown into an opaque type. An opaque type is a data-type which hides its internal details; usually because it enforces some specific invariant on its internal structure. For example, you might define a {Natural} type that holds an {Integer} but ensures that it never gets negative.\n\nA direct consequence means that it isn't generally possible, nor safe, to turn *any* value into an opaque type. Instead, use the constructors and methods provided for lifting values into that opaque type while ensuring that any structural invariant is checked for.",
297        Natural = "Natural".if_supports_color(Stdout, |s| s.cyan()),
298        Integer = "Integer".if_supports_color(Stdout, |s| s.cyan()),
299    ))]
300    ExpectOnOpaqueType {
301        #[label("reckless opaque cast")]
302        location: Span,
303    },
304
305    #[error("I found a type definition that has a function type in it. This is not allowed.\n")]
306    #[diagnostic(code("illegal::function_in_type"))]
307    #[diagnostic(help(
308        "Data-types can't hold functions. If you want to define method-like functions, group the type definition and the methods under a common namespace in a standalone module."
309    ))]
310    FunctionTypeInData {
311        #[label("non-serialisable inhabitants")]
312        location: Span,
313    },
314
315    #[error("I found a type definition that has unsupported inhabitants.\n")]
316    #[diagnostic(code("illegal::type_in_data"))]
317    #[diagnostic(help(
318        r#"Data-types cannot contain values of type {type_info} because they aren't serialisable into a Plutus Data. Yet this is necessary for inhabitants of compound structures like {List}, {Tuple} or {Fuzzer}."#,
319        type_info = tipo.to_pretty(0).if_supports_color(Stdout, |s| s.red()),
320        List = "List".if_supports_color(Stdout, |s| s.cyan()),
321        Tuple = "Tuple".if_supports_color(Stdout, |s| s.cyan()),
322        Fuzzer = "Fuzzer".if_supports_color(Stdout, |s| s.cyan()),
323    ))]
324    IllegalTypeInData {
325        #[label("non-serialisable inhabitants")]
326        location: Span,
327        tipo: Rc<Type>,
328    },
329
330    #[error("I noticed an inadequate use of '=='.\n")]
331    #[diagnostic(code("illegal::comparison"))]
332    #[diagnostic(help(
333        r#"I can compare any value that is serializable to {Data}. This excludes values that are functions, {Fuzzer} or {MillerLoopResult} for example."#,
334        Data = "Data".if_supports_color(Stdout, |s| s.cyan()),
335        Fuzzer = "Fuzzer".if_supports_color(Stdout, |s| s.cyan()),
336        MillerLoopResult = "MillerLoopResult".if_supports_color(Stdout, |s| s.cyan()),
337    ))]
338    IllegalComparison {
339        #[label("non-serialisable operands")]
340        location: Span,
341    },
342
343    #[error("I found a discarded expression not bound to a variable.\n")]
344    #[diagnostic(code("implicit_discard"))]
345    #[diagnostic(help(
346        "A function can contain a sequence of expressions. However, any expression but the last one must be assigned to a variable using the {keyword_let} keyword. If you really wish to discard an expression that is unused, you can assign it to '{discard}'.",
347        keyword_let = "let".if_supports_color(Stdout, |s| s.yellow()),
348        discard = "_".if_supports_color(Stdout, |s| s.yellow())
349    ))]
350    ImplicitlyDiscardedExpression {
351        #[label("implicitly discarded")]
352        location: Span,
353    },
354
355    #[error("I notice a benchmark definition without any argument.\n")]
356    #[diagnostic(url("https://aiken-lang.org/language-tour/bench"))]
357    #[diagnostic(code("arity::bench"))]
358    IncorrectBenchmarkArity {
359        #[label("must have exactly one argument")]
360        location: Span,
361    },
362
363    #[error(
364        "I saw {} field{} in a context where there should be {}.\n",
365        given.if_supports_color(Stdout, |s| s.purple()),
366        if *given <= 1 { "" } else { "s"},
367        expected.if_supports_color(Stdout, |s| s.purple()),
368    )]
369    #[diagnostic(url("https://aiken-lang.org/language-tour/custom-types"))]
370    #[diagnostic(code("arity::constructor"))]
371    IncorrectFieldsArity {
372        #[label("{}", if given < expected { "missing fields" } else { "extraneous fields" })]
373        location: Span,
374        expected: usize,
375        given: usize,
376        labels: Vec<String>,
377    },
378
379    #[error(
380        "I saw a function or constructor that expects {} arguments be called with {} arguments.\n",
381        expected.if_supports_color(Stdout, |s| s.purple()),
382        given.if_supports_color(Stdout, |s| s.purple())
383    )]
384    #[diagnostic(url("https://aiken-lang.org/language-tour/functions#named-functions"))]
385    #[diagnostic(code("arity::invoke"))]
386    #[diagnostic(help(r#"Functions (and constructors) must always be called with all their arguments (comma-separated, between brackets).
387
388Here, the function or constructor needs {expected} arguments.
389
390Note that Aiken supports argument capturing using '{discard}' as placeholder for arguments that aren't yet defined. This is like currying in some other languages.
391
392For example, imagine the following function:
393
394  ┍━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
395  │ {keyword_fn} add(x: {type_Int}, y: {type_Int}) -> {type_Int}
396
397From there, you can define 'increment', a function that takes a single argument and adds one to it, as such:
398
399  ┍━━━━━━━━━━━━━━━━━━━━━━━━━━
400  │ {keyword_let} increment = add(1, _)
401"#
402        , discard = "_".if_supports_color(Stdout, |s| s.yellow())
403        , expected = expected.if_supports_color(Stdout, |s| s.purple())
404        , keyword_fn = "fn".if_supports_color(Stdout, |s| s.yellow())
405        , keyword_let = "let".if_supports_color(Stdout, |s| s.yellow())
406        , type_Int = "Int".if_supports_color(Stdout, |s| s.green())
407    ))]
408    IncorrectFunctionCallArity {
409        #[label("{}", if given < expected { "missing arguments" } else { "extraneous arguments" })]
410        location: Span,
411        expected: usize,
412        given: usize,
413    },
414
415    #[error(
416        "I saw a pattern on a constructor that has {} field(s) be matched with {} argument(s).\n",
417        expected.if_supports_color(Stdout, |s| s.purple()),
418        given.len().if_supports_color(Stdout, |s| s.purple())
419    )]
420    #[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#matching"))]
421    #[diagnostic(code("arity::pattern"))]
422    #[diagnostic(help(
423        "When pattern-matching on constructors, you must either match the exact number of fields, or use the spread operator '{spread}'. Note that unused fields must be discarded by prefixing their name with '{discard}'.",
424        discard = "_".if_supports_color(Stdout, |s| s.yellow()),
425        spread = "..".if_supports_color(Stdout, |s| s.yellow()),
426    ))]
427    IncorrectPatternArity {
428        #[label("{}", suggest_pattern(*expected, name, given, module, *is_record).unwrap_or_default())]
429        location: Span,
430        expected: usize,
431        given: Vec<CallArg<UntypedPattern>>,
432        name: String,
433        module: Option<Namespace>,
434        is_record: bool,
435    },
436
437    #[error(
438        "I saw a pattern on a {}-tuple be matched into a {}-tuple.\n",
439        expected.if_supports_color(Stdout, |s| s.purple()),
440        given.if_supports_color(Stdout, |s| s.purple())
441    )]
442    #[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#destructuring"))]
443    #[diagnostic(code("arity::tuple"))]
444    #[diagnostic(help(
445        "When pattern matching on a tuple, you must match all of its elements. Note that unused fields must be discarded by prefixing their name with '{discard}'.",
446        discard = "_".if_supports_color(Stdout, |s| s.yellow())
447    ))]
448    IncorrectTupleArity {
449        #[label("{}", if given < expected { "missing elements" } else { "extraneous elements" })]
450        location: Span,
451        expected: usize,
452        given: usize,
453    },
454
455    #[error(
456        "I noticed a generic data-type with {} type parameters instead of {}.\n",
457        given.if_supports_color(Stdout, |s| s.purple()),
458        expected.if_supports_color(Stdout, |s| s.purple()),
459    )]
460    #[diagnostic(url("https://aiken-lang.org/language-tour/custom-types#generics"))]
461    #[diagnostic(code("arity::generic"))]
462    #[diagnostic(help(
463        "{}",
464        if *expected == 0 {
465            format!(
466                r#"Data-types without generic parameters should be written without chevrons.
467Perhaps, try the following:
468
469╰─▶  {suggestion}"#,
470                suggestion = suggest_generic(name, *expected)
471            )
472        } else {
473            format!(
474                r#"Data-types that are generic in one or more types must be written with all their generic types in type annotations. Generic types must be indicated between chevrons '{chevron_left}' and '{chevron_right}'.
475Perhaps, try the following:
476
477╰─▶  {suggestion}"#
478                , chevron_left = "<".if_supports_color(Stdout, |s| s.yellow())
479                , chevron_right = ">".if_supports_color(Stdout, |s| s.yellow())
480                , suggestion = suggest_generic(name, *expected)
481            )
482        }
483    ))]
484    IncorrectTypeArity {
485        #[label("incorrect generic arity")]
486        location: Span,
487        name: String,
488        expected: usize,
489        given: usize,
490    },
491
492    #[error(
493      "I realized the module '{}' contains the keyword '{}', which is forbidden.\n",
494      name.if_supports_color(Stdout, |s| s.purple()),
495      keyword.if_supports_color(Stdout, |s| s.purple()),
496    )]
497    #[diagnostic(url("https://aiken-lang.org/language-tour/modules"))]
498    #[diagnostic(code("illegal::module_name"))]
499    #[diagnostic(help(r#"You cannot use keywords as part of a module path name. As a quick reminder, here's a list of all the keywords (and thus, of invalid module path names):
500
501    as, expect, check, const, else, fn, if, is, let, opaque, pub, test, todo, trace, type, use, when"#))]
502    KeywordInModuleName { name: String, keyword: String },
503
504    #[error("I discovered a block which is ending with an assignment.\n")]
505    #[diagnostic(url("https://aiken-lang.org/language-tour/functions#named-functions"))]
506    #[diagnostic(code("illegal::return"))]
507    #[diagnostic(help(r#"In Aiken, code blocks (such as function bodies) must return an explicit result in the form of an expression. While assignments are technically speaking expressions, they aren't allowed to be the last expression of a function because they convey a different meaning and this could be error-prone.
508
509If you really meant to return that last expression, try to replace it with the following:
510
511{sample}"#
512        , sample = format_suggestion(expr)
513    ))]
514    LastExpressionIsAssignment {
515        #[label("let-binding as last expression")]
516        location: Span,
517        expr: expr::UntypedExpr,
518        patterns: Vec1<AssignmentPattern>,
519        kind: UntypedAssignmentKind,
520    },
521
522    #[error(
523        "I found a missing variable in an alternative pattern: {}.\n",
524        name.if_supports_color(Stdout, |s| s.purple())
525    )]
526    #[diagnostic(url(
527        "https://aiken-lang.org/language-tour/control-flow#alternative-clause-patterns"
528    ))]
529    #[diagnostic(code("missing::variable"))]
530    MissingVarInAlternativePattern {
531        #[label("missing case")]
532        location: Span,
533        name: String,
534    },
535
536    #[error("I tripped over an attempt to access elements on something that isn't indexable.\n")]
537    #[diagnostic(url("https://aiken-lang.org/language-tour/primitive-types#tuples"))]
538    #[diagnostic(code("illegal::indexable"))]
539    #[diagnostic(help(
540        r#"Because you used an ordinal index on an element, I assumed it had to be a tuple or a pair but instead I found something of type:
541
542╰─▶ {type_info}"#,
543        type_info = tipo.to_pretty(0).if_supports_color(Stdout, |s| s.red())
544    ))]
545    NotIndexable {
546        #[label("not indexable")]
547        location: Span,
548        tipo: Rc<Type>,
549    },
550
551    #[error("{}\n", if *is_let {
552          "I noticed an incomplete single-pattern matching a value with more than one pattern.".to_string()
553      } else {
554          format!(
555              "I realized that a given '{keyword_when}/{keyword_is}' expression is non-exhaustive.",
556              keyword_is = "is".if_supports_color(Stdout, |s| s.purple()),
557              keyword_when = "when".if_supports_color(Stdout, |s| s.purple())
558          )
559      }
560    )]
561    #[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#matching"))]
562    #[diagnostic(code("non_exhaustive_pattern_match"))]
563    #[diagnostic(help(r#"Let bindings and when clauses must be exhaustive -- that is, they must cover all possible cases of the type they match. In {keyword_when}/{keyword_is} pattern-match, it is recommended to have an explicit branch for each constructor as it prevents future silly mistakes when adding new constructors to a type. However, you can also use the wildcard '{discard}' as a last branch to match any remaining result.
564
565In this particular instance, the following cases are unmatched:
566
567{missing}"#
568        , discard = "_".if_supports_color(Stdout, |s| s.yellow())
569        , keyword_is = "is".if_supports_color(Stdout, |s| s.purple())
570        , keyword_when = "when".if_supports_color(Stdout, |s| s.purple())
571        , missing = unmatched
572            .iter()
573            .map(|s| format!("─▶ {s}"))
574            .collect::<Vec<_>>()
575            .join("\n")
576    ))]
577    NotExhaustivePatternMatch {
578        #[label("{}", if *is_let { "use when/is" } else { "non-exhaustive" })]
579        location: Span,
580        unmatched: Vec<String>,
581        is_let: bool,
582    },
583
584    #[error("I tripped over a call attempt on something that isn't a function.\n")]
585    #[diagnostic(code("illegal::invoke"))]
586    #[diagnostic(help(
587        r#"It seems like you're trying to call something that isn't a function. I am inferring the following type:
588
589╰─▶ {inference}"#,
590        inference = tipo.to_pretty(0)
591    ))]
592    NotFn {
593        #[label("not a function")]
594        location: Span,
595        tipo: Rc<Type>,
596    },
597
598    #[error("I discovered a positional argument after a label argument.\n")]
599    #[diagnostic(url("https://aiken-lang.org/language-tour/functions#labeled-arguments"))]
600    #[diagnostic(code("unexpected::positional_argument"))]
601    #[diagnostic(help(r#"You can mix positional and labeled arguments, but you must put all positional arguments (i.e. without label) at the front.
602
603To fix this, you'll need to either turn that argument as a labeled argument, or make the next one positional."#))]
604    PositionalArgumentAfterLabeled {
605        #[label("by position")]
606        location: Span,
607        #[label("by label")]
608        labeled_arg_location: Span,
609    },
610
611    #[error("I caught a private value trying to escape.\n")]
612    #[diagnostic(url("https://aiken-lang.org/language-tour/modules"))]
613    #[diagnostic(code("private_leak"))]
614    #[diagnostic(help(r#"I found a public value that is making use of a private type. This would prevent other modules from actually using that value because they wouldn't know what this type refer to.
615
616The culprit is:
617
618{type_info}
619
620Maybe you meant to turn it public using the '{keyword_pub}' keyword?"#
621        , type_info = if leaked.alias().is_some() {
622            let alias = leaked.to_pretty(0).if_supports_color(Stdout, |s| s.magenta()).to_string();
623            format!(
624                "{} aliased as {alias}",
625                leaked.clone().set_alias(None).to_pretty(4).if_supports_color(Stdout, |s| s.red()),
626            )
627        } else {
628            leaked.to_pretty(4).if_supports_color(Stdout, |s| s.red()).to_string()
629        }
630        , keyword_pub = "pub".if_supports_color(Stdout, |s| s.bright_blue())
631    ))]
632    PrivateTypeLeak {
633        #[label("private type leak")]
634        location: Span,
635        leaked: Type,
636        #[label("defined here")]
637        leaked_location: Option<Span>,
638    },
639
640    #[error(
641        "{}\n",
642        format!(
643            "I discovered a '{keyword_when}/{keyword_is}' expression with a redundant pattern.",
644            keyword_is = "is".if_supports_color(Stdout, |s| s.purple()),
645            keyword_when = "when".if_supports_color(Stdout, |s| s.purple())
646        )
647    )]
648    #[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#matching"))]
649    #[diagnostic(code("redundant_pattern_match"))]
650    #[diagnostic(help("Double check these patterns and then remove one of the clauses."))]
651    RedundantMatchClause {
652        #[label("first found here")]
653        original: Option<Span>,
654        #[label("redundant")]
655        redundant: Span,
656    },
657
658    #[error("I couldn't figure out the type of a record you're trying to access.\n")]
659    #[diagnostic(url(
660        "https://aiken-lang.org/language-tour/variables-and-constants#type-annotations"
661    ))]
662    #[diagnostic(code("unknown::record_access"))]
663    #[diagnostic(help(r#"I do my best to infer types of any expression; yet sometimes I need help (don't we all?).
664
665Take for example the following expression:
666
667   ┍━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
668   │ {keyword_let} foo = {keyword_fn}(x) {{ x.transaction }}
669
670At this stage, I can't quite figure out whether 'x' has indeed a field 'transaction', because I don't know what the type of 'x' is.
671You can help me by providing a type-annotation for 'x', as such:
672
673   ┍━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
674   │ {keyword_let} foo = {keyword_fn}(x: {type_ScriptContext}) {{ x.transaction }}
675"#
676        , keyword_fn = "fn".if_supports_color(Stdout, |s| s.yellow())
677        , keyword_let = "let".if_supports_color(Stdout, |s| s.yellow())
678        , type_ScriptContext = "ScriptContext".if_supports_color(Stdout, |s| s.green())
679    ))]
680    RecordAccessUnknownType {
681        #[label("annotation needed")]
682        location: Span,
683    },
684
685    #[error("I tripped over an invalid constructor in a record update.\n")]
686    #[diagnostic(url("https://aiken-lang.org/language-tour/custom-types#record-updates"))]
687    #[diagnostic(code("illegal::record_update"))]
688    RecordUpdateInvalidConstructor {
689        #[label("invalid constructor")]
690        location: Span,
691    },
692
693    #[error("I almost got caught in an endless loop while inferring a recursive type.\n")]
694    #[diagnostic(url("https://aiken-lang.org/language-tour/custom-types#type-annotations"))]
695    #[diagnostic(code("missing::type_annotation"))]
696    #[diagnostic(help(
697        "I have several aptitudes, but inferring recursive types isn't one them. It is still possible to define recursive types just fine, but I will need a little help in the form of type annotation to infer their types should they show up."
698    ))]
699    RecursiveType {
700        #[label("infinite recursion")]
701        location: Span,
702    },
703
704    #[error(
705        "I discovered an attempt to access the {} element of a {}-tuple.\n",
706        Ordinal(*index + 1).to_string().if_supports_color(Stdout, |s| s.purple()),
707        size.if_supports_color(Stdout, |s| s.purple())
708    )]
709    #[diagnostic(url("https://aiken-lang.org/language-tour/primitive-types#tuples"))]
710    #[diagnostic(code("invalid::tuple_index"))]
711    TupleIndexOutOfBound {
712        #[label("out of bounds")]
713        location: Span,
714        index: usize,
715        size: usize,
716    },
717
718    #[error(
719        "I discovered an attempt to access the {} element of a {}.\n",
720        Ordinal(*index + 1).to_string().if_supports_color(Stdout, |s| s.purple()),
721        "Pair".if_supports_color(Stdout, |s| s.bright_blue()).if_supports_color(Stdout, |s| s.bold()),
722    )]
723    #[diagnostic(url("https://aiken-lang.org/language-tour/primitive-types#pairs"))]
724    #[diagnostic(code("invalid::pair_index"))]
725    PairIndexOutOfBound {
726        #[label("out of bounds")]
727        location: Span,
728        index: usize,
729    },
730
731    #[error(
732        "I tripped over the following labeled argument: {}.\n",
733        label.if_supports_color(Stdout, |s| s.purple())
734    )]
735    #[diagnostic(url("https://aiken-lang.org/language-tour/functions#labeled-arguments"))]
736    #[diagnostic(code("unexpected::module_name"))]
737    UnexpectedLabeledArg {
738        #[label("unexpected labeled args")]
739        location: Span,
740        label: String,
741    },
742
743    #[error(
744        "I tripped over the following labeled argument: {}.\n",
745        label.if_supports_color(Stdout, |s| s.purple())
746    )]
747    #[diagnostic(url("https://aiken-lang.org/language-tour/custom-types#named-accessors"))]
748    #[diagnostic(code("unexpected::labeled_argument"))]
749    #[diagnostic(help(r#"The constructor '{constructor}' does not have any labeled field. Its fields must therefore be matched only by position.
750
751Perhaps, try the following:
752
753╰─▶  {suggestion}
754"#
755        , constructor = name
756            .if_supports_color(Stdout, |s| s.bright_blue())
757            .if_supports_color(Stdout, |s| s.bold())
758        , suggestion = suggest_constructor_pattern(name, args, module, *spread_location)
759    ))]
760    UnexpectedLabeledArgInPattern {
761        #[label("unexpected labeled arg")]
762        location: Span,
763        label: Box<String>,
764        name: Box<String>,
765        args: Vec<CallArg<UntypedPattern>>,
766        module: Option<Namespace>,
767        spread_location: Option<Span>,
768    },
769
770    #[error("I discovered a regular let assignment with multiple patterns.\n")]
771    #[diagnostic(code("unexpected::multi_pattern_assignment"))]
772    #[diagnostic(help(
773        "Did you mean to use backpassing syntax with {}?",
774        "<-".if_supports_color(Stdout, |s| s.purple())
775    ))]
776    UnexpectedMultiPatternAssignment {
777        #[label("unexpected")]
778        location: Span,
779        #[label("<-")]
780        arrow: Span,
781    },
782
783    #[error("I tripped over some unknown labels in a pattern or function.\n")]
784    #[diagnostic(code("unknown::labels"))]
785    UnknownLabels(#[related] Vec<UnknownLabels>),
786
787    #[error(
788        "I stumbled upon a reference to an unknown module: '{}'\n",
789        name.if_supports_color(Stdout, |s| s.purple())
790    )]
791    #[diagnostic(code("unknown::module"))]
792    #[diagnostic(help(
793        "{}",
794        suggest_neighbor(name, known_modules.iter(), "Did you forget to add a package as dependency?")
795    ))]
796    UnknownModule {
797        #[label("unknown module")]
798        location: Span,
799        name: String,
800        known_modules: Vec<String>,
801    },
802
803    #[error(
804        "I couldn't find any module for the environment: '{}'\n",
805        name.if_supports_color(Stdout, |s| s.purple())
806    )]
807    #[diagnostic(code("unknown::environment"))]
808    #[diagnostic(help(
809        "{}{}",
810        if known_environments.is_empty() {
811            String::new()
812        } else {
813            format!(
814                "I know about the following environments:\n{}\n\n",
815                known_environments
816                    .iter()
817                    .map(|s| format!("─▶ {}", s.if_supports_color(Stdout, |s| s.purple())))
818                    .collect::<Vec<_>>()
819                    .join("\n")
820            )
821        },
822        suggest_neighbor(name, known_environments.iter(), "Did you forget to define this environment?")
823    ))]
824    UnknownEnvironment {
825        name: String,
826        known_environments: Vec<String>,
827    },
828
829    #[error(
830        "I found an unknown import '{}' from module '{}'.\n",
831        name.if_supports_color(Stdout, |s| s.purple()),
832        module_name.if_supports_color(Stdout, |s| s.purple())
833    )]
834    #[diagnostic(code("unknown::module_field"))]
835    #[diagnostic(help(
836        "{}",
837        suggest_neighbor(
838            name,
839            value_constructors.iter().chain(type_constructors),
840            &suggest_make_public()
841        )
842    ))]
843    UnknownModuleField {
844        #[label("unknown import")]
845        location: Span,
846        name: String,
847        module_name: String,
848        value_constructors: Vec<String>,
849        type_constructors: Vec<String>,
850    },
851
852    #[error(
853        "I looked for '{}' in module '{}' but couldn't find it.\n",
854        name.if_supports_color(Stdout, |s| s.purple()),
855        module_name.if_supports_color(Stdout, |s| s.purple())
856    )]
857    #[diagnostic(code("unknown::module_type"))]
858    #[diagnostic(help(
859        "{}",
860        suggest_neighbor(
861            name,
862            type_constructors.iter(),
863            &suggest_make_public()
864        )
865    ))]
866    UnknownModuleType {
867        #[label("not exported?")]
868        location: Span,
869        name: String,
870        module_name: String,
871        type_constructors: Vec<String>,
872    },
873
874    #[error("I looked for '{}' in '{}' but couldn't find it.\n",
875        name.if_supports_color(Stdout, |s| s.purple()),
876        module_name.if_supports_color(Stdout, |s| s.purple())
877    )]
878    #[diagnostic(code("unknown::module_value"))]
879    #[diagnostic(help(
880        "{}",
881        if ["mk_nil_data", "mk_pair_data", "mk_nil_pair_data"].contains(&.name.as_str()) {
882            format!(
883                "It seems like you're looking for a builtin function that has been (recently) renamed. Sorry about that, but take notes of the new names of the following functions:\n\n{:<16} -> {}\n{:<16} -> {}\n{:<16} -> {}",
884                "mk_nil_data".if_supports_color(Stderr, |s| s.red()),
885                "new_list".if_supports_color(Stderr, |s| s.green()),
886                "mk_pair_data".if_supports_color(Stderr, |s| s.red()),
887                "new_pair".if_supports_color(Stderr, |s| s.green()),
888                "mk_nil_pair_data".if_supports_color(Stderr, |s| s.red()),
889                "new_pairs".if_supports_color(Stderr, |s| s.green()),
890            )
891        } else {
892            suggest_neighbor(
893                name,
894                value_constructors.iter(),
895                &suggest_make_public()
896            )
897        }
898    ))]
899    UnknownModuleValue {
900        #[label("not exported by {module_name}?")]
901        location: Span,
902        name: String,
903        module_name: String,
904        value_constructors: Vec<String>,
905    },
906
907    #[error(
908      "I looked for the field '{}' in a record of type '{}' but couldn't find it.\n",
909      label.if_supports_color(Stdout, |s| s.purple()),
910      typ.to_pretty(0).if_supports_color(Stdout, |s| s.purple()),
911    )]
912    #[diagnostic(code("unknown::record_field"))]
913    #[diagnostic(help(
914        "{}",
915        suggest_neighbor(label, fields.iter(), "Did you forget to make it public?\nNote also that record access is only supported on types with a single constructor.")
916    ))]
917    UnknownRecordField {
918        #[label("unknown field")]
919        location: Span,
920        typ: Rc<Type>,
921        label: String,
922        fields: Vec<String>,
923        situation: Option<UnknownRecordFieldSituation>,
924    },
925
926    #[error("I found a reference to an unknown type.\n")]
927    #[diagnostic(code("unknown::type"))]
928    #[diagnostic(help(
929        "{}",
930        suggest_neighbor(name, types.iter(), "Did you forget to import it?")
931    ))]
932    UnknownType {
933        #[label("unknown type")]
934        location: Span,
935        name: String,
936        types: Vec<String>,
937    },
938
939    #[error(
940        "I found a reference to an unknown data-type constructor: '{}'.\n",
941        name.if_supports_color(Stdout, |s| s.purple())
942    )]
943    #[diagnostic(code("unknown::type_constructor"))]
944    #[diagnostic(help(
945        "{}",
946        suggest_neighbor(name, constructors.iter(), &suggest_import_constructor())
947    ))]
948    UnknownTypeConstructor {
949        #[label("unknown constructor")]
950        location: Span,
951        name: String,
952        constructors: Vec<String>,
953    },
954
955    #[error("I found a reference to an unknown variable.\n")]
956    #[diagnostic(code("unknown::variable"))]
957    #[diagnostic(help(
958        "{}",
959        suggest_neighbor(
960            name,
961            variables.iter(),
962            "Did you forget to import it?",
963        )
964    ))]
965    UnknownVariable {
966        #[label("unknown variable")]
967        location: Span,
968        name: String,
969        variables: Vec<String>,
970    },
971
972    #[error("I discovered a redundant spread operator.\n")]
973    #[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#destructuring"))]
974    #[diagnostic(code("unexpected::spread_operator"))]
975    #[diagnostic(help(r#"The spread operator comes in handy when matching on some fields of a constructor. However, here you've matched all {arity} fields of the constructor which makes the spread operator redundant.
976
977The best thing to do from here is to remove it."#))]
978    UnnecessarySpreadOperator {
979        #[label("unnecessary spread")]
980        location: Span,
981        arity: usize,
982    },
983
984    #[error("I tripped over a record-update on a data-type with more than one constructor.\n")]
985    #[diagnostic(url("https://aiken-lang.org/language-tour/custom-types#record-updates"))]
986    #[diagnostic(code("illegal::record_update"))]
987    UpdateMultiConstructorType {
988        #[label("more than one constructor")]
989        location: Span,
990    },
991
992    #[error(
993        "I discovered an attempt to import a validator module in a library: '{}'\n",
994        name.if_supports_color(Stdout, |s| s.purple())
995    )]
996    #[diagnostic(code("illegal::import"))]
997    #[diagnostic(help(
998        "If you are trying to share code defined in a validator then move it to a library module under {}.\nIf, however, you are trying to import a validator for testing, make sure that your test module doesn't export any definition using the {} keyword.",
999        "lib/".if_supports_color(Stdout, |s| s.purple()),
1000        "pub".if_supports_color(Stdout, |s| s.cyan())
1001    ))]
1002    ValidatorImported {
1003        #[label("imported validator")]
1004        location: Span,
1005        name: String,
1006    },
1007
1008    #[error(
1009        "A validator must return {}.\n",
1010        "Bool"
1011            .if_supports_color(Stdout, |s| s.bright_blue())
1012            .if_supports_color(Stdout, |s| s.bold())
1013    )]
1014    #[diagnostic(code("illegal::validator_return_type"))]
1015    #[diagnostic(help(r#"While analyzing the return type of your validator, I found it to be:
1016
1017╰─▶ {signature}
1018
1019...but I expected this to be a {type_Bool}. If I am inferring the wrong type, try annotating the validator's return type with Bool"#
1020        , type_Bool = "Bool"
1021            .if_supports_color(Stdout, |s| s.bright_blue())
1022            .if_supports_color(Stdout, |s| s.bold())
1023        , signature = return_type.to_pretty(0).if_supports_color(Stdout, |s| s.red())
1024    ))]
1025    ValidatorMustReturnBool {
1026        #[label("invalid return type")]
1027        location: Span,
1028        return_type: Rc<Type>,
1029    },
1030
1031    #[error("Validators require at least 2 arguments and at most 3 arguments.\n")]
1032    #[diagnostic(code("illegal::validator_arity"))]
1033    #[diagnostic(help(
1034        "Please {}. If you don't need one of the required arguments use an underscore (e.g. `_datum`).",
1035        if *count < *expected {
1036            let missing = expected - count;
1037
1038            let mut arguments = "argument".to_string();
1039
1040            if missing > 1 {
1041                arguments.push('s');
1042            }
1043
1044            format!(
1045                "add the {} missing {arguments}",
1046                missing.to_string().if_supports_color(Stdout, |s| s.yellow()),
1047            )
1048        } else {
1049            let extra = count - expected;
1050
1051            let mut arguments = "argument".to_string();
1052
1053            if extra > 1 {
1054                arguments.push('s');
1055            }
1056
1057            format!(
1058                "remove the {} extra {arguments}",
1059                extra.to_string().if_supports_color(Stdout, |s| s.yellow()),
1060            )
1061        }
1062    ))]
1063    IncorrectValidatorArity {
1064        count: u32,
1065        expected: u32,
1066        #[label("{} arguments", if count < expected { "not enough" } else { "too many" })]
1067        location: Span,
1068    },
1069
1070    #[error("I caught a test with too many arguments.\n")]
1071    #[diagnostic(code("illegal::test::arity"))]
1072    #[diagnostic(help(
1073        "Tests are allowed to have 0 or 1 argument, but no more. Here I've found a test definition with {count} arguments. If you need to provide multiple values to a test, use a Record or a Tuple.",
1074    ))]
1075    IncorrectTestArity {
1076        count: usize,
1077        #[label("too many arguments")]
1078        location: Span,
1079    },
1080
1081    #[error("I caught a test with an illegal return type.\n")]
1082    #[diagnostic(code("illegal::test::return"))]
1083    #[diagnostic(help(
1084        "Tests must return either {Bool} or {Void}. Note that `expect` assignment are implicitly typed {Void} (and thus, may be the last expression of a test).",
1085        Bool = "Bool".if_supports_color(Stderr, |s| s.cyan()),
1086        Void = "Void".if_supports_color(Stderr, |s| s.cyan()),
1087    ))]
1088    IllegalTestType {
1089        #[label("expected Bool or Void")]
1090        location: Span,
1091    },
1092
1093    #[error("I choked on a generic type left in an outward-facing interface.\n")]
1094    #[diagnostic(code("illegal::generic_in_abi"))]
1095    #[diagnostic(help(
1096        "Elements of the outer-most parts of a project, such as a validator, constants or a property-based test, must be fully instantiated. That means they can no longer carry unbound or generic variables. The type must be fully-known at this point since many structural validation must occur to ensure a safe boundary between the on-chain and off-chain worlds."
1097    ))]
1098    GenericLeftAtBoundary {
1099        #[label("unbound generic at boundary")]
1100        location: Span,
1101    },
1102
1103    #[error("Cannot infer caller without inferring callee first")]
1104    MustInferFirst {
1105        function: UntypedFunction,
1106        location: Span,
1107    },
1108
1109    #[error("I found a validator handler referring to an unknown purpose.\n")]
1110    #[diagnostic(code("unknown::purpose"))]
1111    #[diagnostic(help(
1112        "Handler must be named after a known purpose. Here is a list of available purposes:\n{}",
1113        available_purposes
1114          .iter()
1115          .map(|p| format!("-> {}", p.if_supports_color(Stdout, |s| s.green())))
1116          .join("\n")
1117    ))]
1118    UnknownPurpose {
1119        #[label("unknown purpose")]
1120        location: Span,
1121        available_purposes: Vec<String>,
1122    },
1123
1124    #[error("I could not find an appropriate handler in the validator definition.\n")]
1125    #[diagnostic(code("unknown::handler"))]
1126    #[diagnostic(help(
1127        "When referring to a validator handler via record access, you must refer to one of the declared handlers{}{}",
1128        if available_handlers.is_empty() { "." } else { ":\n" },
1129        available_handlers
1130          .iter()
1131          .map(|p| format!("-> {}", p.if_supports_color(Stdout, |s| s.green())))
1132          .join("\n")
1133    ))]
1134    UnknownValidatorHandler {
1135        #[label("unknown validator handler")]
1136        location: Span,
1137        available_handlers: Vec<String>,
1138    },
1139
1140    #[error("I caught an extraneous fallback handler in an already exhaustive validator.\n")]
1141    #[diagnostic(code("extraneous::fallback"))]
1142    #[diagnostic(help(
1143        "Validator handlers must be exhaustive and either cover all purposes, or provide a fallback handler. Here, you have successfully covered all script purposes with your handler, but left an extraneous fallback branch. I cannot let that happen, but removing it for you would probably be deemed rude. So please, remove the fallback."
1144    ))]
1145    UnexpectedValidatorFallback {
1146        #[label("redundant fallback handler")]
1147        fallback: Span,
1148    },
1149
1150    #[error("I was stopped by a suspicious field access chain.\n")]
1151    #[diagnostic(code("invalid::field_access"))]
1152    #[diagnostic(help(
1153        "It seems like you've got things mixed up a little here? You can only access fields exported by modules or, by types within those modules. Double-check the culprit field access chain, there's likely something wrong about it."
1154    ))]
1155    InvalidFieldAccess {
1156        #[label("invalid field access")]
1157        location: Span,
1158    },
1159}
1160
1161impl ExtraData for Error {
1162    fn extra_data(&self) -> Option<String> {
1163        match self {
1164            Error::CastDataNoAnn { .. }
1165            | Error::CouldNotUnify { .. }
1166            | Error::CyclicTypeDefinitions { .. }
1167            | Error::DuplicateArgument { .. }
1168            | Error::DuplicateConstName { .. }
1169            | Error::DuplicateField { .. }
1170            | Error::DuplicateImport { .. }
1171            | Error::DuplicateName { .. }
1172            | Error::DuplicateTypeName { .. }
1173            | Error::DuplicateVarInPattern { .. }
1174            | Error::ExtraVarInAlternativePattern { .. }
1175            | Error::FunctionTypeInData { .. }
1176            | Error::IllegalTypeInData { .. }
1177            | Error::IllegalComparison { .. }
1178            | Error::ImplicitlyDiscardedExpression { .. }
1179            | Error::IncorrectFieldsArity { .. }
1180            | Error::IncorrectFunctionCallArity { .. }
1181            | Error::IncorrectPatternArity { .. }
1182            | Error::IncorrectTupleArity { .. }
1183            | Error::IncorrectTypeArity { .. }
1184            | Error::IncorrectValidatorArity { .. }
1185            | Error::KeywordInModuleName { .. }
1186            | Error::LastExpressionIsAssignment { .. }
1187            | Error::LogicalOpChainMissingExpr { .. }
1188            | Error::MissingVarInAlternativePattern { .. }
1189            | Error::NotIndexable { .. }
1190            | Error::NotExhaustivePatternMatch { .. }
1191            | Error::NotFn { .. }
1192            | Error::PositionalArgumentAfterLabeled { .. }
1193            | Error::RecordAccessUnknownType { .. }
1194            | Error::RecordUpdateInvalidConstructor { .. }
1195            | Error::RecursiveType { .. }
1196            | Error::RedundantMatchClause { .. }
1197            | Error::TupleIndexOutOfBound { .. }
1198            | Error::PairIndexOutOfBound { .. }
1199            | Error::UnexpectedLabeledArg { .. }
1200            | Error::UnexpectedLabeledArgInPattern { .. }
1201            | Error::UnknownLabels { .. }
1202            | Error::UnknownModuleField { .. }
1203            | Error::UnknownModuleType { .. }
1204            | Error::UnknownModuleValue { .. }
1205            | Error::UnknownRecordField { .. }
1206            | Error::UnknownEnvironment { .. }
1207            | Error::UnnecessarySpreadOperator { .. }
1208            | Error::UpdateMultiConstructorType { .. }
1209            | Error::ValidatorImported { .. }
1210            | Error::IncorrectTestArity { .. }
1211            | Error::IllegalTestType { .. }
1212            | Error::GenericLeftAtBoundary { .. }
1213            | Error::UnexpectedMultiPatternAssignment { .. }
1214            | Error::ExpectOnOpaqueType { .. }
1215            | Error::ValidatorMustReturnBool { .. }
1216            | Error::UnknownPurpose { .. }
1217            | Error::UnknownValidatorHandler { .. }
1218            | Error::UnexpectedValidatorFallback { .. }
1219            | Error::IncorrectBenchmarkArity { .. }
1220            | Error::MustInferFirst { .. }
1221            | Error::DecoratorValidation { .. }
1222            | Error::ConflictingDecorators { .. }
1223            | Error::DecoratorTagOverlap { .. }
1224            | Error::InvalidFieldAccess { .. } => None,
1225
1226            Error::PrivateTypeLeak {
1227                leaked,
1228                leaked_location,
1229                ..
1230            } => leaked_location.map(|span| {
1231                format!(
1232                    "{},{}",
1233                    leaked.clone().set_alias(None).to_pretty(0),
1234                    span.start
1235                )
1236            }),
1237
1238            Error::UnknownType { name, .. }
1239            | Error::UnknownTypeConstructor { name, .. }
1240            | Error::UnknownVariable { name, .. }
1241            | Error::UnknownModule { name, .. } => Some(name.clone()),
1242        }
1243    }
1244}
1245
1246impl Error {
1247    pub fn call_situation(mut self) -> Self {
1248        if let Error::UnknownRecordField {
1249            ref mut situation, ..
1250        } = self
1251        {
1252            *situation = Some(UnknownRecordFieldSituation::FunctionCall);
1253        }
1254        self
1255    }
1256
1257    pub fn case_clause_mismatch(self) -> Self {
1258        self.with_unify_error_situation(UnifyErrorSituation::CaseClauseMismatch)
1259    }
1260
1261    pub fn flip_unify(self) -> Error {
1262        match self {
1263            Error::CouldNotUnify {
1264                location,
1265                expected,
1266                given,
1267                situation: note,
1268                rigid_type_names,
1269            } => Error::CouldNotUnify {
1270                location,
1271                expected: given,
1272                given: expected,
1273                situation: note,
1274                rigid_type_names,
1275            },
1276            other => other,
1277        }
1278    }
1279
1280    pub fn operator_situation(self, binop: BinOp) -> Self {
1281        self.with_unify_error_situation(UnifyErrorSituation::Operator(binop))
1282    }
1283
1284    pub fn return_annotation_mismatch(self) -> Self {
1285        self.with_unify_error_situation(UnifyErrorSituation::ReturnAnnotationMismatch)
1286    }
1287
1288    pub fn with_unify_error_rigid_names(mut self, new_names: &HashMap<u64, String>) -> Self {
1289        match self {
1290            Error::CouldNotUnify {
1291                rigid_type_names: ref mut annotated_names,
1292                ..
1293            } => {
1294                annotated_names.clone_from(new_names);
1295                self
1296            }
1297            _ => self,
1298        }
1299    }
1300
1301    pub fn with_unify_error_situation(mut self, new_situation: UnifyErrorSituation) -> Self {
1302        if let Error::CouldNotUnify {
1303            ref mut situation, ..
1304        } = self
1305        {
1306            *situation = Some(new_situation);
1307        }
1308
1309        self
1310    }
1311}
1312
1313fn suggest_neighbor<'a>(
1314    name: &'a str,
1315    items: impl Iterator<Item = &'a String>,
1316    default: &'a str,
1317) -> String {
1318    let threshold = (name.len() as f64).sqrt().round() as usize;
1319    items
1320        .map(|s| (s, levenshtein::distance(name, s)))
1321        .min_by(|(_, a), (_, b)| a.cmp(b))
1322        .and_then(|(suggestion, distance)| {
1323            if distance <= threshold {
1324                Some(format!(
1325                    "Did you mean '{}'?",
1326                    suggestion.if_supports_color(Stdout, |s| s.yellow())
1327                ))
1328            } else {
1329                None
1330            }
1331        })
1332        .unwrap_or_else(|| default.to_string())
1333}
1334
1335fn suggest_pattern(
1336    expected: usize,
1337    name: &str,
1338    given: &[CallArg<UntypedPattern>],
1339    module: &Option<Namespace>,
1340    is_record: bool,
1341) -> Option<String> {
1342    if expected > given.len() {
1343        Some(format!(
1344            "Try instead: {}",
1345            Formatter::new()
1346                .pattern_constructor(name, given, module, Some(Span::empty()), is_record)
1347                .to_pretty_string(70),
1348        ))
1349    } else {
1350        None
1351    }
1352}
1353
1354fn suggest_generic(name: &str, expected: usize) -> String {
1355    if expected == 0 {
1356        return name.to_doc().to_pretty_string(70);
1357    }
1358
1359    let mut args = vec![];
1360    for i in 0..expected {
1361        args.push(Annotation::Var {
1362            name: char::from_u32(97 + i as u32).unwrap_or('?').to_string(),
1363            location: Span::empty(),
1364        });
1365    }
1366    name.to_doc()
1367        .append(Formatter::new().type_arguments(&args))
1368        .to_pretty_string(70)
1369}
1370
1371fn suggest_constructor_pattern(
1372    name: &str,
1373    args: &[CallArg<UntypedPattern>],
1374    module: &Option<Namespace>,
1375    spread_location: Option<Span>,
1376) -> String {
1377    let fixed_args = args
1378        .iter()
1379        .map(|arg| CallArg {
1380            label: None,
1381            location: arg.location,
1382            value: arg.value.clone(),
1383        })
1384        .collect::<Vec<_>>();
1385
1386    Formatter::new()
1387        .pattern_constructor(name, &fixed_args, module, spread_location, false)
1388        .to_pretty_string(70)
1389}
1390
1391fn suggest_unify(
1392    expected: &Type,
1393    given: &Type,
1394    situation: &Option<UnifyErrorSituation>,
1395    rigid_type_names: &HashMap<u64, String>,
1396) -> String {
1397    let expected_str = expected.to_pretty_with_names(rigid_type_names.clone(), 0);
1398    let given_str = given.to_pretty_with_names(rigid_type_names.clone(), 0);
1399
1400    let (expected, given) = match (expected, given) {
1401        (
1402            Type::App {
1403                module: expected_module,
1404                ..
1405            },
1406            Type::App {
1407                module: given_module,
1408                ..
1409            },
1410        ) if expected_str == given_str => {
1411            let expected_module = if expected_module.is_empty() {
1412                "aiken"
1413            } else {
1414                expected_module
1415            };
1416
1417            let given_module = if given_module.is_empty() {
1418                "aiken"
1419            } else {
1420                given_module
1421            };
1422
1423            (
1424                format!(
1425                    "{}.{{{}}}",
1426                    expected_module.if_supports_color(Stdout, |s| s.bright_blue()),
1427                    expected_str.if_supports_color(Stdout, |s| s.green()),
1428                ),
1429                format!(
1430                    "{}.{{{}}}",
1431                    given_module.if_supports_color(Stdout, |s| s.bright_blue()),
1432                    given_str.if_supports_color(Stdout, |s| s.red()),
1433                ),
1434            )
1435        }
1436        _ => (
1437            expected_str
1438                .if_supports_color(Stdout, |s| s.green())
1439                .to_string(),
1440            given_str.if_supports_color(Stdout, |s| s.red()).to_string(),
1441        ),
1442    };
1443
1444    match situation {
1445        Some(UnifyErrorSituation::CaseClauseMismatch) => formatdoc! {
1446            r#"While comparing branches from a '{keyword_when}/{keyword_is}' expression, I realized not all branches have the same type.
1447
1448               I am expecting all of them to have the following type:
1449
1450                   {expected}
1451
1452               but I found some with type:
1453
1454                   {given}
1455
1456               Note that I infer the type of the entire '{keyword_when}/{keyword_is}' expression based on the type of the first branch I encounter."#,
1457            keyword_when = "when".if_supports_color(Stdout, |s| s.yellow()),
1458            keyword_is = "is".if_supports_color(Stdout, |s| s.yellow()),
1459            expected = expected,
1460            given = given
1461        },
1462        Some(UnifyErrorSituation::ReturnAnnotationMismatch) => formatdoc! {
1463            r#"While comparing the return annotation of a function with its actual return type, I realized that both don't match.
1464
1465               I am inferring the function should return:
1466
1467                   {}
1468
1469               but I found that it returns:
1470
1471                   {}
1472
1473               Either, fix the annotation or adjust the function body to return the expected type."#,
1474            expected,
1475            given
1476        },
1477        Some(UnifyErrorSituation::PipeTypeMismatch) => formatdoc! {
1478            r#"As I was looking at a pipeline you have defined, I realized that one of the pipes isn't valid.
1479
1480               I am expecting the pipe to send into something of type:
1481
1482                   {}
1483
1484               but it is typed:
1485
1486                   {}
1487
1488               Either, fix the input or change the target so that both match."#,
1489            expected,
1490            given
1491        },
1492        Some(UnifyErrorSituation::Operator(op)) => formatdoc! {
1493            r#"While checking operands of a binary operator, I realized that at least one of them doesn't have the expected type.
1494
1495               The '{}' operator expects operands of type:
1496
1497                   {}
1498
1499               but I discovered the following instead:
1500
1501                   {}
1502            "#,
1503            op.to_doc().to_pretty_string(70).if_supports_color(Stdout, |s| s.yellow()),
1504            expected,
1505            given
1506        },
1507        Some(UnifyErrorSituation::FuzzerAnnotationMismatch) => formatdoc! {
1508            r#"While comparing the return annotation of a Fuzzer with its actual return type, I realized that both don't match.
1509
1510               I am inferring the Fuzzer should return:
1511
1512                   {}
1513
1514               but I found a conflicting annotation saying it returns:
1515
1516                   {}
1517
1518               Either, fix (or remove) the annotation or adjust the Fuzzer to return the expected type."#,
1519            expected,
1520            given
1521        },
1522        Some(UnifyErrorSituation::SamplerAnnotationMismatch) => formatdoc! {
1523            r#"While comparing the return annotation of a Sampler with its actual return type, I realized that both don't match.
1524
1525               I am inferring the Sampler should return:
1526
1527                   {}
1528
1529               but I found a conflicting annotation saying it returns:
1530
1531                   {}
1532
1533               Either, fix (or remove) the annotation or adjust the Sampler to return the expected type."#,
1534            expected,
1535            given
1536        },
1537        None => formatdoc! {
1538            r#"I am inferring the following type:
1539
1540                   {}
1541
1542               but I found an expression with a different type:
1543
1544                   {}
1545
1546               Either, add type-annotation to improve my inference, or adjust the expression to have the expected type."#,
1547            expected,
1548            given
1549        },
1550    }
1551}
1552
1553fn suggest_make_public() -> String {
1554    formatdoc! {
1555        r#"Did you forget to make this value public?
1556
1557           Values from module must be exported using the keyword '{keyword_pub}' in order to be available from other modules.
1558           For example:
1559
1560             ┍━ aiken/foo.ak ━━━━━━━━
1561             │ {keyword_fn} foo() {{ {literal_foo} }}
15621563             │ {keyword_pub} {keyword_type} {type_Bar} {{
1564             │   {variant_Bar}
1565             │ }}
1566
1567           The function 'foo' is private and can't be accessed from outside of the 'aiken/foo' module. But the data-type '{type_Bar}' is public and available.
1568        "#
1569        , keyword_fn = "fn".if_supports_color(Stdout, |s| s.yellow())
1570        , keyword_pub = "pub".if_supports_color(Stdout, |s| s.bright_blue())
1571        , keyword_type = "type".if_supports_color(Stdout, |s| s.bright_blue())
1572        , literal_foo = "\"foo\"".if_supports_color(Stdout, |s| s.bright_purple())
1573        , type_Bar = "Bar"
1574            .if_supports_color(Stdout, |s| s.bright_blue())
1575            .if_supports_color(Stdout, |s| s.bold())
1576        , variant_Bar = "Bar"
1577            .if_supports_color(Stdout, |s| s.bright_blue())
1578            .if_supports_color(Stdout, |s| s.bold())
1579    }
1580}
1581
1582fn suggest_import_constructor() -> String {
1583    formatdoc! {
1584        r#"Did you forget to import it?
1585
1586           Data-type constructors are not automatically imported, even if their type is imported. So, if a module 'aiken/pet' defines the following type:
1587
1588             ┍━ aiken/pet.ak ━    ==>   ┍━ foo.ak ━━━━━━━━━━━━━━━━
1589             │ {keyword_pub} {keyword_type} {type_Pet} {{           │ {keyword_use} aiken/pet.{{{type_Pet}, {variant_Dog}}}
1590             │   {variant_Cat}                    │
1591             │   {variant_Dog}                    │ {keyword_fn} foo(pet: {type_Pet}) {{
1592             │   {variant_Fox}                    │   {keyword_when} pet {keyword_is} {{
1593             │ }}                        │     pet.{variant_Cat} -> // ...
1594                                        │     {variant_Dog} -> // ...
1595                                        │     {type_Pet}.{variant_Fox} -> // ...
1596                                        │   }}
1597                                        │ }}
1598
1599           You must import its constructors explicitly to use them, or prefix them with the module or type's name.
1600        "#
1601        , keyword_fn =  "fn".if_supports_color(Stdout, |s| s.yellow())
1602        , keyword_is = "is".if_supports_color(Stdout, |s| s.yellow())
1603        , keyword_pub = "pub".if_supports_color(Stdout, |s| s.bright_purple())
1604        , keyword_type = "type".if_supports_color(Stdout, |s| s.purple())
1605        , keyword_use = "use".if_supports_color(Stdout, |s| s.bright_purple())
1606        , keyword_when = "when".if_supports_color(Stdout, |s| s.yellow())
1607        , type_Pet = "Pet"
1608            .if_supports_color(Stdout, |s| s.bright_blue())
1609            .if_supports_color(Stdout, |s| s.bold())
1610        , variant_Cat = "Cat"
1611            .if_supports_color(Stdout, |s| s.bright_blue())
1612            .if_supports_color(Stdout, |s| s.bold())
1613        , variant_Dog = "Dog"
1614            .if_supports_color(Stdout, |s| s.bright_blue())
1615            .if_supports_color(Stdout, |s| s.bold())
1616        , variant_Fox = "Fox"
1617            .if_supports_color(Stdout, |s| s.bright_blue())
1618            .if_supports_color(Stdout, |s| s.bold())
1619    }
1620}
1621
1622#[derive(Debug, PartialEq, Clone, thiserror::Error, Diagnostic)]
1623pub enum Warning {
1624    #[error("I found a record update using all fields; thus redundant.")]
1625    #[diagnostic(url("https://aiken-lang.org/language-tour/custom-types#record-updates"))]
1626    #[diagnostic(code("record_update::all_fields"))]
1627    AllFieldsRecordUpdate {
1628        #[label("redundant record update")]
1629        location: Span,
1630    },
1631
1632    #[error("I realized the following expression returned a result that is implicitly discarded.")]
1633    #[diagnostic(help(
1634        "You can use the '_' symbol should you want to explicitly discard a result."
1635    ))]
1636    #[diagnostic(code("implicit_discard"))]
1637    ImplicitlyDiscardedResult {
1638        #[label("implicitly discarded result")]
1639        location: Span,
1640    },
1641
1642    #[error("I found a record update with no fields; effectively updating nothing.")]
1643    #[diagnostic(url("https://aiken-lang.org/language-tour/custom-types#record-updates"))]
1644    #[diagnostic(code("record_update::no_fields"))]
1645    NoFieldsRecordUpdate {
1646        #[label("useless record update")]
1647        location: Span,
1648    },
1649
1650    #[error("I found a when expression with a single clause.")]
1651    #[diagnostic(
1652        code("single_when_clause"),
1653        help(
1654            "Prefer using a {} binding like so...\n\n{}",
1655            "let".if_supports_color(Stderr, |s| s.purple()),
1656            format_suggestion(sample)
1657        )
1658    )]
1659    SingleWhenClause {
1660        #[label("use let")]
1661        location: Span,
1662        sample: UntypedExpr,
1663    },
1664
1665    #[error(
1666        "I found an {} {}",
1667        "expect".if_supports_color(Stderr, |s| s.purple()),
1668        "trying to match a type with one constructor".if_supports_color(Stderr, |s| s.yellow())
1669    )]
1670    #[diagnostic(
1671        code("single_constructor_expect"),
1672        help(
1673            "If your type has one constructor, unless you are casting {} {}, you can\nprefer using a {} binding like so...\n\n{}",
1674            "from".if_supports_color(Stderr, |s| s.bold()),
1675            "Data"
1676                .if_supports_color(Stderr, |s| s.bold())
1677                .if_supports_color(Stderr, |s| s.bright_blue()),
1678            "let".if_supports_color(Stderr, |s| s.purple()),
1679            format_suggestion(sample),
1680        )
1681    )]
1682    SingleConstructorExpect {
1683        #[label("use let")]
1684        location: Span,
1685        #[label("only one constructor")]
1686        pattern_location: Span,
1687        #[label("is not Data")]
1688        value_location: Span,
1689        sample: UntypedExpr,
1690    },
1691
1692    #[error("I found a todo left in the code.")]
1693    #[diagnostic(help("You probably want to replace that with actual code... eventually."))]
1694    #[diagnostic(code("todo"))]
1695    Todo {
1696        #[label("An expression of type {} is expected here.", tipo.to_pretty(0))]
1697        location: Span,
1698        tipo: Rc<Type>,
1699    },
1700
1701    #[error("I found a type hole in an annotation.")]
1702    #[diagnostic(code("unexpected::type_hole"))]
1703    UnexpectedTypeHole {
1704        #[label("{}", tipo.to_pretty(0))]
1705        location: Span,
1706        tipo: Rc<Type>,
1707    },
1708
1709    #[error(
1710        "I discovered an unused constructor: {}",
1711        name.if_supports_color(Stderr, |s| s.default_color())
1712    )]
1713    #[diagnostic(help("No big deal, but you might want to remove it to get rid of that warning."))]
1714    #[diagnostic(code("unused::constructor"))]
1715    UnusedConstructor {
1716        #[label("unused constructor")]
1717        location: Span,
1718        name: String,
1719    },
1720
1721    #[error(
1722        "I discovered an unused imported module: {}",
1723        name.if_supports_color(Stderr, |s| s.default_color()),
1724    )]
1725    #[diagnostic(help("No big deal, but you might want to remove it to get rid of that warning."))]
1726    #[diagnostic(code("unused::import::module"))]
1727    UnusedImportedModule {
1728        #[label("unused module")]
1729        location: Span,
1730        name: String,
1731    },
1732
1733    #[error(
1734        "I discovered an unused imported value: {}",
1735        name.if_supports_color(Stderr, |s| s.default_color()),
1736    )]
1737    #[diagnostic(help("No big deal, but you might want to remove it to get rid of that warning."))]
1738    #[diagnostic(code("unused:import::value"))]
1739    UnusedImportedValueOrType {
1740        #[label("unused import")]
1741        location: Span,
1742        name: String,
1743    },
1744
1745    #[error(
1746        "I found an unused private function: {}",
1747        name.if_supports_color(Stderr, |s| s.default_color()),
1748    )]
1749    #[diagnostic(help(
1750        "Perhaps your forgot to make it public using the {keyword_pub} keyword?\n\
1751         Otherwise, you might want to get rid of it altogether.",
1752         keyword_pub = "pub".if_supports_color(Stderr, |s| s.bright_blue())
1753    ))]
1754    #[diagnostic(code("unused::function"))]
1755    UnusedPrivateFunction {
1756        #[label("unused (private) function")]
1757        location: Span,
1758        name: String,
1759    },
1760
1761    #[error(
1762        "I found an unused (private) module constant: {}",
1763        name.if_supports_color(Stderr, |s| s.default_color()),
1764    )]
1765    #[diagnostic(help(
1766        "Perhaps your forgot to make it public using the {keyword_pub} keyword?\n\
1767         Otherwise, you might want to get rid of it altogether.",
1768         keyword_pub = "pub".if_supports_color(Stderr, |s| s.bright_blue())
1769    ))]
1770    #[diagnostic(code("unused::constant"))]
1771    UnusedPrivateModuleConstant {
1772        #[label("unused (private) constant")]
1773        location: Span,
1774        name: String,
1775    },
1776
1777    #[error(
1778        "I discovered an unused type: {}",
1779        name
1780            .if_supports_color(Stderr, |s| s.bright_blue())
1781            .if_supports_color(Stderr, |s| s.bold())
1782    )]
1783    #[diagnostic(code("unused::type"))]
1784    UnusedType {
1785        #[label("unused (private) type")]
1786        location: Span,
1787        name: String,
1788    },
1789
1790    #[error(
1791        "I came across an unused variable: {}",
1792        name.if_supports_color(Stderr, |s| s.default_color()),
1793    )]
1794    #[diagnostic(help("{}", formatdoc! {
1795        r#"No big deal, but you might want to remove it or use a discard {name} to get rid of that warning.
1796
1797           You should also know that, unlike in typical imperative languages, unused let-bindings are {fully_ignored} in Aiken.
1798           They will not produce any side-effect (such as error calls). Programs with or without unused variables are semantically equivalent.
1799
1800           If you do want to enforce some side-effects, use {keyword_expect} with a discard {name} instead of {keyword_let}.
1801        "#,
1802        fully_ignored = "fully_ignored".if_supports_color(Stderr, |s| s.bold()),
1803        keyword_expect = "expect".if_supports_color(Stderr, |s| s.yellow()),
1804        keyword_let = "let".if_supports_color(Stderr, |s| s.yellow()),
1805        name = format!("_{name}").if_supports_color(Stderr, |s| s.yellow())
1806    }))]
1807    #[diagnostic(code("unused::variable"))]
1808    UnusedVariable {
1809        #[label("unused identifier")]
1810        location: Span,
1811        name: String,
1812    },
1813
1814    #[error(
1815        "I found an {} {}",
1816        "if/is".if_supports_color(Stderr, |s| s.purple()),
1817        "that checks an expression with a known type.".if_supports_color(Stderr, |s| s.yellow())
1818    )]
1819    #[diagnostic(
1820        code("if_is_on_non_data"),
1821        help(
1822            "Prefer using a {} to match on all known constructors.",
1823            "when/is".if_supports_color(Stderr, |s| s.purple())
1824        )
1825    )]
1826    UseWhenInstead {
1827        #[label(
1828            "use {}",
1829            "when/is".if_supports_color(Stderr, |s| s.purple())
1830        )]
1831        location: Span,
1832    },
1833
1834    #[error(
1835        "I came across a discarded variable in a let assignment: {}",
1836        name.if_supports_color(Stderr, |s| s.default_color())
1837    )]
1838    #[diagnostic(help("{}", formatdoc! {
1839        r#"If you do want to enforce some side-effects, use {keyword_expect} with {name} instead of {keyword_let}.
1840
1841           You should also know that, unlike in typical imperative languages, unused let-bindings are {fully_ignored} in Aiken.
1842           They will not produce any side-effect (such as error calls). Programs with or without unused variables are semantically equivalent.
1843        "#,
1844        fully_ignored = "fully_ignored".if_supports_color(Stderr, |s| s.bold()),
1845        keyword_expect = "expect".if_supports_color(Stderr, |s| s.yellow()),
1846        keyword_let = "let".if_supports_color(Stderr, |s| s.yellow()),
1847        name = name.if_supports_color(Stderr, |s| s.yellow())
1848    }))]
1849    #[diagnostic(code("unused::discarded_let_assignment"))]
1850    DiscardedLetAssignment {
1851        #[label("discarded result")]
1852        location: Span,
1853        name: String,
1854    },
1855
1856    #[error(
1857        "I came across a validator in a {} {}",
1858        "lib/".if_supports_color(Stderr, |s| s.purple()),
1859        "module which means I'm going to ignore it.".if_supports_color(Stderr, |s| s.yellow()),
1860    )]
1861    #[diagnostic(help(
1862        "No big deal, but you might want to move it to the {} folder or remove it to get rid of that warning.",
1863        "validators".if_supports_color(Stderr, |s| s.purple()),
1864    ))]
1865    #[diagnostic(code("unused::validator"))]
1866    ValidatorInLibraryModule {
1867        #[label("ignored")]
1868        location: Span,
1869    },
1870
1871    #[error(
1872        "I noticed a suspicious {type_ByteArray} {tail}",
1873        type_ByteArray = "ByteArray"
1874            .if_supports_color(Stderr, |s| s.bright_blue())
1875            .if_supports_color(Stderr, |s| s.bold()),
1876        tail = "UTF-8 literal which resembles a hash digest.".if_supports_color(Stderr, |s| s.yellow()),
1877    )]
1878    #[diagnostic(help("{}", formatdoc! {
1879        r#"When you specify a {type_ByteArray} literal using plain double-quotes, it's interpreted as an array of UTF-8 bytes. For example, the literal {literal_foo} is interpreted as the byte sequence {foo_bytes}.
1880
1881           However here, you have specified a literal that resembles a hash digest encoded as an hexadecimal string. This is a common case, but you probably want to capture the raw bytes represented by this sequence, and not the hexadecimal sequence. Fear not! Aiken provides a convenient syntax for that: just prefix the literal with {symbol_hash}. This will decode the hexadecimal string for you and capture the non-encoded bytes as a {type_ByteArray}.
1882
1883           ╰─▶ {symbol_hash}{value}
1884        "#,
1885        type_ByteArray = "ByteArray"
1886            .if_supports_color(Stderr, |s| s.bright_blue())
1887            .if_supports_color(Stderr, |s| s.bold()),
1888        literal_foo = "\"foo\"".if_supports_color(Stderr, |s| s.purple()),
1889        foo_bytes = "#[102, 111, 111]".if_supports_color(Stderr, |s| s.purple()),
1890        value = format!("\"{value}\"").if_supports_color(Stderr, |s| s.purple()),
1891        symbol_hash = "#".if_supports_color(Stderr, |s| s.purple()),
1892    }))]
1893    #[diagnostic(code("syntax::bytearray_literal_is_hex_string"))]
1894    #[diagnostic(url("https://aiken-lang.org/language-tour/primitive-types#bytearray"))]
1895    Utf8ByteArrayIsValidHexString {
1896        #[label("missing '#' to decode hex string")]
1897        location: Span,
1898        value: String,
1899    },
1900
1901    #[error("I tripped over a confusing constructor destructuring")]
1902    #[diagnostic(help("Try instead: \n\n{}", format_pattern_suggestion(suggestion)))]
1903    #[diagnostic(code("syntax::unused_record_fields"))]
1904    #[diagnostic(url("https://aiken-lang.org/language-tour/custom-types#destructuring"))]
1905    UnusedRecordFields {
1906        #[label("prefer destructuring with named fields")]
1907        location: Span,
1908        suggestion: UntypedPattern,
1909    },
1910
1911    #[error("I noticed a (compact) dynamic trace label which is not a string")]
1912    #[diagnostic(help(
1913        "Compiling with a compact trace-level, you are probably expecting compact traces although here, the entire label will need to be serialise *at runtime* which will add a significant overhead.\n\nAs a reminder, trace arguments are fully ignored in compact tracing. Hence, you probably want to put a cute little label here and move the current trace as argument!"
1914    ))]
1915    #[diagnostic(code("trace::label_is_not_string"))]
1916    #[diagnostic(url("https://aiken-lang.org/language-tour/troubleshooting#traces"))]
1917    CompactTraceLabelIsNotstring {
1918        #[label("compact trace label is not String")]
1919        location: Span,
1920    },
1921}
1922
1923impl ExtraData for Warning {
1924    fn extra_data(&self) -> Option<String> {
1925        match self {
1926            Warning::AllFieldsRecordUpdate { .. }
1927            | Warning::ImplicitlyDiscardedResult { .. }
1928            | Warning::NoFieldsRecordUpdate { .. }
1929            | Warning::SingleConstructorExpect { .. }
1930            | Warning::SingleWhenClause { .. }
1931            | Warning::Todo { .. }
1932            | Warning::UnusedConstructor { .. }
1933            | Warning::UnusedVariable { .. }
1934            | Warning::DiscardedLetAssignment { .. }
1935            | Warning::ValidatorInLibraryModule { .. }
1936            | Warning::CompactTraceLabelIsNotstring { .. }
1937            | Warning::UseWhenInstead { .. } => None,
1938            Warning::UnusedPrivateFunction { name, .. }
1939            | Warning::UnusedType { name, .. }
1940            | Warning::UnusedPrivateModuleConstant { name, .. } => Some(name.clone()),
1941            Warning::Utf8ByteArrayIsValidHexString { value, .. } => Some(value.clone()),
1942            Warning::UnexpectedTypeHole { tipo, .. } => Some(tipo.to_pretty(0)),
1943            Warning::UnusedImportedModule { location, .. } => {
1944                Some(format!("{},{}", false, location.start))
1945            }
1946            Warning::UnusedImportedValueOrType { location, .. } => {
1947                Some(format!("{},{}", true, location.start))
1948            }
1949            Warning::UnusedRecordFields { suggestion, .. } => {
1950                Some(Formatter::new().pattern(suggestion).to_pretty_string(80))
1951            }
1952        }
1953    }
1954}
1955
1956#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1957pub enum UnifyErrorSituation {
1958    /// Clauses in a case expression were found to return different types.
1959    CaseClauseMismatch,
1960
1961    /// A function was found to return a value that did not match its return
1962    /// annotation.
1963    ReturnAnnotationMismatch,
1964
1965    PipeTypeMismatch,
1966
1967    /// The operands of a binary operator were incorrect.
1968    Operator(BinOp),
1969
1970    FuzzerAnnotationMismatch,
1971
1972    SamplerAnnotationMismatch,
1973}
1974
1975#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1976pub enum UnknownRecordFieldSituation {
1977    /// This unknown record field is being called as a function. i.e. `record.field()`
1978    FunctionCall,
1979}
1980
1981pub fn format_suggestion(sample: &UntypedExpr) -> String {
1982    Formatter::new()
1983        .expr(sample, false)
1984        .to_pretty_string(70)
1985        .lines()
1986        .enumerate()
1987        .map(|(ix, line)| {
1988            if ix == 0 {
1989                format!("╰─▶ {line}")
1990            } else {
1991                format!("    {line}")
1992            }
1993        })
1994        .collect::<Vec<_>>()
1995        .join("\n")
1996}
1997
1998pub fn format_pattern_suggestion(sample: &UntypedPattern) -> String {
1999    Formatter::new()
2000        .pattern(sample)
2001        .to_pretty_string(70)
2002        .lines()
2003        .enumerate()
2004        .map(|(ix, line)| {
2005            if ix == 0 {
2006                format!("╰─▶ {line}")
2007            } else {
2008                format!("    {line}")
2009            }
2010        })
2011        .collect::<Vec<_>>()
2012        .join("\n")
2013}