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