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