Fork of daniellemaywood.uk/gleam — Wasm codegen work
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}