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