Fork of daniellemaywood.uk/gleam — Wasm codegen work
156 kB
4060 lines
1#![allow(clippy::unwrap_used, clippy::expect_used)]
2use crate::build::{Outcome, Runtime, Target};
3use crate::diagnostic::{Diagnostic, ExtraLabel, Label, Location};
4use crate::type_::error::{
5 MissingAnnotation, ModuleValueUsageContext, Named, UnknownField, UnknownTypeHint,
6 UnsafeRecordUpdateReason,
7};
8use crate::type_::printer::{Names, Printer};
9use crate::type_::{error::PatternMatchKind, FieldAccessUsage};
10use crate::{ast::BinOp, parse::error::ParseErrorType, type_::Type};
11use crate::{bit_array, diagnostic::Level, javascript, type_::UnifyErrorSituation};
12use ecow::EcoString;
13use heck::{ToSnakeCase, ToTitleCase, ToUpperCamelCase};
14use hexpm::version::ResolutionError;
15use itertools::Itertools;
16use pubgrub::package::Package;
17use pubgrub::report::DerivationTree;
18use pubgrub::version::Version;
19use std::borrow::Cow;
20use std::collections::HashSet;
21use std::fmt::{Debug, Display};
22use std::io::Write;
23use std::path::PathBuf;
24use termcolor::Buffer;
25use thiserror::Error;
26use vec1::Vec1;
27
28use camino::{Utf8Path, Utf8PathBuf};
29
30pub type Name = EcoString;
31
32pub type Result<Ok, Err = Error> = std::result::Result<Ok, Err>;
33
34#[cfg(test)]
35pub mod tests;
36
37macro_rules! wrap_format {
38 ($($tts:tt)*) => {
39 wrap(&format!($($tts)*))
40 }
41}
42
43#[derive(Debug, Clone, Eq, PartialEq)]
44pub struct UnknownImportDetails {
45 pub module: Name,
46 pub location: crate::ast::SrcSpan,
47 pub path: Utf8PathBuf,
48 pub src: EcoString,
49 pub modules: Vec<EcoString>,
50}
51
52#[derive(Debug, Clone, Eq, PartialEq)]
53pub struct ImportCycleLocationDetails {
54 pub location: crate::ast::SrcSpan,
55 pub path: Utf8PathBuf,
56 pub src: EcoString,
57}
58
59#[derive(Debug, Eq, PartialEq, Error, Clone)]
60pub enum Error {
61 #[error("failed to parse Gleam source code")]
62 Parse {
63 path: Utf8PathBuf,
64 src: EcoString,
65 error: crate::parse::error::ParseError,
66 },
67
68 #[error("type checking failed")]
69 Type {
70 path: Utf8PathBuf,
71 src: EcoString,
72 errors: Vec1<crate::type_::Error>,
73 names: Names,
74 },
75
76 #[error("unknown import {import}")]
77 UnknownImport {
78 import: EcoString,
79 // Boxed to prevent this variant from being overly large
80 details: Box<UnknownImportDetails>,
81 },
82
83 #[error("duplicate module {module}")]
84 DuplicateModule {
85 module: Name,
86 first: Utf8PathBuf,
87 second: Utf8PathBuf,
88 },
89
90 #[error("duplicate source file {file}")]
91 DuplicateSourceFile { file: String },
92
93 #[error("duplicate native Erlang module {module}")]
94 DuplicateNativeErlangModule {
95 module: Name,
96 first: Utf8PathBuf,
97 second: Utf8PathBuf,
98 },
99
100 #[error("gleam module {module} clashes with native file of same name")]
101 ClashingGleamModuleAndNativeFileName {
102 module: Name,
103 gleam_file: Utf8PathBuf,
104 native_file: Utf8PathBuf,
105 },
106
107 #[error("cyclical module imports")]
108 ImportCycle {
109 modules: Vec1<(EcoString, ImportCycleLocationDetails)>,
110 },
111
112 #[error("cyclical package dependencies")]
113 PackageCycle { packages: Vec<EcoString> },
114
115 #[error("file operation failed")]
116 FileIo {
117 kind: FileKind,
118 action: FileIoAction,
119 path: Utf8PathBuf,
120 err: Option<String>,
121 },
122
123 #[error("Non Utf-8 Path: {path}")]
124 NonUtf8Path { path: PathBuf },
125
126 #[error("{error}")]
127 GitInitialization { error: String },
128
129 #[error("io operation failed")]
130 StandardIo {
131 action: StandardIoAction,
132 err: Option<std::io::ErrorKind>,
133 },
134
135 #[error("source code incorrectly formatted")]
136 Format { problem_files: Vec<Unformatted> },
137
138 #[error("Hex error: {0}")]
139 Hex(String),
140
141 #[error("{error}")]
142 ExpandTar { error: String },
143
144 #[error("{err}")]
145 AddTar { path: Utf8PathBuf, err: String },
146
147 #[error("{0}")]
148 TarFinish(String),
149
150 #[error("{0}")]
151 Gzip(String),
152
153 #[error("shell program `{program}` not found")]
154 ShellProgramNotFound { program: String, os: OS },
155
156 #[error("shell program `{program}` failed")]
157 ShellCommand {
158 program: String,
159 err: Option<std::io::ErrorKind>,
160 },
161
162 #[error("{name} is not a valid project name")]
163 InvalidProjectName {
164 name: String,
165 reason: InvalidProjectNameReason,
166 },
167
168 #[error("{module} is not a valid module name")]
169 InvalidModuleName { module: String },
170
171 #[error("{module} is not module")]
172 ModuleDoesNotExist {
173 module: EcoString,
174 suggestion: Option<EcoString>,
175 },
176
177 #[error("{module} does not have a main function")]
178 ModuleDoesNotHaveMainFunction { module: EcoString },
179
180 #[error("{module}'s main function has the wrong arity so it can not be run")]
181 MainFunctionHasWrongArity { module: EcoString, arity: usize },
182
183 #[error("{module}'s main function does not support the current target")]
184 MainFunctionDoesNotSupportTarget { module: EcoString, target: Target },
185
186 #[error("{input} is not a valid version. {error}")]
187 InvalidVersionFormat { input: String, error: String },
188
189 #[error("project root already exists")]
190 ProjectRootAlreadyExist { path: String },
191
192 #[error("File(s) already exist in {}",
193file_names.iter().map(|x| x.as_str()).join(", "))]
194 OutputFilesAlreadyExist { file_names: Vec<Utf8PathBuf> },
195
196 #[error("Packages not exist: {}", packages.iter().join(", "))]
197 RemovedPackagesNotExist { packages: Vec<String> },
198
199 #[error("unable to find project root")]
200 UnableToFindProjectRoot { path: String },
201
202 #[error("gleam.toml version {toml_ver} does not match .app version {app_ver}")]
203 VersionDoesNotMatch { toml_ver: String, app_ver: String },
204
205 #[error("metadata decoding failed")]
206 MetadataDecodeError { error: Option<String> },
207
208 #[error("warnings are not permitted")]
209 ForbiddenWarnings { count: usize },
210
211 #[error("javascript codegen failed")]
212 JavaScript {
213 path: Utf8PathBuf,
214 src: EcoString,
215 error: javascript::Error,
216 },
217
218 #[error("Invalid runtime for {target} target: {invalid_runtime}")]
219 InvalidRuntime {
220 target: Target,
221 invalid_runtime: Runtime,
222 },
223
224 #[error("package downloading failed: {error}")]
225 DownloadPackageError {
226 package_name: String,
227 package_version: String,
228 error: String,
229 },
230
231 #[error("{0}")]
232 Http(String),
233
234 #[error("Git dependencies are currently unsupported")]
235 GitDependencyUnsupported,
236
237 #[error("Failed to create canonical path for package {0}")]
238 DependencyCanonicalizationFailed(String),
239
240 #[error("Dependency tree resolution failed: {0}")]
241 DependencyResolutionFailed(String),
242
243 #[error("The package {0} is listed in dependencies and dev-dependencies")]
244 DuplicateDependency(EcoString),
245
246 #[error("Expected package {expected} at path {path} but found {found} instead")]
247 WrongDependencyProvided {
248 path: Utf8PathBuf,
249 expected: String,
250 found: String,
251 },
252
253 #[error("The package {package} is provided multiple times, as {source_1} and {source_2}")]
254 ProvidedDependencyConflict {
255 package: String,
256 source_1: String,
257 source_2: String,
258 },
259
260 #[error("The package was missing required fields for publishing")]
261 MissingHexPublishFields {
262 description_missing: bool,
263 licence_missing: bool,
264 },
265
266 #[error("Dependency {package:?} has not been published to Hex")]
267 PublishNonHexDependencies { package: String },
268
269 #[error("The package {package} uses unsupported build tools {build_tools:?}")]
270 UnsupportedBuildTool {
271 package: String,
272 build_tools: Vec<EcoString>,
273 },
274
275 #[error("Opening docs at {path} failed: {error}")]
276 FailedToOpenDocs { path: Utf8PathBuf, error: String },
277
278 #[error(
279 "The package {package} requires a Gleam version satisfying \
280{required_version} and you are using v{gleam_version}"
281 )]
282 IncompatibleCompilerVersion {
283 package: String,
284 required_version: String,
285 gleam_version: String,
286 },
287
288 #[error("The --javascript-prelude flag must be given when compiling to JavaScript")]
289 JavaScriptPreludeRequired,
290
291 #[error("The modules {unfinished:?} contain todo expressions and so cannot be published")]
292 CannotPublishTodo { unfinished: Vec<EcoString> },
293
294 #[error("The modules {unfinished:?} contain internal types in their public API so cannot be published")]
295 CannotPublishLeakedInternalType { unfinished: Vec<EcoString> },
296
297 #[error("Publishing packages to reserve names is not permitted")]
298 HexPackageSquatting,
299
300 #[error("Corrupt manifest.toml")]
301 CorruptManifest,
302
303 #[error("The Gleam module {path} would overwrite the Erlang module {name}")]
304 GleamModuleWouldOverwriteStandardErlangModule { name: EcoString, path: Utf8PathBuf },
305
306 #[error("Version already published")]
307 HexPublishReplaceRequired { version: String },
308
309 #[error("The gleam version constraint is wrong and so cannot be published")]
310 CannotPublishWrongVersion {
311 minimum_required_version: SmallVersion,
312 wrongfully_allowed_version: SmallVersion,
313 },
314
315 #[error("Failed to encrypt data")]
316 FailedToEncrypt { detail: String },
317
318 #[error("Failed to decrypt data")]
319 FailedToDecrypt { detail: String },
320}
321
322/// This is to make clippy happy and not make the error variant too big by
323/// storing an entire `hexpm::version::Version` in the error.
324///
325/// This is enough to report wrong Gleam compiler versions.
326///
327#[derive(Debug, PartialEq, Eq, Clone, Copy)]
328pub struct SmallVersion {
329 major: u8,
330 minor: u8,
331 patch: u8,
332}
333
334impl Display for SmallVersion {
335 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
336 f.write_str(&format!("{}.{}.{}", self.major, self.minor, self.patch))
337 }
338}
339
340impl SmallVersion {
341 pub fn from_hexpm(version: hexpm::version::Version) -> Self {
342 Self {
343 major: version.major as u8,
344 minor: version.minor as u8,
345 patch: version.patch as u8,
346 }
347 }
348}
349#[derive(Debug, Clone, Eq, PartialEq, Copy)]
350pub enum OS {
351 Linux(Distro),
352 MacOS,
353 Windows,
354 Other,
355}
356
357#[derive(Debug, Clone, Eq, PartialEq, Copy)]
358pub enum Distro {
359 Ubuntu,
360 Debian,
361 Other,
362}
363
364pub fn parse_os(os: &str, distro: &str) -> OS {
365 match os {
366 "macos" => OS::MacOS,
367 "windows" => OS::Windows,
368 "linux" => OS::Linux(parse_linux_distribution(distro)),
369 _ => OS::Other,
370 }
371}
372
373pub fn parse_linux_distribution(distro: &str) -> Distro {
374 match distro {
375 "ubuntu" => Distro::Ubuntu,
376 "debian" => Distro::Debian,
377 _ => Distro::Other,
378 }
379}
380
381impl Error {
382 pub fn http<E>(error: E) -> Error
383 where
384 E: std::error::Error,
385 {
386 Self::Http(error.to_string())
387 }
388
389 pub fn hex<E>(error: E) -> Error
390 where
391 E: std::error::Error,
392 {
393 Self::Hex(error.to_string())
394 }
395
396 pub fn add_tar<P, E>(path: P, error: E) -> Error
397 where
398 P: AsRef<Utf8Path>,
399 E: std::error::Error,
400 {
401 Self::AddTar {
402 path: path.as_ref().to_path_buf(),
403 err: error.to_string(),
404 }
405 }
406
407 pub fn finish_tar<E>(error: E) -> Error
408 where
409 E: std::error::Error,
410 {
411 Self::TarFinish(error.to_string())
412 }
413
414 pub fn dependency_resolution_failed(error: ResolutionError) -> Error {
415 fn collect_conflicting_packages<'dt, P: Package, V: Version>(
416 derivation_tree: &'dt DerivationTree<P, V>,
417 conflicting_packages: &mut HashSet<&'dt P>,
418 ) {
419 match derivation_tree {
420 DerivationTree::External(external) => match external {
421 pubgrub::report::External::NotRoot(package, _) => {
422 let _ = conflicting_packages.insert(package);
423 }
424 pubgrub::report::External::NoVersions(package, _) => {
425 let _ = conflicting_packages.insert(package);
426 }
427 pubgrub::report::External::UnavailableDependencies(package, _) => {
428 let _ = conflicting_packages.insert(package);
429 }
430 pubgrub::report::External::FromDependencyOf(package, _, dep_package, _) => {
431 let _ = conflicting_packages.insert(package);
432 let _ = conflicting_packages.insert(dep_package);
433 }
434 },
435 DerivationTree::Derived(derived) => {
436 collect_conflicting_packages(&derived.cause1, conflicting_packages);
437 collect_conflicting_packages(&derived.cause2, conflicting_packages);
438 }
439 }
440 }
441
442 Self::DependencyResolutionFailed(match error {
443 ResolutionError::NoSolution(mut derivation_tree) => {
444 derivation_tree.collapse_no_versions();
445
446 let mut conflicting_packages = HashSet::new();
447 collect_conflicting_packages(&derivation_tree, &mut conflicting_packages);
448
449 wrap_format!("Unable to find compatible versions for \
450the version constraints in your gleam.toml. \
451The conflicting packages are:
452
453{}
454",
455 conflicting_packages.into_iter().map(|s| format!("- {s}")).join("\n"))
456 }
457
458 ResolutionError::ErrorRetrievingDependencies {
459 package,
460 version,
461 source,
462 } => format!(
463 "An error occurred while trying to retrieve dependencies of {package}@{version}: {source}",
464 ),
465
466 ResolutionError::DependencyOnTheEmptySet {
467 package,
468 version,
469 dependent,
470 } => format!(
471 "{package}@{version} has an impossible dependency on {dependent}",
472 ),
473
474 ResolutionError::SelfDependency { package, version } => {
475 format!("{package}@{version} somehow depends on itself.")
476 }
477
478 ResolutionError::ErrorChoosingPackageVersion(err) => {
479 format!("Unable to determine package versions: {err}")
480 }
481
482 ResolutionError::ErrorInShouldCancel(err) => {
483 format!("Dependency resolution was cancelled. {err}")
484 }
485
486 ResolutionError::Failure(err) => format!(
487 "An unrecoverable error happened while solving dependencies: {err}"
488 ),
489 })
490 }
491
492 pub fn expand_tar<E>(error: E) -> Error
493 where
494 E: std::error::Error,
495 {
496 Self::ExpandTar {
497 error: error.to_string(),
498 }
499 }
500}
501
502impl<T> From<Error> for Outcome<T, Error> {
503 fn from(error: Error) -> Self {
504 Outcome::TotalFailure(error)
505 }
506}
507
508impl From<capnp::Error> for Error {
509 fn from(error: capnp::Error) -> Self {
510 Error::MetadataDecodeError {
511 error: Some(error.to_string()),
512 }
513 }
514}
515
516impl From<capnp::NotInSchema> for Error {
517 fn from(error: capnp::NotInSchema) -> Self {
518 Error::MetadataDecodeError {
519 error: Some(error.to_string()),
520 }
521 }
522}
523
524#[derive(Debug, PartialEq, Eq, Clone, Copy)]
525pub enum InvalidProjectNameReason {
526 Format,
527 GleamPrefix,
528 ErlangReservedWord,
529 ErlangStandardLibraryModule,
530 GleamReservedWord,
531 GleamReservedModule,
532}
533
534#[derive(Debug, PartialEq, Eq, Clone, Copy)]
535pub enum StandardIoAction {
536 Read,
537 Write,
538}
539
540impl StandardIoAction {
541 fn text(&self) -> &'static str {
542 match self {
543 StandardIoAction::Read => "read from",
544 StandardIoAction::Write => "write to",
545 }
546 }
547}
548
549#[derive(Debug, PartialEq, Eq, Clone, Copy)]
550pub enum FileIoAction {
551 Link,
552 Open,
553 Copy,
554 Read,
555 Parse,
556 Delete,
557 // Rename,
558 Create,
559 WriteTo,
560 Canonicalise,
561 UpdatePermissions,
562 FindParent,
563 ReadMetadata,
564}
565
566impl FileIoAction {
567 fn text(&self) -> &'static str {
568 match self {
569 FileIoAction::Link => "link",
570 FileIoAction::Open => "open",
571 FileIoAction::Copy => "copy",
572 FileIoAction::Read => "read",
573 FileIoAction::Parse => "parse",
574 FileIoAction::Delete => "delete",
575 // FileIoAction::Rename => "rename",
576 FileIoAction::Create => "create",
577 FileIoAction::WriteTo => "write to",
578 FileIoAction::FindParent => "find the parent of",
579 FileIoAction::Canonicalise => "canonicalise",
580 FileIoAction::UpdatePermissions => "update permissions of",
581 FileIoAction::ReadMetadata => "read metadata of",
582 }
583 }
584}
585
586#[derive(Debug, Clone, Copy, PartialEq, Eq)]
587pub enum FileKind {
588 File,
589 Directory,
590}
591
592impl FileKind {
593 fn text(&self) -> &'static str {
594 match self {
595 FileKind::File => "file",
596 FileKind::Directory => "directory",
597 }
598 }
599}
600
601// https://github.com/rust-lang/rust/blob/03994e498df79aa1f97f7bbcfd52d57c8e865049/compiler/rustc_span/src/edit_distance.rs
602pub fn edit_distance(a: &str, b: &str, limit: usize) -> Option<usize> {
603 let mut a = &a.chars().collect::<Vec<_>>()[..];
604 let mut b = &b.chars().collect::<Vec<_>>()[..];
605
606 if a.len() < b.len() {
607 std::mem::swap(&mut a, &mut b);
608 }
609
610 let min_dist = a.len() - b.len();
611 // If we know the limit will be exceeded, we can return early.
612 if min_dist > limit {
613 return None;
614 }
615
616 // Strip common prefix.
617 while !b.is_empty() && !a.is_empty() {
618 let (b_first, b_rest) = b.split_last().expect("Failed to split 'b' slice");
619 let (a_first, a_rest) = a.split_last().expect("Failed to split 'a' slice");
620
621 if b_first == a_first {
622 a = a_rest;
623 b = b_rest;
624 } else {
625 break;
626 }
627 }
628
629 // If either string is empty, the distance is the length of the other.
630 // We know that `b` is the shorter string, so we don't need to check `a`.
631 if b.is_empty() {
632 return Some(min_dist);
633 }
634
635 let mut prev_prev = vec![usize::MAX; b.len() + 1];
636 let mut prev = (0..=b.len()).collect::<Vec<_>>();
637 let mut current = vec![0; b.len() + 1];
638
639 // row by row
640 for i in 1..=a.len() {
641 if let Some(elem) = current.get_mut(0) {
642 *elem = i;
643 }
644 let a_idx = i - 1;
645
646 // column by column
647 for j in 1..=b.len() {
648 let b_idx = j - 1;
649
650 // There is no cost to substitute a character with itself.
651 let substitution_cost = match (a.get(a_idx), b.get(b_idx)) {
652 (Some(&a_char), Some(&b_char)) => {
653 if a_char == b_char {
654 0
655 } else {
656 1
657 }
658 }
659 _ => panic!("Index out of bounds"),
660 };
661
662 let insertion = current.get(j - 1).map_or(usize::MAX, |&x| x + 1);
663
664 if let Some(value) = current.get_mut(j) {
665 *value = std::cmp::min(
666 // deletion
667 prev.get(j).map_or(usize::MAX, |&x| x + 1),
668 std::cmp::min(
669 // insertion
670 insertion,
671 // substitution
672 prev.get(j - 1)
673 .map_or(usize::MAX, |&x| x + substitution_cost),
674 ),
675 );
676 }
677
678 if (i > 1) && (j > 1) {
679 if let (Some(&a_val), Some(&b_val_prev), Some(&a_val_prev), Some(&b_val)) = (
680 a.get(a_idx),
681 b.get(b_idx - 1),
682 a.get(a_idx - 1),
683 b.get(b_idx),
684 ) {
685 if (a_val == b_val_prev) && (a_val_prev == b_val) {
686 // transposition
687 if let Some(curr) = current.get_mut(j) {
688 if let Some(&prev_prev_val) = prev_prev.get(j - 2) {
689 *curr = std::cmp::min(*curr, prev_prev_val + 1);
690 }
691 }
692 }
693 }
694 }
695 }
696
697 // Rotate the buffers, reusing the memory.
698 [prev_prev, prev, current] = [prev, current, prev_prev];
699 }
700
701 // `prev` because we already rotated the buffers.
702 let distance = match prev.get(b.len()) {
703 Some(&d) => d,
704 None => usize::MAX,
705 };
706 (distance <= limit).then_some(distance)
707}
708
709fn edit_distance_with_substrings(a: &str, b: &str, limit: usize) -> Option<usize> {
710 let n = a.chars().count();
711 let m = b.chars().count();
712
713 // Check one isn't less than half the length of the other. If this is true then there is a
714 // big difference in length.
715 let big_len_diff = (n * 2) < m || (m * 2) < n;
716 let len_diff = if n < m { m - n } else { n - m };
717 let distance = edit_distance(a, b, limit + len_diff)?;
718
719 // This is the crux, subtracting length difference means exact substring matches will now be 0
720 let score = distance - len_diff;
721
722 // If the score is 0 but the words have different lengths then it's a substring match not a full
723 // word match
724 let score = if score == 0 && len_diff > 0 && !big_len_diff {
725 1 // Exact substring match, but not a total word match so return non-zero
726 } else if !big_len_diff {
727 // Not a big difference in length, discount cost of length difference
728 score + (len_diff + 1) / 2
729 } else {
730 // A big difference in length, add back the difference in length to the score
731 score + len_diff
732 };
733
734 (score <= limit).then_some(score)
735}
736
737fn did_you_mean(name: &str, options: &[EcoString]) -> Option<String> {
738 // If only one option is given, return that option.
739 // This seems to solve the `unknown_variable_3` test.
740 if options.len() == 1 {
741 return options
742 .first()
743 .map(|option| format!("Did you mean `{option}`?"));
744 }
745
746 // Check for case-insensitive matches.
747 // This solves the comparison to small and single character terms,
748 // such as the test on `type_vars_must_be_declared`.
749 if let Some(exact_match) = options
750 .iter()
751 .find(|&option| option.eq_ignore_ascii_case(name))
752 {
753 return Some(format!("Did you mean `{exact_match}`?"));
754 }
755
756 // Calculate the threshold as one third of the name's length, with a minimum of 1.
757 let threshold = std::cmp::max(name.chars().count() / 3, 1);
758
759 // Filter and sort options based on edit distance.
760 options
761 .iter()
762 .filter(|&option| option != crate::ast::CAPTURE_VARIABLE)
763 .sorted()
764 .filter_map(|option| {
765 edit_distance_with_substrings(option, name, threshold)
766 .map(|distance| (option, distance))
767 })
768 .min_by_key(|&(_, distance)| distance)
769 .map(|(option, _)| format!("Did you mean `{option}`?"))
770}
771
772impl Error {
773 pub fn pretty_string(&self) -> String {
774 let mut nocolor = Buffer::no_color();
775 self.pretty(&mut nocolor);
776 String::from_utf8(nocolor.into_inner()).expect("Error printing produced invalid utf8")
777 }
778
779 pub fn pretty(&self, buffer: &mut Buffer) {
780 for diagnostic in self.to_diagnostics() {
781 diagnostic.write(buffer);
782 writeln!(buffer).expect("write new line after diagnostic");
783 }
784 }
785
786 pub fn to_diagnostics(&self) -> Vec<Diagnostic> {
787 use crate::type_::Error as TypeError;
788 match self {
789 Error::HexPackageSquatting => {
790 let text =
791 "You appear to be attempting to reserve a name on Hex rather than publishing a
792working package. This is against the Hex terms of service and can result in
793package deletion or account suspension.
794"
795 .into();
796
797 vec![Diagnostic {
798 title: "Invalid Hex package".into(),
799 text,
800 level: Level::Error,
801 location: None,
802 hint: None,
803 }]
804 }
805
806 Error::MetadataDecodeError { error } => {
807 let mut text = "A problem was encountered when decoding the metadata for one \
808of the Gleam dependency modules."
809 .to_string();
810 if let Some(error) = error {
811 text.push_str("\nThe error from the decoder library was:\n\n");
812 text.push_str(error);
813 }
814
815 vec![Diagnostic {
816 title: "Failed to decode module metadata".into(),
817 text,
818 level: Level::Error,
819 location: None,
820 hint: None,
821 }]
822 }
823
824 Error::InvalidProjectName { name, reason } => {
825 let text = wrap_format!(
826 "We were not able to create your project as `{}` {}
827
828Please try again with a different project name.",
829 name,
830 match reason {
831 InvalidProjectNameReason::ErlangReservedWord =>
832 "is a reserved word in Erlang.",
833 InvalidProjectNameReason::ErlangStandardLibraryModule =>
834 "is a standard library module in Erlang.",
835 InvalidProjectNameReason::GleamReservedWord =>
836 "is a reserved word in Gleam.",
837 InvalidProjectNameReason::GleamReservedModule =>
838 "is a reserved module name in Gleam.",
839 InvalidProjectNameReason::Format =>
840 "does not have the correct format. Project names \
841must start with a lowercase letter and may only contain lowercase letters, \
842numbers and underscores.",
843 InvalidProjectNameReason::GleamPrefix =>
844 "has the reserved prefix `gleam_`. \
845This prefix is intended for official Gleam packages only.",
846 }
847 );
848
849 vec![Diagnostic {
850 title: "Invalid project name".into(),
851 text,
852 hint: None,
853 level: Level::Error,
854 location: None,
855 }]
856 }
857
858 Error::InvalidModuleName { module } => vec![Diagnostic {
859 title: "Invalid module name".into(),
860 text: format!(
861 "`{module}` is not a valid module name.
862Module names can only contain lowercase letters, underscore, and
863forward slash and must not end with a slash."
864 ),
865 level: Level::Error,
866 location: None,
867 hint: None,
868 }],
869
870 Error::ModuleDoesNotExist { module, suggestion } => {
871 let hint = match suggestion {
872 Some(suggestion) => format!("Did you mean `{suggestion}`?"),
873 None => format!("Try creating the file `src/{module}.gleam`."),
874 };
875 vec![Diagnostic {
876 title: "Module does not exist".into(),
877 text: format!("Module `{module}` was not found."),
878 level: Level::Error,
879 location: None,
880 hint: Some(hint),
881 }]
882 }
883
884 Error::ModuleDoesNotHaveMainFunction { module } => vec![Diagnostic {
885 title: "Module does not have a main function".into(),
886 text: format!(
887 "`{module}` does not have a main function so the module can not be run."
888 ),
889 level: Level::Error,
890 location: None,
891 hint: Some(format!(
892 "Add a public `main` function to \
893to `src/{module}.gleam`."
894 )),
895 }],
896
897 Error::MainFunctionDoesNotSupportTarget { module, target } => vec![Diagnostic {
898 title: "Target not supported".into(),
899 text: wrap_format!(
900 "`{module}` has a main function, but it does not support the {target} \
901target, so it cannot be run."
902 ),
903 level: Level::Error,
904 location: None,
905 hint: None,
906 }],
907
908 Error::MainFunctionHasWrongArity { module, arity } => vec![Diagnostic {
909 title: "Main function has wrong arity".into(),
910 text: format!(
911 "`{module}:main` should have an arity of 0 to be run but its arity is {arity}."
912 ),
913 level: Level::Error,
914 location: None,
915 hint: Some("Change the function signature of main to `pub fn main() {}`.".into()),
916 }],
917
918 Error::ProjectRootAlreadyExist { path } => vec![Diagnostic {
919 title: "Project folder already exists".into(),
920 text: format!("Project folder root:\n\n {path}"),
921 level: Level::Error,
922 hint: None,
923 location: None,
924 }],
925
926 Error::OutputFilesAlreadyExist { file_names } => vec![Diagnostic {
927 title: format!(
928 "{} already exist{} in target directory",
929 if file_names.len() == 1 {
930 "File"
931 } else {
932 "Files"
933 },
934 if file_names.len() == 1 { "" } else { "s" }
935 ),
936 text: format!(
937 "{}
938If you want to overwrite these files, delete them and run the command again.
939",
940 file_names
941 .iter()
942 .map(|name| format!(" - {}", name.as_str()))
943 .join("\n")
944 ),
945 level: Level::Error,
946 hint: None,
947 location: None,
948 }],
949
950 Error::RemovedPackagesNotExist { packages } => vec![
951 Diagnostic {
952 title: "Package not found".into(),
953 text: format!(
954"These packages are not dependencies of your package so they could not
955be removed.
956
957{}
958",
959 packages
960 .iter()
961 .map(|p| format!(" - {}", p.as_str()))
962 .join("\n")
963 ),
964 level: Level::Error,
965 hint: None,
966 location: None,
967 }
968 ],
969
970 Error::CannotPublishTodo { unfinished } => vec![Diagnostic {
971 title: "Cannot publish unfinished code".into(),
972 text: format!(
973 "These modules contain todo expressions and cannot be published:
974
975{}
976
977Please remove them and try again.
978",
979 unfinished
980 .iter()
981 .map(|name| format!(" - {}", name.as_str()))
982 .join("\n")
983 ),
984 level: Level::Error,
985 hint: None,
986 location: None,
987 }],
988
989 Error::CannotPublishWrongVersion { minimum_required_version, wrongfully_allowed_version } => vec![Diagnostic {
990 title: "Cannot publish package with wrong Gleam version range".into(),
991 text: wrap(&format!(
992 "Your package uses features that require at least v{minimum_required_version}.
993But the Gleam version range specified in your `gleam.toml` would allow this \
994code to run on an earlier version like v{wrongfully_allowed_version}, \
995resulting in compilation errors!"
996 )),
997 level: Level::Error,
998 hint: Some(format!(
999 "Remove the version constraint from your `gleam.toml` or update it to be:
1000
1001 gleam = \">= {minimum_required_version}\""
1002 )),
1003 location: None,
1004 }],
1005
1006 Error::CannotPublishLeakedInternalType { unfinished } => vec![Diagnostic {
1007 title: "Cannot publish unfinished code".into(),
1008 text: format!(
1009 "These modules leak internal types in their public API and cannot be published:
1010
1011{}
1012
1013Please make sure internal types do not appear in public functions and try again.
1014",
1015 unfinished
1016 .iter()
1017 .map(|name| format!(" - {}", name.as_str()))
1018 .join("\n")
1019 ),
1020 level: Level::Error,
1021 hint: None,
1022 location: None,
1023 }],
1024
1025 Error::UnableToFindProjectRoot { path } => {
1026 let text = wrap_format!(
1027 "We were unable to find gleam.toml.
1028
1029We searched in {path} and all parent directories."
1030 );
1031 vec![Diagnostic {
1032 title: "Project not found".into(),
1033 text,
1034 hint: None,
1035 level: Level::Error,
1036 location: None,
1037 }]
1038 }
1039
1040 Error::VersionDoesNotMatch { toml_ver, app_ver } => {
1041 let text = format!(
1042 "The version in gleam.toml \"{toml_ver}\" does not match the version in
1043your app.src file \"{app_ver}\"."
1044 );
1045 vec![Diagnostic {
1046 title: "Version does not match".into(),
1047 hint: None,
1048 text,
1049 level: Level::Error,
1050 location: None,
1051 }]
1052 }
1053
1054 Error::ShellProgramNotFound { program , os } => {
1055 let mut text = format!("The program `{program}` was not found. Is it installed?");
1056
1057 match os {
1058 OS::MacOS => {
1059 fn brew_install(name: &str, pkg: &str) -> String {
1060 format!("\n\nYou can install {} via homebrew: brew install {}", name, pkg)
1061 }
1062 match program.as_str() {
1063 "erl" | "erlc" | "escript" => text.push_str(&brew_install("Erlang", "erlang")),
1064 "rebar3" => text.push_str(&brew_install("Rebar3", "rebar3")),
1065 "deno" => text.push_str(&brew_install("Deno", "deno")),
1066 "elixir" => text.push_str(&brew_install("Elixir", "elixir")),
1067 "node" => text.push_str(&brew_install("Node.js", "node")),
1068 "bun" => text.push_str(&brew_install("Bun", "oven-sh/bun/bun")),
1069 "git" => text.push_str(&brew_install("Git", "git")),
1070 _ => (),
1071 }
1072 }
1073 OS::Linux(distro) => {
1074 fn apt_install(name: &str, pkg: &str) -> String {
1075 format!("\n\nYou can install {} via apt: sudo apt install {}", name, pkg)
1076 }
1077 match distro {
1078 Distro::Ubuntu | Distro::Debian => {
1079 match program.as_str() {
1080 "elixir" => text.push_str(&apt_install("Elixir", "elixir")),
1081 "git" => text.push_str(&apt_install("Git", "git")),
1082 _ => (),
1083 }
1084 }
1085 Distro::Other => (),
1086 }
1087 }
1088 _ => (),
1089 }
1090
1091 text.push('\n');
1092
1093 match program.as_str() {
1094 "erl" | "erlc" | "escript" => text.push_str(
1095 "
1096Documentation for installing Erlang can be viewed here:
1097https://gleam.run/getting-started/installing/",
1098 ),
1099 "rebar3" => text.push_str(
1100 "
1101Documentation for installing rebar3 can be viewed here:
1102https://rebar3.org/docs/getting-started/",
1103 ),
1104 "deno" => text.push_str(
1105 "
1106Documentation for installing Deno can be viewed here:
1107https://docs.deno.com/runtime/getting_started/installation/",
1108 ),
1109 "elixir" => text.push_str(
1110 "
1111Documentation for installing Elixir can be viewed here:
1112https://elixir-lang.org/install.html",
1113 ),
1114 "node" => text.push_str(
1115 "
1116Documentation for installing Node.js via package manager can be viewed here:
1117https://nodejs.org/en/download/package-manager/all/",
1118 ),
1119 "bun" => text.push_str(
1120 "
1121Documentation for installing bun can be viewed here:
1122https://bun.sh/docs/installation/",
1123 ),
1124 "git" => text.push_str(
1125 "
1126Documentation for installing Git can be viewed here:
1127https://git-scm.com/book/en/v2/Getting-Started-Installing-Git",
1128 ),
1129 _ => (),
1130 }
1131
1132 vec![Diagnostic {
1133 title: "Program not found".into(),
1134 text,
1135 hint: None,
1136 level: Level::Error,
1137 location: None,
1138 }]
1139 }
1140
1141 Error::ShellCommand {
1142 program: command,
1143 err: None,
1144 } => {
1145 let text =
1146 format!("There was a problem when running the shell command `{command}`.");
1147 vec![Diagnostic {
1148 title: "Shell command failure".into(),
1149 text,
1150 hint: None,
1151 level: Level::Error,
1152 location: None,
1153 }]
1154 }
1155
1156 Error::ShellCommand {
1157 program: command,
1158 err: Some(err),
1159 } => {
1160 let text = format!(
1161 "There was a problem when running the shell command `{}`.
1162
1163The error from the shell command library was:
1164
1165 {}",
1166 command,
1167 std_io_error_kind_text(err)
1168 );
1169 vec![Diagnostic {
1170 title: "Shell command failure".into(),
1171 text,
1172 hint: None,
1173 level: Level::Error,
1174 location: None,
1175 }]
1176 }
1177
1178 Error::Gzip(detail) => {
1179 let text = format!(
1180 "There was a problem when applying gzip compression.
1181
1182This was error from the gzip library:
1183
1184 {detail}"
1185 );
1186 vec![Diagnostic {
1187 title: "Gzip compression failure".into(),
1188 text,
1189 hint: None,
1190 level: Level::Error,
1191 location: None,
1192 }]
1193 }
1194
1195 Error::AddTar { path, err } => {
1196 let text = format!(
1197 "There was a problem when attempting to add the file {path}
1198to a tar archive.
1199
1200This was error from the tar library:
1201
1202 {err}"
1203 );
1204 vec![Diagnostic {
1205 title: "Failure creating tar archive".into(),
1206 text,
1207 hint: None,
1208 level: Level::Error,
1209 location: None,
1210 }]
1211 }
1212
1213 Error::ExpandTar { error } => {
1214 let text = format!(
1215 "There was a problem when attempting to expand a to a tar archive.
1216
1217This was error from the tar library:
1218
1219 {error}"
1220 );
1221 vec![Diagnostic {
1222 title: "Failure opening tar archive".into(),
1223 text,
1224 hint: None,
1225 level: Level::Error,
1226 location: None,
1227 }]
1228 }
1229
1230 Error::TarFinish(detail) => {
1231 let text = format!(
1232 "There was a problem when creating a tar archive.
1233
1234This was error from the tar library:
1235
1236 {detail}"
1237 );
1238 vec![Diagnostic {
1239 title: "Failure creating tar archive".into(),
1240 text,
1241 hint: None,
1242 level: Level::Error,
1243 location: None,
1244 }]
1245 }
1246
1247 Error::Hex(detail) => {
1248 let text = format!(
1249 "There was a problem when using the Hex API.
1250
1251This was error from the Hex client library:
1252
1253 {detail}"
1254 );
1255 vec![Diagnostic {
1256 title: "Hex API failure".into(),
1257 text,
1258 hint: None,
1259 level: Level::Error,
1260 location: None,
1261 }]
1262 }
1263
1264 Error::DuplicateModule {
1265 module,
1266 first,
1267 second,
1268 } => {
1269 let text = format!(
1270 "The module `{module}` is defined multiple times.
1271
1272First: {first}
1273Second: {second}"
1274 );
1275
1276 vec![Diagnostic {
1277 title: "Duplicate module".into(),
1278 text,
1279 hint: None,
1280 level: Level::Error,
1281 location: None,
1282 }]
1283 }
1284
1285 Error::ClashingGleamModuleAndNativeFileName { module, gleam_file, native_file } => {
1286 let text = format!(
1287 "The Gleam module `{module}` is clashing with a native file
1288with the same name:
1289
1290 Gleam module: {gleam_file}
1291 Native file: {native_file}
1292
1293This is a problem because the Gleam module would be compiled to a file with the
1294same name and extension, unintentionally overwriting the native file.");
1295
1296 vec![Diagnostic {
1297 title: "Gleam module clashes with native file".into(),
1298 text,
1299 hint: Some("Consider renaming one of the files, such as by adding an `_ffi` suffix to the native file's name, and trying again.".into()),
1300 level: Level::Error,
1301 location: None,
1302 }]
1303 },
1304
1305 Error::DuplicateSourceFile { file } => vec![Diagnostic {
1306 title: "Duplicate Source file".into(),
1307 text: format!("The file `{file}` is defined multiple times."),
1308 hint: None,
1309 level: Level::Error,
1310 location: None,
1311 }],
1312
1313 Error::DuplicateNativeErlangModule {
1314 module,
1315 first,
1316 second,
1317 } => {
1318 let text = format!(
1319 "The native Erlang module `{module}` is defined multiple times.
1320
1321First: {first}
1322Second: {second}
1323
1324Erlang modules must have unique names regardless of the subfolders where their
1325`.erl` files are located."
1326 );
1327
1328 vec![Diagnostic {
1329 title: "Duplicate native Erlang module".into(),
1330 text,
1331 hint: Some("Rename one of the native Erlang modules and try again.".into()),
1332 level: Level::Error,
1333 location: None,
1334 }]
1335 },
1336
1337 Error::FileIo {
1338 kind,
1339 action,
1340 path,
1341 err,
1342 } => {
1343 let err = match err {
1344 Some(e) => {
1345 format!("\nThe error message from the file IO library was:\n\n {e}\n")
1346 }
1347 None => "".into(),
1348 };
1349 let mut text = format!(
1350 "An error occurred while trying to {} this {}:
1351
1352 {}
1353{}",
1354 action.text(),
1355 kind.text(),
1356 path,
1357 err,
1358 );
1359 if cfg!(target_family = "windows") && action == &FileIoAction::Link {
1360 text.push_str("
1361
1362Windows does not support symbolic links without developer mode
1363or admin privileges. Please enable developer mode and try again.
1364
1365https://learn.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development#activate-developer-mode");
1366 }
1367 vec![Diagnostic {
1368 title: "File IO failure".into(),
1369 text,
1370 hint: None,
1371 level: Level::Error,
1372 location: None,
1373 }]
1374 }
1375
1376
1377 Error::FailedToEncrypt { detail } => {
1378 let text = wrap_format!("A problem was encountered encrypting data.
1379The error from the encryption library was:
1380
1381 {detail}"
1382);
1383 vec![Diagnostic {
1384 title: "Failed to encrypt data".into(),
1385 text,
1386 hint: None,
1387 level: Level::Error,
1388 location: None,
1389 }]
1390 }
1391
1392 Error::FailedToDecrypt { detail } => {
1393 let text = wrap_format!("A problem was encountered decrypting data.
1394The error from the encryption library was:
1395
1396 {detail}"
1397);
1398 vec![Diagnostic {
1399 title: "Failed to decrypt data".into(),
1400 text,
1401 hint: None,
1402 level: Level::Error,
1403 location: None,
1404 }]
1405 }
1406
1407 Error::NonUtf8Path { path } => {
1408 let text = format!(
1409 "Encountered a non UTF-8 path '{}', but only UTF-8 paths are supported.",
1410 path.to_string_lossy()
1411 );
1412 vec![Diagnostic {
1413 title: "Non UTF-8 Path Encountered".into(),
1414 text,
1415 level: Level::Error,
1416 location: None,
1417 hint: None,
1418 }]
1419 }
1420
1421 Error::GitInitialization { error } => {
1422 let text = format!(
1423 "An error occurred while trying make a git repository for this project:
1424
1425 {error}"
1426 );
1427 vec![Diagnostic {
1428 title: "Failed to initialize git repository".into(),
1429 text,
1430 hint: None,
1431 level: Level::Error,
1432 location: None,
1433 }]
1434 }
1435
1436 Error::Type { path, src, errors: error, names } => error
1437 .iter()
1438 .map(|error| {
1439 match error {
1440 TypeError::SrcImportingTest {
1441 location,
1442 src_module,
1443 test_module,
1444 } => {
1445 let text = wrap_format!(
1446 "The application module `{src_module}` \
1447is importing the test module `{test_module}`.
1448
1449Test modules are not included in production builds so test \
1450modules cannot import them. Perhaps move the `{test_module}` \
1451module to the src directory.",
1452 );
1453
1454 Diagnostic {
1455 title: "App importing test module".into(),
1456 text,
1457 hint: None,
1458 level: Level::Error,
1459 location: Some(Location {
1460 label: Label {
1461 text: Some("Imported here".into()),
1462 span: *location,
1463 },
1464 path: path.clone(),
1465 src: src.clone(),
1466 extra_labels: vec![],
1467 }),
1468 }
1469 }
1470
1471 TypeError::UnknownLabels {
1472 unknown,
1473 valid,
1474 supplied,
1475 } => {
1476 let other_labels: Vec<_> = valid
1477 .iter()
1478 .filter(|label| !supplied.contains(label))
1479 .cloned()
1480 .collect();
1481
1482 let title = if unknown.len() > 1 {
1483 "Unknown labels"
1484 } else {
1485 "Unknown label"
1486 }
1487 .into();
1488
1489 let mut labels = unknown.iter().map(|(label, location)| {
1490 let text = did_you_mean(label, &other_labels)
1491 .unwrap_or_else(|| "Unexpected label".into());
1492 Label {
1493 text: Some(text),
1494 span: *location,
1495 }
1496 });
1497 let label = labels.next().expect("Unknown labels first label");
1498 let extra_labels = labels.map(|label| ExtraLabel {
1499 src_info: None,
1500 label,
1501 }).collect();
1502 let text = if valid.is_empty() {
1503 "This constructor does not accept any labelled arguments.".into()
1504 } else if other_labels.is_empty() {
1505 "You have already supplied all the labelled arguments that this
1506constructor accepts."
1507 .into()
1508 } else {
1509 let mut label_text = String::from("It accepts these labels:\n");
1510 for label in other_labels.iter().sorted() {
1511 label_text.push_str("\n ");
1512 label_text.push_str(label);
1513 }
1514 label_text
1515 };
1516 Diagnostic {
1517 title,
1518 text,
1519 hint: None,
1520 level: Level::Error,
1521 location: Some(Location {
1522 label,
1523 path: path.clone(),
1524 src: src.clone(),
1525 extra_labels,
1526 }),
1527 }
1528 }
1529
1530 TypeError::UnexpectedLabelledArg { location, label } => {
1531 let text = format!(
1532 "This argument has been given a label but the constructor does
1533not expect any. Please remove the label `{label}`."
1534 );
1535 Diagnostic {
1536 title: "Unexpected labelled argument".into(),
1537 text,
1538 hint: None,
1539 level: Level::Error,
1540 location: Some(Location {
1541 label: Label {
1542 text: None,
1543 span: *location,
1544 },
1545 path: path.clone(),
1546 src: src.clone(),
1547 extra_labels: vec![],
1548 }),
1549 }
1550 }
1551
1552 TypeError::PositionalArgumentAfterLabelled { location } => {
1553 let text = wrap("This unlabeled argument has been \
1554supplied after a labelled argument.
1555Once a labelled argument has been supplied all following arguments must
1556also be labelled.");
1557
1558 Diagnostic {
1559 title: "Unexpected positional argument".into(),
1560 text,
1561 hint: None,
1562 level: Level::Error,
1563 location: Some(Location {
1564 label: Label {
1565 text: None,
1566 span: *location,
1567 },
1568 path: path.clone(),
1569 src: src.clone(),
1570 extra_labels: vec![],
1571 }),
1572 }
1573 }
1574
1575 TypeError::DuplicateImport {
1576 location,
1577 previous_location,
1578 name,
1579 } => {
1580 let text = format!(
1581 "`{name}` has been imported multiple times.
1582Names in a Gleam module must be unique so one will need to be renamed."
1583 );
1584 Diagnostic {
1585 title: "Duplicate import".into(),
1586 text,
1587 hint: None,
1588 level: Level::Error,
1589 location: Some(Location {
1590 label: Label {
1591 text: Some("Reimported here".into()),
1592 span: *location,
1593 },
1594 path: path.clone(),
1595 src: src.clone(),
1596 extra_labels: vec![ExtraLabel {
1597 src_info: None,
1598 label: Label {
1599 text: Some("First imported here".into()),
1600 span: *previous_location,
1601 },
1602 }],
1603 }),
1604 }
1605 }
1606
1607 TypeError::DuplicateName {
1608 location_a,
1609 location_b,
1610 name,
1611 ..
1612 } => {
1613 let (first_location, second_location) = if location_a.start < location_b.start {
1614 (location_a, location_b)
1615 } else {
1616 (location_b, location_a)
1617 };
1618 let text = format!(
1619 "`{name}` has been defined multiple times.
1620Names in a Gleam module must be unique so one will need to be renamed."
1621 );
1622 Diagnostic {
1623 title: "Duplicate definition".into(),
1624 text,
1625 hint: None,
1626 level: Level::Error,
1627 location: Some(Location {
1628 label: Label {
1629 text: Some("Redefined here".into()),
1630 span: *second_location,
1631 },
1632 path: path.clone(),
1633 src: src.clone(),
1634 extra_labels: vec![ExtraLabel {
1635 src_info: None,
1636 label: Label {
1637 text: Some("First defined here".into()),
1638 span: *first_location,
1639 },
1640 }],
1641 }),
1642 }
1643 }
1644
1645 TypeError::DuplicateTypeName {
1646 name,
1647 location,
1648 previous_location,
1649 ..
1650 } => {
1651 let text = format!(
1652 "The type `{name}` has been defined multiple times.
1653Names in a Gleam module must be unique so one will need to be renamed."
1654 );
1655 Diagnostic {
1656 title: "Duplicate type definition".into(),
1657 text,
1658 hint: None,
1659 level: Level::Error,
1660 location: Some(Location {
1661 label: Label {
1662 text: Some("Redefined here".into()),
1663 span: *location,
1664 },
1665 path: path.clone(),
1666 src: src.clone(),
1667 extra_labels: vec![ExtraLabel {
1668 src_info: None,
1669 label: Label {
1670 text: Some("First defined here".into()),
1671 span: *previous_location,
1672 }
1673 }],
1674 }),
1675 }
1676 }
1677
1678 TypeError::DuplicateField { location, label } => {
1679 let text =
1680 format!("The label `{label}` has already been defined. Rename this label.");
1681 Diagnostic {
1682 title: "Duplicate label".into(),
1683 text,
1684 hint: None,
1685 level: Level::Error,
1686 location: Some(Location {
1687 label: Label {
1688 text: None,
1689 span: *location,
1690 },
1691 path: path.clone(),
1692 src: src.clone(),
1693 extra_labels: vec![],
1694 }),
1695 }
1696 }
1697
1698 TypeError::DuplicateArgument { location, label } => {
1699 let text = format!("The labelled argument `{label}` has already been supplied.");
1700 Diagnostic {
1701 title: "Duplicate argument".into(),
1702 text,
1703 hint: None,
1704 level: Level::Error,
1705 location: Some(Location {
1706 label: Label {
1707 text: None,
1708 span: *location,
1709 },
1710 path: path.clone(),
1711 src: src.clone(),
1712 extra_labels: vec![],
1713 }),
1714 }
1715 }
1716
1717 TypeError::RecursiveType { location } => {
1718 let text = wrap("I don't know how to work out what type this \
1719value has. It seems to be defined in terms of itself.
1720
1721Hint: Add some type annotations and try again.")
1722 ;
1723 Diagnostic {
1724 title: "Recursive type".into(),
1725 text,
1726 hint: None,
1727 level: Level::Error,
1728 location: Some(Location {
1729 label: Label {
1730 text: None,
1731 span: *location,
1732 },
1733 path: path.clone(),
1734 src: src.clone(),
1735 extra_labels: vec![],
1736 }),
1737 }
1738 }
1739
1740 TypeError::NotFn { location, type_ } => {
1741 let mut printer = Printer::new(names);
1742 let text = format!(
1743 "This value is being called as a function but its type is:\n\n {}",
1744 printer.print_type(type_)
1745 );
1746 Diagnostic {
1747 title: "Type mismatch".into(),
1748 text,
1749 hint: None,
1750 level: Level::Error,
1751 location: Some(Location {
1752 label: Label {
1753 text: None,
1754 span: *location,
1755 },
1756 path: path.clone(),
1757 src: src.clone(),
1758 extra_labels: vec![],
1759 }),
1760 }
1761 }
1762
1763 TypeError::UnknownRecordField {
1764 usage,
1765 location,
1766 type_,
1767 label,
1768 fields,
1769 unknown_field: variants,
1770 } => {
1771 let mut printer = Printer::new(names);
1772
1773 // Give a hint about what type this value has.
1774 let mut text = format!(
1775 "The value being accessed has this type:\n\n {}\n",
1776 printer.print_type(type_)
1777 );
1778
1779 // Give a hint about what record fields this value has, if any.
1780 if fields.is_empty() {
1781 if variants == &UnknownField::NoFields {
1782 text.push_str("\nIt does not have any fields.");
1783 } else {
1784 text.push_str("\nIt does not have fields that are common \
1785across all variants.");
1786 }
1787 } else {
1788 text.push_str("\nIt has these accessible fields:\n");
1789 }
1790 for field in fields.iter().sorted() {
1791 text.push_str("\n .");
1792 text.push_str(field);
1793 }
1794
1795 match variants {
1796 UnknownField::AppearsInAVariant => {
1797 let msg = wrap(
1798 "Note: The field you are trying to access is \
1799not defined consistently across all variants of this custom type. To fix this, \
1800ensure that all variants include the field with the same name, position, and \
1801type.",
1802 );
1803 text.push_str("\n\n");
1804 text.push_str(&msg);
1805 }
1806 UnknownField::AppearsInAnImpossibleVariant => {
1807 let msg = wrap(
1808 "Note: The field exists in this custom type \
1809but is not defined for the current variant. Ensure that you are accessing the \
1810field on a variant where it is valid.",
1811 );
1812 text.push_str("\n\n");
1813 text.push_str(&msg);
1814 }
1815 UnknownField::TrulyUnknown => (),
1816 UnknownField::NoFields => (),
1817 }
1818
1819 // Give a hint about Gleam not having OOP methods if it
1820 // looks like they might be trying to call one.
1821 match usage {
1822 FieldAccessUsage::MethodCall => {
1823 let msg = wrap(
1824 "Gleam is not object oriented, so if you are trying \
1825to call a method on this value you may want to use the function syntax instead.",
1826 );
1827 text.push_str("\n\n");
1828 text.push_str(&msg);
1829 text.push_str("\n\n ");
1830 text.push_str(label);
1831 text.push_str("(value)");
1832 }
1833 FieldAccessUsage::Other | FieldAccessUsage::RecordUpdate => (),
1834 }
1835
1836 let label = did_you_mean(label, fields)
1837 .unwrap_or_else(|| "This field does not exist".into());
1838 Diagnostic {
1839 title: "Unknown record field".into(),
1840 text,
1841 hint: None,
1842 level: Level::Error,
1843 location: Some(Location {
1844 label: Label {
1845 text: Some(label),
1846 span: *location,
1847 },
1848 path: path.clone(),
1849 src: src.clone(),
1850 extra_labels: vec![],
1851 }),
1852 }
1853 }
1854
1855 TypeError::CouldNotUnify {
1856 location,
1857 expected,
1858 given,
1859 situation: Some(UnifyErrorSituation::Operator(op)),
1860 } => {
1861 let mut printer = Printer::new(names);
1862 let mut text = format!(
1863 "The {op} operator expects arguments of this type:
1864
1865 {expected}
1866
1867But this argument has this type:
1868
1869 {given}\n",
1870 op = op.name(),
1871 expected = printer.print_type(expected),
1872 given = printer.print_type(given),
1873 );
1874 if let Some(hint) = hint_alternative_operator(op, given) {
1875 text.push('\n');
1876 text.push_str("Hint: ");
1877 text.push_str(&hint);
1878 }
1879 Diagnostic {
1880 title: "Type mismatch".into(),
1881 text,
1882 hint: None,
1883 level: Level::Error,
1884 location: Some(Location {
1885 label: Label {
1886 text: None,
1887 span: *location,
1888 },
1889 path: path.clone(),
1890 src: src.clone(),
1891 extra_labels: vec![],
1892 }),
1893 }
1894 }
1895
1896 TypeError::CouldNotUnify {
1897 location,
1898 expected,
1899 given,
1900 situation: Some(UnifyErrorSituation::PipeTypeMismatch),
1901 } => {
1902 // Remap the pipe function type into just the type expected by the pipe.
1903 let expected = expected
1904 .fn_types()
1905 .and_then(|(args, _)| args.first().cloned());
1906
1907 // Remap the argument as well, if it's a function.
1908 let given = given
1909 .fn_types()
1910 .and_then(|(args, _)| args.first().cloned())
1911 .unwrap_or_else(|| given.clone());
1912
1913 let mut printer = Printer::new(names);
1914 let text = format!(
1915 "The argument is:
1916
1917 {given}
1918
1919But function expects:
1920
1921 {expected}",
1922 expected = expected
1923 .map(|v| printer.print_type(&v))
1924 .unwrap_or_else(|| " No arguments".into()),
1925 given = printer.print_type(&given)
1926 );
1927
1928 Diagnostic {
1929 title: "Type mismatch".into(),
1930 text,
1931 hint: None,
1932 level: Level::Error,
1933 location: Some(Location {
1934 label: Label {
1935 text: Some("This function does not accept the piped type".into()),
1936 span: *location,
1937 },
1938 path: path.clone(),
1939 src: src.clone(),
1940 extra_labels: vec![],
1941 }),
1942 }
1943 }
1944
1945 TypeError::CouldNotUnify {
1946 location,
1947 expected,
1948 given,
1949 situation,
1950 } => {
1951 let mut printer = Printer::new(names);
1952 let mut text = if let Some(description) = situation.as_ref().and_then(|s| s.description()) {
1953 let mut text = description.to_string();
1954 text.push('\n');
1955 text.push('\n');
1956 text
1957 } else {
1958 "".into()
1959 };
1960 text.push_str("Expected type:\n\n ");
1961 text.push_str(&printer.print_type(expected));
1962 text.push_str("\n\nFound type:\n\n ");
1963 text.push_str(&printer.print_type(given));
1964 Diagnostic {
1965 title: "Type mismatch".into(),
1966 text,
1967 hint: None,
1968 level: Level::Error,
1969 location: Some(Location {
1970 label: Label {
1971 text: None,
1972 span: *location,
1973 },
1974 path: path.clone(),
1975 src: src.clone(),
1976 extra_labels: vec![],
1977 }),
1978 }
1979 }
1980
1981 TypeError::IncorrectTypeArity {
1982 location,
1983 expected,
1984 given,
1985 ..
1986 } => {
1987 let text = wrap("Functions and constructors have to be \
1988called with their expected number of arguments.");
1989 let expected = match expected {
1990 0 => "no arguments".into(),
1991 1 => "1 argument".into(),
1992 _ => format!("{expected} arguments"),
1993 };
1994 Diagnostic {
1995 title: "Incorrect arity".into(),
1996 text,
1997 hint: None,
1998 level: Level::Error,
1999 location: Some(Location {
2000 label: Label {
2001 text: Some(format!("Expected {expected}, got {given}")),
2002 span: *location,
2003 },
2004 path: path.clone(),
2005 src: src.clone(),
2006 extra_labels: vec![],
2007 }),
2008 }
2009 }
2010
2011 TypeError::IncorrectArity {
2012 labels,
2013 location,
2014 expected,
2015 given,
2016 } => {
2017 let text = if labels.is_empty() {
2018 "".into()
2019 } else {
2020 let labels = labels
2021 .iter()
2022 .map(|p| format!(" - {p}"))
2023 .sorted()
2024 .join("\n");
2025 format!("This call accepts these additional labelled arguments:\n\n{labels}",)
2026 };
2027 let expected = match expected {
2028 0 => "no arguments".into(),
2029 1 => "1 argument".into(),
2030 _ => format!("{expected} arguments"),
2031 };
2032 let label = format!("Expected {expected}, got {given}");
2033 Diagnostic {
2034 title: "Incorrect arity".into(),
2035 text,
2036 hint: None,
2037 level: Level::Error,
2038 location: Some(Location {
2039 label: Label {
2040 text: Some(label),
2041 span: *location,
2042 },
2043 path: path.clone(),
2044 src: src.clone(),
2045 extra_labels: vec![],
2046 }),
2047 }
2048 }
2049
2050 TypeError::UnnecessarySpreadOperator { location, arity } => {
2051 let text = wrap_format!(
2052 "This record has {arity} fields and you have already \
2053assigned variables to all of them."
2054 );
2055 Diagnostic {
2056 title: "Unnecessary spread operator".into(),
2057 text,
2058 hint: None,
2059 level: Level::Error,
2060 location: Some(Location {
2061 label: Label {
2062 text: None,
2063 span: *location,
2064 },
2065 path: path.clone(),
2066 src: src.clone(),
2067 extra_labels: vec![],
2068 }),
2069 }
2070 }
2071
2072 TypeError::UnsafeRecordUpdate { location, reason } =>
2073 match reason {
2074 UnsafeRecordUpdateReason::UnknownVariant {constructed_variant} => {
2075 let text = wrap_format!("
2076This value cannot be used to build an updated `{constructed_variant}` \
2077as it could be some other variant.
2078
2079Consider pattern matching on it with a case expression and then \
2080constructing a new record with its values.");
2081
2082 Diagnostic {
2083 title: "Unsafe record update".into(),
2084 text,
2085 hint: None,
2086 level: Level::Error,
2087 location: Some(Location {
2088 label: Label {
2089 text: Some(format!("I'm not sure this is always a `{constructed_variant}`")),
2090 span: *location,
2091 },
2092 path: path.clone(),
2093 src: src.clone(),
2094 extra_labels: vec![],
2095 }),
2096 }
2097 },
2098 UnsafeRecordUpdateReason::WrongVariant {constructed_variant, spread_variant} => {
2099 let text = wrap_format!("This value is a `{spread_variant}` so \
2100it cannot be used to build a `{constructed_variant}`, even if they share some fields.
2101
2102Note: If you want to change one variant of a type into another, you should \
2103specify all fields explicitly instead of using the record update syntax.");
2104
2105 Diagnostic {
2106 title: "Incorrect record update".into(),
2107 text,
2108 hint: None,
2109 level: Level::Error,
2110 location: Some(Location {
2111 label: Label {
2112 text: Some(format!("This is a `{spread_variant}`")),
2113 span: *location,
2114 },
2115 path: path.clone(),
2116 src: src.clone(),
2117 extra_labels: vec![],
2118 }),
2119 }
2120 },
2121 UnsafeRecordUpdateReason::IncompatibleFieldTypes {expected_field_type, record_field_type, record_variant, field_name, ..} => {
2122 let mut printer = Printer::new(names);
2123 let expected_field_type = printer.print_type(expected_field_type);
2124 let record_field_type = printer.print_type(record_field_type);
2125 let record_variant = printer.print_type(record_variant);
2126 let text = wrap_format!("The `{field_name}` field of this value is a `{record_field_type}`, but the arguments given to the record update indicate that it should be a `{expected_field_type}`.
2127
2128Note: If the same type variable is used for multiple fields, all those fields need to be updated at the same time if their type changes.");
2129
2130 Diagnostic {
2131 title: "Incomplete record update".into(),
2132 text,
2133 hint: None,
2134 level: Level::Error,
2135 location: Some(Location {
2136 label: Label {
2137 text: Some(format!("This is a `{record_variant}`")),
2138 span: *location,
2139 },
2140 path: path.clone(),
2141 src: src.clone(),
2142 extra_labels: vec![],
2143 }),
2144 }
2145 }
2146 }
2147
2148
2149 TypeError::UnknownType {
2150 location,
2151 name,
2152 hint,
2153 } => {
2154 let label_text = match hint {
2155 UnknownTypeHint::AlternativeTypes(types) => did_you_mean(name, types),
2156 UnknownTypeHint::ValueInScopeWithSameName => None,
2157 };
2158
2159 let mut text =
2160 wrap_format!("The type `{name}` is not defined or imported in this module.");
2161
2162 match hint {
2163 UnknownTypeHint::ValueInScopeWithSameName => {
2164 let hint = wrap_format!(
2165 "There is a value in scope with the name `{name}`, \
2166but no type in scope with that name."
2167 );
2168 text.push('\n');
2169 text.push_str(hint.as_str());
2170 }
2171 UnknownTypeHint::AlternativeTypes(_) => {}
2172 };
2173
2174 Diagnostic {
2175 title: "Unknown type".into(),
2176 text,
2177 hint: None,
2178 level: Level::Error,
2179 location: Some(Location {
2180 label: Label {
2181 text: label_text,
2182 span: *location,
2183 },
2184 path: path.clone(),
2185 src: src.clone(),
2186 extra_labels: vec![],
2187 }),
2188 }
2189 }
2190
2191 TypeError::UnknownVariable {
2192 location,
2193 variables,
2194 name,
2195 type_with_name_in_scope,
2196 } => {
2197 let text = if *type_with_name_in_scope {
2198 wrap_format!("`{name}` is a type, it cannot be used as a value.")
2199 } else {
2200 wrap_format!("The name `{name}` is not in scope here.")
2201 };
2202 Diagnostic {
2203 title: "Unknown variable".into(),
2204 text,
2205 hint: None,
2206 level: Level::Error,
2207 location: Some(Location {
2208 label: Label {
2209 text: did_you_mean(name, variables),
2210 span: *location,
2211 },
2212 path: path.clone(),
2213 src: src.clone(),
2214 extra_labels: vec![],
2215 }),
2216 }
2217 }
2218
2219 TypeError::PrivateTypeLeak { location, leaked } => {
2220 let mut printer = Printer::new(names);
2221
2222 // TODO: be more precise.
2223 // - is being returned by this public function
2224 // - is taken as an argument by this public function
2225 // - is taken as an argument by this public enum constructor
2226 // etc
2227 let text = wrap_format!(
2228 "The following type is private, but is \
2229being used by this public export.
2230
2231 {}
2232
2233Private types can only be used within the module that defines them.",
2234 printer.print_type(leaked),
2235 );
2236 Diagnostic {
2237 title: "Private type used in public interface".into(),
2238 text,
2239 hint: None,
2240 level: Level::Error,
2241 location: Some(Location {
2242 label: Label {
2243 text: None,
2244 span: *location,
2245 },
2246 path: path.clone(),
2247 src: src.clone(),
2248 extra_labels: vec![],
2249 }),
2250 }
2251 }
2252
2253 TypeError::UnknownModule {
2254 location,
2255 name,
2256 suggestions
2257 } => Diagnostic {
2258 title: "Unknown module".into(),
2259 text: format!("No module has been found with the name `{name}`."),
2260 hint: suggestions.first().map(|suggestion| suggestion.suggestion(name)),
2261 level: Level::Error,
2262 location: Some(Location {
2263 label: Label {
2264 text: None,
2265 span: *location,
2266 },
2267 path: path.clone(),
2268 src: src.clone(),
2269 extra_labels: vec![],
2270 }),
2271 },
2272
2273 TypeError::UnknownModuleType {
2274 location,
2275 name,
2276 module_name,
2277 type_constructors,
2278 value_with_same_name: imported_type_as_value
2279 } => {
2280 let text = if *imported_type_as_value {
2281 format!("`{name}` is only a value, it cannot be imported as a type.")
2282 } else {
2283 format!("The module `{module_name}` does not have a `{name}` type.")
2284 };
2285 Diagnostic {
2286 title: "Unknown module type".into(),
2287 text,
2288 hint: None,
2289 level: Level::Error,
2290 location: Some(Location {
2291 label: Label {
2292 text: if *imported_type_as_value {
2293 Some(format!("Did you mean `{name}`?"))
2294 } else {
2295 did_you_mean(name, type_constructors)
2296 },
2297 span: *location,
2298 },
2299 path: path.clone(),
2300 src: src.clone(),
2301 extra_labels: vec![],
2302 }),
2303 }
2304 }
2305
2306 TypeError::UnknownModuleValue {
2307 location,
2308 name,
2309 module_name,
2310 value_constructors,
2311 type_with_same_name: imported_value_as_type,
2312 context,
2313 } => {
2314 let text = if *imported_value_as_type {
2315 match context {
2316 ModuleValueUsageContext::UnqualifiedImport =>
2317 wrap_format!("`{name}` is only a type, it cannot be imported as a value."),
2318 ModuleValueUsageContext::ModuleAccess =>
2319 wrap_format!("{module_name}.{name} is a type constructor, it cannot be used as a value"),
2320 }
2321 } else {
2322 wrap_format!("The module `{module_name}` does not have a `{name}` value.")
2323 };
2324 Diagnostic {
2325 title: "Unknown module value".into(),
2326 text,
2327 hint: None,
2328 level: Level::Error,
2329 location: Some(Location {
2330 label: Label {
2331 text: if *imported_value_as_type && matches!(context, ModuleValueUsageContext::UnqualifiedImport) {
2332 Some(format!("Did you mean `type {name}`?"))
2333 } else {
2334 did_you_mean(name, value_constructors)
2335 },
2336 span: *location,
2337 },
2338 path: path.clone(),
2339 src: src.clone(),
2340 extra_labels: vec![],
2341 }),
2342 }
2343 }
2344
2345 TypeError::ModuleAliasUsedAsName {
2346 location,
2347 name
2348 } => {
2349 let text = wrap(
2350"Modules are not values, so you cannot assign them to variables, pass \
2351them to functions, or anything else that you would do with a value."
2352 );
2353 Diagnostic {
2354 title: format!("Module `{name}` used as a value"),
2355 text,
2356 hint: None,
2357 level: Level::Error,
2358 location: Some(Location {
2359 label: Label {
2360 text: None,
2361 span: *location,
2362 },
2363 path: path.clone(),
2364 src: src.clone(),
2365 extra_labels: vec![],
2366 }),
2367 }
2368 }
2369
2370 TypeError::IncorrectNumClausePatterns {
2371 location,
2372 expected,
2373 given,
2374 } => {
2375 let text = wrap_format!(
2376 "This case expression has {expected} subjects, \
2377but this pattern matches {given}.
2378Each clause must have a pattern for every subject value.",
2379 );
2380 Diagnostic {
2381 title: "Incorrect number of patterns".into(),
2382 text,
2383 hint: None,
2384 level: Level::Error,
2385 location: Some(Location {
2386 label: Label {
2387 text: Some(format!("Expected {expected} patterns, got {given}")),
2388 span: *location,
2389 },
2390 path: path.clone(),
2391 src: src.clone(),
2392 extra_labels: vec![],
2393 }),
2394 }
2395 }
2396
2397 TypeError::NonLocalClauseGuardVariable { location, name } => {
2398 let text = wrap_format!(
2399 "Variables used in guards must be either defined in the \
2400function, or be an argument to the function. The variable \
2401`{name}` is not defined locally.",
2402 );
2403 Diagnostic {
2404 title: "Invalid guard variable".into(),
2405 text,
2406 hint: None,
2407 level: Level::Error,
2408 location: Some(Location {
2409 label: Label {
2410 text: Some("Is not locally defined".into()),
2411 span: *location,
2412 },
2413 path: path.clone(),
2414 src: src.clone(),
2415 extra_labels: vec![],
2416 }),
2417 }
2418 }
2419
2420 TypeError::ExtraVarInAlternativePattern { location, name } => {
2421 let text = wrap_format!(
2422"All alternative patterns must define the same variables as \
2423the initial pattern. This variable `{name}` has not been \
2424previously defined.",
2425 );
2426 Diagnostic {
2427 title: "Extra alternative pattern variable".into(),
2428 text,
2429 hint: None,
2430 level: Level::Error,
2431 location: Some(Location {
2432 label: Label {
2433 text: Some("Has not been previously defined".into()),
2434 span: *location,
2435 },
2436 path: path.clone(),
2437 src: src.clone(),
2438 extra_labels: vec![],
2439 }),
2440 }
2441 }
2442
2443 TypeError::MissingVarInAlternativePattern { location, name } => {
2444 let text = wrap_format!(
2445 "All alternative patterns must define the same variables \
2446as the initial pattern, but the `{name}` variable is missing.",
2447 );
2448 Diagnostic {
2449 title: "Missing alternative pattern variable".into(),
2450 text,
2451 hint: None,
2452 level: Level::Error,
2453 location: Some(Location {
2454 label: Label {
2455 text: Some("This does not define all required variables".into()),
2456 span: *location,
2457 },
2458 path: path.clone(),
2459 src: src.clone(),
2460 extra_labels: vec![],
2461 }),
2462 }
2463 }
2464
2465 TypeError::DuplicateVarInPattern { location, name } => {
2466 let text = wrap_format!(
2467 "Variables can only be used once per pattern. This \
2468variable `{name}` appears multiple times.
2469If you used the same variable twice deliberately in order to check for equality \
2470please use a guard clause instead.
2471e.g. (x, y) if x == y -> ...",
2472 );
2473 Diagnostic {
2474 title: "Duplicate variable in pattern".into(),
2475 text,
2476 hint: None,
2477 level: Level::Error,
2478 location: Some(Location {
2479 label: Label {
2480 text: Some("This has already been used".into()),
2481 span: *location,
2482 },
2483 path: path.clone(),
2484 src: src.clone(),
2485 extra_labels: vec![],
2486 }),
2487 }
2488 }
2489
2490 TypeError::OutOfBoundsTupleIndex {
2491 location, size: 0, ..
2492 } => Diagnostic {
2493 title: "Out of bounds tuple index".into(),
2494 text: "This tuple has no elements so it cannot be indexed at all.".into(),
2495 hint: None,
2496 level: Level::Error,
2497 location: Some(Location {
2498 label: Label {
2499 text: None,
2500 span: *location,
2501 },
2502 path: path.clone(),
2503 src: src.clone(),
2504 extra_labels: vec![],
2505 }),
2506 },
2507
2508 TypeError::OutOfBoundsTupleIndex {
2509 location,
2510 index,
2511 size,
2512 } => {
2513 let text = wrap_format!(
2514 "The index being accessed for this tuple is {}, but this \
2515tuple has {} elements so the highest valid index is {}.",
2516 index,
2517 size,
2518 size - 1,
2519 );
2520 Diagnostic {
2521 title: "Out of bounds tuple index".into(),
2522 text,
2523 hint: None,
2524 level: Level::Error,
2525 location: Some(Location {
2526 label: Label {
2527 text: Some("This index is too large".into()),
2528 span: *location,
2529 },
2530 path: path.clone(),
2531 src: src.clone(),
2532 extra_labels: vec![],
2533 }),
2534 }
2535 }
2536
2537 TypeError::NotATuple { location, given } => {
2538 let mut printer = Printer::new(names);
2539 let text = format!(
2540 "To index into this value it needs to be a tuple, however it has this type:
2541
2542 {}",
2543 printer.print_type(given),
2544 );
2545 Diagnostic {
2546 title: "Type mismatch".into(),
2547 text,
2548 hint: None,
2549 level: Level::Error,
2550 location: Some(Location {
2551 label: Label {
2552 text: Some("This is not a tuple".into()),
2553 span: *location,
2554 },
2555 path: path.clone(),
2556 src: src.clone(),
2557 extra_labels: vec![],
2558 }),
2559 }
2560 }
2561
2562 TypeError::NotATupleUnbound { location } => {
2563 let text = wrap("To index into a tuple we need to \
2564know its size, but we don't know anything about this type yet. \
2565Please add some type annotations so we can continue."
2566 );
2567 Diagnostic {
2568 title: "Type mismatch".into(),
2569 text,
2570 hint: None,
2571 level: Level::Error,
2572 location: Some(Location {
2573 label: Label {
2574 text: Some("What type is this?".into()),
2575 span: *location,
2576 },
2577 path: path.clone(),
2578 src: src.clone(),
2579 extra_labels: vec![],
2580 }),
2581 }
2582 }
2583
2584 TypeError::RecordAccessUnknownType { location } => {
2585 let text = wrap("In order to access a record field \
2586we need to know what type it is, but I can't tell \
2587the type here. Try adding type annotations to your \
2588function and try again.");
2589 Diagnostic {
2590 title: "Unknown type for record access".into(),
2591 text,
2592 hint: None,
2593 level: Level::Error,
2594 location: Some(Location {
2595 label: Label {
2596 text: Some("I don't know what type this is".into()),
2597 span: *location,
2598 },
2599 path: path.clone(),
2600 src: src.clone(),
2601 extra_labels: vec![],
2602 }),
2603 }
2604 }
2605
2606 TypeError::BitArraySegmentError { error, location } => {
2607 let (label, mut extra) = match error {
2608 bit_array::ErrorType::ConflictingTypeOptions { existing_type } => (
2609 "This is an extra type specifier",
2610 vec![format!("Hint: This segment already has the type {existing_type}.")],
2611 ),
2612
2613 bit_array::ErrorType::ConflictingSignednessOptions {
2614 existing_signed
2615 } => (
2616 "This is an extra signedness specifier",
2617 vec![format!(
2618 "Hint: This segment already has a signedness of {existing_signed}."
2619 )],
2620 ),
2621
2622 bit_array::ErrorType::ConflictingEndiannessOptions {
2623 existing_endianness
2624 } => (
2625 "This is an extra endianness specifier",
2626 vec![format!(
2627 "Hint: This segment already has an endianness of {existing_endianness}."
2628 )],
2629 ),
2630
2631 bit_array::ErrorType::ConflictingSizeOptions => (
2632 "This is an extra size specifier",
2633 vec!["Hint: This segment already has a size.".into()],
2634 ),
2635
2636 bit_array::ErrorType::ConflictingUnitOptions => (
2637 "This is an extra unit specifier",
2638 vec!["Hint: A BitArray segment can have at most 1 unit.".into()],
2639 ),
2640
2641 bit_array::ErrorType::FloatWithSize => (
2642 "Invalid float size",
2643 vec!["Hint: floats have an exact size of 16/32/64 bits.".into()],
2644 ),
2645
2646 bit_array::ErrorType::InvalidEndianness => (
2647 "This option is invalid here",
2648 vec![wrap("Hint: signed and unsigned can only be used with \
2649 int, float, utf16 and utf32 types.")],
2650 ),
2651
2652 bit_array::ErrorType::OptionNotAllowedInValue => (
2653 "This option is only allowed in BitArray patterns",
2654 vec!["Hint: This option has no effect in BitArray values.".into()],
2655 ),
2656
2657 bit_array::ErrorType::SignednessUsedOnNonInt { type_ } => (
2658 "Signedness is only valid with int types",
2659 vec![format!("Hint: This segment has a type of {type_}")],
2660 ),
2661 bit_array::ErrorType::TypeDoesNotAllowSize { type_ } => (
2662 "Size cannot be specified here",
2663 vec![format!("Hint: {type_} segments have an automatic size.")],
2664 ),
2665 bit_array::ErrorType::TypeDoesNotAllowUnit { type_ } => (
2666 "Unit cannot be specified here",
2667 vec![wrap(&format!("Hint: {type_} segments are sized based on their value \
2668 and cannot have a unit."))],
2669 ),
2670 bit_array::ErrorType::VariableUtfSegmentInPattern => (
2671 "This cannot be a variable",
2672 vec![wrap("Hint: in patterns utf8, utf16, and utf32 must be an exact string.")],
2673 ),
2674 bit_array::ErrorType::SegmentMustHaveSize => (
2675 "This segment has no size",
2676 vec![wrap("Hint: Bit array segments without a size are only \
2677 allowed at the end of a bin pattern.")],
2678 ),
2679 bit_array::ErrorType::UnitMustHaveSize => (
2680 "This needs an explicit size",
2681 vec!["Hint: If you specify unit() you must also specify size().".into()],
2682 ),
2683 };
2684 extra.push("See: https://tour.gleam.run/data-types/bit-arrays/".into());
2685 let text = extra.join("\n");
2686 Diagnostic {
2687 title: "Invalid bit array segment".into(),
2688 text,
2689 hint: None,
2690 level: Level::Error,
2691 location: Some(Location {
2692 label: Label {
2693 text: Some(label.into()),
2694 span: *location,
2695 },
2696 path: path.clone(),
2697 src: src.clone(),
2698 extra_labels: vec![],
2699 }),
2700 }
2701 }
2702
2703 TypeError::RecordUpdateInvalidConstructor { location } => Diagnostic {
2704 title: "Invalid record constructor".into(),
2705 text: "Only record constructors can be used with the update syntax.".into(),
2706 hint: None,
2707 level: Level::Error,
2708 location: Some(Location {
2709 label: Label {
2710 text: Some("This is not a record constructor".into()),
2711 span: *location,
2712 },
2713 path: path.clone(),
2714 src: src.clone(),
2715 extra_labels: vec![],
2716 }),
2717 },
2718
2719 TypeError::UnexpectedTypeHole { location } => Diagnostic {
2720 title: "Unexpected type hole".into(),
2721 text: "We need to know the exact type here so type holes cannot be used.".into(),
2722 hint: None,
2723 level: Level::Error,
2724 location: Some(Location {
2725 label: Label {
2726 text: Some("I need to know what this is".into()),
2727 span: *location,
2728 },
2729 path: path.clone(),
2730 src: src.clone(),
2731 extra_labels: vec![],
2732 }),
2733 },
2734
2735 TypeError::ReservedModuleName { name } => {
2736 let text = format!(
2737 "The module name `{name}` is reserved.
2738Try a different name for this module."
2739 );
2740 Diagnostic {
2741 title: "Reserved module name".into(),
2742 text,
2743 hint: None,
2744 location: None,
2745 level: Level::Error,
2746 }
2747 }
2748
2749 TypeError::KeywordInModuleName { name, keyword } => {
2750 let text = wrap_format!(
2751 "The module name `{name}` contains the keyword `{keyword}`, \
2752so importing it would be a syntax error.
2753Try a different name for this module."
2754 );
2755 Diagnostic {
2756 title: "Invalid module name".into(),
2757 text,
2758 hint: None,
2759 location: None,
2760 level: Level::Error,
2761 }
2762 }
2763
2764 TypeError::NotExhaustivePatternMatch {
2765 location,
2766 unmatched,
2767 kind,
2768 } => {
2769 let mut text = match kind {
2770 PatternMatchKind::Case => {
2771 "This case expression does not match all possibilities.
2772Each constructor must have a pattern that matches it or
2773else it could crash."
2774 }
2775 PatternMatchKind::Assignment => {
2776 "This assignment does not match all possibilities.
2777Either use a case expression with patterns for each possible
2778value, or use `let assert` rather than `let`."
2779 }
2780 }
2781 .to_string();
2782
2783 text.push_str("\n\nThese values are not matched:\n\n");
2784 for unmatched in unmatched {
2785 text.push_str(" - ");
2786 text.push_str(unmatched);
2787 text.push('\n');
2788 }
2789 Diagnostic {
2790 title: "Not exhaustive pattern match".into(),
2791 text,
2792 hint: None,
2793 level: Level::Error,
2794 location: Some(Location {
2795 label: Label {
2796 text: None,
2797 span: *location,
2798 },
2799 path: path.clone(),
2800 src: src.clone(),
2801 extra_labels: vec![],
2802 }),
2803 }
2804 }
2805
2806 TypeError::ArgumentNameAlreadyUsed { location, name } => Diagnostic {
2807 title: "Argument name already used".into(),
2808 text: format!("Two `{name}` arguments have been defined for this function."),
2809 hint: None,
2810 level: Level::Error,
2811 location: Some(Location {
2812 label: Label {
2813 text: None,
2814 span: *location,
2815 },
2816 path: path.clone(),
2817 src: src.clone(),
2818 extra_labels: vec![],
2819 }),
2820 },
2821
2822 TypeError::UnlabelledAfterlabelled { location } => Diagnostic {
2823 title: "Unlabelled argument after labelled argument".into(),
2824 text: wrap("All unlabelled arguments must come before any labelled arguments."),
2825 hint: None,
2826 level: Level::Error,
2827 location: Some(Location {
2828 label: Label {
2829 text: None,
2830 span: *location,
2831 },
2832 path: path.clone(),
2833 src: src.clone(),
2834 extra_labels: vec![],
2835 }),
2836 },
2837
2838 TypeError::RecursiveTypeAlias { location, cycle } => {
2839 let mut text = "This type alias is defined in terms of itself.\n".into();
2840 write_cycle(&mut text, cycle);
2841 text.push_str(
2842 "If we tried to compile this recursive type it would expand
2843forever in a loop, and we'd never get the final type.",
2844 );
2845 Diagnostic {
2846 title: "Type cycle".into(),
2847 text,
2848 hint: None,
2849 level: Level::Error,
2850 location: Some(Location {
2851 label: Label {
2852 text: None,
2853 span: *location,
2854 },
2855 path: path.clone(),
2856 src: src.clone(),
2857 extra_labels: vec![],
2858 }),
2859 }
2860 }
2861
2862 TypeError::ExternalMissingAnnotation { location, kind } => {
2863 let kind = match kind {
2864 MissingAnnotation::Parameter => "parameter",
2865 MissingAnnotation::Return => "return",
2866 };
2867 let text = format!(
2868 "A {kind} annotation is missing from this function.
2869
2870Functions with external implementations must have type annotations
2871so we can tell what type of values they accept and return.",
2872 );
2873 Diagnostic {
2874 title: "Missing type annotation".into(),
2875 text,
2876 hint: None,
2877 level: Level::Error,
2878 location: Some(Location {
2879 label: Label {
2880 text: None,
2881 span: *location,
2882 },
2883 path: path.clone(),
2884 src: src.clone(),
2885 extra_labels: vec![],
2886 }),
2887 }
2888 }
2889
2890 TypeError::NoImplementation { location } => {
2891 let text = "We can't compile this function as it doesn't have an
2892implementation. Add a body or an external implementation
2893using the `@external` attribute."
2894 .into();
2895 Diagnostic {
2896 title: "Function without an implementation".into(),
2897 text,
2898 hint: None,
2899 level: Level::Error,
2900 location: Some(Location {
2901 label: Label {
2902 text: None,
2903 span: *location,
2904 },
2905 path: path.clone(),
2906 src: src.clone(),
2907 extra_labels: vec![],
2908 }),
2909 }
2910 }
2911
2912 TypeError::InvalidExternalJavascriptModule {
2913 location,
2914 name,
2915 module,
2916 } => {
2917 let text = wrap_format!(
2918 "The function `{name}` has an external JavaScript \
2919implementation but the module path `{module}` is not valid."
2920 );
2921 Diagnostic {
2922 title: "Invalid JavaScript module".into(),
2923 text,
2924 hint: None,
2925 level: Level::Error,
2926 location: Some(Location {
2927 label: Label {
2928 text: None,
2929 span: *location,
2930 },
2931 path: path.clone(),
2932 src: src.clone(),
2933 extra_labels: vec![],
2934 }),
2935 }
2936 }
2937
2938 TypeError::InvalidExternalJavascriptFunction {
2939 location,
2940 name,
2941 function,
2942 } => {
2943 let text = wrap_format!(
2944 "The function `{name}` has an external JavaScript \
2945implementation but the function name `{function}` is not valid."
2946 );
2947 Diagnostic {
2948 title: "Invalid JavaScript function".into(),
2949 text,
2950 hint: None,
2951 level: Level::Error,
2952 location: Some(Location {
2953 label: Label {
2954 text: None,
2955 span: *location,
2956 },
2957 path: path.clone(),
2958 src: src.clone(),
2959 extra_labels: vec![],
2960 }),
2961 }
2962 }
2963
2964 TypeError::InexhaustiveLetAssignment { location, missing } => {
2965 let mut text =wrap(
2966 "This assignment uses a pattern that does not \
2967match all possible values. If one of the other values \
2968is used then the assignment will crash.
2969
2970The missing patterns are:\n");
2971 for missing in missing {
2972 text.push_str("\n ");
2973 text.push_str(missing);
2974 }
2975 text.push('\n');
2976
2977 Diagnostic {
2978 title: "Inexhaustive pattern".into(),
2979 text,
2980 hint: Some("Use a more general pattern or use `let assert` instead.".into()),
2981 level: Level::Error,
2982 location: Some(Location {
2983 src: src.clone(),
2984 path: path.to_path_buf(),
2985 label: Label {
2986 text: None,
2987 span: *location,
2988 },
2989 extra_labels: Vec::new(),
2990 }),
2991 }
2992 }
2993
2994 TypeError::InexhaustiveCaseExpression { location, missing } => {
2995 let mut text =wrap(
2996 "This case expression does not have a pattern \
2997for all possible values. If it is run on one of the \
2998values without a pattern then it will crash.
2999
3000The missing patterns are:\n"
3001 );
3002 for missing in missing {
3003 text.push_str("\n ");
3004 text.push_str(missing);
3005 }
3006 Diagnostic {
3007 title: "Inexhaustive patterns".into(),
3008 text,
3009 hint: None,
3010 level: Level::Error,
3011 location: Some(Location {
3012 src: src.clone(),
3013 path: path.to_path_buf(),
3014 label: Label {
3015 text: None,
3016 span: *location,
3017 },
3018 extra_labels: Vec::new(),
3019 }),
3020 }
3021 }
3022
3023 TypeError::UnsupportedExpressionTarget {
3024 location,
3025 target: current_target,
3026 } => {
3027 let text = wrap_format!(
3028 "This value is not available as it is defined using externals, \
3029and there is no implementation for the {} target.\n",
3030 match current_target {
3031 Target::Erlang => "Erlang",
3032 Target::JavaScript => "JavaScript",
3033 }
3034 );
3035 let hint = wrap("Did you mean to build for a different target?");
3036 Diagnostic {
3037 title: "Unsupported target".into(),
3038 text,
3039 hint: Some(hint),
3040 level: Level::Error,
3041 location: Some(Location {
3042 path: path.clone(),
3043 src: src.clone(),
3044 label: Label {
3045 text: None,
3046 span: *location,
3047 },
3048 extra_labels: vec![],
3049 }),
3050 }
3051 }
3052
3053 TypeError::UnsupportedPublicFunctionTarget {
3054 location,
3055 name,
3056 target,
3057 } => {
3058 let target = match target {
3059 Target::Erlang => "Erlang",
3060 Target::JavaScript => "JavaScript",
3061 };
3062 let text = wrap_format!(
3063 "The `{name}` function is public but doesn't have an \
3064implementation for the {target} target. All public functions of a package \
3065must be able to compile for a module to be valid."
3066 );
3067 Diagnostic {
3068 title: "Unsupported target".into(),
3069 text,
3070 hint: None,
3071 level: Level::Error,
3072 location: Some(Location {
3073 path: path.clone(),
3074 src: src.clone(),
3075 label: Label {
3076 text: None,
3077 span: *location,
3078 },
3079 extra_labels: vec![],
3080 }),
3081 }
3082 }
3083
3084 TypeError::UnusedTypeAliasParameter { location, name } => {
3085 let text = wrap_format!(
3086 "The type variable `{name}` is unused. It can be safely removed.",
3087 );
3088 Diagnostic {
3089 title: "Unused type parameter".into(),
3090 text,
3091 hint: None,
3092 level: Level::Error,
3093 location: Some(Location {
3094 path: path.clone(),
3095 src: src.clone(),
3096 label: Label {
3097 text: None,
3098 span: *location,
3099 },
3100 extra_labels: vec![],
3101 }),
3102 }
3103 }
3104
3105 TypeError::DuplicateTypeParameter { location, name } => {
3106 let text = wrap_format!(
3107 "This definition has multiple type parameters named `{name}`.
3108Rename or remove one of them.",
3109 );
3110 Diagnostic {
3111 title: "Duplicate type parameter".into(),
3112 text,
3113 hint: None,
3114 level: Level::Error,
3115 location: Some(Location {
3116 path: path.clone(),
3117 src: src.clone(),
3118 label: Label {
3119 text: None,
3120 span: *location,
3121 },
3122 extra_labels: vec![],
3123 }),
3124 }
3125 },
3126
3127 TypeError::NotFnInUse { location, type_ } => {
3128 let mut printer = Printer::new(names);
3129 let text = wrap_format!(
3130 "In a use expression, there should be a function on \
3131the right hand side of `<-`, but this value has type:
3132
3133 {}
3134
3135See: https://tour.gleam.run/advanced-features/use/",
3136 printer.print_type(type_)
3137 );
3138
3139 Diagnostic {
3140 title: "Type mismatch".into(),
3141 text,
3142 hint: None,
3143 level: Level::Error,
3144 location: Some(Location {
3145 label: Label {
3146 text: None,
3147 span: *location,
3148 },
3149 path: path.clone(),
3150 src: src.clone(),
3151 extra_labels: vec![],
3152 }),
3153 }
3154 },
3155
3156 TypeError::UseFnDoesntTakeCallback { location, actual_type: None }
3157 | TypeError::UseFnIncorrectArity { location, expected: 0, given: 1 } => {
3158 let text = wrap("The function on the right of `<-` here \
3159takes no arguments, but it has to take at least \
3160one argument, a callback function.
3161
3162See: https://tour.gleam.run/advanced-features/use/");
3163 Diagnostic {
3164 title: "Incorrect arity".into(),
3165 text,
3166 hint: None,
3167 level: Level::Error,
3168 location: Some(Location {
3169 label: Label {
3170 text: Some("Expected no arguments, got 1".into()),
3171 span: *location,
3172 },
3173 path: path.clone(),
3174 src: src.clone(),
3175 extra_labels: vec![],
3176 }),
3177 }
3178 },
3179
3180 TypeError::UseFnIncorrectArity { location, expected, given } => {
3181 let expected_string = match expected {
3182 0 => "no arguments".into(),
3183 1 => "1 argument".into(),
3184 _ => format!("{expected} arguments"),
3185 };
3186 let supplied_arguments = given - 1;
3187 let supplied_arguments_string = match supplied_arguments {
3188 0 => "no arguments".into(),
3189 1 => "1 argument".into(),
3190 _ => format!("{given} arguments"),
3191 };
3192 let label = format!("Expected {expected_string}, got {given}");
3193 let mut text: String = format!("The function on the right of `<-` \
3194here takes {expected_string}.\n");
3195
3196 if expected > given {
3197 if supplied_arguments == 0 {
3198 text.push_str("The only argument that was supplied is \
3199the `use` callback function.\n")
3200 } else {
3201 text.push_str(&format!("You supplied {supplied_arguments_string} \
3202and the final one is the `use` callback function.\n"));
3203 }
3204 } else {
3205 text.push_str("All the arguments have already been supplied, \
3206so it cannot take the `use` callback function as a final argument.\n")
3207 };
3208
3209 text.push_str("\nSee: https://tour.gleam.run/advanced-features/use/");
3210
3211 Diagnostic {
3212 title: "Incorrect arity".into(),
3213 text: wrap(&text),
3214 hint: None,
3215 level: Level::Error,
3216 location: Some(Location {
3217 label: Label {
3218 text: Some(label),
3219 span: *location,
3220 },
3221 path: path.clone(),
3222 src: src.clone(),
3223 extra_labels: vec![],
3224 }),
3225 }
3226 },
3227
3228 TypeError::UseFnDoesntTakeCallback { location, actual_type: Some(actual) } => {
3229 let mut printer = Printer::new(names);
3230 let text = wrap_format!("The function on the right hand side of `<-` \
3231has to take a callback function as its last argument. \
3232But the last argument of this function has type:
3233
3234 {}
3235
3236See: https://tour.gleam.run/advanced-features/use/",
3237 printer.print_type(actual)
3238 );
3239 Diagnostic {
3240 title: "Type mismatch".into(),
3241 text: wrap(&text),
3242 hint: None,
3243 level: Level::Error,
3244 location: Some(Location {
3245 label: Label {
3246 text: None,
3247 span: *location,
3248 },
3249 path: path.clone(),
3250 src: src.clone(),
3251 extra_labels: vec![],
3252 }),
3253 }
3254 },
3255
3256 TypeError::UseCallbackIncorrectArity { pattern_location, call_location, expected, given } => {
3257 let expected = match expected {
3258 0 => "no arguments".into(),
3259 1 => "1 argument".into(),
3260 _ => format!("{expected} arguments"),
3261 };
3262
3263 let specified = match given {
3264 0 => "none were provided".into(),
3265 1 => "1 was provided".into(),
3266 _ => format!("{given} were provided"),
3267 };
3268
3269 let text = wrap_format!("This function takes a callback that expects {expected}. \
3270But {specified} on the left hand side of `<-`.
3271
3272See: https://tour.gleam.run/advanced-features/use/");
3273 Diagnostic {
3274 title: "Incorrect arity".into(),
3275 text,
3276 hint: None,
3277 level: Level::Error,
3278 location: Some(Location {
3279 label: Label {
3280 text: None,
3281 span: *call_location,
3282 },
3283 path: path.clone(),
3284 src: src.clone(),
3285 extra_labels: vec![ExtraLabel {
3286 src_info: None,
3287 label: Label {
3288 text: Some(format!("Expected {expected}, got {given}")),
3289 span: *pattern_location
3290 }
3291 }],
3292 }),
3293 }
3294 },
3295
3296 TypeError::BadName { location, name, kind } => {
3297 let kind_str = kind.as_str();
3298 let label = format!("This is not a valid {kind_str} name");
3299 let text = match kind {
3300 Named::Type |
3301 Named::TypeAlias |
3302 Named::CustomTypeVariant => wrap_format!("Hint: {} names start with an uppercase \
3303letter and contain only lowercase letters, numbers, \
3304and uppercase letters.
3305Try: {}", kind_str.to_title_case(), name.to_upper_camel_case()),
3306 Named::Variable |
3307 Named::TypeVariable |
3308 Named::Argument |
3309 Named::Label |
3310 Named::Constant |
3311 Named::Function => wrap_format!("Hint: {} names start with a lowercase letter \
3312and contain a-z, 0-9, or _.
3313Try: {}", kind_str.to_title_case(), name.to_snake_case()),
3314 Named::Discard => wrap_format!("Hint: {} names start with _ and contain \
3315a-z, 0-9, or _.
3316Try: _{}", kind_str.to_title_case(), name.to_snake_case()),
3317 };
3318
3319 Diagnostic {
3320 title: format!("Invalid {kind_str} name"),
3321 text,
3322 hint: None,
3323 level: Level::Error,
3324 location: Some(Location {
3325 label: Label {
3326 text: Some(label),
3327 span: *location,
3328 },
3329 path: path.clone(),
3330 src: src.clone(),
3331 extra_labels: vec![],
3332 }),
3333 }
3334 },
3335 TypeError::AllVariantsDeprecated { location } => {
3336 let text = String::from("Consider deprecating the type as a whole.
3337
3338 @deprecated(\"message\")
3339 type Wibble {
3340 Wobble1
3341 Wobble2
3342 }
3343");
3344 Diagnostic {
3345 title: "All variants of custom type deprecated.".into(),
3346 text,
3347 hint: None,
3348 level: Level::Error,
3349 location: Some(Location {
3350 label: Label {
3351 text: None,
3352 span: *location,
3353 },
3354 path: path.clone(),
3355 src: src.clone(),
3356 extra_labels: vec![],
3357 })
3358 }
3359 },
3360 TypeError::DeprecatedVariantOnDeprecatedType{ location } => {
3361 let text = wrap("This custom type has already been deprecated, so deprecating \
3362one of its variants does nothing.
3363Consider removing the deprecation attribute on the variant.");
3364
3365 Diagnostic {
3366 title: "Custom type already deprecated".into(),
3367 text,
3368 hint: None,
3369 level: Level::Error,
3370 location: Some(Location {
3371 label: Label {
3372 text: None,
3373 span: *location,
3374 },
3375 path: path.clone(),
3376 src: src.clone(),
3377 extra_labels: vec![],
3378 })
3379 }
3380 }
3381 }
3382 }).collect_vec(),
3383
3384
3385 Error::Parse { path, src, error } => {
3386 let (label, extra) = error.details();
3387 let text = extra.join("\n");
3388
3389 let adjusted_location = if error.error == ParseErrorType::UnexpectedEof {
3390 crate::ast::SrcSpan {
3391 start: (src.len() - 1) as u32,
3392 end: (src.len() - 1) as u32,
3393 }
3394 } else {
3395 error.location
3396 };
3397
3398 vec![Diagnostic {
3399 title: "Syntax error".into(),
3400 text,
3401 hint: None,
3402 level: Level::Error,
3403 location: Some(Location {
3404 label: Label {
3405 text: Some(label.to_string()),
3406 span: adjusted_location,
3407 },
3408 path: path.clone(),
3409 src: src.clone(),
3410 extra_labels: vec![],
3411 }),
3412 }]
3413 }
3414
3415 Error::ImportCycle { modules } => {
3416 let first_location = &modules.first().1;
3417 let rest_locations = modules.iter().skip(1).map(|(_, l)| ExtraLabel {
3418 label: Label {
3419 text: Some("Imported here".into()),
3420 span: l.location
3421 },
3422 src_info: Some((l.src.clone(), l.path.clone())),
3423 }).collect_vec();
3424 let mut text = "The import statements for these modules form a cycle:
3425"
3426 .into();
3427 let mod_names = modules.iter().map(|m| m.0.clone()).collect_vec();
3428 write_cycle(&mut text, &mod_names);
3429 text.push_str(
3430 "Gleam doesn't support dependency cycles like these, please break the
3431cycle to continue.",
3432 );
3433 vec![Diagnostic {
3434 title: "Import cycle".into(),
3435 text,
3436 hint: None,
3437 level: Level::Error,
3438 location: Some(Location {
3439 label: Label {
3440 text: Some("Imported here".into()),
3441 span: first_location.location,
3442 },
3443 path: first_location.path.clone(),
3444 src: first_location.src.clone(),
3445 extra_labels: rest_locations,
3446 }),
3447 }]
3448 }
3449
3450 Error::PackageCycle { packages } => {
3451 let mut text = "The dependencies for these packages form a cycle:
3452"
3453 .into();
3454 write_cycle(&mut text, packages);
3455 text.push_str(
3456 "Gleam doesn't support dependency cycles like these, please break the
3457cycle to continue.",
3458 );
3459 vec![Diagnostic {
3460 title: "Dependency cycle".into(),
3461 text,
3462 hint: None,
3463 level: Level::Error,
3464 location: None,
3465 }]
3466 }
3467
3468 Error::UnknownImport { import, details } => {
3469 let UnknownImportDetails {
3470 module,
3471 location,
3472 path,
3473 src,
3474 modules,
3475 } = details.as_ref();
3476 let text = wrap(&format!(
3477 "The module `{module}` is trying to import the module `{import}`, \
3478but it cannot be found."
3479 ));
3480 vec![Diagnostic {
3481 title: "Unknown import".into(),
3482 text,
3483 hint: None,
3484 level: Level::Error,
3485 location: Some(Location {
3486 label: Label {
3487 text: did_you_mean(import, modules),
3488 span: *location,
3489 },
3490 path: path.clone(),
3491 src: src.clone(),
3492 extra_labels: vec![],
3493 }),
3494 }]
3495 }
3496
3497 Error::StandardIo { action, err } => {
3498 let err = match err {
3499 Some(e) => format!(
3500 "\nThe error message from the stdio library was:\n\n {}\n",
3501 std_io_error_kind_text(e)
3502 ),
3503 None => "".into(),
3504 };
3505 vec![Diagnostic {
3506 title: "Standard IO failure".into(),
3507 text: format!(
3508 "An error occurred while trying to {}:
3509
3510{}",
3511 action.text(),
3512 err,
3513 ),
3514 hint: None,
3515 location: None,
3516 level: Level::Error,
3517 }]
3518 }
3519
3520 Error::Format { problem_files } => {
3521 let files: Vec<_> = problem_files
3522 .iter()
3523 .map(|formatted| formatted.source.as_str())
3524 .map(|p| format!(" - {p}"))
3525 .sorted()
3526 .collect();
3527 let mut text = files.iter().join("\n");
3528 text.push('\n');
3529 vec![Diagnostic {
3530 title: "These files have not been formatted".into(),
3531 text,
3532 hint: None,
3533 location: None,
3534 level: Level::Error,
3535 }]
3536 }
3537
3538 Error::ForbiddenWarnings { count } => {
3539 let word_warning = match count {
3540 1 => "warning",
3541 _ => "warnings",
3542 };
3543 let text = "Your project was compiled with the `--warnings-as-errors` flag.
3544Fix the warnings and try again."
3545 .into();
3546 vec![Diagnostic {
3547 title: format!("{count} {word_warning} generated."),
3548 text,
3549 hint: None,
3550 location: None,
3551 level: Level::Error,
3552 }]
3553 }
3554
3555 Error::JavaScript { src, path, error } => match error {
3556 javascript::Error::Unsupported { feature, location } => vec![Diagnostic {
3557 title: "Unsupported feature for compilation target".into(),
3558 text: format!("{feature} is not supported for JavaScript compilation."),
3559 hint: None,
3560 level: Level::Error,
3561 location: Some(Location {
3562 label: Label {
3563 text: None,
3564 span: *location,
3565 },
3566 path: path.clone(),
3567 src: src.clone(),
3568 extra_labels: vec![],
3569 }),
3570 }],
3571 },
3572
3573 Error::DownloadPackageError {
3574 package_name,
3575 package_version,
3576 error,
3577 } => {
3578 let text = format!(
3579 "A problem was encountered when downloading `{package_name}` {package_version}.
3580The error from the package manager client was:
3581
3582 {error}"
3583 );
3584 vec![Diagnostic {
3585 title: "Failed to download package".into(),
3586 text,
3587 hint: None,
3588 location: None,
3589 level: Level::Error,
3590 }]
3591 }
3592
3593 Error::Http(error) => {
3594 let text = format!(
3595 "A HTTP request failed.
3596The error from the HTTP client was:
3597
3598 {error}"
3599 );
3600 vec![Diagnostic {
3601 title: "HTTP error".into(),
3602 text,
3603 hint: None,
3604 location: None,
3605 level: Level::Error,
3606 }]
3607 }
3608
3609 Error::InvalidVersionFormat { input, error } => {
3610 let text = format!(
3611 "I was unable to parse the version \"{input}\".
3612The error from the parser was:
3613
3614 {error}"
3615 );
3616 vec![Diagnostic {
3617 title: "Invalid version format".into(),
3618 text,
3619 hint: None,
3620 location: None,
3621 level: Level::Error,
3622 }]
3623 }
3624
3625 Error::DependencyCanonicalizationFailed(package) => {
3626 let text = format!("Local package `{package}` has no canonical path");
3627
3628 vec![Diagnostic {
3629 title: "Failed to create canonical path".into(),
3630 text,
3631 hint: None,
3632 location: None,
3633 level: Level::Error,
3634 }]
3635 }
3636
3637 Error::DependencyResolutionFailed(error) => {
3638 let text = format!(
3639 "An error occurred while determining what dependency packages and
3640versions should be downloaded.
3641The error from the version resolver library was:
3642
3643{}",
3644 wrap(error)
3645 );
3646 vec![Diagnostic {
3647 title: "Dependency resolution failed".into(),
3648 text,
3649 hint: None,
3650 location: None,
3651 level: Level::Error,
3652 }]
3653 }
3654
3655 Error::GitDependencyUnsupported => vec![Diagnostic {
3656 title: "Git dependencies are not currently supported".into(),
3657 text: "Please remove all git dependencies from the gleam.toml file".into(),
3658 hint: None,
3659 location: None,
3660 level: Level::Error,
3661 }],
3662
3663 Error::WrongDependencyProvided {
3664 path,
3665 expected,
3666 found,
3667 } => {
3668 let text = format!(
3669 "Expected package `{expected}` at path `{path}` but found `{found}` instead.",
3670 );
3671
3672 vec![Diagnostic {
3673 title: "Wrong dependency provided".into(),
3674 text,
3675 hint: None,
3676 location: None,
3677 level: Level::Error,
3678 }]
3679 }
3680
3681 Error::ProvidedDependencyConflict {
3682 package,
3683 source_1,
3684 source_2,
3685 } => {
3686 let text = format!(
3687 "The package `{package}` is provided as both `{source_1}` and `{source_2}`.",
3688 );
3689
3690 vec![Diagnostic {
3691 title: "Conflicting provided dependencies".into(),
3692 text,
3693 hint: None,
3694 location: None,
3695 level: Level::Error,
3696 }]
3697 }
3698
3699 Error::DuplicateDependency(name) => {
3700 let text = format!(
3701 "The package `{name}` is specified in both the dependencies and
3702dev-dependencies sections of the gleam.toml file."
3703 );
3704 vec![Diagnostic {
3705 title: "Dependency duplicated".into(),
3706 text,
3707 hint: None,
3708 location: None,
3709 level: Level::Error,
3710 }]
3711 }
3712
3713 Error::MissingHexPublishFields {
3714 description_missing,
3715 licence_missing,
3716 } => {
3717 let mut text =
3718 "Licence information and package description are required to publish a
3719package to Hex.\n"
3720 .to_string();
3721 text.push_str(if *description_missing && *licence_missing {
3722 r#"Add the licences and description fields to your gleam.toml file.
3723
3724description = ""
3725licences = ["Apache-2.0"]"#
3726 } else if *description_missing {
3727 r#"Add the description field to your gleam.toml file.
3728
3729description = """#
3730 } else {
3731 r#"Add the licences field to your gleam.toml file.
3732
3733licences = ["Apache-2.0"]"#
3734 });
3735 vec![Diagnostic {
3736 title: "Missing required package fields".into(),
3737 text,
3738 hint: None,
3739 location: None,
3740 level: Level::Error,
3741 }]
3742 }
3743
3744
3745 Error::PublishNonHexDependencies { package } => vec![Diagnostic {
3746 title: "Unpublished dependencies".into(),
3747 text: wrap_format!(
3748 "The package cannot be published to Hex \
3749because dependency `{package}` is not a Hex dependency.",
3750 ),
3751 hint: None,
3752 location: None,
3753 level: Level::Error,
3754 }],
3755
3756 Error::UnsupportedBuildTool {
3757 package,
3758 build_tools,
3759 } => {
3760 let text = wrap_format!(
3761 "The package `{}` cannot be built as it does not use \
3762a build tool supported by Gleam. It uses {:?}.
3763
3764If you would like us to support this package please let us know by opening an \
3765issue in our tracker: https://github.com/gleam-lang/gleam/issues",
3766 package,
3767 build_tools
3768 );
3769 vec![Diagnostic {
3770 title: "Unsupported build tool".into(),
3771 text,
3772 hint: None,
3773 location: None,
3774 level: Level::Error,
3775 }]
3776 }
3777
3778 Error::FailedToOpenDocs { path, error } => {
3779 let error = format!("\nThe error message from the library was:\n\n {error}\n");
3780 let text = format!(
3781 "An error occurred while trying to open the docs:
3782
3783 {path}
3784{error}",
3785 );
3786 vec![Diagnostic {
3787 title: "Failed to open docs".into(),
3788 text,
3789 hint: None,
3790 level: Level::Error,
3791 location: None,
3792 }]
3793 }
3794
3795 Error::IncompatibleCompilerVersion {
3796 package,
3797 required_version,
3798 gleam_version,
3799 } => {
3800 let text = format!(
3801 "The package `{package}` requires a Gleam version \
3802satisfying {required_version} but you are using v{gleam_version}.",
3803 );
3804 vec![Diagnostic {
3805 title: "Incompatible Gleam version".into(),
3806 text,
3807 hint: None,
3808 location: None,
3809 level: Level::Error,
3810 }]
3811 }
3812
3813 Error::InvalidRuntime {
3814 target,
3815 invalid_runtime,
3816 } => {
3817 let text = format!("Invalid runtime for {target} target: {invalid_runtime}");
3818
3819 let hint = match target {
3820 Target::JavaScript => {
3821 Some("available runtimes for JavaScript are: node, deno.".into())
3822 }
3823 Target::Erlang => Some(
3824 "You can not set a runtime for Erlang. Did you mean to target JavaScript?"
3825 .into(),
3826 ),
3827 };
3828
3829 vec![Diagnostic {
3830 title: format!("Invalid runtime for {target}"),
3831 text,
3832 hint,
3833 location: None,
3834 level: Level::Error,
3835 }]
3836 }
3837
3838 Error::JavaScriptPreludeRequired => vec![Diagnostic {
3839 title: "JavaScript prelude required".into(),
3840 text: "The --javascript-prelude flag must be given when compiling to JavaScript."
3841 .into(),
3842 level: Level::Error,
3843 location: None,
3844 hint: None,
3845 }],
3846 Error::CorruptManifest => vec![Diagnostic {
3847 title: "Corrupt manifest.toml".into(),
3848 text: "The `manifest.toml` file is corrupt.".into(),
3849 level: Level::Error,
3850 location: None,
3851 hint: Some("Please run `gleam update` to fix it.".into()),
3852 }],
3853
3854 Error::GleamModuleWouldOverwriteStandardErlangModule { name, path } =>
3855vec![Diagnostic {
3856 title: "Erlang module name collision".into(),
3857 text: wrap_format!("The module `{path}` compiles to an Erlang module \
3858named `{name}`.
3859
3860By default Erlang includes a module with the same name so if we were \
3861to compile and load your module it would overwrite the Erlang one, potentially \
3862causing confusing errors and crashes.
3863"),
3864 level: Level::Error,
3865 location: None,
3866 hint: Some("Rename this module and try again.".into()),
3867 }],
3868
3869 Error::HexPublishReplaceRequired { version } => vec![Diagnostic {
3870 title: "Version already published".into(),
3871 text: wrap_format!("Version v{version} has already been published.
3872This release has been recently published so you can replace it \
3873or you can publish it using a different version number"),
3874 level: Level::Error,
3875 location: None,
3876 hint: Some("Please add the --replace flag if you want to replace the release.".into()),
3877 }],
3878 }
3879 }
3880}
3881
3882fn std_io_error_kind_text(kind: &std::io::ErrorKind) -> String {
3883 use std::io::ErrorKind;
3884 match kind {
3885 ErrorKind::NotFound => "Could not find the stdio stream".into(),
3886 ErrorKind::PermissionDenied => "Permission was denied".into(),
3887 ErrorKind::ConnectionRefused => "Connection was refused".into(),
3888 ErrorKind::ConnectionReset => "Connection was reset".into(),
3889 ErrorKind::ConnectionAborted => "Connection was aborted".into(),
3890 ErrorKind::NotConnected => "Was not connected".into(),
3891 ErrorKind::AddrInUse => "The stream was already in use".into(),
3892 ErrorKind::AddrNotAvailable => "The stream was not available".into(),
3893 ErrorKind::BrokenPipe => "The pipe was broken".into(),
3894 ErrorKind::AlreadyExists => "A handle to the stream already exists".into(),
3895 ErrorKind::WouldBlock => "This operation would block when it was requested not to".into(),
3896 ErrorKind::InvalidInput => "Some parameter was invalid".into(),
3897 ErrorKind::InvalidData => "The data was invalid. Check that the encoding is UTF-8".into(),
3898 ErrorKind::TimedOut => "The operation timed out".into(),
3899 ErrorKind::WriteZero => {
3900 "An attempt was made to write, but all bytes could not be written".into()
3901 }
3902 ErrorKind::Interrupted => "The operation was interrupted".into(),
3903 ErrorKind::UnexpectedEof => "The end of file was reached before it was expected".into(),
3904 _ => "An unknown error occurred".into(),
3905 }
3906}
3907
3908fn write_cycle(buffer: &mut String, cycle: &[EcoString]) {
3909 buffer.push_str(
3910 "
3911 ┌─────┐\n",
3912 );
3913 for (index, name) in cycle.iter().enumerate() {
3914 if index != 0 {
3915 buffer.push_str(" │ ↓\n");
3916 }
3917 buffer.push_str(" │ ");
3918 buffer.push_str(name);
3919 buffer.push('\n');
3920 }
3921 buffer.push_str(" └─────┘\n");
3922}
3923
3924fn hint_alternative_operator(op: &BinOp, given: &Type) -> Option<String> {
3925 match op {
3926 BinOp::AddInt if given.is_float() => Some(hint_numeric_message("+.", "Float")),
3927 BinOp::DivInt if given.is_float() => Some(hint_numeric_message("/.", "Float")),
3928 BinOp::GtEqInt if given.is_float() => Some(hint_numeric_message(">=.", "Float")),
3929 BinOp::GtInt if given.is_float() => Some(hint_numeric_message(">.", "Float")),
3930 BinOp::LtEqInt if given.is_float() => Some(hint_numeric_message("<=.", "Float")),
3931 BinOp::LtInt if given.is_float() => Some(hint_numeric_message("<.", "Float")),
3932 BinOp::MultInt if given.is_float() => Some(hint_numeric_message("*.", "Float")),
3933 BinOp::SubInt if given.is_float() => Some(hint_numeric_message("-.", "Float")),
3934
3935 BinOp::AddFloat if given.is_int() => Some(hint_numeric_message("+", "Int")),
3936 BinOp::DivFloat if given.is_int() => Some(hint_numeric_message("/", "Int")),
3937 BinOp::GtEqFloat if given.is_int() => Some(hint_numeric_message(">=", "Int")),
3938 BinOp::GtFloat if given.is_int() => Some(hint_numeric_message(">", "Int")),
3939 BinOp::LtEqFloat if given.is_int() => Some(hint_numeric_message("<=", "Int")),
3940 BinOp::LtFloat if given.is_int() => Some(hint_numeric_message("<", "Int")),
3941 BinOp::MultFloat if given.is_int() => Some(hint_numeric_message("*", "Int")),
3942 BinOp::SubFloat if given.is_int() => Some(hint_numeric_message("-", "Int")),
3943
3944 BinOp::AddInt if given.is_string() => Some(hint_string_message()),
3945 BinOp::AddFloat if given.is_string() => Some(hint_string_message()),
3946
3947 _ => None,
3948 }
3949}
3950
3951fn hint_numeric_message(alt: &str, type_: &str) -> String {
3952 format!("the {alt} operator can be used with {type_}s\n")
3953}
3954
3955fn hint_string_message() -> String {
3956 wrap(
3957 "Strings can be joined using the `append` or `concat` \
3958functions from the `gleam/string` module.",
3959 )
3960}
3961
3962#[derive(Debug, Clone, PartialEq, Eq)]
3963pub struct Unformatted {
3964 pub source: Utf8PathBuf,
3965 pub destination: Utf8PathBuf,
3966 pub input: EcoString,
3967 pub output: String,
3968}
3969
3970pub fn wrap(text: &str) -> String {
3971 let mut result = String::with_capacity(text.len());
3972
3973 for (i, line) in wrap_text(text, 75).iter().enumerate() {
3974 if i > 0 {
3975 result.push('\n');
3976 }
3977 result.push_str(line);
3978 }
3979
3980 result
3981}
3982
3983fn wrap_text(text: &str, width: usize) -> Vec<Cow<'_, str>> {
3984 let mut lines: Vec<Cow<'_, str>> = Vec::new();
3985 for line in text.split('\n') {
3986 // check if line needs to be broken
3987 match line.len() > width {
3988 false => lines.push(Cow::from(line)),
3989 true => {
3990 let mut new_lines = break_line(line, width);
3991 lines.append(&mut new_lines);
3992 }
3993 };
3994 }
3995
3996 lines
3997}
3998
3999fn break_line(line: &str, width: usize) -> Vec<Cow<'_, str>> {
4000 let mut lines: Vec<Cow<'_, str>> = Vec::new();
4001 let mut newline = String::from("");
4002
4003 // split line by spaces
4004 for (i, word) in line.split(' ').enumerate() {
4005 let is_new_line = i < 1 || newline.is_empty();
4006
4007 let can_add_word = match is_new_line {
4008 true => newline.len() + word.len() <= width,
4009 // +1 accounts for space added before word
4010 false => newline.len() + (word.len() + 1) <= width,
4011 };
4012
4013 if can_add_word {
4014 if !is_new_line {
4015 newline.push(' ');
4016 }
4017 newline.push_str(word);
4018 } else {
4019 // word too big, save existing line if present
4020 if !newline.is_empty() {
4021 // save current line and reset it
4022 lines.push(Cow::from(newline.to_owned()));
4023 newline.clear();
4024 }
4025
4026 // then save word to a new line or break it
4027 match word.len() > width {
4028 false => newline.push_str(word),
4029 true => {
4030 let (mut newlines, remainder) = break_word(word, width);
4031 lines.append(&mut newlines);
4032 newline.push_str(remainder);
4033 }
4034 }
4035 }
4036 }
4037
4038 // save last line after loop finishes
4039 if !newline.is_empty() {
4040 lines.push(Cow::from(newline));
4041 }
4042
4043 lines
4044}
4045
4046// breaks word into n lines based on width. Returns list of new lines and remainder
4047fn break_word(word: &str, width: usize) -> (Vec<Cow<'_, str>>, &str) {
4048 let mut new_lines: Vec<Cow<'_, str>> = Vec::new();
4049 let (first, mut remainder) = word.split_at(width);
4050 new_lines.push(Cow::from(first));
4051
4052 // split remainder until it's small enough
4053 while remainder.len() > width {
4054 let (first, second) = remainder.split_at(width);
4055 new_lines.push(Cow::from(first));
4056 remainder = second;
4057 }
4058
4059 (new_lines, remainder)
4060}