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

Configure Feed

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

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