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