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 / build.rs
26 kB 839 lines
1#![allow(warnings)] 2 3mod elixir_libraries; 4mod module_loader; 5mod native_file_copier; 6pub mod package_compiler; 7mod package_loader; 8mod project_compiler; 9mod telemetry; 10 11#[cfg(test)] 12mod tests; 13 14pub use self::package_compiler::PackageCompiler; 15pub use self::package_loader::StaleTracker; 16pub use self::project_compiler::{Built, Options, ProjectCompiler}; 17pub use self::telemetry::{NullTelemetry, Telemetry}; 18 19use crate::ast::{ 20 self, CallArg, CustomType, DefinitionLocation, TypeAst, TypedArg, TypedClauseGuard, 21 TypedConstant, TypedCustomType, TypedDefinitions, TypedExpr, TypedFunction, TypedImport, 22 TypedModuleConstant, TypedPattern, TypedRecordConstructor, TypedStatement, TypedTypeAlias, 23}; 24use crate::type_::{Type, TypedCallArg}; 25use crate::{ 26 ast::{Definition, SrcSpan, TypedModule}, 27 config::{self, PackageConfig}, 28 erlang, 29 error::{Error, FileIoAction, FileKind}, 30 io::OutputFile, 31 parse::extra::{Comment, ModuleExtra}, 32 type_, 33}; 34use camino::Utf8PathBuf; 35use clap::ValueEnum; 36use ecow::EcoString; 37use itertools::Itertools; 38use serde::{Deserialize, Serialize}; 39use std::fmt::{Debug, Display}; 40use std::sync::Arc; 41use std::time::SystemTime; 42use std::{collections::HashMap, ffi::OsString, fs::DirEntry, iter::Peekable, process}; 43use strum::{Display, EnumIter, EnumString, EnumVariantNames, VariantNames}; 44use vec1::Vec1; 45 46#[derive( 47 Debug, 48 Serialize, 49 Deserialize, 50 ValueEnum, 51 EnumString, 52 EnumVariantNames, 53 EnumIter, 54 Clone, 55 Copy, 56 PartialEq, 57 Eq, 58)] 59#[strum(serialize_all = "lowercase")] 60#[clap(rename_all = "lower")] 61#[serde(rename_all = "lowercase")] 62pub enum Target { 63 #[strum(serialize = "erl")] 64 #[serde(alias = "erl")] 65 #[clap(alias = "erl")] 66 Erlang, 67 #[strum(serialize = "js")] 68 #[serde(alias = "js")] 69 #[clap(alias = "js")] 70 JavaScript, 71} 72 73impl Target { 74 pub fn as_presentable_str(&self) -> &str { 75 match self { 76 Target::Erlang => "Erlang", 77 Target::JavaScript => "JavaScript", 78 } 79 } 80 81 pub fn variant_strings() -> Vec<EcoString> { 82 Self::VARIANTS.iter().map(|s| (*s).into()).collect() 83 } 84 85 /// Returns `true` if the target is [`JavaScript`]. 86 /// 87 /// [`JavaScript`]: Target::JavaScript 88 #[must_use] 89 pub fn is_javascript(&self) -> bool { 90 matches!(self, Self::JavaScript) 91 } 92 93 /// Returns `true` if the target is [`Erlang`]. 94 /// 95 /// [`Erlang`]: Target::Erlang 96 #[must_use] 97 pub fn is_erlang(&self) -> bool { 98 matches!(self, Self::Erlang) 99 } 100} 101 102#[derive(Debug, PartialEq, Eq, Clone, Copy)] 103/// This is used to skip compiling the root package when running the main 104/// function coming from a dependency. This way a dependency can be run even 105/// there's compilation errors in the root package. 106/// 107pub enum Compile { 108 /// The default compiler behaviour, compile all packages. 109 /// 110 All, 111 /// Only compile the dependency packages, skipping the root package. 112 /// 113 DepsOnly, 114} 115 116#[derive(Debug, PartialEq, Eq, Clone, Copy)] 117pub enum Codegen { 118 All, 119 DepsOnly, 120 None, 121} 122 123impl Codegen { 124 fn should_codegen(&self, is_root_package: bool) -> bool { 125 match self { 126 Codegen::All => true, 127 Codegen::DepsOnly => !is_root_package, 128 Codegen::None => false, 129 } 130 } 131} 132 133#[derive( 134 Debug, 135 Serialize, 136 Deserialize, 137 EnumString, 138 EnumVariantNames, 139 ValueEnum, 140 Clone, 141 Copy, 142 PartialEq, 143 Eq, 144)] 145#[clap(rename_all = "lower")] 146#[serde(rename_all = "lowercase")] 147#[strum(serialize_all = "lowercase")] 148pub enum Runtime { 149 #[strum(serialize = "node")] 150 #[serde(alias = "node")] 151 #[clap(alias = "node")] 152 NodeJs, 153 Deno, 154 Bun, 155} 156 157impl Runtime { 158 pub fn as_presentable_str(&self) -> &str { 159 match self { 160 Runtime::NodeJs => "NodeJS", 161 Runtime::Deno => "Deno", 162 Runtime::Bun => "Bun", 163 } 164 } 165} 166 167impl Default for Runtime { 168 fn default() -> Self { 169 Self::NodeJs 170 } 171} 172 173#[derive(Debug)] 174pub enum TargetCodegenConfiguration { 175 JavaScript { 176 emit_typescript_definitions: bool, 177 emit_source_maps: bool, 178 prelude_location: Utf8PathBuf, 179 }, 180 Erlang { 181 app_file: Option<ErlangAppCodegenConfiguration>, 182 }, 183} 184 185impl TargetCodegenConfiguration { 186 pub fn target(&self) -> Target { 187 match self { 188 Self::JavaScript { .. } => Target::JavaScript, 189 Self::Erlang { .. } => Target::Erlang, 190 } 191 } 192} 193 194#[derive(Debug)] 195pub struct ErlangAppCodegenConfiguration { 196 pub include_dev_deps: bool, 197 /// Some packages have a different OTP application name than their package 198 /// name, as rebar3 (and Mix?) support this. The .app file must use the OTP 199 /// name, not the package name. 200 pub package_name_overrides: HashMap<EcoString, EcoString>, 201} 202 203#[derive( 204 Debug, 205 Serialize, 206 Deserialize, 207 Display, 208 EnumString, 209 EnumVariantNames, 210 EnumIter, 211 Clone, 212 Copy, 213 PartialEq, 214)] 215#[strum(serialize_all = "lowercase")] 216pub enum Mode { 217 Dev, 218 Prod, 219 Lsp, 220} 221 222impl Mode { 223 /// Returns `true` if the mode includes development code. 224 /// 225 pub fn includes_dev_code(&self) -> bool { 226 match self { 227 Self::Dev | Self::Lsp => true, 228 Self::Prod => false, 229 } 230 } 231 232 pub fn includes_dev_dependencies(&self) -> bool { 233 match self { 234 Mode::Dev | Mode::Lsp => true, 235 Mode::Prod => false, 236 } 237 } 238} 239 240#[test] 241fn mode_includes_dev_code() { 242 assert!(Mode::Dev.includes_dev_code()); 243 assert!(Mode::Lsp.includes_dev_code()); 244 assert!(!Mode::Prod.includes_dev_code()); 245} 246 247#[derive(Debug)] 248pub struct Package { 249 pub config: PackageConfig, 250 pub modules: Vec<Module>, 251 pub cached_module_names: Vec<EcoString>, 252} 253 254impl Package { 255 pub fn attach_doc_and_module_comments(&mut self) { 256 for mut module in &mut self.modules { 257 module.attach_doc_and_module_comments(); 258 } 259 } 260 261 pub fn into_modules_hashmap(self) -> HashMap<String, Module> { 262 self.modules 263 .into_iter() 264 .map(|m| (m.name.to_string(), m)) 265 .collect() 266 } 267} 268 269#[derive(Debug)] 270pub struct Module { 271 pub name: EcoString, 272 pub code: EcoString, 273 pub mtime: SystemTime, 274 pub input_path: Utf8PathBuf, 275 pub origin: Origin, 276 pub ast: TypedModule, 277 pub extra: ModuleExtra, 278 pub dependencies: Vec<(EcoString, SrcSpan)>, 279} 280 281#[derive(Debug)] 282/// A data structure used to store all definitions in a single homogeneous 283/// vector and sort them by their position in order to attach to each the right 284/// documentation. 285/// 286enum DocumentableDefinition<'a> { 287 Constant(&'a mut TypedModuleConstant), 288 TypeAlias(&'a mut TypedTypeAlias), 289 CustomType(&'a mut TypedCustomType), 290 Function(&'a mut TypedFunction), 291 Import(&'a mut TypedImport), 292} 293 294impl<'a> DocumentableDefinition<'a> { 295 pub fn location(&self) -> SrcSpan { 296 match self { 297 Self::Constant(module_constant) => module_constant.location, 298 Self::TypeAlias(type_alias) => type_alias.location, 299 Self::CustomType(custom_type) => custom_type.location, 300 Self::Function(function) => function.location, 301 Self::Import(import) => import.location, 302 } 303 } 304 305 pub fn put_doc(&mut self, new_documentation: (u32, EcoString)) { 306 match self { 307 Self::Import(_import) => (), 308 Self::Function(function) => { 309 let _ = function.documentation.replace(new_documentation); 310 } 311 Self::TypeAlias(type_alias) => { 312 let _ = type_alias.documentation.replace(new_documentation); 313 } 314 Self::CustomType(custom_type) => { 315 let _ = custom_type.documentation.replace(new_documentation); 316 } 317 Self::Constant(constant) => { 318 let _ = constant.documentation.replace(new_documentation); 319 } 320 } 321 } 322} 323 324impl Module { 325 pub fn erlang_name(&self) -> EcoString { 326 module_erlang_name(&self.name) 327 } 328 329 pub fn compiled_erlang_path(&self) -> Utf8PathBuf { 330 let mut path = Utf8PathBuf::from(&module_erlang_name(&self.name)); 331 assert!(path.set_extension("erl"), "Couldn't set file extension"); 332 path 333 } 334 335 pub fn find_node(&self, byte_index: u32) -> Option<Located<'_>> { 336 self.ast.find_node(byte_index) 337 } 338 339 pub fn attach_doc_and_module_comments(&mut self) { 340 // Module Comments 341 self.ast.documentation = self 342 .extra 343 .module_comments 344 .iter() 345 .map(|span| Comment::from((span, self.code.as_str())).content.into()) 346 .collect(); 347 348 self.ast.type_info.documentation = self.ast.documentation.clone(); 349 350 // Order definitions to avoid misassociating doc comments after the 351 // order has changed during compilation. 352 let TypedDefinitions { 353 imports, 354 constants, 355 custom_types, 356 type_aliases, 357 functions, 358 } = &mut self.ast.definitions; 359 360 let mut definitions = ((imports.iter_mut()).map(DocumentableDefinition::Import)) 361 .chain((constants.iter_mut()).map(DocumentableDefinition::Constant)) 362 .chain((custom_types.iter_mut()).map(DocumentableDefinition::CustomType)) 363 .chain((type_aliases.iter_mut()).map(DocumentableDefinition::TypeAlias)) 364 .chain((functions.iter_mut()).map(DocumentableDefinition::Function)) 365 .sorted_by_key(|definition| definition.location()) 366 .collect_vec(); 367 368 // Doc Comments 369 let mut doc_comments = self.extra.doc_comments.iter().peekable(); 370 for definition in &mut definitions { 371 let (docs_start, docs): (u32, Vec<&str>) = doc_comments_before( 372 &mut doc_comments, 373 &self.extra, 374 definition.location().start, 375 &self.code, 376 ); 377 if !docs.is_empty() { 378 let doc = docs.join("\n").into(); 379 definition.put_doc((docs_start, doc)); 380 } 381 382 if let DocumentableDefinition::CustomType(CustomType { constructors, .. }) = definition 383 { 384 for constructor in constructors { 385 let (docs_start, docs): (u32, Vec<&str>) = doc_comments_before( 386 &mut doc_comments, 387 &self.extra, 388 constructor.location.start, 389 &self.code, 390 ); 391 if !docs.is_empty() { 392 let doc = docs.join("\n").into(); 393 constructor.put_doc((docs_start, doc)); 394 } 395 396 for argument in constructor.arguments.iter_mut() { 397 let (docs_start, docs): (u32, Vec<&str>) = doc_comments_before( 398 &mut doc_comments, 399 &self.extra, 400 argument.location.start, 401 &self.code, 402 ); 403 if !docs.is_empty() { 404 let doc = docs.join("\n").into(); 405 argument.put_doc((docs_start, doc)); 406 } 407 } 408 } 409 } 410 } 411 } 412} 413 414pub fn module_erlang_name(gleam_name: &EcoString) -> EcoString { 415 gleam_name.replace("/", "@") 416} 417 418#[derive(Debug, Clone, PartialEq)] 419pub struct UnqualifiedImport<'a> { 420 pub name: &'a EcoString, 421 pub module: &'a EcoString, 422 pub is_type: bool, 423 pub location: &'a SrcSpan, 424} 425 426/// The position of a located expression. Used to determine extra context, 427/// such as whether to provide label completions if the expression is in 428/// argument position. 429#[derive(Debug, Clone, PartialEq)] 430pub enum ExpressionPosition<'a> { 431 Expression, 432 ArgumentOrLabel { 433 called_function: &'a TypedExpr, 434 function_arguments: &'a [TypedCallArg], 435 }, 436 /// This is for an expression that is used in a record update spread: 437 /// ```gleam 438 /// Wibble(..wibble, a: 1) 439 /// // ^^^^^^^^ This! 440 /// ``` 441 UpdatedRecord { 442 /// These are all the record fields that are not being updated in the 443 /// update. 444 unchanged_record_fields: Vec<(EcoString, Arc<Type>)>, 445 }, 446} 447 448#[derive(Debug, Clone, PartialEq)] 449pub enum Located<'a> { 450 Pattern(&'a TypedPattern), 451 PatternSpread { 452 spread_location: SrcSpan, 453 pattern: &'a TypedPattern, 454 }, 455 // A prefix alias or a suffix variable defined in a string prefix pattern: 456 // "prefix" as alias <> suffix 457 StringPrefixPatternVariable { 458 location: SrcSpan, 459 name: &'a EcoString, 460 }, 461 Statement(&'a TypedStatement), 462 Expression { 463 expression: &'a TypedExpr, 464 position: ExpressionPosition<'a>, 465 }, 466 VariantConstructorDefinition(&'a TypedRecordConstructor), 467 FunctionBody(&'a TypedFunction), 468 Arg(&'a TypedArg), 469 Annotation { 470 ast: &'a TypeAst, 471 type_: std::sync::Arc<Type>, 472 }, 473 UnqualifiedImport(UnqualifiedImport<'a>), 474 Label(SrcSpan, std::sync::Arc<Type>), 475 ModuleName { 476 location: SrcSpan, 477 module_name: EcoString, 478 module_alias: EcoString, 479 layer: ast::Layer, 480 }, 481 Constant(&'a TypedConstant), 482 ClauseGuard(&'a TypedClauseGuard), 483 484 // A module's top level definitions 485 ModuleFunction(&'a TypedFunction), 486 ModuleConstant(&'a TypedModuleConstant), 487 ModuleImport(&'a TypedImport), 488 ModuleCustomType(&'a TypedCustomType), 489 ModuleTypeAlias(&'a TypedTypeAlias), 490} 491 492impl<'a> Located<'a> { 493 // Looks up the type constructor for the given type and then create the location. 494 fn type_location( 495 &self, 496 importable_modules: &'a im::HashMap<EcoString, type_::ModuleInterface>, 497 type_: std::sync::Arc<Type>, 498 ) -> Option<DefinitionLocation> { 499 type_constructor_from_modules(importable_modules, type_).map(|t| DefinitionLocation { 500 module: Some(t.module.clone()), 501 span: t.origin, 502 }) 503 } 504 505 pub fn definition_location( 506 &self, 507 importable_modules: &'a im::HashMap<EcoString, type_::ModuleInterface>, 508 ) -> Option<DefinitionLocation> { 509 match self { 510 Self::PatternSpread { .. } => None, 511 Self::Pattern(pattern) => pattern.definition_location(), 512 Self::StringPrefixPatternVariable { location, .. } => Some(DefinitionLocation { 513 module: None, 514 span: *location, 515 }), 516 Self::Statement(statement) => statement.definition_location(), 517 Self::FunctionBody(statement) => None, 518 Self::Expression { expression, .. } => expression.definition_location(), 519 520 Self::ModuleImport(import) => Some(DefinitionLocation { 521 module: Some(import.module.clone()), 522 span: SrcSpan { start: 0, end: 0 }, 523 }), 524 Self::ModuleConstant(constant) => Some(DefinitionLocation { 525 module: None, 526 span: constant.location, 527 }), 528 Self::ModuleCustomType(custom_type) => Some(DefinitionLocation { 529 module: None, 530 span: custom_type.location, 531 }), 532 Self::ModuleFunction(function) => Some(DefinitionLocation { 533 module: None, 534 span: function.location, 535 }), 536 Self::ModuleTypeAlias(type_alias) => Some(DefinitionLocation { 537 module: None, 538 span: type_alias.location, 539 }), 540 541 Self::VariantConstructorDefinition(record) => Some(DefinitionLocation { 542 module: None, 543 span: record.location, 544 }), 545 Self::UnqualifiedImport(UnqualifiedImport { 546 module, 547 name, 548 is_type, 549 .. 550 }) => importable_modules.get(*module).and_then(|m| { 551 if *is_type { 552 m.types.get(*name).map(|t| DefinitionLocation { 553 module: Some((*module).clone()), 554 span: t.origin, 555 }) 556 } else { 557 m.values.get(*name).map(|v| DefinitionLocation { 558 module: Some((*module).clone()), 559 span: v.definition_location().span, 560 }) 561 } 562 }), 563 Self::Arg(_) => None, 564 Self::Annotation { type_, .. } => self.type_location(importable_modules, type_.clone()), 565 Self::Label(_, _) => None, 566 Self::ModuleName { module_name, .. } => Some(DefinitionLocation { 567 module: Some(module_name.clone()), 568 span: SrcSpan::new(0, 0), 569 }), 570 Self::Constant(constant) => constant.definition_location(), 571 Self::ClauseGuard(guard) => guard.definition_location(), 572 } 573 } 574 575 pub(crate) fn type_(&self) -> Option<Arc<Type>> { 576 match self { 577 Located::Pattern(pattern) => Some(pattern.type_()), 578 Located::StringPrefixPatternVariable { .. } => Some(type_::string()), 579 Located::Statement(statement) => Some(statement.type_()), 580 Located::Expression { expression, .. } => Some(expression.type_()), 581 Located::Arg(arg) => Some(arg.type_.clone()), 582 Located::Label(_, type_) | Located::Annotation { type_, .. } => Some(type_.clone()), 583 Located::Constant(constant) => Some(constant.type_()), 584 Located::ClauseGuard(guard) => Some(guard.type_()), 585 586 Located::PatternSpread { .. } 587 | Located::ModuleConstant(_) 588 | Located::ModuleCustomType(_) 589 | Located::ModuleFunction(_) 590 | Located::ModuleImport(_) 591 | Located::ModuleTypeAlias(_) 592 | Located::VariantConstructorDefinition(_) 593 | Located::FunctionBody(_) 594 | Located::UnqualifiedImport(_) 595 | Located::ModuleName { .. } => None, 596 } 597 } 598 599 pub fn type_definition_locations( 600 &self, 601 importable_modules: &im::HashMap<EcoString, type_::ModuleInterface>, 602 ) -> Option<Vec<DefinitionLocation>> { 603 let type_ = self.type_()?; 604 Some(type_to_definition_locations(type_, importable_modules)) 605 } 606} 607 608/// Returns the locations of all the types that one could reach starting from 609/// the given type (included). This includes all types that are part of a 610/// tuple/function type or that are used as args in a named type. 611/// 612/// For example, given this type `Dict(Int, #(Wibble, Wobble))` all the 613/// "reachable" include: `Dict`, `Int`, `Wibble` and `Wobble`. 614/// 615/// This is what powers the "go to type definition" capability of the language 616/// server. 617/// 618fn type_to_definition_locations<'a>( 619 type_: Arc<Type>, 620 importable_modules: &'a im::HashMap<EcoString, type_::ModuleInterface>, 621) -> Vec<DefinitionLocation> { 622 match type_.as_ref() { 623 // For named types we start with the location of the named type itself 624 // followed by the locations of all types they reference in their args. 625 // 626 // For example with a `Dict(Wibble, Wobble)` we'd start with the 627 // definition of `Dict`, followed by the definition of `Wibble` and 628 // `Wobble`. 629 // 630 Type::Named { 631 module, 632 name, 633 arguments, 634 .. 635 } => { 636 let Some(module) = importable_modules.get(module) else { 637 return vec![]; 638 }; 639 640 let Some(type_) = module.get_importable_type(&name) else { 641 return vec![]; 642 }; 643 644 let mut locations = vec![DefinitionLocation { 645 module: Some(module.name.clone()), 646 span: type_.origin, 647 }]; 648 for argument in arguments { 649 locations.extend(type_to_definition_locations( 650 argument.clone(), 651 importable_modules, 652 )); 653 } 654 locations 655 } 656 657 // For fn types we just get the locations of their arguments and return 658 // type. 659 // 660 Type::Fn { arguments, return_ } => arguments 661 .iter() 662 .flat_map(|argument| type_to_definition_locations(argument.clone(), importable_modules)) 663 .chain(type_to_definition_locations( 664 return_.clone(), 665 importable_modules, 666 )) 667 .collect_vec(), 668 669 // In case of a var we just follow it and get the locations of the type 670 // it points to. 671 // 672 Type::Var { type_ } => match type_.borrow().clone() { 673 type_::TypeVar::Unbound { .. } | type_::TypeVar::Generic { .. } => vec![], 674 type_::TypeVar::Link { type_ } => { 675 type_to_definition_locations(type_, importable_modules) 676 } 677 }, 678 679 // In case of tuples we get the locations of the wrapped types. 680 // 681 Type::Tuple { elements } => elements 682 .iter() 683 .flat_map(|element| type_to_definition_locations(element.clone(), importable_modules)) 684 .collect_vec(), 685 } 686} 687 688// Looks up the type constructor for the given type 689pub fn type_constructor_from_modules( 690 importable_modules: &im::HashMap<EcoString, type_::ModuleInterface>, 691 type_: std::sync::Arc<Type>, 692) -> Option<&type_::TypeConstructor> { 693 let type_ = type_::collapse_links(type_); 694 match type_.as_ref() { 695 Type::Named { name, module, .. } => importable_modules 696 .get(module) 697 .and_then(|i| i.types.get(name)), 698 _ => None, 699 } 700} 701 702#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] 703pub enum Origin { 704 Src, 705 Test, 706 Dev, 707} 708 709impl Origin { 710 /// Returns `true` if the origin is [`Src`]. 711 /// 712 /// [`Src`]: Origin::Src 713 #[must_use] 714 pub fn is_src(&self) -> bool { 715 matches!(self, Self::Src) 716 } 717 718 /// Returns `true` if the origin is [`Test`]. 719 /// 720 /// [`Test`]: Origin::Test 721 #[must_use] 722 pub fn is_test(&self) -> bool { 723 matches!(self, Self::Test) 724 } 725 726 /// Returns `true` if the origin is [`Dev`]. 727 /// 728 /// [`Dev`]: Origin::Dev 729 #[must_use] 730 pub fn is_dev(&self) -> bool { 731 matches!(self, Self::Dev) 732 } 733 734 /// Name of the folder containing the origin. 735 #[must_use] 736 pub fn folder_name(&self) -> &str { 737 match self { 738 Origin::Src => "src", 739 Origin::Test => "test", 740 Origin::Dev => "dev", 741 } 742 } 743} 744 745fn doc_comments_before<'a>( 746 doc_comments_spans: &mut Peekable<impl Iterator<Item = &'a SrcSpan>>, 747 extra: &ModuleExtra, 748 byte: u32, 749 src: &'a str, 750) -> (u32, Vec<&'a str>) { 751 let mut comments = vec![]; 752 let mut comment_start = u32::MAX; 753 while let Some(SrcSpan { start, end }) = doc_comments_spans.peek() { 754 if start > &byte { 755 break; 756 } 757 if extra.has_comment_between(*end, byte) { 758 // We ignore doc comments that come before a regular comment. 759 _ = doc_comments_spans.next(); 760 continue; 761 } 762 let comment = doc_comments_spans 763 .next() 764 .expect("Comment before accessing next span"); 765 766 if comment.start < comment_start { 767 comment_start = comment.start; 768 } 769 770 comments.push(Comment::from((comment, src)).content) 771 } 772 (comment_start, comments) 773} 774 775#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 776pub struct SourceFingerprint(u64); 777 778impl SourceFingerprint { 779 pub fn new(source: &str) -> Self { 780 SourceFingerprint(xxhash_rust::xxh3::xxh3_64(source.as_bytes())) 781 } 782 783 pub fn to_numerical_string(&self) -> String { 784 self.0.to_string() 785 } 786} 787 788/// Like a `Result`, but the operation can partially succeed or fail. 789/// 790#[derive(Debug)] 791pub enum Outcome<T, E> { 792 /// The operation was totally succesful. 793 Ok(T), 794 795 /// The operation was partially successful but there were problems. 796 PartialFailure(T, E), 797 798 /// The operation was entirely unsuccessful. 799 TotalFailure(E), 800} 801 802impl<T, E> Outcome<T, E> 803where 804 E: Debug, 805{ 806 #[cfg(test)] 807 /// Panic if there's any errors 808 pub fn unwrap(self) -> T { 809 match self { 810 Outcome::Ok(t) => t, 811 Outcome::PartialFailure(_, errors) => panic!("Error: {:?}", errors), 812 Outcome::TotalFailure(error) => panic!("Error: {:?}", error), 813 } 814 } 815 816 /// Panic if there's any errors 817 pub fn expect(self, e: &'static str) -> T { 818 match self { 819 Outcome::Ok(t) => t, 820 Outcome::PartialFailure(_, errors) => panic!("{e}: {:?}", errors), 821 Outcome::TotalFailure(error) => panic!("{e}: {:?}", error), 822 } 823 } 824 825 pub fn into_result(self) -> Result<T, E> { 826 match self { 827 Outcome::Ok(t) => Ok(t), 828 Outcome::PartialFailure(_, e) | Outcome::TotalFailure(e) => Err(e), 829 } 830 } 831 832 pub fn map<T2>(self, f: impl FnOnce(T) -> T2) -> Outcome<T2, E> { 833 match self { 834 Outcome::Ok(t) => Outcome::Ok(f(t)), 835 Outcome::PartialFailure(t, e) => Outcome::PartialFailure(f(t), e), 836 Outcome::TotalFailure(e) => Outcome::TotalFailure(e), 837 } 838 } 839}