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 5313 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, 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 => { 1913 let text = wrap_format!( 1914 "Unable to decrypt the local Hex API key with the given password. 1915Did you make a typo? Please try again." 1916 ); 1917 vec![Diagnostic { 1918 title: "Failed to decrypt local Hex API key".into(), 1919 text, 1920 hint: Some( 1921 "If you have forgotten your local password you can create a new one 1922with `gleam hex authenticate`." 1923 .into(), 1924 ), 1925 level: Level::Error, 1926 location: None, 1927 }] 1928 } 1929 1930 Error::NonUtf8Path { path } => { 1931 let text = format!( 1932 "Encountered a non UTF-8 path '{}', but only UTF-8 paths are supported.", 1933 path.to_string_lossy() 1934 ); 1935 vec![Diagnostic { 1936 title: "Non UTF-8 Path Encountered".into(), 1937 text, 1938 level: Level::Error, 1939 location: None, 1940 hint: None, 1941 }] 1942 } 1943 1944 Error::GitInitialization { error } => { 1945 let text = format!( 1946 "An error occurred while trying make a git repository for this project: 1947 1948 {error}" 1949 ); 1950 vec![Diagnostic { 1951 title: "Failed to initialize git repository".into(), 1952 text, 1953 hint: None, 1954 level: Level::Error, 1955 location: None, 1956 }] 1957 } 1958 1959 Error::Type { 1960 skipped_modules: _, 1961 failed_modules, 1962 } => failed_modules 1963 .values() 1964 .sorted_by_key(|failed_module| &failed_module.path) 1965 .flat_map(failed_module_diagnostics) 1966 .collect_vec(), 1967 1968 Error::Parse { path, src, error } => { 1969 let location = if error.error == ParseErrorType::UnexpectedEof { 1970 SrcSpan { 1971 start: (src.len() - 1) as u32, 1972 end: (src.len() - 1) as u32, 1973 } 1974 } else { 1975 error.location 1976 }; 1977 1978 let title = String::from("Syntax error"); 1979 let ParseErrorDetails { 1980 text, 1981 label_text, 1982 extra_labels, 1983 hint, 1984 } = error.error.details(); 1985 vec![Diagnostic { 1986 title, 1987 text, 1988 level: Level::Error, 1989 location: Some(Location { 1990 src: src.clone(), 1991 path: path.clone(), 1992 label: Label { 1993 text: Some(label_text.into()), 1994 span: location, 1995 }, 1996 extra_labels, 1997 }), 1998 hint, 1999 }] 2000 } 2001 2002 Error::ImportCycle { modules } => { 2003 let first_location = &modules.first().1; 2004 let rest_locations = modules 2005 .iter() 2006 .skip(1) 2007 .map(|(_, l)| ExtraLabel { 2008 label: Label { 2009 text: Some("Imported here".into()), 2010 span: l.location, 2011 }, 2012 src_info: Some((l.src.clone(), l.path.clone())), 2013 }) 2014 .collect_vec(); 2015 let mut text = "The import statements for these modules form a cycle: 2016" 2017 .into(); 2018 let mod_names = modules.iter().map(|m| m.0.clone()).collect_vec(); 2019 write_cycle(&mut text, &mod_names); 2020 text.push_str(&wrap( 2021 "Gleam doesn't support dependency cycles like these, \ 2022please break the cycle to continue.", 2023 )); 2024 vec![Diagnostic { 2025 title: "Import cycle".into(), 2026 text, 2027 hint: None, 2028 level: Level::Error, 2029 location: Some(Location { 2030 label: Label { 2031 text: Some("Imported here".into()), 2032 span: first_location.location, 2033 }, 2034 path: first_location.path.clone(), 2035 src: first_location.src.clone(), 2036 extra_labels: rest_locations, 2037 }), 2038 }] 2039 } 2040 2041 Error::PackageCycle { packages } => { 2042 let mut text = "The dependencies for these packages form a cycle: 2043" 2044 .into(); 2045 write_cycle(&mut text, packages); 2046 text.push_str(&wrap( 2047 "Gleam doesn't support dependency cycles like these, \ 2048please break the cycle to continue.", 2049 )); 2050 vec![Diagnostic { 2051 title: "Dependency cycle".into(), 2052 text, 2053 hint: None, 2054 level: Level::Error, 2055 location: None, 2056 }] 2057 } 2058 2059 Error::UnknownImport { import, details } => { 2060 let UnknownImportDetails { 2061 module, 2062 location, 2063 path, 2064 src, 2065 modules, 2066 } = details.as_ref(); 2067 let text = wrap(&format!( 2068 "The module `{module}` is trying to import the module \ 2069`{import}`, but it cannot be found." 2070 )); 2071 vec![Diagnostic { 2072 title: "Unknown import".into(), 2073 text, 2074 hint: None, 2075 level: Level::Error, 2076 location: Some(Location { 2077 label: Label { 2078 text: did_you_mean(import, modules), 2079 span: *location, 2080 }, 2081 path: path.clone(), 2082 src: src.clone(), 2083 extra_labels: vec![], 2084 }), 2085 }] 2086 } 2087 2088 Error::StandardIo { action, err } => { 2089 let err = match err { 2090 Some(e) => format!( 2091 "\nThe error message from the stdio library was:\n\n {}\n", 2092 std_io_error_kind_text(e) 2093 ), 2094 None => "".into(), 2095 }; 2096 vec![Diagnostic { 2097 title: "Standard IO failure".into(), 2098 text: format!( 2099 "An error occurred while trying to {}: 2100 2101{}", 2102 action.text(), 2103 err, 2104 ), 2105 hint: None, 2106 location: None, 2107 level: Level::Error, 2108 }] 2109 } 2110 2111 Error::Format { problem_files } => { 2112 let files: Vec<_> = problem_files 2113 .iter() 2114 .map(|formatted| formatted.source.as_str()) 2115 .map(|p| format!(" - {p}")) 2116 .sorted() 2117 .collect(); 2118 let mut text = files.iter().join("\n"); 2119 text.push('\n'); 2120 vec![Diagnostic { 2121 title: "These files have not been formatted".into(), 2122 text, 2123 hint: None, 2124 location: None, 2125 level: Level::Error, 2126 }] 2127 } 2128 2129 Error::ForbiddenWarnings { count } => { 2130 let word_warning = match count { 2131 1 => "warning", 2132 _ => "warnings", 2133 }; 2134 let text = "Your project was compiled with the `--warnings-as-errors` flag. 2135Fix the warnings and try again." 2136 .into(); 2137 vec![Diagnostic { 2138 title: format!("{count} {word_warning} generated."), 2139 text, 2140 hint: None, 2141 location: None, 2142 level: Level::Error, 2143 }] 2144 } 2145 2146 Error::DownloadPackageError { 2147 package_name, 2148 package_version, 2149 error, 2150 } => { 2151 let text = format!( 2152 "A problem was encountered when downloading `{package_name}` {package_version}. 2153The error from the package manager client was: 2154 2155 {error}" 2156 ); 2157 vec![Diagnostic { 2158 title: "Failed to download package".into(), 2159 text, 2160 hint: None, 2161 location: None, 2162 level: Level::Error, 2163 }] 2164 } 2165 2166 Error::Http(error) => { 2167 let text = format!( 2168 "A HTTP request failed. 2169The error from the HTTP client was: 2170 2171 {error}" 2172 ); 2173 vec![Diagnostic { 2174 title: "HTTP error".into(), 2175 text, 2176 hint: None, 2177 location: None, 2178 level: Level::Error, 2179 }] 2180 } 2181 2182 Error::InvalidVersionFormat { input, error } => { 2183 let text = format!( 2184 "I was unable to parse the version \"{input}\". 2185The error from the parser was: 2186 2187 {error}" 2188 ); 2189 vec![Diagnostic { 2190 title: "Invalid version format".into(), 2191 text, 2192 hint: None, 2193 location: None, 2194 level: Level::Error, 2195 }] 2196 } 2197 2198 Error::IncompatibleLockedVersion { error } => { 2199 let text = format!( 2200 "There is an incompatiblity between a version specified in 2201manifest.toml and a version range specified in gleam.toml: 2202 2203 {error}" 2204 ); 2205 vec![Diagnostic { 2206 title: "Incompatible locked version".into(), 2207 text, 2208 hint: None, 2209 location: None, 2210 level: Level::Error, 2211 }] 2212 } 2213 2214 Error::DependencyCanonicalizationFailed(package) => { 2215 let text = format!("Local package `{package}` has no canonical path"); 2216 2217 vec![Diagnostic { 2218 title: "Failed to create canonical path".into(), 2219 text, 2220 hint: None, 2221 location: None, 2222 level: Level::Error, 2223 }] 2224 } 2225 2226 Error::DependencyResolutionError(error) => vec![Diagnostic { 2227 title: "Dependency resolution failed".into(), 2228 text: wrap(error), 2229 hint: None, 2230 location: None, 2231 level: Level::Error, 2232 }], 2233 2234 Error::DependencyResolutionNoSolution { 2235 root_package_name, 2236 derivation_tree, 2237 } => { 2238 let text = wrap( 2239 &DerivationTreePrinter::new( 2240 root_package_name.clone(), 2241 derivation_tree.0.clone(), 2242 ) 2243 .print(), 2244 ); 2245 vec![Diagnostic { 2246 title: "Dependency resolution failed".into(), 2247 text, 2248 hint: None, 2249 location: None, 2250 level: Level::Error, 2251 }] 2252 } 2253 2254 Error::WrongDependencyProvided { 2255 path, 2256 expected, 2257 found, 2258 } => { 2259 let text = format!( 2260 "Expected package `{expected}` at path `{path}` but found `{found}` instead.", 2261 ); 2262 2263 vec![Diagnostic { 2264 title: "Wrong dependency provided".into(), 2265 text, 2266 hint: None, 2267 location: None, 2268 level: Level::Error, 2269 }] 2270 } 2271 2272 Error::ProvidedDependencyConflict { 2273 package, 2274 source_1, 2275 source_2, 2276 } => { 2277 let text = format!( 2278 "The package `{package}` is provided as both `{source_1}` and `{source_2}`.", 2279 ); 2280 2281 vec![Diagnostic { 2282 title: "Conflicting provided dependencies".into(), 2283 text, 2284 hint: None, 2285 location: None, 2286 level: Level::Error, 2287 }] 2288 } 2289 2290 Error::GitDependencyPathNotFound { 2291 package, 2292 path, 2293 repo, 2294 } => { 2295 let text = format!( 2296 "The path `{path}` does not exist in the git repository `{repo}` \ 2297for package `{package}`." 2298 ); 2299 2300 vec![Diagnostic { 2301 title: "Git dependency path not found".into(), 2302 text, 2303 hint: None, 2304 location: None, 2305 level: Level::Error, 2306 }] 2307 } 2308 2309 Error::DuplicateDependency(name) => { 2310 let text = format!( 2311 "The package `{name}` is specified in both the dependencies and 2312dev_dependencies sections of the gleam.toml file." 2313 ); 2314 vec![Diagnostic { 2315 title: "Dependency duplicated".into(), 2316 text, 2317 hint: None, 2318 location: None, 2319 level: Level::Error, 2320 }] 2321 } 2322 2323 Error::MissingHexPublishFields { 2324 description_missing, 2325 licence_missing, 2326 } => { 2327 let mut text = 2328 "Licence information and package description are required to publish a 2329package to Hex.\n" 2330 .to_string(); 2331 text.push_str(if *description_missing && *licence_missing { 2332 r#"Add the licences and description fields to your gleam.toml file. 2333 2334description = "" 2335licences = ["Apache-2.0"]"# 2336 } else if *description_missing { 2337 r#"Add the description field to your gleam.toml file. 2338 2339description = """# 2340 } else { 2341 r#"Add the licences field to your gleam.toml file. 2342 2343licences = ["Apache-2.0"]"# 2344 }); 2345 vec![Diagnostic { 2346 title: "Missing required package fields".into(), 2347 text, 2348 hint: None, 2349 location: None, 2350 level: Level::Error, 2351 }] 2352 } 2353 2354 Error::PublishNonHexDependencies { package } => vec![Diagnostic { 2355 title: "Unpublished dependencies".into(), 2356 text: wrap_format!( 2357 "The package cannot be published to Hex \ 2358because dependency `{package}` is not a Hex dependency.", 2359 ), 2360 hint: None, 2361 location: None, 2362 level: Level::Error, 2363 }], 2364 2365 Error::UnsupportedBuildTool { 2366 package, 2367 build_tools, 2368 } => { 2369 let text = wrap_format!( 2370 "The package `{}` cannot be built as it does not use \ 2371a build tool supported by Gleam. It uses {:?}. 2372 2373If you would like us to support this package please let us know by opening an \ 2374issue in our tracker: https://github.com/gleam-lang/gleam/issues", 2375 package, 2376 build_tools 2377 ); 2378 vec![Diagnostic { 2379 title: "Unsupported build tool".into(), 2380 text, 2381 hint: None, 2382 location: None, 2383 level: Level::Error, 2384 }] 2385 } 2386 2387 Error::FailedToOpenDocs { path, error } => { 2388 let error = format!("\nThe error message from the library was:\n\n {error}\n"); 2389 let text = format!( 2390 "An error occurred while trying to open the docs: 2391 2392 {path} 2393{error}", 2394 ); 2395 vec![Diagnostic { 2396 title: "Failed to open docs".into(), 2397 text, 2398 hint: None, 2399 level: Level::Error, 2400 location: None, 2401 }] 2402 } 2403 2404 Error::IncompatibleCompilerVersion { 2405 package, 2406 required_version, 2407 gleam_version, 2408 } => { 2409 let text = format!( 2410 "The package `{package}` requires a Gleam version \ 2411satisfying {required_version} but you are using v{gleam_version}.", 2412 ); 2413 vec![Diagnostic { 2414 title: "Incompatible Gleam version".into(), 2415 text, 2416 hint: None, 2417 location: None, 2418 level: Level::Error, 2419 }] 2420 } 2421 2422 Error::InvalidRuntime { 2423 target, 2424 invalid_runtime, 2425 } => { 2426 let text = format!( 2427 "Invalid runtime for {target} target: {invalid_runtime}", 2428 target = target.as_presentable_str(), 2429 invalid_runtime = invalid_runtime.as_presentable_str(), 2430 ); 2431 2432 let hint = match target { 2433 Target::JavaScript => { 2434 Some("available runtimes for JavaScript are: node, deno.".into()) 2435 } 2436 Target::Erlang => Some( 2437 "You can not set a runtime for Erlang. Did you mean to target JavaScript?" 2438 .into(), 2439 ), 2440 }; 2441 2442 vec![Diagnostic { 2443 title: format!( 2444 "Invalid runtime for {target}", 2445 target = target.as_presentable_str(), 2446 ), 2447 text, 2448 hint, 2449 location: None, 2450 level: Level::Error, 2451 }] 2452 } 2453 2454 Error::JavaScriptPreludeRequired => vec![Diagnostic { 2455 title: "JavaScript prelude required".into(), 2456 text: "The --javascript-prelude flag must be given when compiling to JavaScript." 2457 .into(), 2458 level: Level::Error, 2459 location: None, 2460 hint: None, 2461 }], 2462 Error::CorruptManifest => vec![Diagnostic { 2463 title: "Corrupt manifest.toml".into(), 2464 text: "The `manifest.toml` file is corrupt.".into(), 2465 level: Level::Error, 2466 location: None, 2467 hint: Some("Please run `gleam update` to fix it.".into()), 2468 }], 2469 2470 Error::GleamModuleWouldOverwriteStandardErlangModule { name, path } => { 2471 vec![Diagnostic { 2472 title: "Erlang module name collision".into(), 2473 text: wrap_format!( 2474 "The module `{path}` compiles to an Erlang module \ 2475named `{name}`. 2476 2477By default Erlang includes a module with the same name so if we were \ 2478to compile and load your module it would overwrite the Erlang one, potentially \ 2479causing confusing errors and crashes." 2480 ), 2481 level: Level::Error, 2482 location: None, 2483 hint: Some("Rename this module and try again.".into()), 2484 }] 2485 } 2486 2487 Error::HexPublishReplaceRequired { version } => vec![Diagnostic { 2488 title: "Version already published".into(), 2489 text: wrap_format!( 2490 "Version v{version} has already been published. 2491This release has been recently published so you can replace it \ 2492or you can publish it using a different version number" 2493 ), 2494 level: Level::Error, 2495 location: None, 2496 hint: Some( 2497 "Please add the --replace flag if you want to replace the release.".into(), 2498 ), 2499 }], 2500 Error::HexPublishAccessDenied { name, version } => vec![Diagnostic { 2501 title: "Access denied".to_string(), 2502 text: wrap_format!( 2503 "You are not one of the maintainers of the {name} package, so \ 2504you cannot publish a new {version} version. Are you logged into the correct account? 2505 2506If you are trying to publish a new package then you will need to pick another, \ 2507as this one is already in use. 2508" 2509 ), 2510 level: Level::Error, 2511 location: None, 2512 hint: None, 2513 }], 2514 2515 Error::CannotAddSelfAsDependency { name } => vec![Diagnostic { 2516 title: "Dependency cycle".into(), 2517 text: wrap_format!( 2518 "A package cannot depend on itself, so you cannot \ 2519add `gleam add {name}` in this project." 2520 ), 2521 level: Level::Error, 2522 location: None, 2523 hint: None, 2524 }], 2525 } 2526 } 2527} 2528 2529fn failed_module_diagnostics(failed_module: &FailedModule) -> impl Iterator<Item = Diagnostic> { 2530 use crate::type_::Error as TypeError; 2531 let FailedModule { 2532 path, 2533 src, 2534 errors, 2535 names, 2536 } = failed_module; 2537 2538 errors.iter().map(|error| match error { 2539 TypeError::LiteralFloatOutOfRange { location, .. } => Diagnostic { 2540 title: "Float outside of valid range".into(), 2541 text: wrap( 2542 "This float value is too large to be represented by \ 2543a floating point type: float values must be in the range -1.7976931348623157e308 \ 2544- 1.7976931348623157e308.", 2545 ), 2546 hint: None, 2547 level: Level::Error, 2548 location: Some(Location { 2549 label: Label { 2550 text: None, 2551 span: *location, 2552 }, 2553 path: path.clone(), 2554 src: src.clone(), 2555 extra_labels: vec![], 2556 }), 2557 }, 2558 2559 TypeError::InvalidImport { 2560 location, 2561 importing_module, 2562 imported_module, 2563 kind: InvalidImportKind::SrcImportingTest, 2564 } => { 2565 let text = wrap_format!( 2566 "The application module `{importing_module}` \ 2567is importing the test module `{imported_module}`. 2568 2569Test modules are not included in production builds so application \ 2570modules cannot import them. Perhaps move the `{imported_module}` \ 2571module to the src directory.", 2572 ); 2573 2574 Diagnostic { 2575 title: "App importing test module".into(), 2576 text, 2577 hint: None, 2578 level: Level::Error, 2579 location: Some(Location { 2580 label: Label { 2581 text: Some("Imported here".into()), 2582 span: *location, 2583 }, 2584 path: path.clone(), 2585 src: src.clone(), 2586 extra_labels: vec![], 2587 }), 2588 } 2589 } 2590 2591 TypeError::InvalidImport { 2592 location, 2593 importing_module, 2594 imported_module, 2595 kind: InvalidImportKind::SrcImportingDev, 2596 } => { 2597 let text = wrap_format!( 2598 "The application module `{importing_module}` \ 2599is importing the development module `{imported_module}`. 2600 2601Development modules are not included in production builds so application \ 2602modules cannot import them. Perhaps move the `{imported_module}` \ 2603module to the src directory.", 2604 ); 2605 2606 Diagnostic { 2607 title: "App importing dev module".into(), 2608 text, 2609 hint: None, 2610 level: Level::Error, 2611 location: Some(Location { 2612 label: Label { 2613 text: Some("Imported here".into()), 2614 span: *location, 2615 }, 2616 path: path.clone(), 2617 src: src.clone(), 2618 extra_labels: vec![], 2619 }), 2620 } 2621 } 2622 2623 TypeError::InvalidImport { 2624 location, 2625 importing_module, 2626 imported_module, 2627 kind: InvalidImportKind::DevImportingTest, 2628 } => { 2629 let text = wrap_format!( 2630 "The development module `{importing_module}` \ 2631is importing the test module `{imported_module}`. 2632 2633Test modules should only contain test-related code, and not general development \ 2634code. Perhaps move the `{imported_module}` module to the dev directory.", 2635 ); 2636 2637 Diagnostic { 2638 title: "Dev importing test module".into(), 2639 text, 2640 hint: None, 2641 level: Level::Error, 2642 location: Some(Location { 2643 label: Label { 2644 text: Some("Imported here".into()), 2645 span: *location, 2646 }, 2647 path: path.clone(), 2648 src: src.clone(), 2649 extra_labels: vec![], 2650 }), 2651 } 2652 } 2653 2654 TypeError::UnknownLabels { 2655 unknown, 2656 valid, 2657 supplied, 2658 } => { 2659 let other_labels: Vec<_> = valid 2660 .iter() 2661 .filter(|label| !supplied.contains(label)) 2662 .cloned() 2663 .collect(); 2664 2665 let title = if unknown.len() > 1 { 2666 "Unknown labels" 2667 } else { 2668 "Unknown label" 2669 } 2670 .into(); 2671 2672 let mut labels = unknown.iter().map(|(label, location)| { 2673 let text = 2674 did_you_mean(label, &other_labels).unwrap_or_else(|| "Unexpected label".into()); 2675 Label { 2676 text: Some(text), 2677 span: *location, 2678 } 2679 }); 2680 let label = labels.next().expect("Unknown labels first label"); 2681 let extra_labels = labels 2682 .map(|label| ExtraLabel { 2683 src_info: None, 2684 label, 2685 }) 2686 .collect(); 2687 let text = if valid.is_empty() { 2688 "This constructor does not accept any labelled arguments.".into() 2689 } else if other_labels.is_empty() { 2690 "You have already supplied all the labelled arguments that this 2691constructor accepts." 2692 .into() 2693 } else { 2694 let mut label_text = String::from("It accepts these labels:\n"); 2695 for label in other_labels.iter().sorted() { 2696 label_text.push_str("\n "); 2697 label_text.push_str(label); 2698 } 2699 label_text 2700 }; 2701 Diagnostic { 2702 title, 2703 text, 2704 hint: None, 2705 level: Level::Error, 2706 location: Some(Location { 2707 label, 2708 path: path.clone(), 2709 src: src.clone(), 2710 extra_labels, 2711 }), 2712 } 2713 } 2714 2715 TypeError::UnexpectedLabelledArg { 2716 location, 2717 label, 2718 kind, 2719 } => { 2720 let kind = match kind { 2721 UnexpectedLabelledArgKind::FunctionParameter => "function", 2722 UnexpectedLabelledArgKind::RecordConstructorArgument => "record constructor", 2723 }; 2724 let text = format!( 2725 "This argument has been given a label but the {kind} does 2726not expect any. Please remove the label `{label}`." 2727 ); 2728 Diagnostic { 2729 title: "Unexpected labelled argument".into(), 2730 text, 2731 hint: None, 2732 level: Level::Error, 2733 location: Some(Location { 2734 label: Label { 2735 text: None, 2736 span: *location, 2737 }, 2738 path: path.clone(), 2739 src: src.clone(), 2740 extra_labels: vec![], 2741 }), 2742 } 2743 } 2744 2745 TypeError::PositionalArgumentAfterLabelled { location } => { 2746 let text = wrap( 2747 "This unlabeled argument has been \ 2748supplied after a labelled argument. 2749Once a labelled argument has been supplied all following arguments must 2750also be labelled.", 2751 ); 2752 2753 Diagnostic { 2754 title: "Unexpected positional argument".into(), 2755 text, 2756 hint: None, 2757 level: Level::Error, 2758 location: Some(Location { 2759 label: Label { 2760 text: None, 2761 span: *location, 2762 }, 2763 path: path.clone(), 2764 src: src.clone(), 2765 extra_labels: vec![], 2766 }), 2767 } 2768 } 2769 2770 TypeError::DuplicateImport { 2771 location, 2772 previous_location, 2773 name, 2774 } => { 2775 let text = format!( 2776 "`{name}` has been imported multiple times. 2777Names in a Gleam module must be unique so one will need to be renamed." 2778 ); 2779 Diagnostic { 2780 title: "Duplicate import".into(), 2781 text, 2782 hint: None, 2783 level: Level::Error, 2784 location: Some(Location { 2785 label: Label { 2786 text: Some("Reimported here".into()), 2787 span: *location, 2788 }, 2789 path: path.clone(), 2790 src: src.clone(), 2791 extra_labels: vec![ExtraLabel { 2792 src_info: None, 2793 label: Label { 2794 text: Some("First imported here".into()), 2795 span: *previous_location, 2796 }, 2797 }], 2798 }), 2799 } 2800 } 2801 2802 TypeError::DuplicateName { 2803 location_a, 2804 location_b, 2805 name, 2806 .. 2807 } => { 2808 let (first_location, second_location) = if location_a.start < location_b.start { 2809 (location_a, location_b) 2810 } else { 2811 (location_b, location_a) 2812 }; 2813 let text = format!( 2814 "`{name}` has been defined multiple times. 2815Names in a Gleam module must be unique so one will need to be renamed." 2816 ); 2817 Diagnostic { 2818 title: "Duplicate definition".into(), 2819 text, 2820 hint: None, 2821 level: Level::Error, 2822 location: Some(Location { 2823 label: Label { 2824 text: Some("Redefined here".into()), 2825 span: *second_location, 2826 }, 2827 path: path.clone(), 2828 src: src.clone(), 2829 extra_labels: vec![ExtraLabel { 2830 src_info: None, 2831 label: Label { 2832 text: Some("First defined here".into()), 2833 span: *first_location, 2834 }, 2835 }], 2836 }), 2837 } 2838 } 2839 2840 TypeError::DuplicateTypeName { 2841 name, 2842 location, 2843 previous_location, 2844 .. 2845 } => { 2846 let text = format!( 2847 "The type `{name}` has been defined multiple times. 2848Names in a Gleam module must be unique so one will need to be renamed." 2849 ); 2850 Diagnostic { 2851 title: "Duplicate type definition".into(), 2852 text, 2853 hint: None, 2854 level: Level::Error, 2855 location: Some(Location { 2856 label: Label { 2857 text: Some("Redefined here".into()), 2858 span: *location, 2859 }, 2860 path: path.clone(), 2861 src: src.clone(), 2862 extra_labels: vec![ExtraLabel { 2863 src_info: None, 2864 label: Label { 2865 text: Some("First defined here".into()), 2866 span: *previous_location, 2867 }, 2868 }], 2869 }), 2870 } 2871 } 2872 2873 TypeError::DuplicateField { location, label } => { 2874 let text = format!("The label `{label}` has already been defined. Rename this label."); 2875 Diagnostic { 2876 title: "Duplicate label".into(), 2877 text, 2878 hint: None, 2879 level: Level::Error, 2880 location: Some(Location { 2881 label: Label { 2882 text: None, 2883 span: *location, 2884 }, 2885 path: path.clone(), 2886 src: src.clone(), 2887 extra_labels: vec![], 2888 }), 2889 } 2890 } 2891 2892 TypeError::DuplicateArgument { location, label } => { 2893 let text = format!("The labelled argument `{label}` has already been supplied."); 2894 Diagnostic { 2895 title: "Duplicate argument".into(), 2896 text, 2897 hint: None, 2898 level: Level::Error, 2899 location: Some(Location { 2900 label: Label { 2901 text: None, 2902 span: *location, 2903 }, 2904 path: path.clone(), 2905 src: src.clone(), 2906 extra_labels: vec![], 2907 }), 2908 } 2909 } 2910 2911 TypeError::RecursiveType { location } => { 2912 let text = wrap( 2913 "I don't know how to work out what type this \ 2914value has. It seems to be defined in terms of itself.", 2915 ); 2916 Diagnostic { 2917 title: "Recursive type".into(), 2918 text, 2919 hint: Some("Add some type annotations and try again.".into()), 2920 level: Level::Error, 2921 location: Some(Location { 2922 label: Label { 2923 text: None, 2924 span: *location, 2925 }, 2926 path: path.clone(), 2927 src: src.clone(), 2928 extra_labels: vec![], 2929 }), 2930 } 2931 } 2932 2933 TypeError::NotFn { location, type_ } => { 2934 let mut printer = Printer::new(names); 2935 let text = format!( 2936 "This value is being called as a function but its type is:\n\n {}", 2937 printer.print_type(type_) 2938 ); 2939 Diagnostic { 2940 title: "Type mismatch".into(), 2941 text, 2942 hint: None, 2943 level: Level::Error, 2944 location: Some(Location { 2945 label: Label { 2946 text: None, 2947 span: *location, 2948 }, 2949 path: path.clone(), 2950 src: src.clone(), 2951 extra_labels: vec![], 2952 }), 2953 } 2954 } 2955 2956 TypeError::UnknownRecordField { 2957 usage, 2958 location, 2959 type_, 2960 label, 2961 fields, 2962 unknown_field: variants, 2963 } => { 2964 let mut printer = Printer::new(names); 2965 2966 // Give a hint about what type this value has. 2967 let mut text = format!( 2968 "The value being accessed has this type:\n\n {}\n", 2969 printer.print_type(type_) 2970 ); 2971 2972 // Give a hint about what record fields this value has, if any. 2973 if fields.is_empty() { 2974 if variants == &UnknownField::NoFields { 2975 text.push_str("\nIt does not have any fields."); 2976 } else { 2977 text.push_str( 2978 "\nIt does not have fields that are common \ 2979across all variants.", 2980 ); 2981 } 2982 } else { 2983 text.push_str("\nIt has these accessible fields:\n"); 2984 } 2985 for field in fields.iter().sorted() { 2986 text.push_str("\n ."); 2987 text.push_str(field); 2988 } 2989 2990 match variants { 2991 UnknownField::AppearsInAVariant => { 2992 let msg = wrap( 2993 "Note: The field you are trying to access is \ 2994not defined consistently across all variants of this custom type. To fix this, \ 2995ensure that all variants include the field with the same name, position, and \ 2996type.", 2997 ); 2998 text.push_str("\n\n"); 2999 text.push_str(&msg); 3000 } 3001 UnknownField::AppearsInAnImpossibleVariant => { 3002 let msg = wrap( 3003 "Note: The field exists in this custom type \ 3004but is not defined for the current variant. Ensure that you are accessing the \ 3005field on a variant where it is valid.", 3006 ); 3007 text.push_str("\n\n"); 3008 text.push_str(&msg); 3009 } 3010 UnknownField::TrulyUnknown => (), 3011 UnknownField::NoFields => (), 3012 } 3013 3014 // Give a hint about Gleam not having OOP methods if it 3015 // looks like they might be trying to call one. 3016 match usage { 3017 FieldAccessUsage::MethodCall => { 3018 let msg = wrap( 3019 "Gleam is not object oriented, so if you are trying \ 3020to call a method on this value you may want to use the function syntax instead.", 3021 ); 3022 text.push_str("\n\n"); 3023 text.push_str(&msg); 3024 text.push_str("\n\n "); 3025 text.push_str(label); 3026 text.push_str("(value)"); 3027 } 3028 FieldAccessUsage::Other | FieldAccessUsage::RecordUpdate => (), 3029 } 3030 3031 let label = 3032 did_you_mean(label, fields).unwrap_or_else(|| "This field does not exist".into()); 3033 Diagnostic { 3034 title: "Unknown record field".into(), 3035 text, 3036 hint: None, 3037 level: Level::Error, 3038 location: Some(Location { 3039 label: Label { 3040 text: Some(label), 3041 span: *location, 3042 }, 3043 path: path.clone(), 3044 src: src.clone(), 3045 extra_labels: vec![], 3046 }), 3047 } 3048 } 3049 3050 TypeError::CouldNotUnify { 3051 location, 3052 expected, 3053 given, 3054 situation: Some(UnifyErrorSituation::Operator(op)), 3055 } => { 3056 let mut printer = Printer::new(names); 3057 let text = format!( 3058 "The {op} operator expects arguments of this type: 3059 3060 {expected} 3061 3062But this argument has this type: 3063 3064 {given}", 3065 op = op.name(), 3066 expected = printer.print_type(expected), 3067 given = printer.print_type(given), 3068 ); 3069 Diagnostic { 3070 title: "Type mismatch".into(), 3071 text, 3072 hint: hint_alternative_operator(op, given), 3073 level: Level::Error, 3074 location: Some(Location { 3075 label: Label { 3076 text: None, 3077 span: *location, 3078 }, 3079 path: path.clone(), 3080 src: src.clone(), 3081 extra_labels: vec![], 3082 }), 3083 } 3084 } 3085 3086 TypeError::CouldNotUnify { 3087 location, 3088 expected, 3089 given, 3090 situation: Some(UnifyErrorSituation::PipeTypeMismatch), 3091 } => { 3092 // Remap the pipe function type into just the type expected by the pipe. 3093 let expected = expected 3094 .fn_types() 3095 .and_then(|(arguments, _)| arguments.first().cloned()); 3096 3097 // Remap the argument as well, if it's a function. 3098 let given = given 3099 .fn_types() 3100 .and_then(|(arguments, _)| arguments.first().cloned()) 3101 .unwrap_or_else(|| given.clone()); 3102 3103 let mut printer = Printer::new(names); 3104 let text = format!( 3105 "The argument is: 3106 3107 {given} 3108 3109But function expects: 3110 3111 {expected}", 3112 expected = expected 3113 .map(|v| printer.print_type(&v)) 3114 .unwrap_or_else(|| " No arguments".into()), 3115 given = printer.print_type(&given) 3116 ); 3117 3118 Diagnostic { 3119 title: "Type mismatch".into(), 3120 text, 3121 hint: None, 3122 level: Level::Error, 3123 location: Some(Location { 3124 label: Label { 3125 text: Some("This function does not accept the piped type".into()), 3126 span: *location, 3127 }, 3128 path: path.clone(), 3129 src: src.clone(), 3130 extra_labels: vec![], 3131 }), 3132 } 3133 } 3134 3135 TypeError::CouldNotUnify { 3136 location, 3137 expected, 3138 given, 3139 situation, 3140 } => { 3141 let mut printer = Printer::new(names); 3142 let mut text = 3143 if let Some(description) = situation.as_ref().and_then(|s| s.description()) { 3144 let mut text = description.to_string(); 3145 text.push('\n'); 3146 text.push('\n'); 3147 text 3148 } else { 3149 "".into() 3150 }; 3151 text.push_str("Expected type:\n\n "); 3152 text.push_str(&printer.print_type(expected)); 3153 text.push_str("\n\nFound type:\n\n "); 3154 text.push_str(&printer.print_type(given)); 3155 3156 let (main_message_location, main_message_text, extra_labels) = match situation { 3157 // When the mismatch error comes from a case clause we want to highlight the 3158 // entire branch (pattern included) when reporting the error; in addition, 3159 // if the error could be resolved just by wrapping the value in an `Ok` 3160 // or `Error` we want to add an additional label with this hint below the 3161 // offending value. 3162 Some(UnifyErrorSituation::CaseClauseMismatch { clause_location }) => { 3163 (clause_location, None, vec![]) 3164 } 3165 // In all other cases we just highlight the offending expression, optionally 3166 // adding the wrapping hint if it makes sense. 3167 Some(_) | None => (location, hint_wrap_value_in_result(expected, given), vec![]), 3168 }; 3169 3170 Diagnostic { 3171 title: "Type mismatch".into(), 3172 text, 3173 hint: None, 3174 level: Level::Error, 3175 location: Some(Location { 3176 label: Label { 3177 text: main_message_text, 3178 span: *main_message_location, 3179 }, 3180 path: path.clone(), 3181 src: src.clone(), 3182 extra_labels, 3183 }), 3184 } 3185 } 3186 3187 TypeError::IncorrectTypeArity { 3188 location, 3189 expected, 3190 given: given_number, 3191 name, 3192 } => { 3193 let expected = match expected { 3194 0 => "no type arguments".into(), 3195 1 => "1 type argument".into(), 3196 _ => format!("{expected} type arguments"), 3197 }; 3198 let given = match given_number { 3199 0 => "none", 3200 _ => &format!("{given_number}"), 3201 }; 3202 let text = wrap_format!( 3203 "`{name}` requires {expected} \ 3204but {given} where provided." 3205 ); 3206 Diagnostic { 3207 title: "Incorrect arity".into(), 3208 text, 3209 hint: None, 3210 level: Level::Error, 3211 location: Some(Location { 3212 label: Label { 3213 text: Some(format!("Expected {expected}, got {given_number}")), 3214 span: *location, 3215 }, 3216 path: path.clone(), 3217 src: src.clone(), 3218 extra_labels: vec![], 3219 }), 3220 } 3221 } 3222 3223 TypeError::TypeUsedAsAConstructor { location, name } => { 3224 let text = wrap_format!( 3225 "`{name}` is a type with no parameters, but here it's \ 3226being used as a type constructor." 3227 ); 3228 3229 Diagnostic { 3230 title: "Type used as a type constructor".into(), 3231 text, 3232 hint: None, 3233 level: Level::Error, 3234 location: Some(Location { 3235 label: Label { 3236 text: Some("You can remove this".into()), 3237 span: *location, 3238 }, 3239 path: path.clone(), 3240 src: src.clone(), 3241 extra_labels: vec![], 3242 }), 3243 } 3244 } 3245 3246 TypeError::IncorrectArity { 3247 labels, 3248 location, 3249 context, 3250 expected, 3251 given, 3252 } => { 3253 let text = if labels.is_empty() { 3254 "".into() 3255 } else { 3256 let subject = match context { 3257 IncorrectArityContext::Pattern => "pattern", 3258 IncorrectArityContext::Function => "call", 3259 }; 3260 let labels = labels 3261 .iter() 3262 .map(|p| format!(" - {p}")) 3263 .sorted() 3264 .join("\n"); 3265 format!( 3266 "This {subject} accepts these additional labelled \ 3267 arguments:\n\n{labels}", 3268 ) 3269 }; 3270 let expected = match expected { 3271 0 => "no arguments".into(), 3272 1 => "1 argument".into(), 3273 _ => format!("{expected} arguments"), 3274 }; 3275 let label = format!("Expected {expected}, got {given}"); 3276 Diagnostic { 3277 title: "Incorrect arity".into(), 3278 text, 3279 hint: None, 3280 level: Level::Error, 3281 location: Some(Location { 3282 label: Label { 3283 text: Some(label), 3284 span: *location, 3285 }, 3286 path: path.clone(), 3287 src: src.clone(), 3288 extra_labels: vec![], 3289 }), 3290 } 3291 } 3292 3293 TypeError::UnnecessarySpreadOperator { location, arity } => { 3294 let text = wrap_format!( 3295 "This record has {arity} fields and you have already \ 3296assigned variables to all of them." 3297 ); 3298 Diagnostic { 3299 title: "Unnecessary spread operator".into(), 3300 text, 3301 hint: None, 3302 level: Level::Error, 3303 location: Some(Location { 3304 label: Label { 3305 text: None, 3306 span: *location, 3307 }, 3308 path: path.clone(), 3309 src: src.clone(), 3310 extra_labels: vec![], 3311 }), 3312 } 3313 } 3314 3315 TypeError::UnsafeRecordUpdate { location, reason } => match reason { 3316 UnsafeRecordUpdateReason::UnknownVariant { 3317 constructed_variant, 3318 } => { 3319 let text = wrap_format!( 3320 "This value cannot be used to build an updated \ 3321`{constructed_variant}` as it could be some other variant. 3322 3323Consider pattern matching on it with a case expression and then \ 3324constructing a new record with its values." 3325 ); 3326 3327 Diagnostic { 3328 title: "Unsafe record update".into(), 3329 text, 3330 hint: None, 3331 level: Level::Error, 3332 location: Some(Location { 3333 label: Label { 3334 text: Some(format!( 3335 "I'm not sure this is always a `{constructed_variant}`" 3336 )), 3337 span: *location, 3338 }, 3339 path: path.clone(), 3340 src: src.clone(), 3341 extra_labels: vec![], 3342 }), 3343 } 3344 } 3345 UnsafeRecordUpdateReason::WrongVariant { 3346 constructed_variant, 3347 spread_variant, 3348 } => { 3349 let text = wrap_format!( 3350 "This value is a `{spread_variant}` so \ 3351it cannot be used to build a `{constructed_variant}`, even if they share some fields. 3352 3353Note: If you want to change one variant of a type into another, you should \ 3354specify all fields explicitly instead of using the record update syntax." 3355 ); 3356 3357 Diagnostic { 3358 title: "Incorrect record update".into(), 3359 text, 3360 hint: None, 3361 level: Level::Error, 3362 location: Some(Location { 3363 label: Label { 3364 text: Some(format!("This is a `{spread_variant}`")), 3365 span: *location, 3366 }, 3367 path: path.clone(), 3368 src: src.clone(), 3369 extra_labels: vec![], 3370 }), 3371 } 3372 } 3373 UnsafeRecordUpdateReason::IncompatibleFieldTypes { 3374 expected_field_type, 3375 record_field_type, 3376 record_variant, 3377 field, 3378 .. 3379 } => { 3380 let mut printer = Printer::new(names); 3381 let expected_field_type = printer.print_type(expected_field_type); 3382 let record_field_type = printer.print_type(record_field_type); 3383 let record_variant = printer.print_type(record_variant); 3384 let text = match field { 3385 RecordField::Labelled(label) => wrap_format!( 3386 "The `{label}` field \ 3387of this value is a `{record_field_type}`, but the arguments given to the record \ 3388update indicate that it should be a `{expected_field_type}`. 3389 3390Note: If the same type variable is used for multiple fields, all those fields \ 3391need to be updated at the same time if their type changes." 3392 ), 3393 RecordField::Unlabelled(index) => wrap_format!( 3394 "The {} field \ 3395of this value is a `{record_field_type}`, but the arguments given to the record \ 3396update indicate that it should be a `{expected_field_type}`. 3397 3398Note: Unlabelled fields cannot be updated in a record update, so either add \ 3399a label or use a record constructor.", 3400 to_ordinal(*index + 1), 3401 ), 3402 }; 3403 3404 Diagnostic { 3405 title: "Incomplete record update".into(), 3406 text, 3407 hint: None, 3408 level: Level::Error, 3409 location: Some(Location { 3410 label: Label { 3411 text: Some(format!("This is a `{record_variant}`")), 3412 span: *location, 3413 }, 3414 path: path.clone(), 3415 src: src.clone(), 3416 extra_labels: vec![], 3417 }), 3418 } 3419 } 3420 }, 3421 3422 TypeError::QualifiedTypeMissingName { location } => Diagnostic { 3423 title: "Invalid type".into(), 3424 text: "".into(), 3425 hint: None, 3426 level: Level::Error, 3427 location: Some(Location { 3428 label: Label { 3429 text: Some("This is not a valid type".into()), 3430 span: *location, 3431 }, 3432 path: path.clone(), 3433 src: src.clone(), 3434 extra_labels: vec![], 3435 }), 3436 }, 3437 3438 TypeError::UnknownType { 3439 location, 3440 name, 3441 hint, 3442 .. 3443 } => { 3444 let label_text = match hint { 3445 UnknownTypeHint::AlternativeTypes(types) => did_you_mean(name, types), 3446 UnknownTypeHint::ValueInScopeWithSameName => None, 3447 }; 3448 3449 let mut text = 3450 wrap_format!("The type `{name}` is not defined or imported in this module."); 3451 3452 match hint { 3453 UnknownTypeHint::ValueInScopeWithSameName => { 3454 let hint = wrap_format!( 3455 "There is a value in scope with the name `{name}`, \ 3456but no type in scope with that name." 3457 ); 3458 text.push('\n'); 3459 text.push_str(hint.as_str()); 3460 } 3461 UnknownTypeHint::AlternativeTypes(_) => {} 3462 }; 3463 3464 Diagnostic { 3465 title: "Unknown type".into(), 3466 text, 3467 hint: None, 3468 level: Level::Error, 3469 location: Some(Location { 3470 label: Label { 3471 text: label_text, 3472 span: *location, 3473 }, 3474 path: path.clone(), 3475 src: src.clone(), 3476 extra_labels: vec![], 3477 }), 3478 } 3479 } 3480 3481 TypeError::UnknownVariable { 3482 location, 3483 variables, 3484 discarded_location, 3485 name, 3486 type_with_name_in_scope, 3487 possible_modules, 3488 } => { 3489 let title = String::from("Unknown variable"); 3490 3491 if let Some(ignored_location) = discarded_location { 3492 let location = Location { 3493 label: Label { 3494 text: Some("So this is not in scope".into()), 3495 span: *location, 3496 }, 3497 path: path.clone(), 3498 src: src.clone(), 3499 extra_labels: vec![ExtraLabel { 3500 src_info: None, 3501 label: Label { 3502 text: Some("This value is discarded".into()), 3503 span: *ignored_location, 3504 }, 3505 }], 3506 }; 3507 Diagnostic { 3508 title, 3509 text: "".into(), 3510 hint: Some(wrap_format!( 3511 "Change `_{name}` to `{name}` or reference another variable", 3512 )), 3513 level: Level::Error, 3514 location: Some(location), 3515 } 3516 } else { 3517 let text = if *type_with_name_in_scope { 3518 wrap_format!("`{name}` is a type, it cannot be used as a value.") 3519 } else { 3520 let mut text = if name.starts_with(char::is_uppercase) { 3521 wrap_format!( 3522 "The custom type variant constructor `{name}` is not in scope here." 3523 ) 3524 } else { 3525 wrap_format!("The name `{name}` is not in scope here.") 3526 }; 3527 3528 // If there are some suggestions about public values in imported 3529 // modules put a "did you mean" text after the main message 3530 if !possible_modules.is_empty() { 3531 text.push_str("\nDid you mean one of these:\n\n"); 3532 for module_name in possible_modules { 3533 text.push_str(&format!(" - {module_name}.{name}\n")) 3534 } 3535 } 3536 3537 text 3538 }; 3539 3540 Diagnostic { 3541 title, 3542 text, 3543 hint: None, 3544 level: Level::Error, 3545 location: Some(Location { 3546 label: Label { 3547 text: did_you_mean(name, variables), 3548 span: *location, 3549 }, 3550 path: path.clone(), 3551 src: src.clone(), 3552 extra_labels: vec![], 3553 }), 3554 } 3555 } 3556 } 3557 3558 TypeError::PrivateTypeLeak { location, leaked } => { 3559 let mut printer = Printer::new(names); 3560 3561 // TODO: be more precise. 3562 // - is being returned by this public function 3563 // - is taken as an argument by this public function 3564 // - is taken as an argument by this public enum constructor 3565 // etc 3566 let text = wrap_format!( 3567 "The following type is private, but is \ 3568being used by this public export. 3569 3570 {} 3571 3572Private types can only be used within the module that defines them.", 3573 printer.print_type(leaked), 3574 ); 3575 Diagnostic { 3576 title: "Private type used in public interface".into(), 3577 text, 3578 hint: None, 3579 level: Level::Error, 3580 location: Some(Location { 3581 label: Label { 3582 text: None, 3583 span: *location, 3584 }, 3585 path: path.clone(), 3586 src: src.clone(), 3587 extra_labels: vec![], 3588 }), 3589 } 3590 } 3591 3592 TypeError::UnknownModule { 3593 location, 3594 name, 3595 suggestions, 3596 } => Diagnostic { 3597 title: "Unknown module".into(), 3598 text: format!("No module has been found with the name `{name}`."), 3599 hint: suggestions 3600 .first() 3601 .map(|suggestion| suggestion.suggestion(name)), 3602 level: Level::Error, 3603 location: Some(Location { 3604 label: Label { 3605 text: None, 3606 span: *location, 3607 }, 3608 path: path.clone(), 3609 src: src.clone(), 3610 extra_labels: vec![], 3611 }), 3612 }, 3613 3614 TypeError::UnknownModuleType { 3615 location, 3616 name, 3617 module_name, 3618 type_constructors, 3619 value_with_same_name: imported_type_as_value, 3620 } => { 3621 let text = if *imported_type_as_value { 3622 format!("`{name}` is only a value, it cannot be imported as a type.") 3623 } else { 3624 format!("The module `{module_name}` does not have a `{name}` type.") 3625 }; 3626 Diagnostic { 3627 title: "Unknown module type".into(), 3628 text, 3629 hint: None, 3630 level: Level::Error, 3631 location: Some(Location { 3632 label: Label { 3633 text: if *imported_type_as_value { 3634 Some(format!("Did you mean `{name}`?")) 3635 } else { 3636 did_you_mean(name, type_constructors) 3637 }, 3638 span: *location, 3639 }, 3640 path: path.clone(), 3641 src: src.clone(), 3642 extra_labels: vec![], 3643 }), 3644 } 3645 } 3646 3647 TypeError::UnknownModuleValue { 3648 location, 3649 name, 3650 module_name, 3651 value_constructors, 3652 type_with_same_name: imported_value_as_type, 3653 context, 3654 } => { 3655 let text = if *imported_value_as_type { 3656 match context { 3657 ModuleValueUsageContext::UnqualifiedImport => { 3658 wrap_format!("`{name}` is only a type, it cannot be imported as a value.") 3659 } 3660 ModuleValueUsageContext::ModuleAccess => wrap_format!( 3661 "{module_name}.{name} is a type constructor, \ 3662it cannot be used as a value" 3663 ), 3664 } 3665 } else { 3666 wrap_format!("The module `{module_name}` does not have a `{name}` value.") 3667 }; 3668 Diagnostic { 3669 title: "Unknown module value".into(), 3670 text, 3671 hint: None, 3672 level: Level::Error, 3673 location: Some(Location { 3674 label: Label { 3675 text: if *imported_value_as_type 3676 && matches!(context, ModuleValueUsageContext::UnqualifiedImport) 3677 { 3678 Some(format!("Did you mean `type {name}`?")) 3679 } else { 3680 did_you_mean(name, value_constructors) 3681 }, 3682 span: *location, 3683 }, 3684 path: path.clone(), 3685 src: src.clone(), 3686 extra_labels: vec![], 3687 }), 3688 } 3689 } 3690 3691 TypeError::ModuleAliasUsedAsName { location, name } => { 3692 let text = wrap( 3693 "Modules are not values, so you cannot assign them \ 3694to variables, pass them to functions, or anything else that you would do with a value.", 3695 ); 3696 Diagnostic { 3697 title: format!("Module `{name}` used as a value"), 3698 text, 3699 hint: None, 3700 level: Level::Error, 3701 location: Some(Location { 3702 label: Label { 3703 text: None, 3704 span: *location, 3705 }, 3706 path: path.clone(), 3707 src: src.clone(), 3708 extra_labels: vec![], 3709 }), 3710 } 3711 } 3712 3713 TypeError::IncorrectNumClausePatterns { 3714 location, 3715 expected, 3716 given, 3717 } => { 3718 let subject = if *expected == 1 { 3719 "subject" 3720 } else { 3721 "subjects" 3722 }; 3723 let pattern = if *expected == 1 { 3724 "pattern" 3725 } else { 3726 "patterns" 3727 }; 3728 let text = wrap_format!( 3729 "This case expression has {expected} {subject}, \ 3730but this pattern matches {given}. 3731Each clause must have a pattern for every subject value.", 3732 ); 3733 Diagnostic { 3734 title: "Incorrect number of patterns".into(), 3735 text, 3736 hint: None, 3737 level: Level::Error, 3738 location: Some(Location { 3739 label: Label { 3740 text: Some(format!("Expected {expected} {pattern}, got {given}")), 3741 span: *location, 3742 }, 3743 path: path.clone(), 3744 src: src.clone(), 3745 extra_labels: vec![], 3746 }), 3747 } 3748 } 3749 3750 TypeError::NonLocalClauseGuardVariable { location, name } => { 3751 let text = wrap_format!( 3752 "Variables used in guards must be either defined in the \ 3753function, or be an argument to the function. The variable \ 3754`{name}` is not defined locally.", 3755 ); 3756 Diagnostic { 3757 title: "Invalid guard variable".into(), 3758 text, 3759 hint: None, 3760 level: Level::Error, 3761 location: Some(Location { 3762 label: Label { 3763 text: Some("Is not locally defined".into()), 3764 span: *location, 3765 }, 3766 path: path.clone(), 3767 src: src.clone(), 3768 extra_labels: vec![], 3769 }), 3770 } 3771 } 3772 3773 TypeError::ExtraVarInAlternativePattern { location, name } => { 3774 let text = wrap_format!( 3775 "All alternative patterns must define the same variables as \ 3776the initial pattern. This variable `{name}` has not been previously defined.", 3777 ); 3778 Diagnostic { 3779 title: "Extra alternative pattern variable".into(), 3780 text, 3781 hint: None, 3782 level: Level::Error, 3783 location: Some(Location { 3784 label: Label { 3785 text: Some("Has not been previously defined".into()), 3786 span: *location, 3787 }, 3788 path: path.clone(), 3789 src: src.clone(), 3790 extra_labels: vec![], 3791 }), 3792 } 3793 } 3794 3795 TypeError::MissingVarInAlternativePattern { location, name } => { 3796 let text = wrap_format!( 3797 "All alternative patterns must define the same variables \ 3798as the initial pattern, but the `{name}` variable is missing.", 3799 ); 3800 Diagnostic { 3801 title: "Missing alternative pattern variable".into(), 3802 text, 3803 hint: None, 3804 level: Level::Error, 3805 location: Some(Location { 3806 label: Label { 3807 text: Some("This does not define all required variables".into()), 3808 span: *location, 3809 }, 3810 path: path.clone(), 3811 src: src.clone(), 3812 extra_labels: vec![], 3813 }), 3814 } 3815 } 3816 3817 TypeError::DuplicateVarInPattern { location, name } => { 3818 let text = wrap_format!( 3819 "Variables can only be used once per pattern. This \ 3820variable `{name}` appears multiple times. 3821If you used the same variable twice deliberately in order to check for equality \ 3822please use a guard clause instead. 3823e.g. (x, y) if x == y -> ...", 3824 ); 3825 Diagnostic { 3826 title: "Duplicate variable in pattern".into(), 3827 text, 3828 hint: None, 3829 level: Level::Error, 3830 location: Some(Location { 3831 label: Label { 3832 text: Some("This has already been used".into()), 3833 span: *location, 3834 }, 3835 path: path.clone(), 3836 src: src.clone(), 3837 extra_labels: vec![], 3838 }), 3839 } 3840 } 3841 3842 TypeError::OutOfBoundsTupleIndex { 3843 location, size: 0, .. 3844 } => Diagnostic { 3845 title: "Out of bounds tuple index".into(), 3846 text: "This tuple has no elements so it cannot be indexed at all.".into(), 3847 hint: None, 3848 level: Level::Error, 3849 location: Some(Location { 3850 label: Label { 3851 text: None, 3852 span: *location, 3853 }, 3854 path: path.clone(), 3855 src: src.clone(), 3856 extra_labels: vec![], 3857 }), 3858 }, 3859 3860 TypeError::OutOfBoundsTupleIndex { 3861 location, 3862 index, 3863 size, 3864 } => { 3865 let text = wrap_format!( 3866 "The index being accessed for this tuple is {}, but this \ 3867tuple has {} elements so the highest valid index is {}.", 3868 index, 3869 size, 3870 size - 1, 3871 ); 3872 Diagnostic { 3873 title: "Out of bounds tuple index".into(), 3874 text, 3875 hint: None, 3876 level: Level::Error, 3877 location: Some(Location { 3878 label: Label { 3879 text: Some("This index is too large".into()), 3880 span: *location, 3881 }, 3882 path: path.clone(), 3883 src: src.clone(), 3884 extra_labels: vec![], 3885 }), 3886 } 3887 } 3888 3889 TypeError::NotATuple { location, given } => { 3890 let mut printer = Printer::new(names); 3891 let text = format!( 3892 "To index into this value it needs to be a tuple, \ 3893however it has this type: 3894 3895 {}", 3896 printer.print_type(given), 3897 ); 3898 Diagnostic { 3899 title: "Type mismatch".into(), 3900 text, 3901 hint: None, 3902 level: Level::Error, 3903 location: Some(Location { 3904 label: Label { 3905 text: Some("This is not a tuple".into()), 3906 span: *location, 3907 }, 3908 path: path.clone(), 3909 src: src.clone(), 3910 extra_labels: vec![], 3911 }), 3912 } 3913 } 3914 3915 TypeError::NotATupleUnbound { location } => { 3916 let text = wrap( 3917 "To index into a tuple we need to \ 3918know its size, but we don't know anything about this type yet. \ 3919Please add some type annotations so we can continue.", 3920 ); 3921 Diagnostic { 3922 title: "Type mismatch".into(), 3923 text, 3924 hint: None, 3925 level: Level::Error, 3926 location: Some(Location { 3927 label: Label { 3928 text: Some("What type is this?".into()), 3929 span: *location, 3930 }, 3931 path: path.clone(), 3932 src: src.clone(), 3933 extra_labels: vec![], 3934 }), 3935 } 3936 } 3937 3938 TypeError::RecordAccessUnknownType { location } => { 3939 let text = wrap( 3940 "In order to access a record field \ 3941we need to know what type it is, but I can't tell \ 3942the type here. Try adding type annotations to your \ 3943function and try again.", 3944 ); 3945 Diagnostic { 3946 title: "Unknown type for record access".into(), 3947 text, 3948 hint: None, 3949 level: Level::Error, 3950 location: Some(Location { 3951 label: Label { 3952 text: Some("I don't know what type this is".into()), 3953 span: *location, 3954 }, 3955 path: path.clone(), 3956 src: src.clone(), 3957 extra_labels: vec![], 3958 }), 3959 } 3960 } 3961 3962 TypeError::BitArraySegmentError { error, location } => { 3963 let (label, mut extra) = match error { 3964 bit_array::ErrorType::ConflictingTypeOptions { existing_type } => ( 3965 "This is an extra type specifier", 3966 vec![format!( 3967 "Hint: This segment already has the type {existing_type}." 3968 )], 3969 ), 3970 3971 bit_array::ErrorType::ConflictingSignednessOptions { existing_signed } => ( 3972 "This is an extra signedness specifier", 3973 vec![format!( 3974 "Hint: This segment already has a \ 3975signedness of {existing_signed}." 3976 )], 3977 ), 3978 3979 bit_array::ErrorType::ConflictingEndiannessOptions { 3980 existing_endianness, 3981 } => ( 3982 "This is an extra endianness specifier", 3983 vec![format!( 3984 "Hint: This segment already has an \ 3985endianness of {existing_endianness}." 3986 )], 3987 ), 3988 3989 bit_array::ErrorType::ConflictingSizeOptions => ( 3990 "This is an extra size specifier", 3991 vec!["Hint: This segment already has a size.".into()], 3992 ), 3993 3994 bit_array::ErrorType::ConflictingUnitOptions => ( 3995 "This is an extra unit specifier", 3996 vec!["Hint: A BitArray segment can have at most 1 unit.".into()], 3997 ), 3998 3999 bit_array::ErrorType::FloatWithSize => ( 4000 "Invalid float size", 4001 vec!["Hint: floats have an exact size of 16/32/64 bits.".into()], 4002 ), 4003 4004 bit_array::ErrorType::InvalidEndianness => ( 4005 "This option is invalid here", 4006 vec![wrap( 4007 "Hint: signed and unsigned \ 4008can only be used with int, float, utf16 and utf32 types.", 4009 )], 4010 ), 4011 4012 bit_array::ErrorType::OptionNotAllowedInValue => ( 4013 "This option is only allowed in BitArray patterns", 4014 vec!["Hint: This option has no effect in BitArray values.".into()], 4015 ), 4016 4017 bit_array::ErrorType::SignednessUsedOnNonInt { type_ } => ( 4018 "Signedness is only valid with int types", 4019 vec![format!("Hint: This segment has a type of {type_}")], 4020 ), 4021 bit_array::ErrorType::TypeDoesNotAllowSize { type_ } => ( 4022 "Size cannot be specified here", 4023 vec![format!("Hint: {type_} segments have an automatic size.")], 4024 ), 4025 bit_array::ErrorType::TypeDoesNotAllowUnit { type_ } => ( 4026 "Unit cannot be specified here", 4027 vec![wrap(&format!( 4028 "Hint: {type_} segments \ 4029are sized based on their value and cannot have a unit." 4030 ))], 4031 ), 4032 bit_array::ErrorType::VariableUtfSegmentInPattern => ( 4033 "This cannot be a variable", 4034 vec![wrap( 4035 "Hint: in patterns utf8, utf16, and \ 4036utf32 must be an exact string.", 4037 )], 4038 ), 4039 bit_array::ErrorType::SegmentMustHaveSize => ( 4040 "This segment has no size", 4041 vec![wrap( 4042 "Hint: Bit array segments without \ 4043a size are only allowed at the end of a bin pattern.", 4044 )], 4045 ), 4046 bit_array::ErrorType::UnitMustHaveSize => ( 4047 "This needs an explicit size", 4048 vec!["Hint: If you specify unit() you must also specify size().".into()], 4049 ), 4050 bit_array::ErrorType::ConstantSizeNotPositive => { 4051 ("A constant size must be a positive number", vec![]) 4052 } 4053 bit_array::ErrorType::OptionNotSupportedForTarget { 4054 target, 4055 option: UnsupportedOption::NativeEndianness, 4056 } => ( 4057 "Unsupported endianness", 4058 vec![wrap_format!( 4059 "The {target} target does not support the `native` \ 4060endianness option.", 4061 target = target.as_presentable_str(), 4062 )], 4063 ), 4064 bit_array::ErrorType::OptionNotSupportedForTarget { 4065 target, 4066 option: UnsupportedOption::UtfCodepointPattern, 4067 } => ( 4068 "UTF-codepoint pattern matching is not supported", 4069 vec![wrap_format!( 4070 "The {target} target does not support \ 4071UTF-codepoint pattern matching.", 4072 target = target.as_presentable_str(), 4073 )], 4074 ), 4075 }; 4076 extra.push("See: https://tour.gleam.run/data-types/bit-arrays/".into()); 4077 let text = extra.join("\n"); 4078 Diagnostic { 4079 title: "Invalid bit array segment".into(), 4080 text, 4081 hint: None, 4082 level: Level::Error, 4083 location: Some(Location { 4084 label: Label { 4085 text: Some(label.into()), 4086 span: *location, 4087 }, 4088 path: path.clone(), 4089 src: src.clone(), 4090 extra_labels: vec![], 4091 }), 4092 } 4093 } 4094 TypeError::RecordUpdateInvalidConstructor { location } => Diagnostic { 4095 title: "Invalid record constructor".into(), 4096 text: "Only record constructors can be used with the update syntax.".into(), 4097 hint: None, 4098 level: Level::Error, 4099 location: Some(Location { 4100 label: Label { 4101 text: Some("This is not a record constructor".into()), 4102 span: *location, 4103 }, 4104 path: path.clone(), 4105 src: src.clone(), 4106 extra_labels: vec![], 4107 }), 4108 }, 4109 4110 TypeError::RecordUpdateVariantWithNoFields { location } => Diagnostic { 4111 title: "Invalid record constructor".into(), 4112 text: wrap( 4113 "Only constructors with at least one labelled \ 4114field can be used with the update syntax.", 4115 ), 4116 hint: None, 4117 level: Level::Error, 4118 location: Some(Location { 4119 label: Label { 4120 text: Some("This constructor has no labelled fields".into()), 4121 span: *location, 4122 }, 4123 path: path.clone(), 4124 src: src.clone(), 4125 extra_labels: vec![], 4126 }), 4127 }, 4128 4129 TypeError::UnexpectedTypeHole { location } => Diagnostic { 4130 title: "Unexpected type hole".into(), 4131 text: "We need to know the exact type here so type holes cannot be used.".into(), 4132 hint: None, 4133 level: Level::Error, 4134 location: Some(Location { 4135 label: Label { 4136 text: Some("I need to know what this is".into()), 4137 span: *location, 4138 }, 4139 path: path.clone(), 4140 src: src.clone(), 4141 extra_labels: vec![], 4142 }), 4143 }, 4144 4145 TypeError::ReservedModuleName { name } => { 4146 let text = format!( 4147 "The module name `{name}` is reserved. 4148Try a different name for this module." 4149 ); 4150 Diagnostic { 4151 title: "Reserved module name".into(), 4152 text, 4153 hint: None, 4154 location: None, 4155 level: Level::Error, 4156 } 4157 } 4158 4159 TypeError::KeywordInModuleName { name, keyword } => { 4160 let text = wrap_format!( 4161 "The module name `{name}` contains the keyword `{keyword}`, \ 4162so importing it would be a syntax error. 4163Try a different name for this module." 4164 ); 4165 Diagnostic { 4166 title: "Invalid module name".into(), 4167 text, 4168 hint: None, 4169 location: None, 4170 level: Level::Error, 4171 } 4172 } 4173 4174 TypeError::NotExhaustivePatternMatch { 4175 location, 4176 unmatched, 4177 kind, 4178 } => { 4179 let mut text = match kind { 4180 PatternMatchKind::Case => { 4181 "This case expression does not match all possibilities. 4182Each constructor must have a pattern that matches it or 4183else it could crash." 4184 } 4185 PatternMatchKind::Assignment => { 4186 "This assignment does not match all possibilities. 4187Either use a case expression with patterns for each possible 4188value, or use `let assert` rather than `let`." 4189 } 4190 } 4191 .to_string(); 4192 4193 text.push_str("\n\nThese values are not matched:\n\n"); 4194 for unmatched in unmatched { 4195 text.push_str(" - "); 4196 text.push_str(unmatched); 4197 text.push('\n'); 4198 } 4199 Diagnostic { 4200 title: "Not exhaustive pattern match".into(), 4201 text, 4202 hint: None, 4203 level: Level::Error, 4204 location: Some(Location { 4205 label: Label { 4206 text: None, 4207 span: *location, 4208 }, 4209 path: path.clone(), 4210 src: src.clone(), 4211 extra_labels: vec![], 4212 }), 4213 } 4214 } 4215 4216 TypeError::ArgumentNameAlreadyUsed { location, name } => Diagnostic { 4217 title: "Argument name already used".into(), 4218 text: format!("Two `{name}` arguments have been defined for this function."), 4219 hint: None, 4220 level: Level::Error, 4221 location: Some(Location { 4222 label: Label { 4223 text: None, 4224 span: *location, 4225 }, 4226 path: path.clone(), 4227 src: src.clone(), 4228 extra_labels: vec![], 4229 }), 4230 }, 4231 4232 TypeError::UnlabelledAfterlabelled { location } => Diagnostic { 4233 title: "Unlabelled argument after labelled argument".into(), 4234 text: wrap("All unlabelled arguments must come before any labelled arguments."), 4235 hint: None, 4236 level: Level::Error, 4237 location: Some(Location { 4238 label: Label { 4239 text: None, 4240 span: *location, 4241 }, 4242 path: path.clone(), 4243 src: src.clone(), 4244 extra_labels: vec![], 4245 }), 4246 }, 4247 4248 TypeError::RecursiveTypeAlias { location, cycle } => { 4249 let mut text = "This type alias is defined in terms of itself.\n".into(); 4250 write_cycle(&mut text, cycle); 4251 text.push_str( 4252 "If we tried to compile this recursive type it would expand 4253forever in a loop, and we'd never get the final type.", 4254 ); 4255 Diagnostic { 4256 title: "Type cycle".into(), 4257 text, 4258 hint: None, 4259 level: Level::Error, 4260 location: Some(Location { 4261 label: Label { 4262 text: None, 4263 span: *location, 4264 }, 4265 path: path.clone(), 4266 src: src.clone(), 4267 extra_labels: vec![], 4268 }), 4269 } 4270 } 4271 4272 TypeError::ExternalMissingAnnotation { location, kind } => { 4273 let kind = match kind { 4274 MissingAnnotation::Parameter => "parameter", 4275 MissingAnnotation::Return => "return", 4276 }; 4277 let text = format!( 4278 "A {kind} annotation is missing from this function. 4279 4280Functions with external implementations must have type annotations 4281so we can tell what type of values they accept and return.", 4282 ); 4283 Diagnostic { 4284 title: "Missing type annotation".into(), 4285 text, 4286 hint: None, 4287 level: Level::Error, 4288 location: Some(Location { 4289 label: Label { 4290 text: None, 4291 span: *location, 4292 }, 4293 path: path.clone(), 4294 src: src.clone(), 4295 extra_labels: vec![], 4296 }), 4297 } 4298 } 4299 4300 TypeError::NoImplementation { location } => { 4301 let text = "We can't compile this function as it doesn't have an 4302implementation. Add a body or an external implementation 4303using the `@external` attribute." 4304 .into(); 4305 Diagnostic { 4306 title: "Function without an implementation".into(), 4307 text, 4308 hint: None, 4309 level: Level::Error, 4310 location: Some(Location { 4311 label: Label { 4312 text: None, 4313 span: *location, 4314 }, 4315 path: path.clone(), 4316 src: src.clone(), 4317 extra_labels: vec![], 4318 }), 4319 } 4320 } 4321 4322 TypeError::InvalidExternalJavascriptModule { 4323 location, 4324 name, 4325 module, 4326 } => { 4327 let text = wrap_format!( 4328 "The function `{name}` has an external JavaScript \ 4329implementation but the module path `{module}` is not valid." 4330 ); 4331 Diagnostic { 4332 title: "Invalid JavaScript module".into(), 4333 text, 4334 hint: None, 4335 level: Level::Error, 4336 location: Some(Location { 4337 label: Label { 4338 text: None, 4339 span: *location, 4340 }, 4341 path: path.clone(), 4342 src: src.clone(), 4343 extra_labels: vec![], 4344 }), 4345 } 4346 } 4347 4348 TypeError::InvalidExternalJavascriptFunction { 4349 location, 4350 name, 4351 function, 4352 } => { 4353 let text = wrap_format!( 4354 "The function `{name}` has an external JavaScript \ 4355implementation but the function name `{function}` is not valid." 4356 ); 4357 Diagnostic { 4358 title: "Invalid JavaScript function".into(), 4359 text, 4360 hint: None, 4361 level: Level::Error, 4362 location: Some(Location { 4363 label: Label { 4364 text: None, 4365 span: *location, 4366 }, 4367 path: path.clone(), 4368 src: src.clone(), 4369 extra_labels: vec![], 4370 }), 4371 } 4372 } 4373 4374 TypeError::InexhaustiveLetAssignment { location, missing } => { 4375 let mut text = wrap( 4376 "This assignment uses a pattern that does not \ 4377match all possible values. If one of the other values \ 4378is used then the assignment will crash. 4379 4380The missing patterns are:\n", 4381 ); 4382 for missing in missing { 4383 text.push_str("\n "); 4384 text.push_str(missing); 4385 } 4386 4387 Diagnostic { 4388 title: "Inexhaustive pattern".into(), 4389 text, 4390 hint: Some("Use a more general pattern or use `let assert` instead.".into()), 4391 level: Level::Error, 4392 location: Some(Location { 4393 src: src.clone(), 4394 path: path.to_path_buf(), 4395 label: Label { 4396 text: None, 4397 span: *location, 4398 }, 4399 extra_labels: Vec::new(), 4400 }), 4401 } 4402 } 4403 4404 TypeError::InexhaustiveCaseExpression { location, missing } => { 4405 let mut text = wrap( 4406 "This case expression does not have a pattern \ 4407for all possible values. If it is run on one of the \ 4408values without a pattern then it will crash. 4409 4410The missing patterns are:\n", 4411 ); 4412 for missing in missing { 4413 text.push_str("\n "); 4414 text.push_str(missing); 4415 } 4416 Diagnostic { 4417 title: "Inexhaustive patterns".into(), 4418 text, 4419 hint: None, 4420 level: Level::Error, 4421 location: Some(Location { 4422 src: src.clone(), 4423 path: path.to_path_buf(), 4424 label: Label { 4425 text: None, 4426 span: *location, 4427 }, 4428 extra_labels: Vec::new(), 4429 }), 4430 } 4431 } 4432 4433 TypeError::MissingCaseBody { location } => { 4434 let text = wrap("This case expression is missing its body."); 4435 Diagnostic { 4436 title: "Missing case body".into(), 4437 text, 4438 hint: None, 4439 level: Level::Error, 4440 location: Some(Location { 4441 src: src.clone(), 4442 path: path.to_path_buf(), 4443 label: Label { 4444 text: None, 4445 span: *location, 4446 }, 4447 extra_labels: Vec::new(), 4448 }), 4449 } 4450 } 4451 4452 TypeError::UnsupportedExpressionTarget { 4453 location, 4454 target: current_target, 4455 } => { 4456 let text = wrap_format!( 4457 "This value is not available as it is defined using externals, \ 4458and there is no implementation for the {} target.", 4459 match current_target { 4460 Target::Erlang => "Erlang", 4461 Target::JavaScript => "JavaScript", 4462 } 4463 ); 4464 let hint = wrap("Did you mean to build for a different target?"); 4465 Diagnostic { 4466 title: "Unsupported target".into(), 4467 text, 4468 hint: Some(hint), 4469 level: Level::Error, 4470 location: Some(Location { 4471 path: path.clone(), 4472 src: src.clone(), 4473 label: Label { 4474 text: None, 4475 span: *location, 4476 }, 4477 extra_labels: vec![], 4478 }), 4479 } 4480 } 4481 4482 TypeError::UnsupportedPublicFunctionTarget { 4483 location, 4484 name, 4485 target, 4486 } => { 4487 let target = match target { 4488 Target::Erlang => "Erlang", 4489 Target::JavaScript => "JavaScript", 4490 }; 4491 let text = wrap_format!( 4492 "The `{name}` function is public but doesn't have an \ 4493implementation for the {target} target. All public functions of a package \ 4494must be able to compile for a module to be valid." 4495 ); 4496 Diagnostic { 4497 title: "Unsupported target".into(), 4498 text, 4499 hint: None, 4500 level: Level::Error, 4501 location: Some(Location { 4502 path: path.clone(), 4503 src: src.clone(), 4504 label: Label { 4505 text: None, 4506 span: *location, 4507 }, 4508 extra_labels: vec![], 4509 }), 4510 } 4511 } 4512 4513 TypeError::UnusedTypeAliasParameter { location, name } => { 4514 let text = 4515 wrap_format!("The type variable `{name}` is unused. It can be safely removed.",); 4516 Diagnostic { 4517 title: "Unused type parameter".into(), 4518 text, 4519 hint: None, 4520 level: Level::Error, 4521 location: Some(Location { 4522 path: path.clone(), 4523 src: src.clone(), 4524 label: Label { 4525 text: None, 4526 span: *location, 4527 }, 4528 extra_labels: vec![], 4529 }), 4530 } 4531 } 4532 4533 TypeError::DuplicateTypeParameter { location, name } => { 4534 let text = wrap_format!( 4535 "This definition has multiple type parameters named `{name}`. 4536Rename or remove one of them.", 4537 ); 4538 Diagnostic { 4539 title: "Duplicate type parameter".into(), 4540 text, 4541 hint: None, 4542 level: Level::Error, 4543 location: Some(Location { 4544 path: path.clone(), 4545 src: src.clone(), 4546 label: Label { 4547 text: None, 4548 span: *location, 4549 }, 4550 extra_labels: vec![], 4551 }), 4552 } 4553 } 4554 4555 TypeError::NotFnInUse { location, type_ } => { 4556 let mut printer = Printer::new(names); 4557 let text = wrap_format!( 4558 "In a use expression, there should be a function on \ 4559the right hand side of `<-`, but this value has type: 4560 4561 {} 4562 4563See: https://tour.gleam.run/advanced-features/use/", 4564 printer.print_type(type_) 4565 ); 4566 4567 Diagnostic { 4568 title: "Type mismatch".into(), 4569 text, 4570 hint: None, 4571 level: Level::Error, 4572 location: Some(Location { 4573 label: Label { 4574 text: None, 4575 span: *location, 4576 }, 4577 path: path.clone(), 4578 src: src.clone(), 4579 extra_labels: vec![], 4580 }), 4581 } 4582 } 4583 4584 TypeError::UseFnDoesntTakeCallback { 4585 location, 4586 actual_type: None, 4587 } 4588 | TypeError::UseFnIncorrectArity { 4589 location, 4590 expected: 0, 4591 given: 1, 4592 } => { 4593 let text = wrap( 4594 "The function on the right of `<-` here \ 4595takes no arguments, but it has to take at least \ 4596one argument, a callback function. 4597 4598See: https://tour.gleam.run/advanced-features/use/", 4599 ); 4600 Diagnostic { 4601 title: "Incorrect arity".into(), 4602 text, 4603 hint: None, 4604 level: Level::Error, 4605 location: Some(Location { 4606 label: Label { 4607 text: Some("Expected no arguments, got 1".into()), 4608 span: *location, 4609 }, 4610 path: path.clone(), 4611 src: src.clone(), 4612 extra_labels: vec![], 4613 }), 4614 } 4615 } 4616 4617 TypeError::UseFnIncorrectArity { 4618 location, 4619 expected, 4620 given, 4621 } => { 4622 let expected_string = match expected { 4623 0 => "no arguments".into(), 4624 1 => "1 argument".into(), 4625 _ => format!("{expected} arguments"), 4626 }; 4627 let supplied_arguments = given - 1; 4628 let supplied_arguments_string = match supplied_arguments { 4629 0 => "no arguments".into(), 4630 1 => "1 argument".into(), 4631 _ => format!("{given} arguments"), 4632 }; 4633 let label = format!("Expected {expected_string}, got {given}"); 4634 let mut text: String = format!( 4635 "The function on the right of `<-` \ 4636here takes {expected_string}.\n" 4637 ); 4638 4639 if expected > given { 4640 if supplied_arguments == 0 { 4641 text.push_str( 4642 "The only argument that was supplied is \ 4643the `use` callback function.\n", 4644 ) 4645 } else { 4646 text.push_str(&format!( 4647 "You supplied {supplied_arguments_string} \ 4648and the final one is the `use` callback function.\n" 4649 )); 4650 } 4651 } else { 4652 text.push_str( 4653 "All the arguments have already been supplied, \ 4654so it cannot take the `use` callback function as a final argument.\n", 4655 ) 4656 }; 4657 4658 text.push_str("\nSee: https://tour.gleam.run/advanced-features/use/"); 4659 4660 Diagnostic { 4661 title: "Incorrect arity".into(), 4662 text: wrap(&text), 4663 hint: None, 4664 level: Level::Error, 4665 location: Some(Location { 4666 label: Label { 4667 text: Some(label), 4668 span: *location, 4669 }, 4670 path: path.clone(), 4671 src: src.clone(), 4672 extra_labels: vec![], 4673 }), 4674 } 4675 } 4676 4677 TypeError::UseFnDoesntTakeCallback { 4678 location, 4679 actual_type: Some(actual), 4680 } => { 4681 let mut printer = Printer::new(names); 4682 let text = wrap_format!( 4683 "The function on the right hand side of `<-` \ 4684has to take a callback function as its last argument. \ 4685But the last argument of this function has type: 4686 4687 {} 4688 4689See: https://tour.gleam.run/advanced-features/use/", 4690 printer.print_type(actual) 4691 ); 4692 Diagnostic { 4693 title: "Type mismatch".into(), 4694 text: wrap(&text), 4695 hint: None, 4696 level: Level::Error, 4697 location: Some(Location { 4698 label: Label { 4699 text: None, 4700 span: *location, 4701 }, 4702 path: path.clone(), 4703 src: src.clone(), 4704 extra_labels: vec![], 4705 }), 4706 } 4707 } 4708 4709 TypeError::UseCallbackIncorrectArity { 4710 pattern_location, 4711 call_location, 4712 expected, 4713 given, 4714 } => { 4715 let expected = match expected { 4716 0 => "no arguments".into(), 4717 1 => "1 argument".into(), 4718 _ => format!("{expected} arguments"), 4719 }; 4720 4721 let specified = match given { 4722 0 => "none were provided".into(), 4723 1 => "1 was provided".into(), 4724 _ => format!("{given} were provided"), 4725 }; 4726 4727 let text = wrap_format!( 4728 "This function takes a callback that expects {expected}. \ 4729But {specified} on the left hand side of `<-`. 4730 4731See: https://tour.gleam.run/advanced-features/use/" 4732 ); 4733 Diagnostic { 4734 title: "Incorrect arity".into(), 4735 text, 4736 hint: None, 4737 level: Level::Error, 4738 location: Some(Location { 4739 label: Label { 4740 text: None, 4741 span: *call_location, 4742 }, 4743 path: path.clone(), 4744 src: src.clone(), 4745 extra_labels: vec![ExtraLabel { 4746 src_info: None, 4747 label: Label { 4748 text: Some(format!("Expected {expected}, got {given}")), 4749 span: *pattern_location, 4750 }, 4751 }], 4752 }), 4753 } 4754 } 4755 4756 TypeError::BadName { 4757 location, 4758 name, 4759 kind, 4760 } => { 4761 let kind_str = kind.as_str(); 4762 let label = format!("This is not a valid {} name", kind_str.to_lowercase()); 4763 let hint = match kind { 4764 Named::Type | Named::TypeAlias | Named::CustomTypeVariant => { 4765 format!( 4766 "{} names start with an uppercase \ 4767letter and contain only lowercase letters, numbers, \ 4768and uppercase letters. 4769Try: {}", 4770 kind_str, 4771 to_upper_camel_case(name) 4772 ) 4773 } 4774 Named::Variable 4775 | Named::TypeVariable 4776 | Named::Argument 4777 | Named::Label 4778 | Named::Constant 4779 | Named::Function => format!( 4780 "{} names start with a lowercase letter \ 4781and contain a-z, 0-9, or _. 4782Try: {}", 4783 kind_str, 4784 to_snake_case(name) 4785 ), 4786 Named::Discard => format!( 4787 "{} names start with _ and contain \ 4788a-z, 0-9, or _. 4789Try: _{}", 4790 kind_str, 4791 to_snake_case(name) 4792 ), 4793 }; 4794 4795 Diagnostic { 4796 title: format!("Invalid {} name", kind_str.to_lowercase()), 4797 text: "".into(), 4798 hint: Some(hint), 4799 level: Level::Error, 4800 location: Some(Location { 4801 label: Label { 4802 text: Some(label), 4803 span: *location, 4804 }, 4805 path: path.clone(), 4806 src: src.clone(), 4807 extra_labels: vec![], 4808 }), 4809 } 4810 } 4811 4812 TypeError::AllVariantsDeprecated { location } => { 4813 let text = String::from( 4814 "Consider deprecating the type as a whole. 4815 4816 @deprecated(\"message\") 4817 type Wibble { 4818 Wobble1 4819 Wobble2 4820 } 4821", 4822 ); 4823 Diagnostic { 4824 title: "All variants of custom type deprecated.".into(), 4825 text, 4826 hint: None, 4827 level: Level::Error, 4828 location: Some(Location { 4829 label: Label { 4830 text: None, 4831 span: *location, 4832 }, 4833 path: path.clone(), 4834 src: src.clone(), 4835 extra_labels: vec![], 4836 }), 4837 } 4838 } 4839 TypeError::DeprecatedVariantOnDeprecatedType { location } => { 4840 let text = wrap( 4841 "This custom type has already been deprecated, so deprecating \ 4842one of its variants does nothing. 4843Consider removing the deprecation attribute on the variant.", 4844 ); 4845 4846 Diagnostic { 4847 title: "Custom type already deprecated".into(), 4848 text, 4849 hint: None, 4850 level: Level::Error, 4851 location: Some(Location { 4852 label: Label { 4853 text: None, 4854 span: *location, 4855 }, 4856 path: path.clone(), 4857 src: src.clone(), 4858 extra_labels: vec![], 4859 }), 4860 } 4861 } 4862 4863 TypeError::EchoWithNoFollowingExpression { location } => Diagnostic { 4864 title: "Invalid echo use".to_string(), 4865 text: wrap("The `echo` keyword should be followed by a value to print."), 4866 hint: None, 4867 level: Level::Error, 4868 location: Some(Location { 4869 label: Label { 4870 text: Some("I was expecting a value after this".into()), 4871 span: *location, 4872 }, 4873 path: path.clone(), 4874 src: src.clone(), 4875 extra_labels: vec![], 4876 }), 4877 }, 4878 4879 TypeError::StringConcatenationWithAddInt { location } => Diagnostic { 4880 title: "Type mismatch".to_string(), 4881 text: wrap( 4882 "The + operator can only be used on Ints. 4883To join two strings together you can use the <> operator.", 4884 ), 4885 hint: None, 4886 level: Level::Error, 4887 location: Some(Location { 4888 label: Label { 4889 text: Some("Use <> instead".into()), 4890 span: *location, 4891 }, 4892 path: path.clone(), 4893 src: src.clone(), 4894 extra_labels: vec![], 4895 }), 4896 }, 4897 4898 TypeError::IntOperatorOnFloats { location, operator } => Diagnostic { 4899 title: "Type mismatch".to_string(), 4900 text: wrap_format!("The {} operator can only be used on Ints.", operator.name()), 4901 hint: None, 4902 level: Level::Error, 4903 location: Some(Location { 4904 label: Label { 4905 text: operator 4906 .float_equivalent() 4907 .map(|operator| format!("Use {} instead", operator.name())), 4908 span: *location, 4909 }, 4910 path: path.clone(), 4911 src: src.clone(), 4912 extra_labels: vec![], 4913 }), 4914 }, 4915 4916 TypeError::FloatOperatorOnInts { location, operator } => Diagnostic { 4917 title: "Type mismatch".to_string(), 4918 text: wrap_format!( 4919 "The {} operator can only be used on Floats.", 4920 operator.name() 4921 ), 4922 hint: None, 4923 level: Level::Error, 4924 location: Some(Location { 4925 label: Label { 4926 text: operator 4927 .int_equivalent() 4928 .map(|operator| format!("Use {} instead", operator.name())), 4929 span: *location, 4930 }, 4931 path: path.clone(), 4932 src: src.clone(), 4933 extra_labels: vec![], 4934 }), 4935 }, 4936 4937 TypeError::DoubleVariableAssignmentInBitArray { location } => Diagnostic { 4938 title: "Double variable assignment".to_string(), 4939 text: wrap( 4940 "This pattern assigns to two different variables \ 4941at once, which is not possible in bit arrays.", 4942 ), 4943 hint: Some(wrap("Remove the `as` assignment.")), 4944 level: Level::Error, 4945 location: Some(Location { 4946 label: Label { 4947 text: None, 4948 span: *location, 4949 }, 4950 path: path.clone(), 4951 src: src.clone(), 4952 extra_labels: vec![], 4953 }), 4954 }, 4955 4956 TypeError::NonUtf8StringAssignmentInBitArray { location } => Diagnostic { 4957 title: "Non UTF-8 string assignment".to_string(), 4958 text: wrap( 4959 "This pattern assigns a non UTF-8 string to a \ 4960variable in a bit array. This is planned to be supported in the future, but we are \ 4961unsure of the desired behaviour. Please go to https://github.com/gleam-lang/gleam/issues/4566 \ 4962and explain your usecase for this pattern, and how you would expect it to behave.", 4963 ), 4964 hint: None, 4965 level: Level::Error, 4966 location: Some(Location { 4967 label: Label { 4968 text: None, 4969 span: *location, 4970 }, 4971 path: path.clone(), 4972 src: src.clone(), 4973 extra_labels: vec![], 4974 }), 4975 }, 4976 4977 TypeError::PrivateOpaqueType { location } => Diagnostic { 4978 title: "Private opaque type".to_string(), 4979 text: wrap("Only a public type can be opaque."), 4980 hint: None, 4981 level: Level::Error, 4982 location: Some(Location { 4983 label: Label { 4984 text: Some("You can safely remove this.".to_string()), 4985 span: *location, 4986 }, 4987 path: path.clone(), 4988 src: src.clone(), 4989 extra_labels: vec![], 4990 }), 4991 }, 4992 4993 TypeError::SrcImportingDevDependency { 4994 location, 4995 importing_module, 4996 imported_module, 4997 package, 4998 } => Diagnostic { 4999 title: "App importing dev dependency".to_string(), 5000 text: wrap_format!( 5001 "The application module `{importing_module}` is \ 5002importing the module `{imported_module}`, but `{package}`, the package it \ 5003belongs to, is a dev dependency. 5004 5005Dev dependencies are not included in production builds so application \ 5006modules should not import them. Perhaps change `{package}` to a regular dependency." 5007 ), 5008 hint: None, 5009 level: Level::Error, 5010 location: Some(Location { 5011 label: Label { 5012 text: None, 5013 span: *location, 5014 }, 5015 path: path.clone(), 5016 src: src.clone(), 5017 extra_labels: vec![], 5018 }), 5019 }, 5020 5021 TypeError::ExternalTypeWithConstructors { location } => Diagnostic { 5022 title: "External type with constructors".to_string(), 5023 text: wrap_format!( 5024 "This type is annotated with the `@external` annotation, \ 5025but it has constructors. The `@external` annotation is only for external types \ 5026with no constructors." 5027 ), 5028 hint: Some("Remove the `@external` annotation".into()), 5029 level: Level::Error, 5030 location: Some(Location { 5031 label: Label { 5032 text: None, 5033 span: *location, 5034 }, 5035 path: path.clone(), 5036 src: src.clone(), 5037 extra_labels: vec![], 5038 }), 5039 }, 5040 5041 TypeError::LowercaseBoolPattern { location } => Diagnostic { 5042 title: "Lowercase bool pattern".to_string(), 5043 text: "".into(), 5044 hint: Some( 5045 "In Gleam bool literals are `True` and `False`. 5046See: https://tour.gleam.run/basics/bools/" 5047 .into(), 5048 ), 5049 level: Level::Error, 5050 location: Some(Location { 5051 label: Label { 5052 text: Some("This is not a bool".into()), 5053 span: *location, 5054 }, 5055 path: path.clone(), 5056 src: src.clone(), 5057 extra_labels: vec![], 5058 }), 5059 }, 5060 5061 TypeError::TodoConstant { location } => Diagnostic { 5062 title: "Constant todo found".to_string(), 5063 text: wrap( 5064 "This code will crash if it is run. \ 5065Be sure to finish it before running your program.", 5066 ), 5067 hint: None, 5068 level: Level::Error, 5069 location: Some(Location { 5070 label: Label { 5071 text: Some("This code is incomplete".into()), 5072 span: *location, 5073 }, 5074 path: path.clone(), 5075 src: src.clone(), 5076 extra_labels: vec![], 5077 }), 5078 }, 5079 }) 5080} 5081 5082fn std_io_error_kind_text(kind: &std::io::ErrorKind) -> String { 5083 use std::io::ErrorKind; 5084 match kind { 5085 ErrorKind::NotFound => "Could not find the stdio stream".into(), 5086 ErrorKind::PermissionDenied => "Permission was denied".into(), 5087 ErrorKind::ConnectionRefused => "Connection was refused".into(), 5088 ErrorKind::ConnectionReset => "Connection was reset".into(), 5089 ErrorKind::ConnectionAborted => "Connection was aborted".into(), 5090 ErrorKind::NotConnected => "Was not connected".into(), 5091 ErrorKind::AddrInUse => "The stream was already in use".into(), 5092 ErrorKind::AddrNotAvailable => "The stream was not available".into(), 5093 ErrorKind::BrokenPipe => "The pipe was broken".into(), 5094 ErrorKind::AlreadyExists => "A handle to the stream already exists".into(), 5095 ErrorKind::WouldBlock => "This operation would block when it was requested not to".into(), 5096 ErrorKind::InvalidInput => "Some parameter was invalid".into(), 5097 ErrorKind::InvalidData => "The data was invalid. Check that the encoding is UTF-8".into(), 5098 ErrorKind::TimedOut => "The operation timed out".into(), 5099 ErrorKind::WriteZero => { 5100 "An attempt was made to write, but all bytes could not be written".into() 5101 } 5102 ErrorKind::Interrupted => "The operation was interrupted".into(), 5103 ErrorKind::UnexpectedEof => "The end of file was reached before it was expected".into(), 5104 ErrorKind::HostUnreachable 5105 | ErrorKind::NetworkUnreachable 5106 | ErrorKind::NetworkDown 5107 | ErrorKind::NotADirectory 5108 | ErrorKind::IsADirectory 5109 | ErrorKind::DirectoryNotEmpty 5110 | ErrorKind::ReadOnlyFilesystem 5111 | ErrorKind::StaleNetworkFileHandle 5112 | ErrorKind::StorageFull 5113 | ErrorKind::NotSeekable 5114 | ErrorKind::QuotaExceeded 5115 | ErrorKind::FileTooLarge 5116 | ErrorKind::ResourceBusy 5117 | ErrorKind::ExecutableFileBusy 5118 | ErrorKind::Deadlock 5119 | ErrorKind::CrossesDevices 5120 | ErrorKind::TooManyLinks 5121 | ErrorKind::InvalidFilename 5122 | ErrorKind::ArgumentListTooLong 5123 | ErrorKind::Unsupported 5124 | ErrorKind::OutOfMemory 5125 | ErrorKind::Other 5126 | _ => "An unknown error occurred".into(), 5127 } 5128} 5129 5130fn write_cycle(buffer: &mut String, cycle: &[EcoString]) { 5131 buffer.push_str( 5132 " 5133 ┌─────┐\n", 5134 ); 5135 for (index, name) in cycle.iter().enumerate() { 5136 if index != 0 { 5137 buffer.push_str(" │ ↓\n"); 5138 } 5139 buffer.push_str(""); 5140 buffer.push_str(name); 5141 buffer.push('\n'); 5142 } 5143 buffer.push_str(" └─────┘\n"); 5144} 5145 5146fn hint_alternative_operator(op: &BinOp, given: &Type) -> Option<String> { 5147 match op { 5148 BinOp::AddInt if given.is_float() => Some(hint_numeric_message("+.", "Float")), 5149 BinOp::DivInt if given.is_float() => Some(hint_numeric_message("/.", "Float")), 5150 BinOp::GtEqInt if given.is_float() => Some(hint_numeric_message(">=.", "Float")), 5151 BinOp::GtInt if given.is_float() => Some(hint_numeric_message(">.", "Float")), 5152 BinOp::LtEqInt if given.is_float() => Some(hint_numeric_message("<=.", "Float")), 5153 BinOp::LtInt if given.is_float() => Some(hint_numeric_message("<.", "Float")), 5154 BinOp::MultInt if given.is_float() => Some(hint_numeric_message("*.", "Float")), 5155 BinOp::SubInt if given.is_float() => Some(hint_numeric_message("-.", "Float")), 5156 5157 BinOp::AddFloat if given.is_int() => Some(hint_numeric_message("+", "Int")), 5158 BinOp::DivFloat if given.is_int() => Some(hint_numeric_message("/", "Int")), 5159 BinOp::GtEqFloat if given.is_int() => Some(hint_numeric_message(">=", "Int")), 5160 BinOp::GtFloat if given.is_int() => Some(hint_numeric_message(">", "Int")), 5161 BinOp::LtEqFloat if given.is_int() => Some(hint_numeric_message("<=", "Int")), 5162 BinOp::LtFloat if given.is_int() => Some(hint_numeric_message("<", "Int")), 5163 BinOp::MultFloat if given.is_int() => Some(hint_numeric_message("*", "Int")), 5164 BinOp::SubFloat if given.is_int() => Some(hint_numeric_message("-", "Int")), 5165 5166 BinOp::AddInt if given.is_string() => Some(hint_string_message()), 5167 BinOp::AddFloat if given.is_string() => Some(hint_string_message()), 5168 5169 BinOp::And 5170 | BinOp::Or 5171 | BinOp::Eq 5172 | BinOp::NotEq 5173 | BinOp::LtInt 5174 | BinOp::LtEqInt 5175 | BinOp::LtFloat 5176 | BinOp::LtEqFloat 5177 | BinOp::GtEqInt 5178 | BinOp::GtInt 5179 | BinOp::GtEqFloat 5180 | BinOp::GtFloat 5181 | BinOp::AddInt 5182 | BinOp::AddFloat 5183 | BinOp::SubInt 5184 | BinOp::SubFloat 5185 | BinOp::MultInt 5186 | BinOp::MultFloat 5187 | BinOp::DivInt 5188 | BinOp::DivFloat 5189 | BinOp::RemainderInt 5190 | BinOp::Concatenate => None, 5191 } 5192} 5193 5194fn hint_wrap_value_in_result(expected: &Arc<Type>, given: &Arc<Type>) -> Option<String> { 5195 let expected = collapse_links(expected.clone()); 5196 let (expected_ok_type, expected_error_type) = expected.result_types()?; 5197 5198 if given.same_as(expected_ok_type.as_ref()) { 5199 Some("Did you mean to wrap this in an `Ok`?".into()) 5200 } else if given.same_as(expected_error_type.as_ref()) { 5201 Some("Did you mean to wrap this in an `Error`?".into()) 5202 } else { 5203 None 5204 } 5205} 5206 5207fn hint_numeric_message(alt: &str, type_: &str) -> String { 5208 format!("The {alt} operator can be used with {type_}s\n") 5209} 5210 5211fn hint_string_message() -> String { 5212 wrap("Strings can be joined using the `<>` operator.") 5213} 5214 5215#[derive(Debug, Clone, PartialEq, Eq)] 5216pub struct Unformatted { 5217 pub source: Utf8PathBuf, 5218 pub destination: Utf8PathBuf, 5219 pub input: EcoString, 5220 pub output: String, 5221} 5222 5223pub fn wrap(text: &str) -> String { 5224 let mut result = String::with_capacity(text.len()); 5225 5226 for (i, line) in wrap_text(text, 75).iter().enumerate() { 5227 if i > 0 { 5228 result.push('\n'); 5229 } 5230 result.push_str(line); 5231 } 5232 5233 result 5234} 5235 5236fn wrap_text(text: &str, width: usize) -> Vec<Cow<'_, str>> { 5237 let mut lines: Vec<Cow<'_, str>> = Vec::new(); 5238 for line in text.split('\n') { 5239 // check if line needs to be broken 5240 match line.len() > width { 5241 false => lines.push(Cow::from(line)), 5242 true => { 5243 let mut new_lines = break_line(line, width); 5244 lines.append(&mut new_lines); 5245 } 5246 }; 5247 } 5248 5249 lines 5250} 5251 5252fn break_line(line: &str, width: usize) -> Vec<Cow<'_, str>> { 5253 let mut lines: Vec<Cow<'_, str>> = Vec::new(); 5254 let mut newline = String::from(""); 5255 5256 // split line by spaces 5257 for (i, word) in line.split(' ').enumerate() { 5258 let is_new_line = i < 1 || newline.is_empty(); 5259 5260 let can_add_word = match is_new_line { 5261 true => newline.len() + word.len() <= width, 5262 // +1 accounts for space added before word 5263 false => newline.len() + (word.len() + 1) <= width, 5264 }; 5265 5266 if can_add_word { 5267 if !is_new_line { 5268 newline.push(' '); 5269 } 5270 newline.push_str(word); 5271 } else { 5272 // word too big, save existing line if present 5273 if !newline.is_empty() { 5274 // save current line and reset it 5275 lines.push(Cow::from(newline.to_owned())); 5276 newline.clear(); 5277 } 5278 5279 // then save word to a new line or break it 5280 match word.len() > width { 5281 false => newline.push_str(word), 5282 true => { 5283 let (mut newlines, remainder) = break_word(word, width); 5284 lines.append(&mut newlines); 5285 newline.push_str(remainder); 5286 } 5287 } 5288 } 5289 } 5290 5291 // save last line after loop finishes 5292 if !newline.is_empty() { 5293 lines.push(Cow::from(newline)); 5294 } 5295 5296 lines 5297} 5298 5299// breaks word into n lines based on width. Returns list of new lines and remainder 5300fn break_word(word: &str, width: usize) -> (Vec<Cow<'_, str>>, &str) { 5301 let mut new_lines: Vec<Cow<'_, str>> = Vec::new(); 5302 let (first, mut remainder) = word.split_at(width); 5303 new_lines.push(Cow::from(first)); 5304 5305 // split remainder until it's small enough 5306 while remainder.len() > width { 5307 let (first, second) = remainder.split_at(width); 5308 new_lines.push(Cow::from(first)); 5309 remainder = second; 5310 } 5311 5312 (new_lines, remainder) 5313}