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