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