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