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