1 #![allow(clippy::unwrap_used, clippy::expect_used)]
2 use crate::{ast::BinOp, parse::error::ParseErrorType, type_::Type};
3 use crate::{
4     bit_string,
5     diagnostic::{
6         write, write_diagnostic, write_project, Diagnostic, DiagnosticLabel, LabelStyle,
7         MultiLineDiagnostic, ProjectErrorDiagnostic, Severity,
8     },
9     javascript,
10     type_::{pretty::Printer, UnifyErrorSituation},
11 };
12 use hexpm::version::pubgrub_report::{DefaultStringReporter, Reporter};
13 use hexpm::version::ResolutionError;
14 use itertools::Itertools;
15 use std::fmt::Debug;
16 use std::path::{Path, PathBuf};
17 use termcolor::Buffer;
18 use thiserror::Error;
19 
20 pub type Src = String;
21 pub type Name = String;
22 
23 pub type Result<Ok, Err = Error> = std::result::Result<Ok, Err>;
24 
25 macro_rules! wrap_writeln {
26     ($buf:expr, $($tts:tt)*) => {
27         writeln!($buf, "{}", wrap(&format!($($tts)*)))
28     }
29 }
30 
31 #[derive(Debug, PartialEq, Error)]
32 pub enum Error {
33     #[error("failed to parse Gleam source code")]
34     Parse {
35         path: PathBuf,
36         src: Src,
37         error: crate::parse::error::ParseError,
38     },
39 
40     #[error("type checking failed")]
41     Type {
42         path: PathBuf,
43         src: Src,
44         error: crate::type_::Error,
45     },
46 
47     #[error("unknown import {import} in {module}")]
48     UnknownImport {
49         module: Name,
50         import: Name,
51         location: crate::ast::SrcSpan,
52         path: PathBuf,
53         src: String,
54         modules: Vec<String>,
55     },
56 
57     #[error("duplicate module {module}")]
58     DuplicateModule {
59         module: Name,
60         first: PathBuf,
61         second: PathBuf,
62     },
63 
64     #[error("duplicate Erlang file {file}")]
65     DuplicateErlangFile { file: String },
66 
67     #[error("test module {test_module} imported into application module {src_module}")]
68     SrcImportingTest {
69         path: PathBuf,
70         src: Src,
71         location: crate::ast::SrcSpan,
72         src_module: Name,
73         test_module: Name,
74     },
75 
76     #[error("cyclical module imports")]
77     ImportCycle { modules: Vec<String> },
78 
79     #[error("cyclical package dependencies")]
80     PackageCycle { packages: Vec<String> },
81 
82     #[error("file operation failed")]
83     FileIo {
84         kind: FileKind,
85         action: FileIoAction,
86         path: PathBuf,
87         err: Option<String>,
88     },
89 
90     #[error("io operation failed")]
91     StandardIo {
92         action: StandardIoAction,
93         err: Option<std::io::ErrorKind>,
94     },
95 
96     #[error("source code incorrectly formatted")]
97     Format { problem_files: Vec<Unformatted> },
98 
99     #[error("Hex error: {0}")]
100     Hex(String),
101 
102     #[error("{error}")]
103     ExpandTar { error: String },
104 
105     #[error("{err}")]
106     AddTar { path: PathBuf, err: String },
107 
108     #[error("{0}")]
109     TarFinish(String),
110 
111     #[error("{0}")]
112     Gzip(String),
113 
114     #[error("command failed")]
115     ShellCommand {
116         command: String,
117         err: Option<std::io::ErrorKind>,
118     },
119 
120     #[error("{name} is not a valid project name")]
121     InvalidProjectName {
122         name: String,
123         reason: InvalidProjectNameReason,
124     },
125 
126     #[error("{input} is not a valid version. {error}")]
127     InvalidVersionFormat { input: String, error: String },
128 
129     #[error("project root already exists")]
130     ProjectRootAlreadyExist { path: String },
131 
132     #[error("unable to find project root")]
133     UnableToFindProjectRoot { path: String },
134 
135     #[error("gleam.toml version {toml_ver} does not match .app version {app_ver}")]
136     VersionDoesNotMatch { toml_ver: String, app_ver: String },
137 
138     #[error("metadata decoding failed")]
139     MetadataDecodeError { error: Option<String> },
140 
141     #[error("warnings are not permitted")]
142     ForbiddenWarnings { count: usize },
143 
144     #[error("javascript codegen failed")]
145     JavaScript {
146         path: PathBuf,
147         src: Src,
148         error: crate::javascript::Error,
149     },
150 
151     #[error("package downloading failed: {error}")]
152     DownloadPackageError {
153         package_name: String,
154         package_version: String,
155         error: String,
156     },
157 
158     #[error("{0}")]
159     Http(String),
160 
161     #[error("Dependency tree resolution failed: {0}")]
162     DependencyResolutionFailed(String),
163 
164     #[error("The package {0} is listed in dependencies and dev-dependencies")]
165     DuplicateDependency(String),
166 
167     #[error("The package was missing required fields for publishing")]
168     MissingHexPublishFields {
169         description_missing: bool,
170         licence_missing: bool,
171     },
172 
173     #[error("The package {package} uses unsupported build tools {build_tools:?}")]
174     UnsupportedBuildTool {
175         package: String,
176         build_tools: Vec<String>,
177     },
178 }
179 
180 impl Error {
http<E>(error: E) -> Error where E: std::error::Error,181     pub fn http<E>(error: E) -> Error
182     where
183         E: std::error::Error,
184     {
185         Self::Http(error.to_string())
186     }
187 
hex<E>(error: E) -> Error where E: std::error::Error,188     pub fn hex<E>(error: E) -> Error
189     where
190         E: std::error::Error,
191     {
192         Self::Hex(error.to_string())
193     }
194 
add_tar<P, E>(path: P, error: E) -> Error where P: AsRef<Path>, E: std::error::Error,195     pub fn add_tar<P, E>(path: P, error: E) -> Error
196     where
197         P: AsRef<Path>,
198         E: std::error::Error,
199     {
200         Self::AddTar {
201             path: path.as_ref().to_path_buf(),
202             err: error.to_string(),
203         }
204     }
205 
finish_tar<E>(error: E) -> Error where E: std::error::Error,206     pub fn finish_tar<E>(error: E) -> Error
207     where
208         E: std::error::Error,
209     {
210         Self::TarFinish(error.to_string())
211     }
212 
dependency_resolution_failed(error: ResolutionError) -> Error213     pub fn dependency_resolution_failed(error: ResolutionError) -> Error {
214         Self::DependencyResolutionFailed(match error {
215             ResolutionError::NoSolution(mut derivation_tree) => {
216                 derivation_tree.collapse_no_versions();
217                 let report = DefaultStringReporter::report(&derivation_tree);
218                 wrap(&report)
219             }
220 
221             // TODO: Custom error here
222             // ResolutionError::ErrorRetrievingDependencies {
223             //     package,
224             //     version,
225             //     source,
226             // } => Use the source, it'll provide a better error message
227             error => error.to_string(),
228         })
229     }
230 
expand_tar<E>(error: E) -> Error where E: std::error::Error,231     pub fn expand_tar<E>(error: E) -> Error
232     where
233         E: std::error::Error,
234     {
235         Self::ExpandTar {
236             error: error.to_string(),
237         }
238     }
239 }
240 
241 impl From<capnp::Error> for Error {
from(error: capnp::Error) -> Self242     fn from(error: capnp::Error) -> Self {
243         Error::MetadataDecodeError {
244             error: Some(error.to_string()),
245         }
246     }
247 }
248 
249 impl From<capnp::NotInSchema> for Error {
from(error: capnp::NotInSchema) -> Self250     fn from(error: capnp::NotInSchema) -> Self {
251         Error::MetadataDecodeError {
252             error: Some(error.to_string()),
253         }
254     }
255 }
256 
257 #[derive(Debug, PartialEq, Clone, Copy)]
258 pub enum InvalidProjectNameReason {
259     Format,
260     ErlangReservedWord,
261     ErlangStandardLibraryModule,
262     GleamReservedWord,
263     GleamReservedModule,
264 }
265 
266 #[derive(Debug, PartialEq, Clone, Copy)]
267 pub enum StandardIoAction {
268     Read,
269     Write,
270 }
271 
272 impl StandardIoAction {
text(&self) -> &'static str273     fn text(&self) -> &'static str {
274         match self {
275             StandardIoAction::Read => "read from",
276             StandardIoAction::Write => "write to",
277         }
278     }
279 }
280 
281 #[derive(Debug, PartialEq, Clone, Copy)]
282 pub enum FileIoAction {
283     Open,
284     Copy,
285     Read,
286     Parse,
287     Delete,
288     Create,
289     WriteTo,
290     FindParent,
291 }
292 
293 impl FileIoAction {
text(&self) -> &'static str294     fn text(&self) -> &'static str {
295         match self {
296             FileIoAction::Open => "open",
297             FileIoAction::Copy => "copy",
298             FileIoAction::Read => "read",
299             FileIoAction::Parse => "parse",
300             FileIoAction::Delete => "delete",
301             FileIoAction::Create => "create",
302             FileIoAction::WriteTo => "write to",
303             FileIoAction::FindParent => "find the parent of",
304         }
305     }
306 }
307 
308 #[derive(Debug, Clone, Copy, PartialEq)]
309 pub enum FileKind {
310     File,
311     Directory,
312 }
313 
314 impl FileKind {
text(&self) -> &'static str315     fn text(&self) -> &'static str {
316         match self {
317             FileKind::File => "file",
318             FileKind::Directory => "directory",
319         }
320     }
321 }
322 
did_you_mean(name: &str, options: &[String], alt: &'static str) -> String323 fn did_you_mean(name: &str, options: &[String], alt: &'static str) -> String {
324     // Find best match
325     options
326         .iter()
327         .filter(|&option| option != crate::ast::CAPTURE_VARIABLE)
328         .sorted()
329         .min_by_key(|option| strsim::levenshtein(option, name))
330         .map(|option| format!("did you mean `{}`?", option))
331         .unwrap_or_else(|| alt.to_string())
332 }
333 
334 impl Error {
pretty_string(&self) -> String335     pub fn pretty_string(&self) -> String {
336         let mut nocolor = Buffer::no_color();
337         self.pretty(&mut nocolor);
338         String::from_utf8(nocolor.into_inner()).expect("Error printing produced invalid utf8")
339     }
340 
pretty(&self, buf: &mut Buffer)341     pub fn pretty(&self, buf: &mut Buffer) {
342         use crate::type_::Error as TypeError;
343         use std::io::Write;
344 
345         match self {
346             Error::MetadataDecodeError { error } => {
347                 let diagnostic = ProjectErrorDiagnostic {
348                     title: "Failed to decode module metadata".to_string(),
349                     label: "A problem was encountered when decoding the metadata for one
350 of the Gleam dependency modules."
351                         .to_string(),
352                 };
353                 write_project(buf, diagnostic);
354                 if let Some(error) = error {
355                     writeln!(
356                         buf,
357                         "\nThe error from the decoder library was:
358 
359     {}",
360                         error
361                     )
362                     .unwrap();
363                 }
364             }
365 
366             Error::InvalidProjectName { name, reason } => {
367                 let diagnostic = ProjectErrorDiagnostic {
368                     title: "Invalid project name".to_string(),
369                     label: format!(
370                         "We were not able to create your project as `{}`
371 {}
372 
373 Please try again with a different project name.",
374                         name,
375                         match reason {
376                             InvalidProjectNameReason::ErlangReservedWord =>
377                                 "is a reserved word in Erlang.",
378                             InvalidProjectNameReason::ErlangStandardLibraryModule =>
379                                 "is a standard library module in Erlang.",
380                             InvalidProjectNameReason::GleamReservedWord =>
381                                 "is a reserved word in Gleam.",
382                             InvalidProjectNameReason::GleamReservedModule =>
383                                 "is a reserved module name in Gleam.",
384                             InvalidProjectNameReason::Format =>
385                                 "does not have the correct format. Project names must start
386 with a lowercase letter and may only contain lowercase letters,
387 numbers and underscores.",
388                         }
389                     ),
390                 };
391                 write_project(buf, diagnostic);
392             }
393 
394             Error::ProjectRootAlreadyExist { path } => {
395                 let diagnostic = ProjectErrorDiagnostic {
396                     title: "Project folder already exists".to_string(),
397                     label: format!("Project folder root:\n\n  {}", path),
398                 };
399                 write_project(buf, diagnostic);
400             }
401 
402             Error::UnableToFindProjectRoot { path } => {
403                 let diagnostic = ProjectErrorDiagnostic {
404                     title: "Invalid project root".to_string(),
405                     label: format!("We were unable to find the project root:\n\n  {}", path),
406                 };
407                 write_project(buf, diagnostic);
408             }
409 
410             Error::VersionDoesNotMatch { toml_ver, app_ver } => {
411                 let diagnostic = ProjectErrorDiagnostic {
412                     title: "Version does not match".to_string(),
413                     label: format!(
414                         "The version in gleam.toml \"{}\" does not match the version
415 in your app.src file \"{}\"",
416                         toml_ver, app_ver
417                     ),
418                 };
419                 write_project(buf, diagnostic);
420             }
421 
422             Error::ShellCommand { command, err: None } => {
423                 let diagnostic = ProjectErrorDiagnostic {
424                     title: "Shell command failure".to_string(),
425                     label: format!(
426                         "There was a problem when running the shell command `{}`.",
427                         command
428                     ),
429                 };
430                 write_project(buf, diagnostic);
431             }
432 
433             Error::ShellCommand {
434                 command,
435                 err: Some(err),
436             } => {
437                 let diagnostic = ProjectErrorDiagnostic {
438                     title: "Shell command failure".to_string(),
439                     label: format!(
440                         "There was a problem when running the shell command `{}`.
441 
442 The error from the shell command library was:
443 
444     {}",
445                         command,
446                         std_io_error_kind_text(err)
447                     ),
448                 };
449                 write_project(buf, diagnostic);
450             }
451 
452             Error::Gzip(detail) => {
453                 let diagnostic = ProjectErrorDiagnostic {
454                     title: "Gzip compression failure".to_string(),
455                     label: format!(
456                         "There was a problem when applying gzip compression.
457 
458 This was error from the gzip library:
459 
460     {}",
461                         detail
462                     ),
463                 };
464                 write_project(buf, diagnostic);
465             }
466 
467             Error::AddTar { path, err } => {
468                 let diagnostic = ProjectErrorDiagnostic {
469                     title: "Failure creating tar archive".to_string(),
470                     label: format!(
471                         "There was a problem when attempting to add the file {}
472 to a tar archive.
473 
474 This was error from the tar library:
475 
476     {}",
477                         path.to_str().unwrap(),
478                         err.to_string()
479                     ),
480                 };
481                 write_project(buf, diagnostic);
482             }
483 
484             Error::ExpandTar { error } => {
485                 let diagnostic = ProjectErrorDiagnostic {
486                     title: "Failure opening tar archive".to_string(),
487                     label: format!(
488                         "There was a problem when attempting to expand a to a tar archive.
489 
490 This was error from the tar library:
491 
492     {}",
493                         error.to_string()
494                     ),
495                 };
496                 write_project(buf, diagnostic);
497             }
498 
499             Error::TarFinish(detail) => {
500                 let diagnostic = ProjectErrorDiagnostic {
501                     title: "Failure creating tar archive".to_string(),
502                     label: format!(
503                         "There was a problem when creating a tar archive.
504 
505 This was error from the tar library:
506 
507     {}",
508                         detail
509                     ),
510                 };
511                 write_project(buf, diagnostic);
512             }
513 
514             Error::Hex(detail) => {
515                 let diagnostic = ProjectErrorDiagnostic {
516                     title: "Hex API failure".to_string(),
517                     label: format!(
518                         "There was a problem when using the Hex API.
519 
520 This was error from the Hex client library:
521 
522     {}",
523                         detail
524                     ),
525                 };
526                 write_project(buf, diagnostic);
527             }
528 
529             Error::SrcImportingTest {
530                 path,
531                 src,
532                 location,
533                 src_module,
534                 test_module,
535             } => {
536                 let diagnostic = Diagnostic {
537                     title: "App importing test module".to_string(),
538                     label: "Imported here".to_string(),
539                     file: path.to_str().unwrap().to_string(),
540                     src: src.to_string(),
541                     location: *location,
542                 };
543                 write(buf, diagnostic, Severity::Error);
544                 wrap_writeln!(
545                     buf,
546                     "The application module `{}` is importing the test module `{}`.
547 
548 Test modules are not included in production builds so test modules cannot import them. Perhaps move the `{}` module to the src directory.",
549                     src_module, test_module, test_module,
550                 )
551                 .unwrap();
552             }
553 
554             Error::DuplicateModule {
555                 module,
556                 first,
557                 second,
558             } => {
559                 let diagnostic = ProjectErrorDiagnostic {
560                     title: "Duplicate module".to_string(),
561                     label: format!(
562                         "The module `{}` is defined multiple times.
563 
564 First:  {}
565 Second: {}",
566                         module,
567                         first.to_str().expect("pretty error print PathBuf to_str"),
568                         second.to_str().expect("pretty error print PathBuf to_str"),
569                     ),
570                 };
571                 write_project(buf, diagnostic);
572             }
573 
574             Error::DuplicateErlangFile { file } => {
575                 let diagnostic = ProjectErrorDiagnostic {
576                     title: "Duplicate Erlang file".to_string(),
577                     label: format!("The file `{}` is defined multiple times.", file),
578                 };
579                 write_project(buf, diagnostic);
580             }
581 
582             Error::FileIo {
583                 kind,
584                 action,
585                 path,
586                 err,
587             } => {
588                 let err = match err {
589                     Some(e) => format!(
590                         "\nThe error message from the file IO library was:\n\n    {}\n",
591                         e
592                     ),
593                     None => "".to_string(),
594                 };
595                 let diagnostic = ProjectErrorDiagnostic {
596                     title: "File IO failure".to_string(),
597                     label: format!(
598                         "An error occurred while trying to {} this {}:
599 
600     {}
601 {}",
602                         action.text(),
603                         kind.text(),
604                         path.to_string_lossy(),
605                         err,
606                     ),
607                 };
608                 write_project(buf, diagnostic);
609             }
610 
611             Error::Type { path, src, error } => match error {
612                 TypeError::UnknownLabels {
613                     unknown,
614                     valid,
615                     supplied,
616                 } => {
617                     let other_labels: Vec<String> = valid
618                         .iter()
619                         .cloned()
620                         .filter(|label| !supplied.contains(label))
621                         .collect();
622 
623                     let title = if unknown.len() > 1 {
624                         "Unknown labels"
625                     } else {
626                         "Unknown label"
627                     };
628 
629                     let diagnostic = MultiLineDiagnostic {
630                         title: title.to_string(),
631                         file: path.to_str().unwrap().to_string(),
632                         src: src.to_string(),
633                         labels: unknown
634                             .iter()
635                             .map(|(label, location)| DiagnosticLabel {
636                                 label: did_you_mean(label, &other_labels, "Unexpected label"),
637                                 location: *location,
638                                 style: LabelStyle::Primary,
639                             })
640                             .collect(),
641                     };
642                     write_diagnostic(buf, diagnostic, Severity::Error);
643 
644                     if valid.is_empty() {
645                         writeln!(
646                             buf,
647                             "This constructor does not accept any labelled arguments."
648                         )
649                         .unwrap();
650                     } else if other_labels.is_empty() {
651                         wrap_writeln!(
652                                 buf,
653                                 "You have already supplied all the labelled arguments that this constructor accepts."
654                             )
655                             .unwrap();
656                     } else {
657                         wrap_writeln!(
658                             buf,
659                             "The other labelled arguments that this constructor accepts are `{}`.",
660                             other_labels.iter().join("`, `")
661                         )
662                         .unwrap();
663                     }
664                 }
665 
666                 TypeError::UnexpectedLabelledArg { location, label } => {
667                     let diagnostic = Diagnostic {
668                         title: "Unexpected labelled argument".to_string(),
669                         label: "".to_string(),
670                         file: path.to_str().unwrap().to_string(),
671                         src: src.to_string(),
672                         location: *location,
673                     };
674                     write(buf, diagnostic, Severity::Error);
675                     wrap_writeln!(
676                         buf,
677                         "
678 This argument has been given a label but the constructor does not expect any.
679 Please remove the label `{}`.",
680                         label
681                     )
682                     .unwrap();
683                 }
684 
685                 TypeError::PositionalArgumentAfterLabelled { location } => {
686                     let diagnostic = Diagnostic {
687                         title: "Unexpected positional argument".to_string(),
688                         label: "".to_string(),
689                         file: path.to_str().unwrap().to_string(),
690                         src: src.to_string(),
691                         location: *location,
692                     };
693                     write(buf, diagnostic, Severity::Error);
694                     wrap_writeln!(
695                         buf,
696                         "This unlablled argument has been supplied after a labelled argument.
697 Once a labelled argument has been supplied all following arguments must also be labelled.",
698                     )
699                     .unwrap();
700                 }
701 
702                 TypeError::DuplicateName {
703                     location,
704                     name: fun,
705                     previous_location,
706                     ..
707                 } => {
708                     let diagnostic = MultiLineDiagnostic {
709                         title: format!("Duplicate function definition with name `{}`", fun),
710                         file: path.to_str().unwrap().to_string(),
711                         src: src.to_string(),
712                         labels: vec![
713                             DiagnosticLabel {
714                                 label: "redefined here".to_string(),
715                                 location: *location,
716                                 style: LabelStyle::Primary,
717                             },
718                             DiagnosticLabel {
719                                 label: "previously defined here".to_string(),
720                                 location: *previous_location,
721                                 style: LabelStyle::Secondary,
722                             },
723                         ],
724                     };
725                     write_diagnostic(buf, diagnostic, Severity::Error);
726                 }
727 
728                 TypeError::DuplicateImport {
729                     location,
730                     previous_location,
731                     name,
732                     ..
733                 } => {
734                     let diagnostic = MultiLineDiagnostic {
735                         title: format!("Duplicate import with name `{}`", name),
736                         file: path.to_str().unwrap().to_string(),
737                         src: src.to_string(),
738                         labels: vec![
739                             DiagnosticLabel {
740                                 label: "redefined here".to_string(),
741                                 location: *location,
742                                 style: LabelStyle::Primary,
743                             },
744                             DiagnosticLabel {
745                                 label: "previously defined here".to_string(),
746                                 location: *previous_location,
747                                 style: LabelStyle::Secondary,
748                             },
749                         ],
750                     };
751                     write_diagnostic(buf, diagnostic, Severity::Error);
752                 }
753 
754                 TypeError::DuplicateConstName {
755                     location,
756                     name,
757                     previous_location,
758                     ..
759                 } => {
760                     let diagnostic = MultiLineDiagnostic {
761                         title: format!("Duplicate const definition with name `{}`", name),
762                         file: path.to_str().unwrap().to_string(),
763                         src: src.to_string(),
764                         labels: vec![
765                             DiagnosticLabel {
766                                 label: "redefined here".to_string(),
767                                 location: *location,
768                                 style: LabelStyle::Primary,
769                             },
770                             DiagnosticLabel {
771                                 label: "previously defined here".to_string(),
772                                 location: *previous_location,
773                                 style: LabelStyle::Secondary,
774                             },
775                         ],
776                     };
777                     write_diagnostic(buf, diagnostic, Severity::Error);
778                 }
779 
780                 TypeError::DuplicateTypeName {
781                     name,
782                     location,
783                     previous_location,
784                     ..
785                 } => {
786                     let diagnostic = MultiLineDiagnostic {
787                         title: format!("Duplicate type definition with name `{}`", name),
788                         file: path.to_str().unwrap().to_string(),
789                         src: src.to_string(),
790                         labels: vec![
791                             DiagnosticLabel {
792                                 label: "redefined here".to_string(),
793                                 location: *location,
794                                 style: LabelStyle::Primary,
795                             },
796                             DiagnosticLabel {
797                                 label: "previously defined here".to_string(),
798                                 location: *previous_location,
799                                 style: LabelStyle::Secondary,
800                             },
801                         ],
802                     };
803                     write_diagnostic(buf, diagnostic, Severity::Error);
804                 }
805 
806                 TypeError::DuplicateField { location, label } => {
807                     let diagnostic = Diagnostic {
808                         title: "Duplicate field".to_string(),
809                         label: "".to_string(),
810                         file: path.to_str().unwrap().to_string(),
811                         src: src.to_string(),
812                         location: *location,
813                     };
814                     write(buf, diagnostic, Severity::Error);
815                     wrap_writeln!(
816                         buf,
817                         "The field `{}` has already been defined. Rename this field.",
818                         label
819                     )
820                     .unwrap();
821                 }
822 
823                 TypeError::DuplicateArgument { location, label } => {
824                     let diagnostic = Diagnostic {
825                         title: "Duplicate argument".to_string(),
826                         label: "".to_string(),
827                         file: path.to_str().unwrap().to_string(),
828                         src: src.to_string(),
829                         location: *location,
830                     };
831                     write(buf, diagnostic, Severity::Error);
832                     wrap_writeln!(
833                         buf,
834                         "The labelled argument `{}` has already been supplied.",
835                         label
836                     )
837                     .unwrap();
838                 }
839 
840                 TypeError::RecursiveType { location } => {
841                     let diagnostic = Diagnostic {
842                         title: "Recursive type".to_string(),
843                         label: "".to_string(),
844                         file: path.to_str().unwrap().to_string(),
845                         src: src.to_string(),
846                         location: *location,
847                     };
848                     write(buf, diagnostic, Severity::Error);
849                 }
850 
851                 TypeError::NotFn { location, typ } => {
852                     let diagnostic = Diagnostic {
853                         title: "Type mismatch".to_string(),
854                         label: "".to_string(),
855                         file: path.to_str().unwrap().to_string(),
856                         src: src.to_string(),
857                         location: *location,
858                     };
859                     write(buf, diagnostic, Severity::Error);
860                     let mut printer = Printer::new();
861 
862                     writeln!(
863                         buf,
864                         "This value is being called as a function but its type is:\n\n{}",
865                         printer.pretty_print(typ, 4)
866                     )
867                     .unwrap();
868                 }
869 
870                 TypeError::UnknownRecordField {
871                     location,
872                     typ,
873                     label,
874                     fields,
875                 } => {
876                     let diagnostic = Diagnostic {
877                         title: "Unknown record field".to_string(),
878                         label: did_you_mean(label, fields, "This field does not exist"),
879                         file: path.to_str().unwrap().to_string(),
880                         src: src.to_string(),
881                         location: *location,
882                     };
883                     write(buf, diagnostic, Severity::Error);
884                     let mut printer = Printer::new();
885 
886                     writeln!(
887                         buf,
888                         "The record being updated has this type:
889 
890 {}
891 ",
892                         printer.pretty_print(typ, 4)
893                     )
894                     .unwrap();
895 
896                     if fields.is_empty() {
897                         writeln!(buf, "It does not have any fields.",).unwrap();
898                     } else {
899                         write!(buf, "It has these fields:\n\n").unwrap();
900                         for field in fields.iter().sorted() {
901                             writeln!(buf, "    .{}", field).unwrap();
902                         }
903                     }
904                 }
905 
906                 TypeError::CouldNotUnify {
907                     location,
908                     expected,
909                     given,
910                     situation: Some(UnifyErrorSituation::Operator(op)),
911                 } => {
912                     let diagnostic = Diagnostic {
913                         title: "Type mismatch".to_string(),
914                         label: "".to_string(),
915                         file: path.to_str().unwrap().to_string(),
916                         src: src.to_string(),
917                         location: *location,
918                     };
919                     write(buf, diagnostic, Severity::Error);
920                     let mut printer = Printer::new();
921                     writeln!(
922                         buf,
923                         "The {op} operator expects arguments of this type:
924 
925 {expected}
926 
927 But this argument has this type:
928 
929 {given}\n",
930                         op = op.name(),
931                         expected = printer.pretty_print(expected, 4),
932                         given = printer.pretty_print(given, 4),
933                     )
934                     .unwrap();
935                     if let Some(t) = hint_alternative_operator(op, given) {
936                         writeln!(buf, "Hint: {}\n", t).unwrap();
937                     }
938                 }
939 
940                 TypeError::CouldNotUnify {
941                     location,
942                     expected,
943                     given,
944                     situation: Some(UnifyErrorSituation::PipeTypeMismatch),
945                 } => {
946                     let diagnostic = Diagnostic {
947                         title:
948                             "This function cannot handle the argument sent through the (|>) pipe:"
949                                 .to_string(),
950                         label: "".to_string(),
951                         file: path.to_str().unwrap().to_string(),
952                         src: src.to_string(),
953                         location: *location,
954                     };
955 
956                     // Remap the pipe function type into just the type expected by the pipe.
957                     let expected = expected
958                         .fn_types()
959                         .and_then(|(args, _)| args.get(0).cloned());
960 
961                     // Remap the argument as well, if it's a function.
962                     let given = given
963                         .fn_types()
964                         .and_then(|(args, _)| args.get(0).cloned())
965                         .unwrap_or_else(|| given.clone());
966 
967                     write(buf, diagnostic, Severity::Error);
968                     let mut printer = Printer::new();
969                     writeln!(
970                         buf,
971                         "The argument is:
972 
973 {given}
974 
975 But (|>) is piping it to a function that expects:
976 
977 {expected}
978 
979 \n",
980                         expected = expected
981                             .map(|v| printer.pretty_print(&v, 4))
982                             .unwrap_or_else(|| "    No arguments".to_string()),
983                         given = printer.pretty_print(&given, 4)
984                     )
985                     .unwrap();
986                 }
987 
988                 TypeError::CouldNotUnify {
989                     location,
990                     expected,
991                     given,
992                     situation,
993                 } => {
994                     let diagnostic = Diagnostic {
995                         title: "Type mismatch".to_string(),
996                         label: "".to_string(),
997                         file: path.to_str().unwrap().to_string(),
998                         src: src.to_string(),
999                         location: *location,
1000                     };
1001                     write(buf, diagnostic, Severity::Error);
1002                     if let Some(description) = situation.and_then(|s| s.description()) {
1003                         writeln!(buf, "{}\n", description).unwrap();
1004                     }
1005                     let mut printer = Printer::new();
1006                     writeln!(
1007                         buf,
1008                         "Expected type:
1009 
1010 {}
1011 
1012 Found type:
1013 
1014 {}\n",
1015                         printer.pretty_print(expected, 4),
1016                         printer.pretty_print(given, 4),
1017                     )
1018                     .unwrap();
1019                 }
1020 
1021                 TypeError::IncorrectTypeArity {
1022                     location,
1023                     expected,
1024                     given,
1025                     ..
1026                 } => {
1027                     let diagnostic = Diagnostic {
1028                         title: "Incorrect arity".to_string(),
1029                         label: format!("expected {} arguments, got {}", expected, given),
1030                         file: path.to_str().unwrap().to_string(),
1031                         src: src.to_string(),
1032                         location: *location,
1033                     };
1034                     write(buf, diagnostic, Severity::Error);
1035                 }
1036 
1037                 TypeError::IncorrectArity {
1038                     labels,
1039                     location,
1040                     expected,
1041                     given,
1042                 } => {
1043                     let diagnostic = Diagnostic {
1044                         title: "Incorrect arity".to_string(),
1045                         label: format!("expected {} arguments, got {}", expected, given),
1046                         file: path.to_str().unwrap().to_string(),
1047                         src: src.to_string(),
1048                         location: *location,
1049                     };
1050                     write(buf, diagnostic, Severity::Error);
1051                     if !labels.is_empty() {
1052                         let labels = labels
1053                             .iter()
1054                             .map(|p| format!("  - {}", p))
1055                             .sorted()
1056                             .join("\n");
1057                         writeln!(
1058                             buf,
1059                             "This call accepts these additional labelled arguments:\n\n{}\n",
1060                             labels,
1061                         )
1062                         .unwrap();
1063                     }
1064                 }
1065 
1066                 TypeError::UnnecessarySpreadOperator { location, arity } => {
1067                     let diagnostic = Diagnostic {
1068                         title: "Unnecessary spread operator".to_string(),
1069                         label: format!(""),
1070                         file: path.to_str().unwrap().to_string(),
1071                         src: src.to_string(),
1072                         location: *location,
1073                     };
1074                     write(buf, diagnostic, Severity::Error);
1075 
1076                     wrap_writeln!(
1077                         buf,
1078                         "This record has {} fields and you have already assigned variables to all of them.",
1079                         arity
1080                     )
1081                     .unwrap();
1082                 }
1083 
1084                 TypeError::UnknownType {
1085                     location,
1086                     name,
1087                     types,
1088                 } => {
1089                     let diagnostic = Diagnostic {
1090                         title: "Unknown type".to_string(),
1091                         label: did_you_mean(name, types, ""),
1092                         file: path.to_str().unwrap().to_string(),
1093                         src: src.to_string(),
1094                         location: *location,
1095                     };
1096                     write(buf, diagnostic, Severity::Error);
1097                     wrap_writeln!(
1098                         buf,
1099                         "The type `{}` is not defined or imported in this module.",
1100                         name
1101                     )
1102                     .unwrap();
1103                 }
1104 
1105                 TypeError::UnknownVariable {
1106                     location,
1107                     variables,
1108                     name,
1109                 } => {
1110                     let diagnostic = Diagnostic {
1111                         title: "Unknown variable".to_string(),
1112                         label: did_you_mean(name, variables, ""),
1113                         file: path.to_str().unwrap().to_string(),
1114                         src: src.to_string(),
1115                         location: *location,
1116                     };
1117                     write(buf, diagnostic, Severity::Error);
1118                     wrap_writeln!(buf, "The name `{}` is not in scope here.", name).unwrap();
1119                 }
1120 
1121                 TypeError::PrivateTypeLeak { location, leaked } => {
1122                     let diagnostic = Diagnostic {
1123                         title: "Private type used in public interface".to_string(),
1124                         label: "".to_string(),
1125                         file: path.to_str().unwrap().to_string(),
1126                         src: src.to_string(),
1127                         location: *location,
1128                     };
1129                     write(buf, diagnostic, Severity::Error);
1130                     let mut printer = Printer::new();
1131 
1132                     // TODO: be more precise.
1133                     // - is being returned by this public function
1134                     // - is taken as an argument by this public function
1135                     // - is taken as an argument by this public enum constructor
1136                     // etc
1137                     writeln!(
1138                         buf,
1139                         "The following type is private, but is being used by this public export.
1140 
1141 {}
1142 
1143 Private types can only be used within the module that defines them.",
1144                         printer.pretty_print(leaked, 4),
1145                     )
1146                     .unwrap();
1147                 }
1148 
1149                 TypeError::UnknownModule {
1150                     location,
1151                     name,
1152                     imported_modules,
1153                 } => {
1154                     let diagnostic = Diagnostic {
1155                         title: "Unknown module".to_string(),
1156                         label: did_you_mean(name, imported_modules, ""),
1157                         file: path.to_str().unwrap().to_string(),
1158                         src: src.to_string(),
1159                         location: *location,
1160                     };
1161                     write(buf, diagnostic, Severity::Error);
1162                     writeln!(buf, "No module has been found with the name `{}`.", name).unwrap();
1163                 }
1164 
1165                 TypeError::UnknownModuleType {
1166                     location,
1167                     name,
1168                     module_name,
1169                     type_constructors,
1170                 } => {
1171                     let diagnostic = Diagnostic {
1172                         title: "Unknown module type".to_string(),
1173                         label: did_you_mean(name, type_constructors, ""),
1174                         file: path.to_str().unwrap().to_string(),
1175                         src: src.to_string(),
1176                         location: *location,
1177                     };
1178                     write(buf, diagnostic, Severity::Error);
1179                     writeln!(
1180                         buf,
1181                         "The module `{}` does not have a `{}` type.",
1182                         module_name.join("/"),
1183                         name
1184                     )
1185                     .unwrap();
1186                 }
1187 
1188                 TypeError::UnknownModuleValue {
1189                     location,
1190                     name,
1191                     module_name,
1192                     value_constructors,
1193                 } => {
1194                     let diagnostic = Diagnostic {
1195                         title: "Unknown module field".to_string(),
1196                         label: did_you_mean(name, value_constructors, ""),
1197                         file: path.to_str().unwrap().to_string(),
1198                         src: src.to_string(),
1199                         location: *location,
1200                     };
1201                     write(buf, diagnostic, Severity::Error);
1202                     writeln!(
1203                         buf,
1204                         "The module `{}` does not have a `{}` field.",
1205                         module_name.join("/"),
1206                         name
1207                     )
1208                     .unwrap();
1209                 }
1210 
1211                 TypeError::UnknownModuleField {
1212                     location,
1213                     name,
1214                     module_name,
1215                     type_constructors,
1216                     value_constructors,
1217                 } => {
1218                     let options: Vec<String> = type_constructors
1219                         .iter()
1220                         .chain(value_constructors)
1221                         .map(|s| s.to_string())
1222                         .collect();
1223                     let diagnostic = Diagnostic {
1224                         title: "Unknown module field".to_string(),
1225                         label: did_you_mean(name, &options, ""),
1226                         file: path.to_str().unwrap().to_string(),
1227                         src: src.to_string(),
1228                         location: *location,
1229                     };
1230                     write(buf, diagnostic, Severity::Error);
1231                     writeln!(
1232                         buf,
1233                         "The module `{}` does not have a `{}` field.",
1234                         module_name.join("/"),
1235                         name
1236                     )
1237                     .unwrap();
1238                 }
1239 
1240                 TypeError::IncorrectNumClausePatterns {
1241                     location,
1242                     expected,
1243                     given,
1244                 } => {
1245                     let diagnostic = Diagnostic {
1246                         title: "Incorrect number of patterns".to_string(),
1247                         label: format!("expected {} patterns, got {}", expected, given),
1248                         file: path.to_str().unwrap().to_string(),
1249                         src: src.to_string(),
1250                         location: *location,
1251                     };
1252                     write(buf, diagnostic, Severity::Error);
1253                     wrap_writeln!(
1254                         buf,
1255                         "This case expression has {} subjects, but this pattern matches {}.
1256 Each clause must have a pattern for every subject value.",
1257                         expected,
1258                         given
1259                     )
1260                     .unwrap();
1261                 }
1262 
1263                 TypeError::NonLocalClauseGuardVariable { location, name } => {
1264                     let diagnostic = Diagnostic {
1265                         title: "Invalid guard variable".to_string(),
1266                         label: "is not locally defined".to_string(),
1267                         file: path.to_str().unwrap().to_string(),
1268                         src: src.to_string(),
1269                         location: *location,
1270                     };
1271                     write(buf, diagnostic, Severity::Error);
1272                     wrap_writeln!(
1273                         buf,
1274                         "Variables used in guards must be either defined in the function, or be an argument to the function. The variable `{}` is not defined locally.",
1275                         name
1276                     )
1277                     .unwrap();
1278                 }
1279 
1280                 TypeError::ExtraVarInAlternativePattern { location, name } => {
1281                     let diagnostic = Diagnostic {
1282                         title: "Extra alternative pattern variable".to_string(),
1283                         label: "has not been previously defined".to_string(),
1284                         file: path.to_str().unwrap().to_string(),
1285                         src: src.to_string(),
1286                         location: *location,
1287                     };
1288                     write(buf, diagnostic, Severity::Error);
1289                     wrap_writeln!(
1290                         buf,
1291                         "All alternative patterns must define the same variables as the initial pattern. This variable `{}` has not been previously defined.",
1292                         name
1293                     )
1294                     .unwrap();
1295                 }
1296 
1297                 TypeError::MissingVarInAlternativePattern { location, name } => {
1298                     let diagnostic = Diagnostic {
1299                         title: "Missing alternative pattern variable".to_string(),
1300                         label: "does not define all required variables".to_string(),
1301                         file: path.to_str().unwrap().to_string(),
1302                         src: src.to_string(),
1303                         location: *location,
1304                     };
1305                     write(buf, diagnostic, Severity::Error);
1306                     buf.write_all(wrap(&format!(
1307                         "All alternative patterns must define the same variables as the initial pattern, but the `{}` variable is missing.",
1308                         name
1309                     )).as_bytes())
1310                     .unwrap();
1311                 }
1312 
1313                 TypeError::DuplicateVarInPattern { location, name } => {
1314                     let diagnostic = Diagnostic {
1315                         title: "Duplicate variable in pattern".to_string(),
1316                         label: "has already been used".to_string(),
1317                         file: path.to_str().unwrap().to_string(),
1318                         src: src.to_string(),
1319                         location: *location,
1320                     };
1321                     write(buf, diagnostic, Severity::Error);
1322 
1323                     writeln!(
1324                         buf,
1325                         "{}",
1326                         wrap(&format!(
1327                             "Variables can only be used once per pattern. This variable {} appears multiple times.
1328 If you used the same variable twice deliberately in order to check for equality please use a guard clause instead.
1329 e.g. (x, y) if x == y -> ...",
1330                             name
1331                         ))
1332                     )
1333                     .unwrap();
1334                 }
1335 
1336                 TypeError::OutOfBoundsTupleIndex {
1337                     location, size: 0, ..
1338                 } => {
1339                     let diagnostic = Diagnostic {
1340                         title: "Out of bounds tuple index".to_string(),
1341                         label: "this index is too large".to_string(),
1342                         file: path.to_str().unwrap().to_string(),
1343                         src: src.to_string(),
1344                         location: *location,
1345                     };
1346                     write(buf, diagnostic, Severity::Error);
1347                     wrap_writeln!(
1348                         buf,
1349                         "This tuple has no elements so it cannot be indexed at all!"
1350                     )
1351                     .unwrap();
1352                 }
1353 
1354                 TypeError::OutOfBoundsTupleIndex {
1355                     location,
1356                     index,
1357                     size,
1358                 } => {
1359                     let diagnostic = Diagnostic {
1360                         title: "Out of bounds tuple index".to_string(),
1361                         label: "this index is too large".to_string(),
1362                         file: path.to_str().unwrap().to_string(),
1363                         src: src.to_string(),
1364                         location: *location,
1365                     };
1366                     write(buf, diagnostic, Severity::Error);
1367                     wrap_writeln!(
1368                         buf,
1369                         "The index being accessed for this tuple is {}, but this tuple has {} elements so the highest valid index is {}.",
1370                         index,
1371                         size,
1372                         size - 1,
1373                     )
1374                     .unwrap();
1375                 }
1376 
1377                 TypeError::NotATuple { location, given } => {
1378                     let diagnostic = Diagnostic {
1379                         title: "Type mismatch".to_string(),
1380                         label: "is not a tuple".to_string(),
1381                         file: path.to_str().unwrap().to_string(),
1382                         src: src.to_string(),
1383                         location: *location,
1384                     };
1385                     write(buf, diagnostic, Severity::Error);
1386                     let mut printer = Printer::new();
1387 
1388                     writeln!(
1389                         buf,
1390                         "To index into this value it needs to be a tuple, however it has this type:
1391 
1392 {}",
1393                         printer.pretty_print(given, 4),
1394                     )
1395                     .unwrap();
1396                 }
1397 
1398                 TypeError::NotATupleUnbound { location } => {
1399                     let diagnostic = Diagnostic {
1400                         title: "Type mismatch".to_string(),
1401                         label: "what type is this?".to_string(),
1402                         file: path.to_str().unwrap().to_string(),
1403                         src: src.to_string(),
1404                         location: *location,
1405                     };
1406                     write(buf, diagnostic, Severity::Error);
1407                     wrap_writeln!(
1408                         buf,
1409                         "To index into a tuple we need to know it size, but we don't know anything about this type yet. Please add some type annotations so we can continue.",
1410                     )
1411                     .unwrap();
1412                 }
1413 
1414                 TypeError::RecordAccessUnknownType { location } => {
1415                     let diagnostic = Diagnostic {
1416                         title: "Unknown type for record access".to_string(),
1417                         label: "I don't know what type this is".to_string(),
1418                         file: path.to_str().unwrap().to_string(),
1419                         src: src.to_string(),
1420                         location: *location,
1421                     };
1422                     write(buf, diagnostic, Severity::Error);
1423 
1424                     wrap_writeln!(
1425                         buf,
1426                         "In order to access a record field we need to know what type it is, but I can't tell the type here. Try adding type annotations to your function and try again.",
1427                     )
1428                     .unwrap();
1429                 }
1430 
1431                 TypeError::BitStringSegmentError { error, location } => {
1432                     let (label, mut extra) = match error {
1433                         bit_string::ErrorType::ConflictingTypeOptions { existing_type } => (
1434                             "This is an extra type specifier.",
1435                             vec![format!("Hint: This segment already has the type {}.", existing_type)],
1436                         ),
1437 
1438                         bit_string::ErrorType::ConflictingSignednessOptions {
1439                             existing_signed
1440                         } => (
1441                             "This is an extra signedness specifier.",
1442                             vec![format!(
1443                                 "Hint: This segment already has a signedness of {}.",
1444                                 existing_signed
1445                             )],
1446                         ),
1447 
1448                         bit_string::ErrorType::ConflictingEndiannessOptions {
1449                             existing_endianness
1450                         } => (
1451                             "This is an extra endianness specifier.",
1452                             vec![format!(
1453                                 "Hint: This segment already has an endianness of {}.",
1454                                 existing_endianness
1455                             )],
1456                         ),
1457 
1458                         bit_string::ErrorType::ConflictingSizeOptions => (
1459                             "This is an extra size specifier.",
1460                             vec!["Hint: This segment already has a size.".to_string()],
1461                         ),
1462 
1463                         bit_string::ErrorType::ConflictingUnitOptions => (
1464                             "This is an extra unit specifier.",
1465                             vec!["Hint: A BitString segment can have at most 1 unit.".to_string()],
1466                         ),
1467 
1468                         bit_string::ErrorType::FloatWithSize => (
1469                             "Size cannot be used with float.",
1470                             vec!["Hint: floats have an exact size of 64 bits.".to_string()],
1471                         ),
1472 
1473                         bit_string::ErrorType::InvalidEndianness => (
1474                             "this option is invalid here.",
1475                             vec![wrap("Hint: signed and unsigned can only be used with int, float, utf16 and utf32 types.")],
1476                         ),
1477 
1478                         bit_string::ErrorType::OptionNotAllowedInValue => (
1479                             "This option is only allowed in BitString patterns.",
1480                             vec!["Hint: This option has no effect in BitString values.".to_string()],
1481                         ),
1482 
1483                         bit_string::ErrorType::SignednessUsedOnNonInt { typ } => (
1484                             "signedness is only valid with int types.",
1485                             vec![format!("Hint: This segment has a type of {}", typ)],
1486                         ),
1487                         bit_string::ErrorType::TypeDoesNotAllowSize { typ } => (
1488                             "size cannot be specified here",
1489                             vec![format!("Hint: {} segments have an autoatic size.", typ)],
1490                         ),
1491                         bit_string::ErrorType::TypeDoesNotAllowUnit { typ } => (
1492                             "unit cannot be specified here",
1493                             vec![wrap(&format!("Hint: {} segments are sized based on their value and cannot have a unit.", typ))],
1494                         ),
1495                         bit_string::ErrorType::VariableUtfSegmentInPattern => (
1496                             "this cannot be a variable",
1497                             vec![wrap("Hint: in patterns utf8, utf16, and utf32  must be an exact string.")],
1498                         ),
1499                         bit_string::ErrorType::SegmentMustHaveSize => (
1500                             "This segment has no size",
1501                             vec![wrap("Hint: Bit string segments without a size are only allowed at the end of a bin pattern.")],
1502                         ),
1503                         bit_string::ErrorType::UnitMustHaveSize => (
1504                             "This needs an explicit size",
1505                             vec!["Hint: If you specify unit() you must also specify size().".to_string()],
1506                         ),
1507                     };
1508                     let diagnostic = Diagnostic {
1509                         title: "BitString Segment Error".to_string(),
1510                         label: label.to_string(),
1511                         location: *location,
1512                         file: path.to_str().unwrap().to_string(),
1513                         src: src.to_string(),
1514                     };
1515                     extra.push("See: https://gleam.run/book/tour/bit-strings.html".to_string());
1516                     write(buf, diagnostic, Severity::Error);
1517                     if !extra.is_empty() {
1518                         writeln!(buf, "{}", extra.join("\n")).expect("error pretty buffer write");
1519                     }
1520                 }
1521 
1522                 TypeError::RecordUpdateInvalidConstructor { location } => {
1523                     let diagnostic = Diagnostic {
1524                         title: "Invalid record constructor".to_string(),
1525                         label: "This is not a record constructor".to_string(),
1526                         file: path.to_str().unwrap().to_string(),
1527                         src: src.to_string(),
1528                         location: *location,
1529                     };
1530                     write(buf, diagnostic, Severity::Error);
1531 
1532                     writeln!(
1533                         buf,
1534                         "Only record constructors can be used with the update syntax.",
1535                     )
1536                     .unwrap();
1537                 }
1538 
1539                 TypeError::UnexpectedTypeHole { location } => {
1540                     let diagnostic = Diagnostic {
1541                         title: "Unexpected type hole".to_string(),
1542                         label: "".to_string(),
1543                         file: path.to_str().unwrap().to_string(),
1544                         src: src.to_string(),
1545                         location: *location,
1546                     };
1547                     write(buf, diagnostic, Severity::Error);
1548 
1549                     writeln!(
1550                         buf,
1551                         "We need to know the exact type here so type holes are not permitted.",
1552                     )
1553                     .unwrap();
1554                 }
1555 
1556                 TypeError::ReservedModuleName { name } => {
1557                     let diagnostic = ProjectErrorDiagnostic {
1558                         title: "Reserved module name".to_string(),
1559                         label: format!(
1560                             "The module name `{}` is reserved.
1561 Try a different name for this module.",
1562                             name
1563                         ),
1564                     };
1565                     write_project(buf, diagnostic);
1566                 }
1567 
1568                 TypeError::KeywordInModuleName { name, keyword } => {
1569                     let diagnostic = ProjectErrorDiagnostic {
1570                         title: "Invalid module name".to_string(),
1571                         label: wrap(&format!(
1572                             "The module name `{}` contains the keyword `{}`, so importing it would be a syntax error.
1573 Try a different name for this module.",
1574                             name, keyword
1575                         )),
1576                     };
1577                     write_project(buf, diagnostic);
1578                 }
1579             },
1580 
1581             Error::Parse { path, src, error } => {
1582                 let crate::parse::error::ParseError { location, error } = error;
1583 
1584                 let (label, extra) = match error {
1585                     ParseErrorType::ExpectedExpr => (
1586                         "I was expecting an expression after this.",
1587                         vec![]
1588                     ),
1589                     ParseErrorType::ExpectedName => (
1590                         "I was expecting a name here.",
1591                         vec![]
1592                     ),
1593                     ParseErrorType::ExpectedPattern => (
1594                         "I was expecting a pattern after this.",
1595                         vec!["See: https://gleam.run/book/tour/patterns".to_string()]
1596                     ),
1597                     ParseErrorType::ExpectedType => (
1598                         "I was expecting a type after this.",
1599                         vec!["See: https://gleam.run/book/tour/type-annotations".to_string()]
1600                     ),
1601                     ParseErrorType::ExpectedUpName => (
1602                         "I was expecting a type name here.",
1603                         vec![]
1604                     ),
1605                     ParseErrorType::ExpectedValue => (
1606                         "I was expecting a value after this.",
1607                         vec!["See: https://gleam.run/book/tour/patterns".to_string()]
1608                     ),
1609                     ParseErrorType::ExtraSeparator => (
1610                         "This is an extra delimiter.",
1611                         vec!["Hint: Try removing it?".to_string()]
1612                     ),
1613                     ParseErrorType::ExprLparStart=> (
1614                         "This paren cannot be understood here.",
1615                         vec!["Hint: To group expressions in gleam use \"{\" and \"}\"".to_string()]
1616                     ),
1617                     ParseErrorType::ExprThenlessTry=> (
1618                         "A `try` cannot be the last expression.",
1619                         vec!["Hint: Try using the value?".to_string()]
1620                     ),
1621                     ParseErrorType::IncorrectName => (
1622                         "I'm expecting a lowercase name here.",
1623                         vec![ wrap("Hint: Variable and module names start with a lowercase letter, and can contain a-z, 0-9, or _.")]
1624                     ),
1625                     ParseErrorType::IncorrectUpName => (
1626                         "I'm expecting a type name here.",
1627                             vec![ wrap("Hint: Type names start with a uppercase letter, and can contain a-z, A-Z, or 0-9.")]
1628                     ),
1629                     ParseErrorType::InvalidBitStringSegment => (
1630                         "This is not a valid BitString segment option.",
1631                         vec![ "Hint: Valid BitString segment options are:".to_string(),
1632                         wrap("binary, int, float, bit_string, utf8, utf16, utf32, utf8_codepoint, utf16_codepoint, utf32_codepoint, signed, unsigned, big, little, native, size, unit"),
1633                         "See: https://gleam.run/book/tour/bit-strings".to_string()]
1634                     ),
1635                     ParseErrorType::InvalidBitStringUnit => (
1636                         "This is not a valid BitString unit value.",
1637                         vec!["Hint: unit must be an integer literal >= 1 and <= 256".to_string(),
1638                         "See: https://gleam.run/book/tour/bit-strings".to_string()]
1639                     ),
1640                     ParseErrorType::InvalidTailPattern => (
1641                         "This part of a list pattern can only be a name or a discard.",
1642                         vec!["See: https://gleam.run/book/tour/patterns".to_string()]
1643                     ),
1644                     ParseErrorType::InvalidTupleAccess => (
1645                         "This integer is not valid for tuple access.",
1646                     vec!["Hint: Only non negative integer literals like 0, or 1_000 can be used.".to_string()]
1647                     ),
1648                     ParseErrorType::LexError { error: lex_err } => lex_err.to_parse_error_info(),
1649                     ParseErrorType::NestedBitStringPattern => (
1650                         "BitString patterns cannot be nested.",
1651                         vec!["See: https://gleam.run/book/tour/patterns".to_string()]
1652                     ),
1653                     ParseErrorType::NoCaseClause => (
1654                         "This case expression has no clauses.",
1655                         vec!["See: https://gleam.run/book/tour/case-expressions".to_string()]
1656                     ),
1657                     ParseErrorType::NoConstructors => (
1658                         "Custom types must have at least 1 constructor.",
1659                         vec!["See: https://gleam.run/book/tour/custom-types".to_string()]
1660                     ),
1661                     ParseErrorType::NotConstType => (
1662                         "This type is not allowed in module constants.",
1663                         vec!["See: https://gleam.run/book/tour/constants".to_string()]
1664                     ),
1665                     ParseErrorType::NoExpression=> (
1666                         "There must be an expression in here.",
1667                         vec!["Hint: Put an expression in there or remove the brackets.".to_string()]
1668                     ),
1669                     ParseErrorType::NoValueAfterEqual => (
1670                         "I was expecting to see a value after this equals sign.",
1671                         vec![]
1672                     ),
1673                     ParseErrorType::OpaqueTypeAlias => (
1674                         "Type Aliases cannot be opaque",
1675                         vec!["See: https://gleam.run/book/tour/type-aliases".to_string()]
1676                     ),
1677                     ParseErrorType::OpNakedRight => (
1678                         "This operator has no value on its right side.",
1679                         vec!["Hint: Remove it or put a value after it.".to_string()]
1680                     ),
1681                     ParseErrorType::TooManyArgHoles => (
1682                         "There is more than 1 argument hole in this function call.",
1683                         vec!["Hint: Function calls can have at most one argument hole.".to_string(),
1684                              "See: https://gleam.run/book/tour/functions".to_string()
1685                             ]
1686                     ),
1687                     ParseErrorType::UnexpectedEof => (
1688                         "The module ended unexpectedly.",
1689                         vec![]
1690                     ),
1691                     ParseErrorType::ListSpreadWithoutElements   => (
1692                         "This spread does nothing",
1693                         vec!["Hint: Try prepending some elements [1, 2, ..list].".to_string(),
1694 "See: https://gleam.run/book/tour/lists.html".to_string()
1695                         ]
1696                     ),
1697                     ParseErrorType::UnexpectedReservedWord => (
1698                         "This is a reserved word.",
1699                         vec!["Hint: I was expecting to see a name here.".to_string(), "See: https://gleam.run/book/tour/reserved-words".to_string()]
1700                     ),
1701                     ParseErrorType::UnexpectedToken { expected } => {
1702                         let mut messages = expected.clone();
1703                         if let Some(s) = messages.first_mut() {
1704                             *s = format!("Expected one of: {}", s);
1705                         }
1706 
1707                         ( "I was not expecting this.",
1708                           messages
1709                         )
1710                     }
1711                 };
1712 
1713                 let adjusted_location = if error == &ParseErrorType::UnexpectedEof {
1714                     crate::ast::SrcSpan {
1715                         start: src.len() - 1,
1716                         end: src.len() - 1,
1717                     }
1718                 } else {
1719                     *location
1720                 };
1721 
1722                 let diagnostic = Diagnostic {
1723                     title: "Syntax error".to_string(),
1724                     label: label.to_string(),
1725                     location: adjusted_location,
1726                     file: path.to_str().unwrap().to_string(),
1727                     src: src.to_string(),
1728                 };
1729                 write(buf, diagnostic, Severity::Error);
1730                 if !extra.is_empty() {
1731                     writeln!(buf, "{}", extra.join("\n")).expect("error pretty buffer write");
1732                 }
1733             }
1734 
1735             Error::ImportCycle { modules } => {
1736                 crate::diagnostic::write_title(buf, "Import cycle", Severity::Error);
1737                 writeln!(
1738                     buf,
1739                     "The import statements for these modules form a cycle:\n"
1740                 )
1741                 .unwrap();
1742                 import_cycle(buf, modules);
1743 
1744                 wrap_writeln!(
1745                     buf,
1746                     "Gleam doesn't support import cycles like these, please break the cycle to continue."
1747                 )
1748                 .unwrap();
1749             }
1750 
1751             Error::PackageCycle { packages } => {
1752                 crate::diagnostic::write_title(buf, "Dependency cycle", Severity::Error);
1753                 writeln!(buf, "The dependencies for these packages form a cycle:\n").unwrap();
1754                 import_cycle(buf, packages);
1755                 wrap_writeln!(
1756                     buf,
1757                     "Gleam doesn't support dependency cycles like these, please break the cycle to continue."
1758                 )
1759                 .unwrap();
1760             }
1761 
1762             Error::UnknownImport {
1763                 module,
1764                 import,
1765                 location,
1766                 path,
1767                 src,
1768                 modules,
1769             } => {
1770                 let diagnostic = Diagnostic {
1771                     title: "Unknown import".to_string(),
1772                     label: did_you_mean(import, modules, ""),
1773                     file: path.to_str().unwrap().to_string(),
1774                     src: src.to_string(),
1775                     location: *location,
1776                 };
1777                 write(buf, diagnostic, Severity::Error);
1778                 let msg = wrap(&format!(
1779                     "The module `{}` is trying to import the module `{}`, but it cannot be found.",
1780                     module, import
1781                 ));
1782                 writeln!(buf, "{}", msg).expect("error pretty buffer write");
1783             }
1784 
1785             Error::StandardIo { action, err } => {
1786                 let err = match err {
1787                     Some(e) => format!(
1788                         "\nThe error message from the stdio library was:\n\n    {}\n",
1789                         std_io_error_kind_text(e)
1790                     ),
1791                     None => "".to_string(),
1792                 };
1793                 let diagnostic = ProjectErrorDiagnostic {
1794                     title: "Standard IO failure".to_string(),
1795                     label: format!(
1796                         "An error occurred while trying to {}:
1797 
1798 {}",
1799                         action.text(),
1800                         err,
1801                     ),
1802                 };
1803                 write_project(buf, diagnostic);
1804             }
1805             Error::Format { problem_files } => {
1806                 let files: Vec<_> = problem_files
1807                     .iter()
1808                     .flat_map(|formatted| formatted.source.to_str())
1809                     .map(|p| format!("  - {}", p))
1810                     .sorted()
1811                     .collect();
1812                 let mut label = files.iter().join("\n");
1813                 label.push('\n');
1814                 let diagnostic = ProjectErrorDiagnostic {
1815                     title: "These files have not been formatted".to_string(),
1816                     label,
1817                 };
1818 
1819                 write_project(buf, diagnostic);
1820             }
1821 
1822             Error::ForbiddenWarnings { count } => {
1823                 let word_warning = match count {
1824                     1 => "warning",
1825                     _ => "warnings",
1826                 };
1827                 let diagnostic = ProjectErrorDiagnostic {
1828                     title: format!("{} {} generated.", count, word_warning),
1829                     label: wrap(
1830                         "Your project was compiled with the `--warnings-as-errors` flag.
1831 Fix the warnings and try again!",
1832                     ),
1833                 };
1834                 write_project(buf, diagnostic);
1835             }
1836 
1837             Error::JavaScript { src, path, error } => match error {
1838                 javascript::Error::Unsupported { feature, location } => {
1839                     let diagnostic = Diagnostic {
1840                         title: "Unsupported feature for compilation target".to_string(),
1841                         label: format!("{} is not supported for JavaScript compilation", feature),
1842                         file: path.to_str().unwrap().to_string(),
1843                         src: src.to_string(),
1844                         location: *location,
1845                     };
1846                     write(buf, diagnostic, Severity::Error);
1847                 }
1848             },
1849             Error::DownloadPackageError {
1850                 package_name,
1851                 package_version,
1852                 error,
1853             } => {
1854                 let diagnostic = ProjectErrorDiagnostic {
1855                     title: "Failed to download package".to_string(),
1856                     label: format!(
1857                         "A problem was encountered when downloading {} {}.",
1858                         package_name, package_version
1859                     ),
1860                 };
1861                 write_project(buf, diagnostic);
1862                 writeln!(
1863                     buf,
1864                     "\nThe error from the package manager client was:
1865 
1866     {}",
1867                     error
1868                 )
1869                 .unwrap();
1870             }
1871 
1872             Error::Http(error) => {
1873                 let diagnostic = ProjectErrorDiagnostic {
1874                     title: "HTTP error".to_string(),
1875                     label: "A HTTP request failed".to_string(),
1876                 };
1877                 write_project(buf, diagnostic);
1878                 writeln!(
1879                     buf,
1880                     "\nThe error from the HTTP client was:
1881 
1882     {}",
1883                     error
1884                 )
1885                 .unwrap();
1886             }
1887 
1888             Error::InvalidVersionFormat { input, error } => {
1889                 let diagnostic = ProjectErrorDiagnostic {
1890                     title: "Invalid version format".to_string(),
1891                     label: format!("I was unable to parse the version {}.", input),
1892                 };
1893                 write_project(buf, diagnostic);
1894                 writeln!(
1895                     buf,
1896                     "\nThe error from the parser was:
1897 
1898     {}",
1899                     error
1900                 )
1901                 .unwrap();
1902             }
1903 
1904             Error::DependencyResolutionFailed(error) => {
1905                 let diagnostic = ProjectErrorDiagnostic {
1906                     title: "Dependency resolution failed".to_string(),
1907                     label: wrap("An error occured while determining what dependency packages and versions should be downloaded."
1908                         ),
1909                 };
1910                 write_project(buf, diagnostic);
1911                 writeln!(buf, "\n{}", wrap(&error.to_string())).unwrap();
1912             }
1913 
1914             Error::DuplicateDependency(name) => {
1915                 let label = &format!("The package {name} is specified in both the dependencies and dev-dependencies sections of the gleam.toml file.", name=name);
1916                 let diagnostic = ProjectErrorDiagnostic {
1917                     title: "Dependency duplicated".to_string(),
1918                     label: wrap(label),
1919                 };
1920                 write_project(buf, diagnostic);
1921             }
1922 
1923             Error::MissingHexPublishFields {
1924                 description_missing,
1925                 licence_missing,
1926             } => {
1927                 let label =
1928                     "Licence information and package description are required to publish a package to Hex.";
1929                 let diagnostic = ProjectErrorDiagnostic {
1930                     title: "Missing required package fields".to_string(),
1931                     label: wrap(label),
1932                 };
1933                 write_project(buf, diagnostic);
1934                 let msg = if *description_missing && *licence_missing {
1935                     r#"Add the licences and description fields to your gleam.toml file.
1936 
1937 description = "A Gleam library"
1938 licences = ["Apache-2.0"]"#
1939                 } else if *description_missing {
1940                     r#"Add the description field to your gleam.toml file.
1941 
1942 description = "A Gleam library""#
1943                 } else {
1944                     r#"Add the licences field to your gleam.toml file.
1945 
1946 licences = ["Apache-2.0"]"#
1947                 };
1948                 wrap_writeln!(buf, "{}", &msg).unwrap();
1949             }
1950 
1951             Error::UnsupportedBuildTool {
1952                 package,
1953                 build_tools,
1954             } => {
1955                 let diagnostic = ProjectErrorDiagnostic {
1956                     title: "Unsupported build tool".to_string(),
1957                     label: wrap(&format!(
1958                         "The package {} cannot be built as it does not use \
1959 a build tool supported by Gleam. It uses {:?}.
1960 
1961 If you would like us to support this package please let us know by opening an \
1962 issue in our tracker: https://github.com/gleam-lang/gleam/issues",
1963                         package, build_tools
1964                     )),
1965                 };
1966                 write_project(buf, diagnostic);
1967             }
1968         }
1969     }
1970 }
1971 
std_io_error_kind_text(kind: &std::io::ErrorKind) -> String1972 fn std_io_error_kind_text(kind: &std::io::ErrorKind) -> String {
1973     use std::io::ErrorKind;
1974     match kind {
1975         ErrorKind::NotFound => "Could not find the stdio stream".to_string(),
1976         ErrorKind::PermissionDenied => "Permission was denied".to_string(),
1977         ErrorKind::ConnectionRefused => "Connection was refused".to_string(),
1978         ErrorKind::ConnectionReset => "Connection was reset".to_string(),
1979         ErrorKind::ConnectionAborted => "Connection was aborted".to_string(),
1980         ErrorKind::NotConnected => "Was not connected".to_string(),
1981         ErrorKind::AddrInUse => "The stream was already in use".to_string(),
1982         ErrorKind::AddrNotAvailable => "The stream was not available".to_string(),
1983         ErrorKind::BrokenPipe => "The pipe was broken".to_string(),
1984         ErrorKind::AlreadyExists => "A handle to the stream already exists".to_string(),
1985         ErrorKind::WouldBlock => {
1986             "This operation would block when it was requested not to".to_string()
1987         }
1988         ErrorKind::InvalidInput => "Some parameter was invalid".to_string(),
1989         ErrorKind::InvalidData => {
1990             "The data was invalid.  Check that the encoding is UTF-8".to_string()
1991         }
1992         ErrorKind::TimedOut => "The operation timed out".to_string(),
1993         ErrorKind::WriteZero => {
1994             "An attempt was made to write, but all bytes could not be written".to_string()
1995         }
1996         ErrorKind::Interrupted => "The operation was interrupted".to_string(),
1997         ErrorKind::UnexpectedEof => {
1998             "The end of file was reached before it was expected".to_string()
1999         }
2000         _ => "An unknown error occurred".to_string(),
2001     }
2002 }
2003 
import_cycle(buffer: &mut Buffer, modules: &[String])2004 fn import_cycle(buffer: &mut Buffer, modules: &[String]) {
2005     use std::io::Write;
2006     use termcolor::{Color, ColorSpec, WriteColor};
2007 
2008     writeln!(
2009         buffer,
2010         "
2011     ┌─────┐"
2012     )
2013     .unwrap();
2014     for (index, name) in modules.iter().enumerate() {
2015         if index != 0 {
2016             writeln!(buffer, "    │     ↓").unwrap();
2017         }
2018         write!(buffer, "    │    ").unwrap();
2019         buffer
2020             .set_color(ColorSpec::new().set_fg(Some(Color::Cyan)))
2021             .unwrap();
2022         writeln!(buffer, "{}", name).unwrap();
2023         buffer.set_color(&ColorSpec::new()).unwrap();
2024     }
2025     writeln!(buffer, "    └─────┘\n").unwrap();
2026 }
2027 
hint_alternative_operator(op: &BinOp, given: &Type) -> Option<String>2028 fn hint_alternative_operator(op: &BinOp, given: &Type) -> Option<String> {
2029     match op {
2030         BinOp::AddInt if given.is_float() => Some(hint_numeric_message("+.", "Float")),
2031         BinOp::DivInt if given.is_float() => Some(hint_numeric_message("/.", "Float")),
2032         BinOp::GtEqInt if given.is_float() => Some(hint_numeric_message(">=", "Float")),
2033         BinOp::GtInt if given.is_float() => Some(hint_numeric_message(">", "Float")),
2034         BinOp::LtEqInt if given.is_float() => Some(hint_numeric_message("<=.", "Float")),
2035         BinOp::LtInt if given.is_float() => Some(hint_numeric_message("<.", "Float")),
2036         BinOp::MultInt if given.is_float() => Some(hint_numeric_message("*.", "Float")),
2037         BinOp::SubInt if given.is_float() => Some(hint_numeric_message("-.", "Float")),
2038 
2039         BinOp::AddFloat if given.is_int() => Some(hint_numeric_message("+", "Int")),
2040         BinOp::DivFloat if given.is_int() => Some(hint_numeric_message("/", "Int")),
2041         BinOp::GtEqFloat if given.is_int() => Some(hint_numeric_message(">=.", "Int")),
2042         BinOp::GtFloat if given.is_int() => Some(hint_numeric_message(">.", "Int")),
2043         BinOp::LtEqFloat if given.is_int() => Some(hint_numeric_message("<=", "Int")),
2044         BinOp::LtFloat if given.is_int() => Some(hint_numeric_message("<", "Int")),
2045         BinOp::MultFloat if given.is_int() => Some(hint_numeric_message("*", "Int")),
2046         BinOp::SubFloat if given.is_int() => Some(hint_numeric_message("-", "Int")),
2047 
2048         BinOp::AddInt if given.is_string() => Some(hint_string_message()),
2049         BinOp::AddFloat if given.is_string() => Some(hint_string_message()),
2050 
2051         _ => None,
2052     }
2053 }
2054 
hint_numeric_message(alt: &str, type_: &str) -> String2055 fn hint_numeric_message(alt: &str, type_: &str) -> String {
2056     format!("the {} operator can be used with {}s\n", alt, type_)
2057 }
2058 
hint_string_message() -> String2059 fn hint_string_message() -> String {
2060     wrap("Strings can be joined using the `append` or `concat` functions from the `gleam/string` module")
2061 }
2062 
2063 #[derive(Debug, PartialEq)]
2064 pub struct Unformatted {
2065     pub source: PathBuf,
2066     pub destination: PathBuf,
2067     pub input: String,
2068     pub output: String,
2069 }
2070 
wrap(text: &str) -> String2071 pub fn wrap(text: &str) -> String {
2072     textwrap::fill(text, std::cmp::min(75, textwrap::termwidth()))
2073 }
2074