1#![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} }}
1581 │
1582 │ {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 CaseClauseMismatch,
1979
1980 ReturnAnnotationMismatch,
1983
1984 PipeTypeMismatch,
1985
1986 Operator(BinOp),
1988
1989 FuzzerAnnotationMismatch,
1990
1991 SamplerAnnotationMismatch,
1992}
1993
1994#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1995pub enum UnknownRecordFieldSituation {
1996 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}