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