1pub 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
46pub trait Reporter<E> {
51 fn report(&mut self, e: E);
53
54 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
75pub 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
92pub struct NullReporter {}
94
95impl<E> Reporter<E> for NullReporter {
96 fn report(&mut self, _e: E) {}
97}
98
99#[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#[derive(Default, PartialEq, Clone)]
138pub struct EvalCtxt {
139 pub pos_table: PosTable,
140 pub call_stack: CallStack,
141}
142
143impl 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#[derive(Debug, PartialEq, Clone)]
154pub struct EvalErrorData {
155 pub ctxt: EvalCtxt,
156 pub error: EvalErrorKind,
157}
158
159#[derive(Debug, PartialEq, Clone)]
161pub enum EvalErrorKind {
162 BlameError {
164 evaluated_arg: Option<NickelValue>,
167 label: label::Label,
169 },
170 MissingFieldDef {
172 id: LocIdent,
173 metadata: FieldMetadata,
174 pos_record: PosIdx,
175 pos_access: PosIdx,
176 },
177 TypeError {
179 expected: String,
181 message: String,
183 orig_pos: PosIdx,
185 term: NickelValue,
187 },
188 UnaryPrimopTypeError {
190 primop: String,
191 expected: String,
192 pos_arg: PosIdx,
193 arg_evaluated: NickelValue,
194 },
195 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 ParseError(ParseError),
206 NotAFunc(
208 NickelValue,
209 NickelValue,
210 PosIdx,
211 ),
212 FieldMissing {
215 id: LocIdent,
217 field_names: Vec<LocIdent>,
219 operator: String,
221 pos_record: PosIdx,
223 pos_op: PosIdx,
225 },
226 NotEnoughArgs(
228 usize,
229 String,
230 PosIdx,
231 ),
232 MergeIncompatibleArgs {
235 left_arg: NickelValue,
237 right_arg: NickelValue,
239 merge_label: MergeLabel,
241 },
242 UnboundIdentifier(LocIdent, TermPos),
244 InfiniteRecursion(CallStack, PosIdx),
246 SerializationError(PointedExportErrorData),
248 DeserializationError(
250 String, String, PosIdx, ),
254 DeserializationErrorWithInner {
259 format: InputFormat,
260 inner: ParseError,
261 pos: PosIdx,
263 },
264 IllegalPolymorphicTailAccess {
266 action: IllegalPolymorphicTailAction,
267 evaluated_arg: Option<NickelValue>,
268 label: label::Label,
269 },
270 IncomparableValues {
272 eq_pos: PosIdx,
273 left: NickelValue,
274 right: NickelValue,
275 },
276 NonExhaustiveEnumMatch {
280 expected: Vec<LocIdent>,
282 found: NickelValue,
284 pos: PosIdx,
286 },
287 NonExhaustiveMatch {
288 value: NickelValue,
290 pos: PosIdx,
292 },
293 FailedDestructuring {
294 value: NickelValue,
296 pattern_pos: PosIdx,
298 },
299 QueryNonRecord {
301 pos: PosIdx,
303 id: LocIdent,
305 value: NickelValue,
307 },
308 InternalError(String, PosIdx),
310 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#[self_referencing(pub_extras)]
345#[derive(Debug)]
346pub struct TypecheckErrorData {
347 alloc: AstAlloc,
349 #[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 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#[derive(Debug, PartialEq, Clone)]
373pub enum TypecheckErrorKind<'ast> {
374 UnboundIdentifier(LocIdent),
376 MissingRow {
378 id: LocIdent,
379 kind: RowKind,
380 expected: Type<'ast>,
381 inferred: Type<'ast>,
382 pos: TermPos,
383 },
384 MissingDynTail {
386 expected: Type<'ast>,
387 inferred: Type<'ast>,
388 pos: TermPos,
389 },
390 ExtraRow {
392 id: LocIdent,
393 expected: Type<'ast>,
394 inferred: Type<'ast>,
395 pos: TermPos,
396 },
397 ExtraDynTail {
399 expected: Type<'ast>,
400 inferred: Type<'ast>,
401 pos: TermPos,
402 },
403 ForallParametricityViolation {
416 kind: VarKindDiscriminant,
417 tail: Type<'ast>,
418 violating_type: Type<'ast>,
419 pos: TermPos,
420 },
421 UnboundTypeVariable(LocIdent),
423 TypeMismatch {
426 expected: Type<'ast>,
427 inferred: Type<'ast>,
428 pos: TermPos,
429 },
430 RecordRowMismatch {
434 id: LocIdent,
435 expected: Type<'ast>,
436 inferred: Type<'ast>,
437 cause: Box<TypecheckErrorKind<'ast>>,
438 pos: TermPos,
439 },
440 EnumRowMismatch {
442 id: LocIdent,
443 expected: Type<'ast>,
444 inferred: Type<'ast>,
445 cause: Option<Box<TypecheckErrorKind<'ast>>>,
446 pos: TermPos,
447 },
448 RecordRowConflict {
463 row: RecordRow<'ast>,
466 expected: Type<'ast>,
467 inferred: Type<'ast>,
468 pos: TermPos,
469 },
470 EnumRowConflict {
472 row: EnumRow<'ast>,
475 expected: Type<'ast>,
476 inferred: Type<'ast>,
477 pos: TermPos,
478 },
479 ArrowTypeMismatch {
495 expected: Type<'ast>,
496 inferred: Type<'ast>,
497 type_path: ty_path::Path,
499 cause: Box<TypecheckErrorKind<'ast>>,
500 pos: TermPos,
501 },
502 CtrTypeInTermPos {
511 contract: Ast<'ast>,
513 pos_type: TermPos,
515 },
516 VarLevelMismatch {
542 type_var: LocIdent,
545 pos: TermPos,
547 },
548 InhomogeneousRecord {
550 row_a: Type<'ast>,
552 row_b: Type<'ast>,
554 pos: TermPos,
556 },
557 OrPatternVarsMismatch {
562 var: LocIdent,
565 pos: TermPos,
567 },
568 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#[derive(Debug, PartialEq, Eq, Clone)]
588pub enum ImportErrorKind {
589 IOError(
591 String,
592 String,
593 TermPos,
594 ),
595 ParseErrors(
597 ParseErrors,
598 TermPos,
599 ),
600 MissingDependency {
602 parent: Option<std::path::PathBuf>,
605 missing: Ident,
607 pos: TermPos,
608 },
609 NoPackageMap { pos: TermPos },
611}
612
613#[derive(Debug, Clone, PartialEq)]
615pub struct ExportErrorData {
616 pub pos_table: PosTable,
618 pub data: PointedExportErrorData,
620}
621
622#[derive(Debug, Clone, PartialEq)]
625pub struct PointedExportErrorData {
626 pub path: NickelPointer,
629 pub error: ExportErrorKind,
631}
632
633impl PointedExportErrorData {
634 pub fn with_elem(mut self, elem: NickelPointerElem) -> PointedExportErrorData {
636 self.path.0.push(elem);
637 self
638 }
639
640 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#[derive(Debug, PartialEq, Clone)]
660pub enum ExportErrorKind {
661 UnsupportedNull(ExportFormat, NickelValue),
663 NotAString(NickelValue),
665 NonSerializable(NickelValue),
667 NoDocumentation(NickelValue),
669 NumberOutOfRange {
671 term: NickelValue,
672 value: Number,
673 },
674 ExpectedArray {
676 value: NickelValue,
677 },
678 Other(String),
679}
680
681#[derive(Debug, PartialEq, Eq, Clone)]
683pub struct IOError(pub String);
684
685#[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
784pub 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
801pub trait IntoDiagnostics {
803 fn into_diagnostics(self, files: &mut Files) -> Vec<Diagnostic<FileId>>;
820}
821
822impl IntoDiagnostics for Diagnostic<FileId> {
824 fn into_diagnostics(self, _files: &mut Files) -> Vec<Diagnostic<FileId>> {
825 vec![self]
826 }
827}
828
829fn primary(span: &RawSpan) -> Label<FileId> {
833 Label::primary(span.src_id, span.start.to_usize()..span.end.to_usize())
834}
835
836fn secondary(span: &RawSpan) -> Label<FileId> {
838 Label::secondary(span.src_id, span.start.to_usize()..span.end.to_usize())
839}
840
841fn 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
906fn 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
914fn 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
926fn 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
934fn 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 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 } 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 MergeKind::Standard => "originally merged here",
1174 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 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 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 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 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 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 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 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
1563mod 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 pub fn title(l: &label::Label) -> String {
1583 if ty_path::has_no_arrow(&l.path) {
1584 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 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 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 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 (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 (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 (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 (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 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 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 (Some(ty_path::Elem::Array), None) => "expected array element type",
1774 (Some(ty_path::Elem::Dict), None) => "expected dictionary field type",
1777 (Some(ty_path::Elem::Field(_)), None) => "expected field type",
1780 (Some(_), Some(ty_path::Elem::Codomain)) if ty_path::has_no_dom(&l.path) => {
1784 "expected return type"
1785 }
1786 (Some(_), Some(ty_path::Elem::Domain)) if l.polarity == Polarity::Positive => {
1790 "expected type of an argument of an inner call"
1791 }
1792 (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 (Some(_), Some(ty_path::Elem::Domain)) => {
1803 "expected type of the argument provided by the caller"
1804 }
1805 (Some(_), Some(ty_path::Elem::Codomain)) => {
1809 "expected return type of a function provided by the caller"
1810 }
1811 (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 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 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 let new_msg_block = "\n ";
1856 let mut msg = title(&label);
1857
1858 if !msg_addendum.is_empty() {
1859 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 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 !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
2168fn 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 {
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 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 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 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 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 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 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 match &**cause {
2622 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 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}