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