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