Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-core / src / error.rs
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}