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