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