Fork of daniellemaywood.uk/gleam — Wasm codegen work
185 kB
5323 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 let message = if possible_modules.len() == 1 {
3532 "\nDid you mean:\n\n"
3533 } else {
3534 "\nDid you mean one of these:\n\n"
3535 };
3536 text.push_str(message);
3537 for module_name in possible_modules {
3538 text.push_str(" - ");
3539 text.push_str(module_name);
3540 text.push('.');
3541 text.push_str(name);
3542 text.push('\n');
3543 }
3544 }
3545
3546 text
3547 };
3548
3549 Diagnostic {
3550 title,
3551 text,
3552 hint: None,
3553 level: Level::Error,
3554 location: Some(Location {
3555 label: Label {
3556 text: did_you_mean(name, variables),
3557 span: *location,
3558 },
3559 path: path.clone(),
3560 src: src.clone(),
3561 extra_labels: vec![],
3562 }),
3563 }
3564 }
3565 }
3566
3567 TypeError::PrivateTypeLeak { location, leaked } => {
3568 let mut printer = Printer::new(names);
3569
3570 // TODO: be more precise.
3571 // - is being returned by this public function
3572 // - is taken as an argument by this public function
3573 // - is taken as an argument by this public enum constructor
3574 // etc
3575 let text = wrap_format!(
3576 "The following type is private, but is \
3577being used by this public export.
3578
3579 {}
3580
3581Private types can only be used within the module that defines them.",
3582 printer.print_type(leaked),
3583 );
3584 Diagnostic {
3585 title: "Private type used in public interface".into(),
3586 text,
3587 hint: None,
3588 level: Level::Error,
3589 location: Some(Location {
3590 label: Label {
3591 text: None,
3592 span: *location,
3593 },
3594 path: path.clone(),
3595 src: src.clone(),
3596 extra_labels: vec![],
3597 }),
3598 }
3599 }
3600
3601 TypeError::UnknownModule {
3602 location,
3603 name,
3604 suggestions,
3605 } => Diagnostic {
3606 title: "Unknown module".into(),
3607 text: format!("No module has been found with the name `{name}`."),
3608 hint: suggestions
3609 .first()
3610 .map(|suggestion| suggestion.suggestion(name)),
3611 level: Level::Error,
3612 location: Some(Location {
3613 label: Label {
3614 text: None,
3615 span: *location,
3616 },
3617 path: path.clone(),
3618 src: src.clone(),
3619 extra_labels: vec![],
3620 }),
3621 },
3622
3623 TypeError::UnknownModuleType {
3624 location,
3625 name,
3626 module_name,
3627 type_constructors,
3628 value_with_same_name: imported_type_as_value,
3629 } => {
3630 let text = if *imported_type_as_value {
3631 format!("`{name}` is only a value, it cannot be imported as a type.")
3632 } else {
3633 format!("The module `{module_name}` does not have a `{name}` type.")
3634 };
3635 Diagnostic {
3636 title: "Unknown module type".into(),
3637 text,
3638 hint: None,
3639 level: Level::Error,
3640 location: Some(Location {
3641 label: Label {
3642 text: if *imported_type_as_value {
3643 Some(format!("Did you mean `{name}`?"))
3644 } else {
3645 did_you_mean(name, type_constructors)
3646 },
3647 span: *location,
3648 },
3649 path: path.clone(),
3650 src: src.clone(),
3651 extra_labels: vec![],
3652 }),
3653 }
3654 }
3655
3656 TypeError::UnknownModuleValue {
3657 location,
3658 name,
3659 module_name,
3660 value_constructors,
3661 type_with_same_name: imported_value_as_type,
3662 context,
3663 } => {
3664 let text = if *imported_value_as_type {
3665 match context {
3666 ModuleValueUsageContext::UnqualifiedImport => {
3667 wrap_format!("`{name}` is only a type, it cannot be imported as a value.")
3668 }
3669 ModuleValueUsageContext::ModuleAccess => wrap_format!(
3670 "{module_name}.{name} is a type constructor, \
3671it cannot be used as a value"
3672 ),
3673 }
3674 } else {
3675 wrap_format!("The module `{module_name}` does not have a `{name}` value.")
3676 };
3677 Diagnostic {
3678 title: "Unknown module value".into(),
3679 text,
3680 hint: None,
3681 level: Level::Error,
3682 location: Some(Location {
3683 label: Label {
3684 text: if *imported_value_as_type
3685 && matches!(context, ModuleValueUsageContext::UnqualifiedImport)
3686 {
3687 Some(format!("Did you mean `type {name}`?"))
3688 } else {
3689 did_you_mean(name, value_constructors)
3690 },
3691 span: *location,
3692 },
3693 path: path.clone(),
3694 src: src.clone(),
3695 extra_labels: vec![],
3696 }),
3697 }
3698 }
3699
3700 TypeError::ModuleAliasUsedAsName { location, name } => {
3701 let text = wrap(
3702 "Modules are not values, so you cannot assign them \
3703to variables, pass them to functions, or anything else that you would do with a value.",
3704 );
3705 Diagnostic {
3706 title: format!("Module `{name}` used as a value"),
3707 text,
3708 hint: None,
3709 level: Level::Error,
3710 location: Some(Location {
3711 label: Label {
3712 text: None,
3713 span: *location,
3714 },
3715 path: path.clone(),
3716 src: src.clone(),
3717 extra_labels: vec![],
3718 }),
3719 }
3720 }
3721
3722 TypeError::IncorrectNumClausePatterns {
3723 location,
3724 expected,
3725 given,
3726 } => {
3727 let subject = if *expected == 1 {
3728 "subject"
3729 } else {
3730 "subjects"
3731 };
3732 let pattern = if *expected == 1 {
3733 "pattern"
3734 } else {
3735 "patterns"
3736 };
3737 let text = wrap_format!(
3738 "This case expression has {expected} {subject}, \
3739but this pattern matches {given}.
3740Each clause must have a pattern for every subject value.",
3741 );
3742 Diagnostic {
3743 title: "Incorrect number of patterns".into(),
3744 text,
3745 hint: None,
3746 level: Level::Error,
3747 location: Some(Location {
3748 label: Label {
3749 text: Some(format!("Expected {expected} {pattern}, got {given}")),
3750 span: *location,
3751 },
3752 path: path.clone(),
3753 src: src.clone(),
3754 extra_labels: vec![],
3755 }),
3756 }
3757 }
3758
3759 TypeError::NonLocalClauseGuardVariable { location, name } => {
3760 let text = wrap_format!(
3761 "Variables used in guards must be either defined in the \
3762function, or be an argument to the function. The variable \
3763`{name}` is not defined locally.",
3764 );
3765 Diagnostic {
3766 title: "Invalid guard variable".into(),
3767 text,
3768 hint: None,
3769 level: Level::Error,
3770 location: Some(Location {
3771 label: Label {
3772 text: Some("Is not locally defined".into()),
3773 span: *location,
3774 },
3775 path: path.clone(),
3776 src: src.clone(),
3777 extra_labels: vec![],
3778 }),
3779 }
3780 }
3781
3782 TypeError::ExtraVarInAlternativePattern { location, name } => {
3783 let text = wrap_format!(
3784 "All alternative patterns must define the same variables as \
3785the initial pattern. This variable `{name}` has not been previously defined.",
3786 );
3787 Diagnostic {
3788 title: "Extra alternative pattern variable".into(),
3789 text,
3790 hint: None,
3791 level: Level::Error,
3792 location: Some(Location {
3793 label: Label {
3794 text: Some("Has not been previously defined".into()),
3795 span: *location,
3796 },
3797 path: path.clone(),
3798 src: src.clone(),
3799 extra_labels: vec![],
3800 }),
3801 }
3802 }
3803
3804 TypeError::MissingVarInAlternativePattern { location, name } => {
3805 let text = wrap_format!(
3806 "All alternative patterns must define the same variables \
3807as the initial pattern, but the `{name}` variable is missing.",
3808 );
3809 Diagnostic {
3810 title: "Missing alternative pattern variable".into(),
3811 text,
3812 hint: None,
3813 level: Level::Error,
3814 location: Some(Location {
3815 label: Label {
3816 text: Some("This does not define all required variables".into()),
3817 span: *location,
3818 },
3819 path: path.clone(),
3820 src: src.clone(),
3821 extra_labels: vec![],
3822 }),
3823 }
3824 }
3825
3826 TypeError::DuplicateVarInPattern { location, name } => {
3827 let text = wrap_format!(
3828 "Variables can only be used once per pattern. This \
3829variable `{name}` appears multiple times.
3830If you used the same variable twice deliberately in order to check for equality \
3831please use a guard clause instead.
3832e.g. (x, y) if x == y -> ...",
3833 );
3834 Diagnostic {
3835 title: "Duplicate variable in pattern".into(),
3836 text,
3837 hint: None,
3838 level: Level::Error,
3839 location: Some(Location {
3840 label: Label {
3841 text: Some("This has already been used".into()),
3842 span: *location,
3843 },
3844 path: path.clone(),
3845 src: src.clone(),
3846 extra_labels: vec![],
3847 }),
3848 }
3849 }
3850
3851 TypeError::OutOfBoundsTupleIndex {
3852 location, size: 0, ..
3853 } => Diagnostic {
3854 title: "Out of bounds tuple index".into(),
3855 text: "This tuple has no elements so it cannot be indexed at all.".into(),
3856 hint: None,
3857 level: Level::Error,
3858 location: Some(Location {
3859 label: Label {
3860 text: None,
3861 span: *location,
3862 },
3863 path: path.clone(),
3864 src: src.clone(),
3865 extra_labels: vec![],
3866 }),
3867 },
3868
3869 TypeError::OutOfBoundsTupleIndex {
3870 location,
3871 index,
3872 size,
3873 } => {
3874 let text = wrap_format!(
3875 "The index being accessed for this tuple is {}, but this \
3876tuple has {} elements so the highest valid index is {}.",
3877 index,
3878 size,
3879 size - 1,
3880 );
3881 Diagnostic {
3882 title: "Out of bounds tuple index".into(),
3883 text,
3884 hint: None,
3885 level: Level::Error,
3886 location: Some(Location {
3887 label: Label {
3888 text: Some("This index is too large".into()),
3889 span: *location,
3890 },
3891 path: path.clone(),
3892 src: src.clone(),
3893 extra_labels: vec![],
3894 }),
3895 }
3896 }
3897
3898 TypeError::NotATuple { location, given } => {
3899 let mut printer = Printer::new(names);
3900 let text = format!(
3901 "To index into this value it needs to be a tuple, \
3902however it has this type:
3903
3904 {}",
3905 printer.print_type(given),
3906 );
3907 Diagnostic {
3908 title: "Type mismatch".into(),
3909 text,
3910 hint: None,
3911 level: Level::Error,
3912 location: Some(Location {
3913 label: Label {
3914 text: Some("This is not a tuple".into()),
3915 span: *location,
3916 },
3917 path: path.clone(),
3918 src: src.clone(),
3919 extra_labels: vec![],
3920 }),
3921 }
3922 }
3923
3924 TypeError::NotATupleUnbound { location } => {
3925 let text = wrap(
3926 "To index into a tuple we need to \
3927know its size, but we don't know anything about this type yet. \
3928Please add some type annotations so we can continue.",
3929 );
3930 Diagnostic {
3931 title: "Type mismatch".into(),
3932 text,
3933 hint: None,
3934 level: Level::Error,
3935 location: Some(Location {
3936 label: Label {
3937 text: Some("What type is this?".into()),
3938 span: *location,
3939 },
3940 path: path.clone(),
3941 src: src.clone(),
3942 extra_labels: vec![],
3943 }),
3944 }
3945 }
3946
3947 TypeError::RecordAccessUnknownType { location } => {
3948 let text = wrap(
3949 "In order to access a record field \
3950we need to know what type it is, but I can't tell \
3951the type here. Try adding type annotations to your \
3952function and try again.",
3953 );
3954 Diagnostic {
3955 title: "Unknown type for record access".into(),
3956 text,
3957 hint: None,
3958 level: Level::Error,
3959 location: Some(Location {
3960 label: Label {
3961 text: Some("I don't know what type this is".into()),
3962 span: *location,
3963 },
3964 path: path.clone(),
3965 src: src.clone(),
3966 extra_labels: vec![],
3967 }),
3968 }
3969 }
3970
3971 TypeError::BitArraySegmentError { error, location } => {
3972 let (label, mut extra) = match error {
3973 bit_array::ErrorType::ConflictingTypeOptions { existing_type } => (
3974 "This is an extra type specifier",
3975 vec![format!(
3976 "Hint: This segment already has the type {existing_type}."
3977 )],
3978 ),
3979
3980 bit_array::ErrorType::ConflictingSignednessOptions { existing_signed } => (
3981 "This is an extra signedness specifier",
3982 vec![format!(
3983 "Hint: This segment already has a \
3984signedness of {existing_signed}."
3985 )],
3986 ),
3987
3988 bit_array::ErrorType::ConflictingEndiannessOptions {
3989 existing_endianness,
3990 } => (
3991 "This is an extra endianness specifier",
3992 vec![format!(
3993 "Hint: This segment already has an \
3994endianness of {existing_endianness}."
3995 )],
3996 ),
3997
3998 bit_array::ErrorType::ConflictingSizeOptions => (
3999 "This is an extra size specifier",
4000 vec!["Hint: This segment already has a size.".into()],
4001 ),
4002
4003 bit_array::ErrorType::ConflictingUnitOptions => (
4004 "This is an extra unit specifier",
4005 vec!["Hint: A BitArray segment can have at most 1 unit.".into()],
4006 ),
4007
4008 bit_array::ErrorType::FloatWithSize => (
4009 "Invalid float size",
4010 vec!["Hint: floats have an exact size of 16/32/64 bits.".into()],
4011 ),
4012
4013 bit_array::ErrorType::InvalidEndianness => (
4014 "This option is invalid here",
4015 vec![wrap(
4016 "Hint: signed and unsigned \
4017can only be used with int, float, utf16 and utf32 types.",
4018 )],
4019 ),
4020
4021 bit_array::ErrorType::OptionNotAllowedInValue => (
4022 "This option is only allowed in BitArray patterns",
4023 vec!["Hint: This option has no effect in BitArray values.".into()],
4024 ),
4025
4026 bit_array::ErrorType::SignednessUsedOnNonInt { type_ } => (
4027 "Signedness is only valid with int types",
4028 vec![format!("Hint: This segment has a type of {type_}")],
4029 ),
4030 bit_array::ErrorType::TypeDoesNotAllowSize { type_ } => (
4031 "Size cannot be specified here",
4032 vec![format!("Hint: {type_} segments have an automatic size.")],
4033 ),
4034 bit_array::ErrorType::TypeDoesNotAllowUnit { type_ } => (
4035 "Unit cannot be specified here",
4036 vec![wrap(&format!(
4037 "Hint: {type_} segments \
4038are sized based on their value and cannot have a unit."
4039 ))],
4040 ),
4041 bit_array::ErrorType::VariableUtfSegmentInPattern => (
4042 "This cannot be a variable",
4043 vec![wrap(
4044 "Hint: in patterns utf8, utf16, and \
4045utf32 must be an exact string.",
4046 )],
4047 ),
4048 bit_array::ErrorType::SegmentMustHaveSize => (
4049 "This segment has no size",
4050 vec![wrap(
4051 "Hint: Bit array segments without \
4052a size are only allowed at the end of a bin pattern.",
4053 )],
4054 ),
4055 bit_array::ErrorType::UnitMustHaveSize => (
4056 "This needs an explicit size",
4057 vec!["Hint: If you specify unit() you must also specify size().".into()],
4058 ),
4059 bit_array::ErrorType::ConstantSizeNotPositive => {
4060 ("A constant size must be a positive number", vec![])
4061 }
4062 bit_array::ErrorType::OptionNotSupportedForTarget {
4063 target,
4064 option: UnsupportedOption::NativeEndianness,
4065 } => (
4066 "Unsupported endianness",
4067 vec![wrap_format!(
4068 "The {target} target does not support the `native` \
4069endianness option.",
4070 target = target.as_presentable_str(),
4071 )],
4072 ),
4073 bit_array::ErrorType::OptionNotSupportedForTarget {
4074 target,
4075 option: UnsupportedOption::UtfCodepointPattern,
4076 } => (
4077 "UTF-codepoint pattern matching is not supported",
4078 vec![wrap_format!(
4079 "The {target} target does not support \
4080UTF-codepoint pattern matching.",
4081 target = target.as_presentable_str(),
4082 )],
4083 ),
4084 };
4085 extra.push("See: https://tour.gleam.run/data-types/bit-arrays/".into());
4086 let text = extra.join("\n");
4087 Diagnostic {
4088 title: "Invalid bit array segment".into(),
4089 text,
4090 hint: None,
4091 level: Level::Error,
4092 location: Some(Location {
4093 label: Label {
4094 text: Some(label.into()),
4095 span: *location,
4096 },
4097 path: path.clone(),
4098 src: src.clone(),
4099 extra_labels: vec![],
4100 }),
4101 }
4102 }
4103 TypeError::RecordUpdateInvalidConstructor { location } => Diagnostic {
4104 title: "Invalid record constructor".into(),
4105 text: "Only record constructors can be used with the update syntax.".into(),
4106 hint: None,
4107 level: Level::Error,
4108 location: Some(Location {
4109 label: Label {
4110 text: Some("This is not a record constructor".into()),
4111 span: *location,
4112 },
4113 path: path.clone(),
4114 src: src.clone(),
4115 extra_labels: vec![],
4116 }),
4117 },
4118
4119 TypeError::RecordUpdateVariantWithNoFields { location } => Diagnostic {
4120 title: "Invalid record constructor".into(),
4121 text: wrap(
4122 "Only constructors with at least one labelled \
4123field can be used with the update syntax.",
4124 ),
4125 hint: None,
4126 level: Level::Error,
4127 location: Some(Location {
4128 label: Label {
4129 text: Some("This constructor has no labelled fields".into()),
4130 span: *location,
4131 },
4132 path: path.clone(),
4133 src: src.clone(),
4134 extra_labels: vec![],
4135 }),
4136 },
4137
4138 TypeError::UnexpectedTypeHole { location } => Diagnostic {
4139 title: "Unexpected type hole".into(),
4140 text: "We need to know the exact type here so type holes cannot be used.".into(),
4141 hint: None,
4142 level: Level::Error,
4143 location: Some(Location {
4144 label: Label {
4145 text: Some("I need to know what this is".into()),
4146 span: *location,
4147 },
4148 path: path.clone(),
4149 src: src.clone(),
4150 extra_labels: vec![],
4151 }),
4152 },
4153
4154 TypeError::ReservedModuleName { name } => {
4155 let text = format!(
4156 "The module name `{name}` is reserved.
4157Try a different name for this module."
4158 );
4159 Diagnostic {
4160 title: "Reserved module name".into(),
4161 text,
4162 hint: None,
4163 location: None,
4164 level: Level::Error,
4165 }
4166 }
4167
4168 TypeError::KeywordInModuleName { name, keyword } => {
4169 let text = wrap_format!(
4170 "The module name `{name}` contains the keyword `{keyword}`, \
4171so importing it would be a syntax error.
4172Try a different name for this module."
4173 );
4174 Diagnostic {
4175 title: "Invalid module name".into(),
4176 text,
4177 hint: None,
4178 location: None,
4179 level: Level::Error,
4180 }
4181 }
4182
4183 TypeError::NotExhaustivePatternMatch {
4184 location,
4185 unmatched,
4186 kind,
4187 } => {
4188 let mut text = match kind {
4189 PatternMatchKind::Case => {
4190 "This case expression does not match all possibilities.
4191Each constructor must have a pattern that matches it or
4192else it could crash."
4193 }
4194 PatternMatchKind::Assignment => {
4195 "This assignment does not match all possibilities.
4196Either use a case expression with patterns for each possible
4197value, or use `let assert` rather than `let`."
4198 }
4199 }
4200 .to_string();
4201
4202 text.push_str("\n\nThese values are not matched:\n\n");
4203 for unmatched in unmatched {
4204 text.push_str(" - ");
4205 text.push_str(unmatched);
4206 text.push('\n');
4207 }
4208 Diagnostic {
4209 title: "Not exhaustive pattern match".into(),
4210 text,
4211 hint: None,
4212 level: Level::Error,
4213 location: Some(Location {
4214 label: Label {
4215 text: None,
4216 span: *location,
4217 },
4218 path: path.clone(),
4219 src: src.clone(),
4220 extra_labels: vec![],
4221 }),
4222 }
4223 }
4224
4225 TypeError::ArgumentNameAlreadyUsed { location, name } => Diagnostic {
4226 title: "Argument name already used".into(),
4227 text: format!("Two `{name}` arguments have been defined for this function."),
4228 hint: None,
4229 level: Level::Error,
4230 location: Some(Location {
4231 label: Label {
4232 text: None,
4233 span: *location,
4234 },
4235 path: path.clone(),
4236 src: src.clone(),
4237 extra_labels: vec![],
4238 }),
4239 },
4240
4241 TypeError::UnlabelledAfterlabelled { location } => Diagnostic {
4242 title: "Unlabelled argument after labelled argument".into(),
4243 text: wrap("All unlabelled arguments must come before any labelled arguments."),
4244 hint: None,
4245 level: Level::Error,
4246 location: Some(Location {
4247 label: Label {
4248 text: None,
4249 span: *location,
4250 },
4251 path: path.clone(),
4252 src: src.clone(),
4253 extra_labels: vec![],
4254 }),
4255 },
4256
4257 TypeError::RecursiveTypeAlias { location, cycle } => {
4258 let mut text = "This type alias is defined in terms of itself.\n".into();
4259 write_cycle(&mut text, cycle);
4260 text.push_str(
4261 "If we tried to compile this recursive type it would expand
4262forever in a loop, and we'd never get the final type.",
4263 );
4264 Diagnostic {
4265 title: "Type cycle".into(),
4266 text,
4267 hint: None,
4268 level: Level::Error,
4269 location: Some(Location {
4270 label: Label {
4271 text: None,
4272 span: *location,
4273 },
4274 path: path.clone(),
4275 src: src.clone(),
4276 extra_labels: vec![],
4277 }),
4278 }
4279 }
4280
4281 TypeError::ExternalMissingAnnotation { location, kind } => {
4282 let kind = match kind {
4283 MissingAnnotation::Parameter => "parameter",
4284 MissingAnnotation::Return => "return",
4285 };
4286 let text = format!(
4287 "A {kind} annotation is missing from this function.
4288
4289Functions with external implementations must have type annotations
4290so we can tell what type of values they accept and return.",
4291 );
4292 Diagnostic {
4293 title: "Missing type annotation".into(),
4294 text,
4295 hint: None,
4296 level: Level::Error,
4297 location: Some(Location {
4298 label: Label {
4299 text: None,
4300 span: *location,
4301 },
4302 path: path.clone(),
4303 src: src.clone(),
4304 extra_labels: vec![],
4305 }),
4306 }
4307 }
4308
4309 TypeError::NoImplementation { location } => {
4310 let text = "We can't compile this function as it doesn't have an
4311implementation. Add a body or an external implementation
4312using the `@external` attribute."
4313 .into();
4314 Diagnostic {
4315 title: "Function without an implementation".into(),
4316 text,
4317 hint: None,
4318 level: Level::Error,
4319 location: Some(Location {
4320 label: Label {
4321 text: None,
4322 span: *location,
4323 },
4324 path: path.clone(),
4325 src: src.clone(),
4326 extra_labels: vec![],
4327 }),
4328 }
4329 }
4330
4331 TypeError::InvalidExternalJavascriptModule {
4332 location,
4333 name,
4334 module,
4335 } => {
4336 let text = wrap_format!(
4337 "The function `{name}` has an external JavaScript \
4338implementation but the module path `{module}` is not valid."
4339 );
4340 Diagnostic {
4341 title: "Invalid JavaScript module".into(),
4342 text,
4343 hint: None,
4344 level: Level::Error,
4345 location: Some(Location {
4346 label: Label {
4347 text: None,
4348 span: *location,
4349 },
4350 path: path.clone(),
4351 src: src.clone(),
4352 extra_labels: vec![],
4353 }),
4354 }
4355 }
4356
4357 TypeError::InvalidExternalJavascriptFunction {
4358 location,
4359 name,
4360 function,
4361 } => {
4362 let text = wrap_format!(
4363 "The function `{name}` has an external JavaScript \
4364implementation but the function name `{function}` is not valid."
4365 );
4366 Diagnostic {
4367 title: "Invalid JavaScript function".into(),
4368 text,
4369 hint: None,
4370 level: Level::Error,
4371 location: Some(Location {
4372 label: Label {
4373 text: None,
4374 span: *location,
4375 },
4376 path: path.clone(),
4377 src: src.clone(),
4378 extra_labels: vec![],
4379 }),
4380 }
4381 }
4382
4383 TypeError::InexhaustiveLetAssignment { location, missing } => {
4384 let mut text = wrap(
4385 "This assignment uses a pattern that does not \
4386match all possible values. If one of the other values \
4387is used then the assignment will crash.
4388
4389The missing patterns are:\n",
4390 );
4391 for missing in missing {
4392 text.push_str("\n ");
4393 text.push_str(missing);
4394 }
4395
4396 Diagnostic {
4397 title: "Inexhaustive pattern".into(),
4398 text,
4399 hint: Some("Use a more general pattern or use `let assert` instead.".into()),
4400 level: Level::Error,
4401 location: Some(Location {
4402 src: src.clone(),
4403 path: path.to_path_buf(),
4404 label: Label {
4405 text: None,
4406 span: *location,
4407 },
4408 extra_labels: Vec::new(),
4409 }),
4410 }
4411 }
4412
4413 TypeError::InexhaustiveCaseExpression { location, missing } => {
4414 let mut text = wrap(
4415 "This case expression does not have a pattern \
4416for all possible values. If it is run on one of the \
4417values without a pattern then it will crash.
4418
4419The missing patterns are:\n",
4420 );
4421 for missing in missing {
4422 text.push_str("\n ");
4423 text.push_str(missing);
4424 }
4425 Diagnostic {
4426 title: "Inexhaustive patterns".into(),
4427 text,
4428 hint: None,
4429 level: Level::Error,
4430 location: Some(Location {
4431 src: src.clone(),
4432 path: path.to_path_buf(),
4433 label: Label {
4434 text: None,
4435 span: *location,
4436 },
4437 extra_labels: Vec::new(),
4438 }),
4439 }
4440 }
4441
4442 TypeError::MissingCaseBody { location } => {
4443 let text = wrap("This case expression is missing its body.");
4444 Diagnostic {
4445 title: "Missing case body".into(),
4446 text,
4447 hint: None,
4448 level: Level::Error,
4449 location: Some(Location {
4450 src: src.clone(),
4451 path: path.to_path_buf(),
4452 label: Label {
4453 text: None,
4454 span: *location,
4455 },
4456 extra_labels: Vec::new(),
4457 }),
4458 }
4459 }
4460
4461 TypeError::UnsupportedExpressionTarget {
4462 location,
4463 target: current_target,
4464 } => {
4465 let text = wrap_format!(
4466 "This value is not available as it is defined using externals, \
4467and there is no implementation for the {} target.",
4468 match current_target {
4469 Target::Erlang => "Erlang",
4470 Target::JavaScript => "JavaScript",
4471 }
4472 );
4473 let hint = wrap("Did you mean to build for a different target?");
4474 Diagnostic {
4475 title: "Unsupported target".into(),
4476 text,
4477 hint: Some(hint),
4478 level: Level::Error,
4479 location: Some(Location {
4480 path: path.clone(),
4481 src: src.clone(),
4482 label: Label {
4483 text: None,
4484 span: *location,
4485 },
4486 extra_labels: vec![],
4487 }),
4488 }
4489 }
4490
4491 TypeError::UnsupportedPublicFunctionTarget {
4492 location,
4493 name,
4494 target,
4495 } => {
4496 let target = match target {
4497 Target::Erlang => "Erlang",
4498 Target::JavaScript => "JavaScript",
4499 };
4500 let text = wrap_format!(
4501 "The `{name}` function is public but doesn't have an \
4502implementation for the {target} target. All public functions of a package \
4503must be able to compile for a module to be valid."
4504 );
4505 Diagnostic {
4506 title: "Unsupported target".into(),
4507 text,
4508 hint: None,
4509 level: Level::Error,
4510 location: Some(Location {
4511 path: path.clone(),
4512 src: src.clone(),
4513 label: Label {
4514 text: None,
4515 span: *location,
4516 },
4517 extra_labels: vec![],
4518 }),
4519 }
4520 }
4521
4522 TypeError::UnusedTypeAliasParameter { location, name } => {
4523 let text =
4524 wrap_format!("The type variable `{name}` is unused. It can be safely removed.",);
4525 Diagnostic {
4526 title: "Unused type parameter".into(),
4527 text,
4528 hint: None,
4529 level: Level::Error,
4530 location: Some(Location {
4531 path: path.clone(),
4532 src: src.clone(),
4533 label: Label {
4534 text: None,
4535 span: *location,
4536 },
4537 extra_labels: vec![],
4538 }),
4539 }
4540 }
4541
4542 TypeError::DuplicateTypeParameter { location, name } => {
4543 let text = wrap_format!(
4544 "This definition has multiple type parameters named `{name}`.
4545Rename or remove one of them.",
4546 );
4547 Diagnostic {
4548 title: "Duplicate type parameter".into(),
4549 text,
4550 hint: None,
4551 level: Level::Error,
4552 location: Some(Location {
4553 path: path.clone(),
4554 src: src.clone(),
4555 label: Label {
4556 text: None,
4557 span: *location,
4558 },
4559 extra_labels: vec![],
4560 }),
4561 }
4562 }
4563
4564 TypeError::NotFnInUse { location, type_ } => {
4565 let mut printer = Printer::new(names);
4566 let text = wrap_format!(
4567 "In a use expression, there should be a function on \
4568the right hand side of `<-`, but this value has type:
4569
4570 {}
4571
4572See: https://tour.gleam.run/advanced-features/use/",
4573 printer.print_type(type_)
4574 );
4575
4576 Diagnostic {
4577 title: "Type mismatch".into(),
4578 text,
4579 hint: None,
4580 level: Level::Error,
4581 location: Some(Location {
4582 label: Label {
4583 text: None,
4584 span: *location,
4585 },
4586 path: path.clone(),
4587 src: src.clone(),
4588 extra_labels: vec![],
4589 }),
4590 }
4591 }
4592
4593 TypeError::UseFnDoesntTakeCallback {
4594 location,
4595 actual_type: None,
4596 }
4597 | TypeError::UseFnIncorrectArity {
4598 location,
4599 expected: 0,
4600 given: 1,
4601 } => {
4602 let text = wrap(
4603 "The function on the right of `<-` here \
4604takes no arguments, but it has to take at least \
4605one argument, a callback function.
4606
4607See: https://tour.gleam.run/advanced-features/use/",
4608 );
4609 Diagnostic {
4610 title: "Incorrect arity".into(),
4611 text,
4612 hint: None,
4613 level: Level::Error,
4614 location: Some(Location {
4615 label: Label {
4616 text: Some("Expected no arguments, got 1".into()),
4617 span: *location,
4618 },
4619 path: path.clone(),
4620 src: src.clone(),
4621 extra_labels: vec![],
4622 }),
4623 }
4624 }
4625
4626 TypeError::UseFnIncorrectArity {
4627 location,
4628 expected,
4629 given,
4630 } => {
4631 let expected_string = match expected {
4632 0 => "no arguments".into(),
4633 1 => "1 argument".into(),
4634 _ => format!("{expected} arguments"),
4635 };
4636 let supplied_arguments = given - 1;
4637 let supplied_arguments_string = match supplied_arguments {
4638 0 => "no arguments".into(),
4639 1 => "1 argument".into(),
4640 _ => format!("{given} arguments"),
4641 };
4642 let label = format!("Expected {expected_string}, got {given}");
4643 let mut text: String = format!(
4644 "The function on the right of `<-` \
4645here takes {expected_string}.\n"
4646 );
4647
4648 if expected > given {
4649 if supplied_arguments == 0 {
4650 text.push_str(
4651 "The only argument that was supplied is \
4652the `use` callback function.\n",
4653 );
4654 } else {
4655 text.push_str("You supplied ");
4656 text.push_str(&supplied_arguments_string);
4657 text.push_str(" and the final one is the `use` callback function.\n");
4658 }
4659 } else {
4660 text.push_str(
4661 "All the arguments have already been supplied, \
4662so it cannot take the `use` callback function as a final argument.\n",
4663 );
4664 }
4665
4666 text.push_str("\nSee: https://tour.gleam.run/advanced-features/use/");
4667
4668 Diagnostic {
4669 title: "Incorrect arity".into(),
4670 text: wrap(&text),
4671 hint: None,
4672 level: Level::Error,
4673 location: Some(Location {
4674 label: Label {
4675 text: Some(label),
4676 span: *location,
4677 },
4678 path: path.clone(),
4679 src: src.clone(),
4680 extra_labels: vec![],
4681 }),
4682 }
4683 }
4684
4685 TypeError::UseFnDoesntTakeCallback {
4686 location,
4687 actual_type: Some(actual),
4688 } => {
4689 let mut printer = Printer::new(names);
4690 let text = wrap_format!(
4691 "The function on the right hand side of `<-` \
4692has to take a callback function as its last argument. \
4693But the last argument of this function has type:
4694
4695 {}
4696
4697See: https://tour.gleam.run/advanced-features/use/",
4698 printer.print_type(actual)
4699 );
4700 Diagnostic {
4701 title: "Type mismatch".into(),
4702 text: wrap(&text),
4703 hint: None,
4704 level: Level::Error,
4705 location: Some(Location {
4706 label: Label {
4707 text: None,
4708 span: *location,
4709 },
4710 path: path.clone(),
4711 src: src.clone(),
4712 extra_labels: vec![],
4713 }),
4714 }
4715 }
4716
4717 TypeError::UseCallbackIncorrectArity {
4718 pattern_location,
4719 call_location,
4720 expected,
4721 given,
4722 } => {
4723 let expected = match expected {
4724 0 => "no arguments".into(),
4725 1 => "1 argument".into(),
4726 _ => format!("{expected} arguments"),
4727 };
4728
4729 let specified = match given {
4730 0 => "none were provided".into(),
4731 1 => "1 was provided".into(),
4732 _ => format!("{given} were provided"),
4733 };
4734
4735 let text = wrap_format!(
4736 "This function takes a callback that expects {expected}. \
4737But {specified} on the left hand side of `<-`.
4738
4739See: https://tour.gleam.run/advanced-features/use/"
4740 );
4741 Diagnostic {
4742 title: "Incorrect arity".into(),
4743 text,
4744 hint: None,
4745 level: Level::Error,
4746 location: Some(Location {
4747 label: Label {
4748 text: None,
4749 span: *call_location,
4750 },
4751 path: path.clone(),
4752 src: src.clone(),
4753 extra_labels: vec![ExtraLabel {
4754 src_info: None,
4755 label: Label {
4756 text: Some(format!("Expected {expected}, got {given}")),
4757 span: *pattern_location,
4758 },
4759 }],
4760 }),
4761 }
4762 }
4763
4764 TypeError::BadName {
4765 location,
4766 name,
4767 kind,
4768 } => {
4769 let kind_str = kind.as_str();
4770 let label = format!("This is not a valid {} name", kind_str.to_lowercase());
4771 let hint = match kind {
4772 Named::Type | Named::TypeAlias | Named::CustomTypeVariant => {
4773 format!(
4774 "{} names start with an uppercase \
4775letter and contain only lowercase letters, numbers, \
4776and uppercase letters.
4777Try: {}",
4778 kind_str,
4779 to_upper_camel_case(name)
4780 )
4781 }
4782 Named::Variable
4783 | Named::TypeVariable
4784 | Named::Argument
4785 | Named::Label
4786 | Named::Constant
4787 | Named::Function => format!(
4788 "{} names start with a lowercase letter \
4789and contain a-z, 0-9, or _.
4790Try: {}",
4791 kind_str,
4792 to_snake_case(name)
4793 ),
4794 Named::Discard => format!(
4795 "{} names start with _ and contain \
4796a-z, 0-9, or _.
4797Try: _{}",
4798 kind_str,
4799 to_snake_case(name)
4800 ),
4801 };
4802
4803 Diagnostic {
4804 title: format!("Invalid {} name", kind_str.to_lowercase()),
4805 text: "".into(),
4806 hint: Some(hint),
4807 level: Level::Error,
4808 location: Some(Location {
4809 label: Label {
4810 text: Some(label),
4811 span: *location,
4812 },
4813 path: path.clone(),
4814 src: src.clone(),
4815 extra_labels: vec![],
4816 }),
4817 }
4818 }
4819
4820 TypeError::AllVariantsDeprecated { location } => {
4821 let text = String::from(
4822 "Consider deprecating the type as a whole.
4823
4824 @deprecated(\"message\")
4825 type Wibble {
4826 Wobble1
4827 Wobble2
4828 }
4829",
4830 );
4831 Diagnostic {
4832 title: "All variants of custom type deprecated.".into(),
4833 text,
4834 hint: None,
4835 level: Level::Error,
4836 location: Some(Location {
4837 label: Label {
4838 text: None,
4839 span: *location,
4840 },
4841 path: path.clone(),
4842 src: src.clone(),
4843 extra_labels: vec![],
4844 }),
4845 }
4846 }
4847 TypeError::DeprecatedVariantOnDeprecatedType { location } => {
4848 let text = wrap(
4849 "This custom type has already been deprecated, so deprecating \
4850one of its variants does nothing.
4851Consider removing the deprecation attribute on the variant.",
4852 );
4853
4854 Diagnostic {
4855 title: "Custom type already deprecated".into(),
4856 text,
4857 hint: None,
4858 level: Level::Error,
4859 location: Some(Location {
4860 label: Label {
4861 text: None,
4862 span: *location,
4863 },
4864 path: path.clone(),
4865 src: src.clone(),
4866 extra_labels: vec![],
4867 }),
4868 }
4869 }
4870
4871 TypeError::EchoWithNoFollowingExpression { location } => Diagnostic {
4872 title: "Invalid echo use".to_string(),
4873 text: wrap("The `echo` keyword should be followed by a value to print."),
4874 hint: None,
4875 level: Level::Error,
4876 location: Some(Location {
4877 label: Label {
4878 text: Some("I was expecting a value after this".into()),
4879 span: *location,
4880 },
4881 path: path.clone(),
4882 src: src.clone(),
4883 extra_labels: vec![],
4884 }),
4885 },
4886
4887 TypeError::StringConcatenationWithAddInt { location } => Diagnostic {
4888 title: "Type mismatch".to_string(),
4889 text: wrap(
4890 "The + operator can only be used on Ints.
4891To join two strings together you can use the <> operator.",
4892 ),
4893 hint: None,
4894 level: Level::Error,
4895 location: Some(Location {
4896 label: Label {
4897 text: Some("Use <> instead".into()),
4898 span: *location,
4899 },
4900 path: path.clone(),
4901 src: src.clone(),
4902 extra_labels: vec![],
4903 }),
4904 },
4905
4906 TypeError::IntOperatorOnFloats { location, operator } => Diagnostic {
4907 title: "Type mismatch".to_string(),
4908 text: wrap_format!("The {} operator can only be used on Ints.", operator.name()),
4909 hint: None,
4910 level: Level::Error,
4911 location: Some(Location {
4912 label: Label {
4913 text: operator
4914 .float_equivalent()
4915 .map(|operator| format!("Use {} instead", operator.name())),
4916 span: *location,
4917 },
4918 path: path.clone(),
4919 src: src.clone(),
4920 extra_labels: vec![],
4921 }),
4922 },
4923
4924 TypeError::FloatOperatorOnInts { location, operator } => Diagnostic {
4925 title: "Type mismatch".to_string(),
4926 text: wrap_format!(
4927 "The {} operator can only be used on Floats.",
4928 operator.name()
4929 ),
4930 hint: None,
4931 level: Level::Error,
4932 location: Some(Location {
4933 label: Label {
4934 text: operator
4935 .int_equivalent()
4936 .map(|operator| format!("Use {} instead", operator.name())),
4937 span: *location,
4938 },
4939 path: path.clone(),
4940 src: src.clone(),
4941 extra_labels: vec![],
4942 }),
4943 },
4944
4945 TypeError::DoubleVariableAssignmentInBitArray { location } => Diagnostic {
4946 title: "Double variable assignment".to_string(),
4947 text: wrap(
4948 "This pattern assigns to two different variables \
4949at once, which is not possible in bit arrays.",
4950 ),
4951 hint: Some(wrap("Remove the `as` assignment.")),
4952 level: Level::Error,
4953 location: Some(Location {
4954 label: Label {
4955 text: None,
4956 span: *location,
4957 },
4958 path: path.clone(),
4959 src: src.clone(),
4960 extra_labels: vec![],
4961 }),
4962 },
4963
4964 TypeError::NonUtf8StringAssignmentInBitArray { location } => Diagnostic {
4965 title: "Non UTF-8 string assignment".to_string(),
4966 text: wrap(
4967 "This pattern assigns a non UTF-8 string to a \
4968variable in a bit array. This is planned to be supported in the future, but we are \
4969unsure of the desired behaviour. Please go to https://github.com/gleam-lang/gleam/issues/4566 \
4970and explain your usecase for this pattern, and how you would expect it to behave.",
4971 ),
4972 hint: None,
4973 level: Level::Error,
4974 location: Some(Location {
4975 label: Label {
4976 text: None,
4977 span: *location,
4978 },
4979 path: path.clone(),
4980 src: src.clone(),
4981 extra_labels: vec![],
4982 }),
4983 },
4984
4985 TypeError::PrivateOpaqueType { location } => Diagnostic {
4986 title: "Private opaque type".to_string(),
4987 text: wrap("Only a public type can be opaque."),
4988 hint: None,
4989 level: Level::Error,
4990 location: Some(Location {
4991 label: Label {
4992 text: Some("You can safely remove this.".to_string()),
4993 span: *location,
4994 },
4995 path: path.clone(),
4996 src: src.clone(),
4997 extra_labels: vec![],
4998 }),
4999 },
5000
5001 TypeError::SrcImportingDevDependency {
5002 location,
5003 importing_module,
5004 imported_module,
5005 package,
5006 } => Diagnostic {
5007 title: "App importing dev dependency".to_string(),
5008 text: wrap_format!(
5009 "The application module `{importing_module}` is \
5010importing the module `{imported_module}`, but `{package}`, the package it \
5011belongs to, is a dev dependency.
5012
5013Dev dependencies are not included in production builds so application \
5014modules should not import them. Perhaps change `{package}` to a regular dependency."
5015 ),
5016 hint: None,
5017 level: Level::Error,
5018 location: Some(Location {
5019 label: Label {
5020 text: None,
5021 span: *location,
5022 },
5023 path: path.clone(),
5024 src: src.clone(),
5025 extra_labels: vec![],
5026 }),
5027 },
5028
5029 TypeError::ExternalTypeWithConstructors { location } => Diagnostic {
5030 title: "External type with constructors".to_string(),
5031 text: wrap_format!(
5032 "This type is annotated with the `@external` annotation, \
5033but it has constructors. The `@external` annotation is only for external types \
5034with no constructors."
5035 ),
5036 hint: Some("Remove the `@external` annotation".into()),
5037 level: Level::Error,
5038 location: Some(Location {
5039 label: Label {
5040 text: None,
5041 span: *location,
5042 },
5043 path: path.clone(),
5044 src: src.clone(),
5045 extra_labels: vec![],
5046 }),
5047 },
5048
5049 TypeError::LowercaseBoolPattern { location } => Diagnostic {
5050 title: "Lowercase bool pattern".to_string(),
5051 text: "".into(),
5052 hint: Some(
5053 "In Gleam bool literals are `True` and `False`.
5054See: https://tour.gleam.run/basics/bools/"
5055 .into(),
5056 ),
5057 level: Level::Error,
5058 location: Some(Location {
5059 label: Label {
5060 text: Some("This is not a bool".into()),
5061 span: *location,
5062 },
5063 path: path.clone(),
5064 src: src.clone(),
5065 extra_labels: vec![],
5066 }),
5067 },
5068
5069 TypeError::TodoConstant { location } => Diagnostic {
5070 title: "Constant todo found".to_string(),
5071 text: wrap(
5072 "This code will crash if it is run. \
5073Be sure to finish it before running your program.",
5074 ),
5075 hint: None,
5076 level: Level::Error,
5077 location: Some(Location {
5078 label: Label {
5079 text: Some("This code is incomplete".into()),
5080 span: *location,
5081 },
5082 path: path.clone(),
5083 src: src.clone(),
5084 extra_labels: vec![],
5085 }),
5086 },
5087 })
5088}
5089
5090fn std_io_error_kind_text(kind: &std::io::ErrorKind) -> String {
5091 use std::io::ErrorKind;
5092 match kind {
5093 ErrorKind::NotFound => "Could not find the stdio stream".into(),
5094 ErrorKind::PermissionDenied => "Permission was denied".into(),
5095 ErrorKind::ConnectionRefused => "Connection was refused".into(),
5096 ErrorKind::ConnectionReset => "Connection was reset".into(),
5097 ErrorKind::ConnectionAborted => "Connection was aborted".into(),
5098 ErrorKind::NotConnected => "Was not connected".into(),
5099 ErrorKind::AddrInUse => "The stream was already in use".into(),
5100 ErrorKind::AddrNotAvailable => "The stream was not available".into(),
5101 ErrorKind::BrokenPipe => "The pipe was broken".into(),
5102 ErrorKind::AlreadyExists => "A handle to the stream already exists".into(),
5103 ErrorKind::WouldBlock => "This operation would block when it was requested not to".into(),
5104 ErrorKind::InvalidInput => "Some parameter was invalid".into(),
5105 ErrorKind::InvalidData => "The data was invalid. Check that the encoding is UTF-8".into(),
5106 ErrorKind::TimedOut => "The operation timed out".into(),
5107 ErrorKind::WriteZero => {
5108 "An attempt was made to write, but all bytes could not be written".into()
5109 }
5110 ErrorKind::Interrupted => "The operation was interrupted".into(),
5111 ErrorKind::UnexpectedEof => "The end of file was reached before it was expected".into(),
5112 ErrorKind::HostUnreachable
5113 | ErrorKind::NetworkUnreachable
5114 | ErrorKind::NetworkDown
5115 | ErrorKind::NotADirectory
5116 | ErrorKind::IsADirectory
5117 | ErrorKind::DirectoryNotEmpty
5118 | ErrorKind::ReadOnlyFilesystem
5119 | ErrorKind::StaleNetworkFileHandle
5120 | ErrorKind::StorageFull
5121 | ErrorKind::NotSeekable
5122 | ErrorKind::QuotaExceeded
5123 | ErrorKind::FileTooLarge
5124 | ErrorKind::ResourceBusy
5125 | ErrorKind::ExecutableFileBusy
5126 | ErrorKind::Deadlock
5127 | ErrorKind::CrossesDevices
5128 | ErrorKind::TooManyLinks
5129 | ErrorKind::InvalidFilename
5130 | ErrorKind::ArgumentListTooLong
5131 | ErrorKind::Unsupported
5132 | ErrorKind::OutOfMemory
5133 | ErrorKind::Other
5134 | _ => "An unknown error occurred".into(),
5135 }
5136}
5137
5138fn write_cycle(buffer: &mut String, cycle: &[EcoString]) {
5139 buffer.push_str(
5140 "
5141 ┌─────┐\n",
5142 );
5143 for (index, name) in cycle.iter().enumerate() {
5144 if index != 0 {
5145 buffer.push_str(" │ ↓\n");
5146 }
5147 buffer.push_str(" │ ");
5148 buffer.push_str(name);
5149 buffer.push('\n');
5150 }
5151 buffer.push_str(" └─────┘\n");
5152}
5153
5154fn hint_alternative_operator(op: &BinOp, given: &Type) -> Option<String> {
5155 match op {
5156 BinOp::AddInt if given.is_float() => Some(hint_numeric_message("+.", "Float")),
5157 BinOp::DivInt if given.is_float() => Some(hint_numeric_message("/.", "Float")),
5158 BinOp::GtEqInt if given.is_float() => Some(hint_numeric_message(">=.", "Float")),
5159 BinOp::GtInt if given.is_float() => Some(hint_numeric_message(">.", "Float")),
5160 BinOp::LtEqInt if given.is_float() => Some(hint_numeric_message("<=.", "Float")),
5161 BinOp::LtInt if given.is_float() => Some(hint_numeric_message("<.", "Float")),
5162 BinOp::MultInt if given.is_float() => Some(hint_numeric_message("*.", "Float")),
5163 BinOp::SubInt if given.is_float() => Some(hint_numeric_message("-.", "Float")),
5164
5165 BinOp::AddFloat if given.is_int() => Some(hint_numeric_message("+", "Int")),
5166 BinOp::DivFloat if given.is_int() => Some(hint_numeric_message("/", "Int")),
5167 BinOp::GtEqFloat if given.is_int() => Some(hint_numeric_message(">=", "Int")),
5168 BinOp::GtFloat if given.is_int() => Some(hint_numeric_message(">", "Int")),
5169 BinOp::LtEqFloat if given.is_int() => Some(hint_numeric_message("<=", "Int")),
5170 BinOp::LtFloat if given.is_int() => Some(hint_numeric_message("<", "Int")),
5171 BinOp::MultFloat if given.is_int() => Some(hint_numeric_message("*", "Int")),
5172 BinOp::SubFloat if given.is_int() => Some(hint_numeric_message("-", "Int")),
5173
5174 BinOp::AddInt if given.is_string() => Some(hint_string_message()),
5175 BinOp::AddFloat if given.is_string() => Some(hint_string_message()),
5176
5177 BinOp::And
5178 | BinOp::Or
5179 | BinOp::Eq
5180 | BinOp::NotEq
5181 | BinOp::LtInt
5182 | BinOp::LtEqInt
5183 | BinOp::LtFloat
5184 | BinOp::LtEqFloat
5185 | BinOp::GtEqInt
5186 | BinOp::GtInt
5187 | BinOp::GtEqFloat
5188 | BinOp::GtFloat
5189 | BinOp::AddInt
5190 | BinOp::AddFloat
5191 | BinOp::SubInt
5192 | BinOp::SubFloat
5193 | BinOp::MultInt
5194 | BinOp::MultFloat
5195 | BinOp::DivInt
5196 | BinOp::DivFloat
5197 | BinOp::RemainderInt
5198 | BinOp::Concatenate => None,
5199 }
5200}
5201
5202fn hint_wrap_value_in_result(expected: &Arc<Type>, given: &Arc<Type>) -> Option<String> {
5203 let expected = collapse_links(expected.clone());
5204 let (expected_ok_type, expected_error_type) = expected.result_types()?;
5205
5206 if given.same_as(expected_ok_type.as_ref()) {
5207 Some("Did you mean to wrap this in an `Ok`?".into())
5208 } else if given.same_as(expected_error_type.as_ref()) {
5209 Some("Did you mean to wrap this in an `Error`?".into())
5210 } else {
5211 None
5212 }
5213}
5214
5215fn hint_numeric_message(alt: &str, type_: &str) -> String {
5216 format!("The {alt} operator can be used with {type_}s\n")
5217}
5218
5219fn hint_string_message() -> String {
5220 wrap("Strings can be joined using the `<>` operator.")
5221}
5222
5223#[derive(Debug, Clone, PartialEq, Eq)]
5224pub struct Unformatted {
5225 pub source: Utf8PathBuf,
5226 pub destination: Utf8PathBuf,
5227 pub input: EcoString,
5228 pub output: String,
5229}
5230
5231pub fn wrap(text: &str) -> String {
5232 let mut result = String::with_capacity(text.len());
5233
5234 for (i, line) in wrap_text(text, 75).iter().enumerate() {
5235 if i > 0 {
5236 result.push('\n');
5237 }
5238 result.push_str(line);
5239 }
5240
5241 result
5242}
5243
5244fn wrap_text(text: &str, width: usize) -> Vec<Cow<'_, str>> {
5245 let mut lines: Vec<Cow<'_, str>> = Vec::new();
5246 for line in text.split('\n') {
5247 // check if line needs to be broken
5248 match line.len() > width {
5249 false => lines.push(Cow::from(line)),
5250 true => {
5251 let mut new_lines = break_line(line, width);
5252 lines.append(&mut new_lines);
5253 }
5254 }
5255 }
5256
5257 lines
5258}
5259
5260fn break_line(line: &str, width: usize) -> Vec<Cow<'_, str>> {
5261 let mut lines: Vec<Cow<'_, str>> = Vec::new();
5262 let mut newline = String::from("");
5263
5264 // split line by spaces
5265 for (i, word) in line.split(' ').enumerate() {
5266 let is_new_line = i < 1 || newline.is_empty();
5267
5268 let can_add_word = match is_new_line {
5269 true => newline.len() + word.len() <= width,
5270 // +1 accounts for space added before word
5271 false => newline.len() + (word.len() + 1) <= width,
5272 };
5273
5274 if can_add_word {
5275 if !is_new_line {
5276 newline.push(' ');
5277 }
5278 newline.push_str(word);
5279 } else {
5280 // word too big, save existing line if present
5281 if !newline.is_empty() {
5282 // save current line and reset it
5283 lines.push(Cow::from(newline.to_owned()));
5284 newline.clear();
5285 }
5286
5287 // then save word to a new line or break it
5288 match word.len() > width {
5289 false => newline.push_str(word),
5290 true => {
5291 let (mut newlines, remainder) = break_word(word, width);
5292 lines.append(&mut newlines);
5293 newline.push_str(remainder);
5294 }
5295 }
5296 }
5297 }
5298
5299 // save last line after loop finishes
5300 if !newline.is_empty() {
5301 lines.push(Cow::from(newline));
5302 }
5303
5304 lines
5305}
5306
5307// breaks word into n lines based on width. Returns list of new lines and remainder
5308fn break_word(word: &str, width: usize) -> (Vec<Cow<'_, str>>, &str) {
5309 let mut new_lines: Vec<Cow<'_, str>> = Vec::new();
5310 // Split on a char boundary so a multi-byte UTF-8 char straddling `width`
5311 // doesn't cause a panic.
5312 let (first, mut remainder) = word.split_at(word.floor_char_boundary(width));
5313 new_lines.push(Cow::from(first));
5314
5315 // split remainder until it's small enough
5316 while remainder.len() > width {
5317 let (first, second) = remainder.split_at(remainder.floor_char_boundary(width));
5318 new_lines.push(Cow::from(first));
5319 remainder = second;
5320 }
5321
5322 (new_lines, remainder)
5323}