Skip to main content

nickel_lang_core/error/
mod.rs

1//! Error types and error reporting.
2//!
3//! Define error types for different phases of the execution, together with functions to generate a
4//! [codespan](https://crates.io/crates/codespan-reporting) diagnostic from them.
5pub use codespan_reporting::diagnostic::{Diagnostic, Label, LabelStyle};
6
7use codespan_reporting::term::termcolor::{ColorChoice, StandardStream, WriteColor};
8use malachite::base::num::conversion::traits::ToSci;
9
10use ouroboros::self_referencing;
11
12use crate::{
13    ast::{
14        Ast, MergeKind,
15        alloc::{AstAlloc, CloneTo},
16        compat::ToMainline as _,
17        typ::{EnumRow, RecordRow, Type},
18    },
19    cache::InputFormat,
20    eval::{
21        callstack::CallStack,
22        value::{EnumVariantData, NickelValue},
23    },
24    files::{FileId, Files},
25    identifier::{Ident, LocIdent},
26    label::{
27        self, MergeLabel,
28        ty_path::{self, PathSpan},
29    },
30    position::{PosIdx, PosTable, RawSpan, TermPos},
31    repl,
32    serialize::{ExportFormat, NickelPointer, NickelPointerElem},
33    term::{Number, record::FieldMetadata},
34    typ::{TypeF, VarKindDiscriminant},
35    typecheck::error::RowKind,
36};
37
38pub use nickel_lang_parser::error::{ParseError, ParseErrors};
39
40pub mod report;
41pub mod suggest;
42pub mod warning;
43
44pub use warning::Warning;
45
46/// A `Reporter` is basically a callback function for reporting errors and/or warnings.
47///
48/// The error type `E` is a generic parameter, so the same object can be a `Reporter`
49/// of various different things.
50pub trait Reporter<E> {
51    /// Called when there is something (`e`) for the reporter to report.
52    fn report(&mut self, e: E);
53
54    /// A utility function for reporting error variants.
55    ///
56    /// When this is called with an `Ok(_)` it does nothing; when called with an `Err(e)`
57    /// it reports `e`.
58    fn report_result<T, E2>(&mut self, result: Result<T, E2>)
59    where
60        Self: Sized,
61        E2: Into<E>,
62    {
63        if let Err(e) = result {
64            self.report(e.into());
65        }
66    }
67}
68
69impl<E, R: Reporter<E>> Reporter<E> for &mut R {
70    fn report(&mut self, e: E) {
71        R::report(*self, e)
72    }
73}
74
75/// A [`Reporter`] that just collects errors.
76pub struct Sink<E> {
77    pub errors: Vec<E>,
78}
79
80impl<E> Default for Sink<E> {
81    fn default() -> Self {
82        Sink { errors: Vec::new() }
83    }
84}
85
86impl<E> Reporter<E> for Sink<E> {
87    fn report(&mut self, e: E) {
88        self.errors.push(e);
89    }
90}
91
92/// A [`Reporter`] that throws away all its errors.
93pub struct NullReporter {}
94
95impl<E> Reporter<E> for NullReporter {
96    fn report(&mut self, _e: E) {}
97}
98
99/// A general error occurring during either parsing or evaluation.
100#[derive(Debug, PartialEq, Clone)]
101pub enum Error {
102    EvalError(EvalError),
103    TypecheckError(TypecheckError),
104    ParseErrors(ParseErrors),
105    ImportError(ImportError),
106    ExportError(ExportError),
107    IOError(IOError),
108    ReplError(ReplError),
109}
110
111pub type EvalError = Box<EvalErrorData>;
112pub type TypecheckError = Box<TypecheckErrorData>;
113pub type ImportError = Box<ImportErrorKind>;
114pub type ExportError = Box<ExportErrorData>;
115pub type ReplError = Box<ReplErrorKind>;
116
117impl Error {
118    pub fn eval_error(ctxt: EvalCtxt, error: EvalErrorKind) -> Self {
119        Error::EvalError(Box::new(EvalErrorData { ctxt, error }))
120    }
121
122    pub fn export_error(pos_table: PosTable, data: impl Into<PointedExportErrorData>) -> Self {
123        Error::ExportError(Box::new(ExportErrorData {
124            pos_table,
125            data: data.into(),
126        }))
127    }
128
129    pub fn import_error(kind: ImportErrorKind) -> Self {
130        Error::ImportError(Box::new(kind))
131    }
132}
133
134/// Runtime errors might need additional context to be properly reported. The most important one is
135/// the position table, which is need to map position indices stored inside a [NickelValue] back to
136/// a [TermPos]. Additional data is useful, such as the callstack.
137#[derive(Default, PartialEq, Clone)]
138pub struct EvalCtxt {
139    pub pos_table: PosTable,
140    pub call_stack: CallStack,
141}
142
143// Skip the pos table, because it's likely to be huge and uninformative.
144impl std::fmt::Debug for EvalCtxt {
145    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146        f.debug_struct("EvalCtxt")
147            .field("call_stack", &self.call_stack)
148            .finish()
149    }
150}
151
152/// An error occurring during evaluation with additional [context][EvalCtxt].
153#[derive(Debug, PartialEq, Clone)]
154pub struct EvalErrorData {
155    pub ctxt: EvalCtxt,
156    pub error: EvalErrorKind,
157}
158
159/// An error occurring during evaluation.
160#[derive(Debug, PartialEq, Clone)]
161pub enum EvalErrorKind {
162    /// A blame occurred: a contract has been broken somewhere.
163    BlameError {
164        /// The argument failing the contract. If the argument has been forced by the contract,
165        /// `evaluated_arg` provides the final value.
166        evaluated_arg: Option<NickelValue>,
167        /// The label of the corresponding contract.
168        label: label::Label,
169    },
170    /// A field required by a record contract is missing a definition.
171    MissingFieldDef {
172        id: LocIdent,
173        metadata: FieldMetadata,
174        pos_record: PosIdx,
175        pos_access: PosIdx,
176    },
177    /// Mismatch between the expected type and the actual type of an expression.
178    TypeError {
179        /// The expected type.
180        expected: String,
181        /// A freeform message.
182        message: String,
183        /// Position index of the original unevaluated expression.
184        orig_pos: PosIdx,
185        /// The evaluated expression.
186        term: NickelValue,
187    },
188    /// `TypeError` when evaluating a unary primop
189    UnaryPrimopTypeError {
190        primop: String,
191        expected: String,
192        pos_arg: PosIdx,
193        arg_evaluated: NickelValue,
194    },
195    /// `TypeError` when evaluating a binary primop
196    NAryPrimopTypeError {
197        primop: String,
198        expected: String,
199        arg_number: usize,
200        pos_arg: PosIdx,
201        arg_evaluated: NickelValue,
202        pos_op: PosIdx,
203    },
204    /// Tried to evaluate a term which wasn't parsed correctly.
205    ParseError(ParseError),
206    /// A term which is not a function has been applied to an argument.
207    NotAFunc(
208        /* term */ NickelValue,
209        /* arg */ NickelValue,
210        /* app position */ PosIdx,
211    ),
212    /// A field access, or another record operation requiring the existence of a specific field,
213    /// has been performed on a record missing that field.
214    FieldMissing {
215        // The name of the missing field.
216        id: LocIdent,
217        // The actual fields of the record used to suggest similar fields.
218        field_names: Vec<LocIdent>,
219        // The primitive operation that required the field to exist.
220        operator: String,
221        // The position of the record value which is missing the field.
222        pos_record: PosIdx,
223        // The position of the primitive operation application.
224        pos_op: PosIdx,
225    },
226    /// Too few arguments were provided to a builtin function.
227    NotEnoughArgs(
228        /* required arg count */ usize,
229        /* primitive */ String,
230        PosIdx,
231    ),
232    /// Attempted to merge incompatible values: for example, tried to merge two distinct default
233    /// values into one record field.
234    MergeIncompatibleArgs {
235        /// The left operand of the merge.
236        left_arg: NickelValue,
237        /// The right operand of the merge.
238        right_arg: NickelValue,
239        /// Additional error-reporting data.
240        merge_label: MergeLabel,
241    },
242    /// An unbound identifier was referenced.
243    UnboundIdentifier(LocIdent, TermPos),
244    /// An element in the evaluation Cache was entered during its own update.
245    InfiniteRecursion(CallStack, PosIdx),
246    /// A serialization error occurred during a call to the builtin `serialize`.
247    SerializationError(PointedExportErrorData),
248    /// A parse error occurred during a call to the builtin `deserialize`.
249    DeserializationError(
250        String, /* format */
251        String, /* error message */
252        PosIdx, /* position of the call to deserialize */
253    ),
254    /// A parse error occurred during a call to the builtin `deserialize`.
255    ///
256    /// This differs from `DeserializationError` in that the inner error
257    /// isn't just a string: it can refer to positions.
258    DeserializationErrorWithInner {
259        format: InputFormat,
260        inner: ParseError,
261        /// Position of the call to deserialize.
262        pos: PosIdx,
263    },
264    /// A polymorphic record contract was broken somewhere.
265    IllegalPolymorphicTailAccess {
266        action: IllegalPolymorphicTailAction,
267        evaluated_arg: Option<NickelValue>,
268        label: label::Label,
269    },
270    /// Two non-equatable terms of the same type (e.g. functions) were compared for equality.
271    IncomparableValues {
272        eq_pos: PosIdx,
273        left: NickelValue,
274        right: NickelValue,
275    },
276    /// A value didn't match any branch of a `match` expression at runtime. This is a specialized
277    /// version of [Self::NonExhaustiveMatch] when all branches are enum patterns. In this case,
278    /// the error message is more informative than the generic one.
279    NonExhaustiveEnumMatch {
280        /// The list of expected patterns. Currently, those are just enum tags.
281        expected: Vec<LocIdent>,
282        /// The original term matched
283        found: NickelValue,
284        /// The position of the `match` expression
285        pos: PosIdx,
286    },
287    NonExhaustiveMatch {
288        /// The original term matched.
289        value: NickelValue,
290        /// The position of the `match` expression
291        pos: PosIdx,
292    },
293    FailedDestructuring {
294        /// The original term matched.
295        value: NickelValue,
296        /// The position of the pattern that failed to match.
297        pattern_pos: PosIdx,
298    },
299    /// Tried to query a field of something that wasn't a record.
300    QueryNonRecord {
301        /// Position of the original unevaluated expression.
302        pos: PosIdx,
303        /// The identifier that we tried to query.
304        id: LocIdent,
305        /// Evaluated expression
306        value: NickelValue,
307    },
308    /// An unexpected internal error.
309    InternalError(String, PosIdx),
310    /// Errors occurring rarely enough to not deserve a dedicated variant.
311    Other(String, PosIdx),
312}
313
314#[derive(Clone, Debug, Eq, PartialEq)]
315pub enum IllegalPolymorphicTailAction {
316    FieldAccess { field: String },
317    Map,
318    Merge,
319    FieldRemove { field: String },
320    Freeze,
321}
322
323impl IllegalPolymorphicTailAction {
324    fn message(&self) -> String {
325        use IllegalPolymorphicTailAction::*;
326
327        match self {
328            FieldAccess { field } => {
329                format!("cannot access field `{field}` sealed by a polymorphic contract")
330            }
331            Map => "cannot map over a record sealed by a polymorphic contract".to_owned(),
332            Merge => "cannot merge a record sealed by a polymorphic contract".to_owned(),
333            FieldRemove { field } => {
334                format!("cannot remove field `{field}` sealed by a polymorphic contract")
335            }
336            Freeze => "cannot freeze a record sealed by a polymorphic contract".to_owned(),
337        }
338    }
339}
340
341pub const UNKNOWN_SOURCE_NAME: &str = "<unknown> (generated by evaluation)";
342
343/// An error occurring during the static typechecking phase.
344#[self_referencing(pub_extras)]
345#[derive(Debug)]
346pub struct TypecheckErrorData {
347    /// The allocator hosting the types and AST nodes.
348    alloc: AstAlloc,
349    /// The actual error data.
350    #[borrows(alloc)]
351    #[covariant]
352    pub error: TypecheckErrorKind<'this>,
353}
354
355impl Clone for TypecheckErrorData {
356    fn clone(&self) -> Self {
357        TypecheckErrorData::new(AstAlloc::new(), |alloc| {
358            // We must clone the "shallow" layer of the error data to satisfy the `CloneTo`
359            // interface
360            TypecheckErrorKind::clone_to(self.borrow_error().clone(), alloc)
361        })
362    }
363}
364
365impl PartialEq for TypecheckErrorData {
366    fn eq(&self, other: &Self) -> bool {
367        self.borrow_error() == other.borrow_error()
368    }
369}
370
371/// The various kinds of typechecking errors.
372#[derive(Debug, PartialEq, Clone)]
373pub enum TypecheckErrorKind<'ast> {
374    /// An unbound identifier was referenced.
375    UnboundIdentifier(LocIdent),
376    /// A specific row was expected to be in the type of an expression, but was not.
377    MissingRow {
378        id: LocIdent,
379        kind: RowKind,
380        expected: Type<'ast>,
381        inferred: Type<'ast>,
382        pos: TermPos,
383    },
384    /// A dynamic tail was expected to be in the type of an expression, but was not.
385    MissingDynTail {
386        expected: Type<'ast>,
387        inferred: Type<'ast>,
388        pos: TermPos,
389    },
390    /// A specific row was not expected to be in the type of an expression.
391    ExtraRow {
392        id: LocIdent,
393        expected: Type<'ast>,
394        inferred: Type<'ast>,
395        pos: TermPos,
396    },
397    /// A additional dynamic tail was not expected to be in the type of an expression.
398    ExtraDynTail {
399        expected: Type<'ast>,
400        inferred: Type<'ast>,
401        pos: TermPos,
402    },
403    /// A parametricity violation involving a row-kinded type variable.
404    ///
405    /// For example, in a function like this:
406    ///
407    /// ```nickel
408    /// let f : forall a. { x: String, y: String } -> { x: String; a } =
409    ///   fun r => r
410    /// in ...
411    /// ```
412    ///
413    /// this error would be raised with `{ ; a }` as the `tail` type and
414    /// `{ y : String }` as the `violating_type`.
415    ForallParametricityViolation {
416        kind: VarKindDiscriminant,
417        tail: Type<'ast>,
418        violating_type: Type<'ast>,
419        pos: TermPos,
420    },
421    /// An unbound type variable was referenced.
422    UnboundTypeVariable(LocIdent),
423    /// The actual (inferred or annotated) type of an expression is incompatible with its expected
424    /// type.
425    TypeMismatch {
426        expected: Type<'ast>,
427        inferred: Type<'ast>,
428        pos: TermPos,
429    },
430    /// The actual (inferred or annotated) record row type of an expression is incompatible with
431    /// its expected record row type. Specialized version of [Self::TypeMismatch] with additional
432    /// row-specific information.
433    RecordRowMismatch {
434        id: LocIdent,
435        expected: Type<'ast>,
436        inferred: Type<'ast>,
437        cause: Box<TypecheckErrorKind<'ast>>,
438        pos: TermPos,
439    },
440    /// Same as [Self::RecordRowMismatch] but for enum types.
441    EnumRowMismatch {
442        id: LocIdent,
443        expected: Type<'ast>,
444        inferred: Type<'ast>,
445        cause: Option<Box<TypecheckErrorKind<'ast>>>,
446        pos: TermPos,
447    },
448    /// Two incompatible types have been deduced for the same identifier of a row type.
449    ///
450    /// This is similar to [Self::RecordRowMismatch] but occurs in a slightly different situation.
451    /// Consider a unification variable `t`, which is a placeholder to be filled by a concrete type
452    /// later in the typechecking phase.  If `t` appears as the tail of a row type, i.e. the type
453    /// of some expression is inferred to be `{ field: Type; t}`, then `t` must not be unified
454    /// later with a type including a different declaration for field, such as `field: Type2`.
455    ///
456    /// A [constraint][crate::typecheck::unif::RowConstrs] is added accordingly, and if this
457    /// constraint is violated (that is if `t` does end up being unified with a type of the form `{
458    /// .., field: Type2, .. }`), [Self::RecordRowConflict] is raised.  We do not necessarily have
459    /// access to the original `field: Type` declaration, as opposed to [Self::RecordRowMismatch],
460    /// which corresponds to the direct failure to unify `{ .. , x: T1, .. }` and `{ .., x: T2, ..
461    /// }`.
462    RecordRowConflict {
463        /// The row that couldn't be added to the record type, because it already existed with a
464        /// different type assignement.
465        row: RecordRow<'ast>,
466        expected: Type<'ast>,
467        inferred: Type<'ast>,
468        pos: TermPos,
469    },
470    /// Same as [Self::RecordRowConflict] but for enum types.
471    EnumRowConflict {
472        /// The row that couldn't be added to the record type, because it already existed with a
473        /// different type assignement.
474        row: EnumRow<'ast>,
475        expected: Type<'ast>,
476        inferred: Type<'ast>,
477        pos: TermPos,
478    },
479    /// Type mismatch on a subtype of an an arrow type.
480    ///
481    /// The unification of two arrow types requires the unification of the domain and the codomain
482    /// (and recursively so, if they are themselves arrow types). When the unification of a subtype
483    /// fails, we want to report which part of the arrow types is problematic, and why, rather than
484    /// a generic `TypeMismatch`. Indeed, failing to unify two arrow types is a common type error
485    /// which deserves a good reporting, that can be caused e.g. by applying a function to an
486    /// argument of a wrong type in some cases:
487    ///
488    /// ```text
489    /// let id_mono = fun x => x in let _ign = id_mono true in id_mono 0 : Number
490    /// ```
491    ///
492    /// This specific error stores additionally the [type path][crate::label::ty_path] that
493    /// identifies the subtype where unification failed and the corresponding error.
494    ArrowTypeMismatch {
495        expected: Type<'ast>,
496        inferred: Type<'ast>,
497        /// The path to the incompatible type components
498        type_path: ty_path::Path,
499        cause: Box<TypecheckErrorKind<'ast>>,
500        pos: TermPos,
501    },
502    /// Within statically typed code, the typechecker must reject terms containing nonsensical
503    /// contracts such as `let C = { foo : (4 + 1) } in ({ foo = 5 } | C)`, which will fail at
504    /// runtime.
505    ///
506    /// The typechecker is currently quite conservative and simply forbids to store any custom
507    /// contract in a type that appears in term position. Note that this restriction
508    /// doesn't apply to annotations, which aren't considered part of the statically typed block.
509    /// For example, `{foo = 5} | {foo : (4 + 1)}` is accepted by the typechecker.
510    CtrTypeInTermPos {
511        /// The term that was in a flat type (the `(4 + 1)` in the example above).
512        contract: Ast<'ast>,
513        /// The position of the entire type (the `{foo : 5}` in the example above).
514        pos_type: TermPos,
515    },
516    /// Unsound generalization.
517    ///
518    /// When typechecking polymorphic expressions, polymorphic variables introduced by a `forall`
519    /// are substituted with rigid type variables, which can only unify with a free unification
520    /// variable. However, the condition that the unification variable is free isn't enough.
521    ///
522    /// Consider the following example:
523    ///
524    /// ```nickel
525    /// (fun x => let y : forall a. a = x in (y : Number)) : _
526    /// ```
527    ///
528    /// This example must be rejected, as it is an identity function that casts any value to
529    /// something of type `Number`. It will typically fail with a contract error if applied to a
530    /// string, for example.
531    ///
532    /// But when `let y : forall a. a = x` is typechecked, `x` is affected to a free unification
533    /// variable `_a`, which isn't determined yet. The unsoundess comes from the fact that `_a` was
534    /// introduced **before** the block with the `forall a. a` annotation, and thus shouldn't be
535    /// allowed to be generalized (unified with a rigid type variable) at this point.
536    ///
537    /// Nickel uses an algorithm coming from the OCaml implementation, recognizing that the
538    /// discipline needed to reject those case is similar to region-based memory management. See
539    /// [crate::typecheck] for more details. This error indicates that a case similar to the above
540    /// example happened.
541    VarLevelMismatch {
542        /// The user-defined type variable (the rigid type variable during unification) that
543        /// couldn't be unified.
544        type_var: LocIdent,
545        /// The position of the expression that was being typechecked as `type_var`.
546        pos: TermPos,
547    },
548    /// Record-dict subtyping failed because the record was inhomogeneous.
549    InhomogeneousRecord {
550        /// One row of the record had this type.
551        row_a: Type<'ast>,
552        /// Another row of the record had this type.
553        row_b: Type<'ast>,
554        /// The position of the expression of record type.
555        pos: TermPos,
556    },
557    /// Invalid or-pattern.
558    ///
559    /// This error is raised when the patterns composing an or-pattern don't have the precise
560    /// same set of free variables. For example, `'Foo x or 'Bar y`.
561    OrPatternVarsMismatch {
562        /// A variable which isn't present in all the other patterns (there might be more of them,
563        /// this is just a sample).
564        var: LocIdent,
565        /// The position of the whole or-pattern.
566        pos: TermPos,
567    },
568    /// An error occured during the resolution of an import.
569    ///
570    /// Since RFC007, imports aren't pre-processed anymore, and import resolution can happen
571    /// interleaved with typechecking. In particular, in order to typecheck expressions of the form
572    /// `import "file.ncl"`, the typechecker might ask to resolve the import, which can lead to any
573    /// import error.
574    ImportError(ImportErrorKind),
575}
576
577impl IntoDiagnostics for ParseErrors {
578    fn into_diagnostics(self, files: &mut Files) -> Vec<Diagnostic<FileId>> {
579        self.errors
580            .into_iter()
581            .flat_map(|e| e.into_diagnostics(files))
582            .collect()
583    }
584}
585
586/// An error occurring during the resolution of an import.
587#[derive(Debug, PartialEq, Eq, Clone)]
588pub enum ImportErrorKind {
589    /// An IO error occurred during an import.
590    IOError(
591        /* imported file */ String,
592        /* error message */ String,
593        /* import position */ TermPos,
594    ),
595    /// A parse error occurred during an import.
596    ParseErrors(
597        /* error */ ParseErrors,
598        /* import position */ TermPos,
599    ),
600    /// A package dependency was not found.
601    MissingDependency {
602        /// The package that tried to import the missing dependency, if there was one.
603        /// This will be `None` if the missing dependency was from the top-level
604        parent: Option<std::path::PathBuf>,
605        /// The name of the package that could not be resolved.
606        missing: Ident,
607        pos: TermPos,
608    },
609    /// They tried to import a file from a package, but no package manifest was supplied.
610    NoPackageMap { pos: TermPos },
611}
612
613/// An error occurring during serialization.
614#[derive(Debug, Clone, PartialEq)]
615pub struct ExportErrorData {
616    /// The position table, mapping position indices to spans.
617    pub pos_table: PosTable,
618    /// The cause of the error.
619    pub data: PointedExportErrorData,
620}
621
622/// The type of an error occurring during serialization, together with  a path to the field that
623/// contains a non-serializable value.
624#[derive(Debug, Clone, PartialEq)]
625pub struct PointedExportErrorData {
626    /// The path to the field that contains a non-serializable value. This might be empty if the
627    /// error occurred before entering any record.
628    pub path: NickelPointer,
629    /// The cause of the error.
630    pub error: ExportErrorKind,
631}
632
633impl PointedExportErrorData {
634    /// Pushes a [NickelPointerElem] to the end of the path of this export error.
635    pub fn with_elem(mut self, elem: NickelPointerElem) -> PointedExportErrorData {
636        self.path.0.push(elem);
637        self
638    }
639
640    /// Attaches a position table to this error, producing a [ExportError].
641    pub fn with_pos_table(self, pos_table: PosTable) -> ExportErrorData {
642        ExportErrorData {
643            data: self,
644            pos_table,
645        }
646    }
647}
648
649impl From<ExportErrorKind> for PointedExportErrorData {
650    fn from(error: ExportErrorKind) -> Self {
651        PointedExportErrorData {
652            error,
653            path: NickelPointer::new(),
654        }
655    }
656}
657
658/// The type of error occurring during serialization.
659#[derive(Debug, PartialEq, Clone)]
660pub enum ExportErrorKind {
661    /// Encountered a null value for a format that doesn't support them.
662    UnsupportedNull(ExportFormat, NickelValue),
663    /// Tried exporting something else than a `String` to raw format.
664    NotAString(NickelValue),
665    /// A term contains constructs that cannot be serialized.
666    NonSerializable(NickelValue),
667    /// No exportable documentation was found when requested.
668    NoDocumentation(NickelValue),
669    /// A number was too large (in absolute value) to be serialized as `f64`
670    NumberOutOfRange {
671        term: NickelValue,
672        value: Number,
673    },
674    /// The YAML documents export format expects the value to be an array.
675    ExpectedArray {
676        value: NickelValue,
677    },
678    Other(String),
679}
680
681/// A general I/O error, occurring when reading a source file or writing an export.
682#[derive(Debug, PartialEq, Eq, Clone)]
683pub struct IOError(pub String);
684
685/// An error occurring during an REPL session.
686#[derive(Debug, PartialEq, Eq, Clone)]
687pub enum ReplErrorKind {
688    UnknownCommand(String),
689    MissingArg {
690        cmd: repl::command::CommandType,
691        msg_opt: Option<String>,
692    },
693    InvalidQueryPath(ParseError),
694}
695
696impl From<EvalErrorData> for Error {
697    fn from(error: EvalErrorData) -> Error {
698        Error::EvalError(Box::new(error))
699    }
700}
701
702impl From<EvalError> for Error {
703    fn from(error: EvalError) -> Error {
704        Error::EvalError(error)
705    }
706}
707
708impl From<ParseError> for Error {
709    fn from(error: ParseError) -> Error {
710        Error::ParseErrors(ParseErrors {
711            errors: vec![error],
712        })
713    }
714}
715
716impl From<ParseErrors> for Error {
717    fn from(errors: ParseErrors) -> Error {
718        Error::ParseErrors(errors)
719    }
720}
721
722impl From<TypecheckErrorData> for Error {
723    fn from(error: TypecheckErrorData) -> Error {
724        Error::TypecheckError(Box::new(error))
725    }
726}
727
728impl From<TypecheckError> for Error {
729    fn from(error: TypecheckError) -> Self {
730        Error::TypecheckError(error)
731    }
732}
733
734impl From<ImportErrorKind> for Error {
735    fn from(error: ImportErrorKind) -> Error {
736        Error::ImportError(Box::new(error))
737    }
738}
739
740impl From<ImportError> for Error {
741    fn from(error: ImportError) -> Error {
742        Error::ImportError(error)
743    }
744}
745
746impl From<ExportErrorData> for Error {
747    fn from(error: ExportErrorData) -> Error {
748        Error::ExportError(Box::new(error))
749    }
750}
751
752impl From<ExportError> for Error {
753    fn from(error: ExportError) -> Error {
754        Error::ExportError(error)
755    }
756}
757
758impl From<IOError> for Error {
759    fn from(error: IOError) -> Error {
760        Error::IOError(error)
761    }
762}
763
764impl From<std::io::Error> for IOError {
765    fn from(error: std::io::Error) -> IOError {
766        IOError(error.to_string())
767    }
768}
769
770impl From<PointedExportErrorData> for EvalErrorKind {
771    fn from(error: PointedExportErrorData) -> EvalErrorKind {
772        EvalErrorKind::SerializationError(error)
773    }
774}
775
776impl From<ImportErrorKind> for TypecheckError {
777    fn from(error: ImportErrorKind) -> Self {
778        Box::new(TypecheckErrorData::new(AstAlloc::new(), |_alloc| {
779            TypecheckErrorKind::ImportError(error)
780        }))
781    }
782}
783
784/// Return an escaped version of a string. Used to sanitize strings before inclusion in error
785/// messages, which can contain ASCII code sequences, and in particular ANSI escape codes, that
786/// could alter Nickel's error messages.
787pub fn escape(s: &str) -> String {
788    String::from_utf8(strip_ansi_escapes::strip(s))
789        .expect("escape(): converting from a string should give back a valid UTF8 string")
790}
791
792impl From<ReplErrorKind> for Error {
793    fn from(error: ReplErrorKind) -> Error {
794        Error::ReplError(Box::new(error))
795    }
796}
797
798pub const INTERNAL_ERROR_MSG: &str = "This error should not happen. This is likely a bug in the Nickel interpreter. Please consider \
799 reporting it at https://github.com/tweag/nickel/issues with the above error message.";
800
801/// A trait for converting an error to a diagnostic.
802pub trait IntoDiagnostics {
803    /// Convert an error to a list of printable formatted diagnostic.
804    ///
805    /// # Arguments
806    ///
807    /// - `pos_table`: the position table, mapping position indices stored in Nickel values to
808    /// - `files`: this is a mutable reference to allow insertion of temporary snippets. Note that
809    ///   `Files` is cheaply clonable and copy-on-write, so you can easily get a mutable `Files` from
810    ///   a non-mutable one, but bear in mind that the returned diagnostics may contains file ids that
811    ///   refer to your mutated files.
812    ///
813    /// # Return
814    ///
815    /// Return a list of diagnostics. Most errors generate only one, but showing the callstack
816    /// ordered requires to sidestep a limitation of codespan. The current solution is to generate
817    /// one diagnostic per callstack element. See issue
818    /// [#285](https://github.com/brendanzab/codespan/issues/285).
819    fn into_diagnostics(self, files: &mut Files) -> Vec<Diagnostic<FileId>>;
820}
821
822// Allow the use of a single `Diagnostic` directly as an error that can be reported by Nickel.
823impl IntoDiagnostics for Diagnostic<FileId> {
824    fn into_diagnostics(self, _files: &mut Files) -> Vec<Diagnostic<FileId>> {
825        vec![self]
826    }
827}
828
829// Helpers for the creation of codespan `Label`s
830
831/// Create a primary label from a span.
832fn primary(span: &RawSpan) -> Label<FileId> {
833    Label::primary(span.src_id, span.start.to_usize()..span.end.to_usize())
834}
835
836/// Create a secondary label from a span.
837fn secondary(span: &RawSpan) -> Label<FileId> {
838    Label::secondary(span.src_id, span.start.to_usize()..span.end.to_usize())
839}
840
841/// Create a label from an optional span, or fallback to annotating the alternative snippet
842/// `alt_term` if the span is `None`.
843///
844/// When `span_opt` is `None`, the code snippet `alt_term` is added to `files` under a special name
845/// and is referred to instead.
846///
847/// This is useful because during evaluation, some terms are the results of computations. They
848/// correspond to nothing in the original source, and thus have a position set to `None`(e.g. the
849/// result of `let x = 1 + 1 in x`).  In such cases it may still be valuable to print the term (or a
850/// terse representation) in the error diagnostic rather than nothing, because if you have let `x =
851/// 1 + 1 in` and then 100 lines later, `x arg` - causing a `NotAFunc` error - it may be helpful to
852/// know that `x` holds the value `2`.
853///
854/// For example, if one wants to report an error on a record, `alt_term` may be defined as
855/// `{ ... }`. Then, if this record has no position (`span_opt` is `None`), the error will be
856/// reported as:
857///
858/// ```text
859/// error: some error
860///   -- <unknown> (generated by evaluation):1:2
861///   |
862/// 1 | { ... }
863///     ^^^^^^^ some annotation
864/// ```
865///
866/// The reason for the mutable reference to `files` is that codespan do no let you annotate
867/// something that is not in `files`: you can't provide a raw snippet, you need to provide a
868/// `FileId` referring to a file. This leaves the following possibilities:
869///
870/// 1. Do nothing: just elude annotations which refer to the term
871/// 2. Print the term and the annotation as a note together with the diagnostic. Notes are
872///    additional text placed at the end of diagnostic. What you lose:
873///     - pretty formatting of annotations for such snippets
874///     - style consistency: the style of the error now depends on the term being from the source or
875///       a byproduct of evaluation
876/// 3. Add the term to files, take 1: pass a reference to files so that the code building the
877///    diagnostic can itself add arbitrary snippets if necessary, and get back their `FileId`. This
878///    is what is done here.
879/// 4. Add the term to files, take 2: make a wrapper around the `Files` and `FileId` structures of
880///    codespan which handle source mapping. `FileId` could be something like
881///    `Either<codespan::FileId, CustomId = u32>` so that `to_diagnostic` could construct and use
882///    these separate ids, and return the corresponding snippets to be added together with the
883///    diagnostic without modifying external state. Or even have `FileId = Either<codespan::FileId`,
884///    `LoneCode = String or (Id, String)>` so we don't have to return the additional list of
885///    snippets. This adds some boilerplate, that we wanted to avoid, but this stays on the
886///    reasonable side of being an alternative.
887fn label_alt(
888    span_opt: Option<RawSpan>,
889    alt_term: String,
890    style: LabelStyle,
891    files: &mut Files,
892) -> Label<FileId> {
893    match span_opt {
894        Some(span) => Label::new(
895            style,
896            span.src_id,
897            span.start.to_usize()..span.end.to_usize(),
898        ),
899        None => {
900            let range = 0..alt_term.len();
901            Label::new(style, files.add(UNKNOWN_SOURCE_NAME, alt_term), range)
902        }
903    }
904}
905
906/// Create a secondary label from an optional span, or fallback to annotating the alternative
907/// snippet `alt_term` if the span is `None`.
908///
909/// See [`label_alt`].
910fn primary_alt(span_opt: Option<RawSpan>, alt_term: String, files: &mut Files) -> Label<FileId> {
911    label_alt(span_opt, alt_term, LabelStyle::Primary, files)
912}
913
914/// Create a primary label from a term, or fallback to annotating the shallow representation of this
915/// term if its span is `None`.
916///
917/// See [`label_alt`].
918fn primary_term(pos_table: &PosTable, term: &NickelValue, files: &mut Files) -> Label<FileId> {
919    primary_alt(
920        pos_table.get(term.pos_idx()).into_opt(),
921        term.to_string(),
922        files,
923    )
924}
925
926/// Create a secondary label from an optional span, or fallback to annotating the alternative
927/// snippet `alt_term` if the span is `None`.
928///
929/// See [`label_alt`].
930fn secondary_alt(span_opt: TermPos, alt_term: String, files: &mut Files) -> Label<FileId> {
931    label_alt(span_opt.into_opt(), alt_term, LabelStyle::Secondary, files)
932}
933
934/// Create a secondary label from a term, or fallback to annotating the shallow representation of
935/// this term if its span is `None`.
936///
937/// See [`label_alt`].
938fn secondary_term(pos_table: &PosTable, term: &NickelValue, files: &mut Files) -> Label<FileId> {
939    secondary_alt(pos_table.get(term.pos_idx()), term.to_string(), files)
940}
941
942fn cardinal(number: usize) -> String {
943    let suffix = if number % 10 == 1 {
944        "st"
945    } else if number % 10 == 2 {
946        "nd"
947    } else if number % 10 == 3 {
948        "rd"
949    } else {
950        "th"
951    };
952    format!("{number}{suffix}")
953}
954
955impl<T: IntoDiagnostics> IntoDiagnostics for Box<T> {
956    fn into_diagnostics(self, files: &mut Files) -> Vec<Diagnostic<FileId>> {
957        (*self).into_diagnostics(files)
958    }
959}
960
961impl IntoDiagnostics for Error {
962    fn into_diagnostics(self, files: &mut Files) -> Vec<Diagnostic<FileId>> {
963        match self {
964            Error::ParseErrors(errs) => errs
965                .errors
966                .into_iter()
967                .flat_map(|e| e.into_diagnostics(files))
968                .collect(),
969            Error::TypecheckError(err) => err.into_diagnostics(files),
970            Error::EvalError(err) => err.into_diagnostics(files),
971            Error::ImportError(err) => err.into_diagnostics(files),
972            Error::ExportError(err) => err.into_diagnostics(files),
973            Error::IOError(err) => err.into_diagnostics(files),
974            Error::ReplError(err) => err.into_diagnostics(files),
975        }
976    }
977}
978
979impl IntoDiagnostics for EvalErrorData {
980    fn into_diagnostics(self, files: &mut Files) -> Vec<Diagnostic<FileId>> {
981        let mut pos_table = self.ctxt.pos_table;
982
983        match self.error {
984            EvalErrorKind::BlameError {
985                evaluated_arg,
986                label,
987            } => blame_error::blame_diagnostics(
988                &mut pos_table,
989                files,
990                label,
991                evaluated_arg,
992                &self.ctxt.call_stack,
993                "",
994            ),
995            EvalErrorKind::MissingFieldDef {
996                id,
997                metadata,
998                pos_record,
999                pos_access,
1000            } => {
1001                let mut labels = vec![];
1002
1003                // If there's a contract attached to the missing field, point the error message
1004                // at the contract instead of the access. This seems like a more useful error,
1005                // because if someone hands you a `x | { fld | String }` and you call `x.fld`,
1006                // then the `x.fld` shouldn't be blamed if `fld` is missing: we should point
1007                // at the original `x` and at the `fld` in the record contract.
1008                if let Some(label) = metadata
1009                    .annotation
1010                    .first()
1011                    .map(|labeled_ty| labeled_ty.label.clone())
1012                {
1013                    if let Some(span) = label.field_name.and_then(|id| id.pos.into_opt()) {
1014                        labels.push(primary(&span).with_message("required here"));
1015                    }
1016
1017                    if let Some(span) = pos_table.get(pos_record).into_opt() {
1018                        labels.push(secondary(&span).with_message("in this record"));
1019                    }
1020
1021                    // In this branch, we don't point at the access location because it
1022                    // isn't to blame.
1023                } else {
1024                    if let Some(span) = id.pos.into_opt() {
1025                        labels.push(primary(&span).with_message("required here"));
1026                    }
1027
1028                    if let Some(span) = pos_table.get(pos_record).into_opt() {
1029                        labels.push(secondary(&span).with_message("in this record"));
1030                    }
1031
1032                    if let Some(span) = pos_table.get(pos_access).into_opt() {
1033                        labels.push(secondary(&span).with_message("accessed here"));
1034                    }
1035                }
1036
1037                let diags = vec![
1038                    Diagnostic::error()
1039                        .with_message(format!("missing definition for `{id}`",))
1040                        .with_labels(labels),
1041                ];
1042
1043                diags
1044            }
1045            EvalErrorKind::TypeError {
1046                expected,
1047                message,
1048                orig_pos,
1049                term: t,
1050            } => {
1051                let label = format!(
1052                    "this expression has type {}, but {} was expected",
1053                    t.type_of().unwrap_or("<unevaluated>").to_owned(),
1054                    expected,
1055                );
1056
1057                let labels = match (
1058                    pos_table.get(orig_pos).into_opt(),
1059                    pos_table.get(t.pos_idx()).into_opt(),
1060                ) {
1061                    (Some(span_orig), Some(span_t)) if span_orig == span_t => {
1062                        vec![primary(&span_orig).with_message(label)]
1063                    }
1064                    (Some(span_orig), Some(t_pos)) if !files.is_stdlib(t_pos.src_id) => {
1065                        vec![
1066                            primary(&span_orig).with_message(label),
1067                            secondary_term(&pos_table, &t, files).with_message("evaluated to this"),
1068                        ]
1069                    }
1070                    (Some(span), _) => {
1071                        vec![primary(&span).with_message(label)]
1072                    }
1073                    (None, Some(span)) => {
1074                        vec![primary(&span).with_message(label)]
1075                    }
1076                    (None, None) => {
1077                        vec![primary_term(&pos_table, &t, files).with_message(label)]
1078                    }
1079                };
1080
1081                vec![
1082                    Diagnostic::error()
1083                        .with_message("dynamic type error")
1084                        .with_labels(labels)
1085                        .with_notes(vec![message]),
1086                ]
1087            }
1088            EvalErrorKind::ParseError(parse_error) => parse_error.into_diagnostics(files),
1089            EvalErrorKind::NotAFunc(t, arg, pos_opt) => vec![
1090                Diagnostic::error()
1091                    .with_message("not a function")
1092                    .with_labels(vec![
1093                        primary_term(&pos_table, &t, files)
1094                            .with_message("this term is applied, but it is not a function"),
1095                        secondary_alt(pos_table.get(pos_opt), format!("({t}) ({arg})"), files)
1096                            .with_message("applied here"),
1097                    ]),
1098            ],
1099            EvalErrorKind::FieldMissing {
1100                id: name,
1101                field_names,
1102                operator,
1103                pos_record,
1104                pos_op,
1105            } => {
1106                let mut labels = Vec::new();
1107                let mut notes = Vec::new();
1108                let field = escape(name.as_ref());
1109
1110                if let Some(span) = pos_table.get(pos_op).into_opt() {
1111                    labels.push(
1112                        Label::primary(span.src_id, span.start.to_usize()..span.end.to_usize())
1113                            .with_message(format!("this requires the field `{field}` to exist")),
1114                    );
1115                } else {
1116                    notes.push(format!(
1117                        "The field `{field}` was required by the operator {operator}"
1118                    ));
1119                }
1120
1121                if let Some(span) = pos_table.get(pos_record).as_opt_ref() {
1122                    labels.push(
1123                        secondary(span)
1124                            .with_message(format!("this record lacks the field `{field}`")),
1125                    );
1126                }
1127
1128                suggest::add_suggestion(&mut notes, &field_names, &name);
1129
1130                vec![
1131                    Diagnostic::error()
1132                        .with_message(format!("missing field `{field}`"))
1133                        .with_labels(labels)
1134                        .with_notes(notes),
1135                ]
1136            }
1137            EvalErrorKind::NotEnoughArgs(count, op, span_opt) => {
1138                let mut labels = Vec::new();
1139                let mut notes = Vec::new();
1140                let msg = format!("{op} expects {count} arguments, but not enough were provided");
1141
1142                if let Some(span) = pos_table.get(span_opt).into_opt() {
1143                    labels.push(
1144                        Label::primary(span.src_id, span.start.to_usize()..span.end.to_usize())
1145                            .with_message(msg),
1146                    );
1147                } else {
1148                    notes.push(msg);
1149                }
1150
1151                vec![
1152                    Diagnostic::error()
1153                        .with_message("not enough arguments")
1154                        .with_labels(labels)
1155                        .with_notes(notes),
1156                ]
1157            }
1158            EvalErrorKind::MergeIncompatibleArgs {
1159                left_arg,
1160                right_arg,
1161                merge_label,
1162            } => {
1163                let mut labels = vec![
1164                    primary_term(&pos_table, &left_arg, files)
1165                        .with_message("cannot merge this expression"),
1166                    primary_term(&pos_table, &right_arg, files)
1167                        .with_message("with this expression"),
1168                ];
1169
1170                let span_label = match merge_label.kind {
1171                    // For a standard merge, the span of the label indicates the position of the
1172                    // original merge expression
1173                    MergeKind::Standard => "originally merged here",
1174                    // For a piecewise definition, there isn't such merge expression (the merge has
1175                    // been generated by the parser). The spans thus point to the corresponding
1176                    // field identifier
1177                    MergeKind::PiecewiseDef => "when combining the definitions of this field",
1178                };
1179
1180                if let Some(merge_label_span) = pos_table.get(merge_label.span).into_opt() {
1181                    labels.push(secondary(&merge_label_span).with_message(span_label));
1182                }
1183
1184                fn push_merge_note(notes: &mut Vec<String>, typ: &str) {
1185                    notes.push(format!(
1186                        "Both values are of type {typ} but they aren't equal."
1187                    ));
1188                    notes.push(format!("{typ} values can only be merged if they are equal"));
1189                }
1190
1191                let mut notes = vec![
1192                    "Merge operands have the same merge priority but they can't \
1193                    be combined."
1194                        .to_owned(),
1195                ];
1196
1197                if let (Some(left_ty), Some(right_ty)) = (right_arg.type_of(), left_arg.type_of()) {
1198                    match left_ty {
1199                        _ if left_ty != right_ty => {
1200                            notes.push(format!(
1201                                "One value is of type {left_ty} \
1202                                while the other is of type {right_ty}"
1203                            ));
1204                            notes.push("Values of different types can't be merged".to_owned());
1205                        }
1206                        "String" | "Number" | "Bool" | "Array" | "EnumTag" => {
1207                            push_merge_note(&mut notes, left_ty);
1208                        }
1209                        "Function" | "MatchExpression" => {
1210                            notes.push(
1211                                "Both values are functions (or match expressions)".to_owned(),
1212                            );
1213                            notes.push(
1214                                "Functions can never be merged with anything else, \
1215                                even another function."
1216                                    .to_owned(),
1217                            );
1218                        }
1219                        "EnumVariant" => {
1220                            if let (
1221                                Some(EnumVariantData { tag: tag1, .. }),
1222                                Some(EnumVariantData { tag: tag2, .. }),
1223                            ) = (right_arg.as_enum_variant(), left_arg.as_enum_variant())
1224                            {
1225                                // The only possible cause of failure of merging two enum variants is a
1226                                // different tag (the arguments could fail to merge as well, but then
1227                                // the error would have them as the operands, not the enclosing enums).
1228                                notes.push(format!(
1229                                    "Both values are enum variants, \
1230                                    but their tags differ (`'{tag1}` vs `'{tag2}`)"
1231                                ));
1232                                notes.push(
1233                                    "Enum variants can only be \
1234                                    merged if they have the same tag"
1235                                        .to_owned(),
1236                                );
1237                            } else {
1238                                // This should not happen, but it's recoverable, so let's not fail
1239                                // in release mode.
1240                                debug_assert!(false);
1241
1242                                notes.push(
1243                                    "Primitive values (Number, String, EnumTag and Bool) \
1244                                    and arrays can only be merged if they are equal"
1245                                        .to_owned(),
1246                                );
1247                                notes.push("Enum variants must have the same tag.".to_owned());
1248                                notes.push("Functions can never be merged.".to_owned());
1249                            }
1250                        }
1251                        _ => {
1252                            // In other cases, we print a generic message
1253                            notes.push(
1254                                "Primitive values (Number, String, EnumTag and Bool) \
1255                                    and arrays can only be merged if they are equal"
1256                                    .to_owned(),
1257                            );
1258                            notes.push("Enum variants must have the same tag.".to_owned());
1259                            notes.push("Functions can never be merged.".to_owned());
1260                        }
1261                    }
1262                }
1263
1264                vec![
1265                    Diagnostic::error()
1266                        .with_message("non mergeable terms")
1267                        .with_labels(labels)
1268                        .with_notes(notes),
1269                ]
1270            }
1271            EvalErrorKind::UnboundIdentifier(ident, span_opt) => vec![
1272                Diagnostic::error()
1273                    .with_message(format!("unbound identifier `{ident}`"))
1274                    .with_labels(vec![
1275                        primary_alt(span_opt.into_opt(), ident.to_string(), files)
1276                            .with_message("this identifier is unbound"),
1277                    ]),
1278            ],
1279            EvalErrorKind::InfiniteRecursion(_call_stack, pos_idx) => {
1280                let labels = pos_table
1281                    .get(pos_idx)
1282                    .as_opt_ref()
1283                    .map(|span| vec![primary(span).with_message("recursive reference")])
1284                    .unwrap_or_default();
1285
1286                vec![
1287                    Diagnostic::error()
1288                        .with_message("infinite recursion")
1289                        .with_labels(labels),
1290                ]
1291            }
1292            EvalErrorKind::Other(msg, span_opt) => {
1293                let labels = pos_table
1294                    .get(span_opt)
1295                    .as_opt_ref()
1296                    .map(|span| vec![primary(span).with_message("here")])
1297                    .unwrap_or_default();
1298
1299                vec![Diagnostic::error().with_message(msg).with_labels(labels)]
1300            }
1301            EvalErrorKind::InternalError(msg, span_opt) => {
1302                let labels = pos_table
1303                    .get(span_opt)
1304                    .as_opt_ref()
1305                    .map(|span| vec![primary(span).with_message("here")])
1306                    .unwrap_or_default();
1307
1308                vec![
1309                    Diagnostic::error()
1310                        .with_message(format!("internal error: {msg}"))
1311                        .with_labels(labels)
1312                        .with_notes(vec![String::from(INTERNAL_ERROR_MSG)]),
1313                ]
1314            }
1315            EvalErrorKind::SerializationError(err) => {
1316                err.with_pos_table(pos_table).into_diagnostics(files)
1317            }
1318            EvalErrorKind::DeserializationError(format, msg, span_opt) => {
1319                let labels = pos_table
1320                    .get(span_opt)
1321                    .as_opt_ref()
1322                    .map(|span| vec![primary(span).with_message("here")])
1323                    .unwrap_or_default();
1324
1325                vec![
1326                    Diagnostic::error()
1327                        .with_message(format!("{format} parse error: {msg}"))
1328                        .with_labels(labels),
1329                ]
1330            }
1331            EvalErrorKind::DeserializationErrorWithInner { format, inner, pos } => {
1332                let mut diags = inner.into_diagnostics(files);
1333                if let Some(diag) = diags.first_mut() {
1334                    // TODO: should we really use a pos_idx here, or are those fresh TermPos coming
1335                    // out from the YAML parser?
1336                    if let Some(span) = pos_table.get(pos).as_opt_ref() {
1337                        diag.labels
1338                            .push(secondary(span).with_message("deserialized here"));
1339                    }
1340                    diag.notes.push(format!("while parsing {format}"));
1341                }
1342                diags
1343            }
1344            EvalErrorKind::IncomparableValues {
1345                eq_pos,
1346                left,
1347                right,
1348            } => {
1349                let mut labels = Vec::new();
1350
1351                if let Some(span) = pos_table.get(eq_pos).as_opt_ref() {
1352                    labels.push(primary(span).with_message("in this equality comparison"));
1353                }
1354
1355                // Push the label for the right or left argument and return the type of said
1356                // argument.
1357                let mut push_label = |prefix: &str, term: &NickelValue| -> &'static str {
1358                    let type_of = term.type_of().unwrap_or("<unevaluated>");
1359
1360                    labels.push(
1361                        secondary_term(&pos_table, term, files)
1362                            .with_message(format!("{prefix} argument has type {type_of}")),
1363                    );
1364
1365                    type_of
1366                };
1367
1368                let left_type = push_label("left", &left);
1369                let right_type = push_label("right", &right);
1370
1371                vec![
1372                    Diagnostic::error()
1373                        .with_message("cannot compare values for equality")
1374                        .with_labels(labels)
1375                        .with_notes(vec![format!(
1376                            "A {left_type} can't be meaningfully compared with a {right_type}"
1377                        )]),
1378                ]
1379            }
1380            EvalErrorKind::NonExhaustiveEnumMatch {
1381                expected,
1382                found,
1383                pos,
1384            } => {
1385                let tag_list = expected
1386                    .into_iter()
1387                    .map(|tag| {
1388                        // We let the pretty printer handle proper formatting
1389                        NickelValue::enum_variant_posless(tag, None).to_string()
1390                    })
1391                    .collect::<Vec<_>>()
1392                    .join(", ");
1393
1394                let mut labels = Vec::new();
1395
1396                if let Some(span) = pos_table.get(pos).into_opt() {
1397                    labels.push(primary(&span).with_message("in this match expression"));
1398                }
1399
1400                labels.push(
1401                    secondary_term(&pos_table, &found, files)
1402                        .with_message("this value doesn't match any branch"),
1403                );
1404
1405                vec![Diagnostic::error()
1406                    .with_message("unmatched pattern")
1407                    .with_labels(labels)
1408                    .with_notes(vec![
1409                        format!("This match expression isn't exhaustive, matching only the following pattern(s): `{tag_list}`"),
1410                        "But it has been applied to an argument which doesn't match any of those patterns".to_owned(),
1411                    ])]
1412            }
1413            EvalErrorKind::NonExhaustiveMatch { value, pos } => {
1414                let mut labels = Vec::new();
1415
1416                if let Some(span) = pos_table.get(pos).into_opt() {
1417                    labels.push(primary(&span).with_message("in this match expression"));
1418                }
1419
1420                labels.push(
1421                    secondary_term(&pos_table, &value, files)
1422                        .with_message("this value doesn't match any branch"),
1423                );
1424
1425                vec![
1426                    Diagnostic::error()
1427                        .with_message("unmatched pattern")
1428                        .with_labels(labels),
1429                ]
1430            }
1431            EvalErrorKind::FailedDestructuring { value, pattern_pos } => {
1432                let mut labels = Vec::new();
1433
1434                if let Some(span) = pos_table.get(pattern_pos).into_opt() {
1435                    labels.push(primary(&span).with_message("this pattern"));
1436                }
1437
1438                labels.push(
1439                    secondary_term(&pos_table, &value, files)
1440                        .with_message("this value failed to match"),
1441                );
1442
1443                vec![
1444                    Diagnostic::error()
1445                        .with_message("destructuring failed")
1446                        .with_labels(labels),
1447                ]
1448            }
1449            EvalErrorKind::IllegalPolymorphicTailAccess {
1450                action,
1451                label: contract_label,
1452                evaluated_arg,
1453            } => blame_error::blame_diagnostics(
1454                &mut pos_table,
1455                files,
1456                contract_label,
1457                evaluated_arg,
1458                &self.ctxt.call_stack,
1459                &format!(": {}", &action.message()),
1460            ),
1461            EvalErrorKind::UnaryPrimopTypeError {
1462                primop,
1463                expected,
1464                pos_arg,
1465                arg_evaluated,
1466            } => EvalErrorData {
1467                error: EvalErrorKind::TypeError {
1468                    message: format!("{primop} expects its argument to be a {expected}"),
1469                    expected,
1470                    orig_pos: pos_arg,
1471                    term: arg_evaluated,
1472                },
1473                ctxt: EvalCtxt {
1474                    pos_table,
1475                    call_stack: self.ctxt.call_stack,
1476                },
1477            }
1478            .into_diagnostics(files),
1479            EvalErrorKind::NAryPrimopTypeError {
1480                primop,
1481                expected,
1482                arg_number,
1483                pos_arg,
1484                arg_evaluated,
1485                pos_op,
1486            } => {
1487                // The parsing of binary subtraction vs unary negation has
1488                // proven confusing in practice; for example, `add 1 -1` is
1489                // parsed as `(add 1) - 1`, so the `-` is a subtraction and
1490                // triggers a type error because `(add 1)` is not a number.
1491                //
1492                // We attempt to provide a useful hint for this case.
1493                //
1494                // We don't currently attempt to give a good hint for
1495                // `add -1 1` (parsed as `add - (1 1)`) because the evaluation
1496                // error hits in a context (the `(1 1)`) where we don't see
1497                // the `-`.
1498                let minus_pos = if primop == "(-)"
1499                    && arg_number == 1
1500                    && matches!(arg_evaluated.type_of(), Some("Function"))
1501                {
1502                    pos_table.get(pos_op).into_opt()
1503                } else {
1504                    None
1505                };
1506
1507                let diags = EvalErrorData {
1508                    ctxt: EvalCtxt {
1509                        pos_table,
1510                        call_stack: self.ctxt.call_stack,
1511                    },
1512                    error: EvalErrorKind::TypeError {
1513                        message: format!(
1514                            "{primop} expects its {} argument to be a {expected}",
1515                            cardinal(arg_number)
1516                        ),
1517                        expected,
1518                        orig_pos: pos_arg,
1519                        term: arg_evaluated,
1520                    },
1521                }
1522                .into_diagnostics(files);
1523
1524                if let Some(minus_pos) = minus_pos {
1525                    let label = secondary(&minus_pos)
1526                        .with_message("this expression was parsed as a binary subtraction");
1527                    diags
1528                        .into_iter()
1529                        .map(|d| {
1530                            d.with_label(label.clone())
1531                                .with_note(
1532                                    "for unary negation, add parentheses: write `(-42)` instead of `-42`",
1533                                )
1534                        })
1535                        .collect()
1536                } else {
1537                    diags
1538                }
1539            }
1540            EvalErrorKind::QueryNonRecord { pos, id, value } => {
1541                let label = format!(
1542                    "tried to query field `{}`, but the expression has type {}",
1543                    id,
1544                    value.type_of().unwrap_or("<unevaluated>"),
1545                );
1546
1547                let label = if let Some(span) = pos_table.get(pos).into_opt() {
1548                    primary(&span).with_message(label)
1549                } else {
1550                    primary_term(&pos_table, &value, files).with_message(label)
1551                };
1552
1553                vec![
1554                    Diagnostic::error()
1555                        .with_message("tried to query field of a non-record")
1556                        .with_labels(vec![label]),
1557                ]
1558            }
1559        }
1560    }
1561}
1562
1563/// Common functionality for formatting blame errors.
1564mod blame_error {
1565    use codespan_reporting::diagnostic::{Diagnostic, Label};
1566
1567    use crate::{
1568        eval::{callstack::CallStack, value::NickelValue},
1569        files::{FileId, Files},
1570        label::{
1571            self, Polarity,
1572            ty_path::{self, PathSpan},
1573        },
1574        position::{PosIdx, PosTable, TermPos},
1575        typ::Type,
1576    };
1577
1578    use super::{primary, secondary, secondary_term};
1579
1580    /// Returns a title to be used by blame errors based on the `path` and `polarity`
1581    /// of the label.
1582    pub fn title(l: &label::Label) -> String {
1583        if ty_path::has_no_arrow(&l.path) {
1584            // An empty path or a path that contains only fields necessarily corresponds to
1585            // a positive blame
1586            assert_eq!(l.polarity, Polarity::Positive);
1587            match l.field_name {
1588                Some(ident) => format!("contract broken by the value of `{ident}`"),
1589                None => "contract broken by a value".to_owned(),
1590            }
1591        } else if l.polarity == Polarity::Positive {
1592            match l.field_name {
1593                Some(ident) => format!("contract broken by the function `{ident}`"),
1594                None => "contract broken by a function".to_owned(),
1595            }
1596        } else {
1597            match l.field_name {
1598                Some(ident) => format!("contract broken by the caller of `{ident}`"),
1599                None => "contract broken by the caller".to_owned(),
1600            }
1601        }
1602    }
1603
1604    /// Constructs the diagnostic labels used when raising a blame error.
1605    pub fn build_diagnostic_labels(
1606        pos_table: &PosTable,
1607        evaluated_arg: Option<NickelValue>,
1608        blame_label: &label::Label,
1609        path_label: Label<FileId>,
1610        files: &mut Files,
1611    ) -> Vec<Label<FileId>> {
1612        let mut labels = vec![path_label];
1613
1614        if let Some(ref arg_pos) = pos_table.get(blame_label.arg_pos).into_opt() {
1615            // In some cases, if the blame error is located in an argument or return value
1616            // of an higher order functions for example, the original argument position can
1617            // point to the builtin implementation contract like `func` or `record`, so
1618            // there's no good reason to show it. Note than even in that case, the
1619            // information contained at the argument index can still be useful.
1620            if !files.is_stdlib(arg_pos.src_id) {
1621                labels.push(primary(arg_pos).with_message("applied to this expression"));
1622            }
1623        }
1624
1625        // If we have a reference to the element in the cache that was being tested,
1626        // we can try to show more information about the final, evaluated value that is
1627        // responsible for the blame.
1628        if let Some(mut evaluated_arg) = evaluated_arg {
1629            match (
1630                pos_table.get(evaluated_arg.pos_idx()),
1631                pos_table.get(blame_label.arg_pos).as_opt_ref(),
1632            ) {
1633                // Avoid showing a position inside builtin contracts, it's rarely
1634                // informative.
1635                (TermPos::Original(val_pos), _) if files.is_stdlib(val_pos.src_id) => {
1636                    evaluated_arg = evaluated_arg.with_pos_idx(PosIdx::NONE);
1637                    labels.push(
1638                        secondary_term(pos_table, &evaluated_arg, files)
1639                            .with_message("evaluated to this value"),
1640                    );
1641                }
1642                // Do not show the same thing twice: if arg_pos and val_pos are the same,
1643                // the first label "applied to this value" is sufficient.
1644                (TermPos::Original(ref val_pos), Some(arg_pos)) if val_pos == arg_pos => {}
1645                (TermPos::Original(ref val_pos), _) => {
1646                    labels.push(secondary(val_pos).with_message("evaluated to this expression"))
1647                }
1648                // If the final element is a direct reduct of the original value, rather
1649                // print the actual value than referring to the same position as
1650                // before.
1651                (TermPos::Inherited(ref val_pos), Some(arg_pos)) if val_pos == arg_pos => {
1652                    evaluated_arg = evaluated_arg.with_pos_idx(PosIdx::NONE);
1653                    labels.push(
1654                        secondary_term(pos_table, &evaluated_arg, files)
1655                            .with_message("evaluated to this value"),
1656                    );
1657                }
1658                // Finally, if the parameter reduced to a value which originates from a
1659                // different expression, show both the expression and the value.
1660                (TermPos::Inherited(ref val_pos), _) => {
1661                    if !files.is_stdlib(val_pos.src_id) {
1662                        labels
1663                            .push(secondary(val_pos).with_message("evaluated to this expression"));
1664                    }
1665
1666                    evaluated_arg = evaluated_arg.with_pos_idx(PosIdx::NONE);
1667                    labels.push(
1668                        secondary_term(pos_table, &evaluated_arg, files)
1669                            .with_message("evaluated to this value"),
1670                    );
1671                }
1672                (TermPos::None, _) => labels.push(
1673                    secondary_term(pos_table, &evaluated_arg, files)
1674                        .with_message("evaluated to this value"),
1675                ),
1676            }
1677        }
1678
1679        labels
1680    }
1681
1682    pub trait ExtendWithCallStack {
1683        fn extend_with_call_stack(
1684            &mut self,
1685            pos_table: &PosTable,
1686            files: &Files,
1687            call_stack: &CallStack,
1688        );
1689    }
1690
1691    impl ExtendWithCallStack for Vec<Diagnostic<FileId>> {
1692        fn extend_with_call_stack(
1693            &mut self,
1694            pos_table: &PosTable,
1695            files: &Files,
1696            call_stack: &CallStack,
1697        ) {
1698            let (calls, curr_call) = call_stack.group_by_calls(pos_table, files);
1699            let diag_curr_call = curr_call.map(|cdescr| {
1700                let name = cdescr
1701                    .head
1702                    .map(|ident| ident.to_string())
1703                    .unwrap_or_else(|| String::from("<func>"));
1704                Diagnostic::note().with_labels(vec![
1705                    primary(&cdescr.span).with_message(format!("While calling to {name}")),
1706                ])
1707            });
1708            let diags = calls.into_iter().enumerate().map(|(i, cdescr)| {
1709                let name = cdescr
1710                    .head
1711                    .map(|ident| ident.to_string())
1712                    .unwrap_or_else(|| String::from("<func>"));
1713                Diagnostic::note().with_labels(vec![secondary(&cdescr.span).with_message(format!(
1714                    "({}) calling {}",
1715                    i + 1,
1716                    name
1717                ))])
1718            });
1719
1720            self.extend(diag_curr_call);
1721            self.extend(diags);
1722        }
1723    }
1724
1725    /// Calls [`crate::label::ty_path::span`], but if the call returns `None` (the position of the
1726    /// subtype isn't defined), [path_span] pretty-prints the type inside a new source, parses it,
1727    /// and calls `ty_path::span`. This new type is guaranteed to have all of its positions set,
1728    /// providing a definite `PathSpan`. This is similar to the behavior of [`super::primary_alt`].
1729    pub fn path_span<'a, I>(
1730        pos_table: &mut PosTable,
1731        files: &mut Files,
1732        path: I,
1733        ty: &Type,
1734    ) -> PathSpan
1735    where
1736        I: Iterator<Item = &'a ty_path::Elem> + Clone,
1737    {
1738        use crate::parser::{ErrorTolerantParserCompat, grammar::FixedTypeParser, lexer::Lexer};
1739
1740        ty_path::span(path.clone().peekable(), ty)
1741            .or_else(|| {
1742                let type_pprinted = format!("{ty}");
1743                let file_id = files.add(super::UNKNOWN_SOURCE_NAME, type_pprinted.clone());
1744
1745                let (ty_with_pos, _) = FixedTypeParser::new()
1746                    .parse_tolerant_compat(pos_table, file_id, Lexer::new(&type_pprinted))
1747                    .unwrap();
1748
1749                ty_path::span(path.peekable(), &ty_with_pos)
1750            })
1751            .expect(
1752                "path_span: we pretty-printed and parsed again the type of a label, \
1753                so it must have all of its position defined, but `ty_path::span` returned `None`",
1754            )
1755    }
1756
1757    /// Generate a codespan label that describes the [type path][crate::label::ty_path::Path] of a
1758    /// (Nickel) label.
1759    pub fn report_ty_path(
1760        pos_table: &mut PosTable,
1761        files: &mut Files,
1762        l: &label::Label,
1763    ) -> Label<FileId> {
1764        let PathSpan {
1765            span,
1766            last,
1767            last_arrow_elem,
1768        } = path_span(pos_table, files, l.path.iter(), &l.typ);
1769
1770        let msg = match (last, last_arrow_elem) {
1771            // The type path doesn't contain any arrow, and the failing subcontract is the
1772            // contract for the elements of an array
1773            (Some(ty_path::Elem::Array), None) => "expected array element type",
1774            // The type path doesn't contain any arrow, and the failing subcontract is the contract
1775            // for the fields of a dictionary
1776            (Some(ty_path::Elem::Dict), None) => "expected dictionary field type",
1777            // The type path doesn't contain any arrow, and the failing subcontract is the contract
1778            // for the field of a record
1779            (Some(ty_path::Elem::Field(_)), None) => "expected field type",
1780            // The original contract contains an arrow, and the path is only composed of codomains.
1781            // Then polarity is necessarily true and the cause of the blame is the return value of
1782            // the function
1783            (Some(_), Some(ty_path::Elem::Codomain)) if ty_path::has_no_dom(&l.path) => {
1784                "expected return type"
1785            }
1786            // The original contract contains an arrow, the subcontract is the domain of an
1787            // arrow, and the polarity is positive. The function is to be blamed for calling an
1788            // argument on a value of the wrong type.
1789            (Some(_), Some(ty_path::Elem::Domain)) if l.polarity == Polarity::Positive => {
1790                "expected type of an argument of an inner call"
1791            }
1792            // The original contract contains an arrow, the subcontract is the codomain of an
1793            // arrow, and the polarity is positive. The function is to be blamed for calling a
1794            // higher-order function argument on a function which returns a value of the wrong
1795            // type.
1796            (Some(_), Some(ty_path::Elem::Codomain)) if l.polarity == Polarity::Positive => {
1797                "expected return type of a sub-function passed as an argument of an inner call"
1798            }
1799            // The original contract contains an arrow, the subcontract is the domain of an arrow,
1800            // and the polarity is negative. The caller is to be blamed for providing an argument
1801            // of the wrong type.
1802            (Some(_), Some(ty_path::Elem::Domain)) => {
1803                "expected type of the argument provided by the caller"
1804            }
1805            // The original contract contains an arrow, the subcontract is the codomain of an
1806            // arrow, and the polarity is negative. The caller is to be blamed for providing a
1807            // higher-order function argument which returns a value of the wrong type.
1808            (Some(_), Some(ty_path::Elem::Codomain)) => {
1809                "expected return type of a function provided by the caller"
1810            }
1811            // If there is a last arrow element, then there must be last element
1812            (None, Some(_)) => panic!(
1813                "blame error reporting: inconsistent path analysis, last_elem\
1814is None but last_arrow_elem is Some"
1815            ),
1816            _ => "expected type",
1817        };
1818
1819        secondary(&span).with_message(msg.to_owned())
1820    }
1821
1822    /// Generate codespan diagnostics from blame data. Mostly used by `into_diagnostics`
1823    /// implementations.
1824    ///
1825    /// # Parameters
1826    ///
1827    /// The `msg_addendum` is used to customize the main error message. It's inserted between the
1828    /// leading "contract broken by .." and the custom contract diagnostic message in tail
1829    /// position.
1830    pub fn blame_diagnostics(
1831        pos_table: &mut PosTable,
1832        files: &mut Files,
1833        mut label: label::Label,
1834        evaluated_arg: Option<NickelValue>,
1835        call_stack: &CallStack,
1836        msg_addendum: &str,
1837    ) -> Vec<Diagnostic<FileId>> {
1838        use std::fmt::Write;
1839
1840        let mut diagnostics = Vec::new();
1841
1842        // Contract diagnostics are stacked up in order, which means the last one is
1843        // usually the latest/most precise/most relevant. We ignore empty diagnostics and
1844        // iterate in reverse order, to show the most relevant diagnostics first.
1845        let mut contract_diagnostics = std::mem::take(&mut label.diagnostics)
1846            .into_iter()
1847            .rev()
1848            .filter(|diag| !label::ContractDiagnostic::is_empty(diag));
1849        let head_contract_diagnostic = contract_diagnostics.next();
1850
1851        // The addendum and the custom contract diagnostic are important, so we want to display
1852        // them as part of the main error message. However, they can make the message quite long.
1853        // To avoid clutter, we display each component on a new line, indented with respect to the
1854        // initial "error: "
1855        let new_msg_block = "\n       ";
1856        let mut msg = title(&label);
1857
1858        if !msg_addendum.is_empty() {
1859            // unwrap(): write shouldn't fail on a String
1860            write!(&mut msg, "{new_msg_block}{msg_addendum}").unwrap();
1861        }
1862
1863        if let Some(contract_msg) = head_contract_diagnostic
1864            .as_ref()
1865            .and_then(|diag| diag.message.as_ref())
1866        {
1867            // unwrap(): write shouldn't fail on a String
1868            write!(&mut msg, "{new_msg_block}{}", &super::escape(contract_msg)).unwrap();
1869        }
1870
1871        let contract_notes = head_contract_diagnostic
1872            .map(|diag| diag.notes)
1873            .unwrap_or_default();
1874        let path_label = report_ty_path(pos_table, files, &label);
1875
1876        let labels = build_diagnostic_labels(pos_table, evaluated_arg, &label, path_label, files);
1877
1878        // If there are notes in the head contract diagnostic, we build the first
1879        // diagnostic using them and will put potential generated notes on higher-order
1880        // contracts in a following diagnostic.
1881        if !contract_notes.is_empty() {
1882            diagnostics.push(
1883                Diagnostic::error()
1884                    .with_message(msg)
1885                    .with_labels(labels)
1886                    .with_notes(contract_notes),
1887            );
1888        } else {
1889            diagnostics.push(Diagnostic::error().with_message(msg).with_labels(labels));
1890        }
1891
1892        for ctr_diag in contract_diagnostics {
1893            let mut msg = String::from("from a parent contract violation");
1894
1895            if let Some(msg_contract) = ctr_diag.message {
1896                msg.push_str(": ");
1897                msg.push_str(&super::escape(&msg_contract));
1898            }
1899
1900            diagnostics.push(
1901                Diagnostic::note()
1902                    .with_message(msg)
1903                    .with_notes(ctr_diag.notes),
1904            );
1905        }
1906
1907        if !ty_path::has_no_dom(&label.path) {
1908            diagnostics.extend_with_call_stack(pos_table, files, call_stack);
1909        }
1910
1911        diagnostics
1912    }
1913}
1914
1915impl IntoDiagnostics for ParseError {
1916    fn into_diagnostics(self, files: &mut Files) -> Vec<Diagnostic<FileId>> {
1917        let diagnostic = match self {
1918            ParseError::UnexpectedEOF(file_id, _expected) => {
1919                let end = files.source_span(file_id).end;
1920                Diagnostic::error()
1921                    .with_message(format!(
1922                        "unexpected end of file when parsing {}",
1923                        files.name(file_id).to_string_lossy()
1924                    ))
1925                    .with_labels(vec![primary(&RawSpan {
1926                        start: end,
1927                        end,
1928                        src_id: file_id,
1929                    })])
1930            }
1931            ParseError::UnexpectedToken(span, _expected) => Diagnostic::error()
1932                .with_message("unexpected token")
1933                .with_labels(vec![primary(&span)]),
1934            ParseError::ExtraToken(span) => Diagnostic::error()
1935                .with_message("superfluous unexpected token")
1936                .with_labels(vec![primary(&span)]),
1937            ParseError::UnmatchedCloseBrace(span) => Diagnostic::error()
1938                .with_message("unmatched closing brace \'}\'")
1939                .with_labels(vec![primary(&span)]),
1940            ParseError::InvalidEscapeSequence(span) => Diagnostic::error()
1941                .with_message("invalid escape sequence")
1942                .with_labels(vec![primary(&span)]),
1943            ParseError::InvalidAsciiEscapeCode(span) => Diagnostic::error()
1944                .with_message("invalid ascii escape code")
1945                .with_labels(vec![primary(&span)]),
1946            ParseError::InvalidUnicodeEscapeCode(span) => Diagnostic::error()
1947                .with_message("invalid unicode escape code")
1948                .with_labels(vec![primary(&span)]),
1949            ParseError::StringDelimiterMismatch {
1950                opening_delimiter,
1951                closing_delimiter,
1952            } => Diagnostic::error()
1953                .with_message("string closing delimiter has too many `%`")
1954                .with_labels(vec![
1955                    primary(&closing_delimiter).with_message("the closing delimiter"),
1956                    secondary(&opening_delimiter).with_message("the opening delimiter"),
1957                ])
1958                .with_notes(vec![
1959                    "A special string must be opened and closed with the same number of `%` \
1960                    in the corresponding delimiters."
1961                        .into(),
1962                    "Try removing the superflous `%` in the closing delimiter".into(),
1963                ]),
1964            ParseError::ExternalFormatError(format, msg, span_opt) => {
1965                let labels = span_opt
1966                    .as_ref()
1967                    .map(|span| vec![primary(span)])
1968                    .unwrap_or_default();
1969
1970                Diagnostic::error()
1971                    .with_message(format!("{format} parse error: {msg}"))
1972                    .with_labels(labels)
1973            }
1974            ParseError::UnboundTypeVariables(idents) => Diagnostic::error()
1975                .with_message(format!(
1976                    "unbound type variable(s): {}",
1977                    idents
1978                        .iter()
1979                        .map(|x| format!("`{x}`"))
1980                        .collect::<Vec<_>>()
1981                        .join(",")
1982                ))
1983                .with_labels(
1984                    idents
1985                        .into_iter()
1986                        .filter_map(|id| id.pos.into_opt())
1987                        .map(|span| primary(&span).with_message("this identifier is unbound"))
1988                        .collect(),
1989                ),
1990            ParseError::InvalidRecordType {
1991                record_span,
1992                tail_span,
1993                cause,
1994            } => {
1995                let mut labels: Vec<_> = std::iter::once(primary(&record_span))
1996                    .chain(cause.labels())
1997                    .collect();
1998                let mut notes: Vec<_> = std::iter::once(
1999                    "A record type is a literal composed only of type annotations, of the \
2000                        form `<field>: <type>`."
2001                        .into(),
2002                )
2003                .chain(cause.notes())
2004                .collect();
2005
2006                if let Some(tail_span) = tail_span {
2007                    labels.push(secondary(&tail_span).with_message("tail"));
2008                    notes.push(
2009                        "This literal was interpreted as a record type because it has a \
2010                        polymorphic tail; record values cannot have tails."
2011                            .into(),
2012                    );
2013                } else {
2014                    notes.push(
2015                        "This literal was interpreted as a record type because it has \
2016                        fields with type annotations but no value definitions; to make \
2017                        this a record value, assign values to its fields."
2018                            .into(),
2019                    );
2020                };
2021                Diagnostic::error()
2022                    .with_message("invalid record literal")
2023                    .with_labels(labels)
2024                    .with_notes(notes)
2025            }
2026            ParseError::RecursiveLetPattern(span) => Diagnostic::error()
2027                .with_message("recursive destructuring is not supported")
2028                .with_labels(vec![primary(&span)])
2029                .with_notes(vec![
2030                    "A destructuring let-binding can't be recursive. Try removing the `rec` \
2031                        from `let rec`."
2032                        .into(),
2033                    "You can reference other fields of a record recursively \
2034                        from within a field, so you might not need the recursive let."
2035                        .into(),
2036                ]),
2037            ParseError::PatternInLetBlock(span) => Diagnostic::error()
2038                .with_message("destructuring patterns are not currently permitted in let blocks")
2039                .with_labels(vec![primary(&span)])
2040                .with_notes(vec!["Try re-writing your let block as nested `let ... in` expressions.".into()]),
2041            ParseError::TypeVariableKindMismatch { ty_var, span } => Diagnostic::error()
2042                .with_message(format!(
2043                    "the type variable `{ty_var}` is used in conflicting ways"
2044                ))
2045                .with_labels(vec![primary(&span)])
2046                .with_notes(vec![
2047                    "Type variables may be used either as types, polymorphic record tails, \
2048                    or polymorphic enum tails."
2049                        .into(),
2050                    "Using the same type variable as more than one category at the same time \
2051                    is forbidden."
2052                        .into(),
2053                ]),
2054            ParseError::TypedFieldWithoutDefinition {
2055                field_span,
2056                annot_span,
2057            } => Diagnostic::error()
2058                .with_message("statically typed field without a definition")
2059                .with_labels(vec![
2060                    primary(&field_span).with_message("this field doesn't have a definition"),
2061                    secondary(&annot_span).with_message("but it has a type annotation"),
2062                ])
2063                .with_notes(vec![
2064                    "A static type annotation must be attached to an expression but \
2065                    this field doesn't have a definition."
2066                        .into(),
2067                    "Did you mean to use `|` instead of `:`, for example when defining a \
2068                    record contract?"
2069                        .into(),
2070                    "Typed fields without definitions are only allowed inside \
2071                    record types, but the enclosing record literal doesn't qualify as a \
2072                    record type. Please refer to the manual for the defining conditions of a \
2073                    record type."
2074                        .into(),
2075                ]),
2076            ParseError::InterpolationInStaticPath {
2077                input: _,
2078                path_elem_span,
2079            } => Diagnostic::error()
2080                .with_message("string interpolation is forbidden within a query")
2081                .with_labels(vec![primary(&path_elem_span)])
2082                .with_notes(vec![
2083                    "Field paths don't support string interpolation when querying \
2084                        metadata."
2085                        .into(),
2086                    "Only identifiers and simple string literals are allowed.".into(),
2087                ]),
2088            ParseError::DuplicateIdentInRecordPattern { ident, prev_ident } => Diagnostic::error()
2089                .with_message(format!(
2090                    "duplicated binding `{}` in record pattern",
2091                    ident.label()
2092                ))
2093                .with_labels(vec![
2094                    secondary(&prev_ident.pos.unwrap()).with_message("previous binding here"),
2095                    primary(&ident.pos.unwrap()).with_message("duplicated binding here"),
2096                ]),
2097            ParseError::DuplicateIdentInLetBlock { ident, prev_ident } => Diagnostic::error()
2098                .with_message(format!(
2099                    "duplicated binding `{}` in let block",
2100                    ident.label()
2101                ))
2102                .with_labels(vec![
2103                    secondary(&prev_ident.pos.unwrap()).with_message("previous binding here"),
2104                    primary(&ident.pos.unwrap()).with_message("duplicated binding here"),
2105                ]),
2106            ParseError::DisabledFeature { feature, span } => Diagnostic::error()
2107                .with_message("interpreter compiled without required features")
2108                .with_labels(vec![primary(&span).with_message(format!(
2109                    "this syntax is only supported with the `{feature}` feature enabled"
2110                ))])
2111                .with_notes(vec![format!(
2112                    "Recompile nickel with `--features {}`",
2113                    feature
2114                )]),
2115            ParseError::InvalidContract(span) => Diagnostic::error()
2116                .with_message("invalid contract expression")
2117                .with_labels(vec![primary(&span).with_message("this can't be used as a contract")])
2118                .with_notes(vec![
2119                    "This expression is used as a contract as part of an annotation or a type expression."
2120                        .to_owned(),
2121                    "Only functions and records might be valid contracts".to_owned(),
2122                ]),
2123            ParseError::InvalidImportFormat{span} => Diagnostic::error()
2124                .with_message("unknown import format tag")
2125                .with_labels(vec![primary(&span)])
2126                .with_notes(vec![
2127                    "Examples of valid format tags: 'Nickel, 'Json, 'Yaml, 'Toml, 'Text"
2128                        .to_owned()
2129                ]),
2130            ParseError::UnknownSigilSelector { selector, span } => {
2131                Diagnostic::error()
2132                .with_message(format!("unknown sigil selector `{selector}`"))
2133                .with_labels(vec![primary(&span)])
2134                .with_note("Available selectors are currently: `env`")
2135            }
2136            ParseError::UnknownSigilAttribute { selector, attribute, span } => {
2137                Diagnostic::error()
2138                .with_message(format!("unknown sigil attribute `{attribute}`"))
2139                .with_labels(vec![primary(&span).with_message(format!("unknown attribute for sigil selector `{selector}`"))])
2140                .with_note(available_sigil_attrs_note(&selector))
2141            }
2142            ParseError::SigilExprMissingColon(span) => {
2143                Diagnostic::error()
2144                .with_message("missing sigil expression separator `:`")
2145                .with_labels(vec![primary(&span)])
2146                .with_notes(vec![
2147                    "The CLI sigil expression syntax is `@<selector>:<argument>` or `@<selector>/<attribute>:<argument>`".to_owned(),
2148                    "The provided sigil expression is missing the `:` separator.".to_owned(),
2149                ])
2150            }
2151            ParseError::MultipleFieldDecls { ident, include_span, other_span } => Diagnostic::error()
2152                .with_message(format!(
2153                    "multiple declarations for included field `{ident}`",
2154                ))
2155                .with_labels(vec![
2156                    primary(&include_span).with_message("included here"),
2157                    secondary(&other_span).with_message("but also declared here"),
2158                ])
2159                .with_notes(vec![
2160                    "Piecewise definitions involving an included field are currently not supported".to_owned()
2161                ]),
2162        };
2163
2164        vec![diagnostic]
2165    }
2166}
2167
2168/// Returns the available attributes for each supported sigil
2169// It's currently trivial, but might be expanded in the future
2170fn available_sigil_attrs_note(selector: &str) -> String {
2171    format!(
2172        "No attributes are available for sigil selector `{selector}`. Use the selector directly as in `@{selector}:<argument>`"
2173    )
2174}
2175
2176impl IntoDiagnostics for TypecheckErrorData {
2177    fn into_diagnostics(self, files: &mut Files) -> Vec<Diagnostic<FileId>> {
2178        self.borrow_error().into_diagnostics(files)
2179    }
2180}
2181
2182impl<'ast> IntoDiagnostics for &'_ TypecheckErrorKind<'ast> {
2183    fn into_diagnostics(self, files: &mut Files) -> Vec<Diagnostic<FileId>> {
2184        fn mk_expr_label(span_opt: &TermPos) -> Vec<Label<FileId>> {
2185            mk_expr_label_with_msg(span_opt, "this expression".into())
2186        }
2187
2188        fn mk_expr_label_with_msg(span_opt: &TermPos, msg: String) -> Vec<Label<FileId>> {
2189            span_opt
2190                .as_opt_ref()
2191                .map(|span| vec![primary(span).with_message(msg)])
2192                .unwrap_or_default()
2193        }
2194
2195        fn mk_expected_msg<T: std::fmt::Display>(expected: &T) -> String {
2196            format!("Expected an expression of type `{expected}`")
2197        }
2198
2199        fn mk_inferred_msg<T: std::fmt::Display>(inferred: &T) -> String {
2200            format!("Found an expression of type `{inferred}`")
2201        }
2202
2203        match self {
2204            TypecheckErrorKind::UnboundIdentifier(id) =>
2205            // Use the same diagnostic as `EvalError::UnboundIdentifier` for consistency.
2206            {
2207                EvalErrorData {
2208                    ctxt: Default::default(),
2209                    error: EvalErrorKind::UnboundIdentifier(*id, id.pos),
2210                }
2211                .into_diagnostics(files)
2212            }
2213            TypecheckErrorKind::MissingRow {
2214                id,
2215                kind: RowKind::Record,
2216                expected,
2217                inferred,
2218                pos,
2219            } => {
2220                let id_access = match &expected.typ {
2221                    TypeF::Record(r) => {
2222                        r.find_row(id.ident()).and_then(|row| row.id.pos.into_opt())
2223                    }
2224                    _ => None,
2225                };
2226                let mut labels = mk_expr_label_with_msg(pos, format!("this record lacks `{id}`"));
2227                if let Some(id_access) = id_access {
2228                    labels
2229                        .push(secondary(&id_access).with_message(format!("`{id}` required here")));
2230                }
2231                vec![
2232                    Diagnostic::error()
2233                        .with_message(format!("type error: missing field `{id}`"))
2234                        .with_labels(labels)
2235                        .with_notes(vec![format!(
2236                            "Attempted to access field `{id}` on an expression of type `{inferred}`"
2237                        )]),
2238                ]
2239            }
2240            TypecheckErrorKind::MissingRow {
2241                id,
2242                kind: RowKind::Enum,
2243                expected,
2244                inferred,
2245                pos,
2246            } => vec![
2247                Diagnostic::error()
2248                    .with_message(format!("type error: missing row `{id}`"))
2249                    .with_labels(mk_expr_label(pos))
2250                    .with_notes(vec![
2251                        format!(
2252                            "{}, which contains the field `{id}`",
2253                            mk_expected_msg(expected)
2254                        ),
2255                        format!(
2256                            "{}, which does not contain the field `{id}`",
2257                            mk_inferred_msg(inferred)
2258                        ),
2259                    ]),
2260            ],
2261            TypecheckErrorKind::MissingDynTail {
2262                expected,
2263                inferred,
2264                pos,
2265            } => vec![
2266                Diagnostic::error()
2267                    .with_message(String::from("type error: missing dynamic tail `; Dyn`"))
2268                    .with_labels(mk_expr_label(pos))
2269                    .with_notes(vec![
2270                        format!(
2271                            "{}, which contains the tail `; Dyn`",
2272                            mk_expected_msg(expected)
2273                        ),
2274                        format!(
2275                            "{}, which does not contain the tail `; Dyn`",
2276                            mk_inferred_msg(inferred)
2277                        ),
2278                    ]),
2279            ],
2280            TypecheckErrorKind::ExtraRow {
2281                id,
2282                expected,
2283                inferred,
2284                pos,
2285            } => vec![
2286                Diagnostic::error()
2287                    .with_message(format!("type error: extra row `{id}`"))
2288                    .with_labels(mk_expr_label(pos))
2289                    .with_notes(vec![
2290                        format!(
2291                            "{}, which does not contain the field `{id}`",
2292                            mk_expected_msg(expected)
2293                        ),
2294                        format!(
2295                            "{}, which contains the extra field `{id}`",
2296                            mk_inferred_msg(inferred)
2297                        ),
2298                    ]),
2299            ],
2300            TypecheckErrorKind::ExtraDynTail {
2301                expected,
2302                inferred,
2303                pos,
2304            } => vec![
2305                Diagnostic::error()
2306                    .with_message(String::from("type error: extra dynamic tail `; Dyn`"))
2307                    .with_labels(mk_expr_label(pos))
2308                    .with_notes(vec![
2309                        format!(
2310                            "{}, which does not contain the tail `; Dyn`",
2311                            mk_expected_msg(expected)
2312                        ),
2313                        format!(
2314                            "{}, which contains the extra tail `; Dyn`",
2315                            mk_inferred_msg(inferred)
2316                        ),
2317                    ]),
2318            ],
2319            TypecheckErrorKind::UnboundTypeVariable(ident) => {
2320                vec![Diagnostic::error()
2321                    .with_message(format!("unbound type variable `{ident}`"))
2322                    .with_labels(vec![primary_alt(
2323                        ident.pos.into_opt(),
2324                        ident.to_string(),
2325                        files,
2326                    )
2327                    .with_message("this type variable is unbound")])
2328                    .with_notes(vec![format!(
2329                    "Did you forget to put a `forall {ident}.` somewhere in the enclosing type?"
2330                )])]
2331            }
2332            TypecheckErrorKind::TypeMismatch {
2333                expected,
2334                inferred,
2335                pos,
2336            } => {
2337                fn addendum<'ast>(ty: &Type<'ast>) -> &'static str {
2338                    if ty.typ.is_contract() {
2339                        " (a contract)"
2340                    } else {
2341                        ""
2342                    }
2343                }
2344                let last_note = if expected.typ.is_contract() ^ inferred.typ.is_contract() {
2345                    "Static types and contracts are not compatible"
2346                } else {
2347                    "These types are not compatible"
2348                };
2349
2350                vec![
2351                    Diagnostic::error()
2352                        .with_message("incompatible types")
2353                        .with_labels(mk_expr_label(pos))
2354                        .with_notes(vec![
2355                            format!("{}{}", mk_expected_msg(expected), addendum(expected),),
2356                            format!("{}{}", mk_inferred_msg(inferred), addendum(inferred),),
2357                            String::from(last_note),
2358                        ]),
2359                ]
2360            }
2361            TypecheckErrorKind::RecordRowMismatch {
2362                id,
2363                expected,
2364                inferred,
2365                cause: err,
2366                pos,
2367            } => {
2368                let mut err = err;
2369                // If the unification error is on a nested field, we will have a succession of
2370                // `RowMismatch` errors wrapping the underlying error. In this case, instead of
2371                // showing a cascade of similar error messages, we determine the full path of the
2372                // nested field (e.g. `pkg.subpkg1.meta.url`) and only show once the row mismatch
2373                // error followed by the underlying error.
2374                let mut path = vec![id.ident()];
2375
2376                while let TypecheckErrorKind::RecordRowMismatch {
2377                    id: id_next,
2378                    cause: next,
2379                    ..
2380                } = &**err
2381                {
2382                    path.push(id_next.ident());
2383                    err = next;
2384                }
2385
2386                let path_str: Vec<String> = path
2387                    .clone()
2388                    .into_iter()
2389                    .map(|ident| format!("{ident}"))
2390                    .collect();
2391                let field = path_str.join(".");
2392
2393                let mk_expected_row_msg = |field, ty| {
2394                    format!("Expected an expression of a record type with the row `{field}: {ty}`")
2395                };
2396                let mk_inferred_row_msg = |field, ty| {
2397                    format!("Found an expression of a record type with the row `{field}: {ty}`")
2398                };
2399
2400                //TODO: we should rather have RowMismatch hold a rows, instead of a general type,
2401                //than doing this match.
2402                let note1 = if let TypeF::Record(rrows) = &expected.typ {
2403                    match rrows.find_path(path.as_slice()) {
2404                        Some(row) => mk_expected_row_msg(&field, row.typ),
2405                        None => mk_expected_msg(&expected),
2406                    }
2407                } else {
2408                    mk_expected_msg(&expected)
2409                };
2410
2411                let note2 = if let TypeF::Record(rrows) = &inferred.typ {
2412                    match rrows.find_path(path.as_slice()) {
2413                        Some(row) => mk_inferred_row_msg(&field, row.typ),
2414                        None => mk_inferred_msg(&inferred),
2415                    }
2416                } else {
2417                    mk_inferred_msg(inferred)
2418                };
2419
2420                let mut diags = vec![
2421                    Diagnostic::error()
2422                        .with_message("incompatible record rows declaration")
2423                        .with_labels(mk_expr_label(pos))
2424                        .with_notes(vec![
2425                            note1,
2426                            note2,
2427                            format!("Could not match the two declarations of `{field}`"),
2428                        ]),
2429                ];
2430
2431                // We generate a diagnostic for the underlying error, but append a prefix to the
2432                // error message to make it clear that this is not a separate error but a more
2433                // precise description of why the unification of a row failed.
2434                diags.extend(err.into_diagnostics(files).into_iter().map(|mut diag| {
2435                    diag.message = format!("while typing field `{}`: {}", field, diag.message);
2436                    diag
2437                }));
2438                diags
2439            }
2440            TypecheckErrorKind::EnumRowMismatch {
2441                id,
2442                expected,
2443                inferred,
2444                cause,
2445                pos,
2446            } => {
2447                let mk_expected_row_msg = |row| {
2448                    format!("Expected an expression of an enum type with the enum row `{row}`")
2449                };
2450                let mk_inferred_row_msg =
2451                    |row| format!("Found an expression of an enum type with the enum row `{row}`");
2452
2453                //TODO: we should rather have RowMismatch hold enum rows, instead of a general
2454                //type, to avoid doing this match.
2455                let note1 = if let TypeF::Enum(erows) = &expected.typ {
2456                    if let Some(row) = erows.find_row(id.ident()) {
2457                        mk_expected_row_msg(row)
2458                    } else {
2459                        mk_expected_msg(expected)
2460                    }
2461                } else {
2462                    mk_expected_msg(expected)
2463                };
2464
2465                let note2 = if let TypeF::Enum(erows) = &inferred.typ {
2466                    if let Some(row) = erows.find_row(id.ident()) {
2467                        mk_inferred_row_msg(row)
2468                    } else {
2469                        mk_inferred_msg(expected)
2470                    }
2471                } else {
2472                    mk_inferred_msg(inferred)
2473                };
2474
2475                let mut diags = vec![
2476                    Diagnostic::error()
2477                        .with_message("incompatible enum rows declaration")
2478                        .with_labels(mk_expr_label(pos))
2479                        .with_notes(vec![
2480                            note1,
2481                            note2,
2482                            format!("Could not match the two declarations of `{id}`"),
2483                        ]),
2484                ];
2485
2486                // We generate a diagnostic for the underlying error if any, but append a prefix to
2487                // the error message to make it clear that this is not a separate error but a more
2488                // precise description of why the unification of a row failed.
2489                if let Some(err) = cause {
2490                    diags.extend((*err).into_diagnostics(files).into_iter().map(|mut diag| {
2491                        diag.message = format!("while typing enum row `{id}`: {}", diag.message);
2492                        diag
2493                    }));
2494                }
2495
2496                diags
2497            }
2498            TypecheckErrorKind::RecordRowConflict {
2499                row,
2500                expected,
2501                inferred,
2502                pos,
2503            } => {
2504                let mut diags = Vec::new();
2505
2506                diags.push(
2507                    Diagnostic::error()
2508                        .with_message("multiple record row declarations")
2509                        .with_labels(mk_expr_label(pos))
2510                        .with_notes(vec![
2511                            format!("Found an expression with the row `{row}`"),
2512                            format!(
2513                                "But this row appears inside another record type, \
2514                                which already has a diffent declaration for the field `{}`",
2515                                row.id
2516                            ),
2517                            String::from(
2518                                "A type cannot have two conflicting declarations for the same row",
2519                            ),
2520                        ]),
2521                );
2522
2523                diags.push(
2524                    Diagnostic::note()
2525                        .with_message("while matching types")
2526                        .with_notes(vec![
2527                            format!("Expected type {expected}"),
2528                            format!("With inferred type {inferred}"),
2529                        ]),
2530                );
2531
2532                diags
2533            }
2534            TypecheckErrorKind::EnumRowConflict {
2535                row,
2536                expected,
2537                inferred,
2538                pos,
2539            } => {
2540                let mut diags = Vec::new();
2541
2542                diags.push(
2543                    Diagnostic::error()
2544                        .with_message("multiple enum row declarations")
2545                        .with_labels(mk_expr_label(pos))
2546                        .with_notes(vec![
2547                            format!("Found an expression with the row `{row}`"),
2548                            format!(
2549                                "But this row appears inside another enum type, \
2550                                which already has a diffent declaration for the tag `{}`",
2551                                row.id
2552                            ),
2553                            String::from(
2554                                "A type cannot have two conflicting declarations for the same row",
2555                            ),
2556                        ]),
2557                );
2558
2559                diags.push(
2560                    Diagnostic::note()
2561                        .with_message("while matching types")
2562                        .with_notes(vec![
2563                            format!("Expected type {expected}"),
2564                            format!("With inferred type {inferred}"),
2565                        ]),
2566                );
2567
2568                diags
2569            }
2570            TypecheckErrorKind::ArrowTypeMismatch {
2571                expected,
2572                inferred,
2573                type_path,
2574                cause,
2575                pos,
2576            } => {
2577                // We locally convert the type to their runtime representation as a way to reuse
2578                // the code of `blame_error::path_span`, which works on the latter.
2579                let mut pos_table = PosTable::new();
2580                let expected_mline = expected.to_mainline(&mut pos_table);
2581                let inferred_mline = inferred.to_mainline(&mut pos_table);
2582
2583                let PathSpan {
2584                    span: expd_span, ..
2585                } = blame_error::path_span(
2586                    &mut pos_table,
2587                    files,
2588                    type_path.iter(),
2589                    &expected_mline,
2590                );
2591                let PathSpan {
2592                    span: actual_span, ..
2593                } = blame_error::path_span(
2594                    &mut pos_table,
2595                    files,
2596                    type_path.iter(),
2597                    &inferred_mline,
2598                );
2599
2600                let mut labels = vec![
2601                    secondary(&expd_span).with_message("this part of the expected type"),
2602                    secondary(&actual_span)
2603                        .with_message("does not match this part of the inferred type"),
2604                ];
2605                labels.extend(mk_expr_label(pos));
2606
2607                let mut diags = vec![
2608                    Diagnostic::error()
2609                        .with_message("function types mismatch")
2610                        .with_labels(labels)
2611                        .with_notes(vec![
2612                            mk_expected_msg(expected),
2613                            mk_inferred_msg(inferred),
2614                            String::from("Could not match the two function types"),
2615                        ]),
2616                ];
2617
2618                // We generate a diagnostic for the underlying error, but append a prefix to the
2619                // error message to make it clear that this is not a separated error but a more
2620                // precise description of why the unification of the row failed.
2621                match &**cause {
2622                    // If the underlying error is a type mismatch, printing won't add any useful
2623                    // information, so we just ignore it.
2624                    TypecheckErrorKind::TypeMismatch { .. } => (),
2625                    error => {
2626                        diags.extend(error.into_diagnostics(files).into_iter().map(|mut diag| {
2627                            diag.message =
2628                                format!("while matching function types: {}", diag.message);
2629                            diag
2630                        }));
2631                    }
2632                }
2633
2634                diags
2635            }
2636            TypecheckErrorKind::ForallParametricityViolation {
2637                kind,
2638                tail,
2639                violating_type,
2640                pos,
2641            } => {
2642                let tail_kind = match kind {
2643                    VarKindDiscriminant::Type => "type",
2644                    VarKindDiscriminant::EnumRows => "enum tail",
2645                    VarKindDiscriminant::RecordRows => "record tail",
2646                };
2647                vec![
2648                    Diagnostic::error()
2649                        .with_message(format!(
2650                            "values of type `{violating_type}` are not guaranteed to be compatible \
2651                        with polymorphic {tail_kind} `{tail}`"
2652                        ))
2653                        .with_labels(mk_expr_label(pos))
2654                        .with_notes(vec![
2655                        "Type variables introduced in a `forall` range over all possible types."
2656                            .to_owned(),
2657                    ]),
2658                ]
2659            }
2660            TypecheckErrorKind::CtrTypeInTermPos { contract, pos_type } => {
2661                vec![Diagnostic::error()
2662                    .with_message(
2663                        "types containing user-defined contracts cannot be converted into contracts"
2664                    )
2665                    .with_labels(
2666                        pos_type.as_opt_ref()
2667                            .map(|span| {
2668                                primary(span).with_message("This type (in contract position)")
2669                            })
2670                            .into_iter()
2671                            .chain(contract.pos.as_opt_ref().map(|span| {
2672                                secondary(span).with_message("contains this user-defined contract")
2673                            }))
2674                            .collect(),
2675                    )]
2676            }
2677            TypecheckErrorKind::VarLevelMismatch {
2678                type_var: constant,
2679                pos,
2680            } => {
2681                let mut labels = mk_expr_label(pos);
2682
2683                if let Some(span) = constant.pos.as_opt_ref() {
2684                    labels.push(secondary(span).with_message("this polymorphic type variable"));
2685                }
2686
2687                vec![
2688                    Diagnostic::error()
2689                        .with_message("invalid polymorphic generalization".to_string())
2690                        .with_labels(labels)
2691                        .with_notes(vec![
2692                            "While the type of this expression is still undetermined, it appears \
2693                            indirectly in the type of another expression introduced before \
2694                            the `forall` block."
2695                                .into(),
2696                            format!(
2697                                "The type of this expression escapes the scope of the \
2698                                corresponding `forall` and can't be generalized to the \
2699                                polymorphic type variable `{constant}`"
2700                            ),
2701                        ]),
2702                ]
2703            }
2704            TypecheckErrorKind::InhomogeneousRecord {
2705                pos,
2706                row_a: expected,
2707                row_b: inferred,
2708            } => {
2709                vec![Diagnostic::error()
2710                    .with_message("incompatible types")
2711                    .with_labels(mk_expr_label(pos))
2712                    .with_notes(vec![
2713                        "Expected a dictionary type".into(),
2714                        format!("Found a record with a field of type {expected} and a field of type {inferred}"),
2715                        "Records are compatible with dicts only if all their fields have the same type".into(),
2716                    ])]
2717            }
2718            TypecheckErrorKind::OrPatternVarsMismatch { var, pos } => {
2719                let mut labels = vec![
2720                    primary_alt(var.pos.into_opt(), var.into_label(), files)
2721                        .with_message("this variable must occur in all branches"),
2722                ];
2723
2724                if let Some(span) = pos.as_opt_ref() {
2725                    labels.push(secondary(span).with_message("in this or-pattern"));
2726                }
2727
2728                vec![
2729                    Diagnostic::error()
2730                        .with_message("or-pattern variable mismatch".to_string())
2731                        .with_labels(labels)
2732                        .with_notes(vec![
2733                        "All branches of an or-pattern must bind exactly the same set of variables"
2734                            .into(),
2735                    ]),
2736                ]
2737            }
2738            // clone() here is unfortunate, but I haven't found a better way to interface typecheck
2739            // errors - which can generate a diagnostic by reference - from other errors (import
2740            // errors can themselves hide parsing errors), where `into_diagnostics` consume `self`
2741            // in the current implementation. Maybe we should migrate from `into_diagnostics` to
2742            // `to_diagnostic`, taking the error by reference, but this might cause more copying.
2743            TypecheckErrorKind::ImportError(err) => err.clone().into_diagnostics(files),
2744        }
2745    }
2746}
2747
2748impl IntoDiagnostics for ImportErrorKind {
2749    fn into_diagnostics(self, files: &mut Files) -> Vec<Diagnostic<FileId>> {
2750        match self {
2751            ImportErrorKind::IOError(path, error, span_opt) => {
2752                let labels = span_opt
2753                    .as_opt_ref()
2754                    .map(|span| vec![secondary(span).with_message("imported here")])
2755                    .unwrap_or_default();
2756
2757                vec![
2758                    Diagnostic::error()
2759                        .with_message(format!("import of {path} failed: {error}"))
2760                        .with_labels(labels),
2761                ]
2762            }
2763            ImportErrorKind::ParseErrors(error, span_opt) => {
2764                let mut diagnostic: Vec<Diagnostic<FileId>> = error
2765                    .errors
2766                    .into_iter()
2767                    .flat_map(|e| e.into_diagnostics(files))
2768                    .collect();
2769
2770                if let Some(span) = span_opt.as_opt_ref() {
2771                    diagnostic[0]
2772                        .labels
2773                        .push(secondary(span).with_message("imported here"));
2774                }
2775
2776                diagnostic
2777            }
2778            ImportErrorKind::MissingDependency {
2779                parent,
2780                missing,
2781                pos,
2782            } => {
2783                let labels = pos
2784                    .as_opt_ref()
2785                    .map(|span| vec![primary(span).with_message("imported here")])
2786                    .unwrap_or_default();
2787                let msg = if let Some(parent_path) = parent.as_deref() {
2788                    format!(
2789                        "unknown package {missing}, imported from package {}",
2790                        parent_path.display()
2791                    )
2792                } else {
2793                    format!("unknown package {missing}")
2794                };
2795
2796                vec![Diagnostic::error().with_message(msg).with_labels(labels)]
2797            }
2798            ImportErrorKind::NoPackageMap { pos } => {
2799                let labels = pos
2800                    .as_opt_ref()
2801                    .map(|span| vec![primary(span).with_message("imported here")])
2802                    .unwrap_or_default();
2803                vec![
2804                    Diagnostic::error()
2805                        .with_message(
2806                            "tried to import from a package, but no package manifest found",
2807                        )
2808                        .with_labels(labels)
2809                        .with_notes(vec![
2810                            "did you forget a --manifest-path argument?".to_owned(),
2811                        ]),
2812                ]
2813            }
2814        }
2815    }
2816}
2817
2818impl IntoDiagnostics for ExportErrorData {
2819    fn into_diagnostics(self, files: &mut Files) -> Vec<Diagnostic<FileId>> {
2820        let mut notes = if !self.data.path.0.is_empty() {
2821            vec![format!("When exporting field `{}`", self.data.path)]
2822        } else {
2823            vec![]
2824        };
2825
2826        let pos_table = self.pos_table;
2827
2828        match self.data.error {
2829            ExportErrorKind::NotAString(rt) => vec![
2830                Diagnostic::error()
2831                    .with_message(format!(
2832                        "raw export expects a String value, but got {}",
2833                        rt.type_of().unwrap_or("<unevaluated>")
2834                    ))
2835                    .with_labels(vec![primary_term(&pos_table, &rt, files)])
2836                    .with_notes(notes),
2837            ],
2838            ExportErrorKind::UnsupportedNull(format, rt) => vec![
2839                Diagnostic::error()
2840                    .with_message(format!("{format} format doesn't support null values"))
2841                    .with_labels(vec![primary_term(&pos_table, &rt, files)])
2842                    .with_notes(notes),
2843            ],
2844            ExportErrorKind::NonSerializable(rt) => {
2845                notes.extend([
2846                    "Nickel only supports serializing to and from strings, booleans, numbers, \
2847                    enum tags, `null` (depending on the format), as well as records and arrays \
2848                    of serializable values."
2849                        .into(),
2850                    "Functions and special values (such as contract labels) aren't \
2851                    serializable."
2852                        .into(),
2853                    "If you want serialization to ignore a specific value, please use the \
2854                    `not_exported` metadata."
2855                        .into(),
2856                ]);
2857
2858                vec![
2859                    Diagnostic::error()
2860                        .with_message("non serializable term")
2861                        .with_labels(vec![primary_term(&pos_table, &rt, files)])
2862                        .with_notes(notes),
2863                ]
2864            }
2865            ExportErrorKind::NoDocumentation(rt) => {
2866                notes.push("documentation can only be collected from a record.".to_owned());
2867
2868                vec![
2869                    Diagnostic::error()
2870                        .with_message("no documentation found")
2871                        .with_labels(vec![primary_term(&pos_table, &rt, files)])
2872                        .with_notes(notes),
2873                ]
2874            }
2875            ExportErrorKind::NumberOutOfRange { term, value } => {
2876                notes.push(format!(
2877                    "Only numbers in the range {:e} to {:e} can be portably serialized",
2878                    f64::MIN,
2879                    f64::MAX
2880                ));
2881
2882                vec![
2883                    Diagnostic::error()
2884                        .with_message(format!(
2885                            "The number {} is too large (in absolute value) to be serialized.",
2886                            value.to_sci()
2887                        ))
2888                        .with_labels(vec![primary_term(&pos_table, &term, files)])
2889                        .with_notes(notes),
2890                ]
2891            }
2892            ExportErrorKind::Other(msg) => {
2893                notes.push(msg);
2894
2895                vec![
2896                    Diagnostic::error()
2897                        .with_message("serialization failed")
2898                        .with_notes(notes),
2899                ]
2900            }
2901            ExportErrorKind::ExpectedArray { value } => {
2902                vec![
2903                    Diagnostic::error()
2904                        .with_message("yaml-documents export expects an array")
2905                        .with_labels(vec![primary_term(&pos_table, &value, files)])
2906                        .with_notes(notes),
2907                ]
2908            }
2909        }
2910    }
2911}
2912
2913impl IntoDiagnostics for IOError {
2914    fn into_diagnostics(self, _fil: &mut Files) -> Vec<Diagnostic<FileId>> {
2915        match self {
2916            IOError(msg) => vec![Diagnostic::error().with_message(msg)],
2917        }
2918    }
2919}
2920
2921impl IntoDiagnostics for ReplErrorKind {
2922    fn into_diagnostics(self, files: &mut Files) -> Vec<Diagnostic<FileId>> {
2923        match self {
2924            ReplErrorKind::UnknownCommand(s) => vec![
2925                Diagnostic::error()
2926                    .with_message(format!("unknown command `{s}`"))
2927                    .with_notes(vec![String::from(
2928                        "type `:?` or `:help` for a list of available commands.",
2929                    )]),
2930            ],
2931            ReplErrorKind::InvalidQueryPath(err) => err.into_diagnostics(files),
2932            ReplErrorKind::MissingArg { cmd, msg_opt } => {
2933                let mut notes = msg_opt
2934                    .as_ref()
2935                    .map(|msg| vec![msg.clone()])
2936                    .unwrap_or_default();
2937                notes.push(format!(
2938                    "type `:? {cmd}` or `:help {cmd}` for more information."
2939                ));
2940
2941                vec![
2942                    Diagnostic::error()
2943                        .with_message(format!("{cmd}: missing argument"))
2944                        .with_notes(notes),
2945                ]
2946            }
2947        }
2948    }
2949}
2950
2951impl CloneTo for TypecheckErrorKind<'_> {
2952    type Data<'ast> = TypecheckErrorKind<'ast>;
2953
2954    fn clone_to<'to>(data: Self::Data<'_>, dest: &'to AstAlloc) -> Self::Data<'to> {
2955        match data {
2956            TypecheckErrorKind::UnboundIdentifier(loc_ident) => {
2957                TypecheckErrorKind::UnboundIdentifier(loc_ident)
2958            }
2959            TypecheckErrorKind::MissingRow {
2960                id,
2961                kind,
2962                expected,
2963                inferred,
2964                pos,
2965            } => TypecheckErrorKind::MissingRow {
2966                id,
2967                kind,
2968                expected: Type::clone_to(expected, dest),
2969                inferred: Type::clone_to(inferred, dest),
2970                pos,
2971            },
2972            TypecheckErrorKind::MissingDynTail {
2973                expected,
2974                inferred,
2975                pos,
2976            } => TypecheckErrorKind::MissingDynTail {
2977                expected: Type::clone_to(expected, dest),
2978                inferred: Type::clone_to(inferred, dest),
2979                pos,
2980            },
2981            TypecheckErrorKind::ExtraRow {
2982                id,
2983                expected,
2984                inferred,
2985                pos,
2986            } => TypecheckErrorKind::ExtraRow {
2987                id,
2988                expected: Type::clone_to(expected, dest),
2989                inferred: Type::clone_to(inferred, dest),
2990                pos,
2991            },
2992            TypecheckErrorKind::ExtraDynTail {
2993                expected,
2994                inferred,
2995                pos,
2996            } => TypecheckErrorKind::ExtraDynTail {
2997                expected: Type::clone_to(expected, dest),
2998                inferred: Type::clone_to(inferred, dest),
2999                pos,
3000            },
3001            TypecheckErrorKind::ForallParametricityViolation {
3002                kind,
3003                tail,
3004                violating_type,
3005                pos,
3006            } => TypecheckErrorKind::ForallParametricityViolation {
3007                kind,
3008                tail: Type::clone_to(tail, dest),
3009                violating_type: Type::clone_to(violating_type, dest),
3010                pos,
3011            },
3012            TypecheckErrorKind::UnboundTypeVariable(loc_ident) => {
3013                TypecheckErrorKind::UnboundTypeVariable(loc_ident)
3014            }
3015            TypecheckErrorKind::TypeMismatch {
3016                expected,
3017                inferred,
3018                pos,
3019            } => TypecheckErrorKind::TypeMismatch {
3020                expected: Type::clone_to(expected, dest),
3021                inferred: Type::clone_to(inferred, dest),
3022                pos,
3023            },
3024            TypecheckErrorKind::RecordRowMismatch {
3025                id,
3026                expected,
3027                inferred,
3028                cause,
3029                pos,
3030            } => TypecheckErrorKind::RecordRowMismatch {
3031                id,
3032                expected: Type::clone_to(expected, dest),
3033                inferred: Type::clone_to(inferred, dest),
3034                cause: Box::new(TypecheckErrorKind::clone_to(*cause, dest)),
3035                pos,
3036            },
3037            TypecheckErrorKind::EnumRowMismatch {
3038                id,
3039                expected,
3040                inferred,
3041                cause,
3042                pos,
3043            } => TypecheckErrorKind::EnumRowMismatch {
3044                id,
3045                expected: Type::clone_to(expected, dest),
3046                inferred: Type::clone_to(inferred, dest),
3047                cause: cause.map(|cause| Box::new(TypecheckErrorKind::clone_to(*cause, dest))),
3048                pos,
3049            },
3050            TypecheckErrorKind::RecordRowConflict {
3051                row,
3052                expected,
3053                inferred,
3054                pos,
3055            } => TypecheckErrorKind::RecordRowConflict {
3056                row: RecordRow::clone_to(row, dest),
3057                expected: Type::clone_to(expected, dest),
3058                inferred: Type::clone_to(inferred, dest),
3059                pos,
3060            },
3061            TypecheckErrorKind::EnumRowConflict {
3062                row,
3063                expected,
3064                inferred,
3065                pos,
3066            } => TypecheckErrorKind::EnumRowConflict {
3067                row: EnumRow::clone_to(row, dest),
3068                expected: Type::clone_to(expected, dest),
3069                inferred: Type::clone_to(inferred, dest),
3070                pos,
3071            },
3072            TypecheckErrorKind::ArrowTypeMismatch {
3073                expected,
3074                inferred,
3075                type_path,
3076                cause,
3077                pos,
3078            } => TypecheckErrorKind::ArrowTypeMismatch {
3079                expected: Type::clone_to(expected, dest),
3080                inferred: Type::clone_to(inferred, dest),
3081                type_path,
3082                cause: Box::new(TypecheckErrorKind::clone_to(*cause, dest)),
3083                pos,
3084            },
3085            TypecheckErrorKind::CtrTypeInTermPos { contract, pos_type } => {
3086                TypecheckErrorKind::CtrTypeInTermPos {
3087                    contract: Ast::clone_to(contract, dest),
3088                    pos_type,
3089                }
3090            }
3091            TypecheckErrorKind::VarLevelMismatch { type_var, pos } => {
3092                TypecheckErrorKind::VarLevelMismatch { type_var, pos }
3093            }
3094            TypecheckErrorKind::InhomogeneousRecord { row_a, row_b, pos } => {
3095                TypecheckErrorKind::InhomogeneousRecord {
3096                    row_a: Type::clone_to(row_a, dest),
3097                    row_b: Type::clone_to(row_b, dest),
3098                    pos,
3099                }
3100            }
3101            TypecheckErrorKind::OrPatternVarsMismatch { var, pos } => {
3102                TypecheckErrorKind::OrPatternVarsMismatch { var, pos }
3103            }
3104            TypecheckErrorKind::ImportError(import_error) => {
3105                TypecheckErrorKind::ImportError(import_error)
3106            }
3107        }
3108    }
3109}