Fork of daniellemaywood.uk/gleam — Wasm codegen work
184 kB
5313 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,
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 => {
1913 let text = wrap_format!(
1914 "Unable to decrypt the local Hex API key with the given password.
1915Did you make a typo? Please try again."
1916 );
1917 vec![Diagnostic {
1918 title: "Failed to decrypt local Hex API key".into(),
1919 text,
1920 hint: Some(
1921 "If you have forgotten your local password you can create a new one
1922with `gleam hex authenticate`."
1923 .into(),
1924 ),
1925 level: Level::Error,
1926 location: None,
1927 }]
1928 }
1929
1930 Error::NonUtf8Path { path } => {
1931 let text = format!(
1932 "Encountered a non UTF-8 path '{}', but only UTF-8 paths are supported.",
1933 path.to_string_lossy()
1934 );
1935 vec![Diagnostic {
1936 title: "Non UTF-8 Path Encountered".into(),
1937 text,
1938 level: Level::Error,
1939 location: None,
1940 hint: None,
1941 }]
1942 }
1943
1944 Error::GitInitialization { error } => {
1945 let text = format!(
1946 "An error occurred while trying make a git repository for this project:
1947
1948 {error}"
1949 );
1950 vec![Diagnostic {
1951 title: "Failed to initialize git repository".into(),
1952 text,
1953 hint: None,
1954 level: Level::Error,
1955 location: None,
1956 }]
1957 }
1958
1959 Error::Type {
1960 skipped_modules: _,
1961 failed_modules,
1962 } => failed_modules
1963 .values()
1964 .sorted_by_key(|failed_module| &failed_module.path)
1965 .flat_map(failed_module_diagnostics)
1966 .collect_vec(),
1967
1968 Error::Parse { path, src, error } => {
1969 let location = if error.error == ParseErrorType::UnexpectedEof {
1970 SrcSpan {
1971 start: (src.len() - 1) as u32,
1972 end: (src.len() - 1) as u32,
1973 }
1974 } else {
1975 error.location
1976 };
1977
1978 let title = String::from("Syntax error");
1979 let ParseErrorDetails {
1980 text,
1981 label_text,
1982 extra_labels,
1983 hint,
1984 } = error.error.details();
1985 vec![Diagnostic {
1986 title,
1987 text,
1988 level: Level::Error,
1989 location: Some(Location {
1990 src: src.clone(),
1991 path: path.clone(),
1992 label: Label {
1993 text: Some(label_text.into()),
1994 span: location,
1995 },
1996 extra_labels,
1997 }),
1998 hint,
1999 }]
2000 }
2001
2002 Error::ImportCycle { modules } => {
2003 let first_location = &modules.first().1;
2004 let rest_locations = modules
2005 .iter()
2006 .skip(1)
2007 .map(|(_, l)| ExtraLabel {
2008 label: Label {
2009 text: Some("Imported here".into()),
2010 span: l.location,
2011 },
2012 src_info: Some((l.src.clone(), l.path.clone())),
2013 })
2014 .collect_vec();
2015 let mut text = "The import statements for these modules form a cycle:
2016"
2017 .into();
2018 let mod_names = modules.iter().map(|m| m.0.clone()).collect_vec();
2019 write_cycle(&mut text, &mod_names);
2020 text.push_str(&wrap(
2021 "Gleam doesn't support dependency cycles like these, \
2022please break the cycle to continue.",
2023 ));
2024 vec![Diagnostic {
2025 title: "Import cycle".into(),
2026 text,
2027 hint: None,
2028 level: Level::Error,
2029 location: Some(Location {
2030 label: Label {
2031 text: Some("Imported here".into()),
2032 span: first_location.location,
2033 },
2034 path: first_location.path.clone(),
2035 src: first_location.src.clone(),
2036 extra_labels: rest_locations,
2037 }),
2038 }]
2039 }
2040
2041 Error::PackageCycle { packages } => {
2042 let mut text = "The dependencies for these packages form a cycle:
2043"
2044 .into();
2045 write_cycle(&mut text, packages);
2046 text.push_str(&wrap(
2047 "Gleam doesn't support dependency cycles like these, \
2048please break the cycle to continue.",
2049 ));
2050 vec![Diagnostic {
2051 title: "Dependency cycle".into(),
2052 text,
2053 hint: None,
2054 level: Level::Error,
2055 location: None,
2056 }]
2057 }
2058
2059 Error::UnknownImport { import, details } => {
2060 let UnknownImportDetails {
2061 module,
2062 location,
2063 path,
2064 src,
2065 modules,
2066 } = details.as_ref();
2067 let text = wrap(&format!(
2068 "The module `{module}` is trying to import the module \
2069`{import}`, but it cannot be found."
2070 ));
2071 vec![Diagnostic {
2072 title: "Unknown import".into(),
2073 text,
2074 hint: None,
2075 level: Level::Error,
2076 location: Some(Location {
2077 label: Label {
2078 text: did_you_mean(import, modules),
2079 span: *location,
2080 },
2081 path: path.clone(),
2082 src: src.clone(),
2083 extra_labels: vec![],
2084 }),
2085 }]
2086 }
2087
2088 Error::StandardIo { action, err } => {
2089 let err = match err {
2090 Some(e) => format!(
2091 "\nThe error message from the stdio library was:\n\n {}\n",
2092 std_io_error_kind_text(e)
2093 ),
2094 None => "".into(),
2095 };
2096 vec![Diagnostic {
2097 title: "Standard IO failure".into(),
2098 text: format!(
2099 "An error occurred while trying to {}:
2100
2101{}",
2102 action.text(),
2103 err,
2104 ),
2105 hint: None,
2106 location: None,
2107 level: Level::Error,
2108 }]
2109 }
2110
2111 Error::Format { problem_files } => {
2112 let files: Vec<_> = problem_files
2113 .iter()
2114 .map(|formatted| formatted.source.as_str())
2115 .map(|p| format!(" - {p}"))
2116 .sorted()
2117 .collect();
2118 let mut text = files.iter().join("\n");
2119 text.push('\n');
2120 vec![Diagnostic {
2121 title: "These files have not been formatted".into(),
2122 text,
2123 hint: None,
2124 location: None,
2125 level: Level::Error,
2126 }]
2127 }
2128
2129 Error::ForbiddenWarnings { count } => {
2130 let word_warning = match count {
2131 1 => "warning",
2132 _ => "warnings",
2133 };
2134 let text = "Your project was compiled with the `--warnings-as-errors` flag.
2135Fix the warnings and try again."
2136 .into();
2137 vec![Diagnostic {
2138 title: format!("{count} {word_warning} generated."),
2139 text,
2140 hint: None,
2141 location: None,
2142 level: Level::Error,
2143 }]
2144 }
2145
2146 Error::DownloadPackageError {
2147 package_name,
2148 package_version,
2149 error,
2150 } => {
2151 let text = format!(
2152 "A problem was encountered when downloading `{package_name}` {package_version}.
2153The error from the package manager client was:
2154
2155 {error}"
2156 );
2157 vec![Diagnostic {
2158 title: "Failed to download package".into(),
2159 text,
2160 hint: None,
2161 location: None,
2162 level: Level::Error,
2163 }]
2164 }
2165
2166 Error::Http(error) => {
2167 let text = format!(
2168 "A HTTP request failed.
2169The error from the HTTP client was:
2170
2171 {error}"
2172 );
2173 vec![Diagnostic {
2174 title: "HTTP error".into(),
2175 text,
2176 hint: None,
2177 location: None,
2178 level: Level::Error,
2179 }]
2180 }
2181
2182 Error::InvalidVersionFormat { input, error } => {
2183 let text = format!(
2184 "I was unable to parse the version \"{input}\".
2185The error from the parser was:
2186
2187 {error}"
2188 );
2189 vec![Diagnostic {
2190 title: "Invalid version format".into(),
2191 text,
2192 hint: None,
2193 location: None,
2194 level: Level::Error,
2195 }]
2196 }
2197
2198 Error::IncompatibleLockedVersion { error } => {
2199 let text = format!(
2200 "There is an incompatiblity between a version specified in
2201manifest.toml and a version range specified in gleam.toml:
2202
2203 {error}"
2204 );
2205 vec![Diagnostic {
2206 title: "Incompatible locked version".into(),
2207 text,
2208 hint: None,
2209 location: None,
2210 level: Level::Error,
2211 }]
2212 }
2213
2214 Error::DependencyCanonicalizationFailed(package) => {
2215 let text = format!("Local package `{package}` has no canonical path");
2216
2217 vec![Diagnostic {
2218 title: "Failed to create canonical path".into(),
2219 text,
2220 hint: None,
2221 location: None,
2222 level: Level::Error,
2223 }]
2224 }
2225
2226 Error::DependencyResolutionError(error) => vec![Diagnostic {
2227 title: "Dependency resolution failed".into(),
2228 text: wrap(error),
2229 hint: None,
2230 location: None,
2231 level: Level::Error,
2232 }],
2233
2234 Error::DependencyResolutionNoSolution {
2235 root_package_name,
2236 derivation_tree,
2237 } => {
2238 let text = wrap(
2239 &DerivationTreePrinter::new(
2240 root_package_name.clone(),
2241 derivation_tree.0.clone(),
2242 )
2243 .print(),
2244 );
2245 vec![Diagnostic {
2246 title: "Dependency resolution failed".into(),
2247 text,
2248 hint: None,
2249 location: None,
2250 level: Level::Error,
2251 }]
2252 }
2253
2254 Error::WrongDependencyProvided {
2255 path,
2256 expected,
2257 found,
2258 } => {
2259 let text = format!(
2260 "Expected package `{expected}` at path `{path}` but found `{found}` instead.",
2261 );
2262
2263 vec![Diagnostic {
2264 title: "Wrong dependency provided".into(),
2265 text,
2266 hint: None,
2267 location: None,
2268 level: Level::Error,
2269 }]
2270 }
2271
2272 Error::ProvidedDependencyConflict {
2273 package,
2274 source_1,
2275 source_2,
2276 } => {
2277 let text = format!(
2278 "The package `{package}` is provided as both `{source_1}` and `{source_2}`.",
2279 );
2280
2281 vec![Diagnostic {
2282 title: "Conflicting provided dependencies".into(),
2283 text,
2284 hint: None,
2285 location: None,
2286 level: Level::Error,
2287 }]
2288 }
2289
2290 Error::GitDependencyPathNotFound {
2291 package,
2292 path,
2293 repo,
2294 } => {
2295 let text = format!(
2296 "The path `{path}` does not exist in the git repository `{repo}` \
2297for package `{package}`."
2298 );
2299
2300 vec![Diagnostic {
2301 title: "Git dependency path not found".into(),
2302 text,
2303 hint: None,
2304 location: None,
2305 level: Level::Error,
2306 }]
2307 }
2308
2309 Error::DuplicateDependency(name) => {
2310 let text = format!(
2311 "The package `{name}` is specified in both the dependencies and
2312dev_dependencies sections of the gleam.toml file."
2313 );
2314 vec![Diagnostic {
2315 title: "Dependency duplicated".into(),
2316 text,
2317 hint: None,
2318 location: None,
2319 level: Level::Error,
2320 }]
2321 }
2322
2323 Error::MissingHexPublishFields {
2324 description_missing,
2325 licence_missing,
2326 } => {
2327 let mut text =
2328 "Licence information and package description are required to publish a
2329package to Hex.\n"
2330 .to_string();
2331 text.push_str(if *description_missing && *licence_missing {
2332 r#"Add the licences and description fields to your gleam.toml file.
2333
2334description = ""
2335licences = ["Apache-2.0"]"#
2336 } else if *description_missing {
2337 r#"Add the description field to your gleam.toml file.
2338
2339description = """#
2340 } else {
2341 r#"Add the licences field to your gleam.toml file.
2342
2343licences = ["Apache-2.0"]"#
2344 });
2345 vec![Diagnostic {
2346 title: "Missing required package fields".into(),
2347 text,
2348 hint: None,
2349 location: None,
2350 level: Level::Error,
2351 }]
2352 }
2353
2354 Error::PublishNonHexDependencies { package } => vec![Diagnostic {
2355 title: "Unpublished dependencies".into(),
2356 text: wrap_format!(
2357 "The package cannot be published to Hex \
2358because dependency `{package}` is not a Hex dependency.",
2359 ),
2360 hint: None,
2361 location: None,
2362 level: Level::Error,
2363 }],
2364
2365 Error::UnsupportedBuildTool {
2366 package,
2367 build_tools,
2368 } => {
2369 let text = wrap_format!(
2370 "The package `{}` cannot be built as it does not use \
2371a build tool supported by Gleam. It uses {:?}.
2372
2373If you would like us to support this package please let us know by opening an \
2374issue in our tracker: https://github.com/gleam-lang/gleam/issues",
2375 package,
2376 build_tools
2377 );
2378 vec![Diagnostic {
2379 title: "Unsupported build tool".into(),
2380 text,
2381 hint: None,
2382 location: None,
2383 level: Level::Error,
2384 }]
2385 }
2386
2387 Error::FailedToOpenDocs { path, error } => {
2388 let error = format!("\nThe error message from the library was:\n\n {error}\n");
2389 let text = format!(
2390 "An error occurred while trying to open the docs:
2391
2392 {path}
2393{error}",
2394 );
2395 vec![Diagnostic {
2396 title: "Failed to open docs".into(),
2397 text,
2398 hint: None,
2399 level: Level::Error,
2400 location: None,
2401 }]
2402 }
2403
2404 Error::IncompatibleCompilerVersion {
2405 package,
2406 required_version,
2407 gleam_version,
2408 } => {
2409 let text = format!(
2410 "The package `{package}` requires a Gleam version \
2411satisfying {required_version} but you are using v{gleam_version}.",
2412 );
2413 vec![Diagnostic {
2414 title: "Incompatible Gleam version".into(),
2415 text,
2416 hint: None,
2417 location: None,
2418 level: Level::Error,
2419 }]
2420 }
2421
2422 Error::InvalidRuntime {
2423 target,
2424 invalid_runtime,
2425 } => {
2426 let text = format!(
2427 "Invalid runtime for {target} target: {invalid_runtime}",
2428 target = target.as_presentable_str(),
2429 invalid_runtime = invalid_runtime.as_presentable_str(),
2430 );
2431
2432 let hint = match target {
2433 Target::JavaScript => {
2434 Some("available runtimes for JavaScript are: node, deno.".into())
2435 }
2436 Target::Erlang => Some(
2437 "You can not set a runtime for Erlang. Did you mean to target JavaScript?"
2438 .into(),
2439 ),
2440 };
2441
2442 vec![Diagnostic {
2443 title: format!(
2444 "Invalid runtime for {target}",
2445 target = target.as_presentable_str(),
2446 ),
2447 text,
2448 hint,
2449 location: None,
2450 level: Level::Error,
2451 }]
2452 }
2453
2454 Error::JavaScriptPreludeRequired => vec![Diagnostic {
2455 title: "JavaScript prelude required".into(),
2456 text: "The --javascript-prelude flag must be given when compiling to JavaScript."
2457 .into(),
2458 level: Level::Error,
2459 location: None,
2460 hint: None,
2461 }],
2462 Error::CorruptManifest => vec![Diagnostic {
2463 title: "Corrupt manifest.toml".into(),
2464 text: "The `manifest.toml` file is corrupt.".into(),
2465 level: Level::Error,
2466 location: None,
2467 hint: Some("Please run `gleam update` to fix it.".into()),
2468 }],
2469
2470 Error::GleamModuleWouldOverwriteStandardErlangModule { name, path } => {
2471 vec![Diagnostic {
2472 title: "Erlang module name collision".into(),
2473 text: wrap_format!(
2474 "The module `{path}` compiles to an Erlang module \
2475named `{name}`.
2476
2477By default Erlang includes a module with the same name so if we were \
2478to compile and load your module it would overwrite the Erlang one, potentially \
2479causing confusing errors and crashes."
2480 ),
2481 level: Level::Error,
2482 location: None,
2483 hint: Some("Rename this module and try again.".into()),
2484 }]
2485 }
2486
2487 Error::HexPublishReplaceRequired { version } => vec![Diagnostic {
2488 title: "Version already published".into(),
2489 text: wrap_format!(
2490 "Version v{version} has already been published.
2491This release has been recently published so you can replace it \
2492or you can publish it using a different version number"
2493 ),
2494 level: Level::Error,
2495 location: None,
2496 hint: Some(
2497 "Please add the --replace flag if you want to replace the release.".into(),
2498 ),
2499 }],
2500 Error::HexPublishAccessDenied { name, version } => vec![Diagnostic {
2501 title: "Access denied".to_string(),
2502 text: wrap_format!(
2503 "You are not one of the maintainers of the {name} package, so \
2504you cannot publish a new {version} version. Are you logged into the correct account?
2505
2506If you are trying to publish a new package then you will need to pick another, \
2507as this one is already in use.
2508"
2509 ),
2510 level: Level::Error,
2511 location: None,
2512 hint: None,
2513 }],
2514
2515 Error::CannotAddSelfAsDependency { name } => vec![Diagnostic {
2516 title: "Dependency cycle".into(),
2517 text: wrap_format!(
2518 "A package cannot depend on itself, so you cannot \
2519add `gleam add {name}` in this project."
2520 ),
2521 level: Level::Error,
2522 location: None,
2523 hint: None,
2524 }],
2525 }
2526 }
2527}
2528
2529fn failed_module_diagnostics(failed_module: &FailedModule) -> impl Iterator<Item = Diagnostic> {
2530 use crate::type_::Error as TypeError;
2531 let FailedModule {
2532 path,
2533 src,
2534 errors,
2535 names,
2536 } = failed_module;
2537
2538 errors.iter().map(|error| match error {
2539 TypeError::LiteralFloatOutOfRange { location, .. } => Diagnostic {
2540 title: "Float outside of valid range".into(),
2541 text: wrap(
2542 "This float value is too large to be represented by \
2543a floating point type: float values must be in the range -1.7976931348623157e308 \
2544- 1.7976931348623157e308.",
2545 ),
2546 hint: None,
2547 level: Level::Error,
2548 location: Some(Location {
2549 label: Label {
2550 text: None,
2551 span: *location,
2552 },
2553 path: path.clone(),
2554 src: src.clone(),
2555 extra_labels: vec![],
2556 }),
2557 },
2558
2559 TypeError::InvalidImport {
2560 location,
2561 importing_module,
2562 imported_module,
2563 kind: InvalidImportKind::SrcImportingTest,
2564 } => {
2565 let text = wrap_format!(
2566 "The application module `{importing_module}` \
2567is importing the test module `{imported_module}`.
2568
2569Test modules are not included in production builds so application \
2570modules cannot import them. Perhaps move the `{imported_module}` \
2571module to the src directory.",
2572 );
2573
2574 Diagnostic {
2575 title: "App importing test module".into(),
2576 text,
2577 hint: None,
2578 level: Level::Error,
2579 location: Some(Location {
2580 label: Label {
2581 text: Some("Imported here".into()),
2582 span: *location,
2583 },
2584 path: path.clone(),
2585 src: src.clone(),
2586 extra_labels: vec![],
2587 }),
2588 }
2589 }
2590
2591 TypeError::InvalidImport {
2592 location,
2593 importing_module,
2594 imported_module,
2595 kind: InvalidImportKind::SrcImportingDev,
2596 } => {
2597 let text = wrap_format!(
2598 "The application module `{importing_module}` \
2599is importing the development module `{imported_module}`.
2600
2601Development modules are not included in production builds so application \
2602modules cannot import them. Perhaps move the `{imported_module}` \
2603module to the src directory.",
2604 );
2605
2606 Diagnostic {
2607 title: "App importing dev module".into(),
2608 text,
2609 hint: None,
2610 level: Level::Error,
2611 location: Some(Location {
2612 label: Label {
2613 text: Some("Imported here".into()),
2614 span: *location,
2615 },
2616 path: path.clone(),
2617 src: src.clone(),
2618 extra_labels: vec![],
2619 }),
2620 }
2621 }
2622
2623 TypeError::InvalidImport {
2624 location,
2625 importing_module,
2626 imported_module,
2627 kind: InvalidImportKind::DevImportingTest,
2628 } => {
2629 let text = wrap_format!(
2630 "The development module `{importing_module}` \
2631is importing the test module `{imported_module}`.
2632
2633Test modules should only contain test-related code, and not general development \
2634code. Perhaps move the `{imported_module}` module to the dev directory.",
2635 );
2636
2637 Diagnostic {
2638 title: "Dev importing test module".into(),
2639 text,
2640 hint: None,
2641 level: Level::Error,
2642 location: Some(Location {
2643 label: Label {
2644 text: Some("Imported here".into()),
2645 span: *location,
2646 },
2647 path: path.clone(),
2648 src: src.clone(),
2649 extra_labels: vec![],
2650 }),
2651 }
2652 }
2653
2654 TypeError::UnknownLabels {
2655 unknown,
2656 valid,
2657 supplied,
2658 } => {
2659 let other_labels: Vec<_> = valid
2660 .iter()
2661 .filter(|label| !supplied.contains(label))
2662 .cloned()
2663 .collect();
2664
2665 let title = if unknown.len() > 1 {
2666 "Unknown labels"
2667 } else {
2668 "Unknown label"
2669 }
2670 .into();
2671
2672 let mut labels = unknown.iter().map(|(label, location)| {
2673 let text =
2674 did_you_mean(label, &other_labels).unwrap_or_else(|| "Unexpected label".into());
2675 Label {
2676 text: Some(text),
2677 span: *location,
2678 }
2679 });
2680 let label = labels.next().expect("Unknown labels first label");
2681 let extra_labels = labels
2682 .map(|label| ExtraLabel {
2683 src_info: None,
2684 label,
2685 })
2686 .collect();
2687 let text = if valid.is_empty() {
2688 "This constructor does not accept any labelled arguments.".into()
2689 } else if other_labels.is_empty() {
2690 "You have already supplied all the labelled arguments that this
2691constructor accepts."
2692 .into()
2693 } else {
2694 let mut label_text = String::from("It accepts these labels:\n");
2695 for label in other_labels.iter().sorted() {
2696 label_text.push_str("\n ");
2697 label_text.push_str(label);
2698 }
2699 label_text
2700 };
2701 Diagnostic {
2702 title,
2703 text,
2704 hint: None,
2705 level: Level::Error,
2706 location: Some(Location {
2707 label,
2708 path: path.clone(),
2709 src: src.clone(),
2710 extra_labels,
2711 }),
2712 }
2713 }
2714
2715 TypeError::UnexpectedLabelledArg {
2716 location,
2717 label,
2718 kind,
2719 } => {
2720 let kind = match kind {
2721 UnexpectedLabelledArgKind::FunctionParameter => "function",
2722 UnexpectedLabelledArgKind::RecordConstructorArgument => "record constructor",
2723 };
2724 let text = format!(
2725 "This argument has been given a label but the {kind} does
2726not expect any. Please remove the label `{label}`."
2727 );
2728 Diagnostic {
2729 title: "Unexpected labelled argument".into(),
2730 text,
2731 hint: None,
2732 level: Level::Error,
2733 location: Some(Location {
2734 label: Label {
2735 text: None,
2736 span: *location,
2737 },
2738 path: path.clone(),
2739 src: src.clone(),
2740 extra_labels: vec![],
2741 }),
2742 }
2743 }
2744
2745 TypeError::PositionalArgumentAfterLabelled { location } => {
2746 let text = wrap(
2747 "This unlabeled argument has been \
2748supplied after a labelled argument.
2749Once a labelled argument has been supplied all following arguments must
2750also be labelled.",
2751 );
2752
2753 Diagnostic {
2754 title: "Unexpected positional argument".into(),
2755 text,
2756 hint: None,
2757 level: Level::Error,
2758 location: Some(Location {
2759 label: Label {
2760 text: None,
2761 span: *location,
2762 },
2763 path: path.clone(),
2764 src: src.clone(),
2765 extra_labels: vec![],
2766 }),
2767 }
2768 }
2769
2770 TypeError::DuplicateImport {
2771 location,
2772 previous_location,
2773 name,
2774 } => {
2775 let text = format!(
2776 "`{name}` has been imported multiple times.
2777Names in a Gleam module must be unique so one will need to be renamed."
2778 );
2779 Diagnostic {
2780 title: "Duplicate import".into(),
2781 text,
2782 hint: None,
2783 level: Level::Error,
2784 location: Some(Location {
2785 label: Label {
2786 text: Some("Reimported here".into()),
2787 span: *location,
2788 },
2789 path: path.clone(),
2790 src: src.clone(),
2791 extra_labels: vec![ExtraLabel {
2792 src_info: None,
2793 label: Label {
2794 text: Some("First imported here".into()),
2795 span: *previous_location,
2796 },
2797 }],
2798 }),
2799 }
2800 }
2801
2802 TypeError::DuplicateName {
2803 location_a,
2804 location_b,
2805 name,
2806 ..
2807 } => {
2808 let (first_location, second_location) = if location_a.start < location_b.start {
2809 (location_a, location_b)
2810 } else {
2811 (location_b, location_a)
2812 };
2813 let text = format!(
2814 "`{name}` has been defined multiple times.
2815Names in a Gleam module must be unique so one will need to be renamed."
2816 );
2817 Diagnostic {
2818 title: "Duplicate definition".into(),
2819 text,
2820 hint: None,
2821 level: Level::Error,
2822 location: Some(Location {
2823 label: Label {
2824 text: Some("Redefined here".into()),
2825 span: *second_location,
2826 },
2827 path: path.clone(),
2828 src: src.clone(),
2829 extra_labels: vec![ExtraLabel {
2830 src_info: None,
2831 label: Label {
2832 text: Some("First defined here".into()),
2833 span: *first_location,
2834 },
2835 }],
2836 }),
2837 }
2838 }
2839
2840 TypeError::DuplicateTypeName {
2841 name,
2842 location,
2843 previous_location,
2844 ..
2845 } => {
2846 let text = format!(
2847 "The type `{name}` has been defined multiple times.
2848Names in a Gleam module must be unique so one will need to be renamed."
2849 );
2850 Diagnostic {
2851 title: "Duplicate type definition".into(),
2852 text,
2853 hint: None,
2854 level: Level::Error,
2855 location: Some(Location {
2856 label: Label {
2857 text: Some("Redefined here".into()),
2858 span: *location,
2859 },
2860 path: path.clone(),
2861 src: src.clone(),
2862 extra_labels: vec![ExtraLabel {
2863 src_info: None,
2864 label: Label {
2865 text: Some("First defined here".into()),
2866 span: *previous_location,
2867 },
2868 }],
2869 }),
2870 }
2871 }
2872
2873 TypeError::DuplicateField { location, label } => {
2874 let text = format!("The label `{label}` has already been defined. Rename this label.");
2875 Diagnostic {
2876 title: "Duplicate label".into(),
2877 text,
2878 hint: None,
2879 level: Level::Error,
2880 location: Some(Location {
2881 label: Label {
2882 text: None,
2883 span: *location,
2884 },
2885 path: path.clone(),
2886 src: src.clone(),
2887 extra_labels: vec![],
2888 }),
2889 }
2890 }
2891
2892 TypeError::DuplicateArgument { location, label } => {
2893 let text = format!("The labelled argument `{label}` has already been supplied.");
2894 Diagnostic {
2895 title: "Duplicate argument".into(),
2896 text,
2897 hint: None,
2898 level: Level::Error,
2899 location: Some(Location {
2900 label: Label {
2901 text: None,
2902 span: *location,
2903 },
2904 path: path.clone(),
2905 src: src.clone(),
2906 extra_labels: vec![],
2907 }),
2908 }
2909 }
2910
2911 TypeError::RecursiveType { location } => {
2912 let text = wrap(
2913 "I don't know how to work out what type this \
2914value has. It seems to be defined in terms of itself.",
2915 );
2916 Diagnostic {
2917 title: "Recursive type".into(),
2918 text,
2919 hint: Some("Add some type annotations and try again.".into()),
2920 level: Level::Error,
2921 location: Some(Location {
2922 label: Label {
2923 text: None,
2924 span: *location,
2925 },
2926 path: path.clone(),
2927 src: src.clone(),
2928 extra_labels: vec![],
2929 }),
2930 }
2931 }
2932
2933 TypeError::NotFn { location, type_ } => {
2934 let mut printer = Printer::new(names);
2935 let text = format!(
2936 "This value is being called as a function but its type is:\n\n {}",
2937 printer.print_type(type_)
2938 );
2939 Diagnostic {
2940 title: "Type mismatch".into(),
2941 text,
2942 hint: None,
2943 level: Level::Error,
2944 location: Some(Location {
2945 label: Label {
2946 text: None,
2947 span: *location,
2948 },
2949 path: path.clone(),
2950 src: src.clone(),
2951 extra_labels: vec![],
2952 }),
2953 }
2954 }
2955
2956 TypeError::UnknownRecordField {
2957 usage,
2958 location,
2959 type_,
2960 label,
2961 fields,
2962 unknown_field: variants,
2963 } => {
2964 let mut printer = Printer::new(names);
2965
2966 // Give a hint about what type this value has.
2967 let mut text = format!(
2968 "The value being accessed has this type:\n\n {}\n",
2969 printer.print_type(type_)
2970 );
2971
2972 // Give a hint about what record fields this value has, if any.
2973 if fields.is_empty() {
2974 if variants == &UnknownField::NoFields {
2975 text.push_str("\nIt does not have any fields.");
2976 } else {
2977 text.push_str(
2978 "\nIt does not have fields that are common \
2979across all variants.",
2980 );
2981 }
2982 } else {
2983 text.push_str("\nIt has these accessible fields:\n");
2984 }
2985 for field in fields.iter().sorted() {
2986 text.push_str("\n .");
2987 text.push_str(field);
2988 }
2989
2990 match variants {
2991 UnknownField::AppearsInAVariant => {
2992 let msg = wrap(
2993 "Note: The field you are trying to access is \
2994not defined consistently across all variants of this custom type. To fix this, \
2995ensure that all variants include the field with the same name, position, and \
2996type.",
2997 );
2998 text.push_str("\n\n");
2999 text.push_str(&msg);
3000 }
3001 UnknownField::AppearsInAnImpossibleVariant => {
3002 let msg = wrap(
3003 "Note: The field exists in this custom type \
3004but is not defined for the current variant. Ensure that you are accessing the \
3005field on a variant where it is valid.",
3006 );
3007 text.push_str("\n\n");
3008 text.push_str(&msg);
3009 }
3010 UnknownField::TrulyUnknown => (),
3011 UnknownField::NoFields => (),
3012 }
3013
3014 // Give a hint about Gleam not having OOP methods if it
3015 // looks like they might be trying to call one.
3016 match usage {
3017 FieldAccessUsage::MethodCall => {
3018 let msg = wrap(
3019 "Gleam is not object oriented, so if you are trying \
3020to call a method on this value you may want to use the function syntax instead.",
3021 );
3022 text.push_str("\n\n");
3023 text.push_str(&msg);
3024 text.push_str("\n\n ");
3025 text.push_str(label);
3026 text.push_str("(value)");
3027 }
3028 FieldAccessUsage::Other | FieldAccessUsage::RecordUpdate => (),
3029 }
3030
3031 let label =
3032 did_you_mean(label, fields).unwrap_or_else(|| "This field does not exist".into());
3033 Diagnostic {
3034 title: "Unknown record field".into(),
3035 text,
3036 hint: None,
3037 level: Level::Error,
3038 location: Some(Location {
3039 label: Label {
3040 text: Some(label),
3041 span: *location,
3042 },
3043 path: path.clone(),
3044 src: src.clone(),
3045 extra_labels: vec![],
3046 }),
3047 }
3048 }
3049
3050 TypeError::CouldNotUnify {
3051 location,
3052 expected,
3053 given,
3054 situation: Some(UnifyErrorSituation::Operator(op)),
3055 } => {
3056 let mut printer = Printer::new(names);
3057 let text = format!(
3058 "The {op} operator expects arguments of this type:
3059
3060 {expected}
3061
3062But this argument has this type:
3063
3064 {given}",
3065 op = op.name(),
3066 expected = printer.print_type(expected),
3067 given = printer.print_type(given),
3068 );
3069 Diagnostic {
3070 title: "Type mismatch".into(),
3071 text,
3072 hint: hint_alternative_operator(op, given),
3073 level: Level::Error,
3074 location: Some(Location {
3075 label: Label {
3076 text: None,
3077 span: *location,
3078 },
3079 path: path.clone(),
3080 src: src.clone(),
3081 extra_labels: vec![],
3082 }),
3083 }
3084 }
3085
3086 TypeError::CouldNotUnify {
3087 location,
3088 expected,
3089 given,
3090 situation: Some(UnifyErrorSituation::PipeTypeMismatch),
3091 } => {
3092 // Remap the pipe function type into just the type expected by the pipe.
3093 let expected = expected
3094 .fn_types()
3095 .and_then(|(arguments, _)| arguments.first().cloned());
3096
3097 // Remap the argument as well, if it's a function.
3098 let given = given
3099 .fn_types()
3100 .and_then(|(arguments, _)| arguments.first().cloned())
3101 .unwrap_or_else(|| given.clone());
3102
3103 let mut printer = Printer::new(names);
3104 let text = format!(
3105 "The argument is:
3106
3107 {given}
3108
3109But function expects:
3110
3111 {expected}",
3112 expected = expected
3113 .map(|v| printer.print_type(&v))
3114 .unwrap_or_else(|| " No arguments".into()),
3115 given = printer.print_type(&given)
3116 );
3117
3118 Diagnostic {
3119 title: "Type mismatch".into(),
3120 text,
3121 hint: None,
3122 level: Level::Error,
3123 location: Some(Location {
3124 label: Label {
3125 text: Some("This function does not accept the piped type".into()),
3126 span: *location,
3127 },
3128 path: path.clone(),
3129 src: src.clone(),
3130 extra_labels: vec![],
3131 }),
3132 }
3133 }
3134
3135 TypeError::CouldNotUnify {
3136 location,
3137 expected,
3138 given,
3139 situation,
3140 } => {
3141 let mut printer = Printer::new(names);
3142 let mut text =
3143 if let Some(description) = situation.as_ref().and_then(|s| s.description()) {
3144 let mut text = description.to_string();
3145 text.push('\n');
3146 text.push('\n');
3147 text
3148 } else {
3149 "".into()
3150 };
3151 text.push_str("Expected type:\n\n ");
3152 text.push_str(&printer.print_type(expected));
3153 text.push_str("\n\nFound type:\n\n ");
3154 text.push_str(&printer.print_type(given));
3155
3156 let (main_message_location, main_message_text, extra_labels) = match situation {
3157 // When the mismatch error comes from a case clause we want to highlight the
3158 // entire branch (pattern included) when reporting the error; in addition,
3159 // if the error could be resolved just by wrapping the value in an `Ok`
3160 // or `Error` we want to add an additional label with this hint below the
3161 // offending value.
3162 Some(UnifyErrorSituation::CaseClauseMismatch { clause_location }) => {
3163 (clause_location, None, vec![])
3164 }
3165 // In all other cases we just highlight the offending expression, optionally
3166 // adding the wrapping hint if it makes sense.
3167 Some(_) | None => (location, hint_wrap_value_in_result(expected, given), vec![]),
3168 };
3169
3170 Diagnostic {
3171 title: "Type mismatch".into(),
3172 text,
3173 hint: None,
3174 level: Level::Error,
3175 location: Some(Location {
3176 label: Label {
3177 text: main_message_text,
3178 span: *main_message_location,
3179 },
3180 path: path.clone(),
3181 src: src.clone(),
3182 extra_labels,
3183 }),
3184 }
3185 }
3186
3187 TypeError::IncorrectTypeArity {
3188 location,
3189 expected,
3190 given: given_number,
3191 name,
3192 } => {
3193 let expected = match expected {
3194 0 => "no type arguments".into(),
3195 1 => "1 type argument".into(),
3196 _ => format!("{expected} type arguments"),
3197 };
3198 let given = match given_number {
3199 0 => "none",
3200 _ => &format!("{given_number}"),
3201 };
3202 let text = wrap_format!(
3203 "`{name}` requires {expected} \
3204but {given} where provided."
3205 );
3206 Diagnostic {
3207 title: "Incorrect arity".into(),
3208 text,
3209 hint: None,
3210 level: Level::Error,
3211 location: Some(Location {
3212 label: Label {
3213 text: Some(format!("Expected {expected}, got {given_number}")),
3214 span: *location,
3215 },
3216 path: path.clone(),
3217 src: src.clone(),
3218 extra_labels: vec![],
3219 }),
3220 }
3221 }
3222
3223 TypeError::TypeUsedAsAConstructor { location, name } => {
3224 let text = wrap_format!(
3225 "`{name}` is a type with no parameters, but here it's \
3226being used as a type constructor."
3227 );
3228
3229 Diagnostic {
3230 title: "Type used as a type constructor".into(),
3231 text,
3232 hint: None,
3233 level: Level::Error,
3234 location: Some(Location {
3235 label: Label {
3236 text: Some("You can remove this".into()),
3237 span: *location,
3238 },
3239 path: path.clone(),
3240 src: src.clone(),
3241 extra_labels: vec![],
3242 }),
3243 }
3244 }
3245
3246 TypeError::IncorrectArity {
3247 labels,
3248 location,
3249 context,
3250 expected,
3251 given,
3252 } => {
3253 let text = if labels.is_empty() {
3254 "".into()
3255 } else {
3256 let subject = match context {
3257 IncorrectArityContext::Pattern => "pattern",
3258 IncorrectArityContext::Function => "call",
3259 };
3260 let labels = labels
3261 .iter()
3262 .map(|p| format!(" - {p}"))
3263 .sorted()
3264 .join("\n");
3265 format!(
3266 "This {subject} accepts these additional labelled \
3267 arguments:\n\n{labels}",
3268 )
3269 };
3270 let expected = match expected {
3271 0 => "no arguments".into(),
3272 1 => "1 argument".into(),
3273 _ => format!("{expected} arguments"),
3274 };
3275 let label = format!("Expected {expected}, got {given}");
3276 Diagnostic {
3277 title: "Incorrect arity".into(),
3278 text,
3279 hint: None,
3280 level: Level::Error,
3281 location: Some(Location {
3282 label: Label {
3283 text: Some(label),
3284 span: *location,
3285 },
3286 path: path.clone(),
3287 src: src.clone(),
3288 extra_labels: vec![],
3289 }),
3290 }
3291 }
3292
3293 TypeError::UnnecessarySpreadOperator { location, arity } => {
3294 let text = wrap_format!(
3295 "This record has {arity} fields and you have already \
3296assigned variables to all of them."
3297 );
3298 Diagnostic {
3299 title: "Unnecessary spread operator".into(),
3300 text,
3301 hint: None,
3302 level: Level::Error,
3303 location: Some(Location {
3304 label: Label {
3305 text: None,
3306 span: *location,
3307 },
3308 path: path.clone(),
3309 src: src.clone(),
3310 extra_labels: vec![],
3311 }),
3312 }
3313 }
3314
3315 TypeError::UnsafeRecordUpdate { location, reason } => match reason {
3316 UnsafeRecordUpdateReason::UnknownVariant {
3317 constructed_variant,
3318 } => {
3319 let text = wrap_format!(
3320 "This value cannot be used to build an updated \
3321`{constructed_variant}` as it could be some other variant.
3322
3323Consider pattern matching on it with a case expression and then \
3324constructing a new record with its values."
3325 );
3326
3327 Diagnostic {
3328 title: "Unsafe record update".into(),
3329 text,
3330 hint: None,
3331 level: Level::Error,
3332 location: Some(Location {
3333 label: Label {
3334 text: Some(format!(
3335 "I'm not sure this is always a `{constructed_variant}`"
3336 )),
3337 span: *location,
3338 },
3339 path: path.clone(),
3340 src: src.clone(),
3341 extra_labels: vec![],
3342 }),
3343 }
3344 }
3345 UnsafeRecordUpdateReason::WrongVariant {
3346 constructed_variant,
3347 spread_variant,
3348 } => {
3349 let text = wrap_format!(
3350 "This value is a `{spread_variant}` so \
3351it cannot be used to build a `{constructed_variant}`, even if they share some fields.
3352
3353Note: If you want to change one variant of a type into another, you should \
3354specify all fields explicitly instead of using the record update syntax."
3355 );
3356
3357 Diagnostic {
3358 title: "Incorrect record update".into(),
3359 text,
3360 hint: None,
3361 level: Level::Error,
3362 location: Some(Location {
3363 label: Label {
3364 text: Some(format!("This is a `{spread_variant}`")),
3365 span: *location,
3366 },
3367 path: path.clone(),
3368 src: src.clone(),
3369 extra_labels: vec![],
3370 }),
3371 }
3372 }
3373 UnsafeRecordUpdateReason::IncompatibleFieldTypes {
3374 expected_field_type,
3375 record_field_type,
3376 record_variant,
3377 field,
3378 ..
3379 } => {
3380 let mut printer = Printer::new(names);
3381 let expected_field_type = printer.print_type(expected_field_type);
3382 let record_field_type = printer.print_type(record_field_type);
3383 let record_variant = printer.print_type(record_variant);
3384 let text = match field {
3385 RecordField::Labelled(label) => wrap_format!(
3386 "The `{label}` field \
3387of this value is a `{record_field_type}`, but the arguments given to the record \
3388update indicate that it should be a `{expected_field_type}`.
3389
3390Note: If the same type variable is used for multiple fields, all those fields \
3391need to be updated at the same time if their type changes."
3392 ),
3393 RecordField::Unlabelled(index) => wrap_format!(
3394 "The {} field \
3395of this value is a `{record_field_type}`, but the arguments given to the record \
3396update indicate that it should be a `{expected_field_type}`.
3397
3398Note: Unlabelled fields cannot be updated in a record update, so either add \
3399a label or use a record constructor.",
3400 to_ordinal(*index + 1),
3401 ),
3402 };
3403
3404 Diagnostic {
3405 title: "Incomplete record update".into(),
3406 text,
3407 hint: None,
3408 level: Level::Error,
3409 location: Some(Location {
3410 label: Label {
3411 text: Some(format!("This is a `{record_variant}`")),
3412 span: *location,
3413 },
3414 path: path.clone(),
3415 src: src.clone(),
3416 extra_labels: vec![],
3417 }),
3418 }
3419 }
3420 },
3421
3422 TypeError::QualifiedTypeMissingName { location } => Diagnostic {
3423 title: "Invalid type".into(),
3424 text: "".into(),
3425 hint: None,
3426 level: Level::Error,
3427 location: Some(Location {
3428 label: Label {
3429 text: Some("This is not a valid type".into()),
3430 span: *location,
3431 },
3432 path: path.clone(),
3433 src: src.clone(),
3434 extra_labels: vec![],
3435 }),
3436 },
3437
3438 TypeError::UnknownType {
3439 location,
3440 name,
3441 hint,
3442 ..
3443 } => {
3444 let label_text = match hint {
3445 UnknownTypeHint::AlternativeTypes(types) => did_you_mean(name, types),
3446 UnknownTypeHint::ValueInScopeWithSameName => None,
3447 };
3448
3449 let mut text =
3450 wrap_format!("The type `{name}` is not defined or imported in this module.");
3451
3452 match hint {
3453 UnknownTypeHint::ValueInScopeWithSameName => {
3454 let hint = wrap_format!(
3455 "There is a value in scope with the name `{name}`, \
3456but no type in scope with that name."
3457 );
3458 text.push('\n');
3459 text.push_str(hint.as_str());
3460 }
3461 UnknownTypeHint::AlternativeTypes(_) => {}
3462 };
3463
3464 Diagnostic {
3465 title: "Unknown type".into(),
3466 text,
3467 hint: None,
3468 level: Level::Error,
3469 location: Some(Location {
3470 label: Label {
3471 text: label_text,
3472 span: *location,
3473 },
3474 path: path.clone(),
3475 src: src.clone(),
3476 extra_labels: vec![],
3477 }),
3478 }
3479 }
3480
3481 TypeError::UnknownVariable {
3482 location,
3483 variables,
3484 discarded_location,
3485 name,
3486 type_with_name_in_scope,
3487 possible_modules,
3488 } => {
3489 let title = String::from("Unknown variable");
3490
3491 if let Some(ignored_location) = discarded_location {
3492 let location = Location {
3493 label: Label {
3494 text: Some("So this is not in scope".into()),
3495 span: *location,
3496 },
3497 path: path.clone(),
3498 src: src.clone(),
3499 extra_labels: vec![ExtraLabel {
3500 src_info: None,
3501 label: Label {
3502 text: Some("This value is discarded".into()),
3503 span: *ignored_location,
3504 },
3505 }],
3506 };
3507 Diagnostic {
3508 title,
3509 text: "".into(),
3510 hint: Some(wrap_format!(
3511 "Change `_{name}` to `{name}` or reference another variable",
3512 )),
3513 level: Level::Error,
3514 location: Some(location),
3515 }
3516 } else {
3517 let text = if *type_with_name_in_scope {
3518 wrap_format!("`{name}` is a type, it cannot be used as a value.")
3519 } else {
3520 let mut text = if name.starts_with(char::is_uppercase) {
3521 wrap_format!(
3522 "The custom type variant constructor `{name}` is not in scope here."
3523 )
3524 } else {
3525 wrap_format!("The name `{name}` is not in scope here.")
3526 };
3527
3528 // If there are some suggestions about public values in imported
3529 // modules put a "did you mean" text after the main message
3530 if !possible_modules.is_empty() {
3531 text.push_str("\nDid you mean one of these:\n\n");
3532 for module_name in possible_modules {
3533 text.push_str(&format!(" - {module_name}.{name}\n"))
3534 }
3535 }
3536
3537 text
3538 };
3539
3540 Diagnostic {
3541 title,
3542 text,
3543 hint: None,
3544 level: Level::Error,
3545 location: Some(Location {
3546 label: Label {
3547 text: did_you_mean(name, variables),
3548 span: *location,
3549 },
3550 path: path.clone(),
3551 src: src.clone(),
3552 extra_labels: vec![],
3553 }),
3554 }
3555 }
3556 }
3557
3558 TypeError::PrivateTypeLeak { location, leaked } => {
3559 let mut printer = Printer::new(names);
3560
3561 // TODO: be more precise.
3562 // - is being returned by this public function
3563 // - is taken as an argument by this public function
3564 // - is taken as an argument by this public enum constructor
3565 // etc
3566 let text = wrap_format!(
3567 "The following type is private, but is \
3568being used by this public export.
3569
3570 {}
3571
3572Private types can only be used within the module that defines them.",
3573 printer.print_type(leaked),
3574 );
3575 Diagnostic {
3576 title: "Private type used in public interface".into(),
3577 text,
3578 hint: None,
3579 level: Level::Error,
3580 location: Some(Location {
3581 label: Label {
3582 text: None,
3583 span: *location,
3584 },
3585 path: path.clone(),
3586 src: src.clone(),
3587 extra_labels: vec![],
3588 }),
3589 }
3590 }
3591
3592 TypeError::UnknownModule {
3593 location,
3594 name,
3595 suggestions,
3596 } => Diagnostic {
3597 title: "Unknown module".into(),
3598 text: format!("No module has been found with the name `{name}`."),
3599 hint: suggestions
3600 .first()
3601 .map(|suggestion| suggestion.suggestion(name)),
3602 level: Level::Error,
3603 location: Some(Location {
3604 label: Label {
3605 text: None,
3606 span: *location,
3607 },
3608 path: path.clone(),
3609 src: src.clone(),
3610 extra_labels: vec![],
3611 }),
3612 },
3613
3614 TypeError::UnknownModuleType {
3615 location,
3616 name,
3617 module_name,
3618 type_constructors,
3619 value_with_same_name: imported_type_as_value,
3620 } => {
3621 let text = if *imported_type_as_value {
3622 format!("`{name}` is only a value, it cannot be imported as a type.")
3623 } else {
3624 format!("The module `{module_name}` does not have a `{name}` type.")
3625 };
3626 Diagnostic {
3627 title: "Unknown module type".into(),
3628 text,
3629 hint: None,
3630 level: Level::Error,
3631 location: Some(Location {
3632 label: Label {
3633 text: if *imported_type_as_value {
3634 Some(format!("Did you mean `{name}`?"))
3635 } else {
3636 did_you_mean(name, type_constructors)
3637 },
3638 span: *location,
3639 },
3640 path: path.clone(),
3641 src: src.clone(),
3642 extra_labels: vec![],
3643 }),
3644 }
3645 }
3646
3647 TypeError::UnknownModuleValue {
3648 location,
3649 name,
3650 module_name,
3651 value_constructors,
3652 type_with_same_name: imported_value_as_type,
3653 context,
3654 } => {
3655 let text = if *imported_value_as_type {
3656 match context {
3657 ModuleValueUsageContext::UnqualifiedImport => {
3658 wrap_format!("`{name}` is only a type, it cannot be imported as a value.")
3659 }
3660 ModuleValueUsageContext::ModuleAccess => wrap_format!(
3661 "{module_name}.{name} is a type constructor, \
3662it cannot be used as a value"
3663 ),
3664 }
3665 } else {
3666 wrap_format!("The module `{module_name}` does not have a `{name}` value.")
3667 };
3668 Diagnostic {
3669 title: "Unknown module value".into(),
3670 text,
3671 hint: None,
3672 level: Level::Error,
3673 location: Some(Location {
3674 label: Label {
3675 text: if *imported_value_as_type
3676 && matches!(context, ModuleValueUsageContext::UnqualifiedImport)
3677 {
3678 Some(format!("Did you mean `type {name}`?"))
3679 } else {
3680 did_you_mean(name, value_constructors)
3681 },
3682 span: *location,
3683 },
3684 path: path.clone(),
3685 src: src.clone(),
3686 extra_labels: vec![],
3687 }),
3688 }
3689 }
3690
3691 TypeError::ModuleAliasUsedAsName { location, name } => {
3692 let text = wrap(
3693 "Modules are not values, so you cannot assign them \
3694to variables, pass them to functions, or anything else that you would do with a value.",
3695 );
3696 Diagnostic {
3697 title: format!("Module `{name}` used as a value"),
3698 text,
3699 hint: None,
3700 level: Level::Error,
3701 location: Some(Location {
3702 label: Label {
3703 text: None,
3704 span: *location,
3705 },
3706 path: path.clone(),
3707 src: src.clone(),
3708 extra_labels: vec![],
3709 }),
3710 }
3711 }
3712
3713 TypeError::IncorrectNumClausePatterns {
3714 location,
3715 expected,
3716 given,
3717 } => {
3718 let subject = if *expected == 1 {
3719 "subject"
3720 } else {
3721 "subjects"
3722 };
3723 let pattern = if *expected == 1 {
3724 "pattern"
3725 } else {
3726 "patterns"
3727 };
3728 let text = wrap_format!(
3729 "This case expression has {expected} {subject}, \
3730but this pattern matches {given}.
3731Each clause must have a pattern for every subject value.",
3732 );
3733 Diagnostic {
3734 title: "Incorrect number of patterns".into(),
3735 text,
3736 hint: None,
3737 level: Level::Error,
3738 location: Some(Location {
3739 label: Label {
3740 text: Some(format!("Expected {expected} {pattern}, got {given}")),
3741 span: *location,
3742 },
3743 path: path.clone(),
3744 src: src.clone(),
3745 extra_labels: vec![],
3746 }),
3747 }
3748 }
3749
3750 TypeError::NonLocalClauseGuardVariable { location, name } => {
3751 let text = wrap_format!(
3752 "Variables used in guards must be either defined in the \
3753function, or be an argument to the function. The variable \
3754`{name}` is not defined locally.",
3755 );
3756 Diagnostic {
3757 title: "Invalid guard variable".into(),
3758 text,
3759 hint: None,
3760 level: Level::Error,
3761 location: Some(Location {
3762 label: Label {
3763 text: Some("Is not locally defined".into()),
3764 span: *location,
3765 },
3766 path: path.clone(),
3767 src: src.clone(),
3768 extra_labels: vec![],
3769 }),
3770 }
3771 }
3772
3773 TypeError::ExtraVarInAlternativePattern { location, name } => {
3774 let text = wrap_format!(
3775 "All alternative patterns must define the same variables as \
3776the initial pattern. This variable `{name}` has not been previously defined.",
3777 );
3778 Diagnostic {
3779 title: "Extra alternative pattern variable".into(),
3780 text,
3781 hint: None,
3782 level: Level::Error,
3783 location: Some(Location {
3784 label: Label {
3785 text: Some("Has not been previously defined".into()),
3786 span: *location,
3787 },
3788 path: path.clone(),
3789 src: src.clone(),
3790 extra_labels: vec![],
3791 }),
3792 }
3793 }
3794
3795 TypeError::MissingVarInAlternativePattern { location, name } => {
3796 let text = wrap_format!(
3797 "All alternative patterns must define the same variables \
3798as the initial pattern, but the `{name}` variable is missing.",
3799 );
3800 Diagnostic {
3801 title: "Missing alternative pattern variable".into(),
3802 text,
3803 hint: None,
3804 level: Level::Error,
3805 location: Some(Location {
3806 label: Label {
3807 text: Some("This does not define all required variables".into()),
3808 span: *location,
3809 },
3810 path: path.clone(),
3811 src: src.clone(),
3812 extra_labels: vec![],
3813 }),
3814 }
3815 }
3816
3817 TypeError::DuplicateVarInPattern { location, name } => {
3818 let text = wrap_format!(
3819 "Variables can only be used once per pattern. This \
3820variable `{name}` appears multiple times.
3821If you used the same variable twice deliberately in order to check for equality \
3822please use a guard clause instead.
3823e.g. (x, y) if x == y -> ...",
3824 );
3825 Diagnostic {
3826 title: "Duplicate variable in pattern".into(),
3827 text,
3828 hint: None,
3829 level: Level::Error,
3830 location: Some(Location {
3831 label: Label {
3832 text: Some("This has already been used".into()),
3833 span: *location,
3834 },
3835 path: path.clone(),
3836 src: src.clone(),
3837 extra_labels: vec![],
3838 }),
3839 }
3840 }
3841
3842 TypeError::OutOfBoundsTupleIndex {
3843 location, size: 0, ..
3844 } => Diagnostic {
3845 title: "Out of bounds tuple index".into(),
3846 text: "This tuple has no elements so it cannot be indexed at all.".into(),
3847 hint: None,
3848 level: Level::Error,
3849 location: Some(Location {
3850 label: Label {
3851 text: None,
3852 span: *location,
3853 },
3854 path: path.clone(),
3855 src: src.clone(),
3856 extra_labels: vec![],
3857 }),
3858 },
3859
3860 TypeError::OutOfBoundsTupleIndex {
3861 location,
3862 index,
3863 size,
3864 } => {
3865 let text = wrap_format!(
3866 "The index being accessed for this tuple is {}, but this \
3867tuple has {} elements so the highest valid index is {}.",
3868 index,
3869 size,
3870 size - 1,
3871 );
3872 Diagnostic {
3873 title: "Out of bounds tuple index".into(),
3874 text,
3875 hint: None,
3876 level: Level::Error,
3877 location: Some(Location {
3878 label: Label {
3879 text: Some("This index is too large".into()),
3880 span: *location,
3881 },
3882 path: path.clone(),
3883 src: src.clone(),
3884 extra_labels: vec![],
3885 }),
3886 }
3887 }
3888
3889 TypeError::NotATuple { location, given } => {
3890 let mut printer = Printer::new(names);
3891 let text = format!(
3892 "To index into this value it needs to be a tuple, \
3893however it has this type:
3894
3895 {}",
3896 printer.print_type(given),
3897 );
3898 Diagnostic {
3899 title: "Type mismatch".into(),
3900 text,
3901 hint: None,
3902 level: Level::Error,
3903 location: Some(Location {
3904 label: Label {
3905 text: Some("This is not a tuple".into()),
3906 span: *location,
3907 },
3908 path: path.clone(),
3909 src: src.clone(),
3910 extra_labels: vec![],
3911 }),
3912 }
3913 }
3914
3915 TypeError::NotATupleUnbound { location } => {
3916 let text = wrap(
3917 "To index into a tuple we need to \
3918know its size, but we don't know anything about this type yet. \
3919Please add some type annotations so we can continue.",
3920 );
3921 Diagnostic {
3922 title: "Type mismatch".into(),
3923 text,
3924 hint: None,
3925 level: Level::Error,
3926 location: Some(Location {
3927 label: Label {
3928 text: Some("What type is this?".into()),
3929 span: *location,
3930 },
3931 path: path.clone(),
3932 src: src.clone(),
3933 extra_labels: vec![],
3934 }),
3935 }
3936 }
3937
3938 TypeError::RecordAccessUnknownType { location } => {
3939 let text = wrap(
3940 "In order to access a record field \
3941we need to know what type it is, but I can't tell \
3942the type here. Try adding type annotations to your \
3943function and try again.",
3944 );
3945 Diagnostic {
3946 title: "Unknown type for record access".into(),
3947 text,
3948 hint: None,
3949 level: Level::Error,
3950 location: Some(Location {
3951 label: Label {
3952 text: Some("I don't know what type this is".into()),
3953 span: *location,
3954 },
3955 path: path.clone(),
3956 src: src.clone(),
3957 extra_labels: vec![],
3958 }),
3959 }
3960 }
3961
3962 TypeError::BitArraySegmentError { error, location } => {
3963 let (label, mut extra) = match error {
3964 bit_array::ErrorType::ConflictingTypeOptions { existing_type } => (
3965 "This is an extra type specifier",
3966 vec![format!(
3967 "Hint: This segment already has the type {existing_type}."
3968 )],
3969 ),
3970
3971 bit_array::ErrorType::ConflictingSignednessOptions { existing_signed } => (
3972 "This is an extra signedness specifier",
3973 vec![format!(
3974 "Hint: This segment already has a \
3975signedness of {existing_signed}."
3976 )],
3977 ),
3978
3979 bit_array::ErrorType::ConflictingEndiannessOptions {
3980 existing_endianness,
3981 } => (
3982 "This is an extra endianness specifier",
3983 vec![format!(
3984 "Hint: This segment already has an \
3985endianness of {existing_endianness}."
3986 )],
3987 ),
3988
3989 bit_array::ErrorType::ConflictingSizeOptions => (
3990 "This is an extra size specifier",
3991 vec!["Hint: This segment already has a size.".into()],
3992 ),
3993
3994 bit_array::ErrorType::ConflictingUnitOptions => (
3995 "This is an extra unit specifier",
3996 vec!["Hint: A BitArray segment can have at most 1 unit.".into()],
3997 ),
3998
3999 bit_array::ErrorType::FloatWithSize => (
4000 "Invalid float size",
4001 vec!["Hint: floats have an exact size of 16/32/64 bits.".into()],
4002 ),
4003
4004 bit_array::ErrorType::InvalidEndianness => (
4005 "This option is invalid here",
4006 vec![wrap(
4007 "Hint: signed and unsigned \
4008can only be used with int, float, utf16 and utf32 types.",
4009 )],
4010 ),
4011
4012 bit_array::ErrorType::OptionNotAllowedInValue => (
4013 "This option is only allowed in BitArray patterns",
4014 vec!["Hint: This option has no effect in BitArray values.".into()],
4015 ),
4016
4017 bit_array::ErrorType::SignednessUsedOnNonInt { type_ } => (
4018 "Signedness is only valid with int types",
4019 vec![format!("Hint: This segment has a type of {type_}")],
4020 ),
4021 bit_array::ErrorType::TypeDoesNotAllowSize { type_ } => (
4022 "Size cannot be specified here",
4023 vec![format!("Hint: {type_} segments have an automatic size.")],
4024 ),
4025 bit_array::ErrorType::TypeDoesNotAllowUnit { type_ } => (
4026 "Unit cannot be specified here",
4027 vec![wrap(&format!(
4028 "Hint: {type_} segments \
4029are sized based on their value and cannot have a unit."
4030 ))],
4031 ),
4032 bit_array::ErrorType::VariableUtfSegmentInPattern => (
4033 "This cannot be a variable",
4034 vec![wrap(
4035 "Hint: in patterns utf8, utf16, and \
4036utf32 must be an exact string.",
4037 )],
4038 ),
4039 bit_array::ErrorType::SegmentMustHaveSize => (
4040 "This segment has no size",
4041 vec![wrap(
4042 "Hint: Bit array segments without \
4043a size are only allowed at the end of a bin pattern.",
4044 )],
4045 ),
4046 bit_array::ErrorType::UnitMustHaveSize => (
4047 "This needs an explicit size",
4048 vec!["Hint: If you specify unit() you must also specify size().".into()],
4049 ),
4050 bit_array::ErrorType::ConstantSizeNotPositive => {
4051 ("A constant size must be a positive number", vec![])
4052 }
4053 bit_array::ErrorType::OptionNotSupportedForTarget {
4054 target,
4055 option: UnsupportedOption::NativeEndianness,
4056 } => (
4057 "Unsupported endianness",
4058 vec![wrap_format!(
4059 "The {target} target does not support the `native` \
4060endianness option.",
4061 target = target.as_presentable_str(),
4062 )],
4063 ),
4064 bit_array::ErrorType::OptionNotSupportedForTarget {
4065 target,
4066 option: UnsupportedOption::UtfCodepointPattern,
4067 } => (
4068 "UTF-codepoint pattern matching is not supported",
4069 vec![wrap_format!(
4070 "The {target} target does not support \
4071UTF-codepoint pattern matching.",
4072 target = target.as_presentable_str(),
4073 )],
4074 ),
4075 };
4076 extra.push("See: https://tour.gleam.run/data-types/bit-arrays/".into());
4077 let text = extra.join("\n");
4078 Diagnostic {
4079 title: "Invalid bit array segment".into(),
4080 text,
4081 hint: None,
4082 level: Level::Error,
4083 location: Some(Location {
4084 label: Label {
4085 text: Some(label.into()),
4086 span: *location,
4087 },
4088 path: path.clone(),
4089 src: src.clone(),
4090 extra_labels: vec![],
4091 }),
4092 }
4093 }
4094 TypeError::RecordUpdateInvalidConstructor { location } => Diagnostic {
4095 title: "Invalid record constructor".into(),
4096 text: "Only record constructors can be used with the update syntax.".into(),
4097 hint: None,
4098 level: Level::Error,
4099 location: Some(Location {
4100 label: Label {
4101 text: Some("This is not a record constructor".into()),
4102 span: *location,
4103 },
4104 path: path.clone(),
4105 src: src.clone(),
4106 extra_labels: vec![],
4107 }),
4108 },
4109
4110 TypeError::RecordUpdateVariantWithNoFields { location } => Diagnostic {
4111 title: "Invalid record constructor".into(),
4112 text: wrap(
4113 "Only constructors with at least one labelled \
4114field can be used with the update syntax.",
4115 ),
4116 hint: None,
4117 level: Level::Error,
4118 location: Some(Location {
4119 label: Label {
4120 text: Some("This constructor has no labelled fields".into()),
4121 span: *location,
4122 },
4123 path: path.clone(),
4124 src: src.clone(),
4125 extra_labels: vec![],
4126 }),
4127 },
4128
4129 TypeError::UnexpectedTypeHole { location } => Diagnostic {
4130 title: "Unexpected type hole".into(),
4131 text: "We need to know the exact type here so type holes cannot be used.".into(),
4132 hint: None,
4133 level: Level::Error,
4134 location: Some(Location {
4135 label: Label {
4136 text: Some("I need to know what this is".into()),
4137 span: *location,
4138 },
4139 path: path.clone(),
4140 src: src.clone(),
4141 extra_labels: vec![],
4142 }),
4143 },
4144
4145 TypeError::ReservedModuleName { name } => {
4146 let text = format!(
4147 "The module name `{name}` is reserved.
4148Try a different name for this module."
4149 );
4150 Diagnostic {
4151 title: "Reserved module name".into(),
4152 text,
4153 hint: None,
4154 location: None,
4155 level: Level::Error,
4156 }
4157 }
4158
4159 TypeError::KeywordInModuleName { name, keyword } => {
4160 let text = wrap_format!(
4161 "The module name `{name}` contains the keyword `{keyword}`, \
4162so importing it would be a syntax error.
4163Try a different name for this module."
4164 );
4165 Diagnostic {
4166 title: "Invalid module name".into(),
4167 text,
4168 hint: None,
4169 location: None,
4170 level: Level::Error,
4171 }
4172 }
4173
4174 TypeError::NotExhaustivePatternMatch {
4175 location,
4176 unmatched,
4177 kind,
4178 } => {
4179 let mut text = match kind {
4180 PatternMatchKind::Case => {
4181 "This case expression does not match all possibilities.
4182Each constructor must have a pattern that matches it or
4183else it could crash."
4184 }
4185 PatternMatchKind::Assignment => {
4186 "This assignment does not match all possibilities.
4187Either use a case expression with patterns for each possible
4188value, or use `let assert` rather than `let`."
4189 }
4190 }
4191 .to_string();
4192
4193 text.push_str("\n\nThese values are not matched:\n\n");
4194 for unmatched in unmatched {
4195 text.push_str(" - ");
4196 text.push_str(unmatched);
4197 text.push('\n');
4198 }
4199 Diagnostic {
4200 title: "Not exhaustive pattern match".into(),
4201 text,
4202 hint: None,
4203 level: Level::Error,
4204 location: Some(Location {
4205 label: Label {
4206 text: None,
4207 span: *location,
4208 },
4209 path: path.clone(),
4210 src: src.clone(),
4211 extra_labels: vec![],
4212 }),
4213 }
4214 }
4215
4216 TypeError::ArgumentNameAlreadyUsed { location, name } => Diagnostic {
4217 title: "Argument name already used".into(),
4218 text: format!("Two `{name}` arguments have been defined for this function."),
4219 hint: None,
4220 level: Level::Error,
4221 location: Some(Location {
4222 label: Label {
4223 text: None,
4224 span: *location,
4225 },
4226 path: path.clone(),
4227 src: src.clone(),
4228 extra_labels: vec![],
4229 }),
4230 },
4231
4232 TypeError::UnlabelledAfterlabelled { location } => Diagnostic {
4233 title: "Unlabelled argument after labelled argument".into(),
4234 text: wrap("All unlabelled arguments must come before any labelled arguments."),
4235 hint: None,
4236 level: Level::Error,
4237 location: Some(Location {
4238 label: Label {
4239 text: None,
4240 span: *location,
4241 },
4242 path: path.clone(),
4243 src: src.clone(),
4244 extra_labels: vec![],
4245 }),
4246 },
4247
4248 TypeError::RecursiveTypeAlias { location, cycle } => {
4249 let mut text = "This type alias is defined in terms of itself.\n".into();
4250 write_cycle(&mut text, cycle);
4251 text.push_str(
4252 "If we tried to compile this recursive type it would expand
4253forever in a loop, and we'd never get the final type.",
4254 );
4255 Diagnostic {
4256 title: "Type cycle".into(),
4257 text,
4258 hint: None,
4259 level: Level::Error,
4260 location: Some(Location {
4261 label: Label {
4262 text: None,
4263 span: *location,
4264 },
4265 path: path.clone(),
4266 src: src.clone(),
4267 extra_labels: vec![],
4268 }),
4269 }
4270 }
4271
4272 TypeError::ExternalMissingAnnotation { location, kind } => {
4273 let kind = match kind {
4274 MissingAnnotation::Parameter => "parameter",
4275 MissingAnnotation::Return => "return",
4276 };
4277 let text = format!(
4278 "A {kind} annotation is missing from this function.
4279
4280Functions with external implementations must have type annotations
4281so we can tell what type of values they accept and return.",
4282 );
4283 Diagnostic {
4284 title: "Missing type annotation".into(),
4285 text,
4286 hint: None,
4287 level: Level::Error,
4288 location: Some(Location {
4289 label: Label {
4290 text: None,
4291 span: *location,
4292 },
4293 path: path.clone(),
4294 src: src.clone(),
4295 extra_labels: vec![],
4296 }),
4297 }
4298 }
4299
4300 TypeError::NoImplementation { location } => {
4301 let text = "We can't compile this function as it doesn't have an
4302implementation. Add a body or an external implementation
4303using the `@external` attribute."
4304 .into();
4305 Diagnostic {
4306 title: "Function without an implementation".into(),
4307 text,
4308 hint: None,
4309 level: Level::Error,
4310 location: Some(Location {
4311 label: Label {
4312 text: None,
4313 span: *location,
4314 },
4315 path: path.clone(),
4316 src: src.clone(),
4317 extra_labels: vec![],
4318 }),
4319 }
4320 }
4321
4322 TypeError::InvalidExternalJavascriptModule {
4323 location,
4324 name,
4325 module,
4326 } => {
4327 let text = wrap_format!(
4328 "The function `{name}` has an external JavaScript \
4329implementation but the module path `{module}` is not valid."
4330 );
4331 Diagnostic {
4332 title: "Invalid JavaScript module".into(),
4333 text,
4334 hint: None,
4335 level: Level::Error,
4336 location: Some(Location {
4337 label: Label {
4338 text: None,
4339 span: *location,
4340 },
4341 path: path.clone(),
4342 src: src.clone(),
4343 extra_labels: vec![],
4344 }),
4345 }
4346 }
4347
4348 TypeError::InvalidExternalJavascriptFunction {
4349 location,
4350 name,
4351 function,
4352 } => {
4353 let text = wrap_format!(
4354 "The function `{name}` has an external JavaScript \
4355implementation but the function name `{function}` is not valid."
4356 );
4357 Diagnostic {
4358 title: "Invalid JavaScript function".into(),
4359 text,
4360 hint: None,
4361 level: Level::Error,
4362 location: Some(Location {
4363 label: Label {
4364 text: None,
4365 span: *location,
4366 },
4367 path: path.clone(),
4368 src: src.clone(),
4369 extra_labels: vec![],
4370 }),
4371 }
4372 }
4373
4374 TypeError::InexhaustiveLetAssignment { location, missing } => {
4375 let mut text = wrap(
4376 "This assignment uses a pattern that does not \
4377match all possible values. If one of the other values \
4378is used then the assignment will crash.
4379
4380The missing patterns are:\n",
4381 );
4382 for missing in missing {
4383 text.push_str("\n ");
4384 text.push_str(missing);
4385 }
4386
4387 Diagnostic {
4388 title: "Inexhaustive pattern".into(),
4389 text,
4390 hint: Some("Use a more general pattern or use `let assert` instead.".into()),
4391 level: Level::Error,
4392 location: Some(Location {
4393 src: src.clone(),
4394 path: path.to_path_buf(),
4395 label: Label {
4396 text: None,
4397 span: *location,
4398 },
4399 extra_labels: Vec::new(),
4400 }),
4401 }
4402 }
4403
4404 TypeError::InexhaustiveCaseExpression { location, missing } => {
4405 let mut text = wrap(
4406 "This case expression does not have a pattern \
4407for all possible values. If it is run on one of the \
4408values without a pattern then it will crash.
4409
4410The missing patterns are:\n",
4411 );
4412 for missing in missing {
4413 text.push_str("\n ");
4414 text.push_str(missing);
4415 }
4416 Diagnostic {
4417 title: "Inexhaustive patterns".into(),
4418 text,
4419 hint: None,
4420 level: Level::Error,
4421 location: Some(Location {
4422 src: src.clone(),
4423 path: path.to_path_buf(),
4424 label: Label {
4425 text: None,
4426 span: *location,
4427 },
4428 extra_labels: Vec::new(),
4429 }),
4430 }
4431 }
4432
4433 TypeError::MissingCaseBody { location } => {
4434 let text = wrap("This case expression is missing its body.");
4435 Diagnostic {
4436 title: "Missing case body".into(),
4437 text,
4438 hint: None,
4439 level: Level::Error,
4440 location: Some(Location {
4441 src: src.clone(),
4442 path: path.to_path_buf(),
4443 label: Label {
4444 text: None,
4445 span: *location,
4446 },
4447 extra_labels: Vec::new(),
4448 }),
4449 }
4450 }
4451
4452 TypeError::UnsupportedExpressionTarget {
4453 location,
4454 target: current_target,
4455 } => {
4456 let text = wrap_format!(
4457 "This value is not available as it is defined using externals, \
4458and there is no implementation for the {} target.",
4459 match current_target {
4460 Target::Erlang => "Erlang",
4461 Target::JavaScript => "JavaScript",
4462 }
4463 );
4464 let hint = wrap("Did you mean to build for a different target?");
4465 Diagnostic {
4466 title: "Unsupported target".into(),
4467 text,
4468 hint: Some(hint),
4469 level: Level::Error,
4470 location: Some(Location {
4471 path: path.clone(),
4472 src: src.clone(),
4473 label: Label {
4474 text: None,
4475 span: *location,
4476 },
4477 extra_labels: vec![],
4478 }),
4479 }
4480 }
4481
4482 TypeError::UnsupportedPublicFunctionTarget {
4483 location,
4484 name,
4485 target,
4486 } => {
4487 let target = match target {
4488 Target::Erlang => "Erlang",
4489 Target::JavaScript => "JavaScript",
4490 };
4491 let text = wrap_format!(
4492 "The `{name}` function is public but doesn't have an \
4493implementation for the {target} target. All public functions of a package \
4494must be able to compile for a module to be valid."
4495 );
4496 Diagnostic {
4497 title: "Unsupported target".into(),
4498 text,
4499 hint: None,
4500 level: Level::Error,
4501 location: Some(Location {
4502 path: path.clone(),
4503 src: src.clone(),
4504 label: Label {
4505 text: None,
4506 span: *location,
4507 },
4508 extra_labels: vec![],
4509 }),
4510 }
4511 }
4512
4513 TypeError::UnusedTypeAliasParameter { location, name } => {
4514 let text =
4515 wrap_format!("The type variable `{name}` is unused. It can be safely removed.",);
4516 Diagnostic {
4517 title: "Unused type parameter".into(),
4518 text,
4519 hint: None,
4520 level: Level::Error,
4521 location: Some(Location {
4522 path: path.clone(),
4523 src: src.clone(),
4524 label: Label {
4525 text: None,
4526 span: *location,
4527 },
4528 extra_labels: vec![],
4529 }),
4530 }
4531 }
4532
4533 TypeError::DuplicateTypeParameter { location, name } => {
4534 let text = wrap_format!(
4535 "This definition has multiple type parameters named `{name}`.
4536Rename or remove one of them.",
4537 );
4538 Diagnostic {
4539 title: "Duplicate type parameter".into(),
4540 text,
4541 hint: None,
4542 level: Level::Error,
4543 location: Some(Location {
4544 path: path.clone(),
4545 src: src.clone(),
4546 label: Label {
4547 text: None,
4548 span: *location,
4549 },
4550 extra_labels: vec![],
4551 }),
4552 }
4553 }
4554
4555 TypeError::NotFnInUse { location, type_ } => {
4556 let mut printer = Printer::new(names);
4557 let text = wrap_format!(
4558 "In a use expression, there should be a function on \
4559the right hand side of `<-`, but this value has type:
4560
4561 {}
4562
4563See: https://tour.gleam.run/advanced-features/use/",
4564 printer.print_type(type_)
4565 );
4566
4567 Diagnostic {
4568 title: "Type mismatch".into(),
4569 text,
4570 hint: None,
4571 level: Level::Error,
4572 location: Some(Location {
4573 label: Label {
4574 text: None,
4575 span: *location,
4576 },
4577 path: path.clone(),
4578 src: src.clone(),
4579 extra_labels: vec![],
4580 }),
4581 }
4582 }
4583
4584 TypeError::UseFnDoesntTakeCallback {
4585 location,
4586 actual_type: None,
4587 }
4588 | TypeError::UseFnIncorrectArity {
4589 location,
4590 expected: 0,
4591 given: 1,
4592 } => {
4593 let text = wrap(
4594 "The function on the right of `<-` here \
4595takes no arguments, but it has to take at least \
4596one argument, a callback function.
4597
4598See: https://tour.gleam.run/advanced-features/use/",
4599 );
4600 Diagnostic {
4601 title: "Incorrect arity".into(),
4602 text,
4603 hint: None,
4604 level: Level::Error,
4605 location: Some(Location {
4606 label: Label {
4607 text: Some("Expected no arguments, got 1".into()),
4608 span: *location,
4609 },
4610 path: path.clone(),
4611 src: src.clone(),
4612 extra_labels: vec![],
4613 }),
4614 }
4615 }
4616
4617 TypeError::UseFnIncorrectArity {
4618 location,
4619 expected,
4620 given,
4621 } => {
4622 let expected_string = match expected {
4623 0 => "no arguments".into(),
4624 1 => "1 argument".into(),
4625 _ => format!("{expected} arguments"),
4626 };
4627 let supplied_arguments = given - 1;
4628 let supplied_arguments_string = match supplied_arguments {
4629 0 => "no arguments".into(),
4630 1 => "1 argument".into(),
4631 _ => format!("{given} arguments"),
4632 };
4633 let label = format!("Expected {expected_string}, got {given}");
4634 let mut text: String = format!(
4635 "The function on the right of `<-` \
4636here takes {expected_string}.\n"
4637 );
4638
4639 if expected > given {
4640 if supplied_arguments == 0 {
4641 text.push_str(
4642 "The only argument that was supplied is \
4643the `use` callback function.\n",
4644 )
4645 } else {
4646 text.push_str(&format!(
4647 "You supplied {supplied_arguments_string} \
4648and the final one is the `use` callback function.\n"
4649 ));
4650 }
4651 } else {
4652 text.push_str(
4653 "All the arguments have already been supplied, \
4654so it cannot take the `use` callback function as a final argument.\n",
4655 )
4656 };
4657
4658 text.push_str("\nSee: https://tour.gleam.run/advanced-features/use/");
4659
4660 Diagnostic {
4661 title: "Incorrect arity".into(),
4662 text: wrap(&text),
4663 hint: None,
4664 level: Level::Error,
4665 location: Some(Location {
4666 label: Label {
4667 text: Some(label),
4668 span: *location,
4669 },
4670 path: path.clone(),
4671 src: src.clone(),
4672 extra_labels: vec![],
4673 }),
4674 }
4675 }
4676
4677 TypeError::UseFnDoesntTakeCallback {
4678 location,
4679 actual_type: Some(actual),
4680 } => {
4681 let mut printer = Printer::new(names);
4682 let text = wrap_format!(
4683 "The function on the right hand side of `<-` \
4684has to take a callback function as its last argument. \
4685But the last argument of this function has type:
4686
4687 {}
4688
4689See: https://tour.gleam.run/advanced-features/use/",
4690 printer.print_type(actual)
4691 );
4692 Diagnostic {
4693 title: "Type mismatch".into(),
4694 text: wrap(&text),
4695 hint: None,
4696 level: Level::Error,
4697 location: Some(Location {
4698 label: Label {
4699 text: None,
4700 span: *location,
4701 },
4702 path: path.clone(),
4703 src: src.clone(),
4704 extra_labels: vec![],
4705 }),
4706 }
4707 }
4708
4709 TypeError::UseCallbackIncorrectArity {
4710 pattern_location,
4711 call_location,
4712 expected,
4713 given,
4714 } => {
4715 let expected = match expected {
4716 0 => "no arguments".into(),
4717 1 => "1 argument".into(),
4718 _ => format!("{expected} arguments"),
4719 };
4720
4721 let specified = match given {
4722 0 => "none were provided".into(),
4723 1 => "1 was provided".into(),
4724 _ => format!("{given} were provided"),
4725 };
4726
4727 let text = wrap_format!(
4728 "This function takes a callback that expects {expected}. \
4729But {specified} on the left hand side of `<-`.
4730
4731See: https://tour.gleam.run/advanced-features/use/"
4732 );
4733 Diagnostic {
4734 title: "Incorrect arity".into(),
4735 text,
4736 hint: None,
4737 level: Level::Error,
4738 location: Some(Location {
4739 label: Label {
4740 text: None,
4741 span: *call_location,
4742 },
4743 path: path.clone(),
4744 src: src.clone(),
4745 extra_labels: vec![ExtraLabel {
4746 src_info: None,
4747 label: Label {
4748 text: Some(format!("Expected {expected}, got {given}")),
4749 span: *pattern_location,
4750 },
4751 }],
4752 }),
4753 }
4754 }
4755
4756 TypeError::BadName {
4757 location,
4758 name,
4759 kind,
4760 } => {
4761 let kind_str = kind.as_str();
4762 let label = format!("This is not a valid {} name", kind_str.to_lowercase());
4763 let hint = match kind {
4764 Named::Type | Named::TypeAlias | Named::CustomTypeVariant => {
4765 format!(
4766 "{} names start with an uppercase \
4767letter and contain only lowercase letters, numbers, \
4768and uppercase letters.
4769Try: {}",
4770 kind_str,
4771 to_upper_camel_case(name)
4772 )
4773 }
4774 Named::Variable
4775 | Named::TypeVariable
4776 | Named::Argument
4777 | Named::Label
4778 | Named::Constant
4779 | Named::Function => format!(
4780 "{} names start with a lowercase letter \
4781and contain a-z, 0-9, or _.
4782Try: {}",
4783 kind_str,
4784 to_snake_case(name)
4785 ),
4786 Named::Discard => format!(
4787 "{} names start with _ and contain \
4788a-z, 0-9, or _.
4789Try: _{}",
4790 kind_str,
4791 to_snake_case(name)
4792 ),
4793 };
4794
4795 Diagnostic {
4796 title: format!("Invalid {} name", kind_str.to_lowercase()),
4797 text: "".into(),
4798 hint: Some(hint),
4799 level: Level::Error,
4800 location: Some(Location {
4801 label: Label {
4802 text: Some(label),
4803 span: *location,
4804 },
4805 path: path.clone(),
4806 src: src.clone(),
4807 extra_labels: vec![],
4808 }),
4809 }
4810 }
4811
4812 TypeError::AllVariantsDeprecated { location } => {
4813 let text = String::from(
4814 "Consider deprecating the type as a whole.
4815
4816 @deprecated(\"message\")
4817 type Wibble {
4818 Wobble1
4819 Wobble2
4820 }
4821",
4822 );
4823 Diagnostic {
4824 title: "All variants of custom type deprecated.".into(),
4825 text,
4826 hint: None,
4827 level: Level::Error,
4828 location: Some(Location {
4829 label: Label {
4830 text: None,
4831 span: *location,
4832 },
4833 path: path.clone(),
4834 src: src.clone(),
4835 extra_labels: vec![],
4836 }),
4837 }
4838 }
4839 TypeError::DeprecatedVariantOnDeprecatedType { location } => {
4840 let text = wrap(
4841 "This custom type has already been deprecated, so deprecating \
4842one of its variants does nothing.
4843Consider removing the deprecation attribute on the variant.",
4844 );
4845
4846 Diagnostic {
4847 title: "Custom type already deprecated".into(),
4848 text,
4849 hint: None,
4850 level: Level::Error,
4851 location: Some(Location {
4852 label: Label {
4853 text: None,
4854 span: *location,
4855 },
4856 path: path.clone(),
4857 src: src.clone(),
4858 extra_labels: vec![],
4859 }),
4860 }
4861 }
4862
4863 TypeError::EchoWithNoFollowingExpression { location } => Diagnostic {
4864 title: "Invalid echo use".to_string(),
4865 text: wrap("The `echo` keyword should be followed by a value to print."),
4866 hint: None,
4867 level: Level::Error,
4868 location: Some(Location {
4869 label: Label {
4870 text: Some("I was expecting a value after this".into()),
4871 span: *location,
4872 },
4873 path: path.clone(),
4874 src: src.clone(),
4875 extra_labels: vec![],
4876 }),
4877 },
4878
4879 TypeError::StringConcatenationWithAddInt { location } => Diagnostic {
4880 title: "Type mismatch".to_string(),
4881 text: wrap(
4882 "The + operator can only be used on Ints.
4883To join two strings together you can use the <> operator.",
4884 ),
4885 hint: None,
4886 level: Level::Error,
4887 location: Some(Location {
4888 label: Label {
4889 text: Some("Use <> instead".into()),
4890 span: *location,
4891 },
4892 path: path.clone(),
4893 src: src.clone(),
4894 extra_labels: vec![],
4895 }),
4896 },
4897
4898 TypeError::IntOperatorOnFloats { location, operator } => Diagnostic {
4899 title: "Type mismatch".to_string(),
4900 text: wrap_format!("The {} operator can only be used on Ints.", operator.name()),
4901 hint: None,
4902 level: Level::Error,
4903 location: Some(Location {
4904 label: Label {
4905 text: operator
4906 .float_equivalent()
4907 .map(|operator| format!("Use {} instead", operator.name())),
4908 span: *location,
4909 },
4910 path: path.clone(),
4911 src: src.clone(),
4912 extra_labels: vec![],
4913 }),
4914 },
4915
4916 TypeError::FloatOperatorOnInts { location, operator } => Diagnostic {
4917 title: "Type mismatch".to_string(),
4918 text: wrap_format!(
4919 "The {} operator can only be used on Floats.",
4920 operator.name()
4921 ),
4922 hint: None,
4923 level: Level::Error,
4924 location: Some(Location {
4925 label: Label {
4926 text: operator
4927 .int_equivalent()
4928 .map(|operator| format!("Use {} instead", operator.name())),
4929 span: *location,
4930 },
4931 path: path.clone(),
4932 src: src.clone(),
4933 extra_labels: vec![],
4934 }),
4935 },
4936
4937 TypeError::DoubleVariableAssignmentInBitArray { location } => Diagnostic {
4938 title: "Double variable assignment".to_string(),
4939 text: wrap(
4940 "This pattern assigns to two different variables \
4941at once, which is not possible in bit arrays.",
4942 ),
4943 hint: Some(wrap("Remove the `as` assignment.")),
4944 level: Level::Error,
4945 location: Some(Location {
4946 label: Label {
4947 text: None,
4948 span: *location,
4949 },
4950 path: path.clone(),
4951 src: src.clone(),
4952 extra_labels: vec![],
4953 }),
4954 },
4955
4956 TypeError::NonUtf8StringAssignmentInBitArray { location } => Diagnostic {
4957 title: "Non UTF-8 string assignment".to_string(),
4958 text: wrap(
4959 "This pattern assigns a non UTF-8 string to a \
4960variable in a bit array. This is planned to be supported in the future, but we are \
4961unsure of the desired behaviour. Please go to https://github.com/gleam-lang/gleam/issues/4566 \
4962and explain your usecase for this pattern, and how you would expect it to behave.",
4963 ),
4964 hint: None,
4965 level: Level::Error,
4966 location: Some(Location {
4967 label: Label {
4968 text: None,
4969 span: *location,
4970 },
4971 path: path.clone(),
4972 src: src.clone(),
4973 extra_labels: vec![],
4974 }),
4975 },
4976
4977 TypeError::PrivateOpaqueType { location } => Diagnostic {
4978 title: "Private opaque type".to_string(),
4979 text: wrap("Only a public type can be opaque."),
4980 hint: None,
4981 level: Level::Error,
4982 location: Some(Location {
4983 label: Label {
4984 text: Some("You can safely remove this.".to_string()),
4985 span: *location,
4986 },
4987 path: path.clone(),
4988 src: src.clone(),
4989 extra_labels: vec![],
4990 }),
4991 },
4992
4993 TypeError::SrcImportingDevDependency {
4994 location,
4995 importing_module,
4996 imported_module,
4997 package,
4998 } => Diagnostic {
4999 title: "App importing dev dependency".to_string(),
5000 text: wrap_format!(
5001 "The application module `{importing_module}` is \
5002importing the module `{imported_module}`, but `{package}`, the package it \
5003belongs to, is a dev dependency.
5004
5005Dev dependencies are not included in production builds so application \
5006modules should not import them. Perhaps change `{package}` to a regular dependency."
5007 ),
5008 hint: None,
5009 level: Level::Error,
5010 location: Some(Location {
5011 label: Label {
5012 text: None,
5013 span: *location,
5014 },
5015 path: path.clone(),
5016 src: src.clone(),
5017 extra_labels: vec![],
5018 }),
5019 },
5020
5021 TypeError::ExternalTypeWithConstructors { location } => Diagnostic {
5022 title: "External type with constructors".to_string(),
5023 text: wrap_format!(
5024 "This type is annotated with the `@external` annotation, \
5025but it has constructors. The `@external` annotation is only for external types \
5026with no constructors."
5027 ),
5028 hint: Some("Remove the `@external` annotation".into()),
5029 level: Level::Error,
5030 location: Some(Location {
5031 label: Label {
5032 text: None,
5033 span: *location,
5034 },
5035 path: path.clone(),
5036 src: src.clone(),
5037 extra_labels: vec![],
5038 }),
5039 },
5040
5041 TypeError::LowercaseBoolPattern { location } => Diagnostic {
5042 title: "Lowercase bool pattern".to_string(),
5043 text: "".into(),
5044 hint: Some(
5045 "In Gleam bool literals are `True` and `False`.
5046See: https://tour.gleam.run/basics/bools/"
5047 .into(),
5048 ),
5049 level: Level::Error,
5050 location: Some(Location {
5051 label: Label {
5052 text: Some("This is not a bool".into()),
5053 span: *location,
5054 },
5055 path: path.clone(),
5056 src: src.clone(),
5057 extra_labels: vec![],
5058 }),
5059 },
5060
5061 TypeError::TodoConstant { location } => Diagnostic {
5062 title: "Constant todo found".to_string(),
5063 text: wrap(
5064 "This code will crash if it is run. \
5065Be sure to finish it before running your program.",
5066 ),
5067 hint: None,
5068 level: Level::Error,
5069 location: Some(Location {
5070 label: Label {
5071 text: Some("This code is incomplete".into()),
5072 span: *location,
5073 },
5074 path: path.clone(),
5075 src: src.clone(),
5076 extra_labels: vec![],
5077 }),
5078 },
5079 })
5080}
5081
5082fn std_io_error_kind_text(kind: &std::io::ErrorKind) -> String {
5083 use std::io::ErrorKind;
5084 match kind {
5085 ErrorKind::NotFound => "Could not find the stdio stream".into(),
5086 ErrorKind::PermissionDenied => "Permission was denied".into(),
5087 ErrorKind::ConnectionRefused => "Connection was refused".into(),
5088 ErrorKind::ConnectionReset => "Connection was reset".into(),
5089 ErrorKind::ConnectionAborted => "Connection was aborted".into(),
5090 ErrorKind::NotConnected => "Was not connected".into(),
5091 ErrorKind::AddrInUse => "The stream was already in use".into(),
5092 ErrorKind::AddrNotAvailable => "The stream was not available".into(),
5093 ErrorKind::BrokenPipe => "The pipe was broken".into(),
5094 ErrorKind::AlreadyExists => "A handle to the stream already exists".into(),
5095 ErrorKind::WouldBlock => "This operation would block when it was requested not to".into(),
5096 ErrorKind::InvalidInput => "Some parameter was invalid".into(),
5097 ErrorKind::InvalidData => "The data was invalid. Check that the encoding is UTF-8".into(),
5098 ErrorKind::TimedOut => "The operation timed out".into(),
5099 ErrorKind::WriteZero => {
5100 "An attempt was made to write, but all bytes could not be written".into()
5101 }
5102 ErrorKind::Interrupted => "The operation was interrupted".into(),
5103 ErrorKind::UnexpectedEof => "The end of file was reached before it was expected".into(),
5104 ErrorKind::HostUnreachable
5105 | ErrorKind::NetworkUnreachable
5106 | ErrorKind::NetworkDown
5107 | ErrorKind::NotADirectory
5108 | ErrorKind::IsADirectory
5109 | ErrorKind::DirectoryNotEmpty
5110 | ErrorKind::ReadOnlyFilesystem
5111 | ErrorKind::StaleNetworkFileHandle
5112 | ErrorKind::StorageFull
5113 | ErrorKind::NotSeekable
5114 | ErrorKind::QuotaExceeded
5115 | ErrorKind::FileTooLarge
5116 | ErrorKind::ResourceBusy
5117 | ErrorKind::ExecutableFileBusy
5118 | ErrorKind::Deadlock
5119 | ErrorKind::CrossesDevices
5120 | ErrorKind::TooManyLinks
5121 | ErrorKind::InvalidFilename
5122 | ErrorKind::ArgumentListTooLong
5123 | ErrorKind::Unsupported
5124 | ErrorKind::OutOfMemory
5125 | ErrorKind::Other
5126 | _ => "An unknown error occurred".into(),
5127 }
5128}
5129
5130fn write_cycle(buffer: &mut String, cycle: &[EcoString]) {
5131 buffer.push_str(
5132 "
5133 ┌─────┐\n",
5134 );
5135 for (index, name) in cycle.iter().enumerate() {
5136 if index != 0 {
5137 buffer.push_str(" │ ↓\n");
5138 }
5139 buffer.push_str(" │ ");
5140 buffer.push_str(name);
5141 buffer.push('\n');
5142 }
5143 buffer.push_str(" └─────┘\n");
5144}
5145
5146fn hint_alternative_operator(op: &BinOp, given: &Type) -> Option<String> {
5147 match op {
5148 BinOp::AddInt if given.is_float() => Some(hint_numeric_message("+.", "Float")),
5149 BinOp::DivInt if given.is_float() => Some(hint_numeric_message("/.", "Float")),
5150 BinOp::GtEqInt if given.is_float() => Some(hint_numeric_message(">=.", "Float")),
5151 BinOp::GtInt if given.is_float() => Some(hint_numeric_message(">.", "Float")),
5152 BinOp::LtEqInt if given.is_float() => Some(hint_numeric_message("<=.", "Float")),
5153 BinOp::LtInt if given.is_float() => Some(hint_numeric_message("<.", "Float")),
5154 BinOp::MultInt if given.is_float() => Some(hint_numeric_message("*.", "Float")),
5155 BinOp::SubInt if given.is_float() => Some(hint_numeric_message("-.", "Float")),
5156
5157 BinOp::AddFloat if given.is_int() => Some(hint_numeric_message("+", "Int")),
5158 BinOp::DivFloat if given.is_int() => Some(hint_numeric_message("/", "Int")),
5159 BinOp::GtEqFloat if given.is_int() => Some(hint_numeric_message(">=", "Int")),
5160 BinOp::GtFloat if given.is_int() => Some(hint_numeric_message(">", "Int")),
5161 BinOp::LtEqFloat if given.is_int() => Some(hint_numeric_message("<=", "Int")),
5162 BinOp::LtFloat if given.is_int() => Some(hint_numeric_message("<", "Int")),
5163 BinOp::MultFloat if given.is_int() => Some(hint_numeric_message("*", "Int")),
5164 BinOp::SubFloat if given.is_int() => Some(hint_numeric_message("-", "Int")),
5165
5166 BinOp::AddInt if given.is_string() => Some(hint_string_message()),
5167 BinOp::AddFloat if given.is_string() => Some(hint_string_message()),
5168
5169 BinOp::And
5170 | BinOp::Or
5171 | BinOp::Eq
5172 | BinOp::NotEq
5173 | BinOp::LtInt
5174 | BinOp::LtEqInt
5175 | BinOp::LtFloat
5176 | BinOp::LtEqFloat
5177 | BinOp::GtEqInt
5178 | BinOp::GtInt
5179 | BinOp::GtEqFloat
5180 | BinOp::GtFloat
5181 | BinOp::AddInt
5182 | BinOp::AddFloat
5183 | BinOp::SubInt
5184 | BinOp::SubFloat
5185 | BinOp::MultInt
5186 | BinOp::MultFloat
5187 | BinOp::DivInt
5188 | BinOp::DivFloat
5189 | BinOp::RemainderInt
5190 | BinOp::Concatenate => None,
5191 }
5192}
5193
5194fn hint_wrap_value_in_result(expected: &Arc<Type>, given: &Arc<Type>) -> Option<String> {
5195 let expected = collapse_links(expected.clone());
5196 let (expected_ok_type, expected_error_type) = expected.result_types()?;
5197
5198 if given.same_as(expected_ok_type.as_ref()) {
5199 Some("Did you mean to wrap this in an `Ok`?".into())
5200 } else if given.same_as(expected_error_type.as_ref()) {
5201 Some("Did you mean to wrap this in an `Error`?".into())
5202 } else {
5203 None
5204 }
5205}
5206
5207fn hint_numeric_message(alt: &str, type_: &str) -> String {
5208 format!("The {alt} operator can be used with {type_}s\n")
5209}
5210
5211fn hint_string_message() -> String {
5212 wrap("Strings can be joined using the `<>` operator.")
5213}
5214
5215#[derive(Debug, Clone, PartialEq, Eq)]
5216pub struct Unformatted {
5217 pub source: Utf8PathBuf,
5218 pub destination: Utf8PathBuf,
5219 pub input: EcoString,
5220 pub output: String,
5221}
5222
5223pub fn wrap(text: &str) -> String {
5224 let mut result = String::with_capacity(text.len());
5225
5226 for (i, line) in wrap_text(text, 75).iter().enumerate() {
5227 if i > 0 {
5228 result.push('\n');
5229 }
5230 result.push_str(line);
5231 }
5232
5233 result
5234}
5235
5236fn wrap_text(text: &str, width: usize) -> Vec<Cow<'_, str>> {
5237 let mut lines: Vec<Cow<'_, str>> = Vec::new();
5238 for line in text.split('\n') {
5239 // check if line needs to be broken
5240 match line.len() > width {
5241 false => lines.push(Cow::from(line)),
5242 true => {
5243 let mut new_lines = break_line(line, width);
5244 lines.append(&mut new_lines);
5245 }
5246 };
5247 }
5248
5249 lines
5250}
5251
5252fn break_line(line: &str, width: usize) -> Vec<Cow<'_, str>> {
5253 let mut lines: Vec<Cow<'_, str>> = Vec::new();
5254 let mut newline = String::from("");
5255
5256 // split line by spaces
5257 for (i, word) in line.split(' ').enumerate() {
5258 let is_new_line = i < 1 || newline.is_empty();
5259
5260 let can_add_word = match is_new_line {
5261 true => newline.len() + word.len() <= width,
5262 // +1 accounts for space added before word
5263 false => newline.len() + (word.len() + 1) <= width,
5264 };
5265
5266 if can_add_word {
5267 if !is_new_line {
5268 newline.push(' ');
5269 }
5270 newline.push_str(word);
5271 } else {
5272 // word too big, save existing line if present
5273 if !newline.is_empty() {
5274 // save current line and reset it
5275 lines.push(Cow::from(newline.to_owned()));
5276 newline.clear();
5277 }
5278
5279 // then save word to a new line or break it
5280 match word.len() > width {
5281 false => newline.push_str(word),
5282 true => {
5283 let (mut newlines, remainder) = break_word(word, width);
5284 lines.append(&mut newlines);
5285 newline.push_str(remainder);
5286 }
5287 }
5288 }
5289 }
5290
5291 // save last line after loop finishes
5292 if !newline.is_empty() {
5293 lines.push(Cow::from(newline));
5294 }
5295
5296 lines
5297}
5298
5299// breaks word into n lines based on width. Returns list of new lines and remainder
5300fn break_word(word: &str, width: usize) -> (Vec<Cow<'_, str>>, &str) {
5301 let mut new_lines: Vec<Cow<'_, str>> = Vec::new();
5302 let (first, mut remainder) = word.split_at(width);
5303 new_lines.push(Cow::from(first));
5304
5305 // split remainder until it's small enough
5306 while remainder.len() > width {
5307 let (first, second) = remainder.split_at(width);
5308 new_lines.push(Cow::from(first));
5309 remainder = second;
5310 }
5311
5312 (new_lines, remainder)
5313}