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

Configure Feed

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

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