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