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

Configure Feed

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

gleam / compiler-core / src / warning.rs
64 kB 1572 lines
1// SPDX-License-Identifier: Apache-2.0 2// SPDX-FileCopyrightText: 2020 The Gleam contributors 3 4use crate::{ 5 ast::{BitArraySegmentTruncation, SrcSpan, TodoKind}, 6 build::Target, 7 diagnostic::{self, Diagnostic, ExtraLabel, Location}, 8 error::wrap, 9 exhaustiveness::ImpossibleBitArraySegmentPattern, 10 type_::{ 11 self, 12 error::{ 13 AssertImpossiblePattern, FeatureKind, LiteralCollectionKind, PanicPosition, 14 TodoOrPanic, UnreachablePatternReason, 15 }, 16 expression::ComparisonOutcome, 17 printer::Printer, 18 }, 19}; 20use camino::Utf8PathBuf; 21use debug_ignore::DebugIgnore; 22use ecow::EcoString; 23use itertools::Itertools; 24use std::{ 25 fmt::Write as _, 26 io::Write, 27 sync::{Arc, atomic::Ordering}, 28}; 29use std::{rc::Rc, sync::atomic::AtomicUsize}; 30use termcolor::Buffer; 31 32macro_rules! wrap_format { 33 ($($tts:tt)*) => { 34 wrap(&format!($($tts)*)) 35 } 36} 37 38pub trait WarningEmitterIO { 39 fn emit_warning(&self, warning: Warning); 40} 41 42#[derive(Debug, Clone, Copy)] 43pub struct NullWarningEmitterIO; 44 45impl WarningEmitterIO for NullWarningEmitterIO { 46 fn emit_warning(&self, _warning: Warning) {} 47} 48 49#[derive(Debug, Clone, Default)] 50pub struct VectorWarningEmitterIO { 51 pub warnings: Arc<std::sync::RwLock<Vec<Warning>>>, 52} 53 54impl VectorWarningEmitterIO { 55 pub fn new() -> Self { 56 Self::default() 57 } 58 59 pub fn take(&self) -> Vec<Warning> { 60 let mut warnings = self.write_lock(); 61 std::mem::take(&mut *warnings) 62 } 63 64 pub fn reset(&self) { 65 let mut warnings = self.write_lock(); 66 warnings.clear(); 67 } 68 69 pub fn pop(&self) -> Option<Warning> { 70 let mut warnings = self.write_lock(); 71 warnings.pop() 72 } 73 74 fn write_lock(&self) -> std::sync::RwLockWriteGuard<'_, Vec<Warning>> { 75 self.warnings.write().expect("Vector lock poisoned") 76 } 77} 78 79impl WarningEmitterIO for VectorWarningEmitterIO { 80 fn emit_warning(&self, warning: Warning) { 81 let mut warnings = self.write_lock(); 82 warnings.push(warning); 83 } 84} 85 86#[derive(Debug, Clone)] 87pub struct WarningEmitter { 88 /// The number of warnings emitted. 89 /// In the context of the project compiler this is the count for the root 90 /// package only, the count is reset back to zero after the dependencies are 91 /// compiled. 92 count: Arc<AtomicUsize>, 93 emitter: DebugIgnore<Rc<dyn WarningEmitterIO>>, 94} 95 96impl WarningEmitter { 97 pub fn new(emitter: Rc<dyn WarningEmitterIO>) -> Self { 98 Self { 99 count: Arc::new(AtomicUsize::new(0)), 100 emitter: DebugIgnore(emitter), 101 } 102 } 103 104 pub fn null() -> Self { 105 Self::new(Rc::new(NullWarningEmitterIO)) 106 } 107 108 pub fn reset_count(&self) { 109 self.count.store(0, Ordering::Relaxed); 110 } 111 112 pub fn count(&self) -> usize { 113 self.count.load(Ordering::Relaxed) 114 } 115 116 pub fn emit(&self, warning: Warning) { 117 _ = self.count.fetch_add(1, Ordering::Relaxed); 118 self.emitter.emit_warning(warning); 119 } 120 121 pub fn vector() -> (Self, Rc<VectorWarningEmitterIO>) { 122 let io = Rc::new(VectorWarningEmitterIO::default()); 123 let emitter = Self::new(io.clone()); 124 (emitter, Rc::clone(&io)) 125 } 126} 127 128#[derive(Debug, Clone)] 129pub struct TypeWarningEmitter { 130 module_path: Utf8PathBuf, 131 module_src: EcoString, 132 emitter: WarningEmitter, 133} 134 135impl TypeWarningEmitter { 136 pub fn new(module_path: Utf8PathBuf, module_src: EcoString, emitter: WarningEmitter) -> Self { 137 Self { 138 module_path, 139 module_src, 140 emitter, 141 } 142 } 143 144 pub fn null() -> Self { 145 Self { 146 module_path: Utf8PathBuf::new(), 147 module_src: EcoString::from(""), 148 emitter: WarningEmitter::new(Rc::new(NullWarningEmitterIO)), 149 } 150 } 151 152 pub fn emit(&self, warning: type_::Warning) { 153 self.emitter.emit(Warning::Type { 154 path: self.module_path.clone(), 155 src: self.module_src.clone(), 156 warning: Box::new(warning), 157 }); 158 } 159} 160 161#[derive(Debug, Clone, Eq, PartialEq)] 162pub enum Warning { 163 Type { 164 path: Utf8PathBuf, 165 src: EcoString, 166 warning: Box<type_::Warning>, 167 }, 168 169 InvalidSource { 170 path: Utf8PathBuf, 171 }, 172 173 DeprecatedSyntax { 174 path: Utf8PathBuf, 175 src: EcoString, 176 warning: DeprecatedSyntaxWarning, 177 }, 178 179 EmptyModule { 180 path: Utf8PathBuf, 181 name: EcoString, 182 }, 183 184 DetachedDocComment { 185 path: Utf8PathBuf, 186 src: EcoString, 187 location: SrcSpan, 188 }, 189} 190 191#[derive(Debug, Clone, Eq, PartialEq, Copy)] 192pub enum DeprecatedSyntaxWarning { 193 /// If someone uses the deprecated syntax to append to a list: 194 /// `["a"..rest]`, notice how there's no comma! 195 DeprecatedListPrepend { 196 location: SrcSpan, 197 }, 198 199 /// If someone uses the deprecated syntax to pattern match on a list: 200 /// ```gleam 201 /// case list { 202 /// [first..rest] -> todo 203 /// // ^^ notice there's no comma! 204 /// _ -> 205 /// } 206 /// ``` 207 /// 208 DeprecatedListPattern { 209 location: SrcSpan, 210 }, 211 212 /// If someone uses the deprecated syntax to match on all lists instead of 213 /// a common `_`: 214 /// ```gleam 215 /// case list { 216 /// [..] -> todo 217 /// //^^^^ this matches on all lists so a `_` should be used instead! 218 /// _ -> 219 /// } 220 /// ``` 221 /// 222 DeprecatedListCatchAllPattern { 223 location: SrcSpan, 224 }, 225 226 /// If a record pattern has a spread that is not preceded by a comma: 227 /// ```gleam 228 /// case wibble { 229 /// Wibble(arg1: name ..) -> todo 230 /// // ^^ this should be preceded by a comma! 231 /// } 232 /// ``` 233 /// 234 DeprecatedRecordSpreadPattern { 235 location: SrcSpan, 236 }, 237 238 /// If a guard has an empty clause : 239 /// ```gleam 240 /// case wibble { 241 /// big if -> True 242 /// ^^ This can be removed. 243 /// } 244 /// ``` 245 DeprecatedEmptyClauseGuard { 246 location: SrcSpan, 247 }, 248 249 DeprecatedTargetShorthand { 250 target: Target, 251 location: SrcSpan, 252 }, 253} 254 255impl Warning { 256 pub fn to_diagnostic(&self) -> Diagnostic { 257 match self { 258 Warning::InvalidSource { path } => Diagnostic { 259 title: "Invalid module name".into(), 260 text: wrap( 261 "Module names must begin with a lowercase letter and \ 262contain only lowercase alphanumeric characters or underscores.", 263 ), 264 level: diagnostic::Level::Warning, 265 location: None, 266 hint: Some(wrap_format!( 267 "Rename `{path}` to be valid, or remove this file from the project source." 268 )), 269 }, 270 271 Warning::DeprecatedSyntax { 272 path, 273 src, 274 warning: DeprecatedSyntaxWarning::DeprecatedListPrepend { location }, 275 } => Diagnostic { 276 title: "Deprecated prepend syntax".into(), 277 text: wrap( 278 "This syntax for prepending to a list is deprecated. 279When prepending an item to a list it should be preceded by a comma, \ 280like this: `[item, ..list]`.", 281 ), 282 283 hint: None, 284 level: diagnostic::Level::Warning, 285 location: Some(Location { 286 label: diagnostic::Label { 287 text: Some("This spread should be preceded by a comma".into()), 288 span: *location, 289 }, 290 path: path.clone(), 291 src: src.clone(), 292 extra_labels: vec![], 293 }), 294 }, 295 296 Warning::DeprecatedSyntax { 297 path, 298 src, 299 warning: DeprecatedSyntaxWarning::DeprecatedListPattern { location }, 300 } => Diagnostic { 301 title: "Deprecated list pattern matching syntax".into(), 302 text: wrap( 303 "This syntax for pattern matching on a list is deprecated. 304When matching on the rest of a list it should always be preceded by a comma, \ 305like this: `[item, ..list]`.", 306 ), 307 hint: None, 308 level: diagnostic::Level::Warning, 309 location: Some(Location { 310 label: diagnostic::Label { 311 text: Some("This spread should be preceded by a comma".into()), 312 span: *location, 313 }, 314 path: path.clone(), 315 src: src.clone(), 316 extra_labels: vec![], 317 }), 318 }, 319 320 Warning::DeprecatedSyntax { 321 path, 322 src, 323 warning: DeprecatedSyntaxWarning::DeprecatedRecordSpreadPattern { location }, 324 } => Diagnostic { 325 title: "Deprecated record pattern matching syntax".into(), 326 text: wrap("This syntax for pattern matching on a record is deprecated."), 327 hint: None, 328 level: diagnostic::Level::Warning, 329 location: Some(Location { 330 label: diagnostic::Label { 331 text: Some("This should be preceded by a comma".into()), 332 span: *location, 333 }, 334 path: path.clone(), 335 src: src.clone(), 336 extra_labels: vec![], 337 }), 338 }, 339 340 Warning::DeprecatedSyntax { 341 path, 342 src, 343 warning: DeprecatedSyntaxWarning::DeprecatedListCatchAllPattern { location }, 344 } => Diagnostic { 345 title: "Deprecated list pattern matching syntax".into(), 346 text: wrap( 347 "This syntax for pattern matching on lists is deprecated. 348To match on all possible lists, use the `_` catch-all pattern instead.", 349 ), 350 hint: None, 351 level: diagnostic::Level::Warning, 352 location: Some(Location { 353 label: diagnostic::Label { 354 text: Some("This can be replaced with `_`".into()), 355 span: *location, 356 }, 357 path: path.clone(), 358 src: src.clone(), 359 extra_labels: vec![], 360 }), 361 }, 362 Warning::DeprecatedSyntax { 363 path, 364 src, 365 warning: DeprecatedSyntaxWarning::DeprecatedEmptyClauseGuard { location }, 366 } => Diagnostic { 367 title: "Deprecated empty guard syntax".into(), 368 text: wrap( 369 "This syntax for an empty guard is deprecated. \ 370To have a clause without a guard, remove this.", 371 ), 372 hint: None, 373 level: diagnostic::Level::Warning, 374 location: Some(Location { 375 label: diagnostic::Label { 376 text: Some("This can be removed.".into()), 377 span: *location, 378 }, 379 path: path.clone(), 380 src: src.clone(), 381 extra_labels: vec![], 382 }), 383 }, 384 Warning::DeprecatedSyntax { 385 path, 386 src, 387 warning: DeprecatedSyntaxWarning::DeprecatedTargetShorthand { location, target }, 388 } => { 389 let full_name = match target { 390 Target::Erlang => "erlang", 391 Target::JavaScript => "javascript", 392 }; 393 394 Diagnostic { 395 title: "Deprecated target shorthand syntax".into(), 396 text: wrap_format!( 397 "This shorthand target name is deprecated. \ 398Use the full name: `{full_name}` instead." 399 ), 400 hint: None, 401 level: diagnostic::Level::Warning, 402 location: Some(Location { 403 label: diagnostic::Label { 404 text: Some(format!("This should be replaced with `{full_name}`")), 405 span: *location, 406 }, 407 path: path.clone(), 408 src: src.clone(), 409 extra_labels: vec![], 410 }), 411 } 412 } 413 414 Warning::DetachedDocComment { 415 path, 416 src, 417 location, 418 } => Diagnostic { 419 title: "Detached doc comment".into(), 420 text: wrap( 421 "This doc comment is followed by a regular \ 422comment so it is not attached to any definition.", 423 ), 424 level: diagnostic::Level::Warning, 425 location: Some(Location { 426 path: path.to_path_buf(), 427 src: src.clone(), 428 label: diagnostic::Label { 429 text: Some("This is not attached to a definition".into()), 430 span: *location, 431 }, 432 extra_labels: Vec::new(), 433 }), 434 hint: Some("Move the comment above the doc comment".into()), 435 }, 436 437 Warning::Type { path, warning, src } => match warning.as_ref() { 438 type_::Warning::Todo { 439 kind, 440 location, 441 type_, 442 names, 443 } => { 444 let mut text = String::new(); 445 text.push_str( 446 "\ 447This code will crash if it is run. Be sure to finish it before 448running your program.", 449 ); 450 let title = match kind { 451 TodoKind::Keyword => "Todo found", 452 TodoKind::EmptyBlock => { 453 text.push_str( 454 " 455A block must always contain at least one expression.", 456 ); 457 "Incomplete block" 458 } 459 TodoKind::EmptyFunction { .. } => "Unimplemented function", 460 TodoKind::IncompleteUse => { 461 text.push_str( 462 " 463A use expression must always be followed by at least one expression.", 464 ); 465 "Incomplete use expression" 466 } 467 } 468 .into(); 469 470 let hint = if !type_.is_variable() { 471 Some(format!( 472 "I think its type is `{}`.\n", 473 Printer::new(names).print_type(type_) 474 )) 475 } else { 476 None 477 }; 478 479 Diagnostic { 480 title, 481 text, 482 level: diagnostic::Level::Warning, 483 location: Some(Location { 484 path: path.to_path_buf(), 485 src: src.clone(), 486 label: diagnostic::Label { 487 text: Some("This code is incomplete".into()), 488 span: *location, 489 }, 490 extra_labels: Vec::new(), 491 }), 492 hint, 493 } 494 } 495 496 type_::Warning::ImplicitlyDiscardedResult { location } => Diagnostic { 497 title: "Unused result value".into(), 498 text: "".into(), 499 hint: Some( 500 "If you are sure you don't need it you can assign it to `_`.".into(), 501 ), 502 level: diagnostic::Level::Warning, 503 location: Some(Location { 504 path: path.to_path_buf(), 505 src: src.clone(), 506 label: diagnostic::Label { 507 text: Some("The Result value created here is unused".into()), 508 span: *location, 509 }, 510 extra_labels: Vec::new(), 511 }), 512 }, 513 514 type_::Warning::UnusedLiteral { location } => Diagnostic { 515 title: "Unused literal".into(), 516 text: "".into(), 517 hint: Some("You can safely remove it.".into()), 518 level: diagnostic::Level::Warning, 519 location: Some(Location { 520 path: path.to_path_buf(), 521 src: src.clone(), 522 label: diagnostic::Label { 523 text: Some("This value is never used".into()), 524 span: *location, 525 }, 526 extra_labels: Vec::new(), 527 }), 528 }, 529 530 type_::Warning::NoFieldsRecordUpdate { location } => Diagnostic { 531 title: "Fieldless record update".into(), 532 text: "".into(), 533 hint: Some( 534 "Add some fields to change or replace it with the record itself.".into(), 535 ), 536 level: diagnostic::Level::Warning, 537 location: Some(Location { 538 path: path.to_path_buf(), 539 src: src.clone(), 540 label: diagnostic::Label { 541 text: Some("This record update doesn't change any fields".into()), 542 span: *location, 543 }, 544 extra_labels: Vec::new(), 545 }), 546 }, 547 548 type_::Warning::AllFieldsRecordUpdate { location, .. } => Diagnostic { 549 title: "Redundant record update".into(), 550 text: "".into(), 551 hint: Some("It is better style to use the record creation syntax.".into()), 552 level: diagnostic::Level::Warning, 553 location: Some(Location { 554 src: src.clone(), 555 path: path.to_path_buf(), 556 label: diagnostic::Label { 557 text: Some("This record update specifies all fields".into()), 558 span: *location, 559 }, 560 extra_labels: Vec::new(), 561 }), 562 }, 563 564 type_::Warning::UnusedType { 565 location, imported, .. 566 } => { 567 let title = if *imported { 568 "Unused imported type".into() 569 } else { 570 "Unused private type".into() 571 }; 572 let label = if *imported { 573 "This imported type is never used".into() 574 } else { 575 "This private type is never used".into() 576 }; 577 Diagnostic { 578 title, 579 text: "".into(), 580 hint: Some("You can safely remove it.".into()), 581 level: diagnostic::Level::Warning, 582 location: Some(Location { 583 src: src.clone(), 584 path: path.to_path_buf(), 585 label: diagnostic::Label { 586 text: Some(label), 587 span: *location, 588 }, 589 extra_labels: Vec::new(), 590 }), 591 } 592 } 593 594 type_::Warning::UnusedConstructor { 595 location, imported, .. 596 } => { 597 let title = if *imported { 598 "Unused imported item".into() 599 } else { 600 "Unused private constructor".into() 601 }; 602 let label = if *imported { 603 "This imported constructor is never used".into() 604 } else { 605 "This private constructor is never used".into() 606 }; 607 Diagnostic { 608 title, 609 text: "".into(), 610 hint: Some("You can safely remove it.".into()), 611 level: diagnostic::Level::Warning, 612 location: Some(Location { 613 src: src.clone(), 614 path: path.to_path_buf(), 615 label: diagnostic::Label { 616 text: Some(label), 617 span: *location, 618 }, 619 extra_labels: Vec::new(), 620 }), 621 } 622 } 623 624 type_::Warning::UnusedImportedModule { location, .. } => Diagnostic { 625 title: "Unused imported module".into(), 626 text: "".into(), 627 hint: Some("You can safely remove it.".into()), 628 level: diagnostic::Level::Warning, 629 location: Some(Location { 630 src: src.clone(), 631 path: path.to_path_buf(), 632 label: diagnostic::Label { 633 text: Some("This imported module is never used".into()), 634 span: *location, 635 }, 636 extra_labels: Vec::new(), 637 }), 638 }, 639 640 type_::Warning::UnusedImportedModuleAlias { 641 location, 642 module_name, 643 .. 644 } => { 645 let text = format!( 646 "\ 647Hint: You can safely remove it. 648 649 import {module_name} as _ 650" 651 ); 652 Diagnostic { 653 title: "Unused imported module alias".into(), 654 text, 655 hint: None, 656 level: diagnostic::Level::Warning, 657 location: Some(Location { 658 src: src.clone(), 659 path: path.to_path_buf(), 660 label: diagnostic::Label { 661 text: Some("This alias is never used".into()), 662 span: *location, 663 }, 664 extra_labels: Vec::new(), 665 }), 666 } 667 } 668 669 type_::Warning::UnusedImportedValue { location, .. } => Diagnostic { 670 title: "Unused imported value".into(), 671 text: "".into(), 672 hint: Some("You can safely remove it.".into()), 673 level: diagnostic::Level::Warning, 674 location: Some(Location { 675 src: src.clone(), 676 path: path.to_path_buf(), 677 label: diagnostic::Label { 678 text: Some("This imported value is never used".into()), 679 span: *location, 680 }, 681 extra_labels: Vec::new(), 682 }), 683 }, 684 685 type_::Warning::UnusedPrivateModuleConstant { location, .. } => Diagnostic { 686 title: "Unused private constant".into(), 687 text: "".into(), 688 hint: Some("You can safely remove it.".into()), 689 level: diagnostic::Level::Warning, 690 location: Some(Location { 691 src: src.clone(), 692 path: path.to_path_buf(), 693 label: diagnostic::Label { 694 text: Some("This private constant is never used".into()), 695 span: *location, 696 }, 697 extra_labels: Vec::new(), 698 }), 699 }, 700 701 type_::Warning::UnusedPrivateFunction { location, .. } => Diagnostic { 702 title: "Unused private function".into(), 703 text: "".into(), 704 hint: Some("You can safely remove it.".into()), 705 level: diagnostic::Level::Warning, 706 location: Some(Location { 707 src: src.clone(), 708 path: path.to_path_buf(), 709 label: diagnostic::Label { 710 text: Some("This private function is never used".into()), 711 span: *location, 712 }, 713 extra_labels: Vec::new(), 714 }), 715 }, 716 717 type_::Warning::UnusedVariable { location, origin } => Diagnostic { 718 title: if origin.is_function_parameter() { 719 "Unused function argument".into() 720 } else { 721 "Unused variable".into() 722 }, 723 text: "".into(), 724 hint: origin.how_to_ignore(), 725 level: diagnostic::Level::Warning, 726 location: Some(Location { 727 src: src.clone(), 728 path: path.to_path_buf(), 729 label: diagnostic::Label { 730 text: if origin.is_function_parameter() { 731 Some("This argument is never used".into()) 732 } else { 733 Some("This variable is never used".into()) 734 }, 735 span: *location, 736 }, 737 extra_labels: Vec::new(), 738 }), 739 }, 740 741 type_::Warning::UnusedRecursiveArgument { location } => Diagnostic { 742 title: "Unused function argument".into(), 743 text: wrap( 744 "This argument is passed to the function when recursing, \ 745but it's never used for anything.", 746 ), 747 hint: None, 748 level: diagnostic::Level::Warning, 749 location: Some(Location { 750 src: src.clone(), 751 path: path.to_path_buf(), 752 label: diagnostic::Label { 753 text: Some("This argument is never used".into()), 754 span: *location, 755 }, 756 extra_labels: vec![], 757 }), 758 }, 759 760 type_::Warning::UnnecessaryDoubleIntNegation { location } => Diagnostic { 761 title: "Unnecessary double negation (--) on integer".into(), 762 text: "".into(), 763 hint: None, 764 level: diagnostic::Level::Warning, 765 location: Some(Location { 766 src: src.clone(), 767 path: path.to_path_buf(), 768 label: diagnostic::Label { 769 text: Some("You can safely remove this.".into()), 770 span: *location, 771 }, 772 extra_labels: Vec::new(), 773 }), 774 }, 775 type_::Warning::UnnecessaryDoubleBoolNegation { location } => Diagnostic { 776 title: "Unnecessary double negation (!!) on bool".into(), 777 text: "".into(), 778 hint: None, 779 level: diagnostic::Level::Warning, 780 location: Some(Location { 781 src: src.clone(), 782 path: path.to_path_buf(), 783 label: diagnostic::Label { 784 text: Some("You can safely remove this.".into()), 785 span: *location, 786 }, 787 extra_labels: Vec::new(), 788 }), 789 }, 790 type_::Warning::InefficientEmptyListCheck { location, kind } => { 791 use type_::error::EmptyListCheckKind; 792 let text = wrap( 793 "The `list.length` function has to iterate across the whole \ 794list to calculate the length, which is wasteful if you only \ 795need to know if the list is empty or not.", 796 ); 797 let hint = Some(match kind { 798 EmptyListCheckKind::Empty => "You can use `the_list == []` instead.".into(), 799 EmptyListCheckKind::NonEmpty => { 800 "You can use `the_list != []` instead.".into() 801 } 802 }); 803 804 Diagnostic { 805 title: "Inefficient use of `list.length`".into(), 806 text, 807 hint, 808 level: diagnostic::Level::Warning, 809 location: Some(Location { 810 src: src.clone(), 811 path: path.to_path_buf(), 812 label: diagnostic::Label { 813 text: None, 814 span: *location, 815 }, 816 extra_labels: Vec::new(), 817 }), 818 } 819 } 820 821 type_::Warning::TransitiveDependencyImported { 822 location, 823 module, 824 package, 825 } => { 826 let text = wrap_format!( 827 "The module `{module}` is being imported, but \ 828`{package}`, the package it belongs to, is not a direct dependency of your \ 829package. 830In a future version of Gleam this may become a compile error. 831 832Run this command to add it to your dependencies: 833 834 gleam add {package} 835" 836 ); 837 Diagnostic { 838 title: "Transitive dependency imported".into(), 839 text, 840 hint: None, 841 level: diagnostic::Level::Warning, 842 location: Some(Location { 843 src: src.clone(), 844 path: path.to_path_buf(), 845 label: diagnostic::Label { 846 text: None, 847 span: *location, 848 }, 849 extra_labels: Vec::new(), 850 }), 851 } 852 } 853 854 type_::Warning::DeprecatedItem { 855 location, 856 message, 857 layer, 858 } => { 859 let text = wrap_format!("It was deprecated with this message: {message}"); 860 let (title, diagnostic_label_text) = if layer.is_value() { 861 ( 862 "Deprecated value used".into(), 863 Some("This value has been deprecated".into()), 864 ) 865 } else { 866 ( 867 "Deprecated type used".into(), 868 Some("This type has been deprecated".into()), 869 ) 870 }; 871 872 Diagnostic { 873 title, 874 text, 875 hint: None, 876 level: diagnostic::Level::Warning, 877 location: Some(Location { 878 src: src.clone(), 879 path: path.to_path_buf(), 880 label: diagnostic::Label { 881 text: diagnostic_label_text, 882 span: *location, 883 }, 884 extra_labels: Vec::new(), 885 }), 886 } 887 } 888 889 type_::Warning::UnreachableCasePattern { location, reason } => { 890 let text = match reason { 891 UnreachablePatternReason::DuplicatePattern => wrap( 892 "This pattern cannot be reached as a previous \ 893pattern matches the same values.", 894 ), 895 UnreachablePatternReason::ImpossibleVariant => wrap( 896 "This pattern cannot be reached as it matches on \ 897a variant of a type which is never present.", 898 ), 899 UnreachablePatternReason::ImpossibleSegments(_) => wrap( 900 "This pattern cannot be reached as it contains \ 901segments that will never match.", 902 ), 903 }; 904 905 let extra_labels = match reason { 906 UnreachablePatternReason::DuplicatePattern 907 | UnreachablePatternReason::ImpossibleVariant => vec![], 908 UnreachablePatternReason::ImpossibleSegments(segments) => segments 909 .iter() 910 .map(|segment| ExtraLabel { 911 src_info: None, 912 label: diagnostic::Label { 913 text: Some(explain_impossible_segment(segment)), 914 span: segment.location(), 915 }, 916 }) 917 .collect_vec(), 918 }; 919 920 Diagnostic { 921 title: "Unreachable pattern".into(), 922 text, 923 hint: Some("It can be safely removed.".into()), 924 level: diagnostic::Level::Warning, 925 location: Some(Location { 926 src: src.clone(), 927 path: path.to_path_buf(), 928 label: diagnostic::Label { 929 text: None, 930 span: *location, 931 }, 932 extra_labels, 933 }), 934 } 935 } 936 937 type_::Warning::CaseMatchOnLiteralCollection { kind, location } => { 938 let kind = match kind { 939 LiteralCollectionKind::List => "list", 940 LiteralCollectionKind::Tuple => "tuple", 941 LiteralCollectionKind::Record => "record", 942 }; 943 944 let title = format!("Redundant {kind}"); 945 let text = wrap_format!( 946 "Instead of building a {kind} and matching on it, \ 947you can match on its contents directly. 948A case expression can take multiple subjects separated by commas like this: 949 950 case one_subject, another_subject {{ 951 _, _ -> todo 952 }} 953 954See: https://tour.gleam.run/flow-control/multiple-subjects/" 955 ); 956 957 Diagnostic { 958 title, 959 text, 960 hint: None, 961 level: diagnostic::Level::Warning, 962 location: Some(Location { 963 src: src.clone(), 964 path: path.to_path_buf(), 965 label: diagnostic::Label { 966 text: Some(format!("You can remove this {kind} wrapper")), 967 span: *location, 968 }, 969 extra_labels: Vec::new(), 970 }), 971 } 972 } 973 974 type_::Warning::CaseMatchOnLiteralValue { location } => Diagnostic { 975 title: "Match on a literal value".into(), 976 text: wrap( 977 "Matching on a literal value is redundant since you \ 978can already tell which branch is going to match with this value.", 979 ), 980 hint: None, 981 level: diagnostic::Level::Warning, 982 location: Some(Location { 983 src: src.clone(), 984 path: path.to_path_buf(), 985 label: diagnostic::Label { 986 text: Some("There's no need to pattern match on this value".into()), 987 span: *location, 988 }, 989 extra_labels: Vec::new(), 990 }), 991 }, 992 993 type_::Warning::OpaqueExternalType { location } => Diagnostic { 994 title: "Opaque external type".into(), 995 text: "This type has no constructors so making it opaque is redundant.".into(), 996 hint: Some("Remove the `opaque` qualifier from the type definition.".into()), 997 level: diagnostic::Level::Warning, 998 location: Some(Location { 999 src: src.clone(), 1000 path: path.to_path_buf(), 1001 label: diagnostic::Label { 1002 text: None, 1003 span: *location, 1004 }, 1005 extra_labels: Vec::new(), 1006 }), 1007 }, 1008 1009 type_::Warning::UnusedValue { location } => Diagnostic { 1010 title: "Unused value".into(), 1011 text: wrap( 1012 "This expression computes a value without any side \ 1013effects, but then the value isn't used at all. You might want to assign it to a \ 1014variable, or delete the expression entirely if it's not needed.", 1015 ), 1016 hint: None, 1017 level: diagnostic::Level::Warning, 1018 location: Some(Location { 1019 path: path.to_path_buf(), 1020 src: src.clone(), 1021 label: diagnostic::Label { 1022 text: Some("This value is never used".into()), 1023 span: *location, 1024 }, 1025 extra_labels: Vec::new(), 1026 }), 1027 }, 1028 1029 type_::Warning::RedundantAssertAssignment { location } => Diagnostic { 1030 title: "Redundant assertion".into(), 1031 text: wrap( 1032 "This assertion is redundant since the pattern covers all possibilities.", 1033 ), 1034 hint: None, 1035 level: diagnostic::Level::Warning, 1036 location: Some(Location { 1037 label: diagnostic::Label { 1038 text: Some("You can remove this".into()), 1039 span: *location, 1040 }, 1041 path: path.clone(), 1042 src: src.clone(), 1043 extra_labels: vec![], 1044 }), 1045 }, 1046 1047 type_::Warning::AssertAssignmentOnImpossiblePattern { location, reason } => { 1048 let extra_labels = match reason { 1049 AssertImpossiblePattern::InferredVariant => vec![], 1050 AssertImpossiblePattern::ImpossibleSegments { segments } => segments 1051 .iter() 1052 .map(|segment| ExtraLabel { 1053 src_info: None, 1054 label: diagnostic::Label { 1055 text: Some(explain_impossible_segment(segment)), 1056 span: segment.location(), 1057 }, 1058 }) 1059 .collect_vec(), 1060 }; 1061 1062 Diagnostic { 1063 title: "Assertion that will always fail".into(), 1064 text: wrap( 1065 "We can tell from the code above that the value will never match \ 1066this pattern and that this code will always crash. 1067 1068Either change the pattern or use `panic` to unconditionally fail.", 1069 ), 1070 hint: None, 1071 level: diagnostic::Level::Warning, 1072 location: Some(Location { 1073 label: diagnostic::Label { 1074 text: None, 1075 span: *location, 1076 }, 1077 path: path.clone(), 1078 src: src.clone(), 1079 extra_labels, 1080 }), 1081 } 1082 } 1083 1084 type_::Warning::TodoOrPanicUsedAsFunction { 1085 kind, 1086 location, 1087 arguments_location, 1088 arguments, 1089 } => { 1090 let title = match kind { 1091 TodoOrPanic::Todo => "Todo used as a function".into(), 1092 TodoOrPanic::Panic => "Panic used as a function".into(), 1093 }; 1094 let label_location = match arguments_location { 1095 None => location, 1096 Some(location) => location, 1097 }; 1098 let name = match kind { 1099 TodoOrPanic::Todo => "todo", 1100 TodoOrPanic::Panic => "panic", 1101 }; 1102 let mut text = format!("`{name}` is not a function"); 1103 match arguments { 1104 0 => { 1105 let _ = write!( 1106 text, 1107 ", you can just write `{name}` instead of `{name}()`." 1108 ); 1109 } 1110 1 => text.push_str( 1111 " and will crash before it can do anything with this argument.", 1112 ), 1113 _ => text.push_str( 1114 " and will crash before it can do anything with these arguments.", 1115 ), 1116 } 1117 1118 let hint = match arguments { 1119 0 => None, 1120 _ => Some(wrap_format!( 1121 "if you want to display an error message you should write 1122`{name} as \"my error message\"` 1123See: https://tour.gleam.run/advanced-features/{name}/" 1124 )), 1125 }; 1126 1127 Diagnostic { 1128 title, 1129 text: wrap(&text), 1130 hint, 1131 level: diagnostic::Level::Warning, 1132 location: Some(Location { 1133 label: diagnostic::Label { 1134 text: None, 1135 span: *label_location, 1136 }, 1137 path: path.clone(), 1138 src: src.clone(), 1139 extra_labels: vec![], 1140 }), 1141 } 1142 } 1143 1144 type_::Warning::UnreachableCodeAfterPanic { 1145 location, 1146 panic_position: unreachable_code_kind, 1147 } => { 1148 let text = match unreachable_code_kind { 1149 PanicPosition::PreviousExpression => { 1150 "This code is unreachable because it comes after a `panic`." 1151 } 1152 PanicPosition::PreviousFunctionArgument => { 1153 "This argument is unreachable because the previous one always panics. \ 1154Your code will crash before reaching this point." 1155 } 1156 PanicPosition::LastFunctionArgument => { 1157 "This function call is unreachable because its last argument always panics. \ 1158Your code will crash before reaching this point." 1159 } 1160 PanicPosition::EchoExpression => { 1161 "This `echo` won't print anything because the expression it \ 1162should be printing always panics." 1163 } 1164 }; 1165 1166 Diagnostic { 1167 title: "Unreachable code".into(), 1168 text: wrap(text), 1169 hint: None, 1170 level: diagnostic::Level::Warning, 1171 location: Some(Location { 1172 label: diagnostic::Label { 1173 text: None, 1174 span: *location, 1175 }, 1176 path: path.clone(), 1177 src: src.clone(), 1178 extra_labels: vec![], 1179 }), 1180 } 1181 } 1182 1183 type_::Warning::RedundantPipeFunctionCapture { location } => Diagnostic { 1184 title: "Redundant function capture".into(), 1185 text: wrap( 1186 "This function capture is redundant since the value is \ 1187already piped as the first argument of this call. 1188 1189See: https://tour.gleam.run/functions/pipelines/", 1190 ), 1191 hint: None, 1192 level: diagnostic::Level::Warning, 1193 location: Some(Location { 1194 label: diagnostic::Label { 1195 text: Some("You can safely remove this".into()), 1196 span: *location, 1197 }, 1198 path: path.clone(), 1199 src: src.clone(), 1200 extra_labels: vec![], 1201 }), 1202 }, 1203 type_::Warning::FeatureRequiresHigherGleamVersion { 1204 location, 1205 minimum_required_version, 1206 wrongfully_allowed_version, 1207 feature_kind, 1208 } => { 1209 let feature = match feature_kind { 1210 FeatureKind::LabelShorthandSyntax => "The label shorthand syntax was", 1211 FeatureKind::ConstantStringConcatenation => { 1212 "Constant strings concatenation was" 1213 } 1214 FeatureKind::ArithmeticInGuards => "Arithmetic operations in guards were", 1215 FeatureKind::ConcatenateInGuards => "String concatenation in guards was", 1216 FeatureKind::UnannotatedUtf8StringSegment => { 1217 "The ability to omit the `utf8` annotation for string segments was" 1218 } 1219 FeatureKind::UnannotatedFloatSegment => { 1220 "The ability to omit the `float` annotation for float segments was" 1221 } 1222 FeatureKind::NestedTupleAccess => { 1223 "The ability to access nested tuple fields was" 1224 } 1225 FeatureKind::InternalAnnotation => "The `@internal` annotation was", 1226 FeatureKind::AtInJavascriptModules => { 1227 "The ability to have `@` in a Javascript module's name was" 1228 } 1229 FeatureKind::RecordUpdateVariantInference => { 1230 "Record updates for custom types when the variant is known was" 1231 } 1232 FeatureKind::RecordAccessVariantInference => { 1233 "Field access on custom types when the variant is known was" 1234 } 1235 FeatureKind::LetAssertWithMessage => { 1236 "Specifying a custom panic message when using let assert was" 1237 } 1238 FeatureKind::VariantWithDeprecatedAnnotation => { 1239 "Deprecating individual custom type variants was" 1240 } 1241 FeatureKind::JavaScriptUnalignedBitArray => { 1242 "Use of unaligned bit arrays on the JavaScript target was" 1243 } 1244 FeatureKind::BoolAssert => "The bool `assert` statement was", 1245 FeatureKind::ExpressionInSegmentSize => "Expressions in segment sizes were", 1246 FeatureKind::ExternalCustomType => { 1247 "The `@external` annotation on custom types was" 1248 } 1249 FeatureKind::ConstantRecordUpdate => { 1250 "The record update syntax for constants was" 1251 } 1252 FeatureKind::ConstantListWithTail => "Prepending to a constant list was", 1253 }; 1254 1255 Diagnostic { 1256 title: "Incompatible gleam version range".into(), 1257 text: wrap_format!( 1258 "{feature} introduced in version v{minimum_required_version}. But the Gleam version range \ 1259 specified in your `gleam.toml` would allow this code to run on an earlier \ 1260 version like v{wrongfully_allowed_version}, resulting in compilation errors!", 1261 ), 1262 hint: Some(wrap_format!( 1263 "Remove the version constraint from your `gleam.toml` or update it to be: 1264 1265 gleam = \">= {minimum_required_version}\"" 1266 )), 1267 level: diagnostic::Level::Warning, 1268 location: Some(Location { 1269 label: diagnostic::Label { 1270 text: Some(format!( 1271 "This requires a Gleam version >= {minimum_required_version}" 1272 )), 1273 span: *location, 1274 }, 1275 path: path.clone(), 1276 src: src.clone(), 1277 extra_labels: vec![], 1278 }), 1279 } 1280 } 1281 1282 type_::Warning::JavaScriptIntUnsafe { location } => Diagnostic { 1283 title: "Int is outside JavaScript's safe integer range".into(), 1284 text: wrap( 1285 "This integer value is too large to be represented accurately by \ 1286JavaScript's number type. To avoid this warning integer values must be in the range \ 1287-(2^53 - 1) - (2^53 - 1). 1288 1289See JavaScript's Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER properties for more \ 1290information.", 1291 ), 1292 hint: None, 1293 level: diagnostic::Level::Warning, 1294 location: Some(Location { 1295 path: path.to_path_buf(), 1296 src: src.clone(), 1297 label: diagnostic::Label { 1298 text: Some("This is not a safe integer value on JavaScript".into()), 1299 span: *location, 1300 }, 1301 extra_labels: Vec::new(), 1302 }), 1303 }, 1304 1305 type_::Warning::BitArraySegmentTruncatedValue { 1306 location: _, 1307 truncation: 1308 BitArraySegmentTruncation { 1309 truncated_value, 1310 truncated_into, 1311 segment_bits, 1312 value_location, 1313 }, 1314 } => { 1315 let (unit, segment_size, taken) = if segment_bits % 8 == 0 { 1316 let bytes = segment_bits / 8; 1317 let segment_size = pluralise(format!("{bytes} byte"), bytes); 1318 let taken = if bytes == 1 { 1319 "first byte".into() 1320 } else { 1321 format!("first {bytes} bytes") 1322 }; 1323 1324 ("bytes", segment_size, taken) 1325 } else { 1326 let segment_size = pluralise(format!("{segment_bits} bit"), *segment_bits); 1327 let taken = if *segment_bits == 1 { 1328 "first bit".into() 1329 } else { 1330 format!("first {segment_bits} bits") 1331 }; 1332 ("bits", segment_size, taken) 1333 }; 1334 1335 let text = wrap_format!( 1336 "This segment is {segment_size} long, but {truncated_value} \ 1337doesn't fit in that many {unit}. It would be truncated by taking its {taken}, \ 1338resulting in the value {truncated_into}." 1339 ); 1340 1341 Diagnostic { 1342 title: "Truncated bit array segment".into(), 1343 text, 1344 hint: None, 1345 level: diagnostic::Level::Warning, 1346 location: Some(Location { 1347 path: path.to_path_buf(), 1348 src: src.clone(), 1349 label: diagnostic::Label { 1350 text: Some(format!( 1351 "You can safely replace this with {truncated_into}" 1352 )), 1353 span: *value_location, 1354 }, 1355 extra_labels: vec![], 1356 }), 1357 } 1358 } 1359 1360 type_::Warning::JavaScriptBitArrayUnsafeInt { location, size } => { 1361 let text = wrap_format!( 1362 "This segment is a {size}-bit long int, but on the \ 1363JavaScript target numbers have at most 52 bits. It would be truncated to its \ 1364first 52 bits." 1365 ); 1366 1367 Diagnostic { 1368 title: "Truncated bit array segment".into(), 1369 text: wrap(&text), 1370 hint: Some("Did you mean to use the `bytes` segment option?".into()), 1371 level: diagnostic::Level::Warning, 1372 location: Some(Location { 1373 path: path.to_path_buf(), 1374 src: src.clone(), 1375 label: diagnostic::Label { 1376 text: None, 1377 span: *location, 1378 }, 1379 extra_labels: vec![], 1380 }), 1381 } 1382 } 1383 1384 type_::Warning::AssertLiteralBool { location } => Diagnostic { 1385 title: "Assertion of a literal value".into(), 1386 text: wrap( 1387 "Asserting on a literal bool is redundant since you \ 1388can already tell whether it will be `True` or `False`.", 1389 ), 1390 hint: None, 1391 level: diagnostic::Level::Warning, 1392 location: Some(Location { 1393 src: src.clone(), 1394 path: path.to_path_buf(), 1395 label: diagnostic::Label { 1396 text: None, 1397 span: *location, 1398 }, 1399 extra_labels: Vec::new(), 1400 }), 1401 }, 1402 1403 type_::Warning::ModuleImportedTwice { 1404 name, 1405 first, 1406 second, 1407 } => Diagnostic { 1408 title: "Duplicate import".into(), 1409 text: format!("The {name} module has been imported twice."), 1410 hint: None, 1411 level: diagnostic::Level::Warning, 1412 location: Some(Location { 1413 src: src.clone(), 1414 path: path.to_path_buf(), 1415 label: diagnostic::Label { 1416 text: Some("Reimported here".into()), 1417 span: *second, 1418 }, 1419 extra_labels: vec![ExtraLabel { 1420 src_info: None, 1421 label: diagnostic::Label { 1422 text: Some("First imported here".into()), 1423 span: *first, 1424 }, 1425 }], 1426 }), 1427 }, 1428 1429 type_::Warning::UnusedDiscardPattern { location, name } => Diagnostic { 1430 title: "Unused discard pattern".into(), 1431 text: format!("`_ as {name}` can be written more concisely as `{name}`"), 1432 level: diagnostic::Level::Warning, 1433 location: Some(Location { 1434 src: src.clone(), 1435 path: path.to_path_buf(), 1436 label: diagnostic::Label { 1437 text: None, 1438 span: SrcSpan { 1439 start: location.start - "_ as ".len() as u32, 1440 end: location.end, 1441 }, 1442 }, 1443 extra_labels: vec![], 1444 }), 1445 hint: None, 1446 }, 1447 1448 type_::Warning::TopLevelDefinitionShadowsImport { location, name } => { 1449 let text = wrap_format!( 1450 "Definition of {name} shadows an imported value. 1451The imported value could not be used in this module anyway." 1452 ); 1453 Diagnostic { 1454 title: "Shadowed Import".into(), 1455 text, 1456 level: diagnostic::Level::Warning, 1457 location: Some(Location { 1458 path: path.clone(), 1459 src: src.clone(), 1460 label: diagnostic::Label { 1461 text: Some(wrap_format!("`{name}` is defined here")), 1462 span: *location, 1463 }, 1464 extra_labels: Vec::new(), 1465 }), 1466 hint: Some("Either rename the definition or remove the import.".into()), 1467 } 1468 } 1469 1470 type_::Warning::RedundantComparison { location, outcome } => Diagnostic { 1471 title: "Redundant comparison".into(), 1472 text: format!( 1473 "This comparison is redundant since it always {}.", 1474 match outcome { 1475 ComparisonOutcome::AlwaysSucceeds => "succeeds", 1476 ComparisonOutcome::AlwaysFails => "fails", 1477 } 1478 ), 1479 hint: None, 1480 level: diagnostic::Level::Warning, 1481 location: Some(Location { 1482 label: diagnostic::Label { 1483 text: Some(format!( 1484 "This is always `{}`", 1485 match outcome { 1486 ComparisonOutcome::AlwaysSucceeds => "True", 1487 ComparisonOutcome::AlwaysFails => "False", 1488 } 1489 )), 1490 span: *location, 1491 }, 1492 path: path.clone(), 1493 src: src.clone(), 1494 extra_labels: vec![], 1495 }), 1496 }, 1497 1498 type_::Warning::PipeIntoCallWhichReturnsFunction { location } => Diagnostic { 1499 title: "Deprecated pipe use".into(), 1500 text: wrap( 1501 "This use of pipelines is deprecated, as it creates \ 1502ambiguity in pipe syntax. Currently `a |> b(c)` can desugar to either `b(a, c)` or \ 1503`b(a)(c)` depending on the type of `b`. The latter syntax is deprecated, and should \ 1504not be used.", 1505 ), 1506 hint: Some( 1507 "Add an extra set of parentheses after the call, like `a |> b(c)()`".into(), 1508 ), 1509 level: diagnostic::Level::Warning, 1510 location: Some(Location { 1511 label: diagnostic::Label { 1512 text: Some("This should have an extra set of parentheses".into()), 1513 span: *location, 1514 }, 1515 path: path.clone(), 1516 src: src.clone(), 1517 extra_labels: vec![], 1518 }), 1519 }, 1520 }, 1521 1522 Warning::EmptyModule { path: _, name } => Diagnostic { 1523 title: "Empty module".into(), 1524 text: format!("Module '{name}' contains no public definitions."), 1525 hint: Some("You can safely remove this module.".into()), 1526 level: diagnostic::Level::Warning, 1527 location: None, 1528 }, 1529 } 1530 } 1531 1532 pub fn pretty(&self, buffer: &mut Buffer) { 1533 self.to_diagnostic().write(buffer); 1534 buffer 1535 .write_all(b"\n") 1536 .expect("error pretty buffer write space after"); 1537 } 1538 1539 pub fn to_pretty_string(&self) -> String { 1540 let mut nocolor = Buffer::no_color(); 1541 self.pretty(&mut nocolor); 1542 String::from_utf8(nocolor.into_inner()).expect("Warning printing produced invalid utf8") 1543 } 1544} 1545 1546fn explain_impossible_segment(segment: &ImpossibleBitArraySegmentPattern) -> String { 1547 match segment { 1548 ImpossibleBitArraySegmentPattern::UnrepresentableInteger { 1549 size, 1550 signed, 1551 value: _, 1552 location: _, 1553 } => { 1554 let human_readable_size = match size { 1555 1 => "1 bit".into(), 1556 8 => "1 byte".into(), 1557 n if n % 8 == 0 => format!("{} bytes", n / 8), 1558 n => format!("{n} bits"), 1559 }; 1560 let sign = if *signed { "signed" } else { "unsigned" }; 1561 format!("A {human_readable_size} {sign} integer will never match this value") 1562 } 1563 } 1564} 1565 1566fn pluralise(string: String, quantity: i64) -> String { 1567 if quantity == 1 { 1568 string 1569 } else { 1570 format!("{string}s") 1571 } 1572}