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
48 kB 1187 lines
1use crate::{ 2 ast::{SrcSpan, TodoKind}, 3 build::Target, 4 diagnostic::{self, Diagnostic, Location}, 5 error::wrap, 6 type_::{ 7 self, 8 error::{ 9 FeatureKind, LiteralCollectionKind, PanicPosition, TodoOrPanic, 10 UnreachableCaseClauseReason, 11 }, 12 pretty::Printer, 13 }, 14}; 15use camino::Utf8PathBuf; 16use debug_ignore::DebugIgnore; 17use ecow::EcoString; 18use std::{ 19 io::Write, 20 sync::{Arc, atomic::Ordering}, 21}; 22use std::{rc::Rc, sync::atomic::AtomicUsize}; 23use termcolor::Buffer; 24 25pub trait WarningEmitterIO { 26 fn emit_warning(&self, warning: Warning); 27} 28 29#[derive(Debug, Clone, Copy)] 30pub struct NullWarningEmitterIO; 31 32impl WarningEmitterIO for NullWarningEmitterIO { 33 fn emit_warning(&self, _warning: Warning) {} 34} 35 36#[derive(Debug, Clone, Default)] 37pub struct VectorWarningEmitterIO { 38 pub warnings: Arc<std::sync::RwLock<Vec<Warning>>>, 39} 40 41impl VectorWarningEmitterIO { 42 pub fn new() -> Self { 43 Self::default() 44 } 45 46 pub fn take(&self) -> Vec<Warning> { 47 let mut warnings = self.write_lock(); 48 std::mem::take(&mut *warnings) 49 } 50 51 pub fn reset(&self) { 52 let mut warnings = self.write_lock(); 53 warnings.clear(); 54 } 55 56 pub fn pop(&self) -> Option<Warning> { 57 let mut warnings = self.write_lock(); 58 warnings.pop() 59 } 60 61 fn write_lock(&self) -> std::sync::RwLockWriteGuard<'_, Vec<Warning>> { 62 self.warnings.write().expect("Vector lock poisoned") 63 } 64} 65 66impl WarningEmitterIO for VectorWarningEmitterIO { 67 fn emit_warning(&self, warning: Warning) { 68 let mut warnings = self.write_lock(); 69 warnings.push(warning); 70 } 71} 72 73#[derive(Debug, Clone)] 74pub struct WarningEmitter { 75 /// The number of warnings emitted. 76 /// In the context of the project compiler this is the count for the root 77 /// package only, the count is reset back to zero after the dependencies are 78 /// compiled. 79 count: Arc<AtomicUsize>, 80 emitter: DebugIgnore<Rc<dyn WarningEmitterIO>>, 81} 82 83impl WarningEmitter { 84 pub fn new(emitter: Rc<dyn WarningEmitterIO>) -> Self { 85 Self { 86 count: Arc::new(AtomicUsize::new(0)), 87 emitter: DebugIgnore(emitter), 88 } 89 } 90 91 pub fn null() -> Self { 92 Self::new(Rc::new(NullWarningEmitterIO)) 93 } 94 95 pub fn reset_count(&self) { 96 self.count.store(0, Ordering::Relaxed); 97 } 98 99 pub fn count(&self) -> usize { 100 self.count.load(Ordering::Relaxed) 101 } 102 103 pub fn emit(&self, warning: Warning) { 104 _ = self.count.fetch_add(1, Ordering::Relaxed); 105 self.emitter.emit_warning(warning); 106 } 107 108 pub fn vector() -> (Self, Rc<VectorWarningEmitterIO>) { 109 let io = Rc::new(VectorWarningEmitterIO::default()); 110 let emitter = Self::new(io.clone()); 111 (emitter, Rc::clone(&io)) 112 } 113} 114 115#[derive(Debug, Clone)] 116pub struct TypeWarningEmitter { 117 module_path: Utf8PathBuf, 118 module_src: EcoString, 119 emitter: WarningEmitter, 120} 121 122impl TypeWarningEmitter { 123 pub fn new(module_path: Utf8PathBuf, module_src: EcoString, emitter: WarningEmitter) -> Self { 124 Self { 125 module_path, 126 module_src, 127 emitter, 128 } 129 } 130 131 pub fn null() -> Self { 132 Self { 133 module_path: Utf8PathBuf::new(), 134 module_src: EcoString::from(""), 135 emitter: WarningEmitter::new(Rc::new(NullWarningEmitterIO)), 136 } 137 } 138 139 pub fn emit(&self, warning: type_::Warning) { 140 self.emitter.emit(Warning::Type { 141 path: self.module_path.clone(), 142 src: self.module_src.clone(), 143 warning, 144 }); 145 } 146} 147 148#[derive(Debug, Clone, Eq, PartialEq)] 149pub enum Warning { 150 Type { 151 path: Utf8PathBuf, 152 src: EcoString, 153 warning: type_::Warning, 154 }, 155 156 InvalidSource { 157 path: Utf8PathBuf, 158 }, 159 160 DeprecatedSyntax { 161 path: Utf8PathBuf, 162 src: EcoString, 163 warning: DeprecatedSyntaxWarning, 164 }, 165} 166 167#[derive(Debug, Clone, Eq, PartialEq, Copy)] 168pub enum DeprecatedSyntaxWarning { 169 /// If someone uses the deprecated syntax to append to a list: 170 /// `["a"..rest]`, notice how there's no comma! 171 DeprecatedListPrepend { 172 location: SrcSpan, 173 }, 174 175 /// If someone uses the deprecated syntax to pattern match on a list: 176 /// ```gleam 177 /// case list { 178 /// [first..rest] -> todo 179 /// // ^^ notice there's no comma! 180 /// _ -> 181 /// } 182 /// ``` 183 /// 184 DeprecatedListPattern { 185 location: SrcSpan, 186 }, 187 188 /// If someone uses the deprecated syntax to match on all lists instead of 189 /// a common `_`: 190 /// ```gleam 191 /// case list { 192 /// [..] -> todo 193 /// //^^^^ this matches on all lists so a `_` should be used instead! 194 /// _ -> 195 /// } 196 /// ``` 197 /// 198 DeprecatedListCatchAllPattern { 199 location: SrcSpan, 200 }, 201 202 /// If a record pattern has a spread that is not preceded by a comma: 203 /// ```gleam 204 /// case wibble { 205 /// Wibble(arg1: name ..) -> todo 206 /// // ^^ this should be preceded by a comma! 207 /// } 208 /// ``` 209 /// 210 DeprecatedRecordSpreadPattern { 211 location: SrcSpan, 212 }, 213 214 DeprecatedTargetShorthand { 215 target: Target, 216 location: SrcSpan, 217 }, 218} 219 220impl Warning { 221 pub fn to_diagnostic(&self) -> Diagnostic { 222 match self { 223 Warning::InvalidSource { path } => Diagnostic { 224 title: "Invalid module name".into(), 225 text: "\ 226Module names must begin with a lowercase letter and contain 227only lowercase alphanumeric characters or underscores." 228 .into(), 229 level: diagnostic::Level::Warning, 230 location: None, 231 hint: Some(format!( 232 "Rename `{path}` to be valid, or remove this file from the project source." 233 )), 234 }, 235 236 Warning::DeprecatedSyntax { 237 path, 238 src, 239 warning: DeprecatedSyntaxWarning::DeprecatedListPrepend { location }, 240 } => Diagnostic { 241 title: "Deprecated prepend syntax".into(), 242 text: wrap( 243 "This syntax for prepending to a list is deprecated. 244When prepending an item to a list it should be preceded by a comma, \ 245like this: `[item, ..list]`.", 246 ), 247 248 hint: None, 249 level: diagnostic::Level::Warning, 250 location: Some(Location { 251 label: diagnostic::Label { 252 text: Some("This spread should be preceded by a comma".into()), 253 span: *location, 254 }, 255 path: path.clone(), 256 src: src.clone(), 257 extra_labels: vec![], 258 }), 259 }, 260 261 Warning::DeprecatedSyntax { 262 path, 263 src, 264 warning: DeprecatedSyntaxWarning::DeprecatedListPattern { location }, 265 } => Diagnostic { 266 title: "Deprecated list pattern matching syntax".into(), 267 text: wrap( 268 "This syntax for pattern matching on a list is deprecated. 269When matching on the rest of a list it should always be preceded by a comma, \ 270like this: `[item, ..list]`.", 271 ), 272 hint: None, 273 level: diagnostic::Level::Warning, 274 location: Some(Location { 275 label: diagnostic::Label { 276 text: Some("This spread should be preceded by a comma".into()), 277 span: *location, 278 }, 279 path: path.clone(), 280 src: src.clone(), 281 extra_labels: vec![], 282 }), 283 }, 284 285 Warning::DeprecatedSyntax { 286 path, 287 src, 288 warning: DeprecatedSyntaxWarning::DeprecatedRecordSpreadPattern { location }, 289 } => Diagnostic { 290 title: "Deprecated record pattern matching syntax".into(), 291 text: wrap("This syntax for pattern matching on a record is deprecated."), 292 hint: None, 293 level: diagnostic::Level::Warning, 294 location: Some(Location { 295 label: diagnostic::Label { 296 text: Some("This should be preceded by a comma".into()), 297 span: *location, 298 }, 299 path: path.clone(), 300 src: src.clone(), 301 extra_labels: vec![], 302 }), 303 }, 304 305 Warning::DeprecatedSyntax { 306 path, 307 src, 308 warning: DeprecatedSyntaxWarning::DeprecatedListCatchAllPattern { location }, 309 } => Diagnostic { 310 title: "Deprecated list pattern matching syntax".into(), 311 text: wrap( 312 "This syntax for pattern matching on lists is deprecated. 313To match on all possible lists, use the `_` catch-all pattern instead.", 314 ), 315 hint: None, 316 level: diagnostic::Level::Warning, 317 location: Some(Location { 318 label: diagnostic::Label { 319 text: Some("This can be replaced with `_`".into()), 320 span: *location, 321 }, 322 path: path.clone(), 323 src: src.clone(), 324 extra_labels: vec![], 325 }), 326 }, 327 328 Warning::DeprecatedSyntax { 329 path, 330 src, 331 warning: DeprecatedSyntaxWarning::DeprecatedTargetShorthand { location, target }, 332 } => { 333 let full_name = match target { 334 Target::Erlang => "erlang", 335 Target::JavaScript => "javascript", 336 }; 337 338 Diagnostic { 339 title: "Deprecated target shorthand syntax".into(), 340 text: wrap(&format!( 341 "This shorthand target name is deprecated. Use the full name: `{full_name}` instead." 342 )), 343 hint: None, 344 level: diagnostic::Level::Warning, 345 location: Some(Location { 346 label: diagnostic::Label { 347 text: Some(format!("This should be replaced with `{full_name}`")), 348 span: *location, 349 }, 350 path: path.clone(), 351 src: src.clone(), 352 extra_labels: vec![], 353 }), 354 } 355 } 356 357 Self::Type { path, warning, src } => match warning { 358 type_::Warning::Todo { 359 kind, 360 location, 361 type_, 362 } => { 363 let mut text = String::new(); 364 text.push_str( 365 "\ 366This code will crash if it is run. Be sure to finish it before 367running your program.", 368 ); 369 let title = match kind { 370 TodoKind::Keyword => "Todo found", 371 TodoKind::EmptyBlock => { 372 text.push_str( 373 " 374A block must always contain at least one expression.", 375 ); 376 "Incomplete block" 377 } 378 TodoKind::EmptyFunction { .. } => "Unimplemented function", 379 TodoKind::IncompleteUse => { 380 text.push_str( 381 " 382A use expression must always be followed by at least one expression.", 383 ); 384 "Incomplete use expression" 385 } 386 } 387 .into(); 388 if !type_.is_variable() { 389 text.push_str(&format!( 390 "\n\nHint: I think its type is `{}`.\n", 391 Printer::new().pretty_print(type_, 0) 392 )); 393 } 394 395 Diagnostic { 396 title, 397 text, 398 level: diagnostic::Level::Warning, 399 location: Some(Location { 400 path: path.to_path_buf(), 401 src: src.clone(), 402 label: diagnostic::Label { 403 text: Some("This code is incomplete".into()), 404 span: *location, 405 }, 406 extra_labels: Vec::new(), 407 }), 408 hint: None, 409 } 410 } 411 412 type_::Warning::ImplicitlyDiscardedResult { location } => Diagnostic { 413 title: "Unused result value".into(), 414 text: "".into(), 415 hint: Some( 416 "If you are sure you don't need it you can assign it to `_`.".into(), 417 ), 418 level: diagnostic::Level::Warning, 419 location: Some(Location { 420 path: path.to_path_buf(), 421 src: src.clone(), 422 label: diagnostic::Label { 423 text: Some("The Result value created here is unused".into()), 424 span: *location, 425 }, 426 extra_labels: Vec::new(), 427 }), 428 }, 429 430 type_::Warning::UnusedLiteral { location } => Diagnostic { 431 title: "Unused literal".into(), 432 text: "".into(), 433 hint: Some("You can safely remove it.".into()), 434 level: diagnostic::Level::Warning, 435 location: Some(Location { 436 path: path.to_path_buf(), 437 src: src.clone(), 438 label: diagnostic::Label { 439 text: Some("This value is never used".into()), 440 span: *location, 441 }, 442 extra_labels: Vec::new(), 443 }), 444 }, 445 446 type_::Warning::NoFieldsRecordUpdate { location } => Diagnostic { 447 title: "Fieldless record update".into(), 448 text: "".into(), 449 hint: Some( 450 "Add some fields to change or replace it with the record itself.".into(), 451 ), 452 level: diagnostic::Level::Warning, 453 location: Some(Location { 454 path: path.to_path_buf(), 455 src: src.clone(), 456 label: diagnostic::Label { 457 text: Some("This record update doesn't change any fields".into()), 458 span: *location, 459 }, 460 extra_labels: Vec::new(), 461 }), 462 }, 463 464 type_::Warning::AllFieldsRecordUpdate { location } => Diagnostic { 465 title: "Redundant record update".into(), 466 text: "".into(), 467 hint: Some("It is better style to use the record creation syntax.".into()), 468 level: diagnostic::Level::Warning, 469 location: Some(Location { 470 src: src.clone(), 471 path: path.to_path_buf(), 472 label: diagnostic::Label { 473 text: Some("This record update specifies all fields".into()), 474 span: *location, 475 }, 476 extra_labels: Vec::new(), 477 }), 478 }, 479 480 type_::Warning::UnusedType { 481 location, imported, .. 482 } => { 483 let title = if *imported { 484 "Unused imported type".into() 485 } else { 486 "Unused private type".into() 487 }; 488 let label = if *imported { 489 "This imported type is never used".into() 490 } else { 491 "This private type is never used".into() 492 }; 493 Diagnostic { 494 title, 495 text: "".into(), 496 hint: Some("You can safely remove it.".into()), 497 level: diagnostic::Level::Warning, 498 location: Some(Location { 499 src: src.clone(), 500 path: path.to_path_buf(), 501 label: diagnostic::Label { 502 text: Some(label), 503 span: *location, 504 }, 505 extra_labels: Vec::new(), 506 }), 507 } 508 } 509 510 type_::Warning::UnusedConstructor { 511 location, imported, .. 512 } => { 513 let title = if *imported { 514 "Unused imported item".into() 515 } else { 516 "Unused private constructor".into() 517 }; 518 let label = if *imported { 519 "This imported constructor is never used".into() 520 } else { 521 "This private constructor is never used".into() 522 }; 523 Diagnostic { 524 title, 525 text: "".into(), 526 hint: Some("You can safely remove it.".into()), 527 level: diagnostic::Level::Warning, 528 location: Some(Location { 529 src: src.clone(), 530 path: path.to_path_buf(), 531 label: diagnostic::Label { 532 text: Some(label), 533 span: *location, 534 }, 535 extra_labels: Vec::new(), 536 }), 537 } 538 } 539 540 type_::Warning::UnusedImportedModule { location, .. } => Diagnostic { 541 title: "Unused imported module".into(), 542 text: "".into(), 543 hint: Some("You can safely remove it.".into()), 544 level: diagnostic::Level::Warning, 545 location: Some(Location { 546 src: src.clone(), 547 path: path.to_path_buf(), 548 label: diagnostic::Label { 549 text: Some("This imported module is never used".into()), 550 span: *location, 551 }, 552 extra_labels: Vec::new(), 553 }), 554 }, 555 556 type_::Warning::UnusedImportedModuleAlias { 557 location, 558 module_name, 559 .. 560 } => { 561 let text = format!( 562 "\ 563Hint: You can safely remove it. 564 565 import {module_name} as _ 566" 567 ); 568 Diagnostic { 569 title: "Unused imported module alias".into(), 570 text, 571 hint: None, 572 level: diagnostic::Level::Warning, 573 location: Some(Location { 574 src: src.clone(), 575 path: path.to_path_buf(), 576 label: diagnostic::Label { 577 text: Some("This alias is never used".into()), 578 span: *location, 579 }, 580 extra_labels: Vec::new(), 581 }), 582 } 583 } 584 585 type_::Warning::UnusedImportedValue { location, .. } => Diagnostic { 586 title: "Unused imported value".into(), 587 text: "".into(), 588 hint: Some("You can safely remove it.".into()), 589 level: diagnostic::Level::Warning, 590 location: Some(Location { 591 src: src.clone(), 592 path: path.to_path_buf(), 593 label: diagnostic::Label { 594 text: Some("This imported value is never used".into()), 595 span: *location, 596 }, 597 extra_labels: Vec::new(), 598 }), 599 }, 600 601 type_::Warning::UnusedPrivateModuleConstant { location, .. } => Diagnostic { 602 title: "Unused private constant".into(), 603 text: "".into(), 604 hint: Some("You can safely remove it.".into()), 605 level: diagnostic::Level::Warning, 606 location: Some(Location { 607 src: src.clone(), 608 path: path.to_path_buf(), 609 label: diagnostic::Label { 610 text: Some("This private constant is never used".into()), 611 span: *location, 612 }, 613 extra_labels: Vec::new(), 614 }), 615 }, 616 617 type_::Warning::UnusedPrivateFunction { location, .. } => Diagnostic { 618 title: "Unused private function".into(), 619 text: "".into(), 620 hint: Some("You can safely remove it.".into()), 621 level: diagnostic::Level::Warning, 622 location: Some(Location { 623 src: src.clone(), 624 path: path.to_path_buf(), 625 label: diagnostic::Label { 626 text: Some("This private function is never used".into()), 627 span: *location, 628 }, 629 extra_labels: Vec::new(), 630 }), 631 }, 632 633 type_::Warning::UnusedVariable { location, origin } => Diagnostic { 634 title: "Unused variable".into(), 635 text: "".into(), 636 hint: origin.how_to_ignore(), 637 level: diagnostic::Level::Warning, 638 location: Some(Location { 639 src: src.clone(), 640 path: path.to_path_buf(), 641 label: diagnostic::Label { 642 text: Some("This variable is never used".into()), 643 span: *location, 644 }, 645 extra_labels: Vec::new(), 646 }), 647 }, 648 type_::Warning::UnnecessaryDoubleIntNegation { location } => Diagnostic { 649 title: "Unnecessary double negation (--) on integer".into(), 650 text: "".into(), 651 hint: Some("You can safely remove this.".into()), 652 level: diagnostic::Level::Warning, 653 location: Some(Location { 654 src: src.clone(), 655 path: path.to_path_buf(), 656 label: diagnostic::Label { 657 text: None, 658 span: *location, 659 }, 660 extra_labels: Vec::new(), 661 }), 662 }, 663 type_::Warning::UnnecessaryDoubleBoolNegation { location } => Diagnostic { 664 title: "Unnecessary double negation (!!) on bool".into(), 665 text: "".into(), 666 hint: Some("You can safely remove this.".into()), 667 level: diagnostic::Level::Warning, 668 location: Some(Location { 669 src: src.clone(), 670 path: path.to_path_buf(), 671 label: diagnostic::Label { 672 text: None, 673 span: *location, 674 }, 675 extra_labels: Vec::new(), 676 }), 677 }, 678 type_::Warning::InefficientEmptyListCheck { location, kind } => { 679 use type_::error::EmptyListCheckKind; 680 let text = "The `list.length` function has to iterate across the whole 681list to calculate the length, which is wasteful if you only 682need to know if the list is empty or not. 683" 684 .into(); 685 let hint = Some(match kind { 686 EmptyListCheckKind::Empty => "You can use `the_list == []` instead.".into(), 687 EmptyListCheckKind::NonEmpty => { 688 "You can use `the_list != []` instead.".into() 689 } 690 }); 691 692 Diagnostic { 693 title: "Inefficient use of `list.length`".into(), 694 text, 695 hint, 696 level: diagnostic::Level::Warning, 697 location: Some(Location { 698 src: src.clone(), 699 path: path.to_path_buf(), 700 label: diagnostic::Label { 701 text: None, 702 span: *location, 703 }, 704 extra_labels: Vec::new(), 705 }), 706 } 707 } 708 709 type_::Warning::TransitiveDependencyImported { 710 location, 711 module, 712 package, 713 } => { 714 let text = wrap(&format!( 715 "The module `{module}` is being imported, but \ 716`{package}`, the package it belongs to, is not a direct dependency of your \ 717package. 718In a future version of Gleam this may become a compile error. 719 720Run this command to add it to your dependencies: 721 722 gleam add {package} 723" 724 )); 725 Diagnostic { 726 title: "Transitive dependency imported".into(), 727 text, 728 hint: None, 729 level: diagnostic::Level::Warning, 730 location: Some(Location { 731 src: src.clone(), 732 path: path.to_path_buf(), 733 label: diagnostic::Label { 734 text: None, 735 span: *location, 736 }, 737 extra_labels: Vec::new(), 738 }), 739 } 740 } 741 742 type_::Warning::DeprecatedItem { 743 location, 744 message, 745 layer, 746 } => { 747 let text = wrap(&format!("It was deprecated with this message: {message}")); 748 let (title, diagnostic_label_text) = if layer.is_value() { 749 ( 750 "Deprecated value used".into(), 751 Some("This value has been deprecated".into()), 752 ) 753 } else { 754 ( 755 "Deprecated type used".into(), 756 Some("This type has been deprecated".into()), 757 ) 758 }; 759 760 Diagnostic { 761 title, 762 text, 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: diagnostic_label_text, 770 span: *location, 771 }, 772 extra_labels: Vec::new(), 773 }), 774 } 775 } 776 777 type_::Warning::UnreachableCaseClause { location, reason } => { 778 let text: String = match reason { 779 UnreachableCaseClauseReason::DuplicatePattern => wrap( 780 "This case clause cannot be reached as a previous clause matches \ 781the same values.\n", 782 ), 783 UnreachableCaseClauseReason::ImpossibleVariant => wrap( 784 "This case clause cannot be reached as it matches \ 785on a variant of a type which is never present.\n", 786 ), 787 }; 788 Diagnostic { 789 title: "Unreachable case clause".into(), 790 text, 791 hint: Some("It can be safely removed.".into()), 792 level: diagnostic::Level::Warning, 793 location: Some(Location { 794 src: src.clone(), 795 path: path.to_path_buf(), 796 label: diagnostic::Label { 797 text: None, 798 span: *location, 799 }, 800 extra_labels: Vec::new(), 801 }), 802 } 803 } 804 805 type_::Warning::CaseMatchOnLiteralCollection { kind, location } => { 806 let kind = match kind { 807 LiteralCollectionKind::List => "list", 808 LiteralCollectionKind::Tuple => "tuple", 809 LiteralCollectionKind::Record => "record", 810 }; 811 812 let title = format!("Redundant {kind}"); 813 let text = wrap(&format!( 814 "Instead of building a {kind} and matching on it, \ 815you can match on its contents directly. 816A case expression can take multiple subjects separated by commas like this: 817 818 case one_subject, another_subject {{ 819 _, _ -> todo 820 }} 821 822See: https://tour.gleam.run/flow-control/multiple-subjects/" 823 )); 824 825 Diagnostic { 826 title, 827 text, 828 hint: None, 829 level: diagnostic::Level::Warning, 830 location: Some(Location { 831 src: src.clone(), 832 path: path.to_path_buf(), 833 label: diagnostic::Label { 834 text: Some(format!("You can remove this {kind} wrapper")), 835 span: *location, 836 }, 837 extra_labels: Vec::new(), 838 }), 839 } 840 } 841 842 type_::Warning::CaseMatchOnLiteralValue { location } => Diagnostic { 843 title: "Match on a literal value".into(), 844 text: wrap( 845 "Matching on a literal value is redundant since you \ 846can already tell which branch is going to match with this value.", 847 ), 848 hint: None, 849 level: diagnostic::Level::Warning, 850 location: Some(Location { 851 src: src.clone(), 852 path: path.to_path_buf(), 853 label: diagnostic::Label { 854 text: Some("There's no need to pattern match on this value".into()), 855 span: *location, 856 }, 857 extra_labels: Vec::new(), 858 }), 859 }, 860 861 type_::Warning::OpaqueExternalType { location } => Diagnostic { 862 title: "Opaque external type".into(), 863 text: "This type has no constructors so making it opaque is redundant.".into(), 864 hint: Some("Remove the `opaque` qualifier from the type definition.".into()), 865 level: diagnostic::Level::Warning, 866 location: Some(Location { 867 src: src.clone(), 868 path: path.to_path_buf(), 869 label: diagnostic::Label { 870 text: None, 871 span: *location, 872 }, 873 extra_labels: Vec::new(), 874 }), 875 }, 876 877 type_::Warning::UnusedValue { location } => Diagnostic { 878 title: "Unused value".into(), 879 text: "".into(), 880 hint: None, 881 level: diagnostic::Level::Warning, 882 location: Some(Location { 883 path: path.to_path_buf(), 884 src: src.clone(), 885 label: diagnostic::Label { 886 text: Some("This value is never used".into()), 887 span: *location, 888 }, 889 extra_labels: Vec::new(), 890 }), 891 }, 892 893 type_::Warning::InternalTypeLeak { location, leaked } => { 894 let mut printer = Printer::new(); 895 896 // TODO: be more precise. 897 // - is being returned by this public function 898 // - is taken as an argument by this public function 899 // - is taken as an argument by this public enum constructor 900 // etc 901 let text = format!( 902 "The following type is internal, but is being used by this public export. 903 904{} 905 906Internal types should not be used in a public facing function since they are 907hidden from the package's documentation.", 908 printer.pretty_print(leaked, 4), 909 ); 910 Diagnostic { 911 title: "Internal type used in public interface".into(), 912 text, 913 hint: None, 914 level: diagnostic::Level::Warning, 915 location: Some(Location { 916 label: diagnostic::Label { 917 text: None, 918 span: *location, 919 }, 920 path: path.clone(), 921 src: src.clone(), 922 extra_labels: vec![], 923 }), 924 } 925 } 926 type_::Warning::RedundantAssertAssignment { location } => Diagnostic { 927 title: "Redundant assertion".into(), 928 text: "This assertion is redundant since the pattern covers all possibilities." 929 .into(), 930 hint: None, 931 level: diagnostic::Level::Warning, 932 location: Some(Location { 933 label: diagnostic::Label { 934 text: Some("You can remove this".into()), 935 span: *location, 936 }, 937 path: path.clone(), 938 src: src.clone(), 939 extra_labels: vec![], 940 }), 941 }, 942 943 type_::Warning::AssertAssignmentOnInferredVariant { location } => Diagnostic { 944 title: "Assertion that will always fail".into(), 945 text: wrap( 946 "We can tell from the code above that the value will never match \ 947this pattern and that this code will always crash. 948 949Either change the pattern or use `panic` to unconditionally fail.", 950 ), 951 hint: None, 952 level: diagnostic::Level::Warning, 953 location: Some(Location { 954 label: diagnostic::Label { 955 text: None, 956 span: *location, 957 }, 958 path: path.clone(), 959 src: src.clone(), 960 extra_labels: vec![], 961 }), 962 }, 963 964 type_::Warning::TodoOrPanicUsedAsFunction { 965 kind, 966 location, 967 args_location, 968 args, 969 } => { 970 let title = match kind { 971 TodoOrPanic::Todo => "Todo used as a function".into(), 972 TodoOrPanic::Panic => "Panic used as a function".into(), 973 }; 974 let label_location = match args_location { 975 None => location, 976 Some(location) => location, 977 }; 978 let name = match kind { 979 TodoOrPanic::Todo => "todo", 980 TodoOrPanic::Panic => "panic", 981 }; 982 let mut text = format!("`{name}` is not a function"); 983 match args { 984 0 => text.push_str(&format!( 985 ", you can just write `{name}` instead of `{name}()`." 986 )), 987 1 => text.push_str( 988 " and will crash before it can do anything with this argument.", 989 ), 990 _ => text.push_str( 991 " and will crash before it can do anything with these arguments.", 992 ), 993 }; 994 995 match args { 996 0 => {} 997 _ => text.push_str(&format!( 998 "\n\nHint: if you want to display an error message you should write 999`{name} as \"my error message\"` 1000See: https://tour.gleam.run/advanced-features/{name}/" 1001 )), 1002 } 1003 1004 Diagnostic { 1005 title, 1006 text: wrap(&text), 1007 hint: None, 1008 level: diagnostic::Level::Warning, 1009 location: Some(Location { 1010 label: diagnostic::Label { 1011 text: None, 1012 span: *label_location, 1013 }, 1014 path: path.clone(), 1015 src: src.clone(), 1016 extra_labels: vec![], 1017 }), 1018 } 1019 } 1020 1021 type_::Warning::UnreachableCodeAfterPanic { 1022 location, 1023 panic_position: unreachable_code_kind, 1024 } => { 1025 let text = match unreachable_code_kind { 1026 PanicPosition::PreviousExpression => { 1027 "This code is unreachable because it comes after a `panic`." 1028 } 1029 PanicPosition::PreviousFunctionArgument => { 1030 "This argument is unreachable because the previous one always panics. \ 1031Your code will crash before reaching this point." 1032 } 1033 PanicPosition::LastFunctionArgument => { 1034 "This function call is unreachable because its last argument always panics. \ 1035Your code will crash before reaching this point." 1036 } 1037 PanicPosition::EchoExpression => { 1038 "This `echo` won't print anything because the expression it \ 1039should be printing always panics." 1040 } 1041 }; 1042 1043 Diagnostic { 1044 title: "Unreachable code".into(), 1045 text: wrap(text), 1046 hint: None, 1047 level: diagnostic::Level::Warning, 1048 location: Some(Location { 1049 label: diagnostic::Label { 1050 text: None, 1051 span: *location, 1052 }, 1053 path: path.clone(), 1054 src: src.clone(), 1055 extra_labels: vec![], 1056 }), 1057 } 1058 } 1059 1060 type_::Warning::RedundantPipeFunctionCapture { location } => Diagnostic { 1061 title: "Redundant function capture".into(), 1062 text: wrap( 1063 "This function capture is redundant since the value is already piped as \ 1064the first argument of this call. 1065 1066See: https://tour.gleam.run/functions/pipelines/", 1067 ), 1068 hint: None, 1069 level: diagnostic::Level::Warning, 1070 location: Some(Location { 1071 label: diagnostic::Label { 1072 text: Some("You can safely remove this".into()), 1073 span: *location, 1074 }, 1075 path: path.clone(), 1076 src: src.clone(), 1077 extra_labels: vec![], 1078 }), 1079 }, 1080 type_::Warning::FeatureRequiresHigherGleamVersion { 1081 location, 1082 minimum_required_version, 1083 wrongfully_allowed_version, 1084 feature_kind, 1085 } => { 1086 let feature = match feature_kind { 1087 FeatureKind::LabelShorthandSyntax => "The label shorthand syntax was", 1088 FeatureKind::ConstantStringConcatenation => { 1089 "Constant strings concatenation was" 1090 } 1091 FeatureKind::ArithmeticInGuards => "Arithmetic operations in guards were", 1092 FeatureKind::UnannotatedUtf8StringSegment => { 1093 "The ability to omit the `utf8` annotation for string segments was" 1094 } 1095 FeatureKind::UnannotatedFloatSegment => { 1096 "The ability to omit the `float` annotation for segments was" 1097 } 1098 FeatureKind::NestedTupleAccess => { 1099 "The ability to access nested tuple fields was" 1100 } 1101 FeatureKind::InternalAnnotation => "The `@internal` annotation was", 1102 FeatureKind::AtInJavascriptModules => { 1103 "The ability to have `@` in a Javascript module's name was" 1104 } 1105 FeatureKind::RecordUpdateVariantInference => { 1106 "Record updates for custom types when the variant is known was" 1107 } 1108 FeatureKind::RecordAccessVariantInference => { 1109 "Field access on custom types when the variant is known was" 1110 } 1111 FeatureKind::LetAssertWithMessage => { 1112 "Specifying a custom panic message when using let assert was" 1113 } 1114 FeatureKind::VariantWithDeprecatedAnnotation => { 1115 "Deprecating individual custom type variants was" 1116 } 1117 FeatureKind::JavaScriptUnalignedBitArray => { 1118 "Use of unaligned bit arrays on the JavaScript target was" 1119 } 1120 }; 1121 1122 Diagnostic { 1123 title: "Incompatible gleam version range".into(), 1124 text: wrap(&format!( 1125 "{feature} introduced in version v{minimum_required_version}. But the Gleam version range \ 1126 specified in your `gleam.toml` would allow this code to run on an earlier \ 1127 version like v{wrongfully_allowed_version}, resulting in compilation errors!", 1128 )), 1129 hint: Some(format!( 1130 "Remove the version constraint from your `gleam.toml` or update it to be: 1131 1132 gleam = \">= {minimum_required_version}\"" 1133 )), 1134 level: diagnostic::Level::Warning, 1135 location: Some(Location { 1136 label: diagnostic::Label { 1137 text: Some(format!( 1138 "This requires a Gleam version >= {minimum_required_version}" 1139 )), 1140 span: *location, 1141 }, 1142 path: path.clone(), 1143 src: src.clone(), 1144 extra_labels: vec![], 1145 }), 1146 } 1147 } 1148 1149 type_::Warning::JavaScriptIntUnsafe { location } => Diagnostic { 1150 title: "Int is outside JavaScript's safe integer range".into(), 1151 text: wrap( 1152 "This integer value is too large to be represented accurately by \ 1153JavaScript's number type. To avoid this warning integer values must be in the range \ 1154-(2^53 - 1) - (2^53 - 1). 1155 1156See JavaScript's Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER properties for more \ 1157information.", 1158 ), 1159 hint: None, 1160 level: diagnostic::Level::Warning, 1161 location: Some(Location { 1162 path: path.to_path_buf(), 1163 src: src.clone(), 1164 label: diagnostic::Label { 1165 text: Some("This is not a safe integer value on JavaScript".into()), 1166 span: *location, 1167 }, 1168 extra_labels: Vec::new(), 1169 }), 1170 }, 1171 }, 1172 } 1173 } 1174 1175 pub fn pretty(&self, buffer: &mut Buffer) { 1176 self.to_diagnostic().write(buffer); 1177 buffer 1178 .write_all(b"\n") 1179 .expect("error pretty buffer write space after"); 1180 } 1181 1182 pub fn to_pretty_string(&self) -> String { 1183 let mut nocolor = Buffer::no_color(); 1184 self.pretty(&mut nocolor); 1185 String::from_utf8(nocolor.into_inner()).expect("Warning printing produced invalid utf8") 1186 } 1187}