Fork of daniellemaywood.uk/gleam — Wasm codegen work
29 kB
891 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2020 The Gleam contributors
3
4#[cfg(test)]
5mod tests;
6
7use crate::analyse::{ModuleAnalyzerConstructor, TargetSupport};
8use crate::build::package_loader::CacheFiles;
9
10use crate::error::{DefinedModuleOrigin, FailedModule, SkipReason, SkippedModule};
11use crate::io::files_with_extension;
12use crate::line_numbers::{self, LineNumbers};
13use crate::type_::PRELUDE_MODULE_NAME;
14use crate::{
15 Error, Result, Warning,
16 ast::{SrcSpan, TypedModule, UntypedModule},
17 build::{
18 Mode, Module, Origin, Outcome, Package, SourceFingerprint, Target,
19 elixir_libraries::ElixirLibraries,
20 native_file_copier::NativeFileCopier,
21 package_loader::{CodegenRequired, PackageLoader, StaleTracker},
22 },
23 codegen::{Erlang, ErlangApp, JavaScript, TypeScriptDeclarations},
24 config::PackageConfig,
25 dep_tree, error,
26 io::{BeamCompiler, CommandExecutor, FileSystemReader, FileSystemWriter, Stdio},
27 parse::extra::ModuleExtra,
28 paths, type_,
29 uid::UniqueIdGenerator,
30 warning::{TypeWarningEmitter, WarningEmitter},
31};
32use crate::{inline, metadata};
33use askama::Template;
34use ecow::EcoString;
35use itertools::Itertools;
36use std::collections::HashSet;
37use std::{collections::HashMap, fmt::write, time::SystemTime};
38use vec1::Vec1;
39
40use camino::{Utf8Path, Utf8PathBuf};
41
42use super::{ErlangAppCodegenConfiguration, TargetCodegenConfiguration, Telemetry};
43
44#[derive(Debug)]
45pub struct Compiled {
46 /// The modules which were just compiled
47 pub modules: Vec<Module>,
48 /// The names of all cached modules, which are not present in the `modules` field.
49 pub cached_module_names: Vec<EcoString>,
50}
51
52#[derive(Debug)]
53pub struct PackageCompiler<'a, IO> {
54 pub io: IO,
55 pub out: &'a Utf8Path,
56 pub lib: &'a Utf8Path,
57 pub root: &'a Utf8Path,
58 pub mode: Mode,
59 pub target: &'a TargetCodegenConfiguration,
60 pub config: &'a PackageConfig,
61 pub ids: UniqueIdGenerator,
62 pub write_metadata: bool,
63 pub perform_codegen: bool,
64 /// If set to false the compiler won't load and analyse any of the package's
65 /// modules and always succeed compilation returning no compile modules.
66 ///
67 /// Code generation is still carried out so that a root package will have an
68 /// entry point nonetheless.
69 ///
70 pub compile_modules: bool,
71 pub write_entrypoint: bool,
72 pub copy_native_files: bool,
73 pub compile_beam_bytecode: bool,
74 pub subprocess_stdio: Stdio,
75 pub target_support: TargetSupport,
76 pub cached_warnings: CachedWarnings,
77 pub check_module_conflicts: CheckModuleConflicts,
78}
79
80impl<'a, IO> PackageCompiler<'a, IO>
81where
82 IO: FileSystemReader + FileSystemWriter + CommandExecutor + BeamCompiler + Clone,
83{
84 pub fn new(
85 config: &'a PackageConfig,
86 mode: Mode,
87 root: &'a Utf8Path,
88 out: &'a Utf8Path,
89 lib: &'a Utf8Path,
90 target: &'a TargetCodegenConfiguration,
91 ids: UniqueIdGenerator,
92 io: IO,
93 ) -> Self {
94 Self {
95 io,
96 ids,
97 out,
98 lib,
99 root,
100 mode,
101 config,
102 target,
103 write_metadata: true,
104 perform_codegen: true,
105 compile_modules: true,
106 write_entrypoint: false,
107 copy_native_files: true,
108 compile_beam_bytecode: true,
109 subprocess_stdio: Stdio::Inherit,
110 target_support: TargetSupport::NotEnforced,
111 cached_warnings: CachedWarnings::Ignore,
112 check_module_conflicts: CheckModuleConflicts::DoNotCheck,
113 }
114 }
115
116 /// Compile the package.
117 /// Returns a list of modules that were compiled. Any modules that were read
118 /// from the cache will not be returned.
119 // TODO: return the cached modules.
120 pub fn compile(
121 mut self,
122 warnings: &WarningEmitter,
123 existing_modules: &mut im::HashMap<EcoString, type_::ModuleInterface>,
124 already_defined_modules: &mut im::HashMap<EcoString, DefinedModuleOrigin>,
125 stale_modules: &mut StaleTracker,
126 incomplete_modules: &mut HashSet<EcoString>,
127 telemetry: &dyn Telemetry,
128 ) -> Outcome<Compiled, Error> {
129 let span = tracing::info_span!("compile", package = %self.config.name.as_str());
130 let _enter = span.enter();
131
132 // Ensure that the package is compatible with this version of Gleam
133 if let Err(error) = self.config.check_gleam_compatibility() {
134 return error.into();
135 }
136
137 let artefact_directory = self.out.join(paths::ARTEFACT_DIRECTORY_NAME);
138 let codegen_required = if self.perform_codegen {
139 CodegenRequired::Yes
140 } else {
141 CodegenRequired::No
142 };
143
144 let loader = PackageLoader::new(
145 self.io.clone(),
146 self.ids.clone(),
147 self.mode,
148 self.root,
149 self.cached_warnings,
150 warnings,
151 codegen_required,
152 &artefact_directory,
153 self.target.target(),
154 &self.config.name,
155 stale_modules,
156 already_defined_modules,
157 incomplete_modules,
158 );
159
160 let loaded = if self.compile_modules {
161 match loader.run() {
162 Ok(loaded) => loaded,
163 Err(error) => return error.into(),
164 }
165 } else {
166 Loaded::empty()
167 };
168
169 let mut cached_module_names = Vec::new();
170
171 // Load the cached modules that have previously been compiled
172 for module in loaded.cached.into_iter() {
173 // Emit any cached warnings.
174 // Note that `self.cached_warnings` is set to `Ignore` (such as for
175 // dependency packages) then this field will not be populated.
176 if let Err(error) = self.emit_warnings(warnings, &module) {
177 return error.into();
178 }
179
180 cached_module_names.push(module.name.clone());
181
182 // Register the cached module so its type information etc can be
183 // used for compiling futher modules.
184 _ = existing_modules.insert(module.name.clone(), module);
185 }
186
187 if !loaded.to_compile.is_empty() {
188 // Print that work is being done
189 if self.perform_codegen {
190 telemetry.compiling_package(&self.config.name);
191 } else {
192 telemetry.checking_package(&self.config.name)
193 }
194 }
195
196 // Type check the modules that are new or have changed
197 tracing::info!(count=%loaded.to_compile.len(), "analysing_modules");
198 let outcome = analyse(
199 &self.config,
200 self.target.target(),
201 self.mode,
202 &self.ids,
203 loaded.to_compile,
204 existing_modules,
205 warnings,
206 self.target_support,
207 incomplete_modules,
208 );
209
210 let mut modules = match outcome {
211 Outcome::Ok(modules) => modules,
212 Outcome::PartialFailure(modules, errors) => {
213 return Outcome::PartialFailure(
214 Compiled {
215 modules,
216 cached_module_names,
217 },
218 errors,
219 );
220 }
221 Outcome::TotalFailure(errors) => return Outcome::TotalFailure(errors),
222 };
223
224 tracing::debug!("performing_code_generation");
225
226 // Inlining is currently disabled. See
227 // https://github.com/gleam-lang/gleam/pull/5010 for information.
228
229 // let modules = if self.perform_codegen {
230 // modules
231 // .into_iter()
232 // .map(|mut module| {
233 // module.ast = inline::module(module.ast, &existing_modules);
234 // module
235 // })
236 // .collect()
237 // } else {
238 // modules
239 // };
240
241 if let Err(error) = self.perform_codegen(&modules) {
242 return error.into();
243 }
244
245 if let Err(error) = self.encode_and_write_metadata(&mut modules) {
246 return error.into();
247 }
248
249 Outcome::Ok(Compiled {
250 modules,
251 cached_module_names,
252 })
253 }
254
255 fn compile_erlang_to_beam(
256 &mut self,
257 modules: &HashSet<Utf8PathBuf>,
258 ) -> Result<Vec<EcoString>, Error> {
259 if modules.is_empty() {
260 tracing::debug!("no_erlang_to_compile");
261 return Ok(Vec::new());
262 }
263
264 tracing::debug!("compiling_erlang");
265
266 self.io
267 .compile_beam(self.out, self.lib, modules, self.subprocess_stdio)
268 .map(|modules| modules.iter().map(|str| EcoString::from(str)).collect())
269 }
270
271 fn copy_project_native_files(
272 &mut self,
273 destination_dir: &Utf8Path,
274 to_compile_modules: &mut HashSet<Utf8PathBuf>,
275 ) -> Result<(), Error> {
276 tracing::debug!("copying_native_source_files");
277
278 // TODO: unit test
279 let priv_source = self.root.join("priv");
280 let priv_build = self.out.join("priv");
281 if self.io.is_directory(&priv_source) && !self.io.is_directory(&priv_build) {
282 tracing::debug!("linking_priv_to_build");
283 self.io.symlink_dir(&priv_source, &priv_build)?;
284 }
285
286 let copier = NativeFileCopier::new(
287 self.io.clone(),
288 self.root.clone(),
289 destination_dir,
290 self.check_module_conflicts,
291 );
292 let copied = copier.run()?;
293
294 to_compile_modules.extend(copied.to_compile.into_iter());
295
296 // If there are any Elixir files then we need to locate Elixir
297 // installed on this system for use in compilation.
298 if copied.any_elixir {
299 ElixirLibraries::make_available(
300 &self.io,
301 &self.lib.to_path_buf(),
302 self.subprocess_stdio,
303 )?;
304 }
305
306 Ok(())
307 }
308
309 fn encode_and_write_metadata(&mut self, modules: &mut [Module]) -> Result<()> {
310 if !self.write_metadata {
311 tracing::debug!("package_metadata_writing_disabled");
312 return Ok(());
313 }
314 if modules.is_empty() {
315 return Ok(());
316 }
317
318 let artefact_dir = self.out.join(paths::ARTEFACT_DIRECTORY_NAME);
319
320 tracing::debug!("writing_module_caches");
321 for module in modules {
322 let cache_files = CacheFiles::new(&artefact_dir, &module.name);
323
324 let result = if self.cached_warnings.should_use() {
325 metadata::encode(&module.ast.type_info)
326 } else {
327 // Dependency packages don't get warnings persisted as the
328 // programmer doesn't want to be told every time about warnings they
329 // cannot fix directly.
330 let warnings = std::mem::take(&mut module.ast.type_info.warnings);
331 let result = metadata::encode(&module.ast.type_info);
332 module.ast.type_info.warnings = warnings;
333 result
334 };
335
336 // Write cache file
337 let bytes = result.expect("Failed to serialise module cache");
338 self.io.write_bytes(&cache_files.cache_path, &bytes)?;
339
340 // Write cache metadata
341 let info = CacheMetadata {
342 mtime: module.mtime,
343 codegen_performed: self.perform_codegen,
344 dependencies: module.dependencies.clone(),
345 fingerprint: SourceFingerprint::new(&module.code),
346 line_numbers: module.ast.type_info.line_numbers.clone(),
347 };
348 self.io
349 .write_bytes(&cache_files.meta_path, &info.to_binary())?;
350 }
351 Ok(())
352 }
353
354 fn perform_codegen(&mut self, modules: &[Module]) -> Result<()> {
355 if !self.perform_codegen {
356 tracing::debug!("skipping_codegen");
357 return Ok(());
358 }
359
360 match self.target {
361 TargetCodegenConfiguration::JavaScript {
362 emit_typescript_definitions,
363 emit_source_maps,
364 prelude_location,
365 } => self.perform_javascript_codegen(
366 modules,
367 *emit_typescript_definitions,
368 *emit_source_maps,
369 prelude_location,
370 ),
371 TargetCodegenConfiguration::Erlang { app_file } => {
372 self.perform_erlang_codegen(modules, app_file.as_ref())
373 }
374 }
375 }
376
377 fn perform_erlang_codegen(
378 &mut self,
379 modules: &[Module],
380 app_file_config: Option<&ErlangAppCodegenConfiguration>,
381 ) -> Result<(), Error> {
382 let mut written = HashSet::new();
383 let build_dir = self.out.join(paths::ARTEFACT_DIRECTORY_NAME);
384 let include_dir = self.out.join("include");
385 let io = self.io.clone();
386
387 io.mkdir(&build_dir)?;
388
389 if self.copy_native_files {
390 self.copy_project_native_files(&build_dir, &mut written)?;
391 } else {
392 tracing::debug!("skipping_native_file_copying");
393 }
394
395 if self.compile_beam_bytecode && self.write_entrypoint {
396 self.render_erlang_entrypoint_module(&build_dir, &mut written)?;
397 } else {
398 tracing::debug!("skipping_entrypoint_generation");
399 }
400
401 // NOTE: This must come after `copy_project_native_files` to ensure that
402 // we overwrite any precompiled Erlang that was included in the Hex
403 // package. Otherwise we will build the potentially outdated precompiled
404 // version and not the newly compiled version.
405 Erlang::new(&build_dir, &include_dir).render(io.clone(), modules, self.root)?;
406
407 let native_modules: Vec<EcoString> = if self.compile_beam_bytecode {
408 written.extend(modules.iter().map(Module::compiled_erlang_path));
409 self.compile_erlang_to_beam(&written)?
410 } else {
411 tracing::debug!("skipping_erlang_bytecode_compilation");
412 Vec::new()
413 };
414
415 if let Some(config) = app_file_config {
416 ErlangApp::new(&self.out.join("ebin"), config).render(
417 io,
418 &self.config,
419 modules,
420 native_modules,
421 )?;
422 }
423 Ok(())
424 }
425
426 fn perform_javascript_codegen(
427 &mut self,
428 modules: &[Module],
429 typescript: bool,
430 sourcemaps: bool,
431 prelude_location: &Utf8Path,
432 ) -> Result<(), Error> {
433 let mut written = HashSet::new();
434 let typescript = if typescript {
435 TypeScriptDeclarations::Emit
436 } else {
437 TypeScriptDeclarations::None
438 };
439 JavaScript::new(
440 &self.out,
441 typescript,
442 sourcemaps,
443 prelude_location,
444 &self.root,
445 )
446 .render(&self.io, modules, self.stdlib_package())?;
447
448 if self.copy_native_files {
449 self.copy_project_native_files(&self.out, &mut written)?;
450 } else {
451 tracing::debug!("skipping_native_file_copying");
452 }
453
454 // Source maps files include a reference to the source Gleam file, so if
455 // we are generating source maps we need to make the source file available
456 // in the build directory next to the JavaScript file and the source map.
457 if sourcemaps {
458 for module in modules {
459 self.link_module_source_file_to_out(module)?;
460 }
461 }
462
463 Ok(())
464 }
465
466 /// Link the module source .gleam file to the output directory.
467 /// This is done if we are generating source maps because source
468 /// maps include a reference to the source file, so the source
469 /// file needs to be accessible to the user of the source file.
470 ///
471 fn link_module_source_file_to_out(&mut self, module: &Module) -> Result<(), Error> {
472 let source = module.input_path.as_path();
473 let destination = self.out.join(module.name.as_str()).with_extension("gleam");
474 let file_at_destination = self.io.exists(&destination);
475
476 // If the file does not exist then linking is needed. It could already
477 // exist due to having been linked by a previous compilation run.
478 if !file_at_destination {
479 return self.io.hardlink(&source, &destination);
480 }
481
482 // If file is already there then there is nothing to do.
483 //
484 // If there is already a file there but it is not a link that means
485 // it is a link to the source file for a previous module that had
486 // the same name.
487 if self.io.is_same_file(&source, &destination)? {
488 return Ok(());
489 }
490
491 self.io.delete_file(&destination)?;
492 self.io.hardlink(&source, &destination)
493 }
494
495 fn render_erlang_entrypoint_module(
496 &mut self,
497 out: &Utf8Path,
498 modules_to_compile: &mut HashSet<Utf8PathBuf>,
499 ) -> Result<(), Error> {
500 let name = format!("{name}@@main.erl", name = self.config.name);
501 let path = out.join(&name);
502
503 // If the entrypoint module has already been created then we don't need
504 // to write and compile it again.
505 if self.io.is_file(&path) {
506 tracing::debug!("erlang_entrypoint_already_exists");
507 return Ok(());
508 }
509
510 let template = ErlangEntrypointModule {
511 application: &self.config.name,
512 };
513 let module = template.render().expect("Erlang entrypoint rendering");
514 self.io.write(&path, &module)?;
515 let _ = modules_to_compile.insert(name.into());
516 tracing::debug!("erlang_entrypoint_written");
517 Ok(())
518 }
519
520 fn emit_warnings(
521 &self,
522 warnings: &WarningEmitter,
523 module: &type_::ModuleInterface,
524 ) -> Result<()> {
525 for warning in &module.warnings {
526 let src = self.io.read(&module.src_path)?;
527 warnings.emit(Warning::Type {
528 path: module.src_path.clone(),
529 src: src.into(),
530 warning: Box::new(warning.clone()),
531 });
532 }
533
534 Ok(())
535 }
536
537 fn stdlib_package(&self) -> StdlibPackage {
538 if self.config.dependencies.contains_key("gleam_stdlib")
539 || self.config.dev_dependencies.contains_key("gleam_stdlib")
540 {
541 StdlibPackage::Present
542 } else {
543 StdlibPackage::Missing
544 }
545 }
546}
547
548#[derive(Debug, Clone, Copy, Eq, PartialEq)]
549pub enum StdlibPackage {
550 Present,
551 Missing,
552}
553
554fn analyse(
555 package_config: &PackageConfig,
556 target: Target,
557 mode: Mode,
558 ids: &UniqueIdGenerator,
559 mut parsed_modules: Vec<UncompiledModule>,
560 module_types: &mut im::HashMap<EcoString, type_::ModuleInterface>,
561 warnings: &WarningEmitter,
562 target_support: TargetSupport,
563 incomplete_modules: &mut HashSet<EcoString>,
564) -> Outcome<Vec<Module>, Error> {
565 let mut modules = Vec::with_capacity(parsed_modules.len() + 1);
566 let direct_dependencies = package_config.dependencies_for(mode).expect("Package deps");
567 let dev_dependencies = package_config.dev_dependencies.keys().cloned().collect();
568
569 // Insert the prelude
570 // DUPE: preludeinsertion
571 // TODO: Currently we do this here and also in the tests. It would be better
572 // to have one place where we create all this required state for use in each
573 // place.
574 let _ = module_types.insert(PRELUDE_MODULE_NAME.into(), type_::build_prelude(ids));
575
576 let mut skipped_modules: HashMap<EcoString, SkippedModule> = HashMap::new();
577 let mut failed_modules = HashMap::new();
578
579 for UncompiledModule {
580 name,
581 code,
582 ast,
583 path,
584 mtime,
585 origin,
586 package,
587 dependencies,
588 extra,
589 } in parsed_modules
590 {
591 tracing::debug!(module = ?name, "Type checking");
592
593 // We first need to check if the module can actually be compiled.
594 // If we weren't able to compile one of the modules it depends on, then
595 // we have to skip this one to avoid reporting false errors.
596 let skipped_dependency = dependencies.iter().find_map(|(dependency, location)| {
597 if let Some(failed_module) = failed_modules.get(dependency) {
598 // This module imports a module with an error.
599 let reason = SkipReason::DependencyHasError {
600 name: dependency.clone(),
601 };
602 Some((*location, reason))
603 } else if let Some(skipped_module) = skipped_modules.get(dependency) {
604 // This module imports a module that has been skipped too.
605 let reason = SkipReason::DependencyWasSkipped {
606 name: dependency.clone(),
607 erroring_module: skipped_module.reason.erroring_module(),
608 };
609 Some((*location, reason))
610 } else {
611 None
612 }
613 });
614
615 // The module does depend on some other module that had to be skipped,
616 // so we have to skip this one as well.
617 if let Some((location, reason)) = skipped_dependency {
618 let _ = skipped_modules.insert(
619 name.clone(),
620 SkippedModule {
621 path,
622 name,
623 code: code.clone(),
624 location,
625 reason,
626 },
627 );
628 continue;
629 }
630
631 let line_numbers = LineNumbers::new(&code);
632
633 let analysis = crate::analyse::ModuleAnalyzerConstructor {
634 target,
635 ids,
636 origin,
637 importable_modules: module_types,
638 warnings: &TypeWarningEmitter::new(path.clone(), code.clone(), warnings.clone()),
639 direct_dependencies: &direct_dependencies,
640 dev_dependencies: &dev_dependencies,
641 target_support,
642 package_config,
643 }
644 .infer_module(ast, line_numbers, path.clone());
645
646 match analysis {
647 Outcome::Ok(ast) => {
648 // Module has compiled successfully.
649 // Make sure it isn't marked as incomplete.
650 let _ = incomplete_modules.remove(&name.clone());
651
652 let mut module = Module {
653 dependencies,
654 origin,
655 extra,
656 mtime,
657 name,
658 code,
659 ast,
660 input_path: path,
661 };
662 module.attach_doc_and_module_comments();
663
664 // Register the types from this module so they can be imported into
665 // other modules.
666 let _ = module_types.insert(module.name.clone(), module.ast.type_info.clone());
667
668 // Check for empty modules and emit warning
669 // Only emit the empty module warning if the module has no definitions at all.
670 // Modules with only private definitions already emit their own warnings.
671 if module_types
672 .get(&module.name)
673 .map(|interface| interface.values.is_empty() && interface.types.is_empty())
674 .unwrap_or(false)
675 {
676 warnings.emit(crate::warning::Warning::EmptyModule {
677 path: module.input_path.clone(),
678 name: module.name.clone(),
679 });
680 }
681
682 // Register the successfully type checked module data so that it can be
683 // used for code generation and in the language server.
684 modules.push(module);
685 }
686
687 Outcome::PartialFailure(ast, errors) => {
688 // Mark as incomplete so that this module isn't reloaded from
689 // cache.
690 let _ = incomplete_modules.insert(name.clone());
691 // Register the partially type checked module data so that it
692 // can be used in the language server.
693 let names = ast.names.clone();
694 let mut module = Module {
695 dependencies,
696 origin,
697 extra,
698 mtime,
699 name,
700 code: code.clone(),
701 ast,
702 input_path: path.clone(),
703 };
704 module.attach_doc_and_module_comments();
705
706 let _ = module_types.insert(module.ast.name.clone(), module.ast.type_info.clone());
707 let _ = failed_modules.insert(
708 module.name.clone(),
709 FailedModule {
710 names: Box::new(names),
711 path: path,
712 src: code,
713 errors,
714 },
715 );
716 modules.push(module);
717 }
718
719 Outcome::TotalFailure(errors) => {
720 let _ = failed_modules.insert(
721 name.clone(),
722 FailedModule {
723 names: Default::default(),
724 path: path.clone(),
725 src: code.clone(),
726 errors,
727 },
728 );
729 }
730 };
731 }
732
733 // Now we need to check if any module has failed and return the appropriate
734 // outcome.
735 let skipped_modules = skipped_modules.into_values().collect();
736
737 if failed_modules.is_empty() {
738 Outcome::Ok(modules)
739 } else if modules.is_empty() {
740 let error = Error::Type {
741 skipped_modules,
742 failed_modules,
743 };
744 Outcome::TotalFailure(error)
745 } else {
746 let error = Error::Type {
747 skipped_modules,
748 failed_modules,
749 };
750 Outcome::PartialFailure(modules, error)
751 }
752}
753
754#[derive(Debug)]
755pub(crate) enum Input {
756 New(UncompiledModule),
757 Cached(CachedModule),
758}
759
760impl Input {
761 pub fn name(&self) -> &EcoString {
762 match self {
763 Input::New(m) => &m.name,
764 Input::Cached(m) => &m.name,
765 }
766 }
767
768 pub fn source_path(&self) -> &Utf8Path {
769 match self {
770 Input::New(m) => &m.path,
771 Input::Cached(m) => &m.source_path,
772 }
773 }
774
775 pub fn dependencies(&self) -> Vec<EcoString> {
776 match self {
777 Input::New(m) => m.dependencies.iter().map(|(n, _)| n.clone()).collect(),
778 Input::Cached(m) => m.dependencies.iter().map(|(n, _)| n.clone()).collect(),
779 }
780 }
781
782 /// Returns `true` if the input is [`New`].
783 ///
784 /// [`New`]: Input::New
785 #[must_use]
786 pub(crate) fn is_new(&self) -> bool {
787 matches!(self, Self::New(..))
788 }
789
790 /// Returns `true` if the input is [`Cached`].
791 ///
792 /// [`Cached`]: Input::Cached
793 #[must_use]
794 pub(crate) fn is_cached(&self) -> bool {
795 matches!(self, Self::Cached(..))
796 }
797}
798
799#[derive(Debug)]
800pub(crate) struct CachedModule {
801 pub name: EcoString,
802 pub origin: Origin,
803 pub dependencies: Vec<(EcoString, SrcSpan)>,
804 pub source_path: Utf8PathBuf,
805 pub line_numbers: LineNumbers,
806}
807
808#[derive(Debug, serde::Serialize, serde::Deserialize)]
809pub(crate) struct CacheMetadata {
810 pub mtime: SystemTime,
811 pub codegen_performed: bool,
812 pub dependencies: Vec<(EcoString, SrcSpan)>,
813 pub fingerprint: SourceFingerprint,
814 pub line_numbers: LineNumbers,
815}
816
817impl CacheMetadata {
818 pub fn to_binary(&self) -> Vec<u8> {
819 bincode::serde::encode_to_vec(self, bincode::config::legacy())
820 .expect("Serializing cache info")
821 }
822
823 pub fn from_binary(bytes: &[u8]) -> Result<Self, String> {
824 match bincode::serde::decode_from_slice(bytes, bincode::config::legacy()) {
825 Ok((data, _)) => Ok(data),
826 Err(e) => Err(e.to_string()),
827 }
828 }
829}
830
831#[derive(Debug, Default, PartialEq, Eq)]
832pub(crate) struct Loaded {
833 pub to_compile: Vec<UncompiledModule>,
834 pub cached: Vec<type_::ModuleInterface>,
835}
836
837impl Loaded {
838 fn empty() -> Self {
839 Self {
840 to_compile: vec![],
841 cached: vec![],
842 }
843 }
844}
845
846#[derive(Debug, PartialEq, Eq)]
847pub(crate) struct UncompiledModule {
848 pub path: Utf8PathBuf,
849 pub name: EcoString,
850 pub code: EcoString,
851 pub mtime: SystemTime,
852 pub origin: Origin,
853 pub package: EcoString,
854 pub dependencies: Vec<(EcoString, SrcSpan)>,
855 pub ast: UntypedModule,
856 pub extra: ModuleExtra,
857}
858
859#[derive(Template)]
860#[template(path = "gleam@@main.erl", escape = "none")]
861struct ErlangEntrypointModule<'a> {
862 application: &'a str,
863}
864
865#[derive(Debug, Clone, Copy)]
866pub enum CachedWarnings {
867 Use,
868 Ignore,
869}
870impl CachedWarnings {
871 pub(crate) fn should_use(&self) -> bool {
872 match self {
873 CachedWarnings::Use => true,
874 CachedWarnings::Ignore => false,
875 }
876 }
877}
878
879#[derive(Debug, Clone, Copy)]
880pub enum CheckModuleConflicts {
881 Check,
882 DoNotCheck,
883}
884impl CheckModuleConflicts {
885 pub(crate) fn should_check(&self) -> bool {
886 match self {
887 CheckModuleConflicts::Check => true,
888 CheckModuleConflicts::DoNotCheck => false,
889 }
890 }
891}