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