Skip to main content

aiken_lang/tipo/
error.rs

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