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