spade/
lib.rs

1pub mod compiler_state;
2pub mod error_handling;
3mod name_dump;
4pub mod namespaced_file;
5
6use compiler_state::{CompilerState, MirContext};
7use error_handling::{ErrorHandler, Reportable};
8use itertools::Itertools;
9use logos::Logos;
10use rayon::prelude::*;
11use rustc_hash::FxHashMap as HashMap;
12use spade_ast_lowering::id_tracker::ExprIdTracker;
13use spade_codespan_reporting::term::termcolor::Buffer;
14use spade_common::location_info::{Loc, WithLocation};
15pub use spade_common::namespace::ModuleNamespace;
16use spade_diagnostics::diag_list::DiagList;
17use spade_hir::expression::Safety;
18use spade_mir::codegen::{prepare_codegen, Codegenable};
19use spade_mir::passes::deduplicate_mut_wires::DeduplicateMutWires;
20use spade_mir::unit_name::InstanceMap;
21use spade_mir::verilator_wrapper::verilator_wrappers;
22use spade_typeinference::traits::TraitImplList;
23use std::collections::BTreeMap;
24use std::io::Write;
25use std::path::PathBuf;
26use std::sync::{Arc, RwLock};
27use tracing::Level;
28use typeinference::TypeState;
29
30use spade_ast::ModuleBody;
31use spade_ast_lowering::{
32    ensure_unique_anonymous_traits, global_symbols, visit_module_body, Context as AstLoweringCtx,
33    SelfContext,
34};
35use spade_common::id_tracker::ImplIdTracker;
36use spade_common::name::{NameID, Path as SpadePath, Visibility};
37use spade_diagnostics::{CodeBundle, DiagHandler, Diagnostic};
38use spade_hir::symbol_table::SymbolTable;
39use spade_hir::{ExecutableItem, ItemList};
40use spade_hir_lowering::monomorphisation::MirOutput;
41use spade_hir_lowering::NameSourceMap;
42pub use spade_parser::lexer;
43use spade_parser::Parser;
44use spade_typeinference::trace_stack::format_trace_stack;
45use spade_typeinference::{self as typeinference, SharedTypeState};
46
47pub struct Opt<'b> {
48    pub error_buffer: &'b mut Buffer,
49    pub outfile: Option<PathBuf>,
50    pub mir_output: Option<PathBuf>,
51    pub verilator_wrapper_output: Option<PathBuf>,
52    pub state_dump_file: Option<PathBuf>,
53    pub item_list_file: Option<PathBuf>,
54    pub print_type_traceback: bool,
55    pub print_parse_traceback: bool,
56    pub opt_passes: Vec<String>,
57}
58
59/// Compiler output.
60pub struct Artefacts {
61    pub code: CodeBundle,
62    pub item_list: ItemList,
63    // MIR entities before aliases have been flattened
64    pub bumpy_mir_entities: Vec<spade_mir::Entity>,
65    // MIR entities after flattening
66    pub flat_mir_entities: Vec<Codegenable>,
67    pub state: CompilerState,
68    pub impl_list: TraitImplList,
69    pub type_states: BTreeMap<NameID, TypeState>,
70}
71
72/// Like [Artefacts], but if the compiler didn't finish due to errors.
73pub struct UnfinishedArtefacts {
74    pub code: CodeBundle,
75    pub symtab: Option<SymbolTable>,
76    pub item_list: Option<ItemList>,
77    pub type_states: Option<BTreeMap<NameID, TypeState>>,
78}
79
80pub enum CompilationResult {
81    EarlyFailure(UnfinishedArtefacts),
82    LateFailure(Artefacts),
83}
84
85struct CodegenArtefacts {
86    bumpy_mir_entities: Vec<spade_mir::Entity>,
87    flat_mir_entities: Vec<Codegenable>,
88    module_code: Vec<String>,
89    mir_code: Vec<String>,
90    instance_map: InstanceMap,
91    mir_context: HashMap<NameID, MirContext>,
92}
93
94#[tracing::instrument(skip_all)]
95pub fn compile(
96    mut sources: Vec<(ModuleNamespace, String, String)>,
97    include_stdlib_and_prelude: bool,
98    opts: Opt,
99    diag_handler: DiagHandler,
100) -> Result<Artefacts, CompilationResult> {
101    let mut symtab = SymbolTable::new();
102    let mut item_list = ItemList::new();
103
104    let mut sources = if include_stdlib_and_prelude {
105        // We want to build stdlib and prelude before building user code,
106        // to give `previously defined <here>` pointing into user code, instead
107        // of stdlib code
108        let mut all_sources = stdlib_and_prelude();
109        all_sources.append(&mut sources);
110        all_sources
111    } else {
112        sources
113    };
114    sources.append(&mut core_files());
115
116    spade_ast_lowering::builtins::populate_symtab(&mut symtab, &mut item_list);
117
118    let code = Arc::new(RwLock::new(CodeBundle::new("".to_string())));
119
120    let mut errors = ErrorHandler::new(opts.error_buffer, diag_handler, Arc::clone(&code));
121
122    let module_asts = parse(
123        sources,
124        Arc::clone(&code),
125        opts.print_parse_traceback,
126        &mut errors,
127    );
128    errors.errors_are_recoverable();
129
130    let mut unfinished_artefacts = UnfinishedArtefacts {
131        code: code.read().unwrap().clone(),
132        symtab: None,
133        item_list: None,
134        type_states: None,
135    };
136
137    let pass_impls = spade_mir::passes::mir_passes();
138    let opt_passes = opts
139        .opt_passes
140        .iter()
141        .map(|pass| {
142            if let Some(pass) = pass_impls.get(pass.as_str()) {
143                Ok(pass.as_ref())
144            } else {
145                let err = format!("{pass} is not a known optimization pass.");
146                Err(err)
147            }
148        })
149        .collect::<Result<Vec<_>, _>>();
150    let mut opt_passes = match opt_passes {
151        Ok(p) => p,
152        Err(e) => {
153            errors.error_buffer.write_all(e.as_bytes()).unwrap();
154            return Err(CompilationResult::EarlyFailure(unfinished_artefacts));
155        }
156    };
157    // This is a non-optional pass that prevents codegen bugs
158    let deduplicate_mut_wires = DeduplicateMutWires {};
159    opt_passes.push(&deduplicate_mut_wires);
160
161    let mut ctx = AstLoweringCtx {
162        symtab,
163        item_list,
164        idtracker: Arc::new(ExprIdTracker::new()),
165        impl_idtracker: ImplIdTracker::new(),
166        pipeline_ctx: None,
167        self_ctx: SelfContext::FreeStanding,
168        current_unit: None,
169        diags: DiagList::new(),
170        safety: Safety::Default,
171    };
172
173    // Add all "root" project main.spade modules
174    for root in module_asts
175        .iter()
176        .filter(|(ns, _ast)| ns.base_namespace == ns.namespace)
177    {
178        let namespace = &root.0;
179        if !namespace.namespace.0.is_empty() {
180            let name_id = ctx.symtab.add_thing(
181                namespace.namespace.clone(),
182                spade_hir::symbol_table::Thing::Module(
183                    ().nowhere(),
184                    namespace.namespace.0.last().unwrap().unwrap_named().clone(),
185                ),
186                Some(Visibility::Implicit.nowhere()),
187            );
188            ctx.item_list.modules.insert(
189                name_id.clone(),
190                spade_hir::Module {
191                    name: name_id.at_loc(&root.1),
192                    documentation: "".to_string(),
193                },
194            );
195        }
196    }
197
198    let mut missing_namespace_set = module_asts
199        .iter()
200        .map(|(ns, _ast)| (ns.namespace.clone(), ns.file.clone()))
201        .collect::<HashMap<_, _>>();
202
203    for (namespace, module_ast) in &module_asts {
204        do_in_namespace(namespace, &mut ctx, &mut |ctx| {
205            global_symbols::handle_external_modules(
206                &namespace.file,
207                None,
208                module_ast,
209                &mut missing_namespace_set,
210                ctx,
211            )
212            .or_report(&mut errors);
213        })
214    }
215
216    if errors.failed_now() {
217        unfinished_artefacts.symtab = Some(ctx.symtab);
218        return Err(CompilationResult::EarlyFailure(unfinished_artefacts));
219    }
220
221    for err in global_symbols::report_missing_mod_declarations(&module_asts, &missing_namespace_set)
222    {
223        errors.report(&err);
224    }
225
226    errors.errors_are_recoverable();
227
228    for (namespace, module_ast) in &module_asts {
229        do_in_namespace(namespace, &mut ctx, &mut |ctx| {
230            global_symbols::gather_types(module_ast, ctx).or_report(&mut errors);
231        })
232    }
233
234    if errors.failed_now() {
235        unfinished_artefacts.symtab = Some(ctx.symtab);
236        errors.drain_diag_list(&mut ctx.diags);
237        return Err(CompilationResult::EarlyFailure(unfinished_artefacts));
238    }
239
240    for (namespace, module_ast) in &module_asts {
241        do_in_namespace(namespace, &mut ctx, &mut |ctx| {
242            global_symbols::gather_symbols(module_ast, ctx).or_report(&mut errors);
243        })
244    }
245
246    unfinished_artefacts.item_list = Some(ctx.item_list.clone());
247
248    if errors.failed_now() {
249        unfinished_artefacts.symtab = Some(ctx.symtab);
250        errors.drain_diag_list(&mut ctx.diags);
251        return Err(CompilationResult::EarlyFailure(unfinished_artefacts));
252    }
253    // Let's prevent using this thing again
254    let _unfinished_artefacts = unfinished_artefacts;
255
256    lower_ast(&module_asts, &mut ctx, &mut errors);
257
258    let AstLoweringCtx {
259        symtab,
260        mut item_list,
261        idtracker,
262        impl_idtracker,
263        pipeline_ctx: _,
264        self_ctx: _,
265        current_unit: _,
266        mut diags,
267        safety: _,
268    } = ctx;
269
270    errors.drain_diag_list(&mut diags);
271
272    for e in ensure_unique_anonymous_traits(&mut item_list) {
273        errors.report(&e)
274    }
275
276    let frozen_symtab = symtab.freeze();
277
278    let shared_type_state = Arc::new(SharedTypeState::new());
279    let mut impl_type_state = TypeState::fresh(Arc::clone(&shared_type_state));
280    let mapped_trait_impls = impl_type_state.visit_impl_blocks(&item_list);
281
282    errors.drain_diag_list(&mut impl_type_state.owned.diags);
283
284    let type_inference_ctx = typeinference::Context {
285        symtab: frozen_symtab.symtab(),
286        items: &item_list,
287        trait_impls: &mapped_trait_impls,
288    };
289
290    let mut type_states = BTreeMap::new();
291
292    let executables_and_types = item_list
293        .executables
294        .iter()
295        .filter_map(|(name, item)| match item {
296            ExecutableItem::Unit(u) => {
297                let mut type_state = impl_type_state.create_child();
298
299                let result = type_state
300                    .visit_unit(u, &type_inference_ctx)
301                    .report(&mut errors);
302
303                let failures = type_state.owned.diags.errors.len() != 0;
304                errors.drain_diag_list(&mut type_state.owned.diags);
305
306                // Later stages will fail if we don't have a a complete type state,
307                // so we'll need to filter out modules that failed. However, for the LSP
308                // we still want to retain the incomplete type state
309                type_states.insert(name.clone(), type_state.clone());
310
311                if let Ok(()) = result {
312                    if opts.print_type_traceback {
313                        type_state.print_equations();
314                        println!("{}", format_trace_stack(&type_state));
315                    }
316                    if !failures {
317                        Some((name, (item, type_state)))
318                    } else {
319                        None
320                    }
321                } else {
322                    if opts.print_type_traceback {
323                        type_state.print_equations();
324                        println!("{}", format_trace_stack(&type_state))
325                    }
326                    None
327                }
328            }
329            ExecutableItem::EnumInstance { .. } => None,
330            ExecutableItem::StructInstance { .. } => None,
331            ExecutableItem::ExternUnit(_, _) => None,
332        })
333        .collect::<BTreeMap<_, _>>();
334
335    let name_source_map = Arc::new(RwLock::new(NameSourceMap::new()));
336    let mir_entities = spade_hir_lowering::monomorphisation::compile_items(
337        &executables_and_types,
338        &frozen_symtab,
339        &idtracker,
340        &name_source_map,
341        &item_list,
342        &opt_passes,
343        &impl_type_state,
344        &mapped_trait_impls,
345    );
346
347    let CodegenArtefacts {
348        bumpy_mir_entities,
349        flat_mir_entities,
350        module_code,
351        mir_code,
352        instance_map,
353        mir_context,
354    } = codegen(mir_entities, Arc::clone(&code), &mut errors, &idtracker);
355
356    let state = CompilerState {
357        code: code
358            .read()
359            .unwrap()
360            .dump_files()
361            .into_iter()
362            .map(|(n, s)| (n.to_string(), s.to_string()))
363            .collect(),
364        symtab: frozen_symtab,
365        idtracker,
366        impl_idtracker,
367        item_list: item_list.clone(),
368        name_source_map,
369        instance_map,
370        mir_context,
371        shared_type_state,
372        trait_impl_list: mapped_trait_impls.clone(),
373    };
374
375    let code = code.read().unwrap();
376
377    if !errors.failed() {
378        if let Some(outfile) = opts.outfile {
379            std::fs::write(outfile, module_code.join("\n\n")).or_report(&mut errors);
380        }
381        if let Some(cpp_file) = opts.verilator_wrapper_output {
382            let cpp_code =
383                verilator_wrappers(&flat_mir_entities.iter().map(|e| &e.0).collect::<Vec<_>>());
384            std::fs::write(cpp_file, cpp_code).or_report(&mut errors);
385        }
386        if let Some(mir_output) = opts.mir_output {
387            std::fs::write(mir_output, mir_code.join("\n\n")).or_report(&mut errors);
388        }
389        if let Some(item_list_file) = opts.item_list_file {
390            let list = name_dump::list_names(&item_list);
391
392            match ron::to_string(&list) {
393                Ok(encoded) => {
394                    std::fs::write(item_list_file, encoded).or_report(&mut errors);
395                }
396                Err(e) => {
397                    errors.set_failed();
398                    println!("Failed to encode item list as RON {e:?}")
399                }
400            }
401        }
402
403        let stored_state = state.into_stored();
404
405        if let Some(state_dump_file) = opts.state_dump_file {
406            if std::env::var("SPADE_REPORT_SERIALIZED_SIZE").is_ok() {
407                spade_common::sizes::SerializedSize::report_size(&stored_state);
408            }
409
410            match postcard::to_stdvec(&stored_state) {
411                Ok(encoded) => {
412                    std::fs::write(state_dump_file, encoded).or_report(&mut errors);
413                }
414                Err(e) => {
415                    errors.set_failed();
416                    println!("Failed to encode compiler state info as bincode {:?}", e)
417                }
418            }
419        }
420        let artefacts = Artefacts {
421            bumpy_mir_entities,
422            flat_mir_entities,
423            code: code.clone(),
424            item_list,
425            impl_list: mapped_trait_impls,
426            state: stored_state.into_compiler_state(),
427            type_states,
428        };
429
430        Ok(artefacts)
431    } else {
432        let artefacts = Artefacts {
433            bumpy_mir_entities,
434            flat_mir_entities,
435            code: code.clone(),
436            item_list,
437            impl_list: mapped_trait_impls,
438            state,
439            type_states,
440        };
441
442        Err(CompilationResult::LateFailure(artefacts))
443    }
444}
445
446fn do_in_namespace(
447    namespace: &ModuleNamespace,
448    ctx: &mut AstLoweringCtx,
449    to_do: &mut dyn FnMut(&mut AstLoweringCtx),
450) {
451    for segment in &namespace.namespace.0 {
452        // NOTE: These identifiers do not have the correct file_id. However,
453        // as far as I know, they will never be part of an error, so we *should*
454        // be safe.
455        ctx.symtab.push_namespace(segment.clone());
456    }
457    ctx.symtab
458        .set_base_namespace(namespace.base_namespace.clone());
459    to_do(ctx);
460    ctx.symtab.set_base_namespace(SpadePath(vec![]));
461    for _ in &namespace.namespace.0 {
462        ctx.symtab.pop_namespace();
463    }
464}
465
466#[tracing::instrument(skip_all)]
467fn parse(
468    sources: Vec<(ModuleNamespace, String, String)>,
469    code: Arc<RwLock<CodeBundle>>,
470    print_parse_traceback: bool,
471    errors: &mut ErrorHandler,
472) -> Vec<(ModuleNamespace, Loc<ModuleBody>)> {
473    let mut module_asts = vec![];
474    // Read and parse input files
475    for (namespace, name, content) in sources {
476        let _span = tracing::span!(Level::TRACE, "source", ?name).entered();
477        let file_id = code.write().unwrap().add_file(name, content.clone());
478        let mut parser = Parser::new(lexer::TokenKind::lexer(&content), file_id);
479
480        let result = parser
481            .top_level_module_body()
482            .map_err(|e| {
483                if print_parse_traceback {
484                    println!("{}", spade_parser::format_parse_stack(&parser.parse_stack));
485                };
486                e
487            })
488            .or_report(errors);
489
490        errors.drain_diag_list(&mut parser.diags);
491
492        if let Some(ast) = result {
493            module_asts.push((namespace, ast))
494        }
495    }
496
497    module_asts
498}
499
500#[tracing::instrument(skip_all)]
501fn lower_ast(
502    module_asts: &[(ModuleNamespace, Loc<ModuleBody>)],
503    ctx: &mut AstLoweringCtx,
504    errors: &mut ErrorHandler,
505) {
506    for (namespace, module_ast) in module_asts {
507        // Cannot be done by do_in_namespace because the symtab has been moved
508        // into `ctx`
509        for segment in &namespace.namespace.0 {
510            // NOTE: These identifiers do not have the correct file_id. However,
511            // as far as I know, they will never be part of an error, so we *should*
512            // be safe.
513            ctx.symtab.push_namespace(segment.clone());
514        }
515        ctx.symtab
516            .set_base_namespace(namespace.base_namespace.clone());
517        visit_module_body(module_ast, ctx).or_report(errors);
518        ctx.symtab.set_base_namespace(SpadePath(vec![]));
519        for _ in &namespace.namespace.0 {
520            ctx.symtab.pop_namespace();
521        }
522    }
523}
524
525struct CodegenArtefact {
526    bumpy_mir_entity: spade_mir::Entity,
527    flat_mir_entity: Codegenable,
528    local_module_code: String,
529    local_mir_code: String,
530    local_instance_map: InstanceMap,
531    local_mir_context: (NameID, MirContext),
532}
533
534#[tracing::instrument(skip_all)]
535fn codegen(
536    mir_entities: Vec<Result<MirOutput, Diagnostic>>,
537    code: Arc<RwLock<CodeBundle>>,
538    error_handler: &mut ErrorHandler,
539    idtracker: &Arc<ExprIdTracker>,
540) -> CodegenArtefacts {
541    let codegen_results = mir_entities
542        .into_iter()
543        .filter_map(|m| match m {
544            Ok(mir) => Some(mir),
545            Err(e) => {
546                e.or_report(error_handler);
547                None
548            }
549        })
550        .collect::<Vec<_>>()
551        .into_par_iter()
552        .enumerate()
553        .filter_map(
554            |(
555                i,
556                MirOutput {
557                    mir,
558                    type_state,
559                    reg_name_map,
560                },
561            )| {
562                // Codegen breaks if not all statements are valid, and since we don't need
563                // codegen if there are errors, we can safely bail from codegen of units with errors
564                if mir
565                    .statements
566                    .iter()
567                    .any(|stmt| matches!(stmt, spade_mir::Statement::Error))
568                {
569                    return None;
570                }
571                let bumpy_mir_entity = mir.clone();
572
573                let codegenable = prepare_codegen(mir, idtracker);
574
575                let mut local_instance_map = InstanceMap::new();
576                let code = spade_mir::codegen::entity_code(
577                    &codegenable,
578                    &mut local_instance_map,
579                    &Some(code.read().unwrap().clone()),
580                );
581
582                let flat_mir_entity = codegenable.clone();
583
584                let (code, name_map) = code;
585
586                let mir_context = (
587                    codegenable.0.name.source.clone(),
588                    MirContext {
589                        reg_name_map: reg_name_map.clone(),
590                        type_state,
591                        verilog_name_map: name_map,
592                    },
593                );
594                Some((
595                    i,
596                    CodegenArtefact {
597                        bumpy_mir_entity,
598                        flat_mir_entity,
599                        local_module_code: code.to_string(),
600                        local_mir_code: codegenable.0.to_string(),
601                        local_instance_map,
602                        local_mir_context: mir_context,
603                    },
604                ))
605            },
606        )
607        .collect::<Vec<_>>();
608
609    let mut bumpy_mir_entities = Vec::with_capacity(codegen_results.len());
610    let mut flat_mir_entities = Vec::with_capacity(codegen_results.len());
611    let mut module_code = Vec::with_capacity(codegen_results.len());
612    let mut mir_code = Vec::with_capacity(codegen_results.len());
613    let mut instance_map = InstanceMap::new();
614    let mut mir_context = HashMap::default();
615
616    for CodegenArtefact {
617        bumpy_mir_entity,
618        flat_mir_entity,
619        local_module_code,
620        local_mir_code,
621        local_instance_map,
622        local_mir_context,
623    } in codegen_results
624        .into_iter()
625        .sorted_by_key(|(k, _)| *k)
626        .map(|(_, v)| v)
627    {
628        bumpy_mir_entities.push(bumpy_mir_entity);
629        flat_mir_entities.push(flat_mir_entity);
630        module_code.push(local_module_code);
631        mir_code.push(local_mir_code);
632        instance_map
633            .inner
634            .extend(&mut local_instance_map.inner.into_iter());
635        mir_context.insert(local_mir_context.0, local_mir_context.1);
636    }
637
638    // Acts as a sanity check to catch if we ever attempt to use a wire that isn't
639    // defined, for example if a zero-sized wire is used.
640    module_code.push("`default_nettype none".into());
641
642    CodegenArtefacts {
643        bumpy_mir_entities,
644        flat_mir_entities,
645        module_code,
646        mir_code,
647        instance_map,
648        mir_context,
649    }
650}
651
652macro_rules! sources {
653    ($(($base_namespace:expr, $namespace:expr, $filename:expr)),*$(,)?) => {
654        vec! [
655            $(
656                (
657                    ModuleNamespace {
658                        namespace: SpadePath::from_strs(&$namespace),
659                        base_namespace: SpadePath::from_strs(&$base_namespace),
660                        file: String::from($filename).replace("../", "<compiler dir>/")
661                    },
662                    String::from($filename).replace("../", "<compiler dir>/"),
663                    String::from(include_str!($filename))
664                )
665            ),*
666        ]
667    }
668}
669
670pub fn core_files() -> Vec<(ModuleNamespace, String, String)> {
671    sources! {
672        ([], [], "../core/core.spade"),
673    }
674}
675
676/// The spade source files which are included statically in the binary, rather
677/// than being passed on the command line. This includes the stdlib and prelude
678pub fn stdlib_and_prelude() -> Vec<(ModuleNamespace, String, String)> {
679    sources! {
680        ([], [], "../prelude/prelude.spade"),
681
682        (["std"], ["std"], "../stdlib/main.spade"),
683        (["std"], ["std", "array"], "../stdlib/array.spade"),
684        (["std"], ["std", "cdc"], "../stdlib/cdc.spade"),
685        (["std"], ["std", "conv"], "../stdlib/conv.spade"),
686        (["std"], ["std", "io"], "../stdlib/io.spade"),
687        (["std"], ["std", "mem"], "../stdlib/mem.spade"),
688        (["std"], ["std", "ops"], "../stdlib/ops.spade"),
689        (["std"], ["std", "option"], "../stdlib/option.spade"),
690        (["std"], ["std", "ports"], "../stdlib/ports.spade"),
691        (["std"], ["std", "undef"], "../stdlib/undef.spade"),
692    }
693}
694
695#[cfg(test)]
696mod tests {
697    use std::path::PathBuf;
698
699    /// Having to maintain the stdlib list is error prone, so having a test
700    /// here to verify that all files in stdlib/<file>.spade
701    #[test]
702    fn sanity_check_static_sources_stdlib_included() {
703        let included = super::stdlib_and_prelude()
704            .into_iter()
705            .filter_map(|(ns, file, _)| {
706                if ns.base_namespace.to_named_strs() == [Some("std")] {
707                    Some(
708                        PathBuf::from(file)
709                            .file_name()
710                            .map(|f| f.to_string_lossy().to_string()),
711                    )
712                } else {
713                    None
714                }
715            })
716            .collect::<Vec<_>>();
717
718        let missing_files = std::fs::read_dir("stdlib/")
719            .expect("Failed to read stdlib")
720            .into_iter()
721            .map(|f| {
722                f.unwrap()
723                    .path()
724                    .file_name()
725                    .map(|f| f.to_string_lossy().to_string())
726            })
727            .filter(|f| !included.contains(f))
728            .collect::<Vec<_>>();
729
730        assert_eq!(missing_files, vec![])
731    }
732}