Skip to main content

vm/compiler/
pipeline.rs

1use std::collections::HashMap;
2use std::path::{Path, PathBuf};
3
4use crate::HostImport;
5
6use super::codegen::Compiler;
7use super::frontends;
8use super::ir::{Expr, FrontendIr, FunctionDecl, FunctionImpl, LocalSlot, Stmt, TypeSchema};
9use super::linker::merge_units;
10use super::source_loader::load_units_for_source_file;
11use super::source_map::SourceMap;
12use super::{
13    CompileError, CompileSourceFileOptions, CompiledProgram, CompiledReplProgram, ParseError,
14    ReplLocalBinding, SourceError, SourceFlavor, SourcePathError, TypingMode, lifetime, parser,
15    typing,
16};
17
18#[derive(Clone, Copy, Debug, Default)]
19pub(super) struct LocalDebugRange {
20    pub(super) declared_line: Option<u32>,
21    pub(super) last_line: Option<u32>,
22}
23
24#[derive(Clone, Debug, PartialEq, Eq)]
25pub struct UnknownInferredLocal {
26    pub name: String,
27    pub line: usize,
28    pub span: Option<crate::compiler::source_map::Span>,
29}
30
31#[derive(Clone, Debug, PartialEq, Eq)]
32pub struct InferredLocalTypeHint {
33    pub name: String,
34    pub inferred_type: String,
35    pub declared_line: Option<u32>,
36    pub last_line: Option<u32>,
37}
38
39#[derive(Clone, Copy, Debug)]
40struct CompileBehavior {
41    clear_dead_locals: bool,
42}
43
44impl CompileBehavior {
45    const DEFAULT: Self = Self {
46        clear_dead_locals: true,
47    };
48    const REPL: Self = Self {
49        clear_dead_locals: false,
50    };
51}
52
53fn collect_named_local_debug_ranges(parsed: &FrontendIr) -> HashMap<String, LocalDebugRange> {
54    let slot_ranges = collect_local_debug_ranges(&parsed.stmts, &parsed.function_impls);
55    let mut named_ranges = HashMap::<String, LocalDebugRange>::new();
56    for (name, slot) in &parsed.local_bindings {
57        let Some(range) = slot_ranges.get(slot).copied() else {
58            continue;
59        };
60        let entry = named_ranges.entry(name.clone()).or_default();
61        entry.declared_line = merge_min_debug_line(entry.declared_line, range.declared_line);
62        entry.last_line = merge_max_debug_line(entry.last_line, range.last_line);
63    }
64    named_ranges
65}
66
67fn collect_local_debug_ranges(
68    stmts: &[Stmt],
69    function_impls: &HashMap<u16, FunctionImpl>,
70) -> HashMap<LocalSlot, LocalDebugRange> {
71    let mut ranges = HashMap::<LocalSlot, LocalDebugRange>::new();
72    for stmt in stmts {
73        record_stmt_local_debug_ranges(stmt, &mut ranges);
74    }
75    for function_impl in function_impls.values() {
76        for stmt in &function_impl.body_stmts {
77            record_stmt_local_debug_ranges(stmt, &mut ranges);
78        }
79        let fallback_line = function_impl
80            .body_stmts
81            .last()
82            .map(stmt_source_line)
83            .unwrap_or(1);
84        let body_expr_line = if function_impl.body_expr_line > 0 {
85            function_impl.body_expr_line
86        } else {
87            fallback_line
88        };
89        record_expr_local_debug_ranges(&function_impl.body_expr, body_expr_line, &mut ranges);
90    }
91    ranges
92}
93
94fn record_stmt_local_debug_ranges(stmt: &Stmt, ranges: &mut HashMap<LocalSlot, LocalDebugRange>) {
95    match stmt {
96        Stmt::Noop { .. } | Stmt::FuncDecl { .. } | Stmt::Break { .. } | Stmt::Continue { .. } => {}
97        Stmt::Drop { index, line } => {
98            note_local_use(ranges, *index, *line);
99        }
100        Stmt::Let {
101            index, expr, line, ..
102        } => {
103            note_local_decl(ranges, *index, *line);
104            record_expr_local_debug_ranges(expr, *line, ranges);
105        }
106        Stmt::Assign {
107            index, expr, line, ..
108        } => {
109            note_local_use(ranges, *index, *line);
110            record_expr_local_debug_ranges(expr, *line, ranges);
111        }
112        Stmt::ClosureLet { line, closure } => {
113            for (source_slot, captured_slot) in &closure.capture_copies {
114                note_local_use(ranges, *source_slot, *line);
115                note_local_use(ranges, *captured_slot, *line);
116            }
117            record_expr_local_debug_ranges(&closure.body, *line, ranges);
118        }
119        Stmt::Expr { expr, line } => {
120            record_expr_local_debug_ranges(expr, *line, ranges);
121        }
122        Stmt::IfElse {
123            condition,
124            then_branch,
125            else_branch,
126            line,
127        } => {
128            record_expr_local_debug_ranges(condition, *line, ranges);
129            for nested in then_branch {
130                record_stmt_local_debug_ranges(nested, ranges);
131            }
132            for nested in else_branch {
133                record_stmt_local_debug_ranges(nested, ranges);
134            }
135        }
136        Stmt::For {
137            init,
138            condition,
139            post,
140            body,
141            line,
142        } => {
143            record_stmt_local_debug_ranges(init, ranges);
144            record_expr_local_debug_ranges(condition, *line, ranges);
145            record_stmt_local_debug_ranges(post, ranges);
146            for nested in body {
147                record_stmt_local_debug_ranges(nested, ranges);
148            }
149        }
150        Stmt::While {
151            condition,
152            body,
153            line,
154        } => {
155            record_expr_local_debug_ranges(condition, *line, ranges);
156            for nested in body {
157                record_stmt_local_debug_ranges(nested, ranges);
158            }
159        }
160    }
161}
162
163fn record_expr_local_debug_ranges(
164    expr: &Expr,
165    line: u32,
166    ranges: &mut HashMap<LocalSlot, LocalDebugRange>,
167) {
168    match expr {
169        Expr::Null
170        | Expr::Int(_)
171        | Expr::Float(_)
172        | Expr::Bool(_)
173        | Expr::Bytes(_)
174        | Expr::String(_)
175        | Expr::FunctionRef(_) => {}
176        Expr::Var(index) | Expr::MoveVar(index) => {
177            note_local_use(ranges, *index, line);
178        }
179        Expr::MoveField { root, .. } | Expr::MoveIndex { root, .. } => {
180            note_local_use(ranges, *root, line);
181        }
182        Expr::OptionalGet {
183            container,
184            key,
185            container_slot,
186            key_slot,
187        } => {
188            note_local_use(ranges, *container_slot, line);
189            note_local_use(ranges, *key_slot, line);
190            record_expr_local_debug_ranges(container, line, ranges);
191            record_expr_local_debug_ranges(key, line, ranges);
192        }
193        Expr::OptionUnwrapOr {
194            value,
195            value_slot,
196            fallback,
197        } => {
198            note_local_use(ranges, *value_slot, line);
199            record_expr_local_debug_ranges(value, line, ranges);
200            record_expr_local_debug_ranges(fallback, line, ranges);
201        }
202        Expr::Call(_, _, args) => {
203            for arg in args {
204                record_expr_local_debug_ranges(arg, line, ranges);
205            }
206        }
207        Expr::LocalCall(index, _, args) => {
208            note_local_use(ranges, *index, line);
209            for arg in args {
210                record_expr_local_debug_ranges(arg, line, ranges);
211            }
212        }
213        Expr::Closure(closure) => {
214            for (source_slot, captured_slot) in &closure.capture_copies {
215                note_local_use(ranges, *source_slot, line);
216                note_local_use(ranges, *captured_slot, line);
217            }
218            record_expr_local_debug_ranges(&closure.body, line, ranges);
219        }
220        Expr::ClosureCall(closure, args) => {
221            for arg in args {
222                record_expr_local_debug_ranges(arg, line, ranges);
223            }
224            for (source_slot, captured_slot) in &closure.capture_copies {
225                note_local_use(ranges, *source_slot, line);
226                note_local_use(ranges, *captured_slot, line);
227            }
228            record_expr_local_debug_ranges(&closure.body, line, ranges);
229        }
230        Expr::Add(lhs, rhs)
231        | Expr::Sub(lhs, rhs)
232        | Expr::Mul(lhs, rhs)
233        | Expr::Div(lhs, rhs)
234        | Expr::Mod(lhs, rhs)
235        | Expr::And(lhs, rhs)
236        | Expr::Or(lhs, rhs)
237        | Expr::Eq(lhs, rhs)
238        | Expr::Lt(lhs, rhs)
239        | Expr::Gt(lhs, rhs) => {
240            record_expr_local_debug_ranges(lhs, line, ranges);
241            record_expr_local_debug_ranges(rhs, line, ranges);
242        }
243        Expr::Neg(inner)
244        | Expr::Not(inner)
245        | Expr::ToOwned(inner)
246        | Expr::Borrow(inner)
247        | Expr::BorrowMut(inner) => {
248            record_expr_local_debug_ranges(inner, line, ranges);
249        }
250        Expr::IfElse {
251            condition,
252            then_expr,
253            else_expr,
254        } => {
255            record_expr_local_debug_ranges(condition, line, ranges);
256            record_expr_local_debug_ranges(then_expr, line, ranges);
257            record_expr_local_debug_ranges(else_expr, line, ranges);
258        }
259        Expr::Match {
260            value_slot,
261            result_slot,
262            value,
263            arms,
264            default,
265        } => {
266            note_local_use(ranges, *value_slot, line);
267            note_local_use(ranges, *result_slot, line);
268            record_expr_local_debug_ranges(value, line, ranges);
269            for (pattern, arm_expr) in arms {
270                if let Some(binding_slot) = pattern.binding_slot() {
271                    note_local_use(ranges, binding_slot, line);
272                }
273                record_expr_local_debug_ranges(arm_expr, line, ranges);
274            }
275            record_expr_local_debug_ranges(default, line, ranges);
276        }
277        Expr::Block { stmts, expr } => {
278            for stmt in stmts {
279                record_stmt_local_debug_ranges(stmt, ranges);
280            }
281            record_expr_local_debug_ranges(expr, line, ranges);
282        }
283    }
284}
285
286fn note_local_decl(ranges: &mut HashMap<LocalSlot, LocalDebugRange>, slot: LocalSlot, line: u32) {
287    let entry = ranges.entry(slot).or_default();
288    entry.declared_line = Some(
289        entry
290            .declared_line
291            .map_or(line, |current| current.min(line)),
292    );
293    entry.last_line = Some(entry.last_line.map_or(line, |current| current.max(line)));
294}
295
296fn note_local_use(ranges: &mut HashMap<LocalSlot, LocalDebugRange>, slot: LocalSlot, line: u32) {
297    let entry = ranges.entry(slot).or_default();
298    entry.last_line = Some(entry.last_line.map_or(line, |current| current.max(line)));
299}
300
301fn merge_min_debug_line(current: Option<u32>, incoming: Option<u32>) -> Option<u32> {
302    match (current, incoming) {
303        (Some(lhs), Some(rhs)) => Some(lhs.min(rhs)),
304        (Some(lhs), None) => Some(lhs),
305        (None, Some(rhs)) => Some(rhs),
306        (None, None) => None,
307    }
308}
309
310fn merge_max_debug_line(current: Option<u32>, incoming: Option<u32>) -> Option<u32> {
311    match (current, incoming) {
312        (Some(lhs), Some(rhs)) => Some(lhs.max(rhs)),
313        (Some(lhs), None) => Some(lhs),
314        (None, Some(rhs)) => Some(rhs),
315        (None, None) => None,
316    }
317}
318
319fn stmt_source_line(stmt: &Stmt) -> u32 {
320    match stmt {
321        Stmt::Noop { line }
322        | Stmt::Let { line, .. }
323        | Stmt::Assign { line, .. }
324        | Stmt::ClosureLet { line, .. }
325        | Stmt::FuncDecl { line, .. }
326        | Stmt::Expr { line, .. }
327        | Stmt::IfElse { line, .. }
328        | Stmt::For { line, .. }
329        | Stmt::While { line, .. }
330        | Stmt::Break { line }
331        | Stmt::Continue { line }
332        | Stmt::Drop { line, .. } => *line,
333    }
334}
335
336fn is_compiler_primitive_import(name: &str) -> bool {
337    name.starts_with("__prim_")
338}
339
340fn compile_parsed_output(
341    source: String,
342    parsed: FrontendIr,
343    behavior: CompileBehavior,
344    typing_mode: TypingMode,
345    enable_local_move_semantics: bool,
346) -> Result<CompiledProgram, SourceError> {
347    compile_parsed_output_with_entry_locals(
348        source,
349        parsed,
350        &[],
351        &[],
352        behavior,
353        typing_mode,
354        enable_local_move_semantics,
355    )
356}
357
358fn compile_parsed_output_with_entry_locals(
359    source: String,
360    parsed: FrontendIr,
361    entry_definite_locals: &[LocalSlot],
362    entry_local_types: &[typing::EntryLocalType],
363    behavior: CompileBehavior,
364    typing_mode: TypingMode,
365    enable_local_move_semantics: bool,
366) -> Result<CompiledProgram, SourceError> {
367    // Normal compilation passes no entry locals. The REPL uses this hook to treat
368    // carried-over locals from prior entries as already available at snippet start.
369    if typing_mode.is_strict() {
370        reject_strict_unknown_annotations(&parsed).map_err(SourceError::Parse)?;
371    }
372    let local_debug_ranges = collect_named_local_debug_ranges(&parsed);
373    let parsed = typing::legalize_builtins_and_bind_types(parsed, typing_mode, entry_local_types);
374    typing::validate_if_else_type_consistency(&parsed, typing_mode, entry_local_types)
375        .map_err(SourceError::Compile)?;
376    if typing_mode.is_strict() {
377        let strict_type_info = typing::infer_types(&parsed, typing_mode, entry_local_types);
378        enforce_strict_rustscript_type_resolution(&parsed, &strict_type_info)
379            .map_err(SourceError::Compile)?;
380    }
381    let parsed = lifetime::enforce_local_availability_with_entry_locals(
382        parsed,
383        entry_definite_locals,
384        behavior.clear_dead_locals,
385        enable_local_move_semantics,
386    )
387    .map_err(SourceError::Parse)?;
388    let type_info = typing::infer_types(&parsed, typing_mode, entry_local_types);
389    let FrontendIr {
390        stmts,
391        locals,
392        local_bindings,
393        struct_schemas,
394        functions,
395        function_impls,
396        ..
397    } = parsed;
398    let function_decls = functions
399        .iter()
400        .cloned()
401        .map(|decl| (decl.index, decl))
402        .collect::<HashMap<_, _>>();
403
404    let mut runtime_import_functions: Vec<FunctionDecl> = functions
405        .iter()
406        .filter(|func| !function_impls.contains_key(&func.index))
407        .cloned()
408        .collect();
409    let mut call_index_remap = HashMap::<u16, u16>::new();
410    for (next_index, func) in runtime_import_functions.iter_mut().enumerate() {
411        let next_index = u16::try_from(next_index).map_err(|_| {
412            SourceError::Parse(ParseError {
413                span: None,
414                code: None,
415                line: 1,
416                message: "too many host imports after RSS function inlining".to_string(),
417            })
418        })?;
419        call_index_remap.insert(func.index, next_index);
420        func.index = next_index;
421    }
422    let visible_runtime_import_functions = runtime_import_functions
423        .iter()
424        .filter(|func| !is_compiler_primitive_import(&func.name))
425        .cloned()
426        .collect::<Vec<_>>();
427    let host_import_return_types = functions
428        .iter()
429        .filter(|func| !function_impls.contains_key(&func.index))
430        .map(|func| (func.index, typing::BoundType::from(func.return_type)))
431        .collect::<HashMap<_, _>>();
432    let host_import_signatures = typing::build_host_import_signatures(&functions, &function_impls);
433
434    let mut compiler = Compiler::new();
435    compiler.set_type_inference(type_info);
436    compiler.set_typing_mode(typing_mode);
437    compiler.set_source(source);
438    compiler.set_function_decls(function_decls);
439    compiler.set_function_impls(function_impls);
440    compiler.set_struct_schemas(struct_schemas);
441    compiler.set_host_import_return_types(host_import_return_types);
442    compiler.set_host_import_signatures(host_import_signatures);
443    compiler.set_call_index_remap(call_index_remap);
444    compiler.set_enable_local_move_semantics(enable_local_move_semantics);
445    for func in &functions {
446        compiler.add_function_debug(func);
447    }
448    for (name, index) in local_bindings {
449        let range = local_debug_ranges.get(&name).copied().unwrap_or_default();
450        compiler
451            .add_local_debug(name, index, range.declared_line, range.last_line)
452            .map_err(SourceError::Compile)?;
453    }
454    let mut program = compiler
455        .compile_program(&stmts)
456        .map_err(SourceError::Compile)?;
457    program.local_count = locals;
458    program.imports = runtime_import_functions
459        .iter()
460        .map(|func| HostImport {
461            name: func.name.clone(),
462            arity: func.arity,
463            return_type: func.return_type,
464        })
465        .collect();
466    Ok(CompiledProgram {
467        program,
468        locals,
469        functions: visible_runtime_import_functions,
470    })
471}
472
473#[derive(Clone, Debug)]
474struct StrictSlotSite {
475    name: String,
476    kind: &'static str,
477    line: Option<u32>,
478    source_name: Option<String>,
479}
480
481fn reject_strict_unknown_annotations(parsed: &FrontendIr) -> Result<(), ParseError> {
482    let Some(span) = parsed.unknown_type_spans.first().copied() else {
483        return Ok(());
484    };
485    Err(ParseError {
486        line: 1,
487        message:
488            "RustScript requires concrete compile-time types; 'unknown' annotations are not allowed"
489                .to_string(),
490        span: Some(span),
491        code: Some("E_STRICT_UNKNOWN_TYPE".to_string()),
492    })
493}
494
495fn enforce_strict_rustscript_type_resolution(
496    parsed: &FrontendIr,
497    type_info: &typing::TypeInferenceResult,
498) -> Result<(), CompileError> {
499    for schema in parsed.struct_schemas.values() {
500        if schema_is_fully_known(&schema.body_schema) {
501            continue;
502        }
503        return Err(CompileError::StrictTypingRequired {
504            line: None,
505            source_name: None,
506            detail: format!(
507                "struct '{}' contains non-concrete field types; RustScript requires concrete schemas",
508                schema.name
509            ),
510        });
511    }
512
513    let function_decl_lines = collect_function_decl_lines(&parsed.stmts);
514    for decl in &parsed.functions {
515        if let Some(schema) = decl.return_schema.as_ref()
516            && !schema_is_fully_known(schema)
517        {
518            return Err(CompileError::StrictTypingRequired {
519                line: function_decl_lines.get(&decl.index).copied(),
520                source_name: parsed.function_sources.get(&decl.index).cloned(),
521                detail: format!(
522                    "function '{}' uses a non-concrete return schema; RustScript requires concrete return types",
523                    decl.name
524                ),
525            });
526        }
527    }
528
529    for (slot, site) in collect_strict_slot_sites(parsed) {
530        if slot_is_fully_typed(slot, type_info) {
531            continue;
532        }
533        return Err(CompileError::StrictTypingRequired {
534            line: site.line,
535            source_name: site.source_name,
536            detail: format!(
537                "{} '{}' does not resolve to a concrete compile-time type in RustScript",
538                site.kind, site.name
539            ),
540        });
541    }
542
543    Ok(())
544}
545
546fn slot_is_fully_typed(slot: LocalSlot, type_info: &typing::TypeInferenceResult) -> bool {
547    let slot_index = usize::from(slot);
548    if type_info
549        .callable_slots
550        .get(slot_index)
551        .copied()
552        .unwrap_or(false)
553    {
554        return true;
555    }
556    if let Some(schema) = type_info
557        .local_schemas
558        .get(slot_index)
559        .and_then(|schema| schema.as_ref())
560    {
561        return schema_is_fully_known(schema);
562    }
563    type_info.local_types.get(slot_index).copied() != Some(crate::ValueType::Unknown)
564}
565
566fn schema_is_fully_known(schema: &TypeSchema) -> bool {
567    match schema {
568        TypeSchema::Unknown => false,
569        TypeSchema::Null
570        | TypeSchema::Int
571        | TypeSchema::Float
572        | TypeSchema::Number
573        | TypeSchema::Bool
574        | TypeSchema::String
575        | TypeSchema::Bytes
576        | TypeSchema::GenericParam(_) => true,
577        TypeSchema::Optional(inner) => schema_is_fully_known(inner),
578        TypeSchema::Named(_, type_args) => type_args.iter().all(schema_is_fully_known),
579        TypeSchema::Array(item) | TypeSchema::Map(item) => schema_is_fully_known(item),
580        TypeSchema::ArrayTuple(items) => items.iter().all(schema_is_fully_known),
581        TypeSchema::ArrayTupleRest { prefix, rest } => {
582            prefix.iter().all(schema_is_fully_known) && schema_is_fully_known(rest)
583        }
584        TypeSchema::Object(fields) => fields.values().all(schema_is_fully_known),
585        TypeSchema::Callable { params, result } => {
586            params.iter().all(schema_is_fully_known) && schema_is_fully_known(result)
587        }
588    }
589}
590
591fn collect_strict_slot_sites(parsed: &FrontendIr) -> Vec<(LocalSlot, StrictSlotSite)> {
592    let mut sites = Vec::new();
593    let local_debug_ranges = collect_local_debug_ranges(&parsed.stmts, &parsed.function_impls);
594    let local_source_names = collect_local_source_names(parsed);
595    for (name, slot) in &parsed.local_bindings {
596        let line = local_debug_ranges
597            .get(slot)
598            .and_then(|range| range.declared_line);
599        sites.push((
600            *slot,
601            StrictSlotSite {
602                name: name.clone(),
603                kind: "local",
604                line,
605                source_name: local_source_names.get(slot).cloned().flatten(),
606            },
607        ));
608    }
609
610    let function_decl_lines = collect_function_decl_lines(&parsed.stmts);
611    for decl in &parsed.functions {
612        let Some(function_impl) = parsed.function_impls.get(&decl.index) else {
613            continue;
614        };
615        for (name, slot) in decl.args.iter().zip(function_impl.param_slots.iter()) {
616            sites.push((
617                *slot,
618                StrictSlotSite {
619                    name: name.clone(),
620                    kind: "parameter",
621                    line: function_decl_lines.get(&decl.index).copied(),
622                    source_name: parsed.function_sources.get(&decl.index).cloned(),
623                },
624            ));
625        }
626    }
627
628    sites.sort_by_key(|(slot, _)| *slot);
629    sites
630}
631
632fn collect_local_source_names(parsed: &FrontendIr) -> HashMap<LocalSlot, Option<String>> {
633    let mut out = HashMap::new();
634    for (index, stmt) in parsed.stmts.iter().enumerate() {
635        let source_name = parsed
636            .stmt_sources
637            .get(index)
638            .and_then(|source| source.as_deref());
639        record_local_source_names(std::slice::from_ref(stmt), source_name, &mut out);
640    }
641    for decl in &parsed.functions {
642        let Some(function_impl) = parsed.function_impls.get(&decl.index) else {
643            continue;
644        };
645        let source_name = parsed.function_sources.get(&decl.index).map(String::as_str);
646        record_local_source_names(&function_impl.body_stmts, source_name, &mut out);
647    }
648    out
649}
650
651fn record_local_source_names(
652    stmts: &[Stmt],
653    source_name: Option<&str>,
654    out: &mut HashMap<LocalSlot, Option<String>>,
655) {
656    let source_name = source_name.map(str::to_string);
657    for stmt in stmts {
658        match stmt {
659            Stmt::Let { index, .. } => {
660                out.entry(*index).or_insert_with(|| source_name.clone());
661            }
662            Stmt::IfElse {
663                then_branch,
664                else_branch,
665                ..
666            } => {
667                record_local_source_names(then_branch, source_name.as_deref(), out);
668                record_local_source_names(else_branch, source_name.as_deref(), out);
669            }
670            Stmt::For {
671                init, post, body, ..
672            } => {
673                record_local_source_names(
674                    std::slice::from_ref(init.as_ref()),
675                    source_name.as_deref(),
676                    out,
677                );
678                record_local_source_names(
679                    std::slice::from_ref(post.as_ref()),
680                    source_name.as_deref(),
681                    out,
682                );
683                record_local_source_names(body, source_name.as_deref(), out);
684            }
685            Stmt::While { body, .. } => {
686                record_local_source_names(body, source_name.as_deref(), out);
687            }
688            Stmt::Noop { .. }
689            | Stmt::Assign { .. }
690            | Stmt::ClosureLet { .. }
691            | Stmt::FuncDecl { .. }
692            | Stmt::Expr { .. }
693            | Stmt::Break { .. }
694            | Stmt::Continue { .. }
695            | Stmt::Drop { .. } => {}
696        }
697    }
698}
699
700pub fn compile_source(source: &str) -> Result<CompiledProgram, SourceError> {
701    compile_source_with_flavor(source, SourceFlavor::RustScript)
702}
703
704pub fn lint_trailing_function_return_semicolons(
705    source: &str,
706    flavor: SourceFlavor,
707) -> Result<Vec<ParseError>, ParseError> {
708    let Some(dialect) =
709        frontends::parser_dialect_for_flavor(flavor, &CompileSourceFileOptions::default())
710    else {
711        return Ok(Vec::new());
712    };
713    parser::lint_trailing_function_return_semicolons(source, 0, dialect)
714}
715
716pub fn lint_unknown_type_annotations(
717    source: &str,
718    flavor: SourceFlavor,
719) -> Result<Vec<crate::compiler::source_map::Span>, SourceError> {
720    let mut source_map = SourceMap::new();
721    let source_id = source_map.add_source("<source>", source.to_string());
722    let parsed = frontends::parse_source(source, flavor, &CompileSourceFileOptions::default())
723        .map_err(|err| {
724            SourceError::Parse(err.with_line_span_from_source(&source_map, source_id))
725        })?;
726    Ok(parsed.unknown_type_spans)
727}
728
729pub fn lint_unknown_inferred_local_types(
730    source: &str,
731    flavor: SourceFlavor,
732) -> Result<Vec<UnknownInferredLocal>, SourceError> {
733    lint_unknown_inferred_local_types_impl(source, flavor)
734}
735
736pub fn collect_inferred_local_type_hints(
737    source: &str,
738    flavor: SourceFlavor,
739) -> Result<Vec<InferredLocalTypeHint>, SourceError> {
740    collect_inferred_local_type_hints_impl(source, flavor)
741}
742
743pub fn collect_inferred_local_type_hints_with_options(
744    source: &str,
745    flavor: SourceFlavor,
746    options: CompileSourceFileOptions,
747) -> Result<Vec<InferredLocalTypeHint>, SourcePathError> {
748    let source_owned = source.to_string();
749    run_with_compiler_stack(move || {
750        collect_inferred_local_type_hints_with_options_impl(&source_owned, flavor, &options)
751    })
752}
753
754pub fn collect_inferred_local_type_hints_at_path_with_options(
755    path: impl AsRef<Path>,
756    source: &str,
757    flavor: SourceFlavor,
758    options: CompileSourceFileOptions,
759) -> Result<Vec<InferredLocalTypeHint>, SourcePathError> {
760    let path = path.as_ref().to_path_buf();
761    let source_owned = source.to_string();
762    run_with_compiler_stack(move || {
763        collect_inferred_local_type_hints_at_path_with_options_impl(
764            &path,
765            &source_owned,
766            flavor,
767            &options,
768        )
769    })
770}
771
772pub fn lint_unknown_inferred_local_types_with_options(
773    source: &str,
774    flavor: SourceFlavor,
775    options: CompileSourceFileOptions,
776) -> Result<Vec<UnknownInferredLocal>, SourcePathError> {
777    let source_owned = source.to_string();
778    run_with_compiler_stack(move || {
779        lint_unknown_inferred_local_types_with_options_impl(&source_owned, flavor, &options)
780    })
781}
782
783pub fn lint_unknown_inferred_local_types_at_path_with_options(
784    path: impl AsRef<Path>,
785    source: &str,
786    flavor: SourceFlavor,
787    options: CompileSourceFileOptions,
788) -> Result<Vec<UnknownInferredLocal>, SourcePathError> {
789    let path = path.as_ref().to_path_buf();
790    let source_owned = source.to_string();
791    run_with_compiler_stack(move || {
792        lint_unknown_inferred_local_types_at_path_with_options_impl(
793            &path,
794            &source_owned,
795            flavor,
796            &options,
797        )
798    })
799}
800
801fn lint_unknown_inferred_local_types_impl(
802    source: &str,
803    flavor: SourceFlavor,
804) -> Result<Vec<UnknownInferredLocal>, SourceError> {
805    let mut source_map = SourceMap::new();
806    let source_id = source_map.add_source("<source>", source.to_string());
807    let parsed = frontends::parse_source(source, flavor, &CompileSourceFileOptions::default())
808        .map_err(|err| {
809            SourceError::Parse(err.with_line_span_from_source(&source_map, source_id))
810        })?;
811    Ok(collect_unknown_inferred_local_types(
812        &source_map,
813        source_id,
814        parsed,
815    ))
816}
817
818fn collect_inferred_local_type_hints_impl(
819    source: &str,
820    flavor: SourceFlavor,
821) -> Result<Vec<InferredLocalTypeHint>, SourceError> {
822    let mut source_map = SourceMap::new();
823    let source_id = source_map.add_source("<source>", source.to_string());
824    let parsed = frontends::parse_source(source, flavor, &CompileSourceFileOptions::default())
825        .map_err(|err| {
826            SourceError::Parse(err.with_line_span_from_source(&source_map, source_id))
827        })?;
828    Ok(collect_named_local_type_hints(parsed))
829}
830
831fn lint_unknown_inferred_local_types_with_options_impl(
832    source: &str,
833    flavor: SourceFlavor,
834    options: &CompileSourceFileOptions,
835) -> Result<Vec<UnknownInferredLocal>, SourcePathError> {
836    if !options.has_module_overrides() && !options.has_source_plugins() {
837        return lint_unknown_inferred_local_types_impl(source, flavor)
838            .map_err(SourcePathError::Source);
839    }
840
841    let path = virtual_inmemory_entry_path(flavor);
842    lint_unknown_inferred_local_types_at_path_with_options_impl(&path, source, flavor, options)
843}
844
845fn collect_inferred_local_type_hints_with_options_impl(
846    source: &str,
847    flavor: SourceFlavor,
848    options: &CompileSourceFileOptions,
849) -> Result<Vec<InferredLocalTypeHint>, SourcePathError> {
850    if !options.has_module_overrides() && !options.has_source_plugins() {
851        return collect_inferred_local_type_hints_impl(source, flavor)
852            .map_err(SourcePathError::Source);
853    }
854
855    let path = virtual_inmemory_entry_path(flavor);
856    collect_inferred_local_type_hints_at_path_with_options_impl(&path, source, flavor, options)
857}
858
859fn lint_unknown_inferred_local_types_at_path_with_options_impl(
860    path: &Path,
861    source: &str,
862    flavor: SourceFlavor,
863    options: &CompileSourceFileOptions,
864) -> Result<Vec<UnknownInferredLocal>, SourcePathError> {
865    let mut source_map = SourceMap::new();
866    let source_id = source_map.add_source(path.display().to_string(), source.to_string());
867    let (_root_parse_source, units) = load_units_for_source_file(path, flavor, source, options)?;
868    let parsed = units
869        .into_iter()
870        .last()
871        .map(|unit| unit.parsed)
872        .expect("root parsed unit should always be present");
873    Ok(collect_unknown_inferred_local_types(
874        &source_map,
875        source_id,
876        parsed,
877    ))
878}
879
880fn collect_inferred_local_type_hints_at_path_with_options_impl(
881    path: &Path,
882    source: &str,
883    flavor: SourceFlavor,
884    options: &CompileSourceFileOptions,
885) -> Result<Vec<InferredLocalTypeHint>, SourcePathError> {
886    let (_root_parse_source, units) = load_units_for_source_file(path, flavor, source, options)?;
887    let parsed = units
888        .into_iter()
889        .last()
890        .map(|unit| unit.parsed)
891        .expect("root parsed unit should always be present");
892    Ok(collect_named_local_type_hints(parsed))
893}
894
895fn collect_unknown_inferred_local_types(
896    source_map: &SourceMap,
897    source_id: u32,
898    parsed: FrontendIr,
899) -> Vec<UnknownInferredLocal> {
900    let local_debug_ranges = collect_local_debug_ranges(&parsed.stmts, &parsed.function_impls);
901    let parsed = typing::legalize_builtins_and_bind_types(parsed, TypingMode::DynamicHints, &[]);
902    let type_info = typing::infer_types(&parsed, TypingMode::DynamicHints, &[]);
903
904    let mut warnings = Vec::new();
905    for (name, slot) in &parsed.local_bindings {
906        let Some(range) = local_debug_ranges.get(slot) else {
907            continue;
908        };
909        let Some(line_u32) = range.declared_line else {
910            continue;
911        };
912        let slot_index = usize::from(*slot);
913        if type_info
914            .callable_slots
915            .get(slot_index)
916            .copied()
917            .unwrap_or(false)
918        {
919            continue;
920        }
921        if type_info
922            .local_schema_labels
923            .get(slot_index)
924            .and_then(|label| label.as_ref())
925            .is_some_and(|label| label != "unknown")
926        {
927            continue;
928        }
929        if type_info.local_types.get(slot_index) != Some(&crate::ValueType::Unknown) {
930            continue;
931        }
932        let line = usize::try_from(line_u32).unwrap_or(usize::MAX);
933        warnings.push(UnknownInferredLocal {
934            name: name.clone(),
935            line,
936            span: find_local_name_span(source_map, source_id, line, name)
937                .or_else(|| source_map.line_span(source_id, line)),
938        });
939    }
940    warnings
941}
942
943fn collect_named_local_type_hints(parsed: FrontendIr) -> Vec<InferredLocalTypeHint> {
944    let slot_ranges = collect_local_debug_ranges(&parsed.stmts, &parsed.function_impls);
945    let function_decl_lines = collect_function_decl_lines(&parsed.stmts);
946    let parsed = typing::legalize_builtins_and_bind_types(parsed, TypingMode::DynamicHints, &[]);
947    let type_info = typing::infer_types(&parsed, TypingMode::DynamicHints, &[]);
948
949    let mut hints = Vec::new();
950    for (name, slot) in &parsed.local_bindings {
951        hints.push(InferredLocalTypeHint {
952            name: name.clone(),
953            inferred_type: inferred_slot_type_name(&type_info, *slot),
954            declared_line: slot_ranges.get(slot).and_then(|range| range.declared_line),
955            last_line: slot_ranges.get(slot).and_then(|range| range.last_line),
956        });
957    }
958
959    for decl in &parsed.functions {
960        let Some(function_impl) = parsed.function_impls.get(&decl.index) else {
961            continue;
962        };
963        let declared_line = function_decl_lines.get(&decl.index).copied();
964        let last_line = function_scope_last_line(function_impl).or(declared_line);
965        for (name, slot) in decl.args.iter().zip(function_impl.param_slots.iter()) {
966            hints.push(InferredLocalTypeHint {
967                name: name.clone(),
968                inferred_type: inferred_slot_type_name(&type_info, *slot),
969                declared_line: slot_ranges
970                    .get(slot)
971                    .and_then(|range| range.declared_line)
972                    .or(declared_line),
973                last_line: slot_ranges
974                    .get(slot)
975                    .and_then(|range| range.last_line)
976                    .or(last_line),
977            });
978        }
979    }
980
981    hints
982}
983
984fn inferred_slot_type_name(type_info: &typing::TypeInferenceResult, slot: LocalSlot) -> String {
985    let slot_index = usize::from(slot);
986    if type_info
987        .callable_slots
988        .get(slot_index)
989        .copied()
990        .unwrap_or(false)
991    {
992        return "function".to_string();
993    }
994    if let Some(label) = type_info
995        .local_schema_labels
996        .get(slot_index)
997        .and_then(|label| label.as_ref())
998        .filter(|label| label.as_str() != "unknown")
999    {
1000        return label.clone();
1001    }
1002    value_type_name(
1003        type_info
1004            .local_types
1005            .get(slot_index)
1006            .copied()
1007            .unwrap_or(crate::ValueType::Unknown),
1008    )
1009    .to_string()
1010}
1011
1012fn value_type_name(value: crate::ValueType) -> &'static str {
1013    match value {
1014        crate::ValueType::Unknown => "unknown",
1015        crate::ValueType::Null => "null",
1016        crate::ValueType::Int => "int",
1017        crate::ValueType::Float => "float",
1018        crate::ValueType::Bool => "bool",
1019        crate::ValueType::String => "string",
1020        crate::ValueType::Bytes => "bytes",
1021        crate::ValueType::Array => "array",
1022        crate::ValueType::Map => "map",
1023    }
1024}
1025
1026fn collect_function_decl_lines(stmts: &[Stmt]) -> HashMap<u16, u32> {
1027    let mut lines = HashMap::new();
1028    record_function_decl_lines(stmts, &mut lines);
1029    lines
1030}
1031
1032fn record_function_decl_lines(stmts: &[Stmt], lines: &mut HashMap<u16, u32>) {
1033    for stmt in stmts {
1034        match stmt {
1035            Stmt::FuncDecl { index, line, .. } => {
1036                lines.insert(*index, *line);
1037            }
1038            Stmt::IfElse {
1039                then_branch,
1040                else_branch,
1041                ..
1042            } => {
1043                record_function_decl_lines(then_branch, lines);
1044                record_function_decl_lines(else_branch, lines);
1045            }
1046            Stmt::For {
1047                init, post, body, ..
1048            } => {
1049                record_function_decl_lines(std::slice::from_ref(init.as_ref()), lines);
1050                record_function_decl_lines(std::slice::from_ref(post.as_ref()), lines);
1051                record_function_decl_lines(body, lines);
1052            }
1053            Stmt::While { body, .. } => {
1054                record_function_decl_lines(body, lines);
1055            }
1056            Stmt::Noop { .. }
1057            | Stmt::Let { .. }
1058            | Stmt::Assign { .. }
1059            | Stmt::ClosureLet { .. }
1060            | Stmt::Expr { .. }
1061            | Stmt::Break { .. }
1062            | Stmt::Continue { .. }
1063            | Stmt::Drop { .. } => {}
1064        }
1065    }
1066}
1067
1068fn function_scope_last_line(function_impl: &FunctionImpl) -> Option<u32> {
1069    let stmt_last_line = function_impl.body_stmts.last().map(stmt_source_line);
1070    match stmt_last_line {
1071        Some(line) => Some(line.max(function_impl.body_expr_line)),
1072        None if function_impl.body_expr_line > 0 => Some(function_impl.body_expr_line),
1073        None => None,
1074    }
1075}
1076
1077fn find_local_name_span(
1078    source_map: &SourceMap,
1079    source_id: u32,
1080    line: usize,
1081    name: &str,
1082) -> Option<crate::compiler::source_map::Span> {
1083    let file = source_map.file(source_id)?;
1084    let line_range = file.line_span(line)?;
1085    let line_text = file.line_text(line)?;
1086    let mut search_start = 0usize;
1087    while let Some(relative) = line_text[search_start..].find(name) {
1088        let start = search_start + relative;
1089        let end = start + name.len();
1090        let prev_ok = start == 0
1091            || !line_text[..start]
1092                .chars()
1093                .next_back()
1094                .is_some_and(is_ident_char);
1095        let next_ok =
1096            end == line_text.len() || !line_text[end..].chars().next().is_some_and(is_ident_char);
1097        if prev_ok && next_ok {
1098            return Some(crate::compiler::source_map::Span::new(
1099                source_id,
1100                line_range.start + start,
1101                line_range.start + end,
1102            ));
1103        }
1104        search_start = end;
1105    }
1106    None
1107}
1108
1109fn is_ident_char(ch: char) -> bool {
1110    ch.is_ascii_alphanumeric() || ch == '_'
1111}
1112
1113pub fn compile_source_for_repl(source: &str) -> Result<CompiledProgram, SourceError> {
1114    compile_source_for_repl_with_locals(source, &[]).map(|compiled| compiled.compiled)
1115}
1116
1117pub fn compile_source_for_repl_with_locals(
1118    source: &str,
1119    predefined_locals: &[ReplLocalBinding],
1120) -> Result<CompiledReplProgram, SourceError> {
1121    let source_owned = source.to_string();
1122    let predefined_locals = predefined_locals.to_vec();
1123    run_with_compiler_stack(move || {
1124        compile_source_for_repl_with_locals_impl(&source_owned, &predefined_locals)
1125    })
1126}
1127
1128pub fn compile_source_with_flavor(
1129    source: &str,
1130    flavor: SourceFlavor,
1131) -> Result<CompiledProgram, SourceError> {
1132    compile_source_with_flavor_and_behavior(source, flavor, CompileBehavior::DEFAULT)
1133}
1134
1135pub fn compile_source_with_flavor_and_options(
1136    source: &str,
1137    flavor: SourceFlavor,
1138    options: CompileSourceFileOptions,
1139) -> Result<CompiledProgram, SourcePathError> {
1140    let source_owned = source.to_string();
1141    run_with_compiler_stack(move || {
1142        compile_source_with_flavor_and_options_impl(&source_owned, flavor, &options)
1143    })
1144}
1145
1146pub fn compile_source_at_path_with_flavor_and_options(
1147    path: impl AsRef<Path>,
1148    source: &str,
1149    flavor: SourceFlavor,
1150    options: CompileSourceFileOptions,
1151) -> Result<CompiledProgram, SourcePathError> {
1152    let path = path.as_ref().to_path_buf();
1153    let source_owned = source.to_string();
1154    run_with_compiler_stack(move || {
1155        compile_source_at_path_with_flavor_and_options_impl(&path, &source_owned, flavor, &options)
1156    })
1157}
1158
1159fn compile_source_with_flavor_and_behavior(
1160    source: &str,
1161    flavor: SourceFlavor,
1162    behavior: CompileBehavior,
1163) -> Result<CompiledProgram, SourceError> {
1164    let owned_source = source.to_string();
1165    run_with_compiler_stack(move || {
1166        compile_source_with_flavor_impl(&owned_source, flavor, behavior)
1167    })
1168}
1169
1170fn compile_source_for_repl_with_locals_impl(
1171    source: &str,
1172    predefined_locals: &[ReplLocalBinding],
1173) -> Result<CompiledReplProgram, SourceError> {
1174    let mut source_map = SourceMap::new();
1175    let source_id = source_map.add_source("<source>", source.to_string());
1176    // REPL parsing/compiler entry state is separate from normal program compilation so
1177    // persisted locals do not leak into the generic frontend or IR surface.
1178    let parsed =
1179        frontends::parse_rustscript_repl_source(source, predefined_locals).map_err(|err| {
1180            SourceError::Parse(err.with_line_span_from_source(&source_map, source_id))
1181        })?;
1182    let entry_local_types = build_entry_local_types(&parsed.ir, predefined_locals);
1183    let compiled = match compile_parsed_output_with_entry_locals(
1184        source.to_string(),
1185        parsed.ir,
1186        &parsed.entry_definite_locals,
1187        &entry_local_types,
1188        CompileBehavior::REPL,
1189        TypingMode::StrictRustScript,
1190        true,
1191    ) {
1192        Err(SourceError::Parse(err)) => Err(SourceError::Parse(
1193            err.with_line_span_from_source(&source_map, source_id),
1194        )),
1195        other => other,
1196    }?;
1197    Ok(CompiledReplProgram {
1198        compiled,
1199        bindings: parsed.bindings,
1200    })
1201}
1202
1203fn build_entry_local_types(
1204    parsed: &FrontendIr,
1205    predefined_locals: &[ReplLocalBinding],
1206) -> Vec<typing::EntryLocalType> {
1207    let predefined_by_name = predefined_locals
1208        .iter()
1209        .map(|binding| (binding.name.as_str(), binding))
1210        .collect::<HashMap<_, _>>();
1211    parsed
1212        .local_bindings
1213        .iter()
1214        .filter_map(|(name, slot)| {
1215            let binding = predefined_by_name.get(name.as_str())?;
1216            let (schema, schema_optional) = binding
1217                .schema
1218                .clone()
1219                .map(|schema| schema.split_optional())
1220                .map(|(schema, optional)| (Some(schema), optional))
1221                .unwrap_or((None, false));
1222            Some(typing::EntryLocalType {
1223                slot: *slot,
1224                schema,
1225                optional: binding.optional || schema_optional,
1226            })
1227        })
1228        .collect()
1229}
1230
1231fn compile_source_with_flavor_impl(
1232    source: &str,
1233    flavor: SourceFlavor,
1234    behavior: CompileBehavior,
1235) -> Result<CompiledProgram, SourceError> {
1236    let mut source_map = SourceMap::new();
1237    let source_id = source_map.add_source("<source>", source.to_string());
1238    let parsed = frontends::parse_source(source, flavor, &CompileSourceFileOptions::default())
1239        .map_err(|err| {
1240            SourceError::Parse(err.with_line_span_from_source(&source_map, source_id))
1241        })?;
1242    match compile_parsed_output(
1243        source.to_string(),
1244        parsed,
1245        behavior,
1246        TypingMode::for_flavor(flavor),
1247        matches!(flavor, SourceFlavor::RustScript),
1248    ) {
1249        Err(SourceError::Parse(err)) => Err(SourceError::Parse(
1250            err.with_line_span_from_source(&source_map, source_id),
1251        )),
1252        other => other,
1253    }
1254}
1255
1256fn compile_source_with_flavor_and_options_impl(
1257    source: &str,
1258    flavor: SourceFlavor,
1259    options: &CompileSourceFileOptions,
1260) -> Result<CompiledProgram, SourcePathError> {
1261    if !options.has_module_overrides() && !options.has_source_plugins() {
1262        return compile_source_with_flavor_impl(source, flavor, CompileBehavior::DEFAULT)
1263            .map_err(SourcePathError::Source);
1264    }
1265
1266    let path = virtual_inmemory_entry_path(flavor);
1267    let (_root_parse_source, units) = load_units_for_source_file(&path, flavor, source, options)?;
1268    let merged = merge_units(units)?;
1269    compile_parsed_output(
1270        source.to_string(),
1271        merged,
1272        CompileBehavior::DEFAULT,
1273        TypingMode::for_flavor(flavor),
1274        matches!(flavor, SourceFlavor::RustScript),
1275    )
1276    .map_err(SourcePathError::Source)
1277}
1278
1279fn compile_source_at_path_with_flavor_and_options_impl(
1280    path: &Path,
1281    source: &str,
1282    flavor: SourceFlavor,
1283    options: &CompileSourceFileOptions,
1284) -> Result<CompiledProgram, SourcePathError> {
1285    let (_root_parse_source, units) = load_units_for_source_file(path, flavor, source, options)?;
1286    let merged = merge_units(units)?;
1287    compile_parsed_output(
1288        source.to_string(),
1289        merged,
1290        CompileBehavior::DEFAULT,
1291        TypingMode::for_flavor(flavor),
1292        matches!(flavor, SourceFlavor::RustScript),
1293    )
1294    .map_err(SourcePathError::Source)
1295}
1296
1297fn virtual_inmemory_entry_path(flavor: SourceFlavor) -> PathBuf {
1298    let ext = match flavor {
1299        SourceFlavor::RustScript => "rss",
1300        SourceFlavor::JavaScript => "js",
1301        SourceFlavor::Lua => "lua",
1302    };
1303    PathBuf::from("__pd_vm_inmemory__").join(format!("main.{ext}"))
1304}
1305
1306pub fn compile_source_file(path: impl AsRef<Path>) -> Result<CompiledProgram, SourcePathError> {
1307    compile_source_file_with_options(path, CompileSourceFileOptions::default())
1308}
1309
1310pub fn compile_source_file_with_options(
1311    path: impl AsRef<Path>,
1312    options: CompileSourceFileOptions,
1313) -> Result<CompiledProgram, SourcePathError> {
1314    let path = path.as_ref().to_path_buf();
1315    run_with_compiler_stack(move || compile_source_file_impl(&path, &options))
1316}
1317
1318fn compile_source_file_impl(
1319    path: &Path,
1320    options: &CompileSourceFileOptions,
1321) -> Result<CompiledProgram, SourcePathError> {
1322    let flavor = SourceFlavor::from_path_with_options(path, options)?;
1323    let source_raw = std::fs::read_to_string(path)?;
1324    let (_root_parse_source, units) =
1325        load_units_for_source_file(path, flavor, &source_raw, options)?;
1326    let merged = merge_units(units)?;
1327    compile_parsed_output(
1328        source_raw,
1329        merged,
1330        CompileBehavior::DEFAULT,
1331        TypingMode::for_flavor(flavor),
1332        matches!(flavor, SourceFlavor::RustScript),
1333    )
1334    .map_err(SourcePathError::Source)
1335}
1336
1337fn run_with_compiler_stack<T, F>(f: F) -> T
1338where
1339    T: Send + 'static,
1340    F: FnOnce() -> T + Send + 'static,
1341{
1342    #[cfg(target_arch = "wasm32")]
1343    {
1344        f()
1345    }
1346
1347    #[cfg(not(target_arch = "wasm32"))]
1348    {
1349        const COMPILER_STACK_SIZE: usize = 32 * 1024 * 1024;
1350        let handle = std::thread::Builder::new()
1351            .name("pd-vm-compile".to_string())
1352            .stack_size(COMPILER_STACK_SIZE)
1353            .spawn(f)
1354            .expect("failed to spawn compiler thread");
1355        match handle.join() {
1356            Ok(value) => value,
1357            Err(payload) => std::panic::resume_unwind(payload),
1358        }
1359    }
1360}