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_function_decls(function_decls);
440 compiler.set_function_impls(function_impls);
441 compiler.set_struct_schemas(struct_schemas);
442 compiler.set_host_import_return_types(host_import_return_types);
443 compiler.set_host_import_signatures(host_import_signatures);
444 compiler.set_call_index_remap(call_index_remap);
445 compiler.set_enable_local_move_semantics(enable_local_move_semantics);
446 for func in &functions {
447 compiler.add_function_debug(func);
448 }
449 for (name, index) in local_bindings {
450 let range = local_debug_ranges.get(&name).copied().unwrap_or_default();
451 compiler
452 .add_local_debug(name, index, range.declared_line, range.last_line)
453 .map_err(SourceError::Compile)?;
454 }
455 let mut program = compiler
456 .compile_program(&stmts)
457 .map_err(SourceError::Compile)?;
458 program.local_count = locals;
459 program.imports = runtime_import_functions
460 .iter()
461 .map(|func| HostImport {
462 name: func.name.clone(),
463 arity: func.arity,
464 return_type: func.return_type,
465 })
466 .collect();
467 Ok(CompiledProgram {
468 program,
469 locals,
470 functions: visible_runtime_import_functions,
471 })
472}
473
474#[derive(Clone, Debug)]
475struct StrictSlotSite {
476 name: String,
477 kind: &'static str,
478 line: Option<u32>,
479 source_name: Option<String>,
480}
481
482fn reject_strict_unknown_annotations(parsed: &FrontendIr) -> Result<(), ParseError> {
483 let Some(span) = parsed.unknown_type_spans.first().copied() else {
484 return Ok(());
485 };
486 Err(ParseError {
487 line: 1,
488 message:
489 "RustScript requires concrete compile-time types; 'unknown' annotations are not allowed"
490 .to_string(),
491 span: Some(span),
492 code: Some("E_STRICT_UNKNOWN_TYPE".to_string()),
493 })
494}
495
496fn enforce_strict_rustscript_type_resolution(
497 parsed: &FrontendIr,
498 type_info: &typing::TypeInferenceResult,
499) -> Result<(), CompileError> {
500 for schema in parsed.struct_schemas.values() {
501 if schema_is_fully_known(&schema.body_schema) {
502 continue;
503 }
504 return Err(CompileError::StrictTypingRequired {
505 line: None,
506 source_name: None,
507 detail: format!(
508 "struct '{}' contains non-concrete field types; RustScript requires concrete schemas",
509 schema.name
510 ),
511 });
512 }
513
514 let function_decl_lines = collect_function_decl_lines(&parsed.stmts);
515 for decl in &parsed.functions {
516 if let Some(schema) = decl.return_schema.as_ref()
517 && !schema_is_fully_known(schema)
518 {
519 return Err(CompileError::StrictTypingRequired {
520 line: function_decl_lines.get(&decl.index).copied(),
521 source_name: parsed.function_sources.get(&decl.index).cloned(),
522 detail: format!(
523 "function '{}' uses a non-concrete return schema; RustScript requires concrete return types",
524 decl.name
525 ),
526 });
527 }
528 }
529
530 for (slot, site) in collect_strict_slot_sites(parsed) {
531 if slot_is_fully_typed(slot, type_info) {
532 continue;
533 }
534 return Err(CompileError::StrictTypingRequired {
535 line: site.line,
536 source_name: site.source_name,
537 detail: format!(
538 "{} '{}' does not resolve to a concrete compile-time type in RustScript",
539 site.kind, site.name
540 ),
541 });
542 }
543
544 Ok(())
545}
546
547fn slot_is_fully_typed(slot: LocalSlot, type_info: &typing::TypeInferenceResult) -> bool {
548 let slot_index = usize::from(slot);
549 if type_info
550 .callable_slots
551 .get(slot_index)
552 .copied()
553 .unwrap_or(false)
554 {
555 return true;
556 }
557 if let Some(schema) = type_info
558 .local_schemas
559 .get(slot_index)
560 .and_then(|schema| schema.as_ref())
561 {
562 return schema_is_fully_known(schema);
563 }
564 type_info.local_types.get(slot_index).copied() != Some(crate::ValueType::Unknown)
565}
566
567fn schema_is_fully_known(schema: &TypeSchema) -> bool {
568 match schema {
569 TypeSchema::Unknown => false,
570 TypeSchema::Null
571 | TypeSchema::Int
572 | TypeSchema::Float
573 | TypeSchema::Number
574 | TypeSchema::Bool
575 | TypeSchema::String
576 | TypeSchema::Bytes
577 | TypeSchema::GenericParam(_) => true,
578 TypeSchema::Optional(inner) => schema_is_fully_known(inner),
579 TypeSchema::Named(_, type_args) => type_args.iter().all(schema_is_fully_known),
580 TypeSchema::Array(item) | TypeSchema::Map(item) => schema_is_fully_known(item),
581 TypeSchema::ArrayTuple(items) => items.iter().all(schema_is_fully_known),
582 TypeSchema::ArrayTupleRest { prefix, rest } => {
583 prefix.iter().all(schema_is_fully_known) && schema_is_fully_known(rest)
584 }
585 TypeSchema::Object(fields) => fields.values().all(schema_is_fully_known),
586 TypeSchema::Callable { params, result } => {
587 params.iter().all(schema_is_fully_known) && schema_is_fully_known(result)
588 }
589 }
590}
591
592fn collect_strict_slot_sites(parsed: &FrontendIr) -> Vec<(LocalSlot, StrictSlotSite)> {
593 let mut sites = Vec::new();
594 let local_debug_ranges = collect_local_debug_ranges(&parsed.stmts, &parsed.function_impls);
595 let local_source_names = collect_local_source_names(parsed);
596 for (name, slot) in &parsed.local_bindings {
597 let line = local_debug_ranges
598 .get(slot)
599 .and_then(|range| range.declared_line);
600 sites.push((
601 *slot,
602 StrictSlotSite {
603 name: name.clone(),
604 kind: "local",
605 line,
606 source_name: local_source_names.get(slot).cloned().flatten(),
607 },
608 ));
609 }
610
611 let function_decl_lines = collect_function_decl_lines(&parsed.stmts);
612 for decl in &parsed.functions {
613 let Some(function_impl) = parsed.function_impls.get(&decl.index) else {
614 continue;
615 };
616 for (name, slot) in decl.args.iter().zip(function_impl.param_slots.iter()) {
617 sites.push((
618 *slot,
619 StrictSlotSite {
620 name: name.clone(),
621 kind: "parameter",
622 line: function_decl_lines.get(&decl.index).copied(),
623 source_name: parsed.function_sources.get(&decl.index).cloned(),
624 },
625 ));
626 }
627 }
628
629 sites.sort_by_key(|(slot, _)| *slot);
630 sites
631}
632
633fn collect_local_source_names(parsed: &FrontendIr) -> HashMap<LocalSlot, Option<String>> {
634 let mut out = HashMap::new();
635 for (index, stmt) in parsed.stmts.iter().enumerate() {
636 let source_name = parsed
637 .stmt_sources
638 .get(index)
639 .and_then(|source| source.as_deref());
640 record_local_source_names(std::slice::from_ref(stmt), source_name, &mut out);
641 }
642 for decl in &parsed.functions {
643 let Some(function_impl) = parsed.function_impls.get(&decl.index) else {
644 continue;
645 };
646 let source_name = parsed.function_sources.get(&decl.index).map(String::as_str);
647 record_local_source_names(&function_impl.body_stmts, source_name, &mut out);
648 }
649 out
650}
651
652fn record_local_source_names(
653 stmts: &[Stmt],
654 source_name: Option<&str>,
655 out: &mut HashMap<LocalSlot, Option<String>>,
656) {
657 let source_name = source_name.map(str::to_string);
658 for stmt in stmts {
659 match stmt {
660 Stmt::Let { index, .. } => {
661 out.entry(*index).or_insert_with(|| source_name.clone());
662 }
663 Stmt::IfElse {
664 then_branch,
665 else_branch,
666 ..
667 } => {
668 record_local_source_names(then_branch, source_name.as_deref(), out);
669 record_local_source_names(else_branch, source_name.as_deref(), out);
670 }
671 Stmt::For {
672 init, post, body, ..
673 } => {
674 record_local_source_names(
675 std::slice::from_ref(init.as_ref()),
676 source_name.as_deref(),
677 out,
678 );
679 record_local_source_names(
680 std::slice::from_ref(post.as_ref()),
681 source_name.as_deref(),
682 out,
683 );
684 record_local_source_names(body, source_name.as_deref(), out);
685 }
686 Stmt::While { body, .. } => {
687 record_local_source_names(body, source_name.as_deref(), out);
688 }
689 Stmt::Noop { .. }
690 | Stmt::Assign { .. }
691 | Stmt::ClosureLet { .. }
692 | Stmt::FuncDecl { .. }
693 | Stmt::Expr { .. }
694 | Stmt::Break { .. }
695 | Stmt::Continue { .. }
696 | Stmt::Drop { .. } => {}
697 }
698 }
699}
700
701pub fn compile_source(source: &str) -> Result<CompiledProgram, SourceError> {
702 compile_source_with_flavor(source, SourceFlavor::RustScript)
703}
704
705pub fn lint_trailing_function_return_semicolons(
706 source: &str,
707 flavor: SourceFlavor,
708) -> Result<Vec<ParseError>, ParseError> {
709 let Some(dialect) =
710 frontends::parser_dialect_for_flavor(flavor, &CompileSourceFileOptions::default())
711 else {
712 return Ok(Vec::new());
713 };
714 parser::lint_trailing_function_return_semicolons(source, 0, dialect)
715}
716
717pub fn lint_unknown_type_annotations(
718 source: &str,
719 flavor: SourceFlavor,
720) -> Result<Vec<crate::compiler::source_map::Span>, SourceError> {
721 let mut source_map = SourceMap::new();
722 let source_id = source_map.add_source("<source>", source.to_string());
723 let parsed = frontends::parse_source(source, flavor, &CompileSourceFileOptions::default())
724 .map_err(|err| {
725 SourceError::Parse(err.with_line_span_from_source(&source_map, source_id))
726 })?;
727 Ok(parsed.unknown_type_spans)
728}
729
730pub fn lint_unknown_inferred_local_types(
731 source: &str,
732 flavor: SourceFlavor,
733) -> Result<Vec<UnknownInferredLocal>, SourceError> {
734 lint_unknown_inferred_local_types_impl(source, flavor)
735}
736
737pub fn collect_inferred_local_type_hints(
738 source: &str,
739 flavor: SourceFlavor,
740) -> Result<Vec<InferredLocalTypeHint>, SourceError> {
741 collect_inferred_local_type_hints_impl(source, flavor)
742}
743
744pub fn collect_inferred_local_type_hints_with_options(
745 source: &str,
746 flavor: SourceFlavor,
747 options: CompileSourceFileOptions,
748) -> Result<Vec<InferredLocalTypeHint>, SourcePathError> {
749 let source_owned = source.to_string();
750 run_with_compiler_stack(move || {
751 collect_inferred_local_type_hints_with_options_impl(&source_owned, flavor, &options)
752 })
753}
754
755pub fn collect_inferred_local_type_hints_at_path_with_options(
756 path: impl AsRef<Path>,
757 source: &str,
758 flavor: SourceFlavor,
759 options: CompileSourceFileOptions,
760) -> Result<Vec<InferredLocalTypeHint>, SourcePathError> {
761 let path = path.as_ref().to_path_buf();
762 let source_owned = source.to_string();
763 run_with_compiler_stack(move || {
764 collect_inferred_local_type_hints_at_path_with_options_impl(
765 &path,
766 &source_owned,
767 flavor,
768 &options,
769 )
770 })
771}
772
773pub fn lint_unknown_inferred_local_types_with_options(
774 source: &str,
775 flavor: SourceFlavor,
776 options: CompileSourceFileOptions,
777) -> Result<Vec<UnknownInferredLocal>, SourcePathError> {
778 let source_owned = source.to_string();
779 run_with_compiler_stack(move || {
780 lint_unknown_inferred_local_types_with_options_impl(&source_owned, flavor, &options)
781 })
782}
783
784pub fn lint_unknown_inferred_local_types_at_path_with_options(
785 path: impl AsRef<Path>,
786 source: &str,
787 flavor: SourceFlavor,
788 options: CompileSourceFileOptions,
789) -> Result<Vec<UnknownInferredLocal>, SourcePathError> {
790 let path = path.as_ref().to_path_buf();
791 let source_owned = source.to_string();
792 run_with_compiler_stack(move || {
793 lint_unknown_inferred_local_types_at_path_with_options_impl(
794 &path,
795 &source_owned,
796 flavor,
797 &options,
798 )
799 })
800}
801
802fn lint_unknown_inferred_local_types_impl(
803 source: &str,
804 flavor: SourceFlavor,
805) -> Result<Vec<UnknownInferredLocal>, SourceError> {
806 let mut source_map = SourceMap::new();
807 let source_id = source_map.add_source("<source>", source.to_string());
808 let parsed = frontends::parse_source(source, flavor, &CompileSourceFileOptions::default())
809 .map_err(|err| {
810 SourceError::Parse(err.with_line_span_from_source(&source_map, source_id))
811 })?;
812 Ok(collect_unknown_inferred_local_types(
813 &source_map,
814 source_id,
815 parsed,
816 ))
817}
818
819fn collect_inferred_local_type_hints_impl(
820 source: &str,
821 flavor: SourceFlavor,
822) -> Result<Vec<InferredLocalTypeHint>, SourceError> {
823 let mut source_map = SourceMap::new();
824 let source_id = source_map.add_source("<source>", source.to_string());
825 let parsed = frontends::parse_source(source, flavor, &CompileSourceFileOptions::default())
826 .map_err(|err| {
827 SourceError::Parse(err.with_line_span_from_source(&source_map, source_id))
828 })?;
829 Ok(collect_named_local_type_hints(parsed))
830}
831
832fn lint_unknown_inferred_local_types_with_options_impl(
833 source: &str,
834 flavor: SourceFlavor,
835 options: &CompileSourceFileOptions,
836) -> Result<Vec<UnknownInferredLocal>, SourcePathError> {
837 if !options.has_module_overrides() && !options.has_source_plugins() {
838 return lint_unknown_inferred_local_types_impl(source, flavor)
839 .map_err(SourcePathError::Source);
840 }
841
842 let path = virtual_inmemory_entry_path(flavor);
843 lint_unknown_inferred_local_types_at_path_with_options_impl(&path, source, flavor, options)
844}
845
846fn collect_inferred_local_type_hints_with_options_impl(
847 source: &str,
848 flavor: SourceFlavor,
849 options: &CompileSourceFileOptions,
850) -> Result<Vec<InferredLocalTypeHint>, SourcePathError> {
851 if !options.has_module_overrides() && !options.has_source_plugins() {
852 return collect_inferred_local_type_hints_impl(source, flavor)
853 .map_err(SourcePathError::Source);
854 }
855
856 let path = virtual_inmemory_entry_path(flavor);
857 collect_inferred_local_type_hints_at_path_with_options_impl(&path, source, flavor, options)
858}
859
860fn lint_unknown_inferred_local_types_at_path_with_options_impl(
861 path: &Path,
862 source: &str,
863 flavor: SourceFlavor,
864 options: &CompileSourceFileOptions,
865) -> Result<Vec<UnknownInferredLocal>, SourcePathError> {
866 let mut source_map = SourceMap::new();
867 let source_id = source_map.add_source(path.display().to_string(), source.to_string());
868 let (_root_parse_source, units) = load_units_for_source_file(path, flavor, source, options)?;
869 let parsed = units
870 .into_iter()
871 .last()
872 .map(|unit| unit.parsed)
873 .expect("root parsed unit should always be present");
874 Ok(collect_unknown_inferred_local_types(
875 &source_map,
876 source_id,
877 parsed,
878 ))
879}
880
881fn collect_inferred_local_type_hints_at_path_with_options_impl(
882 path: &Path,
883 source: &str,
884 flavor: SourceFlavor,
885 options: &CompileSourceFileOptions,
886) -> Result<Vec<InferredLocalTypeHint>, SourcePathError> {
887 let (_root_parse_source, units) = load_units_for_source_file(path, flavor, source, options)?;
888 let parsed = units
889 .into_iter()
890 .last()
891 .map(|unit| unit.parsed)
892 .expect("root parsed unit should always be present");
893 Ok(collect_named_local_type_hints(parsed))
894}
895
896fn collect_unknown_inferred_local_types(
897 source_map: &SourceMap,
898 source_id: u32,
899 parsed: FrontendIr,
900) -> Vec<UnknownInferredLocal> {
901 let local_debug_ranges = collect_local_debug_ranges(&parsed.stmts, &parsed.function_impls);
902 let parsed = typing::legalize_builtins_and_bind_types(parsed, TypingMode::DynamicHints, &[]);
903 let type_info = typing::infer_types(&parsed, TypingMode::DynamicHints, &[]);
904
905 let mut warnings = Vec::new();
906 for (name, slot) in &parsed.local_bindings {
907 let Some(range) = local_debug_ranges.get(slot) else {
908 continue;
909 };
910 let Some(line_u32) = range.declared_line else {
911 continue;
912 };
913 let slot_index = usize::from(*slot);
914 if type_info
915 .callable_slots
916 .get(slot_index)
917 .copied()
918 .unwrap_or(false)
919 {
920 continue;
921 }
922 if type_info
923 .local_schema_labels
924 .get(slot_index)
925 .and_then(|label| label.as_ref())
926 .is_some_and(|label| label != "unknown")
927 {
928 continue;
929 }
930 if type_info.local_types.get(slot_index) != Some(&crate::ValueType::Unknown) {
931 continue;
932 }
933 let line = usize::try_from(line_u32).unwrap_or(usize::MAX);
934 warnings.push(UnknownInferredLocal {
935 name: name.clone(),
936 line,
937 span: find_local_name_span(source_map, source_id, line, name)
938 .or_else(|| source_map.line_span(source_id, line)),
939 });
940 }
941 warnings
942}
943
944fn collect_named_local_type_hints(parsed: FrontendIr) -> Vec<InferredLocalTypeHint> {
945 let slot_ranges = collect_local_debug_ranges(&parsed.stmts, &parsed.function_impls);
946 let function_decl_lines = collect_function_decl_lines(&parsed.stmts);
947 let parsed = typing::legalize_builtins_and_bind_types(parsed, TypingMode::DynamicHints, &[]);
948 let type_info = typing::infer_types(&parsed, TypingMode::DynamicHints, &[]);
949
950 let mut hints = Vec::new();
951 for (name, slot) in &parsed.local_bindings {
952 hints.push(InferredLocalTypeHint {
953 name: name.clone(),
954 inferred_type: inferred_slot_type_name(&type_info, *slot),
955 declared_line: slot_ranges.get(slot).and_then(|range| range.declared_line),
956 last_line: slot_ranges.get(slot).and_then(|range| range.last_line),
957 });
958 }
959
960 for decl in &parsed.functions {
961 let Some(function_impl) = parsed.function_impls.get(&decl.index) else {
962 continue;
963 };
964 let declared_line = function_decl_lines.get(&decl.index).copied();
965 let last_line = function_scope_last_line(function_impl).or(declared_line);
966 for (name, slot) in decl.args.iter().zip(function_impl.param_slots.iter()) {
967 hints.push(InferredLocalTypeHint {
968 name: name.clone(),
969 inferred_type: inferred_slot_type_name(&type_info, *slot),
970 declared_line: slot_ranges
971 .get(slot)
972 .and_then(|range| range.declared_line)
973 .or(declared_line),
974 last_line: slot_ranges
975 .get(slot)
976 .and_then(|range| range.last_line)
977 .or(last_line),
978 });
979 }
980 }
981
982 hints
983}
984
985fn inferred_slot_type_name(type_info: &typing::TypeInferenceResult, slot: LocalSlot) -> String {
986 let slot_index = usize::from(slot);
987 if type_info
988 .callable_slots
989 .get(slot_index)
990 .copied()
991 .unwrap_or(false)
992 {
993 return "function".to_string();
994 }
995 if let Some(label) = type_info
996 .local_schema_labels
997 .get(slot_index)
998 .and_then(|label| label.as_ref())
999 .filter(|label| label.as_str() != "unknown")
1000 {
1001 return label.clone();
1002 }
1003 value_type_name(
1004 type_info
1005 .local_types
1006 .get(slot_index)
1007 .copied()
1008 .unwrap_or(crate::ValueType::Unknown),
1009 )
1010 .to_string()
1011}
1012
1013fn value_type_name(value: crate::ValueType) -> &'static str {
1014 match value {
1015 crate::ValueType::Unknown => "unknown",
1016 crate::ValueType::Null => "null",
1017 crate::ValueType::Int => "int",
1018 crate::ValueType::Float => "float",
1019 crate::ValueType::Bool => "bool",
1020 crate::ValueType::String => "string",
1021 crate::ValueType::Bytes => "bytes",
1022 crate::ValueType::Array => "array",
1023 crate::ValueType::Map => "map",
1024 }
1025}
1026
1027fn collect_function_decl_lines(stmts: &[Stmt]) -> HashMap<u16, u32> {
1028 let mut lines = HashMap::new();
1029 record_function_decl_lines(stmts, &mut lines);
1030 lines
1031}
1032
1033fn record_function_decl_lines(stmts: &[Stmt], lines: &mut HashMap<u16, u32>) {
1034 for stmt in stmts {
1035 match stmt {
1036 Stmt::FuncDecl { index, line, .. } => {
1037 lines.insert(*index, *line);
1038 }
1039 Stmt::IfElse {
1040 then_branch,
1041 else_branch,
1042 ..
1043 } => {
1044 record_function_decl_lines(then_branch, lines);
1045 record_function_decl_lines(else_branch, lines);
1046 }
1047 Stmt::For {
1048 init, post, body, ..
1049 } => {
1050 record_function_decl_lines(std::slice::from_ref(init.as_ref()), lines);
1051 record_function_decl_lines(std::slice::from_ref(post.as_ref()), lines);
1052 record_function_decl_lines(body, lines);
1053 }
1054 Stmt::While { body, .. } => {
1055 record_function_decl_lines(body, lines);
1056 }
1057 Stmt::Noop { .. }
1058 | Stmt::Let { .. }
1059 | Stmt::Assign { .. }
1060 | Stmt::ClosureLet { .. }
1061 | Stmt::Expr { .. }
1062 | Stmt::Break { .. }
1063 | Stmt::Continue { .. }
1064 | Stmt::Drop { .. } => {}
1065 }
1066 }
1067}
1068
1069fn function_scope_last_line(function_impl: &FunctionImpl) -> Option<u32> {
1070 let stmt_last_line = function_impl.body_stmts.last().map(stmt_source_line);
1071 match stmt_last_line {
1072 Some(line) => Some(line.max(function_impl.body_expr_line)),
1073 None if function_impl.body_expr_line > 0 => Some(function_impl.body_expr_line),
1074 None => None,
1075 }
1076}
1077
1078fn find_local_name_span(
1079 source_map: &SourceMap,
1080 source_id: u32,
1081 line: usize,
1082 name: &str,
1083) -> Option<crate::compiler::source_map::Span> {
1084 let file = source_map.file(source_id)?;
1085 let line_range = file.line_span(line)?;
1086 let line_text = file.line_text(line)?;
1087 let mut search_start = 0usize;
1088 while let Some(relative) = line_text[search_start..].find(name) {
1089 let start = search_start + relative;
1090 let end = start + name.len();
1091 let prev_ok = start == 0
1092 || !line_text[..start]
1093 .chars()
1094 .next_back()
1095 .is_some_and(is_ident_char);
1096 let next_ok =
1097 end == line_text.len() || !line_text[end..].chars().next().is_some_and(is_ident_char);
1098 if prev_ok && next_ok {
1099 return Some(crate::compiler::source_map::Span::new(
1100 source_id,
1101 line_range.start + start,
1102 line_range.start + end,
1103 ));
1104 }
1105 search_start = end;
1106 }
1107 None
1108}
1109
1110fn is_ident_char(ch: char) -> bool {
1111 ch.is_ascii_alphanumeric() || ch == '_'
1112}
1113
1114pub fn compile_source_for_repl(source: &str) -> Result<CompiledProgram, SourceError> {
1115 compile_source_for_repl_with_locals(source, &[]).map(|compiled| compiled.compiled)
1116}
1117
1118pub fn compile_source_for_repl_with_locals(
1119 source: &str,
1120 predefined_locals: &[ReplLocalBinding],
1121) -> Result<CompiledReplProgram, SourceError> {
1122 let source_owned = source.to_string();
1123 let predefined_locals = predefined_locals.to_vec();
1124 run_with_compiler_stack(move || {
1125 compile_source_for_repl_with_locals_impl(&source_owned, &predefined_locals, &[])
1126 })
1127}
1128
1129pub fn compile_source_for_repl_with_state(
1130 source: &str,
1131 predefined_locals: &[ReplLocalState],
1132) -> Result<CompiledReplProgram, SourceError> {
1133 let source_owned = source.to_string();
1134 let predefined_locals = predefined_locals.to_vec();
1135 run_with_compiler_stack(move || {
1136 let bindings = predefined_locals
1137 .iter()
1138 .map(|state| state.binding.clone())
1139 .collect::<Vec<_>>();
1140 let moved_names = predefined_locals
1141 .iter()
1142 .filter(|state| state.moved)
1143 .map(|state| state.binding.name.clone())
1144 .collect::<Vec<_>>();
1145 compile_source_for_repl_with_locals_impl(&source_owned, &bindings, &moved_names)
1146 })
1147}
1148
1149pub fn compile_source_with_flavor(
1150 source: &str,
1151 flavor: SourceFlavor,
1152) -> Result<CompiledProgram, SourceError> {
1153 compile_source_with_flavor_and_behavior(source, flavor, CompileBehavior::DEFAULT)
1154}
1155
1156pub fn compile_source_with_flavor_and_options(
1157 source: &str,
1158 flavor: SourceFlavor,
1159 options: CompileSourceFileOptions,
1160) -> Result<CompiledProgram, SourcePathError> {
1161 let source_owned = source.to_string();
1162 run_with_compiler_stack(move || {
1163 compile_source_with_flavor_and_options_impl(&source_owned, flavor, &options)
1164 })
1165}
1166
1167pub fn compile_source_at_path_with_flavor_and_options(
1168 path: impl AsRef<Path>,
1169 source: &str,
1170 flavor: SourceFlavor,
1171 options: CompileSourceFileOptions,
1172) -> Result<CompiledProgram, SourcePathError> {
1173 let path = path.as_ref().to_path_buf();
1174 let source_owned = source.to_string();
1175 run_with_compiler_stack(move || {
1176 compile_source_at_path_with_flavor_and_options_impl(&path, &source_owned, flavor, &options)
1177 })
1178}
1179
1180fn compile_source_with_flavor_and_behavior(
1181 source: &str,
1182 flavor: SourceFlavor,
1183 behavior: CompileBehavior,
1184) -> Result<CompiledProgram, SourceError> {
1185 let owned_source = source.to_string();
1186 run_with_compiler_stack(move || {
1187 compile_source_with_flavor_impl(&owned_source, flavor, behavior)
1188 })
1189}
1190
1191fn compile_source_for_repl_with_locals_impl(
1192 source: &str,
1193 predefined_locals: &[ReplLocalBinding],
1194 moved_names: &[String],
1195) -> Result<CompiledReplProgram, SourceError> {
1196 let mut source_map = SourceMap::new();
1197 let source_id = source_map.add_source("<source>", source.to_string());
1198 let parsed =
1201 frontends::parse_rustscript_repl_source(source, predefined_locals).map_err(|err| {
1202 SourceError::Parse(err.with_line_span_from_source(&source_map, source_id))
1203 })?;
1204 let entry_local_types = build_entry_local_types(&parsed.ir, predefined_locals);
1205 let entry_availability =
1206 build_entry_local_availability(&parsed.ir, predefined_locals, moved_names);
1207 let compiled = match compile_parsed_output_with_entry_locals(
1208 source.to_string(),
1209 parsed.ir,
1210 &entry_availability,
1211 &entry_local_types,
1212 CompileBehavior::REPL,
1213 TypingMode::StrictRustScript,
1214 true,
1215 ) {
1216 Err(SourceError::Parse(err)) => Err(SourceError::Parse(
1217 err.with_line_span_from_source(&source_map, source_id),
1218 )),
1219 other => other,
1220 }?;
1221 Ok(CompiledReplProgram {
1222 compiled,
1223 bindings: parsed.bindings,
1224 })
1225}
1226
1227fn build_entry_local_availability(
1228 parsed: &FrontendIr,
1229 predefined_locals: &[ReplLocalBinding],
1230 moved_names: &[String],
1231) -> Vec<lifetime::EntryLocalAvailability> {
1232 let predefined_by_name = predefined_locals
1233 .iter()
1234 .map(|binding| (binding.name.as_str(), binding))
1235 .collect::<HashMap<_, _>>();
1236 parsed
1237 .local_bindings
1238 .iter()
1239 .filter_map(|(name, slot)| {
1240 let binding = predefined_by_name.get(name.as_str())?;
1241 let schema = binding
1242 .schema
1243 .as_ref()
1244 .map(|schema| schema.split_optional().0);
1245 let copyable = matches!(
1246 schema,
1247 Some(
1248 TypeSchema::Null
1249 | TypeSchema::Int
1250 | TypeSchema::Float
1251 | TypeSchema::Number
1252 | TypeSchema::Bool
1253 )
1254 );
1255 let movable = matches!(schema, Some(TypeSchema::String | TypeSchema::Bytes));
1256 Some(lifetime::EntryLocalAvailability {
1257 slot: *slot,
1258 copyable,
1259 movable,
1260 moved: moved_names.iter().any(|moved| moved == name),
1261 })
1262 })
1263 .collect()
1264}
1265
1266fn build_entry_local_types(
1267 parsed: &FrontendIr,
1268 predefined_locals: &[ReplLocalBinding],
1269) -> Vec<typing::EntryLocalType> {
1270 let predefined_by_name = predefined_locals
1271 .iter()
1272 .map(|binding| (binding.name.as_str(), binding))
1273 .collect::<HashMap<_, _>>();
1274 parsed
1275 .local_bindings
1276 .iter()
1277 .filter_map(|(name, slot)| {
1278 let binding = predefined_by_name.get(name.as_str())?;
1279 let (schema, schema_optional) = binding
1280 .schema
1281 .clone()
1282 .map(|schema| schema.split_optional())
1283 .map(|(schema, optional)| (Some(schema), optional))
1284 .unwrap_or((None, false));
1285 Some(typing::EntryLocalType {
1286 slot: *slot,
1287 schema,
1288 optional: binding.optional || schema_optional,
1289 })
1290 })
1291 .collect()
1292}
1293
1294fn compile_source_with_flavor_impl(
1295 source: &str,
1296 flavor: SourceFlavor,
1297 behavior: CompileBehavior,
1298) -> Result<CompiledProgram, SourceError> {
1299 let mut source_map = SourceMap::new();
1300 let source_id = source_map.add_source("<source>", source.to_string());
1301 let parsed = frontends::parse_source(source, flavor, &CompileSourceFileOptions::default())
1302 .map_err(|err| {
1303 SourceError::Parse(err.with_line_span_from_source(&source_map, source_id))
1304 })?;
1305 match compile_parsed_output(
1306 source.to_string(),
1307 parsed,
1308 behavior,
1309 TypingMode::for_flavor(flavor),
1310 matches!(flavor, SourceFlavor::RustScript),
1311 ) {
1312 Err(SourceError::Parse(err)) => Err(SourceError::Parse(
1313 err.with_line_span_from_source(&source_map, source_id),
1314 )),
1315 other => other,
1316 }
1317}
1318
1319fn compile_source_with_flavor_and_options_impl(
1320 source: &str,
1321 flavor: SourceFlavor,
1322 options: &CompileSourceFileOptions,
1323) -> Result<CompiledProgram, SourcePathError> {
1324 if !options.has_module_overrides() && !options.has_source_plugins() {
1325 return compile_source_with_flavor_impl(source, flavor, CompileBehavior::DEFAULT)
1326 .map_err(SourcePathError::Source);
1327 }
1328
1329 let path = virtual_inmemory_entry_path(flavor);
1330 let (_root_parse_source, units) = load_units_for_source_file(&path, flavor, source, options)?;
1331 let merged = merge_units(units)?;
1332 compile_parsed_output(
1333 source.to_string(),
1334 merged,
1335 CompileBehavior::DEFAULT,
1336 TypingMode::for_flavor(flavor),
1337 matches!(flavor, SourceFlavor::RustScript),
1338 )
1339 .map_err(SourcePathError::Source)
1340}
1341
1342fn compile_source_at_path_with_flavor_and_options_impl(
1343 path: &Path,
1344 source: &str,
1345 flavor: SourceFlavor,
1346 options: &CompileSourceFileOptions,
1347) -> Result<CompiledProgram, SourcePathError> {
1348 let (_root_parse_source, units) = load_units_for_source_file(path, flavor, source, options)?;
1349 let merged = merge_units(units)?;
1350 compile_parsed_output(
1351 source.to_string(),
1352 merged,
1353 CompileBehavior::DEFAULT,
1354 TypingMode::for_flavor(flavor),
1355 matches!(flavor, SourceFlavor::RustScript),
1356 )
1357 .map_err(SourcePathError::Source)
1358}
1359
1360fn virtual_inmemory_entry_path(flavor: SourceFlavor) -> PathBuf {
1361 let ext = match flavor {
1362 SourceFlavor::RustScript => "rss",
1363 SourceFlavor::JavaScript => "js",
1364 SourceFlavor::Lua => "lua",
1365 };
1366 PathBuf::from("__pd_vm_inmemory__").join(format!("main.{ext}"))
1367}
1368
1369pub fn compile_source_file(path: impl AsRef<Path>) -> Result<CompiledProgram, SourcePathError> {
1370 compile_source_file_with_options(path, CompileSourceFileOptions::default())
1371}
1372
1373pub fn compile_source_file_with_options(
1374 path: impl AsRef<Path>,
1375 options: CompileSourceFileOptions,
1376) -> Result<CompiledProgram, SourcePathError> {
1377 let path = path.as_ref().to_path_buf();
1378 run_with_compiler_stack(move || compile_source_file_impl(&path, &options))
1379}
1380
1381fn compile_source_file_impl(
1382 path: &Path,
1383 options: &CompileSourceFileOptions,
1384) -> Result<CompiledProgram, SourcePathError> {
1385 let flavor = SourceFlavor::from_path_with_options(path, options)?;
1386 let source_raw = std::fs::read_to_string(path)?;
1387 let (_root_parse_source, units) =
1388 load_units_for_source_file(path, flavor, &source_raw, options)?;
1389 let merged = merge_units(units)?;
1390 compile_parsed_output(
1391 source_raw,
1392 merged,
1393 CompileBehavior::DEFAULT,
1394 TypingMode::for_flavor(flavor),
1395 matches!(flavor, SourceFlavor::RustScript),
1396 )
1397 .map_err(SourcePathError::Source)
1398}
1399
1400fn run_with_compiler_stack<T, F>(f: F) -> T
1401where
1402 T: Send + 'static,
1403 F: FnOnce() -> T + Send + 'static,
1404{
1405 #[cfg(target_arch = "wasm32")]
1406 {
1407 f()
1408 }
1409
1410 #[cfg(not(target_arch = "wasm32"))]
1411 {
1412 const COMPILER_STACK_SIZE: usize = 32 * 1024 * 1024;
1413 let handle = std::thread::Builder::new()
1414 .name("pd-vm-compile".to_string())
1415 .stack_size(COMPILER_STACK_SIZE)
1416 .spawn(f)
1417 .expect("failed to spawn compiler thread");
1418 match handle.join() {
1419 Ok(value) => value,
1420 Err(payload) => std::panic::resume_unwind(payload),
1421 }
1422 }
1423}