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} }}
1562 │
1563 │ {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 CaseClauseMismatch,
1960
1961 ReturnAnnotationMismatch,
1964
1965 PipeTypeMismatch,
1966
1967 Operator(BinOp),
1969
1970 FuzzerAnnotationMismatch,
1971
1972 SamplerAnnotationMismatch,
1973}
1974
1975#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1976pub enum UnknownRecordFieldSituation {
1977 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}