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