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