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