1mod diagnostic;
29mod drop_glue;
30mod unit;
31
32pub use unit::CompilationUnit;
33
34use rayon::prelude::*;
35use tracing::{info, info_span};
36
37pub use diagnostic::{
38 ColorChoice, JsonDiagnostic, JsonSpan, JsonSuggestion, MultiFileFormatter,
39 MultiFileJsonFormatter, SourceInfo,
40};
41
42use std::io::Write;
43use std::path::PathBuf;
44use std::process::Command;
45use std::sync::atomic::{AtomicU64, Ordering};
46
47fn io_link_error(context: &str, err: std::io::Error) -> CompileError {
61 CompileError::without_span(ErrorKind::LinkError(format!("{}: {}", context, err)))
62}
63
64static TEMP_DIR_COUNTER: AtomicU64 = AtomicU64::new(0);
66
67struct TempLinkDir {
73 path: PathBuf,
75 obj_paths: Vec<PathBuf>,
77 runtime_path: PathBuf,
79 output_path: PathBuf,
81}
82
83impl TempLinkDir {
84 fn new() -> CompileResult<Self> {
90 let unique_id = TEMP_DIR_COUNTER.fetch_add(1, Ordering::Relaxed);
91 let path = std::env::temp_dir().join(format!("gruel-{}-{}", std::process::id(), unique_id));
92 std::fs::create_dir_all(&path)
93 .map_err(|e| io_link_error("failed to create temp directory", e))?;
94
95 let runtime_path = path.join("libgruel_runtime.a");
96 let output_path = path.join("output");
97
98 Ok(Self {
99 path,
100 obj_paths: Vec::new(),
101 runtime_path,
102 output_path,
103 })
104 }
105
106 fn write_object_files(&mut self, object_files: &[Vec<u8>]) -> CompileResult<()> {
111 for (i, obj_bytes) in object_files.iter().enumerate() {
112 let obj_path = self.path.join(format!("obj{}.o", i));
113 let mut file = std::fs::File::create(&obj_path)
114 .map_err(|e| io_link_error("failed to create temp object file", e))?;
115 file.write_all(obj_bytes)
116 .map_err(|e| io_link_error("failed to write temp object file", e))?;
117 self.obj_paths.push(obj_path);
118 }
119 Ok(())
120 }
121
122 fn write_runtime(&self, runtime_bytes: &[u8]) -> CompileResult<()> {
124 std::fs::write(&self.runtime_path, runtime_bytes)
125 .map_err(|e| io_link_error("failed to write runtime archive", e))
126 }
127
128 fn read_output(&self) -> CompileResult<Vec<u8>> {
130 std::fs::read(&self.output_path)
131 .map_err(|e| io_link_error("failed to read linked executable", e))
132 }
133}
134
135impl Drop for TempLinkDir {
136 fn drop(&mut self) {
137 let _ = std::fs::remove_dir_all(&self.path);
139 }
140}
141
142static RUNTIME_BYTES: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/libgruel_runtime.a"));
145
146pub use gruel_air::{Air, AnalyzedFunction, Sema, SemaOutput, StructDef, Type, TypeInternPool};
148pub use gruel_cfg::{Cfg, CfgBuilder, CfgOutput, OptLevel};
149pub use gruel_error::{
150 Applicability, CompileError, CompileErrors, CompileResult, CompileWarning, Diagnostic,
151 ErrorCode, ErrorKind, MultiErrorResult, PreviewFeature, PreviewFeatures, Suggestion,
152 WarningKind,
153};
154pub use gruel_lexer::{Lexer, Token, TokenKind};
155pub use gruel_parser::{Ast, Expr, Function, Item, Parser};
156pub use gruel_rir::{AstGen, Rir, RirPrinter};
157pub use gruel_span::{FileId, Span};
158pub use gruel_target::{Arch, Target};
159pub use lasso::{Spur, ThreadedRodeo};
160
161#[derive(Debug, Clone)]
169pub struct SourceFile<'a> {
170 pub path: &'a str,
172 pub source: &'a str,
174 pub file_id: FileId,
176}
177
178impl<'a> SourceFile<'a> {
179 pub fn new(path: &'a str, source: &'a str, file_id: FileId) -> Self {
181 Self {
182 path,
183 source,
184 file_id,
185 }
186 }
187}
188
189#[derive(Debug)]
194pub struct ParsedFile {
195 pub path: String,
197 pub file_id: FileId,
199 pub ast: Ast,
201 pub interner: ThreadedRodeo,
203}
204
205#[derive(Debug)]
209pub struct ParsedProgram {
210 pub files: Vec<ParsedFile>,
212 pub interner: ThreadedRodeo,
214}
215
216pub fn parse_all_files(sources: &[SourceFile<'_>]) -> MultiErrorResult<ParsedProgram> {
248 let mut parsed_files = Vec::with_capacity(sources.len());
251 let mut interner = ThreadedRodeo::new();
252
253 for source in sources {
254 let lexer = Lexer::with_interner_and_file_id(source.source, interner, source.file_id);
256
257 let (tokens, returned_interner) = lexer.tokenize().map_err(CompileErrors::from)?;
259 interner = returned_interner;
260
261 let parser = Parser::new(tokens, interner);
263 let (ast, returned_interner) = parser.parse()?;
264 interner = returned_interner;
265
266 parsed_files.push(ParsedFile {
267 path: source.path.to_string(),
268 file_id: source.file_id,
269 ast,
270 interner: ThreadedRodeo::new(),
273 });
274 }
275
276 Ok(ParsedProgram {
277 files: parsed_files,
278 interner,
279 })
280}
281
282#[derive(Debug)]
287pub struct MergedProgram {
288 pub ast: Ast,
290 pub interner: ThreadedRodeo,
292}
293
294pub struct ValidatedProgram {
299 pub rir: Rir,
301 pub interner: ThreadedRodeo,
303 pub file_paths: std::collections::HashMap<FileId, String>,
305}
306
307#[derive(Debug, Clone)]
309struct SymbolDef {
310 span: Span,
312 file_path: String,
314}
315
316pub fn merge_symbols(program: ParsedProgram) -> MultiErrorResult<MergedProgram> {
346 use std::collections::HashMap;
347
348 let _span = info_span!("merge_symbols", file_count = program.files.len()).entered();
349
350 let mut functions: HashMap<String, SymbolDef> = HashMap::new();
353 let mut structs: HashMap<String, SymbolDef> = HashMap::new();
354 let mut enums: HashMap<String, SymbolDef> = HashMap::new();
355
356 let mut all_items = Vec::new();
358 let mut errors: Vec<CompileError> = Vec::new();
359
360 let interner = &program.interner;
362
363 for file in &program.files {
364 for item in &file.ast.items {
365 match item {
366 Item::Function(func) => {
367 let name = interner.resolve(&func.name.name).to_string();
369 if let Some(first) = functions.get(&name) {
370 let err = CompileError::new(
372 ErrorKind::DuplicateTypeDefinition {
373 type_name: format!("function `{}`", name),
374 },
375 func.span,
376 )
377 .with_label(format!("first defined in {}", first.file_path), first.span);
378 errors.push(err);
379 } else {
380 functions.insert(
381 name.clone(),
382 SymbolDef {
383 span: func.span,
384 file_path: file.path.clone(),
385 },
386 );
387 }
388 }
389 Item::Struct(s) => {
390 let name = interner.resolve(&s.name.name).to_string();
392 if let Some(first) = structs.get(&name) {
393 let err = CompileError::new(
395 ErrorKind::DuplicateTypeDefinition {
396 type_name: format!("struct `{}`", name),
397 },
398 s.span,
399 )
400 .with_label(format!("first defined in {}", first.file_path), first.span);
401 errors.push(err);
402 } else if let Some(first) = enums.get(&name) {
403 let err = CompileError::new(
405 ErrorKind::DuplicateTypeDefinition {
406 type_name: format!("struct `{}` (conflicts with enum)", name),
407 },
408 s.span,
409 )
410 .with_label(
411 format!("enum first defined in {}", first.file_path),
412 first.span,
413 );
414 errors.push(err);
415 } else {
416 structs.insert(
417 name.clone(),
418 SymbolDef {
419 span: s.span,
420 file_path: file.path.clone(),
421 },
422 );
423 }
424 }
425 Item::Enum(e) => {
426 let name = interner.resolve(&e.name.name).to_string();
428 if let Some(first) = enums.get(&name) {
429 let err = CompileError::new(
431 ErrorKind::DuplicateTypeDefinition {
432 type_name: format!("enum `{}`", name),
433 },
434 e.span,
435 )
436 .with_label(format!("first defined in {}", first.file_path), first.span);
437 errors.push(err);
438 } else if let Some(first) = structs.get(&name) {
439 let err = CompileError::new(
441 ErrorKind::DuplicateTypeDefinition {
442 type_name: format!("enum `{}` (conflicts with struct)", name),
443 },
444 e.span,
445 )
446 .with_label(
447 format!("struct first defined in {}", first.file_path),
448 first.span,
449 );
450 errors.push(err);
451 } else {
452 enums.insert(
453 name.clone(),
454 SymbolDef {
455 span: e.span,
456 file_path: file.path.clone(),
457 },
458 );
459 }
460 }
461 Item::DropFn(_) | Item::Const(_) => {
462 }
465 Item::Error(_) => {
466 }
468 }
469 all_items.push(item.clone());
470 }
471 }
472
473 if !errors.is_empty() {
475 return Err(CompileErrors::from(errors));
476 }
477
478 info!(
479 function_count = functions.len(),
480 struct_count = structs.len(),
481 enum_count = enums.len(),
482 "symbol merging complete"
483 );
484
485 Ok(MergedProgram {
486 ast: Ast { items: all_items },
487 interner: program.interner,
488 })
489}
490
491pub fn validate_and_generate_rir_parallel(
510 program: ParsedProgram,
511) -> MultiErrorResult<ValidatedProgram> {
512 use std::collections::HashMap;
513
514 let _span = info_span!(
515 "validate_and_generate_rir",
516 file_count = program.files.len()
517 )
518 .entered();
519
520 let file_paths: HashMap<FileId, String> = program
522 .files
523 .iter()
524 .map(|f| (f.file_id, f.path.clone()))
525 .collect();
526
527 let mut functions: HashMap<String, SymbolDef> = HashMap::new();
529 let mut structs: HashMap<String, SymbolDef> = HashMap::new();
530 let mut enums: HashMap<String, SymbolDef> = HashMap::new();
531 let mut errors: Vec<CompileError> = Vec::new();
532
533 let interner = &program.interner;
534
535 for file in &program.files {
536 for item in &file.ast.items {
537 match item {
538 Item::Function(func) => {
539 let name = interner.resolve(&func.name.name).to_string();
540 if let Some(first) = functions.get(&name) {
541 let err = CompileError::new(
542 ErrorKind::DuplicateTypeDefinition {
543 type_name: format!("function `{}`", name),
544 },
545 func.span,
546 )
547 .with_label(format!("first defined in {}", first.file_path), first.span);
548 errors.push(err);
549 } else {
550 functions.insert(
551 name.clone(),
552 SymbolDef {
553 span: func.span,
554 file_path: file.path.clone(),
555 },
556 );
557 }
558 }
559 Item::Struct(s) => {
560 let name = interner.resolve(&s.name.name).to_string();
561 if let Some(first) = structs.get(&name) {
562 let err = CompileError::new(
563 ErrorKind::DuplicateTypeDefinition {
564 type_name: format!("struct `{}`", name),
565 },
566 s.span,
567 )
568 .with_label(format!("first defined in {}", first.file_path), first.span);
569 errors.push(err);
570 } else if let Some(first) = enums.get(&name) {
571 let err = CompileError::new(
572 ErrorKind::DuplicateTypeDefinition {
573 type_name: format!("struct `{}` (conflicts with enum)", name),
574 },
575 s.span,
576 )
577 .with_label(
578 format!("enum first defined in {}", first.file_path),
579 first.span,
580 );
581 errors.push(err);
582 } else {
583 structs.insert(
584 name.clone(),
585 SymbolDef {
586 span: s.span,
587 file_path: file.path.clone(),
588 },
589 );
590 }
591 }
592 Item::Enum(e) => {
593 let name = interner.resolve(&e.name.name).to_string();
594 if let Some(first) = enums.get(&name) {
595 let err = CompileError::new(
596 ErrorKind::DuplicateTypeDefinition {
597 type_name: format!("enum `{}`", name),
598 },
599 e.span,
600 )
601 .with_label(format!("first defined in {}", first.file_path), first.span);
602 errors.push(err);
603 } else if let Some(first) = structs.get(&name) {
604 let err = CompileError::new(
605 ErrorKind::DuplicateTypeDefinition {
606 type_name: format!("enum `{}` (conflicts with struct)", name),
607 },
608 e.span,
609 )
610 .with_label(
611 format!("struct first defined in {}", first.file_path),
612 first.span,
613 );
614 errors.push(err);
615 } else {
616 enums.insert(
617 name.clone(),
618 SymbolDef {
619 span: e.span,
620 file_path: file.path.clone(),
621 },
622 );
623 }
624 }
625 Item::DropFn(_) | Item::Const(_) => {
626 }
628 Item::Error(_) => {
629 }
631 }
632 }
633 }
634
635 if !errors.is_empty() {
636 return Err(CompileErrors::from(errors));
637 }
638
639 info!(
640 function_count = functions.len(),
641 struct_count = structs.len(),
642 enum_count = enums.len(),
643 "symbol validation complete"
644 );
645
646 let interner = program.interner;
648 let rirs: Vec<Rir> = {
649 let _span = info_span!("parallel_astgen").entered();
650
651 program
652 .files
653 .par_iter()
654 .map(|file| {
655 let astgen = AstGen::new(&file.ast, &interner);
656 astgen.generate()
657 })
658 .collect()
659 };
660
661 let rir = {
663 let _span = info_span!("merge_rirs", rir_count = rirs.len()).entered();
664 Rir::merge(&rirs)
665 };
666
667 info!(instruction_count = rir.len(), "RIR generation complete");
668
669 Ok(ValidatedProgram {
670 rir,
671 interner,
672 file_paths,
673 })
674}
675
676#[derive(Debug, Clone, PartialEq, Eq, Default)]
681pub enum LinkerMode {
682 #[default]
684 Internal,
685 System(String),
687}
688
689#[derive(Debug, Clone)]
706pub struct CompileOptions {
707 pub target: Target,
709 pub linker: LinkerMode,
711 pub opt_level: OptLevel,
713 pub preview_features: PreviewFeatures,
715 pub jobs: usize,
717 pub capture_comptime_dbg: bool,
721}
722
723impl Default for CompileOptions {
724 fn default() -> Self {
725 Self {
726 target: Target::host(),
727 linker: LinkerMode::Internal,
728 opt_level: OptLevel::default(),
729 preview_features: PreviewFeatures::new(),
730 jobs: 0,
731 capture_comptime_dbg: false,
732 }
733 }
734}
735
736#[derive(Debug)]
740pub struct FunctionWithCfg {
741 pub analyzed: AnalyzedFunction,
743 pub cfg: Cfg,
745}
746
747pub struct CompileState {
752 pub ast: Ast,
754 pub interner: ThreadedRodeo,
756 pub rir: Rir,
758 pub functions: Vec<FunctionWithCfg>,
760 pub type_pool: TypeInternPool,
762 pub strings: Vec<String>,
764 pub warnings: Vec<CompileWarning>,
766 pub comptime_dbg_output: Vec<String>,
768}
769
770#[derive(Debug)]
776pub struct CompileOutput {
777 pub elf: Vec<u8>,
779 pub warnings: Vec<CompileWarning>,
781}
782
783pub fn compile_frontend(source: &str) -> MultiErrorResult<CompileState> {
794 compile_frontend_with_options(source, &PreviewFeatures::new())
795}
796
797pub fn compile_frontend_with_options(
805 source: &str,
806 preview_features: &PreviewFeatures,
807) -> MultiErrorResult<CompileState> {
808 compile_frontend_with_options_full(source, preview_features, false)
809}
810
811pub fn compile_frontend_with_options_full(
817 source: &str,
818 preview_features: &PreviewFeatures,
819 suppress_comptime_dbg_print: bool,
820) -> MultiErrorResult<CompileState> {
821 let _span = info_span!("frontend", source_bytes = source.len()).entered();
822
823 let (tokens, interner) = {
825 let _span = info_span!("lexer").entered();
826 let lexer = Lexer::new(source);
827 let (tokens, interner) = lexer.tokenize().map_err(CompileErrors::from)?;
828 info!(token_count = tokens.len(), "lexing complete");
829 (tokens, interner)
830 };
831
832 let (ast, interner) = {
834 let _span = info_span!("parser").entered();
835 let parser = Parser::new(tokens, interner);
836 let (ast, interner) = parser.parse()?;
837 info!(item_count = ast.items.len(), "parsing complete");
838 (ast, interner)
839 };
840
841 compile_frontend_from_ast_with_options_full(
842 ast,
843 interner,
844 preview_features,
845 suppress_comptime_dbg_print,
846 )
847}
848
849pub fn compile_frontend_from_ast(
857 ast: Ast,
858 interner: ThreadedRodeo,
859) -> MultiErrorResult<CompileState> {
860 compile_frontend_from_ast_with_options(ast, interner, &PreviewFeatures::new())
861}
862
863pub fn compile_frontend_from_ast_with_options(
872 ast: Ast,
873 interner: ThreadedRodeo,
874 preview_features: &PreviewFeatures,
875) -> MultiErrorResult<CompileState> {
876 compile_frontend_from_ast_with_options_full(ast, interner, preview_features, false)
877}
878
879pub fn compile_frontend_from_ast_with_options_full(
882 ast: Ast,
883 interner: ThreadedRodeo,
884 preview_features: &PreviewFeatures,
885 suppress_comptime_dbg_print: bool,
886) -> MultiErrorResult<CompileState> {
887 let (rir, interner) = {
889 let _span = info_span!("astgen").entered();
890 let astgen = AstGen::new(&ast, &interner);
891 let rir = astgen.generate();
892 info!(instruction_count = rir.len(), "AST generation complete");
893 (rir, interner)
894 };
895
896 let sema_output = {
898 let _span = info_span!("sema").entered();
899 let mut sema = Sema::new(&rir, &interner, preview_features.clone());
900 sema.set_suppress_comptime_dbg_print(suppress_comptime_dbg_print);
901 let output = sema.analyze_all()?;
902 info!(
903 function_count = output.functions.len(),
904 struct_count = output.type_pool.stats().struct_count,
905 "semantic analysis complete"
906 );
907 output
908 };
909
910 let drop_glue_functions = drop_glue::synthesize_drop_glue(&sema_output.type_pool, &interner);
912
913 let all_functions: Vec<_> = sema_output
916 .functions
917 .into_iter()
918 .filter(|f| f.air.return_type() != Type::COMPTIME_TYPE)
919 .chain(drop_glue_functions)
920 .collect();
921
922 let (functions, warnings) = {
924 let _span = info_span!("cfg_construction").entered();
925
926 let results: Vec<(FunctionWithCfg, Vec<CompileWarning>)> = all_functions
928 .into_par_iter()
929 .map(|func| {
930 let cfg_output = CfgBuilder::build(&func, &sema_output.type_pool);
931
932 (
933 FunctionWithCfg {
934 analyzed: func,
935 cfg: cfg_output.cfg,
936 },
937 cfg_output.warnings,
938 )
939 })
940 .collect();
941
942 let mut functions = Vec::with_capacity(results.len());
944 let mut warnings = sema_output.warnings;
945 for (func, func_warnings) in results {
946 functions.push(func);
947 warnings.extend(func_warnings);
948 }
949
950 info!(
951 function_count = functions.len(),
952 "CFG construction complete"
953 );
954 (functions, warnings)
955 };
956
957 Ok(CompileState {
958 ast,
959 interner,
960 rir,
961 functions,
962 type_pool: sema_output.type_pool,
963 strings: sema_output.strings,
964 warnings,
965 comptime_dbg_output: sema_output.comptime_dbg_output,
966 })
967}
968
969pub fn compile_frontend_from_rir_with_options(
986 rir: Rir,
987 interner: ThreadedRodeo,
988 preview_features: &PreviewFeatures,
989) -> MultiErrorResult<CompileStateFromRir> {
990 compile_frontend_from_rir_with_file_paths(
991 rir,
992 interner,
993 preview_features,
994 std::collections::HashMap::new(),
995 )
996}
997
998pub fn compile_frontend_from_rir_with_file_paths(
1003 rir: Rir,
1004 interner: ThreadedRodeo,
1005 preview_features: &PreviewFeatures,
1006 file_paths: std::collections::HashMap<FileId, String>,
1007) -> MultiErrorResult<CompileStateFromRir> {
1008 let sema_output = {
1010 let _span = info_span!("sema").entered();
1011 let mut sema = Sema::new(&rir, &interner, preview_features.clone());
1012 sema.set_file_paths(file_paths);
1013 let output = sema.analyze_all()?;
1014 info!(
1015 function_count = output.functions.len(),
1016 struct_count = output.type_pool.stats().struct_count,
1017 "semantic analysis complete"
1018 );
1019 output
1020 };
1021
1022 let drop_glue_functions = drop_glue::synthesize_drop_glue(&sema_output.type_pool, &interner);
1024
1025 let all_functions: Vec<_> = sema_output
1028 .functions
1029 .into_iter()
1030 .filter(|f| f.air.return_type() != Type::COMPTIME_TYPE)
1031 .chain(drop_glue_functions)
1032 .collect();
1033
1034 let (functions, warnings) = {
1036 let _span = info_span!("cfg_construction").entered();
1037
1038 let results: Vec<(FunctionWithCfg, Vec<CompileWarning>)> = all_functions
1040 .into_par_iter()
1041 .map(|func| {
1042 let cfg_output = CfgBuilder::build(&func, &sema_output.type_pool);
1043
1044 (
1045 FunctionWithCfg {
1046 analyzed: func,
1047 cfg: cfg_output.cfg,
1048 },
1049 cfg_output.warnings,
1050 )
1051 })
1052 .collect();
1053
1054 let mut functions = Vec::with_capacity(results.len());
1056 let mut warnings = sema_output.warnings;
1057 for (func, func_warnings) in results {
1058 functions.push(func);
1059 warnings.extend(func_warnings);
1060 }
1061
1062 info!(
1063 function_count = functions.len(),
1064 "CFG construction complete"
1065 );
1066 (functions, warnings)
1067 };
1068
1069 Ok(CompileStateFromRir {
1070 interner,
1071 rir,
1072 functions,
1073 type_pool: sema_output.type_pool,
1074 strings: sema_output.strings,
1075 warnings,
1076 comptime_dbg_output: sema_output.comptime_dbg_output,
1077 })
1078}
1079
1080pub struct CompileStateFromRir {
1085 pub interner: ThreadedRodeo,
1087 pub rir: Rir,
1089 pub functions: Vec<FunctionWithCfg>,
1091 pub type_pool: TypeInternPool,
1093 pub strings: Vec<String>,
1095 pub warnings: Vec<CompileWarning>,
1097 pub comptime_dbg_output: Vec<String>,
1099}
1100
1101pub fn compile(source: &str) -> MultiErrorResult<CompileOutput> {
1109 compile_with_options(source, &CompileOptions::default())
1110}
1111
1112pub fn compile_with_options(
1119 source: &str,
1120 options: &CompileOptions,
1121) -> MultiErrorResult<CompileOutput> {
1122 let sources = vec![SourceFile::new("<source>", source, FileId::new(1))];
1124 compile_multi_file_with_options(&sources, options)
1125}
1126
1127pub fn compile_multi_file_with_options(
1162 sources: &[SourceFile<'_>],
1163 options: &CompileOptions,
1164) -> MultiErrorResult<CompileOutput> {
1165 if options.jobs > 0 {
1169 let _ = rayon::ThreadPoolBuilder::new()
1172 .num_threads(options.jobs)
1173 .build_global();
1174 }
1175
1176 let total_source_bytes: usize = sources.iter().map(|s| s.source.len()).sum();
1177 let _span = info_span!(
1178 "compile",
1179 target = %options.target,
1180 file_count = sources.len(),
1181 source_bytes = total_source_bytes
1182 )
1183 .entered();
1184
1185 let mut unit = CompilationUnit::new(sources.to_vec(), options.clone());
1187 unit.run_all()
1188}
1189
1190fn link_system_with_warnings(
1192 options: &CompileOptions,
1193 object_files: &[Vec<u8>],
1194 linker_cmd: &str,
1195 warnings: &[CompileWarning],
1196) -> MultiErrorResult<CompileOutput> {
1197 let _span = info_span!("linker", mode = "system", command = linker_cmd).entered();
1198
1199 let mut temp_dir = TempLinkDir::new().map_err(CompileErrors::from)?;
1201 temp_dir
1202 .write_object_files(object_files)
1203 .map_err(CompileErrors::from)?;
1204 temp_dir
1205 .write_runtime(RUNTIME_BYTES)
1206 .map_err(CompileErrors::from)?;
1207
1208 let mut cmd = Command::new(linker_cmd);
1210
1211 if options.target.is_macho() {
1215 cmd.arg("-nostartfiles");
1217 cmd.arg("-arch").arg("arm64");
1218 cmd.arg("-e").arg("__main");
1219 } else {
1220 cmd.arg("-nostartfiles");
1225 }
1226
1227 cmd.arg("-o");
1228 cmd.arg(&temp_dir.output_path);
1229
1230 for path in &temp_dir.obj_paths {
1232 cmd.arg(path);
1233 }
1234
1235 cmd.arg(&temp_dir.runtime_path);
1237
1238 if options.target.is_macho() {
1240 cmd.arg("-lSystem");
1241 }
1242
1243 let output = cmd.output().map_err(|e| {
1245 CompileErrors::from(CompileError::without_span(ErrorKind::LinkError(format!(
1246 "failed to execute linker '{}': {}",
1247 linker_cmd, e
1248 ))))
1249 })?;
1250
1251 if !output.status.success() {
1252 let stderr = String::from_utf8_lossy(&output.stderr);
1253 return Err(CompileErrors::from(CompileError::without_span(
1255 ErrorKind::LinkError(format!("linker '{}' failed: {}", linker_cmd, stderr)),
1256 )));
1257 }
1258
1259 let elf = temp_dir.read_output().map_err(CompileErrors::from)?;
1261 info!(
1262 object_count = object_files.len(),
1263 output_bytes = elf.len(),
1264 "linking complete"
1265 );
1266
1267 Ok(CompileOutput {
1269 elf,
1270 warnings: warnings.to_vec(),
1271 })
1272}
1273
1274pub fn compile_backend(
1288 functions: &[FunctionWithCfg],
1289 type_pool: &TypeInternPool,
1290 strings: &[String],
1291 interner: &ThreadedRodeo,
1292 options: &CompileOptions,
1293 warnings: &[CompileWarning],
1294) -> MultiErrorResult<CompileOutput> {
1295 let _main_fn = functions
1297 .iter()
1298 .find(|f| f.analyzed.name == "main")
1299 .ok_or_else(|| {
1300 CompileErrors::from(CompileError::without_span(ErrorKind::NoMainFunction))
1301 })?;
1302
1303 generate_llvm_objects_and_link(functions, type_pool, strings, interner, options, warnings)
1304}
1305
1306fn generate_llvm_objects_and_link(
1313 functions: &[FunctionWithCfg],
1314 type_pool: &TypeInternPool,
1315 strings: &[String],
1316 interner: &ThreadedRodeo,
1317 options: &CompileOptions,
1318 warnings: &[CompileWarning],
1319) -> MultiErrorResult<CompileOutput> {
1320 let _span = info_span!("codegen", backend = "llvm").entered();
1321
1322 let cfgs: Vec<&Cfg> = functions.iter().map(|f| &f.cfg).collect();
1323 let object_bytes =
1324 gruel_codegen_llvm::generate(&cfgs, type_pool, strings, interner, options.opt_level)
1325 .map_err(CompileErrors::from)?;
1326
1327 let object_files = vec![object_bytes];
1329
1330 let linker_cmd = match &options.linker {
1333 LinkerMode::System(cmd) => cmd.clone(),
1334 LinkerMode::Internal => "cc".to_string(),
1335 };
1336 link_system_with_warnings(options, &object_files, &linker_cmd, warnings)
1337}
1338
1339pub fn generate_llvm_ir(
1344 functions: &[FunctionWithCfg],
1345 type_pool: &TypeInternPool,
1346 strings: &[String],
1347 interner: &ThreadedRodeo,
1348 opt_level: OptLevel,
1349) -> CompileResult<String> {
1350 let cfgs: Vec<&Cfg> = functions.iter().map(|f| &f.cfg).collect();
1351 gruel_codegen_llvm::generate_ir(&cfgs, type_pool, strings, interner, opt_level)
1352}
1353
1354#[derive(Debug)]
1363pub struct AirOutput {
1364 pub ast: Ast,
1366 pub interner: ThreadedRodeo,
1368 pub rir: Rir,
1370 pub functions: Vec<AnalyzedFunction>,
1372 pub type_pool: TypeInternPool,
1374 pub strings: Vec<String>,
1376 pub warnings: Vec<CompileWarning>,
1378}
1379
1380pub fn compile_to_air(source: &str) -> MultiErrorResult<AirOutput> {
1395 let lexer = Lexer::new(source);
1397 let (tokens, interner) = lexer.tokenize().map_err(CompileErrors::from)?;
1398
1399 let parser = Parser::new(tokens, interner);
1401 let (ast, interner) = parser.parse()?;
1402
1403 let astgen = AstGen::new(&ast, &interner);
1405 let rir = astgen.generate();
1406
1407 let sema = Sema::new(&rir, &interner, PreviewFeatures::new());
1409 let sema_output = sema.analyze_all()?;
1410
1411 Ok(AirOutput {
1412 ast,
1413 interner,
1414 rir,
1415 functions: sema_output.functions,
1416 type_pool: sema_output.type_pool,
1417 strings: sema_output.strings,
1418 warnings: sema_output.warnings,
1419 })
1420}
1421
1422pub fn compile_to_cfg(source: &str) -> MultiErrorResult<CompileState> {
1439 compile_frontend(source)
1440}
1441
1442#[cfg(test)]
1443mod tests {
1444 use super::*;
1445
1446 #[test]
1447 fn test_compile_simple() {
1448 let output = compile("fn main() -> i32 { 42 }").unwrap();
1449 let magic = &output.elf[0..4];
1451 let is_elf = magic == [0x7F, b'E', b'L', b'F'];
1452 let is_macho = magic == 0xFEEDFACF_u32.to_le_bytes();
1453 assert!(
1454 is_elf || is_macho,
1455 "should produce valid ELF or Mach-O binary"
1456 );
1457 }
1458
1459 #[test]
1460 fn test_compile_no_main() {
1461 let result = compile("fn foo() -> i32 { 42 }");
1462 assert!(result.is_err());
1463 }
1464
1465 #[test]
1466 fn test_unused_variable_warning() {
1467 let output = compile("fn main() -> i32 { let x = 42; 0 }").unwrap();
1468 assert_eq!(output.warnings.len(), 1);
1469 assert!(output.warnings[0].to_string().contains("unused variable"));
1470 assert!(output.warnings[0].to_string().contains("'x'"));
1471 }
1472
1473 #[test]
1474 fn test_underscore_prefix_no_warning() {
1475 let output = compile("fn main() -> i32 { let _x = 42; 0 }").unwrap();
1476 assert_eq!(output.warnings.len(), 0);
1477 }
1478
1479 #[test]
1480 fn test_used_variable_no_warning() {
1481 let output = compile("fn main() -> i32 { let x = 42; x }").unwrap();
1482 assert_eq!(output.warnings.len(), 0);
1483 }
1484
1485 #[test]
1486 fn test_compile_frontend_includes_warnings() {
1487 let state = compile_frontend("fn main() -> i32 { let x = 42; 0 }").unwrap();
1488 assert_eq!(state.warnings.len(), 1);
1489 assert!(state.warnings[0].to_string().contains("unused variable"));
1490 }
1491
1492 #[test]
1493 fn test_multiple_errors_collected() {
1494 let source = r#"
1498 fn foo() -> i32 { true }
1499 fn bar() -> i32 { false }
1500 fn main() -> i32 { foo() + bar() }
1501 "#;
1502 let result = compile_frontend(source);
1503 let errors = match result {
1504 Ok(_) => panic!("expected error, got success"),
1505 Err(e) => e,
1506 };
1507
1508 assert!(
1510 errors.len() >= 2,
1511 "expected at least 2 errors, got {}",
1512 errors.len()
1513 );
1514
1515 for error in errors.iter() {
1517 assert!(
1518 error.to_string().contains("type mismatch"),
1519 "expected type mismatch error, got: {}",
1520 error
1521 );
1522 }
1523 }
1524
1525 #[test]
1526 fn test_multiple_errors_display() {
1527 let source = r#"
1530 fn foo() -> i32 { true }
1531 fn bar() -> i32 { false }
1532 fn main() -> i32 { foo() + bar() }
1533 "#;
1534 let errors = match compile_frontend(source) {
1535 Ok(_) => panic!("expected error, got success"),
1536 Err(e) => e,
1537 };
1538
1539 let display = errors.to_string();
1541 assert!(
1542 display.contains("type mismatch"),
1543 "display should contain error message"
1544 );
1545 if errors.len() > 1 {
1546 assert!(
1547 display.contains("more error"),
1548 "display should indicate more errors"
1549 );
1550 }
1551 }
1552
1553 #[test]
1554 fn test_single_error_still_works() {
1555 let source = "fn main() -> i32 { true }";
1557 let errors = match compile_frontend(source) {
1558 Ok(_) => panic!("expected error, got success"),
1559 Err(e) => e,
1560 };
1561
1562 assert_eq!(errors.len(), 1);
1563 assert!(
1564 errors
1565 .first()
1566 .unwrap()
1567 .to_string()
1568 .contains("type mismatch")
1569 );
1570 }
1571
1572 #[test]
1577 fn test_merge_symbols_no_duplicates() {
1578 let sources = vec![
1579 SourceFile::new("main.gruel", "fn main() -> i32 { 0 }", FileId::new(1)),
1580 SourceFile::new("utils.gruel", "fn helper() -> i32 { 42 }", FileId::new(2)),
1581 ];
1582 let parsed = parse_all_files(&sources).unwrap();
1583 let merged = merge_symbols(parsed);
1584 assert!(merged.is_ok(), "merge should succeed with no duplicates");
1585
1586 let program = merged.unwrap();
1587 assert_eq!(program.ast.items.len(), 2, "should have 2 items");
1588 }
1589
1590 #[test]
1591 fn test_merge_symbols_duplicate_function() {
1592 let sources = vec![
1593 SourceFile::new("a.gruel", "fn foo() -> i32 { 1 }", FileId::new(1)),
1594 SourceFile::new("b.gruel", "fn foo() -> i32 { 2 }", FileId::new(2)),
1595 ];
1596 let parsed = parse_all_files(&sources).unwrap();
1597 let result = merge_symbols(parsed);
1598 assert!(result.is_err(), "merge should fail with duplicate function");
1599
1600 let errors = result.unwrap_err();
1601 assert_eq!(errors.len(), 1, "should have 1 error");
1602 let err_msg = errors.first().unwrap().to_string();
1603 assert!(
1604 err_msg.contains("function `foo`"),
1605 "error should mention the function name"
1606 );
1607 }
1608
1609 #[test]
1610 fn test_merge_symbols_duplicate_struct() {
1611 let sources = vec![
1612 SourceFile::new(
1613 "a.gruel",
1614 "struct Point { x: i32 } fn main() -> i32 { 0 }",
1615 FileId::new(1),
1616 ),
1617 SourceFile::new("b.gruel", "struct Point { y: i32 }", FileId::new(2)),
1618 ];
1619 let parsed = parse_all_files(&sources).unwrap();
1620 let result = merge_symbols(parsed);
1621 assert!(result.is_err(), "merge should fail with duplicate struct");
1622
1623 let errors = result.unwrap_err();
1624 assert_eq!(errors.len(), 1, "should have 1 error");
1625 let err_msg = errors.first().unwrap().to_string();
1626 assert!(
1627 err_msg.contains("struct `Point`"),
1628 "error should mention the struct name"
1629 );
1630 }
1631
1632 #[test]
1633 fn test_merge_symbols_duplicate_enum() {
1634 let sources = vec![
1635 SourceFile::new(
1636 "a.gruel",
1637 "enum Color { Red } fn main() -> i32 { 0 }",
1638 FileId::new(1),
1639 ),
1640 SourceFile::new("b.gruel", "enum Color { Blue }", FileId::new(2)),
1641 ];
1642 let parsed = parse_all_files(&sources).unwrap();
1643 let result = merge_symbols(parsed);
1644 assert!(result.is_err(), "merge should fail with duplicate enum");
1645
1646 let errors = result.unwrap_err();
1647 assert_eq!(errors.len(), 1, "should have 1 error");
1648 let err_msg = errors.first().unwrap().to_string();
1649 assert!(
1650 err_msg.contains("enum `Color`"),
1651 "error should mention the enum name"
1652 );
1653 }
1654
1655 #[test]
1656 fn test_merge_symbols_struct_enum_conflict() {
1657 let sources = vec![
1659 SourceFile::new(
1660 "a.gruel",
1661 "struct Foo { x: i32 } fn main() -> i32 { 0 }",
1662 FileId::new(1),
1663 ),
1664 SourceFile::new("b.gruel", "enum Foo { Bar }", FileId::new(2)),
1665 ];
1666 let parsed = parse_all_files(&sources).unwrap();
1667 let result = merge_symbols(parsed);
1668 assert!(
1669 result.is_err(),
1670 "merge should fail when struct and enum have same name"
1671 );
1672
1673 let errors = result.unwrap_err();
1674 assert_eq!(errors.len(), 1, "should have 1 error");
1675 let err_msg = errors.first().unwrap().to_string();
1676 assert!(
1677 err_msg.contains("Foo") && err_msg.contains("conflicts"),
1678 "error should mention the conflict: {}",
1679 err_msg
1680 );
1681 }
1682
1683 #[test]
1684 fn test_merge_symbols_multiple_duplicates() {
1685 let sources = vec![
1687 SourceFile::new(
1688 "a.gruel",
1689 "fn foo() -> i32 { 1 } fn bar() -> i32 { 2 }",
1690 FileId::new(1),
1691 ),
1692 SourceFile::new(
1693 "b.gruel",
1694 "fn foo() -> i32 { 3 } fn bar() -> i32 { 4 }",
1695 FileId::new(2),
1696 ),
1697 ];
1698 let parsed = parse_all_files(&sources).unwrap();
1699 let result = merge_symbols(parsed);
1700 assert!(
1701 result.is_err(),
1702 "merge should fail with duplicate functions"
1703 );
1704
1705 let errors = result.unwrap_err();
1706 assert_eq!(errors.len(), 2, "should have 2 errors for 2 duplicates");
1707 }
1708
1709 #[test]
1710 fn test_merge_symbols_with_struct_methods() {
1711 let sources = vec![
1713 SourceFile::new(
1714 "a.gruel",
1715 "struct Point { x: i32, fn get_x(self) -> i32 { self.x } } fn main() -> i32 { 0 }",
1716 FileId::new(1),
1717 ),
1718 SourceFile::new("b.gruel", "fn helper() -> i32 { 42 }", FileId::new(2)),
1719 ];
1720 let parsed = parse_all_files(&sources).unwrap();
1721 let result = merge_symbols(parsed);
1722 assert!(result.is_ok(), "struct methods should not cause conflicts");
1723 }
1724
1725 #[test]
1730 fn test_cross_file_function_call() {
1731 let sources = vec![
1733 SourceFile::new(
1734 "main.gruel",
1735 "fn main() -> i32 { helper() }",
1736 FileId::new(1),
1737 ),
1738 SourceFile::new("utils.gruel", "fn helper() -> i32 { 42 }", FileId::new(2)),
1739 ];
1740 let result = compile_multi_file_with_options(&sources, &CompileOptions::default());
1741 assert!(
1742 result.is_ok(),
1743 "cross-file function call should compile: {:?}",
1744 result.err()
1745 );
1746 }
1747
1748 #[test]
1749 fn test_cross_file_function_call_with_args() {
1750 let sources = vec![
1752 SourceFile::new(
1753 "main.gruel",
1754 "fn main() -> i32 { add(10, 32) }",
1755 FileId::new(1),
1756 ),
1757 SourceFile::new(
1758 "utils.gruel",
1759 "fn add(a: i32, b: i32) -> i32 { a + b }",
1760 FileId::new(2),
1761 ),
1762 ];
1763 let result = compile_multi_file_with_options(&sources, &CompileOptions::default());
1764 assert!(
1765 result.is_ok(),
1766 "cross-file function call with args should compile: {:?}",
1767 result.err()
1768 );
1769 }
1770
1771 #[test]
1772 fn test_cross_file_struct_usage() {
1773 let sources = vec![
1775 SourceFile::new(
1776 "main.gruel",
1777 "fn main() -> i32 { let p = Point { x: 1, y: 2 }; p.x + p.y }",
1778 FileId::new(1),
1779 ),
1780 SourceFile::new(
1781 "types.gruel",
1782 "struct Point { x: i32, y: i32 }",
1783 FileId::new(2),
1784 ),
1785 ];
1786 let result = compile_multi_file_with_options(&sources, &CompileOptions::default());
1787 assert!(
1788 result.is_ok(),
1789 "cross-file struct usage should compile: {:?}",
1790 result.err()
1791 );
1792 }
1793
1794 #[test]
1795 fn test_cross_file_struct_as_function_param() {
1796 let sources = vec![
1798 SourceFile::new(
1799 "main.gruel",
1800 "fn main() -> i32 { let p = Point { x: 10, y: 5 }; get_sum(p) }",
1801 FileId::new(1),
1802 ),
1803 SourceFile::new(
1804 "types.gruel",
1805 "struct Point { x: i32, y: i32 }",
1806 FileId::new(2),
1807 ),
1808 SourceFile::new(
1809 "utils.gruel",
1810 "fn get_sum(p: Point) -> i32 { p.x + p.y }",
1811 FileId::new(3),
1812 ),
1813 ];
1814 let result = compile_multi_file_with_options(&sources, &CompileOptions::default());
1815 assert!(
1816 result.is_ok(),
1817 "cross-file struct as function param should compile: {:?}",
1818 result.err()
1819 );
1820 }
1821
1822 #[test]
1823 fn test_cross_file_enum_usage() {
1824 let sources = vec![
1826 SourceFile::new(
1827 "main.gruel",
1828 r#"fn main() -> i32 {
1829 let c = Color::Red;
1830 match c { Color::Red => 1, Color::Green => 2, Color::Blue => 3 }
1831 }"#,
1832 FileId::new(1),
1833 ),
1834 SourceFile::new(
1835 "types.gruel",
1836 "enum Color { Red, Green, Blue }",
1837 FileId::new(2),
1838 ),
1839 ];
1840 let result = compile_multi_file_with_options(&sources, &CompileOptions::default());
1841 assert!(
1842 result.is_ok(),
1843 "cross-file enum usage should compile: {:?}",
1844 result.err()
1845 );
1846 }
1847
1848 #[test]
1849 fn test_cross_file_no_main_function() {
1850 let sources = vec![
1852 SourceFile::new("a.gruel", "fn foo() -> i32 { 1 }", FileId::new(1)),
1853 SourceFile::new("b.gruel", "fn bar() -> i32 { 2 }", FileId::new(2)),
1854 ];
1855 let result = compile_multi_file_with_options(&sources, &CompileOptions::default());
1856 assert!(result.is_err(), "should fail without main function");
1857
1858 let errors = result.unwrap_err();
1859 let err_msg = errors.first().unwrap().to_string();
1860 assert!(
1861 err_msg.contains("main") && err_msg.contains("function"),
1862 "error should mention missing main function: {}",
1863 err_msg
1864 );
1865 }
1866
1867 #[test]
1868 fn test_cross_file_duplicate_main() {
1869 let sources = vec![
1871 SourceFile::new("a.gruel", "fn main() -> i32 { 1 }", FileId::new(1)),
1872 SourceFile::new("b.gruel", "fn main() -> i32 { 2 }", FileId::new(2)),
1873 ];
1874 let result = compile_multi_file_with_options(&sources, &CompileOptions::default());
1875 assert!(result.is_err(), "should fail with duplicate main");
1876
1877 let errors = result.unwrap_err();
1878 let err_msg = errors.first().unwrap().to_string();
1879 assert!(
1880 err_msg.contains("main"),
1881 "error should mention duplicate main: {}",
1882 err_msg
1883 );
1884 }
1885
1886 #[test]
1887 fn test_cross_file_undefined_function() {
1888 let sources = vec![
1890 SourceFile::new(
1891 "main.gruel",
1892 "fn main() -> i32 { nonexistent() }",
1893 FileId::new(1),
1894 ),
1895 SourceFile::new("utils.gruel", "fn helper() -> i32 { 42 }", FileId::new(2)),
1896 ];
1897 let result = compile_multi_file_with_options(&sources, &CompileOptions::default());
1898 assert!(result.is_err(), "should fail with undefined function");
1899
1900 let errors = result.unwrap_err();
1901 let err_msg = errors.first().unwrap().to_string();
1902 assert!(
1903 err_msg.contains("nonexistent") || err_msg.contains("undefined"),
1904 "error should mention undefined function: {}",
1905 err_msg
1906 );
1907 }
1908
1909 #[test]
1910 fn test_cross_file_three_files_chain() {
1911 let sources = vec![
1913 SourceFile::new(
1914 "main.gruel",
1915 "fn main() -> i32 { compute(6, 7) }",
1916 FileId::new(1),
1917 ),
1918 SourceFile::new(
1919 "utils.gruel",
1920 "fn compute(a: i32, b: i32) -> i32 { multiply(a, b) }",
1921 FileId::new(2),
1922 ),
1923 SourceFile::new(
1924 "math.gruel",
1925 "fn multiply(x: i32, y: i32) -> i32 { x * y }",
1926 FileId::new(3),
1927 ),
1928 ];
1929 let result = compile_multi_file_with_options(&sources, &CompileOptions::default());
1930 assert!(
1931 result.is_ok(),
1932 "chain of cross-file calls should compile: {:?}",
1933 result.err()
1934 );
1935 }
1936
1937 #[test]
1938 fn test_cross_file_mutual_calls() {
1939 let sources = vec![
1941 SourceFile::new(
1942 "main.gruel",
1943 r#"fn main() -> i32 { is_even(4) }
1944 fn is_even(n: i32) -> i32 { if n == 0 { 1 } else { is_odd(n - 1) } }"#,
1945 FileId::new(1),
1946 ),
1947 SourceFile::new(
1948 "utils.gruel",
1949 "fn is_odd(n: i32) -> i32 { if n == 0 { 0 } else { is_even(n - 1) } }",
1950 FileId::new(2),
1951 ),
1952 ];
1953 let result = compile_multi_file_with_options(&sources, &CompileOptions::default());
1954 assert!(
1955 result.is_ok(),
1956 "mutual cross-file calls should compile: {:?}",
1957 result.err()
1958 );
1959 }
1960
1961 #[test]
1966 fn test_module_member_access() {
1967 let sources = vec![
1971 SourceFile::new(
1972 "main.gruel",
1973 r#"fn main() -> i32 {
1974 let math = @import("math.gruel");
1975 math.add(1, 2)
1976 }"#,
1977 FileId::new(1),
1978 ),
1979 SourceFile::new(
1980 "math.gruel",
1981 "fn add(a: i32, b: i32) -> i32 { a + b }",
1982 FileId::new(2),
1983 ),
1984 ];
1985 let result = compile_multi_file_with_options(&sources, &CompileOptions::default());
1986 assert!(
1987 result.is_ok(),
1988 "module member access should compile: {:?}",
1989 result.err()
1990 );
1991 }
1992
1993 #[test]
1994 fn test_module_member_access_multiple_functions() {
1995 let sources = vec![
1997 SourceFile::new(
1998 "main.gruel",
1999 r#"fn main() -> i32 {
2000 let math = @import("math.gruel");
2001 let sum = math.add(10, 20);
2002 let diff = math.sub(sum, 5);
2003 diff
2004 }"#,
2005 FileId::new(1),
2006 ),
2007 SourceFile::new(
2008 "math.gruel",
2009 r#"fn add(a: i32, b: i32) -> i32 { a + b }
2010 fn sub(a: i32, b: i32) -> i32 { a - b }"#,
2011 FileId::new(2),
2012 ),
2013 ];
2014 let result = compile_multi_file_with_options(&sources, &CompileOptions::default());
2015 assert!(
2016 result.is_ok(),
2017 "module with multiple functions should compile: {:?}",
2018 result.err()
2019 );
2020 }
2021
2022 #[test]
2023 fn test_module_undefined_function_error() {
2024 let sources = vec![
2026 SourceFile::new(
2027 "main.gruel",
2028 r#"fn main() -> i32 {
2029 let math = @import("math.gruel");
2030 math.nonexistent(1, 2)
2031 }"#,
2032 FileId::new(1),
2033 ),
2034 SourceFile::new(
2035 "math.gruel",
2036 "fn add(a: i32, b: i32) -> i32 { a + b }",
2037 FileId::new(2),
2038 ),
2039 ];
2040 let result = compile_multi_file_with_options(&sources, &CompileOptions::default());
2041 assert!(
2042 result.is_err(),
2043 "undefined module function should fail to compile"
2044 );
2045 let err = result.err().unwrap().to_string();
2046 assert!(
2047 err.contains("undefined function") || err.contains("nonexistent"),
2048 "error should mention undefined function: {}",
2049 err
2050 );
2051 }
2052}
2053
2054#[cfg(test)]
2069mod integration_tests {
2070 use super::*;
2071
2072 mod integer_types {
2077 use super::*;
2078
2079 #[test]
2080 fn signed_integer_return() {
2081 assert!(compile_to_air("fn main() -> i8 { 42 }").is_ok());
2082 assert!(compile_to_air("fn main() -> i16 { 42 }").is_ok());
2083 assert!(compile_to_air("fn main() -> i32 { 42 }").is_ok());
2084 assert!(compile_to_air("fn main() -> i64 { 42 }").is_ok());
2085 }
2086
2087 #[test]
2088 fn unsigned_integer_return() {
2089 assert!(compile_to_air("fn main() -> u8 { 42 }").is_ok());
2090 assert!(compile_to_air("fn main() -> u16 { 42 }").is_ok());
2091 assert!(compile_to_air("fn main() -> u32 { 42 }").is_ok());
2092 assert!(compile_to_air("fn main() -> u64 { 42 }").is_ok());
2093 }
2094
2095 #[test]
2096 fn integer_type_mismatch() {
2097 let result = compile_to_air("fn main() -> i32 { let x: i64 = 1; x }");
2098 assert!(result.is_err());
2099 let err = result.unwrap_err().to_string();
2100 assert!(err.contains("type mismatch") || err.contains("expected"));
2101 }
2102
2103 #[test]
2104 fn integer_literal_type_inference() {
2105 assert!(compile_to_air("fn main() -> i64 { 100 }").is_ok());
2107 assert!(compile_to_air("fn main() -> i32 { let x: i64 = 100; 0 }").is_ok());
2109 }
2110 }
2111
2112 mod boolean_type {
2117 use super::*;
2118
2119 #[test]
2120 fn boolean_literals() {
2121 assert!(compile_to_air("fn main() -> bool { true }").is_ok());
2122 assert!(compile_to_air("fn main() -> bool { false }").is_ok());
2123 }
2124
2125 #[test]
2126 fn boolean_in_condition() {
2127 assert!(compile_to_cfg("fn main() -> i32 { if true { 1 } else { 0 } }").is_ok());
2128 }
2129
2130 #[test]
2131 fn non_boolean_condition_rejected() {
2132 let result = compile_to_air("fn main() -> i32 { if 1 { 1 } else { 0 } }");
2133 assert!(result.is_err());
2134 }
2135 }
2136
2137 mod unit_type {
2142 use super::*;
2143
2144 #[test]
2145 fn unit_return_type() {
2146 assert!(compile_to_air("fn main() -> () { () }").is_ok());
2147 }
2148
2149 #[test]
2150 fn unit_in_expression() {
2151 assert!(compile_to_air("fn main() -> () { let _x = (); () }").is_ok());
2152 }
2153
2154 #[test]
2155 fn implicit_unit_return() {
2156 assert!(compile_to_air("fn foo() -> () { } fn main() -> i32 { 0 }").is_ok());
2157 }
2158 }
2159
2160 mod arithmetic {
2165 use super::*;
2166
2167 #[test]
2168 fn basic_addition() {
2169 assert!(compile_to_air("fn main() -> i32 { 1 + 2 }").is_ok());
2170 }
2171
2172 #[test]
2173 fn basic_subtraction() {
2174 assert!(compile_to_air("fn main() -> i32 { 5 - 3 }").is_ok());
2175 }
2176
2177 #[test]
2178 fn basic_multiplication() {
2179 assert!(compile_to_air("fn main() -> i32 { 3 * 4 }").is_ok());
2180 }
2181
2182 #[test]
2183 fn basic_division() {
2184 assert!(compile_to_air("fn main() -> i32 { 10 / 2 }").is_ok());
2185 }
2186
2187 #[test]
2188 fn basic_modulo() {
2189 assert!(compile_to_air("fn main() -> i32 { 10 % 3 }").is_ok());
2190 }
2191
2192 #[test]
2193 fn unary_negation() {
2194 assert!(compile_to_air("fn main() -> i32 { -42 }").is_ok());
2195 }
2196
2197 #[test]
2198 fn operator_precedence() {
2199 let state = compile_to_cfg("fn main() -> i32 { 1 + 2 * 3 }").unwrap();
2201 assert_eq!(state.functions.len(), 1);
2202 }
2203
2204 #[test]
2205 fn chained_operations() {
2206 assert!(compile_to_air("fn main() -> i32 { 1 + 2 + 3 + 4 }").is_ok());
2207 }
2208
2209 #[test]
2210 fn mixed_type_arithmetic_rejected() {
2211 let result = compile_to_air("fn main() -> i32 { 1 + true }");
2212 assert!(result.is_err());
2213 }
2214
2215 #[test]
2216 fn unsigned_arithmetic() {
2217 assert!(compile_to_air("fn main() -> u32 { 10 + 5 }").is_ok());
2218 assert!(compile_to_air("fn main() -> u32 { 10 - 5 }").is_ok());
2219 assert!(compile_to_air("fn main() -> u32 { 10 * 5 }").is_ok());
2220 }
2221 }
2222
2223 mod comparison {
2228 use super::*;
2229
2230 #[test]
2231 fn equality_comparison() {
2232 assert!(compile_to_air("fn main() -> bool { 1 == 1 }").is_ok());
2233 assert!(compile_to_air("fn main() -> bool { 1 != 2 }").is_ok());
2234 }
2235
2236 #[test]
2237 fn ordering_comparison() {
2238 assert!(compile_to_air("fn main() -> bool { 1 < 2 }").is_ok());
2239 assert!(compile_to_air("fn main() -> bool { 2 > 1 }").is_ok());
2240 assert!(compile_to_air("fn main() -> bool { 1 <= 2 }").is_ok());
2241 assert!(compile_to_air("fn main() -> bool { 2 >= 1 }").is_ok());
2242 }
2243
2244 #[test]
2245 fn boolean_equality() {
2246 assert!(compile_to_air("fn main() -> bool { true == true }").is_ok());
2247 assert!(compile_to_air("fn main() -> bool { true != false }").is_ok());
2248 }
2249
2250 #[test]
2251 fn comparison_returns_bool() {
2252 let result = compile_to_air("fn main() -> i32 { 1 < 2 }");
2253 assert!(result.is_err()); }
2255
2256 #[test]
2257 fn mixed_type_comparison_rejected() {
2258 let result = compile_to_air("fn main() -> bool { 1 == true }");
2259 assert!(result.is_err());
2260 }
2261 }
2262
2263 mod logical {
2268 use super::*;
2269
2270 #[test]
2271 fn logical_and() {
2272 assert!(compile_to_cfg("fn main() -> bool { true && false }").is_ok());
2273 }
2274
2275 #[test]
2276 fn logical_or() {
2277 assert!(compile_to_cfg("fn main() -> bool { true || false }").is_ok());
2278 }
2279
2280 #[test]
2281 fn logical_not() {
2282 assert!(compile_to_air("fn main() -> bool { !true }").is_ok());
2283 }
2284
2285 #[test]
2286 fn chained_logical() {
2287 assert!(compile_to_cfg("fn main() -> bool { true && false || true }").is_ok());
2288 }
2289
2290 #[test]
2291 fn logical_with_non_bool_rejected() {
2292 let result = compile_to_air("fn main() -> bool { 1 && true }");
2293 assert!(result.is_err());
2294 }
2295 }
2296
2297 mod bitwise {
2302 use super::*;
2303
2304 #[test]
2305 fn bitwise_and() {
2306 assert!(compile_to_air("fn main() -> i32 { 5 & 3 }").is_ok());
2307 }
2308
2309 #[test]
2310 fn bitwise_or() {
2311 assert!(compile_to_air("fn main() -> i32 { 5 | 3 }").is_ok());
2312 }
2313
2314 #[test]
2315 fn bitwise_xor() {
2316 assert!(compile_to_air("fn main() -> i32 { 5 ^ 3 }").is_ok());
2317 }
2318
2319 #[test]
2320 fn bitwise_not() {
2321 assert!(compile_to_air("fn main() -> i32 { ~5 }").is_ok());
2322 }
2323
2324 #[test]
2325 fn shift_left() {
2326 assert!(compile_to_air("fn main() -> i32 { 1 << 4 }").is_ok());
2327 }
2328
2329 #[test]
2330 fn shift_right() {
2331 assert!(compile_to_air("fn main() -> i32 { 16 >> 2 }").is_ok());
2332 }
2333
2334 #[test]
2335 fn bitwise_on_bool_rejected() {
2336 let result = compile_to_air("fn main() -> bool { true & false }");
2337 assert!(result.is_err());
2338 }
2339 }
2340
2341 mod if_expressions {
2346 use super::*;
2347
2348 #[test]
2349 fn basic_if_else() {
2350 assert!(compile_to_cfg("fn main() -> i32 { if true { 1 } else { 0 } }").is_ok());
2351 }
2352
2353 #[test]
2354 fn if_with_condition_expr() {
2355 assert!(compile_to_cfg("fn main() -> i32 { if 1 < 2 { 1 } else { 0 } }").is_ok());
2356 }
2357
2358 #[test]
2359 fn nested_if() {
2360 let src = "fn main() -> i32 { if true { if false { 1 } else { 2 } } else { 3 } }";
2361 assert!(compile_to_cfg(src).is_ok());
2362 }
2363
2364 #[test]
2365 fn if_branches_must_match_type() {
2366 let result = compile_to_air("fn main() -> i32 { if true { 1 } else { true } }");
2367 assert!(result.is_err());
2368 }
2369
2370 #[test]
2371 fn if_result_type_checked() {
2372 let result = compile_to_air("fn main() -> bool { if true { 1 } else { 0 } }");
2373 assert!(result.is_err());
2374 }
2375 }
2376
2377 mod match_expressions {
2382 use super::*;
2383
2384 #[test]
2385 fn match_on_integer() {
2386 let src = r#"
2387 fn main() -> i32 {
2388 let x = 1;
2389 match x {
2390 1 => 10,
2391 2 => 20,
2392 _ => 0,
2393 }
2394 }
2395 "#;
2396 assert!(compile_to_cfg(src).is_ok());
2397 }
2398
2399 #[test]
2400 fn match_on_boolean() {
2401 let src = r#"
2402 fn main() -> i32 {
2403 match true {
2404 true => 1,
2405 false => 0,
2406 }
2407 }
2408 "#;
2409 assert!(compile_to_cfg(src).is_ok());
2410 }
2411
2412 #[test]
2413 fn match_exhaustiveness_required() {
2414 let result = compile_to_air(
2416 r#"
2417 fn main() -> i32 {
2418 match 1 {
2419 1 => 10,
2420 }
2421 }
2422 "#,
2423 );
2424 assert!(result.is_err());
2425 }
2426
2427 #[test]
2428 fn match_branches_must_match_type() {
2429 let result = compile_to_air(
2430 r#"
2431 fn main() -> i32 {
2432 match true {
2433 true => 1,
2434 false => true,
2435 }
2436 }
2437 "#,
2438 );
2439 assert!(result.is_err());
2440 }
2441 }
2442
2443 mod loops {
2448 use super::*;
2449
2450 #[test]
2451 fn while_loop_basic() {
2452 let src = r#"
2453 fn main() -> i32 {
2454 let mut x = 0;
2455 while x < 10 {
2456 x = x + 1;
2457 }
2458 x
2459 }
2460 "#;
2461 assert!(compile_to_cfg(src).is_ok());
2462 }
2463
2464 #[test]
2465 fn while_with_break() {
2466 let src = r#"
2467 fn main() -> i32 {
2468 let mut x = 0;
2469 while true {
2470 x = x + 1;
2471 if x == 5 {
2472 break;
2473 }
2474 }
2475 x
2476 }
2477 "#;
2478 assert!(compile_to_cfg(src).is_ok());
2479 }
2480
2481 #[test]
2482 fn while_with_continue() {
2483 let src = r#"
2484 fn main() -> i32 {
2485 let mut x = 0;
2486 let mut sum = 0;
2487 while x < 10 {
2488 x = x + 1;
2489 if x == 5 {
2490 continue;
2491 }
2492 sum = sum + x;
2493 }
2494 sum
2495 }
2496 "#;
2497 assert!(compile_to_cfg(src).is_ok());
2498 }
2499
2500 #[test]
2501 fn break_outside_loop_rejected() {
2502 let result = compile_to_air("fn main() -> i32 { break; 0 }");
2503 assert!(result.is_err());
2504 }
2505
2506 #[test]
2507 fn continue_outside_loop_rejected() {
2508 let result = compile_to_air("fn main() -> i32 { continue; 0 }");
2509 assert!(result.is_err());
2510 }
2511 }
2512
2513 mod let_bindings {
2518 use super::*;
2519
2520 #[test]
2521 fn basic_let() {
2522 assert!(compile_to_air("fn main() -> i32 { let x = 42; x }").is_ok());
2523 }
2524
2525 #[test]
2526 fn let_with_type_annotation() {
2527 assert!(compile_to_air("fn main() -> i32 { let x: i32 = 42; x }").is_ok());
2528 }
2529
2530 #[test]
2531 fn mutable_let() {
2532 let src = "fn main() -> i32 { let mut x = 1; x = 2; x }";
2533 assert!(compile_to_air(src).is_ok());
2534 }
2535
2536 #[test]
2537 fn immutable_assignment_rejected() {
2538 let result = compile_to_air("fn main() -> i32 { let x = 1; x = 2; x }");
2539 assert!(result.is_err());
2540 }
2541
2542 #[test]
2543 fn shadowing_allowed() {
2544 let src = "fn main() -> i32 { let x = 1; let x = 2; x }";
2545 assert!(compile_to_air(src).is_ok());
2546 }
2547
2548 #[test]
2549 fn shadowing_can_change_type() {
2550 let src = "fn main() -> bool { let x = 1; let x = true; x }";
2551 assert!(compile_to_air(src).is_ok());
2552 }
2553
2554 #[test]
2555 fn undefined_variable_rejected() {
2556 let result = compile_to_air("fn main() -> i32 { x }");
2557 assert!(result.is_err());
2558 }
2559 }
2560
2561 mod functions {
2566 use super::*;
2567
2568 #[test]
2569 fn function_call() {
2570 let src = r#"
2571 fn add(a: i32, b: i32) -> i32 { a + b }
2572 fn main() -> i32 { add(1, 2) }
2573 "#;
2574 assert!(compile_to_air(src).is_ok());
2575 }
2576
2577 #[test]
2578 fn function_forward_reference() {
2579 let src = r#"
2580 fn main() -> i32 { foo() }
2581 fn foo() -> i32 { 42 }
2582 "#;
2583 assert!(compile_to_air(src).is_ok());
2584 }
2585
2586 #[test]
2587 fn recursion() {
2588 let src = r#"
2589 fn factorial(n: i32) -> i32 {
2590 if n <= 1 { 1 } else { n * factorial(n - 1) }
2591 }
2592 fn main() -> i32 { factorial(5) }
2593 "#;
2594 assert!(compile_to_cfg(src).is_ok());
2595 }
2596
2597 #[test]
2598 fn mutual_recursion() {
2599 let src = r#"
2600 fn is_even(n: i32) -> bool {
2601 if n == 0 { true } else { is_odd(n - 1) }
2602 }
2603 fn is_odd(n: i32) -> bool {
2604 if n == 0 { false } else { is_even(n - 1) }
2605 }
2606 fn main() -> i32 { if is_even(4) { 1 } else { 0 } }
2607 "#;
2608 assert!(compile_to_cfg(src).is_ok());
2609 }
2610
2611 #[test]
2612 fn wrong_argument_count_rejected() {
2613 let src = r#"
2614 fn add(a: i32, b: i32) -> i32 { a + b }
2615 fn main() -> i32 { add(1) }
2616 "#;
2617 let result = compile_to_air(src);
2618 assert!(result.is_err());
2619 }
2620
2621 #[test]
2622 fn wrong_argument_type_rejected() {
2623 let src = r#"
2624 fn foo(x: i32) -> i32 { x }
2625 fn main() -> i32 { foo(true) }
2626 "#;
2627 let result = compile_to_air(src);
2628 assert!(result.is_err());
2629 }
2630
2631 #[test]
2632 fn undefined_function_rejected() {
2633 let result = compile_to_air("fn main() -> i32 { unknown() }");
2634 assert!(result.is_err());
2635 }
2636
2637 #[test]
2638 fn return_type_mismatch_rejected() {
2639 let result = compile_to_air("fn main() -> i32 { true }");
2640 assert!(result.is_err());
2641 }
2642 }
2643
2644 mod structs {
2649 use super::*;
2650
2651 #[test]
2652 fn struct_definition() {
2653 let src = r#"
2654 struct Point { x: i32, y: i32 }
2655 fn main() -> i32 { 0 }
2656 "#;
2657 let result = compile_to_air(src).unwrap();
2658 let all_struct_ids = result.type_pool.all_struct_ids();
2661 assert_eq!(all_struct_ids.len(), 2);
2662 let point_name = result.interner.get_or_intern("Point");
2664 let point_interned = result.type_pool.get_struct_by_name(point_name);
2665 assert!(
2666 point_interned.is_some(),
2667 "Point struct should exist in pool"
2668 );
2669 }
2670
2671 #[test]
2672 fn struct_literal() {
2673 let src = r#"
2674 struct Point { x: i32, y: i32 }
2675 fn main() -> i32 {
2676 let _p = Point { x: 1, y: 2 };
2677 0
2678 }
2679 "#;
2680 assert!(compile_to_air(src).is_ok());
2681 }
2682
2683 #[test]
2684 fn struct_field_access() {
2685 let src = r#"
2686 struct Point { x: i32, y: i32 }
2687 fn main() -> i32 {
2688 let p = Point { x: 10, y: 20 };
2689 p.x + p.y
2690 }
2691 "#;
2692 assert!(compile_to_air(src).is_ok());
2693 }
2694
2695 #[test]
2696 fn struct_field_order_independent() {
2697 let src = r#"
2698 struct Point { x: i32, y: i32 }
2699 fn main() -> i32 {
2700 let p = Point { y: 2, x: 1 };
2701 p.x
2702 }
2703 "#;
2704 assert!(compile_to_air(src).is_ok());
2705 }
2706
2707 #[test]
2708 fn struct_unknown_field_rejected() {
2709 let src = r#"
2710 struct Point { x: i32, y: i32 }
2711 fn main() -> i32 {
2712 let p = Point { x: 1, z: 2 };
2713 0
2714 }
2715 "#;
2716 let result = compile_to_air(src);
2717 assert!(result.is_err());
2718 }
2719
2720 #[test]
2721 fn struct_equality() {
2722 let src = r#"
2723 struct Point { x: i32, y: i32 }
2724 fn main() -> bool {
2725 let a = Point { x: 1, y: 2 };
2726 let b = Point { x: 1, y: 2 };
2727 a == b
2728 }
2729 "#;
2730 assert!(compile_to_air(src).is_ok());
2731 }
2732
2733 #[test]
2734 fn struct_move_semantics() {
2735 let src = r#"
2737 struct Point { x: i32, y: i32 }
2738 fn consume(p: Point) -> i32 { p.x }
2739 fn main() -> i32 {
2740 let p = Point { x: 1, y: 2 };
2741 let _a = consume(p);
2742 p.x
2743 }
2744 "#;
2745 let result = compile_to_air(src);
2746 assert!(result.is_err());
2747 }
2748 }
2749
2750 mod enums {
2755 use super::*;
2756
2757 #[test]
2758 fn enum_definition() {
2759 let src = r#"
2760 enum Color { Red, Green, Blue }
2761 fn main() -> i32 { 0 }
2762 "#;
2763 assert!(compile_to_air(src).is_ok());
2764 }
2765
2766 #[test]
2767 fn enum_variant_access() {
2768 let src = r#"
2769 enum Color { Red, Green, Blue }
2770 fn main() -> i32 {
2771 let _c = Color::Red;
2772 0
2773 }
2774 "#;
2775 assert!(compile_to_air(src).is_ok());
2776 }
2777
2778 #[test]
2779 fn enum_match() {
2780 let src = r#"
2781 enum Color { Red, Green, Blue }
2782 fn main() -> i32 {
2783 let c = Color::Green;
2784 match c {
2785 Color::Red => 1,
2786 Color::Green => 2,
2787 Color::Blue => 3,
2788 }
2789 }
2790 "#;
2791 assert!(compile_to_cfg(src).is_ok());
2792 }
2793
2794 #[test]
2795 fn enum_comparison_via_match() {
2796 let src = r#"
2799 enum Color { Red, Green, Blue }
2800 fn eq(a: Color, b: Color) -> bool {
2801 match a {
2802 Color::Red => match b { Color::Red => true, _ => false },
2803 Color::Green => match b { Color::Green => true, _ => false },
2804 Color::Blue => match b { Color::Blue => true, _ => false },
2805 }
2806 }
2807 fn main() -> i32 { if eq(Color::Red, Color::Red) { 1 } else { 0 } }
2808 "#;
2809 assert!(compile_to_cfg(src).is_ok());
2810 }
2811
2812 #[test]
2813 fn unknown_enum_variant_rejected() {
2814 let src = r#"
2815 enum Color { Red, Green, Blue }
2816 fn main() -> i32 {
2817 let _c = Color::Yellow;
2818 0
2819 }
2820 "#;
2821 let result = compile_to_air(src);
2822 assert!(result.is_err());
2823 }
2824 }
2825
2826 mod arrays {
2831 use super::*;
2832
2833 #[test]
2834 fn array_literal() {
2835 let src = "fn main() -> i32 { let _arr: [i32; 3] = [1, 2, 3]; 0 }";
2836 assert!(compile_to_air(src).is_ok());
2837 }
2838
2839 #[test]
2840 fn array_indexing() {
2841 let src = "fn main() -> i32 { let arr: [i32; 3] = [1, 2, 3]; arr[1] }";
2842 assert!(compile_to_air(src).is_ok());
2843 }
2844
2845 #[test]
2846 fn array_element_assignment() {
2847 let src = r#"
2848 fn main() -> i32 {
2849 let mut arr: [i32; 3] = [1, 2, 3];
2850 arr[0] = 10;
2851 arr[0]
2852 }
2853 "#;
2854 assert!(compile_to_air(src).is_ok());
2855 }
2856
2857 #[test]
2858 fn array_wrong_length_rejected() {
2859 let src = "fn main() -> i32 { let _arr: [i32; 3] = [1, 2]; 0 }";
2860 let result = compile_to_air(src);
2861 assert!(result.is_err());
2862 }
2863
2864 #[test]
2865 fn array_mixed_types_rejected() {
2866 let src = "fn main() -> i32 { let _arr: [i32; 2] = [1, true]; 0 }";
2867 let result = compile_to_air(src);
2868 assert!(result.is_err());
2869 }
2870 }
2871
2872 mod strings {
2877 use super::*;
2878
2879 #[test]
2880 fn string_literal() {
2881 let src = r#"fn main() -> i32 { let _s = "hello"; 0 }"#;
2882 assert!(compile_to_air(src).is_ok());
2883 }
2884
2885 #[test]
2886 fn string_with_quote_escape() {
2887 let src = r#"fn main() -> i32 { let _s = "hello\"world"; 0 }"#;
2889 assert!(compile_to_air(src).is_ok());
2890 }
2891
2892 #[test]
2893 fn string_with_backslash_escape() {
2894 let src = r#"fn main() -> i32 { let _s = "hello\\world"; 0 }"#;
2896 assert!(compile_to_air(src).is_ok());
2897 }
2898 }
2899
2900 mod blocks {
2905 use super::*;
2906
2907 #[test]
2908 fn block_returns_final_expression() {
2909 let src = "fn main() -> i32 { { 1; 2; 3 } }";
2910 assert!(compile_to_air(src).is_ok());
2911 }
2912
2913 #[test]
2914 fn block_with_let_bindings() {
2915 let src = "fn main() -> i32 { { let x = 1; let y = 2; x + y } }";
2916 assert!(compile_to_air(src).is_ok());
2917 }
2918
2919 #[test]
2920 fn nested_blocks() {
2921 let src = "fn main() -> i32 { { { { 42 } } } }";
2922 assert!(compile_to_air(src).is_ok());
2923 }
2924
2925 #[test]
2926 fn block_scoping() {
2927 let result = compile_to_air("fn main() -> i32 { { let x = 1; } x }");
2929 assert!(result.is_err());
2930 }
2931 }
2932
2933 mod never_type {
2938 use super::*;
2939
2940 #[test]
2941 fn return_is_never() {
2942 let src = "fn main() -> i32 { return 42; }";
2943 assert!(compile_to_cfg(src).is_ok());
2944 }
2945
2946 #[test]
2947 fn break_is_never() {
2948 let src = r#"
2949 fn main() -> i32 {
2950 while true {
2951 break;
2952 }
2953 0
2954 }
2955 "#;
2956 assert!(compile_to_cfg(src).is_ok());
2957 }
2958
2959 #[test]
2960 fn never_in_if_branch() {
2961 let src = "fn main() -> i32 { if true { 1 } else { return 2; } }";
2962 assert!(compile_to_cfg(src).is_ok());
2963 }
2964 }
2965
2966 mod intrinsics {
2971 use super::*;
2972
2973 #[test]
2974 fn size_of_intrinsic() {
2975 let src = "fn main() -> i32 { @size_of(i32) }";
2977 assert!(compile_to_air(src).is_ok());
2978 }
2979
2980 #[test]
2981 fn align_of_intrinsic() {
2982 let src = "fn main() -> i32 { @align_of(i64) }";
2984 assert!(compile_to_air(src).is_ok());
2985 }
2986 }
2987
2988 mod cfg_construction {
2993 use super::*;
2994
2995 #[test]
2996 fn cfg_has_correct_function_count() {
2997 let src = r#"
2998 fn foo() -> i32 { 1 }
2999 fn bar() -> i32 { 2 }
3000 fn main() -> i32 { foo() + bar() }
3001 "#;
3002 let state = compile_to_cfg(src).unwrap();
3003 assert_eq!(state.functions.len(), 3);
3004 }
3005
3006 #[test]
3007 fn cfg_branches_for_if() {
3008 let src = "fn main() -> i32 { if true { 1 } else { 0 } }";
3009 let state = compile_to_cfg(src).unwrap();
3010 let main_cfg = &state.functions[0].cfg;
3012 assert!(main_cfg.blocks().len() >= 3); }
3014
3015 #[test]
3016 fn cfg_loop_for_while() {
3017 let src = r#"
3018 fn main() -> i32 {
3019 let mut x = 0;
3020 while x < 10 { x = x + 1; }
3021 x
3022 }
3023 "#;
3024 let state = compile_to_cfg(src).unwrap();
3025 let main_cfg = &state.functions[0].cfg;
3026 assert!(main_cfg.blocks().len() >= 3); }
3028 }
3029
3030 mod error_messages {
3035 use super::*;
3036
3037 #[test]
3038 fn type_mismatch_error_is_descriptive() {
3039 let result = compile_to_air("fn main() -> i32 { true }");
3040 let err = result.unwrap_err().to_string();
3041 assert!(err.contains("type mismatch") || err.contains("expected"));
3042 assert!(err.contains("i32") || err.contains("bool"));
3043 }
3044
3045 #[test]
3046 fn undefined_variable_error_is_descriptive() {
3047 let result = compile_to_air("fn main() -> i32 { unknown_var }");
3048 let err = result.unwrap_err().to_string();
3049 assert!(err.contains("undefined") || err.contains("unknown"));
3050 }
3051
3052 #[test]
3053 fn missing_field_error_is_descriptive() {
3054 let src = r#"
3055 struct Point { x: i32, y: i32 }
3056 fn main() -> i32 {
3057 let p = Point { x: 1 };
3058 0
3059 }
3060 "#;
3061 let result = compile_to_air(src);
3062 let err = result.unwrap_err().to_string();
3063 assert!(err.contains("missing") || err.contains("field"));
3064 }
3065 }
3066
3067 mod warnings {
3072 use super::*;
3073
3074 #[test]
3075 fn unused_variable_warning() {
3076 let result = compile_to_air("fn main() -> i32 { let x = 42; 0 }").unwrap();
3077 assert_eq!(result.warnings.len(), 1);
3078 assert!(result.warnings[0].to_string().contains("unused"));
3079 }
3080
3081 #[test]
3082 fn underscore_prefix_suppresses_warning() {
3083 let result = compile_to_air("fn main() -> i32 { let _x = 42; 0 }").unwrap();
3084 assert_eq!(result.warnings.len(), 0);
3085 }
3086
3087 #[test]
3088 fn used_variable_no_warning() {
3089 let result = compile_to_air("fn main() -> i32 { let x = 42; x }").unwrap();
3090 assert_eq!(result.warnings.len(), 0);
3091 }
3092 }
3093
3094 mod edge_cases {
3099 use super::*;
3100
3101 #[test]
3102 fn empty_function_body() {
3103 assert!(compile_to_air("fn main() -> () { }").is_ok());
3104 }
3105
3106 #[test]
3107 fn deeply_nested_expressions() {
3108 let src = "fn main() -> i32 { ((((((1 + 2)))))) }";
3109 assert!(compile_to_air(src).is_ok());
3110 }
3111
3112 #[test]
3113 fn many_parameters() {
3114 let src = r#"
3115 fn many(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32) -> i32 {
3116 a + b + c + d + e + f
3117 }
3118 fn main() -> i32 { many(1, 2, 3, 4, 5, 6) }
3119 "#;
3120 assert!(compile_to_air(src).is_ok());
3121 }
3122
3123 #[test]
3124 fn long_chain_of_operations() {
3125 let src = "fn main() -> i32 { 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 }";
3126 assert!(compile_to_air(src).is_ok());
3127 }
3128
3129 #[test]
3130 fn multiple_functions_same_local_names() {
3131 let src = r#"
3132 fn foo() -> i32 { let x = 1; x }
3133 fn bar() -> i32 { let x = 2; x }
3134 fn main() -> i32 { foo() + bar() }
3135 "#;
3136 assert!(compile_to_air(src).is_ok());
3137 }
3138 }
3139
3140 mod multi_file_parsing {
3145 use super::*;
3146
3147 #[test]
3148 fn parse_single_file() {
3149 let sources = vec![SourceFile::new(
3150 "main.gruel",
3151 "fn main() -> i32 { 42 }",
3152 FileId::new(1),
3153 )];
3154 let program = parse_all_files(&sources).unwrap();
3155 assert_eq!(program.files.len(), 1);
3156 assert_eq!(program.files[0].path, "main.gruel");
3157 assert_eq!(program.files[0].file_id, FileId::new(1));
3158 }
3159
3160 #[test]
3161 fn parse_multiple_files() {
3162 let sources = vec![
3163 SourceFile::new(
3164 "main.gruel",
3165 "fn main() -> i32 { helper() }",
3166 FileId::new(1),
3167 ),
3168 SourceFile::new("utils.gruel", "fn helper() -> i32 { 42 }", FileId::new(2)),
3169 ];
3170 let program = parse_all_files(&sources).unwrap();
3171 assert_eq!(program.files.len(), 2);
3172
3173 let paths: Vec<_> = program.files.iter().map(|f| f.path.as_str()).collect();
3175 assert!(paths.contains(&"main.gruel"));
3176 assert!(paths.contains(&"utils.gruel"));
3177 }
3178
3179 #[test]
3180 fn parse_many_files_in_parallel() {
3181 let sources: Vec<_> = (1..=10)
3183 .map(|i| {
3184 SourceFile::new(
3185 Box::leak(format!("file{}.gruel", i).into_boxed_str()),
3187 Box::leak(format!("fn func{}() -> i32 {{ {} }}", i, i).into_boxed_str()),
3188 FileId::new(i as u32),
3189 )
3190 })
3191 .collect();
3192
3193 let program = parse_all_files(&sources).unwrap();
3194 assert_eq!(program.files.len(), 10);
3195
3196 for (i, file) in program.files.iter().enumerate() {
3198 assert!(!file.ast.items.is_empty(), "File {} has no items", i);
3199 }
3200 }
3201
3202 #[test]
3203 fn parse_error_in_single_file() {
3204 let sources = vec![SourceFile::new(
3205 "bad.gruel",
3206 "fn main( { }", FileId::new(1),
3208 )];
3209
3210 let result = parse_all_files(&sources);
3211 assert!(result.is_err());
3212
3213 let errors = result.unwrap_err();
3214 assert!(!errors.is_empty());
3215 }
3216
3217 #[test]
3218 fn parse_error_in_multiple_files() {
3219 let sources = vec![
3220 SourceFile::new("good.gruel", "fn good() -> i32 { 42 }", FileId::new(1)),
3221 SourceFile::new(
3222 "bad.gruel",
3223 "fn bad( { }", FileId::new(2),
3225 ),
3226 ];
3227
3228 let result = parse_all_files(&sources);
3229 assert!(result.is_err());
3230
3231 let errors = result.unwrap_err();
3233 assert!(!errors.is_empty());
3234 }
3235
3236 #[test]
3237 fn lexer_error_in_file() {
3238 let sources = vec![SourceFile::new(
3239 "lexer_error.gruel",
3240 "fn main() -> i32 { $ }", FileId::new(1),
3242 )];
3243
3244 let result = parse_all_files(&sources);
3245 assert!(result.is_err());
3246 }
3247
3248 #[test]
3249 fn interner_merges_across_files() {
3250 let sources = vec![
3251 SourceFile::new("a.gruel", "fn foo() -> i32 { 1 }", FileId::new(1)),
3252 SourceFile::new("b.gruel", "fn bar() -> i32 { 2 }", FileId::new(2)),
3253 ];
3254
3255 let program = parse_all_files(&sources).unwrap();
3256
3257 let has_foo = program.interner.iter().any(|(_, s)| s == "foo");
3259 let has_bar = program.interner.iter().any(|(_, s)| s == "bar");
3260
3261 assert!(has_foo, "Interner should contain 'foo'");
3262 assert!(has_bar, "Interner should contain 'bar'");
3263 }
3264
3265 #[test]
3266 fn empty_file_parses_ok() {
3267 let sources = vec![SourceFile::new("empty.gruel", "", FileId::new(1))];
3268
3269 let program = parse_all_files(&sources).unwrap();
3270 assert_eq!(program.files.len(), 1);
3271 assert!(program.files[0].ast.items.is_empty());
3272 }
3273
3274 #[test]
3275 fn file_ids_preserved() {
3276 let sources = vec![
3277 SourceFile::new("a.gruel", "fn a() -> i32 { 1 }", FileId::new(42)),
3278 SourceFile::new("b.gruel", "fn b() -> i32 { 2 }", FileId::new(99)),
3279 ];
3280
3281 let program = parse_all_files(&sources).unwrap();
3282
3283 let file_ids: Vec<_> = program.files.iter().map(|f| f.file_id).collect();
3284 assert!(file_ids.contains(&FileId::new(42)));
3285 assert!(file_ids.contains(&FileId::new(99)));
3286 }
3287 }
3288}