Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-core / src / build / package_compiler.rs
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}