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