Fork of daniellemaywood.uk/gleam — Wasm codegen work
123 kB
3265 lines
1#![allow(clippy::unwrap_used, clippy::expect_used)]
2use crate::build::{Runtime, Target};
3use crate::diagnostic::{Diagnostic, Label, Location};
4use crate::type_::error::RecordVariants;
5use crate::type_::error::{MissingAnnotation, UnknownTypeHint};
6use crate::type_::{error::PatternMatchKind, FieldAccessUsage};
7use crate::{ast::BinOp, parse::error::ParseErrorType, type_::Type};
8use crate::{
9 bit_array,
10 diagnostic::Level,
11 javascript,
12 type_::{pretty::Printer, UnifyErrorSituation},
13};
14use ecow::EcoString;
15use hexpm::version::pubgrub_report::{DefaultStringReporter, Reporter};
16use hexpm::version::ResolutionError;
17use itertools::Itertools;
18use std::env;
19use std::fmt::Debug;
20use std::path::PathBuf;
21use termcolor::Buffer;
22use thiserror::Error;
23
24use camino::{Utf8Path, Utf8PathBuf};
25
26pub type Name = EcoString;
27
28pub type Result<Ok, Err = Error> = std::result::Result<Ok, Err>;
29
30macro_rules! wrap_format {
31 ($($tts:tt)*) => {
32 wrap(&format!($($tts)*))
33 }
34}
35
36#[derive(Debug, Clone, Eq, PartialEq)]
37pub struct UnknownImportDetails {
38 pub module: Name,
39 pub location: crate::ast::SrcSpan,
40 pub path: Utf8PathBuf,
41 pub src: EcoString,
42 pub modules: Vec<EcoString>,
43}
44
45#[derive(Debug, Eq, PartialEq, Error, Clone)]
46pub enum Error {
47 #[error("failed to parse Gleam source code")]
48 Parse {
49 path: Utf8PathBuf,
50 src: EcoString,
51 error: crate::parse::error::ParseError,
52 },
53
54 #[error("type checking failed")]
55 Type {
56 path: Utf8PathBuf,
57 src: EcoString,
58 error: crate::type_::Error,
59 },
60
61 #[error("unknown import {import}")]
62 UnknownImport {
63 import: EcoString,
64 // Boxed to prevent this variant from being overly large
65 details: Box<UnknownImportDetails>,
66 },
67
68 #[error("duplicate module {module}")]
69 DuplicateModule {
70 module: Name,
71 first: Utf8PathBuf,
72 second: Utf8PathBuf,
73 },
74
75 #[error("duplicate source file {file}")]
76 DuplicateSourceFile { file: String },
77
78 #[error("cyclical module imports")]
79 ImportCycle { modules: Vec<EcoString> },
80
81 #[error("cyclical package dependencies")]
82 PackageCycle { packages: Vec<EcoString> },
83
84 #[error("file operation failed")]
85 FileIo {
86 kind: FileKind,
87 action: FileIoAction,
88 path: Utf8PathBuf,
89 err: Option<String>,
90 },
91
92 #[error("Non Utf-8 Path: {path}")]
93 NonUtf8Path { path: PathBuf },
94
95 #[error("{error}")]
96 GitInitialization { error: String },
97
98 #[error("io operation failed")]
99 StandardIo {
100 action: StandardIoAction,
101 err: Option<std::io::ErrorKind>,
102 },
103
104 #[error("source code incorrectly formatted")]
105 Format { problem_files: Vec<Unformatted> },
106
107 #[error("Hex error: {0}")]
108 Hex(String),
109
110 #[error("{error}")]
111 ExpandTar { error: String },
112
113 #[error("{err}")]
114 AddTar { path: Utf8PathBuf, err: String },
115
116 #[error("{0}")]
117 TarFinish(String),
118
119 #[error("{0}")]
120 Gzip(String),
121
122 #[error("shell program `{program}` not found")]
123 ShellProgramNotFound { program: String },
124
125 #[error("shell program `{program}` failed")]
126 ShellCommand {
127 program: String,
128 err: Option<std::io::ErrorKind>,
129 },
130
131 #[error("{name} is not a valid project name")]
132 InvalidProjectName {
133 name: String,
134 reason: InvalidProjectNameReason,
135 },
136
137 #[error("{module} is not a valid module name")]
138 InvalidModuleName { module: String },
139
140 #[error("{module} is not module")]
141 ModuleDoesNotExist {
142 module: EcoString,
143 suggestion: Option<EcoString>,
144 },
145
146 #[error("{module} does not have a main function")]
147 ModuleDoesNotHaveMainFunction { module: EcoString },
148
149 #[error("{module}'s main function has the wrong arity so it can not be run")]
150 MainFunctionHasWrongArity { module: EcoString, arity: usize },
151
152 #[error("{module}'s main function does not support the current target")]
153 MainFunctionDoesNotSupportTarget { module: EcoString, target: Target },
154
155 #[error("{input} is not a valid version. {error}")]
156 InvalidVersionFormat { input: String, error: String },
157
158 #[error("project root already exists")]
159 ProjectRootAlreadyExist { path: String },
160
161 #[error("File(s) already exist in {}",
162file_names.iter().map(|x| x.as_str()).join(", "))]
163 OutputFilesAlreadyExist { file_names: Vec<Utf8PathBuf> },
164
165 #[error("unable to find project root")]
166 UnableToFindProjectRoot { path: String },
167
168 #[error("gleam.toml version {toml_ver} does not match .app version {app_ver}")]
169 VersionDoesNotMatch { toml_ver: String, app_ver: String },
170
171 #[error("metadata decoding failed")]
172 MetadataDecodeError { error: Option<String> },
173
174 #[error("warnings are not permitted")]
175 ForbiddenWarnings { count: usize },
176
177 #[error("javascript codegen failed")]
178 JavaScript {
179 path: Utf8PathBuf,
180 src: EcoString,
181 error: crate::javascript::Error,
182 },
183
184 #[error("Invalid runtime for {target} target: {invalid_runtime}")]
185 InvalidRuntime {
186 target: Target,
187 invalid_runtime: Runtime,
188 },
189
190 #[error("package downloading failed: {error}")]
191 DownloadPackageError {
192 package_name: String,
193 package_version: String,
194 error: String,
195 },
196
197 #[error("{0}")]
198 Http(String),
199
200 #[error("Git dependencies are currently unsupported")]
201 GitDependencyUnsupported,
202
203 #[error("Failed to create canonical path for package {0}")]
204 DependencyCanonicalizationFailed(String),
205
206 #[error("Dependency tree resolution failed: {0}")]
207 DependencyResolutionFailed(String),
208
209 #[error("The package {0} is listed in dependencies and dev-dependencies")]
210 DuplicateDependency(EcoString),
211
212 #[error("Expected package {expected} at path {path} but found {found} instead")]
213 WrongDependencyProvided {
214 path: Utf8PathBuf,
215 expected: String,
216 found: String,
217 },
218
219 #[error("The package {package} is provided multiple times, as {source_1} and {source_2}")]
220 ProvidedDependencyConflict {
221 package: String,
222 source_1: String,
223 source_2: String,
224 },
225
226 #[error("The package was missing required fields for publishing")]
227 MissingHexPublishFields {
228 description_missing: bool,
229 licence_missing: bool,
230 },
231
232 #[error("Dependency {package:?} has not been published to Hex")]
233 PublishNonHexDependencies { package: String },
234
235 #[error("The package {package} uses unsupported build tools {build_tools:?}")]
236 UnsupportedBuildTool {
237 package: String,
238 build_tools: Vec<EcoString>,
239 },
240
241 #[error("Opening docs at {path} failed: {error}")]
242 FailedToOpenDocs { path: Utf8PathBuf, error: String },
243
244 #[error("The package {package} requires a Gleam version satisfying {required_version} and you are using v{gleam_version}")]
245 IncompatibleCompilerVersion {
246 package: String,
247 required_version: String,
248 gleam_version: String,
249 },
250
251 #[error("The --javascript-prelude flag must be given when compiling to JavaScript")]
252 JavaScriptPreludeRequired,
253
254 #[error("The modules {unfinished:?} contain todo expressions and so cannot be published")]
255 CannotPublishTodo { unfinished: Vec<EcoString> },
256
257 #[error("The modules {unfinished:?} contain internal types in their public API so cannot be published")]
258 CannotPublishLeakedInternalType { unfinished: Vec<EcoString> },
259
260 #[error("Publishing packages to reserve names is not permitted")]
261 HexPackageSquatting,
262}
263
264impl Error {
265 pub fn http<E>(error: E) -> Error
266 where
267 E: std::error::Error,
268 {
269 Self::Http(error.to_string())
270 }
271
272 pub fn hex<E>(error: E) -> Error
273 where
274 E: std::error::Error,
275 {
276 Self::Hex(error.to_string())
277 }
278
279 pub fn add_tar<P, E>(path: P, error: E) -> Error
280 where
281 P: AsRef<Utf8Path>,
282 E: std::error::Error,
283 {
284 Self::AddTar {
285 path: path.as_ref().to_path_buf(),
286 err: error.to_string(),
287 }
288 }
289
290 pub fn finish_tar<E>(error: E) -> Error
291 where
292 E: std::error::Error,
293 {
294 Self::TarFinish(error.to_string())
295 }
296
297 pub fn dependency_resolution_failed(error: ResolutionError) -> Error {
298 Self::DependencyResolutionFailed(match error {
299 ResolutionError::NoSolution(mut derivation_tree) => {
300 derivation_tree.collapse_no_versions();
301 let report = DefaultStringReporter::report(&derivation_tree);
302 wrap(&report)
303 }
304
305 ResolutionError::ErrorRetrievingDependencies {
306 package,
307 version,
308 source,
309 } => format!(
310 "An error occurred while trying to retrieve dependencies of {package}@{version}: {source}",
311 ),
312
313 ResolutionError::DependencyOnTheEmptySet {
314 package,
315 version,
316 dependent,
317 } => format!(
318 "{package}@{version} has an impossible dependency on {dependent}",
319 ),
320
321 ResolutionError::SelfDependency { package, version } => {
322 format!("{package}@{version} somehow depends on itself.")
323 }
324
325 ResolutionError::ErrorChoosingPackageVersion(err) => {
326 format!("Unable to determine package versions: {err}")
327 }
328
329 ResolutionError::ErrorInShouldCancel(err) => {
330 format!("Dependency resolution was cancelled. {err}")
331 }
332
333 ResolutionError::Failure(err) => format!(
334 "An unrecoverable error happened while solving dependencies: {err}"
335 ),
336 })
337 }
338
339 pub fn expand_tar<E>(error: E) -> Error
340 where
341 E: std::error::Error,
342 {
343 Self::ExpandTar {
344 error: error.to_string(),
345 }
346 }
347}
348
349impl From<capnp::Error> for Error {
350 fn from(error: capnp::Error) -> Self {
351 Error::MetadataDecodeError {
352 error: Some(error.to_string()),
353 }
354 }
355}
356
357impl From<capnp::NotInSchema> for Error {
358 fn from(error: capnp::NotInSchema) -> Self {
359 Error::MetadataDecodeError {
360 error: Some(error.to_string()),
361 }
362 }
363}
364
365#[derive(Debug, PartialEq, Eq, Clone, Copy)]
366pub enum InvalidProjectNameReason {
367 Format,
368 GleamPrefix,
369 ErlangReservedWord,
370 ErlangStandardLibraryModule,
371 GleamReservedWord,
372 GleamReservedModule,
373}
374
375#[derive(Debug, PartialEq, Eq, Clone, Copy)]
376pub enum StandardIoAction {
377 Read,
378 Write,
379}
380
381impl StandardIoAction {
382 fn text(&self) -> &'static str {
383 match self {
384 StandardIoAction::Read => "read from",
385 StandardIoAction::Write => "write to",
386 }
387 }
388}
389
390#[derive(Debug, PartialEq, Eq, Clone, Copy)]
391pub enum FileIoAction {
392 Link,
393 Open,
394 Copy,
395 Read,
396 Parse,
397 Delete,
398 // Rename,
399 Create,
400 WriteTo,
401 Canonicalise,
402 UpdatePermissions,
403 FindParent,
404 ReadMetadata,
405}
406
407impl FileIoAction {
408 fn text(&self) -> &'static str {
409 match self {
410 FileIoAction::Link => "link",
411 FileIoAction::Open => "open",
412 FileIoAction::Copy => "copy",
413 FileIoAction::Read => "read",
414 FileIoAction::Parse => "parse",
415 FileIoAction::Delete => "delete",
416 // FileIoAction::Rename => "rename",
417 FileIoAction::Create => "create",
418 FileIoAction::WriteTo => "write to",
419 FileIoAction::FindParent => "find the parent of",
420 FileIoAction::Canonicalise => "canonicalise",
421 FileIoAction::UpdatePermissions => "update permissions of",
422 FileIoAction::ReadMetadata => "read metadata of",
423 }
424 }
425}
426
427#[derive(Debug, Clone, Copy, PartialEq, Eq)]
428pub enum FileKind {
429 File,
430 Directory,
431}
432
433impl FileKind {
434 fn text(&self) -> &'static str {
435 match self {
436 FileKind::File => "file",
437 FileKind::Directory => "directory",
438 }
439 }
440}
441
442// https://github.com/rust-lang/rust/blob/03994e498df79aa1f97f7bbcfd52d57c8e865049/compiler/rustc_span/src/edit_distance.rs
443fn edit_distance(a: &str, b: &str, limit: usize) -> Option<usize> {
444 let mut a = &a.chars().collect::<Vec<_>>()[..];
445 let mut b = &b.chars().collect::<Vec<_>>()[..];
446
447 if a.len() < b.len() {
448 std::mem::swap(&mut a, &mut b);
449 }
450
451 let min_dist = a.len() - b.len();
452 // If we know the limit will be exceeded, we can return early.
453 if min_dist > limit {
454 return None;
455 }
456
457 // Strip common prefix.
458 while !b.is_empty() && !a.is_empty() {
459 let (b_first, b_rest) = b.split_last().expect("Failed to split 'b' slice");
460 let (a_first, a_rest) = a.split_last().expect("Failed to split 'a' slice");
461
462 if b_first == a_first {
463 a = a_rest;
464 b = b_rest;
465 } else {
466 break;
467 }
468 }
469
470 // If either string is empty, the distance is the length of the other.
471 // We know that `b` is the shorter string, so we don't need to check `a`.
472 if b.is_empty() {
473 return Some(min_dist);
474 }
475
476 let mut prev_prev = vec![usize::MAX; b.len() + 1];
477 let mut prev = (0..=b.len()).collect::<Vec<_>>();
478 let mut current = vec![0; b.len() + 1];
479
480 // row by row
481 for i in 1..=a.len() {
482 if let Some(elem) = current.get_mut(0) {
483 *elem = i;
484 }
485 let a_idx = i - 1;
486
487 // column by column
488 for j in 1..=b.len() {
489 let b_idx = j - 1;
490
491 // There is no cost to substitute a character with itself.
492 let substitution_cost = match (a.get(a_idx), b.get(b_idx)) {
493 (Some(&a_char), Some(&b_char)) => {
494 if a_char == b_char {
495 0
496 } else {
497 1
498 }
499 }
500 _ => panic!("Index out of bounds"),
501 };
502
503 let insertion = current.get(j - 1).map_or(std::usize::MAX, |&x| x + 1);
504
505 if let Some(value) = current.get_mut(j) {
506 *value = std::cmp::min(
507 // deletion
508 prev.get(j).map_or(std::usize::MAX, |&x| x + 1),
509 std::cmp::min(
510 // insertion
511 insertion,
512 // substitution
513 prev.get(j - 1)
514 .map_or(std::usize::MAX, |&x| x + substitution_cost),
515 ),
516 );
517 }
518
519 if (i > 1) && (j > 1) {
520 if let (Some(&a_val), Some(&b_val_prev), Some(&a_val_prev), Some(&b_val)) = (
521 a.get(a_idx),
522 b.get(b_idx - 1),
523 a.get(a_idx - 1),
524 b.get(b_idx),
525 ) {
526 if (a_val == b_val_prev) && (a_val_prev == b_val) {
527 // transposition
528 if let Some(curr) = current.get_mut(j) {
529 if let Some(&prev_prev_val) = prev_prev.get(j - 2) {
530 *curr = std::cmp::min(*curr, prev_prev_val + 1);
531 }
532 }
533 }
534 }
535 }
536 }
537
538 // Rotate the buffers, reusing the memory.
539 [prev_prev, prev, current] = [prev, current, prev_prev];
540 }
541
542 // `prev` because we already rotated the buffers.
543 let distance = match prev.get(b.len()) {
544 Some(&d) => d,
545 None => std::usize::MAX,
546 };
547 (distance <= limit).then_some(distance)
548}
549
550fn edit_distance_with_substrings(a: &str, b: &str, limit: usize) -> Option<usize> {
551 let n = a.chars().count();
552 let m = b.chars().count();
553
554 // Check one isn't less than half the length of the other. If this is true then there is a
555 // big difference in length.
556 let big_len_diff = (n * 2) < m || (m * 2) < n;
557 let len_diff = if n < m { m - n } else { n - m };
558 let distance = edit_distance(a, b, limit + len_diff)?;
559
560 // This is the crux, subtracting length difference means exact substring matches will now be 0
561 let score = distance - len_diff;
562
563 // If the score is 0 but the words have different lengths then it's a substring match not a full
564 // word match
565 let score = if score == 0 && len_diff > 0 && !big_len_diff {
566 1 // Exact substring match, but not a total word match so return non-zero
567 } else if !big_len_diff {
568 // Not a big difference in length, discount cost of length difference
569 score + (len_diff + 1) / 2
570 } else {
571 // A big difference in length, add back the difference in length to the score
572 score + len_diff
573 };
574
575 (score <= limit).then_some(score)
576}
577
578fn did_you_mean(name: &str, options: &[EcoString]) -> Option<String> {
579 // If only one option is given, return that option.
580 // This seems to solve the `unknown_variable_3` test.
581 if options.len() == 1 {
582 return options
583 .first()
584 .map(|option| format!("Did you mean `{}`?", option));
585 }
586
587 // Check for case-insensitive matches.
588 // This solves the comparison to small and single character terms,
589 // such as the test on `type_vars_must_be_declared`.
590 if let Some(exact_match) = options
591 .iter()
592 .find(|&option| option.eq_ignore_ascii_case(name))
593 {
594 return Some(format!("Did you mean `{}`?", exact_match));
595 }
596
597 // Calculate the threshold as one third of the name's length, with a minimum of 1.
598 let threshold = std::cmp::max(name.chars().count() / 3, 1);
599
600 // Filter and sort options based on edit distance.
601 options
602 .iter()
603 .filter(|&option| option != crate::ast::CAPTURE_VARIABLE)
604 .sorted()
605 .filter_map(|option| {
606 edit_distance_with_substrings(option, name, threshold)
607 .map(|distance| (option, distance))
608 })
609 .min_by_key(|&(_, distance)| distance)
610 .map(|(option, _)| format!("Did you mean `{}`?", option))
611}
612
613impl Error {
614 pub fn pretty_string(&self) -> String {
615 self.to_diagnostic().pretty_string()
616 }
617
618 pub fn pretty(&self, buffer: &mut Buffer) {
619 self.to_diagnostic().write(buffer)
620 }
621
622 pub fn to_diagnostic(&self) -> Diagnostic {
623 use crate::type_::Error as TypeError;
624 match self {
625 Error::HexPackageSquatting => {
626 let text =
627 "You appear to be attempting to reserve a name on Hex rather than publishing a
628working package. This is against the Hex terms of service and can result in
629package deletion or account suspension.
630"
631 .into();
632
633 Diagnostic {
634 title: "Invalid Hex package".into(),
635 text,
636 level: Level::Error,
637 location: None,
638 hint: None,
639 }
640 }
641
642 Error::MetadataDecodeError { error } => {
643 let mut text = "A problem was encountered when decoding the metadata for one \
644of the Gleam dependency modules."
645 .to_string();
646 if let Some(error) = error {
647 text.push_str("\nThe error from the decoder library was:\n\n");
648 text.push_str(error);
649 }
650
651 Diagnostic {
652 title: "Failed to decode module metadata".into(),
653 text,
654 level: Level::Error,
655 location: None,
656 hint: None,
657 }
658 }
659
660 Error::InvalidProjectName { name, reason } => {
661 let text = wrap_format!(
662 "We were not able to create your project as `{}` {}
663
664Please try again with a different project name.",
665 name,
666 match reason {
667 InvalidProjectNameReason::ErlangReservedWord =>
668 "is a reserved word in Erlang.",
669 InvalidProjectNameReason::ErlangStandardLibraryModule =>
670 "is a standard library module in Erlang.",
671 InvalidProjectNameReason::GleamReservedWord =>
672 "is a reserved word in Gleam.",
673 InvalidProjectNameReason::GleamReservedModule =>
674 "is a reserved module name in Gleam.",
675 InvalidProjectNameReason::Format =>
676 "does not have the correct format. Project names \
677must start with a lowercase letter and may only contain lowercase letters, \
678numbers and underscores.",
679 InvalidProjectNameReason::GleamPrefix =>
680 "has the reserved prefix `gleam_`. \
681This prefix is intended for official Gleam packages only.",
682 }
683 );
684
685 Diagnostic {
686 title: "Invalid project name".into(),
687 text,
688 hint: None,
689 level: Level::Error,
690 location: None,
691 }
692 }
693
694 Error::InvalidModuleName { module } => Diagnostic {
695 title: "Invalid module name".into(),
696 text: format!(
697 "`{module}` is not a valid module name.
698Module names can only contain lowercase letters, underscore, and
699forward slash and must not end with a slash."
700 ),
701 level: Level::Error,
702 location: None,
703 hint: None,
704 },
705
706 Error::ModuleDoesNotExist { module, suggestion } => {
707 let hint = match suggestion {
708 Some(suggestion) => format!("Did you mean `{suggestion}`?"),
709 None => format!("Try creating the file `src/{module}.gleam`."),
710 };
711 Diagnostic {
712 title: "Module does not exist".into(),
713 text: format!("Module `{module}` was not found."),
714 level: Level::Error,
715 location: None,
716 hint: Some(hint),
717 }
718 }
719
720 Error::ModuleDoesNotHaveMainFunction { module } => Diagnostic {
721 title: "Module does not have a main function".into(),
722 text: format!(
723 "`{module}` does not have a main function so the module can not be run."
724 ),
725 level: Level::Error,
726 location: None,
727 hint: Some(format!(
728 "Add a public `main` function to \
729to `src/{module}.gleam`."
730 )),
731 },
732
733 Error::MainFunctionDoesNotSupportTarget { module, target } => Diagnostic {
734 title: "Target not supported".into(),
735 text: wrap_format!(
736 "`{module}` has a main function, but it does not support the {target} \
737target, so it cannot be run."
738 ),
739 level: Level::Error,
740 location: None,
741 hint: None,
742 },
743
744 Error::MainFunctionHasWrongArity { module, arity } => Diagnostic {
745 title: "Main function has wrong arity".into(),
746 text: format!(
747 "`{module}:main` should have an arity of 0 to be run but its arity is {arity}."
748 ),
749 level: Level::Error,
750 location: None,
751 hint: Some("Change the function signature of main to `pub fn main() {}`.".into()),
752 },
753
754 Error::ProjectRootAlreadyExist { path } => Diagnostic {
755 title: "Project folder already exists".into(),
756 text: format!("Project folder root:\n\n {path}"),
757 level: Level::Error,
758 hint: None,
759 location: None,
760 },
761
762 Error::OutputFilesAlreadyExist { file_names } => Diagnostic {
763 title: format!(
764 "{} already exist{} in target directory",
765 if file_names.len() == 1 {
766 "File"
767 } else {
768 "Files"
769 },
770 if file_names.len() == 1 { "" } else { "s" }
771 ),
772 text: format!(
773 "{}
774If you want to overwrite these files, delete them and run the command again.
775",
776 file_names
777 .iter()
778 .map(|name| format!(" - {}", name.as_str()))
779 .join("\n")
780 ),
781 level: Level::Error,
782 hint: None,
783 location: None,
784 },
785
786 Error::CannotPublishTodo { unfinished } => Diagnostic {
787 title: "Cannot publish unfinished code".into(),
788 text: format!(
789 "These modules contain todo expressions and cannot be published:
790
791{}
792
793Please remove them and try again.
794",
795 unfinished
796 .iter()
797 .map(|name| format!(" - {}", name.as_str()))
798 .join("\n")
799 ),
800 level: Level::Error,
801 hint: None,
802 location: None,
803 },
804
805 Error::CannotPublishLeakedInternalType { unfinished } => Diagnostic {
806 title: "Cannot publish unfinished code".into(),
807 text: format!(
808 "These modules leak internal types in their public API and cannot be published:
809
810{}
811
812Please make sure internal types do not appear in public functions and try again.
813",
814 unfinished
815 .iter()
816 .map(|name| format!(" - {}", name.as_str()))
817 .join("\n")
818 ),
819 level: Level::Error,
820 hint: None,
821 location: None,
822 },
823
824 Error::UnableToFindProjectRoot { path } => {
825 let text = wrap_format!(
826 "We were unable to find gleam.toml.
827
828We searched in {path} and all parent directories."
829 );
830 Diagnostic {
831 title: "Project not found".into(),
832 text,
833 hint: None,
834 level: Level::Error,
835 location: None,
836 }
837 }
838
839 Error::VersionDoesNotMatch { toml_ver, app_ver } => {
840 let text = format!(
841 "The version in gleam.toml \"{toml_ver}\" does not match the version in
842your app.src file \"{app_ver}\"."
843 );
844 Diagnostic {
845 title: "Version does not match".into(),
846 hint: None,
847 text,
848 level: Level::Error,
849 location: None,
850 }
851 }
852
853 Error::ShellProgramNotFound { program } => {
854 let mut text = format!("The program `{program}` was not found. Is it installed?");
855
856 match program.as_str() {
857 "erl" | "erlc" | "escript" => text.push_str(
858 "
859Documentation for installing Erlang can be viewed here:
860https://gleam.run/getting-started/installing/",
861 ),
862 "rebar3" => text.push_str(
863 "
864Documentation for installing rebar3 can be viewed here:
865https://gleam.run/getting-started/installing/",
866 ),
867 _ => (),
868 }
869 match (program.as_str(), env::consts::OS) {
870 // TODO: Further suggestions for other OSes?
871 ("erl" | "erlc" | "escript", "macos") => text.push_str(
872 "
873You can also install Erlang via homebrew using \"brew install erlang\"",
874 ),
875 ("rebar3", "macos") => text.push_str(
876 "
877You can also install rebar3 via homebrew using \"brew install rebar3\"",
878 ),
879 _ => (),
880 };
881
882 Diagnostic {
883 title: "Program not found".into(),
884 text,
885 hint: None,
886 level: Level::Error,
887 location: None,
888 }
889 }
890
891 Error::ShellCommand {
892 program: command,
893 err: None,
894 } => {
895 let text =
896 format!("There was a problem when running the shell command `{command}`.");
897 Diagnostic {
898 title: "Shell command failure".into(),
899 text,
900 hint: None,
901 level: Level::Error,
902 location: None,
903 }
904 }
905
906 Error::ShellCommand {
907 program: command,
908 err: Some(err),
909 } => {
910 let text = format!(
911 "There was a problem when running the shell command `{}`.
912
913The error from the shell command library was:
914
915 {}",
916 command,
917 std_io_error_kind_text(err)
918 );
919 Diagnostic {
920 title: "Shell command failure".into(),
921 text,
922 hint: None,
923 level: Level::Error,
924 location: None,
925 }
926 }
927
928 Error::Gzip(detail) => {
929 let text = format!(
930 "There was a problem when applying gzip compression.
931
932This was error from the gzip library:
933
934 {detail}"
935 );
936 Diagnostic {
937 title: "Gzip compression failure".into(),
938 text,
939 hint: None,
940 level: Level::Error,
941 location: None,
942 }
943 }
944
945 Error::AddTar { path, err } => {
946 let text = format!(
947 "There was a problem when attempting to add the file {path}
948to a tar archive.
949
950This was error from the tar library:
951
952 {err}"
953 );
954 Diagnostic {
955 title: "Failure creating tar archive".into(),
956 text,
957 hint: None,
958 level: Level::Error,
959 location: None,
960 }
961 }
962
963 Error::ExpandTar { error } => {
964 let text = format!(
965 "There was a problem when attempting to expand a to a tar archive.
966
967This was error from the tar library:
968
969 {error}"
970 );
971 Diagnostic {
972 title: "Failure opening tar archive".into(),
973 text,
974 hint: None,
975 level: Level::Error,
976 location: None,
977 }
978 }
979
980 Error::TarFinish(detail) => {
981 let text = format!(
982 "There was a problem when creating a tar archive.
983
984This was error from the tar library:
985
986 {detail}"
987 );
988 Diagnostic {
989 title: "Failure creating tar archive".into(),
990 text,
991 hint: None,
992 level: Level::Error,
993 location: None,
994 }
995 }
996
997 Error::Hex(detail) => {
998 let text = format!(
999 "There was a problem when using the Hex API.
1000
1001This was error from the Hex client library:
1002
1003 {detail}"
1004 );
1005 Diagnostic {
1006 title: "Hex API failure".into(),
1007 text,
1008 hint: None,
1009 level: Level::Error,
1010 location: None,
1011 }
1012 }
1013
1014 Error::DuplicateModule {
1015 module,
1016 first,
1017 second,
1018 } => {
1019 let text = format!(
1020 "The module `{module}` is defined multiple times.
1021
1022First: {first}
1023Second: {second}"
1024 );
1025
1026 Diagnostic {
1027 title: "Duplicate module".into(),
1028 text,
1029 hint: None,
1030 level: Level::Error,
1031 location: None,
1032 }
1033 }
1034
1035 Error::DuplicateSourceFile { file } => Diagnostic {
1036 title: "Duplicate Source file".into(),
1037 text: format!("The file `{file}` is defined multiple times."),
1038 hint: None,
1039 level: Level::Error,
1040 location: None,
1041 },
1042
1043 Error::FileIo {
1044 kind,
1045 action,
1046 path,
1047 err,
1048 } => {
1049 let err = match err {
1050 Some(e) => {
1051 format!("\nThe error message from the file IO library was:\n\n {e}\n")
1052 }
1053 None => "".into(),
1054 };
1055 let text = format!(
1056 "An error occurred while trying to {} this {}:
1057
1058 {}
1059{}",
1060 action.text(),
1061 kind.text(),
1062 path,
1063 err,
1064 );
1065 Diagnostic {
1066 title: "File IO failure".into(),
1067 text,
1068 hint: None,
1069 level: Level::Error,
1070 location: None,
1071 }
1072 }
1073
1074 Error::NonUtf8Path { path } => {
1075 let text = format!(
1076 "Encountered a non UTF-8 path '{}', but only UTF-8 paths are supported.",
1077 path.to_string_lossy()
1078 );
1079 Diagnostic {
1080 title: "Non UTF-8 Path Encountered".into(),
1081 text,
1082 level: Level::Error,
1083 location: None,
1084 hint: None,
1085 }
1086 }
1087
1088 Error::GitInitialization { error } => {
1089 let text = format!(
1090 "An error occurred while trying make a git repository for this project:
1091
1092 {error}"
1093 );
1094 Diagnostic {
1095 title: "Failed to initialize git repository".into(),
1096 text,
1097 hint: None,
1098 level: Level::Error,
1099 location: None,
1100 }
1101 }
1102
1103 Error::Type { path, src, error } => match error {
1104 TypeError::SrcImportingTest {
1105 location,
1106 src_module,
1107 test_module,
1108 } => {
1109 let text = wrap_format!(
1110 "The application module `{src_module}` is importing the test module `{test_module}`.
1111
1112Test modules are not included in production builds so test \
1113modules cannot import them. Perhaps move the `{test_module}` module to the src directory.",
1114 );
1115
1116 Diagnostic {
1117 title: "App importing test module".into(),
1118 text,
1119 hint: None,
1120 level: Level::Error,
1121 location: Some(Location {
1122 label: Label {
1123 text: Some("Imported here".into()),
1124 span: *location,
1125 },
1126 path: path.clone(),
1127 src: src.clone(),
1128 extra_labels: vec![],
1129 }),
1130 }
1131 }
1132
1133 TypeError::UnknownLabels {
1134 unknown,
1135 valid,
1136 supplied,
1137 } => {
1138 let other_labels: Vec<_> = valid
1139 .iter()
1140 .filter(|label| !supplied.contains(label))
1141 .cloned()
1142 .collect();
1143
1144 let title = if unknown.len() > 1 {
1145 "Unknown labels"
1146 } else {
1147 "Unknown label"
1148 }
1149 .into();
1150
1151 let mut labels = unknown.iter().map(|(label, location)| {
1152 let text = did_you_mean(label, &other_labels)
1153 .unwrap_or_else(|| "Unexpected label".into());
1154 Label {
1155 text: Some(text),
1156 span: *location,
1157 }
1158 });
1159 let label = labels.next().expect("Unknown labels first label");
1160 let extra_labels = labels.collect();
1161 let text = if valid.is_empty() {
1162 "This constructor does not accept any labelled arguments.".into()
1163 } else if other_labels.is_empty() {
1164 "You have already supplied all the labelled arguments that this
1165constructor accepts."
1166 .into()
1167 } else {
1168 let mut label_text = String::from("It accepts these labels:\n");
1169 for label in other_labels.iter().sorted() {
1170 label_text.push_str("\n ");
1171 label_text.push_str(label);
1172 }
1173 label_text
1174 };
1175 Diagnostic {
1176 title,
1177 text,
1178 hint: None,
1179 level: Level::Error,
1180 location: Some(Location {
1181 label,
1182 path: path.clone(),
1183 src: src.clone(),
1184 extra_labels,
1185 }),
1186 }
1187 }
1188
1189 TypeError::UnexpectedLabelledArg { location, label } => {
1190 let text = format!(
1191 "This argument has been given a label but the constructor does
1192not expect any. Please remove the label `{label}`."
1193 );
1194 Diagnostic {
1195 title: "Unexpected labelled argument".into(),
1196 text,
1197 hint: None,
1198 level: Level::Error,
1199 location: Some(Location {
1200 label: Label {
1201 text: None,
1202 span: *location,
1203 },
1204 path: path.clone(),
1205 src: src.clone(),
1206 extra_labels: vec![],
1207 }),
1208 }
1209 }
1210
1211 TypeError::PositionalArgumentAfterLabelled { location } => {
1212 let text =
1213 "This unlabeled argument has been supplied after a labelled argument.
1214Once a labelled argument has been supplied all following arguments must
1215also be labelled."
1216 .into();
1217 Diagnostic {
1218 title: "Unexpected positional argument".into(),
1219 text,
1220 hint: None,
1221 level: Level::Error,
1222 location: Some(Location {
1223 label: Label {
1224 text: None,
1225 span: *location,
1226 },
1227 path: path.clone(),
1228 src: src.clone(),
1229 extra_labels: vec![],
1230 }),
1231 }
1232 }
1233
1234 TypeError::DuplicateImport {
1235 location,
1236 previous_location,
1237 name,
1238 } => {
1239 let text = format!(
1240 "`{name}` has been imported multiple times.
1241Names in a Gleam module must be unique so one will need to be renamed."
1242 );
1243 Diagnostic {
1244 title: "Duplicate import".into(),
1245 text,
1246 hint: None,
1247 level: Level::Error,
1248 location: Some(Location {
1249 label: Label {
1250 text: Some("Reimported here".into()),
1251 span: *location,
1252 },
1253 path: path.clone(),
1254 src: src.clone(),
1255 extra_labels: vec![Label {
1256 text: Some("First imported here".into()),
1257 span: *previous_location,
1258 }],
1259 }),
1260 }
1261 }
1262
1263 TypeError::DuplicateName {
1264 location_a,
1265 location_b,
1266 name,
1267 ..
1268 } => {
1269 let (first_location, second_location) = if location_a.start < location_b.start {
1270 (location_a, location_b)
1271 } else {
1272 (location_b, location_a)
1273 };
1274 let text = format!(
1275 "`{name}` has been defined multiple times.
1276Names in a Gleam module must be unique so one will need to be renamed."
1277 );
1278 Diagnostic {
1279 title: "Duplicate definition".into(),
1280 text,
1281 hint: None,
1282 level: Level::Error,
1283 location: Some(Location {
1284 label: Label {
1285 text: Some("Redefined here".into()),
1286 span: *second_location,
1287 },
1288 path: path.clone(),
1289 src: src.clone(),
1290 extra_labels: vec![Label {
1291 text: Some("First defined here".into()),
1292 span: *first_location,
1293 }],
1294 }),
1295 }
1296 }
1297
1298 TypeError::DuplicateTypeName {
1299 name,
1300 location,
1301 previous_location,
1302 ..
1303 } => {
1304 let text = format!(
1305 "The type `{name}` has been defined multiple times.
1306Names in a Gleam module must be unique so one will need to be renamed."
1307 );
1308 Diagnostic {
1309 title: "Duplicate type definition".into(),
1310 text,
1311 hint: None,
1312 level: Level::Error,
1313 location: Some(Location {
1314 label: Label {
1315 text: Some("Redefined here".into()),
1316 span: *location,
1317 },
1318 path: path.clone(),
1319 src: src.clone(),
1320 extra_labels: vec![Label {
1321 text: Some("First defined here".into()),
1322 span: *previous_location,
1323 }],
1324 }),
1325 }
1326 }
1327
1328 TypeError::DuplicateField { location, label } => {
1329 let text =
1330 format!("The field `{label}` has already been defined. Rename this field.");
1331 Diagnostic {
1332 title: "Duplicate field".into(),
1333 text,
1334 hint: None,
1335 level: Level::Error,
1336 location: Some(Location {
1337 label: Label {
1338 text: None,
1339 span: *location,
1340 },
1341 path: path.clone(),
1342 src: src.clone(),
1343 extra_labels: vec![],
1344 }),
1345 }
1346 }
1347
1348 TypeError::DuplicateArgument { location, label } => {
1349 let text =
1350 format!("The labelled argument `{label}` has already been supplied.");
1351 Diagnostic {
1352 title: "Duplicate argument".into(),
1353 text,
1354 hint: None,
1355 level: Level::Error,
1356 location: Some(Location {
1357 label: Label {
1358 text: None,
1359 span: *location,
1360 },
1361 path: path.clone(),
1362 src: src.clone(),
1363 extra_labels: vec![],
1364 }),
1365 }
1366 }
1367
1368 TypeError::RecursiveType { location } => {
1369 let text = "I don't know how to work out what type this value has. It seems
1370to be defined in terms of itself.
1371
1372Hint: Add some type annotations and try again."
1373 .into();
1374 Diagnostic {
1375 title: "Recursive type".into(),
1376 text,
1377 hint: None,
1378 level: Level::Error,
1379 location: Some(Location {
1380 label: Label {
1381 text: None,
1382 span: *location,
1383 },
1384 path: path.clone(),
1385 src: src.clone(),
1386 extra_labels: vec![],
1387 }),
1388 }
1389 }
1390
1391 TypeError::NotFn { location, typ } => {
1392 let mut printer = Printer::new();
1393 let text = format!(
1394 "This value is being called as a function but its type is:\n\n{}",
1395 printer.pretty_print(typ, 4)
1396 );
1397 Diagnostic {
1398 title: "Type mismatch".into(),
1399 text,
1400 hint: None,
1401 level: Level::Error,
1402 location: Some(Location {
1403 label: Label {
1404 text: None,
1405 span: *location,
1406 },
1407 path: path.clone(),
1408 src: src.clone(),
1409 extra_labels: vec![],
1410 }),
1411 }
1412 }
1413
1414 TypeError::UnknownRecordField {
1415 usage,
1416 location,
1417 typ,
1418 label,
1419 fields,
1420 variants,
1421 } => {
1422 let mut printer = Printer::new();
1423
1424 // Give a hint about what type this value has.
1425 let mut text = format!(
1426 "The value being accessed has this type:\n\n{}\n",
1427 printer.pretty_print(typ, 4)
1428 );
1429
1430 // Give a hint about what record fields this value has, if any.
1431 if fields.is_empty() {
1432 text.push_str("\nIt does not have any fields.");
1433 } else {
1434 text.push_str("\nIt has these fields:\n");
1435 }
1436 for field in fields.iter().sorted() {
1437 text.push_str("\n .");
1438 text.push_str(field);
1439 }
1440
1441 match variants {
1442 RecordVariants::HasVariants => {
1443 let msg = wrap(
1444 "Note: The field you are trying to \
1445access might not be consistently present or positioned across the custom \
1446type's variants, preventing reliable access. Ensure the field exists in the \
1447same position and has the same type in all variants to enable direct accessor syntax.",
1448 );
1449 text.push_str("\n\n");
1450 text.push_str(&msg);
1451 }
1452 RecordVariants::NoVariants => (),
1453 }
1454
1455 // Give a hint about Gleam not having OOP methods if it
1456 // looks like they might be trying to call one.
1457 match usage {
1458 FieldAccessUsage::MethodCall => {
1459 let msg = wrap(
1460 "Gleam is not object oriented, so if you are trying \
1461to call a method on this value you may want to use the function syntax instead.",
1462 );
1463 text.push_str("\n\n");
1464 text.push_str(&msg);
1465 text.push_str("\n\n ");
1466 text.push_str(label);
1467 text.push_str("(value)");
1468 }
1469 FieldAccessUsage::Other => (),
1470 }
1471
1472 let label = did_you_mean(label, fields)
1473 .unwrap_or_else(|| "This field does not exist".into());
1474 Diagnostic {
1475 title: "Unknown record field".into(),
1476 text,
1477 hint: None,
1478 level: Level::Error,
1479 location: Some(Location {
1480 label: Label {
1481 text: Some(label),
1482 span: *location,
1483 },
1484 path: path.clone(),
1485 src: src.clone(),
1486 extra_labels: vec![],
1487 }),
1488 }
1489 }
1490
1491 TypeError::CouldNotUnify {
1492 location,
1493 expected,
1494 given,
1495 situation: Some(UnifyErrorSituation::Operator(op)),
1496 rigid_type_names: annotated_names,
1497 } => {
1498 let mut printer = Printer::new();
1499 printer.with_names(annotated_names.clone());
1500 let mut text = format!(
1501 "The {op} operator expects arguments of this type:
1502
1503{expected}
1504
1505But this argument has this type:
1506
1507{given}\n",
1508 op = op.name(),
1509 expected = printer.pretty_print(expected, 4),
1510 given = printer.pretty_print(given, 4),
1511 );
1512 if let Some(hint) = hint_alternative_operator(op, given) {
1513 text.push('\n');
1514 text.push_str("Hint: ");
1515 text.push_str(&hint);
1516 }
1517 Diagnostic {
1518 title: "Type mismatch".into(),
1519 text,
1520 hint: None,
1521 level: Level::Error,
1522 location: Some(Location {
1523 label: Label {
1524 text: None,
1525 span: *location,
1526 },
1527 path: path.clone(),
1528 src: src.clone(),
1529 extra_labels: vec![],
1530 }),
1531 }
1532 }
1533
1534 TypeError::CouldNotUnify {
1535 location,
1536 expected,
1537 given,
1538 situation: Some(UnifyErrorSituation::PipeTypeMismatch),
1539 rigid_type_names: annotated_names,
1540 } => {
1541 // Remap the pipe function type into just the type expected by the pipe.
1542 let expected = expected
1543 .fn_types()
1544 .and_then(|(args, _)| args.first().cloned());
1545
1546 // Remap the argument as well, if it's a function.
1547 let given = given
1548 .fn_types()
1549 .and_then(|(args, _)| args.first().cloned())
1550 .unwrap_or_else(|| given.clone());
1551
1552 let mut printer = Printer::new();
1553 printer.with_names(annotated_names.clone());
1554 let text = format!(
1555 "The argument is:
1556
1557{given}
1558
1559But function expects:
1560
1561{expected}",
1562 expected = expected
1563 .map(|v| printer.pretty_print(&v, 4))
1564 .unwrap_or_else(|| " No arguments".into()),
1565 given = printer.pretty_print(&given, 4)
1566 );
1567
1568 Diagnostic {
1569 title: "Type mismatch".into(),
1570 text,
1571 hint: None,
1572 level: Level::Error,
1573 location: Some(Location {
1574 label: Label {
1575 text: Some("This function does not accept the piped type".into()),
1576 span: *location,
1577 },
1578 path: path.clone(),
1579 src: src.clone(),
1580 extra_labels: vec![],
1581 }),
1582 }
1583 }
1584
1585 TypeError::CouldNotUnify {
1586 location,
1587 expected,
1588 given,
1589 situation,
1590 rigid_type_names: annotated_names,
1591 } => {
1592 let mut printer = Printer::new();
1593 printer.with_names(annotated_names.clone());
1594 let mut text =
1595 if let Some(description) = situation.and_then(|s| s.description()) {
1596 let mut text = description.to_string();
1597 text.push('\n');
1598 text.push('\n');
1599 text
1600 } else {
1601 "".into()
1602 };
1603 text.push_str("Expected type:\n\n");
1604 text.push_str(&printer.pretty_print(expected, 4));
1605 text.push_str("\n\nFound type:\n\n");
1606 text.push_str(&printer.pretty_print(given, 4));
1607 Diagnostic {
1608 title: "Type mismatch".into(),
1609 text,
1610 hint: None,
1611 level: Level::Error,
1612 location: Some(Location {
1613 label: Label {
1614 text: None,
1615 span: *location,
1616 },
1617 path: path.clone(),
1618 src: src.clone(),
1619 extra_labels: vec![],
1620 }),
1621 }
1622 }
1623
1624 TypeError::IncorrectTypeArity {
1625 location,
1626 expected,
1627 given,
1628 ..
1629 } => {
1630 let text = "Functions and constructors have to be called with their expected
1631number of arguments."
1632 .into();
1633 let expected = match expected {
1634 0 => "no arguments".into(),
1635 1 => "1 argument".into(),
1636 _ => format!("{expected} arguments"),
1637 };
1638 Diagnostic {
1639 title: "Incorrect arity".into(),
1640 text,
1641 hint: None,
1642 level: Level::Error,
1643 location: Some(Location {
1644 label: Label {
1645 text: Some(format!("Expected {expected}, got {given}")),
1646 span: *location,
1647 },
1648 path: path.clone(),
1649 src: src.clone(),
1650 extra_labels: vec![],
1651 }),
1652 }
1653 }
1654
1655 TypeError::IncorrectArity {
1656 labels,
1657 location,
1658 expected,
1659 given,
1660 } => {
1661 let text = if labels.is_empty() {
1662 "".into()
1663 } else {
1664 let labels = labels
1665 .iter()
1666 .map(|p| format!(" - {p}"))
1667 .sorted()
1668 .join("\n");
1669 format!(
1670 "This call accepts these additional labelled arguments:\n\n{labels}",
1671 )
1672 };
1673 let expected = match expected {
1674 0 => "no arguments".into(),
1675 1 => "1 argument".into(),
1676 _ => format!("{expected} arguments"),
1677 };
1678 let label = format!("Expected {expected}, got {given}");
1679 Diagnostic {
1680 title: "Incorrect arity".into(),
1681 text,
1682 hint: None,
1683 level: Level::Error,
1684 location: Some(Location {
1685 label: Label {
1686 text: Some(label),
1687 span: *location,
1688 },
1689 path: path.clone(),
1690 src: src.clone(),
1691 extra_labels: vec![],
1692 }),
1693 }
1694 }
1695
1696 TypeError::UnnecessarySpreadOperator { location, arity } => {
1697 let text = wrap_format!(
1698 "This record has {arity} fields and you have already \
1699assigned variables to all of them."
1700 );
1701 Diagnostic {
1702 title: "Unnecessary spread operator".into(),
1703 text,
1704 hint: None,
1705 level: Level::Error,
1706 location: Some(Location {
1707 label: Label {
1708 text: None,
1709 span: *location,
1710 },
1711 path: path.clone(),
1712 src: src.clone(),
1713 extra_labels: vec![],
1714 }),
1715 }
1716 }
1717
1718 TypeError::UpdateMultiConstructorType { location } => {
1719 let text = "This type has multiple constructors so it cannot be safely updated.
1720If this value was one of the other variants then the update would be
1721produce incorrect results.
1722
1723Consider pattern matching on it with a case expression and then
1724constructing a new record with its values."
1725 .into();
1726
1727 Diagnostic {
1728 title: "Unsafe record update".into(),
1729 text,
1730 hint: None,
1731 level: Level::Error,
1732 location: Some(Location {
1733 label: Label {
1734 text: Some(
1735 "I can't tell this is always the right constructor".into(),
1736 ),
1737 span: *location,
1738 },
1739 path: path.clone(),
1740 src: src.clone(),
1741 extra_labels: vec![],
1742 }),
1743 }
1744 }
1745
1746 TypeError::UnknownType {
1747 location,
1748 name,
1749 hint,
1750 } => {
1751 let label_text = match hint {
1752 UnknownTypeHint::AlternativeTypes(types) => did_you_mean(name, types),
1753 UnknownTypeHint::ValueInScopeWithSameName => None,
1754 };
1755
1756 let mut text = wrap_format!(
1757 "The type `{name}` is not defined or imported in this module."
1758 );
1759
1760 match hint {
1761 UnknownTypeHint::ValueInScopeWithSameName => {
1762 let hint = wrap_format!(
1763 "There is a value in scope with the name `{name}`, but no type in scope with that name."
1764 );
1765 text.push('\n');
1766 text.push_str(hint.as_str());
1767 }
1768 UnknownTypeHint::AlternativeTypes(_) => {}
1769 };
1770
1771 Diagnostic {
1772 title: "Unknown type".into(),
1773 text,
1774 hint: None,
1775 level: Level::Error,
1776 location: Some(Location {
1777 label: Label {
1778 text: label_text,
1779 span: *location,
1780 },
1781 path: path.clone(),
1782 src: src.clone(),
1783 extra_labels: vec![],
1784 }),
1785 }
1786 }
1787
1788 TypeError::UnknownVariable {
1789 location,
1790 variables,
1791 name,
1792 type_with_name_in_scope,
1793 } => {
1794 let text = if *type_with_name_in_scope {
1795 wrap_format!("`{name}` is a type, it cannot be used as a value.")
1796 } else {
1797 wrap_format!("The name `{name}` is not in scope here.")
1798 };
1799 Diagnostic {
1800 title: "Unknown variable".into(),
1801 text,
1802 hint: None,
1803 level: Level::Error,
1804 location: Some(Location {
1805 label: Label {
1806 text: did_you_mean(name, variables),
1807 span: *location,
1808 },
1809 path: path.clone(),
1810 src: src.clone(),
1811 extra_labels: vec![],
1812 }),
1813 }
1814 }
1815
1816 TypeError::PrivateTypeLeak { location, leaked } => {
1817 let mut printer = Printer::new();
1818
1819 // TODO: be more precise.
1820 // - is being returned by this public function
1821 // - is taken as an argument by this public function
1822 // - is taken as an argument by this public enum constructor
1823 // etc
1824 let text = format!(
1825 "The following type is private, but is being used by this public export.
1826
1827{}
1828
1829Private types can only be used within the module that defines them.",
1830 printer.pretty_print(leaked, 4),
1831 );
1832 Diagnostic {
1833 title: "Private type used in public interface".into(),
1834 text,
1835 hint: None,
1836 level: Level::Error,
1837 location: Some(Location {
1838 label: Label {
1839 text: None,
1840 span: *location,
1841 },
1842 path: path.clone(),
1843 src: src.clone(),
1844 extra_labels: vec![],
1845 }),
1846 }
1847 }
1848
1849 TypeError::UnknownModule {
1850 location,
1851 name,
1852 imported_modules,
1853 } => Diagnostic {
1854 title: "Unknown module".into(),
1855 text: format!("No module has been found with the name `{name}`."),
1856 hint: None,
1857 level: Level::Error,
1858 location: Some(Location {
1859 label: Label {
1860 text: did_you_mean(name, imported_modules),
1861 span: *location,
1862 },
1863 path: path.clone(),
1864 src: src.clone(),
1865 extra_labels: vec![],
1866 }),
1867 },
1868
1869 TypeError::UnknownModuleType {
1870 location,
1871 name,
1872 module_name,
1873 type_constructors,
1874 } => {
1875 let text =
1876 format!("The module `{module_name}` does not have a `{name}` type.",);
1877 Diagnostic {
1878 title: "Unknown module type".into(),
1879 text,
1880 hint: None,
1881 level: Level::Error,
1882 location: Some(Location {
1883 label: Label {
1884 text: did_you_mean(name, type_constructors),
1885 span: *location,
1886 },
1887 path: path.clone(),
1888 src: src.clone(),
1889 extra_labels: vec![],
1890 }),
1891 }
1892 }
1893
1894 TypeError::UnknownModuleValue {
1895 location,
1896 name,
1897 module_name,
1898 value_constructors,
1899 } => {
1900 let text =
1901 format!("The module `{module_name}` does not have a `{name}` value.",);
1902 Diagnostic {
1903 title: "Unknown module field".into(),
1904 text,
1905 hint: None,
1906 level: Level::Error,
1907 location: Some(Location {
1908 label: Label {
1909 text: did_you_mean(name, value_constructors),
1910 span: *location,
1911 },
1912 path: path.clone(),
1913 src: src.clone(),
1914 extra_labels: vec![],
1915 }),
1916 }
1917 }
1918
1919 TypeError::UnknownModuleField {
1920 location,
1921 name,
1922 module_name,
1923 type_constructors,
1924 value_constructors,
1925 } => {
1926 let options: Vec<_> = type_constructors
1927 .iter()
1928 .chain(value_constructors)
1929 .cloned()
1930 .collect();
1931 let text =
1932 format!("The module `{module_name}` does not have a `{name}` field.",);
1933 Diagnostic {
1934 title: "Unknown module field".into(),
1935 text,
1936 hint: None,
1937 level: Level::Error,
1938 location: Some(Location {
1939 label: Label {
1940 text: did_you_mean(name, &options),
1941 span: *location,
1942 },
1943 path: path.clone(),
1944 src: src.clone(),
1945 extra_labels: vec![],
1946 }),
1947 }
1948 }
1949
1950 TypeError::IncorrectNumClausePatterns {
1951 location,
1952 expected,
1953 given,
1954 } => {
1955 let text = wrap_format!(
1956 "This case expression has {expected} subjects, but this pattern matches {given}.
1957Each clause must have a pattern for every subject value.",
1958 );
1959 Diagnostic {
1960 title: "Incorrect number of patterns".into(),
1961 text,
1962 hint: None,
1963 level: Level::Error,
1964 location: Some(Location {
1965 label: Label {
1966 text: Some(format!("Expected {expected} patterns, got {given}")),
1967 span: *location,
1968 },
1969 path: path.clone(),
1970 src: src.clone(),
1971 extra_labels: vec![],
1972 }),
1973 }
1974 }
1975
1976 TypeError::NonLocalClauseGuardVariable { location, name } => {
1977 let text = wrap_format!(
1978 "Variables used in guards must be either defined in the \
1979function, or be an argument to the function. The variable `{name}` is not defined locally.",
1980 );
1981 Diagnostic {
1982 title: "Invalid guard variable".into(),
1983 text,
1984 hint: None,
1985 level: Level::Error,
1986 location: Some(Location {
1987 label: Label {
1988 text: Some("Is not locally defined".into()),
1989 span: *location,
1990 },
1991 path: path.clone(),
1992 src: src.clone(),
1993 extra_labels: vec![],
1994 }),
1995 }
1996 }
1997
1998 TypeError::ExtraVarInAlternativePattern { location, name } => {
1999 let text = wrap_format!(
2000"All alternative patterns must define the same variables as the initial pattern. \
2001This variable `{name}` has not been previously defined.",
2002 );
2003 Diagnostic {
2004 title: "Extra alternative pattern variable".into(),
2005 text,
2006 hint: None,
2007 level: Level::Error,
2008 location: Some(Location {
2009 label: Label {
2010 text: Some("Has not been previously defined".into()),
2011 span: *location,
2012 },
2013 path: path.clone(),
2014 src: src.clone(),
2015 extra_labels: vec![],
2016 }),
2017 }
2018 }
2019
2020 TypeError::MissingVarInAlternativePattern { location, name } => {
2021 let text = wrap_format!(
2022 "All alternative patterns must define the same variables \
2023as the initial pattern, but the `{name}` variable is missing.",
2024 );
2025 Diagnostic {
2026 title: "Missing alternative pattern variable".into(),
2027 text,
2028 hint: None,
2029 level: Level::Error,
2030 location: Some(Location {
2031 label: Label {
2032 text: Some("This does not define all required variables".into()),
2033 span: *location,
2034 },
2035 path: path.clone(),
2036 src: src.clone(),
2037 extra_labels: vec![],
2038 }),
2039 }
2040 }
2041
2042 TypeError::DuplicateVarInPattern { location, name } => {
2043 let text = wrap_format!(
2044 "Variables can only be used once per pattern. This \
2045variable `{name}` appears multiple times.
2046If you used the same variable twice deliberately in order to check for equality \
2047please use a guard clause instead.
2048e.g. (x, y) if x == y -> ...",
2049 );
2050 Diagnostic {
2051 title: "Duplicate variable in pattern".into(),
2052 text,
2053 hint: None,
2054 level: Level::Error,
2055 location: Some(Location {
2056 label: Label {
2057 text: Some("This has already been used".into()),
2058 span: *location,
2059 },
2060 path: path.clone(),
2061 src: src.clone(),
2062 extra_labels: vec![],
2063 }),
2064 }
2065 }
2066
2067 TypeError::OutOfBoundsTupleIndex {
2068 location, size: 0, ..
2069 } => Diagnostic {
2070 title: "Out of bounds tuple index".into(),
2071 text: "This tuple has no elements so it cannot be indexed at all.".into(),
2072 hint: None,
2073 level: Level::Error,
2074 location: Some(Location {
2075 label: Label {
2076 text: None,
2077 span: *location,
2078 },
2079 path: path.clone(),
2080 src: src.clone(),
2081 extra_labels: vec![],
2082 }),
2083 },
2084
2085 TypeError::OutOfBoundsTupleIndex {
2086 location,
2087 index,
2088 size,
2089 } => {
2090 let text = wrap_format!(
2091 "The index being accessed for this tuple is {}, but this \
2092tuple has {} elements so the highest valid index is {}.",
2093 index,
2094 size,
2095 size - 1,
2096 );
2097 Diagnostic {
2098 title: "Out of bounds tuple index".into(),
2099 text,
2100 hint: None,
2101 level: Level::Error,
2102 location: Some(Location {
2103 label: Label {
2104 text: Some("This index is too large".into()),
2105 span: *location,
2106 },
2107 path: path.clone(),
2108 src: src.clone(),
2109 extra_labels: vec![],
2110 }),
2111 }
2112 }
2113
2114 TypeError::NotATuple { location, given } => {
2115 let mut printer = Printer::new();
2116 let text = format!(
2117 "To index into this value it needs to be a tuple, however it has this type:
2118
2119{}",
2120 printer.pretty_print(given, 4),
2121 );
2122 Diagnostic {
2123 title: "Type mismatch".into(),
2124 text,
2125 hint: None,
2126 level: Level::Error,
2127 location: Some(Location {
2128 label: Label {
2129 text: Some("This is not a tuple".into()),
2130 span: *location,
2131 },
2132 path: path.clone(),
2133 src: src.clone(),
2134 extra_labels: vec![],
2135 }),
2136 }
2137 }
2138
2139 TypeError::NotATupleUnbound { location } => {
2140 let text = "To index into a tuple we need to know it size, but we don't know
2141anything about this type yet. Please add some type annotations so
2142we can continue."
2143 .into();
2144 Diagnostic {
2145 title: "Type mismatch".into(),
2146 text,
2147 hint: None,
2148 level: Level::Error,
2149 location: Some(Location {
2150 label: Label {
2151 text: Some("What type is this?".into()),
2152 span: *location,
2153 },
2154 path: path.clone(),
2155 src: src.clone(),
2156 extra_labels: vec![],
2157 }),
2158 }
2159 }
2160
2161 TypeError::RecordAccessUnknownType { location } => {
2162 let text = "In order to access a record field we need to know what type it is,
2163but I can't tell the type here. Try adding type annotations to your
2164function and try again."
2165 .into();
2166 Diagnostic {
2167 title: "Unknown type for record access".into(),
2168 text,
2169 hint: None,
2170 level: Level::Error,
2171 location: Some(Location {
2172 label: Label {
2173 text: Some("I don't know what type this is".into()),
2174 span: *location,
2175 },
2176 path: path.clone(),
2177 src: src.clone(),
2178 extra_labels: vec![],
2179 }),
2180 }
2181 }
2182
2183 TypeError::BitArraySegmentError { error, location } => {
2184 let (label, mut extra) = match error {
2185 bit_array::ErrorType::ConflictingTypeOptions { existing_type } => (
2186 "This is an extra type specifier",
2187 vec![format!("Hint: This segment already has the type {existing_type}.")],
2188 ),
2189
2190 bit_array::ErrorType::ConflictingSignednessOptions {
2191 existing_signed
2192 } => (
2193 "This is an extra signedness specifier",
2194 vec![format!(
2195 "Hint: This segment already has a signedness of {existing_signed}."
2196 )],
2197 ),
2198
2199 bit_array::ErrorType::ConflictingEndiannessOptions {
2200 existing_endianness
2201 } => (
2202 "This is an extra endianness specifier",
2203 vec![format!(
2204 "Hint: This segment already has an endianness of {existing_endianness}."
2205 )],
2206 ),
2207
2208 bit_array::ErrorType::ConflictingSizeOptions => (
2209 "This is an extra size specifier",
2210 vec!["Hint: This segment already has a size.".into()],
2211 ),
2212
2213 bit_array::ErrorType::ConflictingUnitOptions => (
2214 "This is an extra unit specifier",
2215 vec!["Hint: A BitArray segment can have at most 1 unit.".into()],
2216 ),
2217
2218 bit_array::ErrorType::FloatWithSize => (
2219 "Invalid float size",
2220 vec!["Hint: floats have an exact size of 16/32/64 bits.".into()],
2221 ),
2222
2223 bit_array::ErrorType::InvalidEndianness => (
2224 "This option is invalid here",
2225 vec![wrap("Hint: signed and unsigned can only be used with \
2226int, float, utf16 and utf32 types.")],
2227 ),
2228
2229 bit_array::ErrorType::OptionNotAllowedInValue => (
2230 "This option is only allowed in BitArray patterns",
2231 vec!["Hint: This option has no effect in BitArray values.".into()],
2232 ),
2233
2234 bit_array::ErrorType::SignednessUsedOnNonInt { typ } => (
2235 "Signedness is only valid with int types",
2236 vec![format!("Hint: This segment has a type of {typ}")],
2237 ),
2238 bit_array::ErrorType::TypeDoesNotAllowSize { typ } => (
2239 "Size cannot be specified here",
2240 vec![format!("Hint: {typ} segments have an automatic size.")],
2241 ),
2242 bit_array::ErrorType::TypeDoesNotAllowUnit { typ } => (
2243 "Unit cannot be specified here",
2244 vec![wrap(&format!("Hint: {typ} segments are sized based on their value \
2245and cannot have a unit."))],
2246 ),
2247 bit_array::ErrorType::VariableUtfSegmentInPattern => (
2248 "This cannot be a variable",
2249 vec![wrap("Hint: in patterns utf8, utf16, and utf32 must be an exact string.")],
2250 ),
2251 bit_array::ErrorType::SegmentMustHaveSize => (
2252 "This segment has no size",
2253 vec![wrap("Hint: Bit array segments without a size are only \
2254allowed at the end of a bin pattern.")],
2255 ),
2256 bit_array::ErrorType::UnitMustHaveSize => (
2257 "This needs an explicit size",
2258 vec!["Hint: If you specify unit() you must also specify size().".into()],
2259 ),
2260 };
2261 extra.push("See: https://tour.gleam.run/data-types/bit-arrays/".into());
2262 let text = extra.join("\n");
2263 Diagnostic {
2264 title: "Invalid bit array segment".into(),
2265 text,
2266 hint: None,
2267 level: Level::Error,
2268 location: Some(Location {
2269 label: Label {
2270 text: Some(label.into()),
2271 span: *location,
2272 },
2273 path: path.clone(),
2274 src: src.clone(),
2275 extra_labels: vec![],
2276 }),
2277 }
2278 }
2279
2280 TypeError::RecordUpdateInvalidConstructor { location } => Diagnostic {
2281 title: "Invalid record constructor".into(),
2282 text: "Only record constructors can be used with the update syntax.".into(),
2283 hint: None,
2284 level: Level::Error,
2285 location: Some(Location {
2286 label: Label {
2287 text: Some("This is not a record constructor".into()),
2288 span: *location,
2289 },
2290 path: path.clone(),
2291 src: src.clone(),
2292 extra_labels: vec![],
2293 }),
2294 },
2295
2296 TypeError::UnexpectedTypeHole { location } => Diagnostic {
2297 title: "Unexpected type hole".into(),
2298 text: "We need to know the exact type here so type holes cannot be used."
2299 .into(),
2300 hint: None,
2301 level: Level::Error,
2302 location: Some(Location {
2303 label: Label {
2304 text: Some("I need to know what this is".into()),
2305 span: *location,
2306 },
2307 path: path.clone(),
2308 src: src.clone(),
2309 extra_labels: vec![],
2310 }),
2311 },
2312
2313 TypeError::ReservedModuleName { name } => {
2314 let text = format!(
2315 "The module name `{name}` is reserved.
2316Try a different name for this module."
2317 );
2318 Diagnostic {
2319 title: "Reserved module name".into(),
2320 text,
2321 hint: None,
2322 location: None,
2323 level: Level::Error,
2324 }
2325 }
2326
2327 TypeError::KeywordInModuleName { name, keyword } => {
2328 let text = wrap(&format!(
2329 "The module name `{name}` contains the keyword `{keyword}`, so importing \
2330it would be a syntax error.
2331Try a different name for this module."
2332 ));
2333 Diagnostic {
2334 title: "Invalid module name".into(),
2335 text,
2336 hint: None,
2337 location: None,
2338 level: Level::Error,
2339 }
2340 }
2341
2342 TypeError::NotExhaustivePatternMatch {
2343 location,
2344 unmatched,
2345 kind,
2346 } => {
2347 let mut text = match kind {
2348 PatternMatchKind::Case => {
2349 "This case expression does not match all possibilities.
2350Each constructor must have a pattern that matches it or
2351else it could crash."
2352 }
2353 PatternMatchKind::Assignment => {
2354 "This assignment does not match all possibilities.
2355Either use a case expression with patterns for each possible
2356value, or use `let assert` rather than `let`."
2357 }
2358 }
2359 .to_string();
2360
2361 text.push_str("\n\nThese values are not matched:\n\n");
2362 for unmatched in unmatched {
2363 text.push_str(" - ");
2364 text.push_str(unmatched);
2365 text.push('\n');
2366 }
2367 Diagnostic {
2368 title: "Not exhaustive pattern match".into(),
2369 text,
2370 hint: None,
2371 level: Level::Error,
2372 location: Some(Location {
2373 label: Label {
2374 text: None,
2375 span: *location,
2376 },
2377 path: path.clone(),
2378 src: src.clone(),
2379 extra_labels: vec![],
2380 }),
2381 }
2382 }
2383
2384 TypeError::ArgumentNameAlreadyUsed { location, name } => Diagnostic {
2385 title: "Argument name already used".into(),
2386 text: format!("Two `{name}` arguments have been defined for this function."),
2387 hint: None,
2388 level: Level::Error,
2389 location: Some(Location {
2390 label: Label {
2391 text: None,
2392 span: *location,
2393 },
2394 path: path.clone(),
2395 src: src.clone(),
2396 extra_labels: vec![],
2397 }),
2398 },
2399
2400 TypeError::UnlabelledAfterlabelled { location } => Diagnostic {
2401 title: "Unlabelled argument after labelled argument".into(),
2402 text: wrap("All unlabelled arguments must come before any labelled arguments."),
2403 hint: None,
2404 level: Level::Error,
2405 location: Some(Location {
2406 label: Label {
2407 text: None,
2408 span: *location,
2409 },
2410 path: path.clone(),
2411 src: src.clone(),
2412 extra_labels: vec![],
2413 }),
2414 },
2415
2416 TypeError::RecursiveTypeAlias { location, cycle } => {
2417 let mut text = "This type alias is defined in terms of itself.\n".into();
2418 write_cycle(&mut text, cycle);
2419 text.push_str(
2420 "If we tried to compile this recursive type it would expand
2421forever in a loop, and we'd never get the final type.",
2422 );
2423 Diagnostic {
2424 title: "Type cycle".into(),
2425 text,
2426 hint: None,
2427 level: Level::Error,
2428 location: Some(Location {
2429 label: Label {
2430 text: None,
2431 span: *location,
2432 },
2433 path: path.clone(),
2434 src: src.clone(),
2435 extra_labels: vec![],
2436 }),
2437 }
2438 }
2439
2440 TypeError::ExternalMissingAnnotation { location, kind } => {
2441 let kind = match kind {
2442 MissingAnnotation::Parameter => "parameter",
2443 MissingAnnotation::Return => "return",
2444 };
2445 let text = format!(
2446 "A {kind} annotation is missing from this function.
2447
2448Functions with external implementations must have type annotations
2449so we can tell what type of values they accept and return.",
2450 );
2451 Diagnostic {
2452 title: "Missing type annotation".into(),
2453 text,
2454 hint: None,
2455 level: Level::Error,
2456 location: Some(Location {
2457 label: Label {
2458 text: None,
2459 span: *location,
2460 },
2461 path: path.clone(),
2462 src: src.clone(),
2463 extra_labels: vec![],
2464 }),
2465 }
2466 }
2467
2468 TypeError::NoImplementation { location } => {
2469 let text = "We can't compile this function as it doesn't have an
2470implementation. Add a body or an external implementation
2471using the `@external` attribute."
2472 .into();
2473 Diagnostic {
2474 title: "Function without an implementation".into(),
2475 text,
2476 hint: None,
2477 level: Level::Error,
2478 location: Some(Location {
2479 label: Label {
2480 text: None,
2481 span: *location,
2482 },
2483 path: path.clone(),
2484 src: src.clone(),
2485 extra_labels: vec![],
2486 }),
2487 }
2488 }
2489
2490 TypeError::InvalidExternalJavascriptModule {
2491 location,
2492 name,
2493 module,
2494 } => {
2495 let text = wrap_format!(
2496 "The function `{name}` has an external JavaScript \
2497implementation but the module path `{module}` is not valid."
2498 );
2499 Diagnostic {
2500 title: "Invalid JavaScript module".into(),
2501 text,
2502 hint: None,
2503 level: Level::Error,
2504 location: Some(Location {
2505 label: Label {
2506 text: None,
2507 span: *location,
2508 },
2509 path: path.clone(),
2510 src: src.clone(),
2511 extra_labels: vec![],
2512 }),
2513 }
2514 }
2515
2516 TypeError::InvalidExternalJavascriptFunction {
2517 location,
2518 name,
2519 function,
2520 } => {
2521 let text = wrap_format!(
2522 "The function `{name}` has an external JavaScript \
2523implementation but the function name `{function}` is not valid."
2524 );
2525 Diagnostic {
2526 title: "Invalid JavaScript function".into(),
2527 text,
2528 hint: None,
2529 level: Level::Error,
2530 location: Some(Location {
2531 label: Label {
2532 text: None,
2533 span: *location,
2534 },
2535 path: path.clone(),
2536 src: src.clone(),
2537 extra_labels: vec![],
2538 }),
2539 }
2540 }
2541
2542 TypeError::InexhaustiveLetAssignment { location, missing } => {
2543 let mut text: String =
2544 "This assignment uses a pattern that does not match all possible
2545values. If one of the other values is used then the assignment
2546will crash.
2547
2548The missing patterns are:\n"
2549 .into();
2550 for missing in missing {
2551 text.push_str("\n ");
2552 text.push_str(missing);
2553 }
2554 text.push('\n');
2555
2556 Diagnostic {
2557 title: "Inexhaustive pattern".into(),
2558 text,
2559 hint: Some(
2560 "Use a more general pattern or use `let assert` instead.".into(),
2561 ),
2562 level: Level::Error,
2563 location: Some(Location {
2564 src: src.clone(),
2565 path: path.to_path_buf(),
2566 label: Label {
2567 text: None,
2568 span: *location,
2569 },
2570 extra_labels: Vec::new(),
2571 }),
2572 }
2573 }
2574
2575 TypeError::EmptyCaseExpression { location } => {
2576 let text: String = "This case expression has no clauses.".into();
2577
2578 Diagnostic {
2579 title: "Empty case expression".into(),
2580 text,
2581 hint: None,
2582 level: Level::Error,
2583 location: Some(Location {
2584 src: src.clone(),
2585 path: path.to_path_buf(),
2586 label: Label {
2587 text: None,
2588 span: *location,
2589 },
2590 extra_labels: Vec::new(),
2591 }),
2592 }
2593 }
2594
2595 TypeError::InexhaustiveCaseExpression { location, missing } => {
2596 let mut text: String =
2597 "This case expression does not have a pattern for all possible values.
2598If it is run on one of the values without a pattern then it will crash.
2599
2600The missing patterns are:\n"
2601 .into();
2602 for missing in missing {
2603 text.push_str("\n ");
2604 text.push_str(missing);
2605 }
2606 Diagnostic {
2607 title: "Inexhaustive patterns".into(),
2608 text,
2609 hint: None,
2610 level: Level::Error,
2611 location: Some(Location {
2612 src: src.clone(),
2613 path: path.to_path_buf(),
2614 label: Label {
2615 text: None,
2616 span: *location,
2617 },
2618 extra_labels: Vec::new(),
2619 }),
2620 }
2621 }
2622
2623 TypeError::UnsupportedExpressionTarget {
2624 location,
2625 target: current_target,
2626 } => {
2627 let text = wrap_format!(
2628 "This value is not available as it is defined using externals, \
2629and there is no implementation for the {} target.\n",
2630 match current_target {
2631 Target::Erlang => "Erlang",
2632 Target::JavaScript => "JavaScript",
2633 }
2634 );
2635 let hint = wrap("Did you mean to build for a different target?");
2636 Diagnostic {
2637 title: "Unsupported target".into(),
2638 text,
2639 hint: Some(hint),
2640 level: Level::Error,
2641 location: Some(Location {
2642 path: path.clone(),
2643 src: src.clone(),
2644 label: Label {
2645 text: None,
2646 span: *location,
2647 },
2648 extra_labels: vec![],
2649 }),
2650 }
2651 }
2652
2653 TypeError::UnsupportedPublicFunctionTarget {
2654 location,
2655 name,
2656 target,
2657 } => {
2658 let target = match target {
2659 Target::Erlang => "Erlang",
2660 Target::JavaScript => "JavaScript",
2661 };
2662 let text = wrap_format!(
2663 "The `{name}` function is public but doesn't have an \
2664implementation for the {target} target. All public functions of a package \
2665must be able to compile for a module to be valid."
2666 );
2667 Diagnostic {
2668 title: "Unsupported target".into(),
2669 text,
2670 hint: None,
2671 level: Level::Error,
2672 location: Some(Location {
2673 path: path.clone(),
2674 src: src.clone(),
2675 label: Label {
2676 text: None,
2677 span: *location,
2678 },
2679 extra_labels: vec![],
2680 }),
2681 }
2682 }
2683
2684 TypeError::UnusedTypeAliasParameter { location, name } => {
2685 let text = wrap_format!(
2686 "The type variable `{name}` is unused. It can be safely removed.",
2687 );
2688 Diagnostic {
2689 title: "Unused type parameter".into(),
2690 text,
2691 hint: None,
2692 level: Level::Error,
2693 location: Some(Location {
2694 path: path.clone(),
2695 src: src.clone(),
2696 label: Label {
2697 text: None,
2698 span: *location,
2699 },
2700 extra_labels: vec![],
2701 }),
2702 }
2703 }
2704
2705 TypeError::DuplicateTypeParameter { location, name } => {
2706 let text = wrap_format!(
2707 "This definition has multiple type parameters named `{name}`.
2708Rename or remove one of them.",
2709 );
2710 Diagnostic {
2711 title: "Duplicate type parameter".into(),
2712 text,
2713 hint: None,
2714 level: Level::Error,
2715 location: Some(Location {
2716 path: path.clone(),
2717 src: src.clone(),
2718 label: Label {
2719 text: None,
2720 span: *location,
2721 },
2722 extra_labels: vec![],
2723 }),
2724 }
2725 }
2726 },
2727
2728 Error::Parse { path, src, error } => {
2729 let (label, extra) = error.details();
2730 let text = extra.join("\n");
2731
2732 let adjusted_location = if error.error == ParseErrorType::UnexpectedEof {
2733 crate::ast::SrcSpan {
2734 start: (src.len() - 1) as u32,
2735 end: (src.len() - 1) as u32,
2736 }
2737 } else {
2738 error.location
2739 };
2740
2741 Diagnostic {
2742 title: "Syntax error".into(),
2743 text,
2744 hint: None,
2745 level: Level::Error,
2746 location: Some(Location {
2747 label: Label {
2748 text: Some(label.to_string()),
2749 span: adjusted_location,
2750 },
2751 path: path.clone(),
2752 src: src.clone(),
2753 extra_labels: vec![],
2754 }),
2755 }
2756 }
2757
2758 Error::ImportCycle { modules } => {
2759 let mut text = "The import statements for these modules form a cycle:
2760"
2761 .into();
2762 write_cycle(&mut text, modules);
2763 text.push_str(
2764 "Gleam doesn't support dependency cycles like these, please break the
2765cycle to continue.",
2766 );
2767 Diagnostic {
2768 title: "Import cycle".into(),
2769 text,
2770 hint: None,
2771 level: Level::Error,
2772 location: None,
2773 }
2774 }
2775
2776 Error::PackageCycle { packages } => {
2777 let mut text = "The dependencies for these packages form a cycle:
2778"
2779 .into();
2780 write_cycle(&mut text, packages);
2781 text.push_str(
2782 "Gleam doesn't support dependency cycles like these, please break the
2783cycle to continue.",
2784 );
2785 Diagnostic {
2786 title: "Dependency cycle".into(),
2787 text,
2788 hint: None,
2789 level: Level::Error,
2790 location: None,
2791 }
2792 }
2793
2794 Error::UnknownImport { import, details } => {
2795 let UnknownImportDetails {
2796 module,
2797 location,
2798 path,
2799 src,
2800 modules,
2801 } = details.as_ref();
2802 let text = wrap(&format!(
2803 "The module `{module}` is trying to import the module `{import}`, \
2804but it cannot be found."
2805 ));
2806 Diagnostic {
2807 title: "Unknown import".into(),
2808 text,
2809 hint: None,
2810 level: Level::Error,
2811 location: Some(Location {
2812 label: Label {
2813 text: did_you_mean(import, modules),
2814 span: *location,
2815 },
2816 path: path.clone(),
2817 src: src.clone(),
2818 extra_labels: vec![],
2819 }),
2820 }
2821 }
2822
2823 Error::StandardIo { action, err } => {
2824 let err = match err {
2825 Some(e) => format!(
2826 "\nThe error message from the stdio library was:\n\n {}\n",
2827 std_io_error_kind_text(e)
2828 ),
2829 None => "".into(),
2830 };
2831 Diagnostic {
2832 title: "Standard IO failure".into(),
2833 text: format!(
2834 "An error occurred while trying to {}:
2835
2836{}",
2837 action.text(),
2838 err,
2839 ),
2840 hint: None,
2841 location: None,
2842 level: Level::Error,
2843 }
2844 }
2845
2846 Error::Format { problem_files } => {
2847 let files: Vec<_> = problem_files
2848 .iter()
2849 .map(|formatted| formatted.source.as_str())
2850 .map(|p| format!(" - {p}"))
2851 .sorted()
2852 .collect();
2853 let mut text = files.iter().join("\n");
2854 text.push('\n');
2855 Diagnostic {
2856 title: "These files have not been formatted".into(),
2857 text,
2858 hint: None,
2859 location: None,
2860 level: Level::Error,
2861 }
2862 }
2863
2864 Error::ForbiddenWarnings { count } => {
2865 let word_warning = match count {
2866 1 => "warning",
2867 _ => "warnings",
2868 };
2869 let text = "Your project was compiled with the `--warnings-as-errors` flag.
2870Fix the warnings and try again."
2871 .into();
2872 Diagnostic {
2873 title: format!("{count} {word_warning} generated."),
2874 text,
2875 hint: None,
2876 location: None,
2877 level: Level::Error,
2878 }
2879 }
2880
2881 Error::JavaScript { src, path, error } => match error {
2882 javascript::Error::Unsupported { feature, location } => Diagnostic {
2883 title: "Unsupported feature for compilation target".into(),
2884 text: format!("{feature} is not supported for JavaScript compilation."),
2885 hint: None,
2886 level: Level::Error,
2887 location: Some(Location {
2888 label: Label {
2889 text: None,
2890 span: *location,
2891 },
2892 path: path.clone(),
2893 src: src.clone(),
2894 extra_labels: vec![],
2895 }),
2896 },
2897 },
2898
2899 Error::DownloadPackageError {
2900 package_name,
2901 package_version,
2902 error,
2903 } => {
2904 let text = format!(
2905 "A problem was encountered when downloading `{package_name}` {package_version}.
2906The error from the package manager client was:
2907
2908 {error}"
2909 );
2910 Diagnostic {
2911 title: "Failed to download package".into(),
2912 text,
2913 hint: None,
2914 location: None,
2915 level: Level::Error,
2916 }
2917 }
2918
2919 Error::Http(error) => {
2920 let text = format!(
2921 "A HTTP request failed.
2922The error from the HTTP client was:
2923
2924 {error}"
2925 );
2926 Diagnostic {
2927 title: "HTTP error".into(),
2928 text,
2929 hint: None,
2930 location: None,
2931 level: Level::Error,
2932 }
2933 }
2934
2935 Error::InvalidVersionFormat { input, error } => {
2936 let text = format!(
2937 "I was unable to parse the version \"{input}\".
2938The error from the parser was:
2939
2940 {error}"
2941 );
2942 Diagnostic {
2943 title: "Invalid version format".into(),
2944 text,
2945 hint: None,
2946 location: None,
2947 level: Level::Error,
2948 }
2949 }
2950
2951 Error::DependencyCanonicalizationFailed(package) => {
2952 let text = format!("Local package `{package}` has no canonical path");
2953
2954 Diagnostic {
2955 title: "Failed to create canonical path".into(),
2956 text,
2957 hint: None,
2958 location: None,
2959 level: Level::Error,
2960 }
2961 }
2962
2963 Error::DependencyResolutionFailed(error) => {
2964 let text = format!(
2965 "An error occurred while determining what dependency packages and
2966versions should be downloaded.
2967The error from the version resolver library was:
2968
2969{}",
2970 wrap(error)
2971 );
2972 Diagnostic {
2973 title: "Dependency resolution failed".into(),
2974 text,
2975 hint: None,
2976 location: None,
2977 level: Level::Error,
2978 }
2979 }
2980
2981 Error::GitDependencyUnsupported => Diagnostic {
2982 title: "Git dependencies are not currently supported".into(),
2983 text: "Please remove all git dependencies from the gleam.toml file".into(),
2984 hint: None,
2985 location: None,
2986 level: Level::Error,
2987 },
2988
2989 Error::WrongDependencyProvided {
2990 path,
2991 expected,
2992 found,
2993 } => {
2994 let text = format!(
2995 "Expected package `{expected}` at path `{path}` but found `{found}` instead.",
2996 );
2997
2998 Diagnostic {
2999 title: "Wrong dependency provided".into(),
3000 text,
3001 hint: None,
3002 location: None,
3003 level: Level::Error,
3004 }
3005 }
3006
3007 Error::ProvidedDependencyConflict {
3008 package,
3009 source_1,
3010 source_2,
3011 } => {
3012 let text = format!(
3013 "The package `{package}` is provided as both `{source_1}` and `{source_2}`.",
3014 );
3015
3016 Diagnostic {
3017 title: "Conflicting provided dependencies".into(),
3018 text,
3019 hint: None,
3020 location: None,
3021 level: Level::Error,
3022 }
3023 }
3024
3025 Error::DuplicateDependency(name) => {
3026 let text = format!(
3027 "The package `{name}` is specified in both the dependencies and
3028dev-dependencies sections of the gleam.toml file."
3029 );
3030 Diagnostic {
3031 title: "Dependency duplicated".into(),
3032 text,
3033 hint: None,
3034 location: None,
3035 level: Level::Error,
3036 }
3037 }
3038
3039 Error::MissingHexPublishFields {
3040 description_missing,
3041 licence_missing,
3042 } => {
3043 let mut text =
3044 "Licence information and package description are required to publish a
3045package to Hex.\n"
3046 .to_string();
3047 text.push_str(if *description_missing && *licence_missing {
3048 r#"Add the licences and description fields to your gleam.toml file.
3049
3050description = ""
3051licences = ["Apache-2.0"]"#
3052 } else if *description_missing {
3053 r#"Add the description field to your gleam.toml file.
3054
3055description = """#
3056 } else {
3057 r#"Add the licences field to your gleam.toml file.
3058
3059licences = ["Apache-2.0"]"#
3060 });
3061 Diagnostic {
3062 title: "Missing required package fields".into(),
3063 text,
3064 hint: None,
3065 location: None,
3066 level: Level::Error,
3067 }
3068 }
3069
3070 Error::PublishNonHexDependencies { package } => Diagnostic {
3071 title: "Unblished dependencies".into(),
3072 text: wrap_format!(
3073 "The package cannot be published to Hex \
3074because dependency `{package}` is not a Hex dependency.",
3075 ),
3076 hint: None,
3077 location: None,
3078 level: Level::Error,
3079 },
3080
3081 Error::UnsupportedBuildTool {
3082 package,
3083 build_tools,
3084 } => {
3085 let text = wrap_format!(
3086 "The package `{}` cannot be built as it does not use \
3087a build tool supported by Gleam. It uses {:?}.
3088
3089If you would like us to support this package please let us know by opening an \
3090issue in our tracker: https://github.com/gleam-lang/gleam/issues",
3091 package,
3092 build_tools
3093 );
3094 Diagnostic {
3095 title: "Unsupported build tool".into(),
3096 text,
3097 hint: None,
3098 location: None,
3099 level: Level::Error,
3100 }
3101 }
3102
3103 Error::FailedToOpenDocs { path, error } => {
3104 let error = format!("\nThe error message from the library was:\n\n {error}\n");
3105 let text = format!(
3106 "An error occurred while trying to open the docs:
3107
3108 {path}
3109{error}",
3110 );
3111 Diagnostic {
3112 title: "Failed to open docs".into(),
3113 text,
3114 hint: None,
3115 level: Level::Error,
3116 location: None,
3117 }
3118 }
3119
3120 Error::IncompatibleCompilerVersion {
3121 package,
3122 required_version,
3123 gleam_version,
3124 } => {
3125 let text = format!(
3126 "The package `{package}` requires a Gleam version satisfying {required_version} \
3127but you are using v{gleam_version}.",
3128 );
3129 Diagnostic {
3130 title: "Incompatible Gleam version".into(),
3131 text,
3132 hint: None,
3133 location: None,
3134 level: Level::Error,
3135 }
3136 }
3137
3138 Error::InvalidRuntime {
3139 target,
3140 invalid_runtime,
3141 } => {
3142 let text = format!("Invalid runtime for {target} target: {invalid_runtime}");
3143
3144 let hint = match target {
3145 Target::JavaScript => {
3146 Some("available runtimes for JavaScript are: node, deno.".into())
3147 }
3148 Target::Erlang => Some(
3149 "You can not set a runtime for Erlang. Did you mean to target JavaScript?"
3150 .into(),
3151 ),
3152 };
3153
3154 Diagnostic {
3155 title: format!("Invalid runtime for {target}"),
3156 text,
3157 hint,
3158 location: None,
3159 level: Level::Error,
3160 }
3161 }
3162
3163 Error::JavaScriptPreludeRequired => Diagnostic {
3164 title: "JavaScript prelude required".into(),
3165 text: "The --javascript-prelude flag must be given when compiling to JavaScript."
3166 .into(),
3167 level: Level::Error,
3168 location: None,
3169 hint: None,
3170 },
3171 }
3172 }
3173}
3174
3175fn std_io_error_kind_text(kind: &std::io::ErrorKind) -> String {
3176 use std::io::ErrorKind;
3177 match kind {
3178 ErrorKind::NotFound => "Could not find the stdio stream".into(),
3179 ErrorKind::PermissionDenied => "Permission was denied".into(),
3180 ErrorKind::ConnectionRefused => "Connection was refused".into(),
3181 ErrorKind::ConnectionReset => "Connection was reset".into(),
3182 ErrorKind::ConnectionAborted => "Connection was aborted".into(),
3183 ErrorKind::NotConnected => "Was not connected".into(),
3184 ErrorKind::AddrInUse => "The stream was already in use".into(),
3185 ErrorKind::AddrNotAvailable => "The stream was not available".into(),
3186 ErrorKind::BrokenPipe => "The pipe was broken".into(),
3187 ErrorKind::AlreadyExists => "A handle to the stream already exists".into(),
3188 ErrorKind::WouldBlock => "This operation would block when it was requested not to".into(),
3189 ErrorKind::InvalidInput => "Some parameter was invalid".into(),
3190 ErrorKind::InvalidData => "The data was invalid. Check that the encoding is UTF-8".into(),
3191 ErrorKind::TimedOut => "The operation timed out".into(),
3192 ErrorKind::WriteZero => {
3193 "An attempt was made to write, but all bytes could not be written".into()
3194 }
3195 ErrorKind::Interrupted => "The operation was interrupted".into(),
3196 ErrorKind::UnexpectedEof => "The end of file was reached before it was expected".into(),
3197 _ => "An unknown error occurred".into(),
3198 }
3199}
3200
3201fn write_cycle(buffer: &mut String, cycle: &[EcoString]) {
3202 buffer.push_str(
3203 "
3204 ┌─────┐\n",
3205 );
3206 for (index, name) in cycle.iter().enumerate() {
3207 if index != 0 {
3208 buffer.push_str(" │ ↓\n");
3209 }
3210 buffer.push_str(" │ ");
3211 buffer.push_str(name);
3212 buffer.push('\n');
3213 }
3214 buffer.push_str(" └─────┘\n");
3215}
3216
3217fn hint_alternative_operator(op: &BinOp, given: &Type) -> Option<String> {
3218 match op {
3219 BinOp::AddInt if given.is_float() => Some(hint_numeric_message("+.", "Float")),
3220 BinOp::DivInt if given.is_float() => Some(hint_numeric_message("/.", "Float")),
3221 BinOp::GtEqInt if given.is_float() => Some(hint_numeric_message(">=.", "Float")),
3222 BinOp::GtInt if given.is_float() => Some(hint_numeric_message(">.", "Float")),
3223 BinOp::LtEqInt if given.is_float() => Some(hint_numeric_message("<=.", "Float")),
3224 BinOp::LtInt if given.is_float() => Some(hint_numeric_message("<.", "Float")),
3225 BinOp::MultInt if given.is_float() => Some(hint_numeric_message("*.", "Float")),
3226 BinOp::SubInt if given.is_float() => Some(hint_numeric_message("-.", "Float")),
3227
3228 BinOp::AddFloat if given.is_int() => Some(hint_numeric_message("+", "Int")),
3229 BinOp::DivFloat if given.is_int() => Some(hint_numeric_message("/", "Int")),
3230 BinOp::GtEqFloat if given.is_int() => Some(hint_numeric_message(">=", "Int")),
3231 BinOp::GtFloat if given.is_int() => Some(hint_numeric_message(">", "Int")),
3232 BinOp::LtEqFloat if given.is_int() => Some(hint_numeric_message("<=", "Int")),
3233 BinOp::LtFloat if given.is_int() => Some(hint_numeric_message("<", "Int")),
3234 BinOp::MultFloat if given.is_int() => Some(hint_numeric_message("*", "Int")),
3235 BinOp::SubFloat if given.is_int() => Some(hint_numeric_message("-", "Int")),
3236
3237 BinOp::AddInt if given.is_string() => Some(hint_string_message()),
3238 BinOp::AddFloat if given.is_string() => Some(hint_string_message()),
3239
3240 _ => None,
3241 }
3242}
3243
3244fn hint_numeric_message(alt: &str, type_: &str) -> String {
3245 format!("the {alt} operator can be used with {type_}s\n")
3246}
3247
3248fn hint_string_message() -> String {
3249 wrap(
3250 "Strings can be joined using the `append` or `concat` \
3251functions from the `gleam/string` module.",
3252 )
3253}
3254
3255#[derive(Debug, Clone, PartialEq, Eq)]
3256pub struct Unformatted {
3257 pub source: Utf8PathBuf,
3258 pub destination: Utf8PathBuf,
3259 pub input: EcoString,
3260 pub output: String,
3261}
3262
3263pub fn wrap(text: &str) -> String {
3264 textwrap::fill(text, std::cmp::min(75, textwrap::termwidth()))
3265}