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
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}