1mod diagnostic;
29mod drop_glue;
30mod import_overlay;
31mod link;
32mod parse_cache;
33mod prelude_source;
34mod unit;
35
36pub use import_overlay::{
37 ImportLoadError, ImportOverlay, LoadedFile, load_import_closure, loaded_files_as_view,
38};
39
40pub use prelude_source::{
41 ResolvedPrelude, ResolvedPreludeFile, embedded_prelude, resolved_prelude,
42};
43
44pub use parse_cache::{ParseCacheStats, parse_all_files_cached, parse_key};
45
46pub use unit::CompilationUnit;
47
48use rayon::prelude::*;
49use tracing::{info, info_span};
50
51pub use diagnostic::{
52 ColorChoice, JsonDiagnostic, JsonSpan, JsonSuggestion, MultiFileFormatter,
53 MultiFileJsonFormatter, SourceInfo,
54};
55pub use link::LinkerMode;
56use link::link_system_with_warnings;
57
58pub use gruel_air::{Air, AnalyzedFunction, Sema, SemaOutput, StructDef, Type, TypeInternPool};
60pub use gruel_cfg::{Cfg, CfgBuilder, CfgOutput};
61pub use gruel_codegen_llvm::OptLevel;
62pub use gruel_lexer::{Lexer, Token, TokenKind};
63pub use gruel_parser::{Ast, Expr, Function, Item, Parser};
64pub use gruel_rir::{AstGen, Rir, RirPrinter};
65pub use gruel_target::{Arch, Target};
66pub use gruel_util::{
67 Applicability, CompileError, CompileErrors, CompileResult, CompileWarning, Diagnostic,
68 ErrorCode, ErrorKind, MultiErrorResult, PreviewFeature, PreviewFeatures, Suggestion,
69 WarningKind,
70};
71pub use gruel_util::{FileId, Span};
72pub use lasso::{Spur, ThreadedRodeo};
73
74#[derive(Debug, Clone)]
82pub struct SourceFile<'a> {
83 pub path: &'a str,
85 pub source: &'a str,
87 pub file_id: FileId,
89}
90
91impl<'a> SourceFile<'a> {
92 pub fn new(path: &'a str, source: &'a str, file_id: FileId) -> Self {
94 Self {
95 path,
96 source,
97 file_id,
98 }
99 }
100}
101
102#[derive(Debug)]
107pub struct ParsedFile {
108 pub path: String,
110 pub file_id: FileId,
112 pub ast: Ast,
114 pub interner: ThreadedRodeo,
116}
117
118#[derive(Debug)]
122pub struct ParsedProgram {
123 pub files: Vec<ParsedFile>,
125 pub interner: ThreadedRodeo,
127}
128
129pub fn parse_all_files(sources: &[SourceFile<'_>]) -> MultiErrorResult<ParsedProgram> {
161 parse_all_files_with_preview(sources, &PreviewFeatures::default())
162}
163
164pub fn parse_all_files_with_preview(
166 sources: &[SourceFile<'_>],
167 preview_features: &PreviewFeatures,
168) -> MultiErrorResult<ParsedProgram> {
169 let mut parsed_files = Vec::with_capacity(sources.len());
172 let mut interner = ThreadedRodeo::new();
173
174 for source in sources {
175 let lexer = Lexer::with_interner_and_file_id(source.source, interner, source.file_id);
177
178 let (tokens, returned_interner) = lexer.tokenize().map_err(CompileErrors::from)?;
180 interner = returned_interner;
181
182 let parser = Parser::new(tokens, interner)
184 .with_preview_features(preview_features.clone())
185 .with_source(source.source);
186 let (ast, returned_interner) = parser.parse()?;
187 interner = returned_interner;
188
189 parsed_files.push(ParsedFile {
190 path: source.path.to_string(),
191 file_id: source.file_id,
192 ast,
193 interner: ThreadedRodeo::new(),
196 });
197 }
198
199 Ok(ParsedProgram {
200 files: parsed_files,
201 interner,
202 })
203}
204
205#[derive(Debug)]
210pub struct MergedProgram {
211 pub ast: Ast,
213 pub interner: ThreadedRodeo,
215}
216
217pub struct ValidatedProgram {
222 pub rir: Rir,
224 pub interner: ThreadedRodeo,
226 pub file_paths: rustc_hash::FxHashMap<FileId, String>,
228}
229
230#[derive(Debug, Clone)]
232struct SymbolDef {
233 span: Span,
235 file_path: String,
237}
238
239pub fn merge_symbols(program: ParsedProgram) -> MultiErrorResult<MergedProgram> {
269 use rustc_hash::FxHashMap as HashMap;
270
271 let _span = info_span!("merge_symbols", file_count = program.files.len()).entered();
272
273 let mut functions: HashMap<String, SymbolDef> = HashMap::default();
276 let mut structs: HashMap<String, SymbolDef> = HashMap::default();
277 let mut enums: HashMap<String, SymbolDef> = HashMap::default();
278
279 let mut all_items = Vec::new();
281 let mut errors: Vec<CompileError> = Vec::new();
282
283 let interner = &program.interner;
285
286 for file in &program.files {
287 for item in &file.ast.items {
288 match item {
289 Item::Function(func) => {
290 let name = interner.resolve(&func.name.name).to_string();
292 if let Some(first) = functions.get(&name) {
293 let err = CompileError::new(
295 ErrorKind::DuplicateTypeDefinition {
296 type_name: format!("function `{}`", name),
297 },
298 func.span,
299 )
300 .with_label(format!("first defined in {}", first.file_path), first.span);
301 errors.push(err);
302 } else {
303 functions.insert(
304 name.clone(),
305 SymbolDef {
306 span: func.span,
307 file_path: file.path.clone(),
308 },
309 );
310 }
311 }
312 Item::Struct(s) => {
313 let name = interner.resolve(&s.name.name).to_string();
315 if let Some(first) = structs.get(&name) {
316 let err = CompileError::new(
318 ErrorKind::DuplicateTypeDefinition {
319 type_name: format!("struct `{}`", name),
320 },
321 s.span,
322 )
323 .with_label(format!("first defined in {}", first.file_path), first.span);
324 errors.push(err);
325 } else if let Some(first) = enums.get(&name) {
326 let err = CompileError::new(
328 ErrorKind::DuplicateTypeDefinition {
329 type_name: format!("struct `{}` (conflicts with enum)", name),
330 },
331 s.span,
332 )
333 .with_label(
334 format!("enum first defined in {}", first.file_path),
335 first.span,
336 );
337 errors.push(err);
338 } else {
339 structs.insert(
340 name.clone(),
341 SymbolDef {
342 span: s.span,
343 file_path: file.path.clone(),
344 },
345 );
346 }
347 }
348 Item::Enum(e) => {
349 let name = interner.resolve(&e.name.name).to_string();
351 if let Some(first) = enums.get(&name) {
352 let err = CompileError::new(
354 ErrorKind::DuplicateTypeDefinition {
355 type_name: format!("enum `{}`", name),
356 },
357 e.span,
358 )
359 .with_label(format!("first defined in {}", first.file_path), first.span);
360 errors.push(err);
361 } else if let Some(first) = structs.get(&name) {
362 let err = CompileError::new(
364 ErrorKind::DuplicateTypeDefinition {
365 type_name: format!("enum `{}` (conflicts with struct)", name),
366 },
367 e.span,
368 )
369 .with_label(
370 format!("struct first defined in {}", first.file_path),
371 first.span,
372 );
373 errors.push(err);
374 } else {
375 enums.insert(
376 name.clone(),
377 SymbolDef {
378 span: e.span,
379 file_path: file.path.clone(),
380 },
381 );
382 }
383 }
384 Item::Interface(_) => {
385 }
389 Item::Derive(_) => {
390 }
393 Item::Const(_) => {
394 }
397 Item::LinkExtern(_) => {
398 }
403 Item::Error(_) => {
404 }
406 }
407 all_items.push(item.clone());
408 }
409 }
410
411 if !errors.is_empty() {
413 return Err(CompileErrors::from(errors));
414 }
415
416 info!(
417 function_count = functions.len(),
418 struct_count = structs.len(),
419 enum_count = enums.len(),
420 "symbol merging complete"
421 );
422
423 Ok(MergedProgram {
424 ast: Ast {
425 module_doc: None,
426 items: all_items,
427 },
428 interner: program.interner,
429 })
430}
431
432pub fn validate_and_generate_rir_parallel(
451 program: ParsedProgram,
452) -> MultiErrorResult<ValidatedProgram> {
453 use rustc_hash::FxHashMap as HashMap;
454
455 let _span = info_span!(
456 "validate_and_generate_rir",
457 file_count = program.files.len()
458 )
459 .entered();
460
461 let file_paths: HashMap<FileId, String> = program
463 .files
464 .iter()
465 .map(|f| (f.file_id, f.path.clone()))
466 .collect();
467
468 let mut functions: HashMap<String, SymbolDef> = HashMap::default();
470 let mut structs: HashMap<String, SymbolDef> = HashMap::default();
471 let mut enums: HashMap<String, SymbolDef> = HashMap::default();
472 let mut errors: Vec<CompileError> = Vec::new();
473
474 let interner = &program.interner;
475
476 for file in &program.files {
477 for item in &file.ast.items {
478 match item {
479 Item::Function(func) => {
480 let name = interner.resolve(&func.name.name).to_string();
481 if let Some(first) = functions.get(&name) {
482 let err = CompileError::new(
483 ErrorKind::DuplicateTypeDefinition {
484 type_name: format!("function `{}`", name),
485 },
486 func.span,
487 )
488 .with_label(format!("first defined in {}", first.file_path), first.span);
489 errors.push(err);
490 } else {
491 functions.insert(
492 name.clone(),
493 SymbolDef {
494 span: func.span,
495 file_path: file.path.clone(),
496 },
497 );
498 }
499 }
500 Item::Struct(s) => {
501 let name = interner.resolve(&s.name.name).to_string();
502 if let Some(first) = structs.get(&name) {
503 let err = CompileError::new(
504 ErrorKind::DuplicateTypeDefinition {
505 type_name: format!("struct `{}`", name),
506 },
507 s.span,
508 )
509 .with_label(format!("first defined in {}", first.file_path), first.span);
510 errors.push(err);
511 } else if let Some(first) = enums.get(&name) {
512 let err = CompileError::new(
513 ErrorKind::DuplicateTypeDefinition {
514 type_name: format!("struct `{}` (conflicts with enum)", name),
515 },
516 s.span,
517 )
518 .with_label(
519 format!("enum first defined in {}", first.file_path),
520 first.span,
521 );
522 errors.push(err);
523 } else {
524 structs.insert(
525 name.clone(),
526 SymbolDef {
527 span: s.span,
528 file_path: file.path.clone(),
529 },
530 );
531 }
532 }
533 Item::Enum(e) => {
534 let name = interner.resolve(&e.name.name).to_string();
535 if let Some(first) = enums.get(&name) {
536 let err = CompileError::new(
537 ErrorKind::DuplicateTypeDefinition {
538 type_name: format!("enum `{}`", name),
539 },
540 e.span,
541 )
542 .with_label(format!("first defined in {}", first.file_path), first.span);
543 errors.push(err);
544 } else if let Some(first) = structs.get(&name) {
545 let err = CompileError::new(
546 ErrorKind::DuplicateTypeDefinition {
547 type_name: format!("enum `{}` (conflicts with struct)", name),
548 },
549 e.span,
550 )
551 .with_label(
552 format!("struct first defined in {}", first.file_path),
553 first.span,
554 );
555 errors.push(err);
556 } else {
557 enums.insert(
558 name.clone(),
559 SymbolDef {
560 span: e.span,
561 file_path: file.path.clone(),
562 },
563 );
564 }
565 }
566 Item::Interface(_) => {
567 }
570 Item::Derive(_) => {
571 }
574 Item::Const(_) => {
575 }
577 Item::LinkExtern(_) => {
578 }
583 Item::Error(_) => {
584 }
586 }
587 }
588 }
589
590 if !errors.is_empty() {
591 return Err(CompileErrors::from(errors));
592 }
593
594 info!(
595 function_count = functions.len(),
596 struct_count = structs.len(),
597 enum_count = enums.len(),
598 "symbol validation complete"
599 );
600
601 let interner = program.interner;
603 let rirs: Vec<Rir> = {
604 let _span = info_span!("parallel_astgen").entered();
605
606 program
607 .files
608 .par_iter()
609 .map(|file| {
610 let astgen = AstGen::new(&file.ast, &interner);
611 astgen.generate()
612 })
613 .collect()
614 };
615
616 let rir = {
618 let _span = info_span!("merge_rirs", rir_count = rirs.len()).entered();
619 Rir::merge(&rirs)
620 };
621
622 info!(instruction_count = rir.len(), "RIR generation complete");
623
624 Ok(ValidatedProgram {
625 rir,
626 interner,
627 file_paths,
628 })
629}
630
631#[derive(Debug, Clone)]
648pub struct CompileOptions {
649 pub target: Target,
651 pub linker: LinkerMode,
653 pub opt_level: OptLevel,
655 pub preview_features: PreviewFeatures,
657 pub jobs: usize,
659 pub capture_comptime_dbg: bool,
663 pub cache_dir: Option<std::path::PathBuf>,
668}
669
670impl Default for CompileOptions {
671 fn default() -> Self {
672 Self {
673 target: Target::host(),
674 linker: LinkerMode::Internal,
675 opt_level: OptLevel::default(),
676 preview_features: PreviewFeatures::default(),
677 jobs: 0,
678 capture_comptime_dbg: false,
679 cache_dir: None,
680 }
681 }
682}
683
684#[derive(Debug)]
688pub struct FunctionWithCfg {
689 pub analyzed: AnalyzedFunction,
691 pub cfg: Cfg,
693}
694
695pub struct BackendInputs<'a> {
702 pub functions: &'a [FunctionWithCfg],
703 pub type_pool: &'a TypeInternPool,
704 pub strings: &'a [String],
705 pub bytes: &'a [Vec<u8>],
706 pub interner: &'a ThreadedRodeo,
707 pub interface_defs: &'a [gruel_air::InterfaceDef],
708 pub interface_vtables: &'a gruel_air::InterfaceVtables,
709 pub target: &'a Target,
710 pub extra_link_libraries: &'a [(String, crate::link::LinkMode)],
716}
717
718impl<'a> BackendInputs<'a> {
719 fn to_codegen_inputs(&self, cfgs: &'a [&'a Cfg]) -> gruel_codegen_llvm::CodegenInputs<'a> {
722 gruel_codegen_llvm::CodegenInputs {
723 functions: cfgs,
724 type_pool: self.type_pool,
725 strings: self.strings,
726 bytes: self.bytes,
727 interner: self.interner,
728 interface_defs: self.interface_defs,
729 interface_vtables: self.interface_vtables,
730 target: self.target,
731 }
732 }
733}
734
735pub struct CompileState {
740 pub ast: Ast,
742 pub interner: ThreadedRodeo,
744 pub rir: Rir,
746 pub functions: Vec<FunctionWithCfg>,
748 pub type_pool: TypeInternPool,
750 pub strings: Vec<String>,
752 pub bytes: Vec<Vec<u8>>,
754 pub warnings: Vec<CompileWarning>,
756 pub comptime_dbg_output: Vec<String>,
758 pub interface_defs: Vec<gruel_air::InterfaceDef>,
760 pub interface_vtables: gruel_air::InterfaceVtables,
763}
764
765#[derive(Debug)]
771pub struct CompileOutput {
772 pub elf: Vec<u8>,
774 pub warnings: Vec<CompileWarning>,
776}
777
778pub fn prepend_prelude(
791 ast: Ast,
792 mut interner: ThreadedRodeo,
793 preview_features: &PreviewFeatures,
794) -> MultiErrorResult<(Ast, ThreadedRodeo)> {
795 use gruel_util::FileId;
796
797 let mut all_items = Vec::new();
806 let mut prelude_file_id = FileId::PRELUDE.index();
807 let resolved = prelude_source::embedded_prelude();
808 for file in &resolved.prelude_dir {
809 let lexer =
810 Lexer::with_interner_and_file_id(&file.source, interner, FileId::new(prelude_file_id));
811 let (tokens, returned_interner) = lexer.tokenize().map_err(CompileErrors::from)?;
812 interner = returned_interner;
813 let parser = Parser::new(tokens, interner)
814 .with_preview_features(preview_features.clone())
815 .with_source(&*file.source);
816 let (parsed, returned_interner) = parser.parse()?;
817 interner = returned_interner;
818 all_items.extend(parsed.items);
819 prelude_file_id = prelude_file_id.wrapping_sub(1);
820 }
821
822 let mut merged = Ast {
823 module_doc: None,
824 items: all_items,
825 };
826 merged.items.extend(ast.items);
827 Ok((merged, interner))
828}
829
830pub fn compile_frontend(source: &str) -> MultiErrorResult<CompileState> {
841 compile_frontend_with_options(source, &PreviewFeatures::default())
842}
843
844pub fn compile_frontend_with_options(
852 source: &str,
853 preview_features: &PreviewFeatures,
854) -> MultiErrorResult<CompileState> {
855 compile_frontend_with_options_full(source, preview_features, false)
856}
857
858pub fn compile_frontend_with_options_full(
864 source: &str,
865 preview_features: &PreviewFeatures,
866 suppress_comptime_dbg_print: bool,
867) -> MultiErrorResult<CompileState> {
868 let _span = info_span!("frontend", source_bytes = source.len()).entered();
869
870 let (tokens, interner) = {
872 let _span = info_span!("lexer").entered();
873 let lexer = Lexer::new(source);
874 let (tokens, interner) = lexer.tokenize().map_err(CompileErrors::from)?;
875 info!(token_count = tokens.len(), "lexing complete");
876 (tokens, interner)
877 };
878
879 let (ast, interner) = {
881 let _span = info_span!("parser").entered();
882 let parser = Parser::new(tokens, interner)
883 .with_preview_features(preview_features.clone())
884 .with_source(source);
885 let (ast, interner) = parser.parse()?;
886 info!(item_count = ast.items.len(), "parsing complete");
887 (ast, interner)
888 };
889
890 compile_frontend_from_ast_with_options_full(
891 ast,
892 interner,
893 preview_features,
894 suppress_comptime_dbg_print,
895 )
896}
897
898pub fn compile_frontend_from_ast(
906 ast: Ast,
907 interner: ThreadedRodeo,
908) -> MultiErrorResult<CompileState> {
909 compile_frontend_from_ast_with_options(ast, interner, &PreviewFeatures::default())
910}
911
912pub fn compile_frontend_from_ast_with_options(
921 ast: Ast,
922 interner: ThreadedRodeo,
923 preview_features: &PreviewFeatures,
924) -> MultiErrorResult<CompileState> {
925 compile_frontend_from_ast_with_options_full(ast, interner, preview_features, false)
926}
927
928pub fn compile_frontend_from_ast_with_options_full(
931 ast: Ast,
932 interner: ThreadedRodeo,
933 preview_features: &PreviewFeatures,
934 suppress_comptime_dbg_print: bool,
935) -> MultiErrorResult<CompileState> {
936 compile_frontend_from_ast_with_options_full_target(
937 ast,
938 interner,
939 preview_features,
940 suppress_comptime_dbg_print,
941 &Target::host(),
942 )
943}
944
945pub fn compile_frontend_from_ast_with_options_full_target(
950 ast: Ast,
951 interner: ThreadedRodeo,
952 preview_features: &PreviewFeatures,
953 suppress_comptime_dbg_print: bool,
954 target: &Target,
955) -> MultiErrorResult<CompileState> {
956 compile_frontend_from_ast_with_file_paths(
957 ast,
958 interner,
959 preview_features,
960 suppress_comptime_dbg_print,
961 target,
962 rustc_hash::FxHashMap::default(),
963 )
964}
965
966pub fn compile_frontend_from_ast_with_file_paths(
973 ast: Ast,
974 interner: ThreadedRodeo,
975 preview_features: &PreviewFeatures,
976 suppress_comptime_dbg_print: bool,
977 target: &Target,
978 file_paths: rustc_hash::FxHashMap<FileId, String>,
979) -> MultiErrorResult<CompileState> {
980 let (rir, interner) = {
982 let _span = info_span!("astgen").entered();
983 let astgen = AstGen::new(&ast, &interner);
984 let rir = astgen.generate();
985 info!(instruction_count = rir.len(), "AST generation complete");
986 (rir, interner)
987 };
988
989 let sema_output = {
991 let _span = info_span!("sema").entered();
992 let mut sema = Sema::new(&rir, &interner, preview_features.clone());
993 sema.set_suppress_comptime_dbg_print(suppress_comptime_dbg_print);
994 sema.set_target(target.clone());
995 sema.set_file_paths(file_paths);
996 let output = sema.analyze_all()?;
997 info!(
998 function_count = output.functions.len(),
999 struct_count = output.type_pool.stats().struct_count,
1000 "semantic analysis complete"
1001 );
1002 output
1003 };
1004
1005 let drop_glue_functions = drop_glue::synthesize_drop_glue(&sema_output.type_pool, &interner);
1007 let all_functions: Vec<_> = sema_output
1013 .functions
1014 .into_iter()
1015 .filter(|f| f.air.return_type() != Type::COMPTIME_TYPE)
1016 .chain(drop_glue_functions)
1017 .collect();
1018
1019 let (functions, warnings) = {
1021 let _span = info_span!("cfg_construction").entered();
1022
1023 let results: Vec<(FunctionWithCfg, Vec<CompileWarning>)> = all_functions
1025 .into_par_iter()
1026 .map(|func| {
1027 let cfg_output = CfgBuilder::build(&func, &sema_output.type_pool, &interner);
1028
1029 (
1030 FunctionWithCfg {
1031 analyzed: func,
1032 cfg: cfg_output.cfg,
1033 },
1034 cfg_output.warnings,
1035 )
1036 })
1037 .collect();
1038
1039 let mut functions = Vec::with_capacity(results.len());
1041 let mut warnings = sema_output.warnings;
1042 for (func, func_warnings) in results {
1043 functions.push(func);
1044 warnings.extend(func_warnings);
1045 }
1046
1047 info!(
1048 function_count = functions.len(),
1049 "CFG construction complete"
1050 );
1051 (functions, warnings)
1052 };
1053
1054 Ok(CompileState {
1055 ast,
1056 interner,
1057 rir,
1058 functions,
1059 type_pool: sema_output.type_pool,
1060 strings: sema_output.strings,
1061 bytes: sema_output.bytes,
1062 warnings,
1063 comptime_dbg_output: sema_output.comptime_dbg_output,
1064 interface_defs: sema_output.interface_defs,
1065 interface_vtables: sema_output.interface_vtables,
1066 })
1067}
1068
1069pub fn compile_frontend_from_rir_with_options(
1086 rir: Rir,
1087 interner: ThreadedRodeo,
1088 preview_features: &PreviewFeatures,
1089) -> MultiErrorResult<CompileStateFromRir> {
1090 compile_frontend_from_rir_with_file_paths(
1091 rir,
1092 interner,
1093 preview_features,
1094 rustc_hash::FxHashMap::default(),
1095 )
1096}
1097
1098pub fn compile_frontend_from_rir_with_file_paths(
1103 rir: Rir,
1104 interner: ThreadedRodeo,
1105 preview_features: &PreviewFeatures,
1106 file_paths: rustc_hash::FxHashMap<FileId, String>,
1107) -> MultiErrorResult<CompileStateFromRir> {
1108 let sema_output = {
1110 let _span = info_span!("sema").entered();
1111 let mut sema = Sema::new(&rir, &interner, preview_features.clone());
1112 sema.set_file_paths(file_paths);
1113 let output = sema.analyze_all()?;
1114 info!(
1115 function_count = output.functions.len(),
1116 struct_count = output.type_pool.stats().struct_count,
1117 "semantic analysis complete"
1118 );
1119 output
1120 };
1121
1122 let drop_glue_functions = drop_glue::synthesize_drop_glue(&sema_output.type_pool, &interner);
1124 let all_functions: Vec<_> = sema_output
1130 .functions
1131 .into_iter()
1132 .filter(|f| f.air.return_type() != Type::COMPTIME_TYPE)
1133 .chain(drop_glue_functions)
1134 .collect();
1135
1136 let (functions, warnings) = {
1138 let _span = info_span!("cfg_construction").entered();
1139
1140 let results: Vec<(FunctionWithCfg, Vec<CompileWarning>)> = all_functions
1142 .into_par_iter()
1143 .map(|func| {
1144 let cfg_output = CfgBuilder::build(&func, &sema_output.type_pool, &interner);
1145
1146 (
1147 FunctionWithCfg {
1148 analyzed: func,
1149 cfg: cfg_output.cfg,
1150 },
1151 cfg_output.warnings,
1152 )
1153 })
1154 .collect();
1155
1156 let mut functions = Vec::with_capacity(results.len());
1158 let mut warnings = sema_output.warnings;
1159 for (func, func_warnings) in results {
1160 functions.push(func);
1161 warnings.extend(func_warnings);
1162 }
1163
1164 info!(
1165 function_count = functions.len(),
1166 "CFG construction complete"
1167 );
1168 (functions, warnings)
1169 };
1170
1171 Ok(CompileStateFromRir {
1172 interner,
1173 rir,
1174 functions,
1175 type_pool: sema_output.type_pool,
1176 strings: sema_output.strings,
1177 bytes: sema_output.bytes,
1178 warnings,
1179 comptime_dbg_output: sema_output.comptime_dbg_output,
1180 interface_defs: sema_output.interface_defs,
1181 interface_vtables: sema_output.interface_vtables,
1182 })
1183}
1184
1185pub struct CompileStateFromRir {
1190 pub interner: ThreadedRodeo,
1192 pub rir: Rir,
1194 pub functions: Vec<FunctionWithCfg>,
1196 pub type_pool: TypeInternPool,
1198 pub strings: Vec<String>,
1200 pub bytes: Vec<Vec<u8>>,
1202 pub warnings: Vec<CompileWarning>,
1204 pub comptime_dbg_output: Vec<String>,
1206 pub interface_defs: Vec<gruel_air::InterfaceDef>,
1208 pub interface_vtables: gruel_air::InterfaceVtables,
1211}
1212
1213pub fn compile(source: &str) -> MultiErrorResult<CompileOutput> {
1221 compile_with_options(source, &CompileOptions::default())
1222}
1223
1224pub fn compile_with_options(
1231 source: &str,
1232 options: &CompileOptions,
1233) -> MultiErrorResult<CompileOutput> {
1234 let sources = vec![SourceFile::new("<source>", source, FileId::new(1))];
1236 compile_multi_file_with_options(&sources, options)
1237}
1238
1239pub fn compile_multi_file_with_options(
1274 sources: &[SourceFile<'_>],
1275 options: &CompileOptions,
1276) -> MultiErrorResult<CompileOutput> {
1277 if options.jobs > 0 {
1281 let _ = rayon::ThreadPoolBuilder::new()
1284 .num_threads(options.jobs)
1285 .build_global();
1286 }
1287
1288 let total_source_bytes: usize = sources.iter().map(|s| s.source.len()).sum();
1289 let _span = info_span!(
1290 "compile",
1291 target = %options.target,
1292 file_count = sources.len(),
1293 source_bytes = total_source_bytes
1294 )
1295 .entered();
1296
1297 let mut unit = CompilationUnit::new(sources.to_vec(), options.clone());
1299 unit.run_all()
1300}
1301
1302pub fn compile_backend(
1316 inputs: &BackendInputs<'_>,
1317 options: &CompileOptions,
1318 warnings: &[CompileWarning],
1319) -> MultiErrorResult<CompileOutput> {
1320 let _main_fn = inputs
1322 .functions
1323 .iter()
1324 .find(|f| f.analyzed.name == "main")
1325 .ok_or_else(|| {
1326 CompileErrors::from(CompileError::without_span(ErrorKind::NoMainFunction))
1327 })?;
1328
1329 generate_llvm_objects_and_link(inputs, options, warnings)
1330}
1331
1332fn generate_llvm_objects_and_link(
1339 inputs: &BackendInputs<'_>,
1340 options: &CompileOptions,
1341 warnings: &[CompileWarning],
1342) -> MultiErrorResult<CompileOutput> {
1343 let _span = info_span!("codegen", backend = "llvm").entered();
1344
1345 let cfgs: Vec<&Cfg> = inputs.functions.iter().map(|f| &f.cfg).collect();
1346 let codegen_inputs = inputs.to_codegen_inputs(&cfgs);
1347 let object_bytes = gruel_codegen_llvm::generate(&codegen_inputs, options.opt_level)
1348 .map_err(CompileErrors::from)?;
1349
1350 let object_files = vec![object_bytes];
1352
1353 let linker_cmd = match &options.linker {
1356 LinkerMode::System(cmd) => cmd.clone(),
1357 LinkerMode::Internal => "cc".to_string(),
1358 };
1359 link_system_with_warnings(
1360 options,
1361 &object_files,
1362 &linker_cmd,
1363 warnings,
1364 inputs.extra_link_libraries,
1365 )
1366}
1367
1368pub fn generate_llvm_ir(inputs: &BackendInputs<'_>, opt_level: OptLevel) -> CompileResult<String> {
1373 let cfgs: Vec<&Cfg> = inputs.functions.iter().map(|f| &f.cfg).collect();
1374 let codegen_inputs = inputs.to_codegen_inputs(&cfgs);
1375 gruel_codegen_llvm::generate_ir(&codegen_inputs, opt_level)
1376}
1377
1378#[derive(Debug)]
1387pub struct AirOutput {
1388 pub ast: Ast,
1390 pub interner: ThreadedRodeo,
1392 pub rir: Rir,
1394 pub functions: Vec<AnalyzedFunction>,
1396 pub type_pool: TypeInternPool,
1398 pub strings: Vec<String>,
1400 pub bytes: Vec<Vec<u8>>,
1402 pub warnings: Vec<CompileWarning>,
1404}
1405
1406pub fn compile_to_air(source: &str) -> MultiErrorResult<AirOutput> {
1421 let lexer = Lexer::new(source);
1423 let (tokens, interner) = lexer.tokenize().map_err(CompileErrors::from)?;
1424
1425 let parser = Parser::new(tokens, interner);
1427 let (ast, interner) = parser.parse()?;
1428
1429 let astgen = AstGen::new(&ast, &interner);
1431 let rir = astgen.generate();
1432
1433 let sema = Sema::new(&rir, &interner, PreviewFeatures::default());
1435 let sema_output = sema.analyze_all()?;
1436
1437 Ok(AirOutput {
1438 ast,
1439 interner,
1440 rir,
1441 functions: sema_output.functions,
1442 type_pool: sema_output.type_pool,
1443 strings: sema_output.strings,
1444 bytes: sema_output.bytes,
1445 warnings: sema_output.warnings,
1446 })
1447}
1448
1449pub fn compile_to_cfg(source: &str) -> MultiErrorResult<CompileState> {
1466 compile_frontend(source)
1467}
1468
1469#[cfg(test)]
1470mod tests {
1471 use super::*;
1472
1473 #[test]
1474 fn test_compile_simple() {
1475 let output = compile("fn main() -> i32 { 42 }").unwrap();
1476 let magic = &output.elf[0..4];
1478 let is_elf = magic == [0x7F, b'E', b'L', b'F'];
1479 let is_macho = magic == 0xFEEDFACF_u32.to_le_bytes();
1480 assert!(
1481 is_elf || is_macho,
1482 "should produce valid ELF or Mach-O binary"
1483 );
1484 }
1485
1486 #[test]
1487 fn test_compile_no_main() {
1488 let result = compile("fn foo() -> i32 { 42 }");
1489 assert!(result.is_err());
1490 }
1491
1492 #[test]
1500 fn test_target_arch_intrinsic_uses_compile_target() {
1501 let source = r#"
1502 fn classify(a: Arch) -> i32 {
1503 match a {
1504 Arch::X86_64 => 1,
1505 Arch::Aarch64 => 2,
1506 Arch::X86 => 3,
1507 Arch::Arm => 4,
1508 Arch::Riscv32 => 5,
1509 Arch::Riscv64 => 6,
1510 Arch::Wasm32 => 7,
1511 Arch::Wasm64 => 8,
1512 }
1513 }
1514 fn main() -> i32 { classify(@target_arch()) }
1515 "#;
1516 let lexer = Lexer::new(source);
1517 let (tokens, interner) = lexer.tokenize().unwrap();
1518 let parser = Parser::new(tokens, interner);
1519 let (ast, interner) = parser.parse().unwrap();
1520
1521 let prev = PreviewFeatures::default();
1525 let (ast, interner) = prepend_prelude(ast, interner, &prev).unwrap();
1526
1527 let aarch64: Target = "aarch64-unknown-linux-gnu".parse().unwrap();
1528 let state = compile_frontend_from_ast_with_options_full_target(
1529 ast, interner, &prev, false, &aarch64,
1530 )
1531 .unwrap();
1532 let inputs = BackendInputs {
1533 functions: &state.functions,
1534 type_pool: &state.type_pool,
1535 strings: &state.strings,
1536 bytes: &state.bytes,
1537 interner: &state.interner,
1538 interface_defs: &state.interface_defs,
1539 interface_vtables: &state.interface_vtables,
1540 target: &aarch64,
1541 extra_link_libraries: &[],
1542 };
1543 let ir = generate_llvm_ir(&inputs, OptLevel::O3).unwrap();
1544 assert!(
1547 ir.contains("ret i32 2"),
1548 "expected `ret i32 2` (Aarch64 = 2) in IR, got:\n{}",
1549 ir,
1550 );
1551 }
1552
1553 #[test]
1559 fn test_target_threads_into_llvm_ir() {
1560 let state = compile_to_cfg("fn main() -> i32 { 0 }").unwrap();
1561 let target: Target = "aarch64-unknown-linux-gnu".parse().unwrap();
1562 let inputs = BackendInputs {
1563 functions: &state.functions,
1564 type_pool: &state.type_pool,
1565 strings: &state.strings,
1566 bytes: &state.bytes,
1567 interner: &state.interner,
1568 interface_defs: &state.interface_defs,
1569 interface_vtables: &state.interface_vtables,
1570 target: &target,
1571 extra_link_libraries: &[],
1572 };
1573 let ir = generate_llvm_ir(&inputs, OptLevel::O1).unwrap();
1576 assert!(
1577 ir.contains("aarch64"),
1578 "expected aarch64 triple in IR, got:\n{}",
1579 ir.lines().take(5).collect::<Vec<_>>().join("\n")
1580 );
1581 }
1582
1583 #[test]
1584 fn test_unused_variable_warning() {
1585 let output = compile("fn main() -> i32 { let x = 42; 0 }").unwrap();
1586 assert_eq!(output.warnings.len(), 1);
1587 assert!(output.warnings[0].to_string().contains("unused variable"));
1588 assert!(output.warnings[0].to_string().contains("'x'"));
1589 }
1590
1591 #[test]
1592 fn test_underscore_prefix_no_warning() {
1593 let output = compile("fn main() -> i32 { let _x = 42; 0 }").unwrap();
1594 assert_eq!(output.warnings.len(), 0);
1595 }
1596
1597 #[test]
1598 fn test_used_variable_no_warning() {
1599 let output = compile("fn main() -> i32 { let x = 42; x }").unwrap();
1600 assert_eq!(output.warnings.len(), 0);
1601 }
1602
1603 #[test]
1604 fn test_compile_frontend_includes_warnings() {
1605 let state = compile_frontend("fn main() -> i32 { let x = 42; 0 }").unwrap();
1606 assert_eq!(state.warnings.len(), 1);
1607 assert!(state.warnings[0].to_string().contains("unused variable"));
1608 }
1609
1610 #[test]
1611 fn test_multiple_errors_collected() {
1612 let source = r#"
1616 fn foo() -> i32 { true }
1617 fn bar() -> i32 { false }
1618 fn main() -> i32 { foo() + bar() }
1619 "#;
1620 let result = compile_frontend(source);
1621 let errors = match result {
1622 Ok(_) => panic!("expected error, got success"),
1623 Err(e) => e,
1624 };
1625
1626 assert!(
1628 errors.len() >= 2,
1629 "expected at least 2 errors, got {}",
1630 errors.len()
1631 );
1632
1633 for error in errors.iter() {
1635 assert!(
1636 error.to_string().contains("type mismatch"),
1637 "expected type mismatch error, got: {}",
1638 error
1639 );
1640 }
1641 }
1642
1643 #[test]
1644 fn test_multiple_errors_display() {
1645 let source = r#"
1648 fn foo() -> i32 { true }
1649 fn bar() -> i32 { false }
1650 fn main() -> i32 { foo() + bar() }
1651 "#;
1652 let errors = match compile_frontend(source) {
1653 Ok(_) => panic!("expected error, got success"),
1654 Err(e) => e,
1655 };
1656
1657 let display = errors.to_string();
1659 assert!(
1660 display.contains("type mismatch"),
1661 "display should contain error message"
1662 );
1663 if errors.len() > 1 {
1664 assert!(
1665 display.contains("more error"),
1666 "display should indicate more errors"
1667 );
1668 }
1669 }
1670
1671 #[test]
1672 fn test_single_error_still_works() {
1673 let source = "fn main() -> i32 { true }";
1675 let errors = match compile_frontend(source) {
1676 Ok(_) => panic!("expected error, got success"),
1677 Err(e) => e,
1678 };
1679
1680 assert_eq!(errors.len(), 1);
1681 assert!(
1682 errors
1683 .first()
1684 .unwrap()
1685 .to_string()
1686 .contains("type mismatch")
1687 );
1688 }
1689
1690 #[test]
1695 fn test_merge_symbols_no_duplicates() {
1696 let sources = vec![
1697 SourceFile::new("main.gruel", "fn main() -> i32 { 0 }", FileId::new(1)),
1698 SourceFile::new("utils.gruel", "fn helper() -> i32 { 42 }", FileId::new(2)),
1699 ];
1700 let parsed = parse_all_files(&sources).unwrap();
1701 let merged = merge_symbols(parsed);
1702 assert!(merged.is_ok(), "merge should succeed with no duplicates");
1703
1704 let program = merged.unwrap();
1705 assert_eq!(program.ast.items.len(), 2, "should have 2 items");
1706 }
1707
1708 #[test]
1709 fn test_merge_symbols_duplicate_function() {
1710 let sources = vec![
1711 SourceFile::new("a.gruel", "fn foo() -> i32 { 1 }", FileId::new(1)),
1712 SourceFile::new("b.gruel", "fn foo() -> i32 { 2 }", FileId::new(2)),
1713 ];
1714 let parsed = parse_all_files(&sources).unwrap();
1715 let result = merge_symbols(parsed);
1716 assert!(result.is_err(), "merge should fail with duplicate function");
1717
1718 let errors = result.unwrap_err();
1719 assert_eq!(errors.len(), 1, "should have 1 error");
1720 let err_msg = errors.first().unwrap().to_string();
1721 assert!(
1722 err_msg.contains("function `foo`"),
1723 "error should mention the function name"
1724 );
1725 }
1726
1727 #[test]
1728 fn test_merge_symbols_duplicate_struct() {
1729 let sources = vec![
1730 SourceFile::new(
1731 "a.gruel",
1732 "struct Point { x: i32 } fn main() -> i32 { 0 }",
1733 FileId::new(1),
1734 ),
1735 SourceFile::new("b.gruel", "struct Point { y: i32 }", FileId::new(2)),
1736 ];
1737 let parsed = parse_all_files(&sources).unwrap();
1738 let result = merge_symbols(parsed);
1739 assert!(result.is_err(), "merge should fail with duplicate struct");
1740
1741 let errors = result.unwrap_err();
1742 assert_eq!(errors.len(), 1, "should have 1 error");
1743 let err_msg = errors.first().unwrap().to_string();
1744 assert!(
1745 err_msg.contains("struct `Point`"),
1746 "error should mention the struct name"
1747 );
1748 }
1749
1750 #[test]
1751 fn test_merge_symbols_duplicate_enum() {
1752 let sources = vec![
1753 SourceFile::new(
1754 "a.gruel",
1755 "enum Color { Red } fn main() -> i32 { 0 }",
1756 FileId::new(1),
1757 ),
1758 SourceFile::new("b.gruel", "enum Color { Blue }", FileId::new(2)),
1759 ];
1760 let parsed = parse_all_files(&sources).unwrap();
1761 let result = merge_symbols(parsed);
1762 assert!(result.is_err(), "merge should fail with duplicate enum");
1763
1764 let errors = result.unwrap_err();
1765 assert_eq!(errors.len(), 1, "should have 1 error");
1766 let err_msg = errors.first().unwrap().to_string();
1767 assert!(
1768 err_msg.contains("enum `Color`"),
1769 "error should mention the enum name"
1770 );
1771 }
1772
1773 #[test]
1774 fn test_merge_symbols_struct_enum_conflict() {
1775 let sources = vec![
1777 SourceFile::new(
1778 "a.gruel",
1779 "struct Foo { x: i32 } fn main() -> i32 { 0 }",
1780 FileId::new(1),
1781 ),
1782 SourceFile::new("b.gruel", "enum Foo { Bar }", FileId::new(2)),
1783 ];
1784 let parsed = parse_all_files(&sources).unwrap();
1785 let result = merge_symbols(parsed);
1786 assert!(
1787 result.is_err(),
1788 "merge should fail when struct and enum have same name"
1789 );
1790
1791 let errors = result.unwrap_err();
1792 assert_eq!(errors.len(), 1, "should have 1 error");
1793 let err_msg = errors.first().unwrap().to_string();
1794 assert!(
1795 err_msg.contains("Foo") && err_msg.contains("conflicts"),
1796 "error should mention the conflict: {}",
1797 err_msg
1798 );
1799 }
1800
1801 #[test]
1802 fn test_merge_symbols_multiple_duplicates() {
1803 let sources = vec![
1805 SourceFile::new(
1806 "a.gruel",
1807 "fn foo() -> i32 { 1 } fn bar() -> i32 { 2 }",
1808 FileId::new(1),
1809 ),
1810 SourceFile::new(
1811 "b.gruel",
1812 "fn foo() -> i32 { 3 } fn bar() -> i32 { 4 }",
1813 FileId::new(2),
1814 ),
1815 ];
1816 let parsed = parse_all_files(&sources).unwrap();
1817 let result = merge_symbols(parsed);
1818 assert!(
1819 result.is_err(),
1820 "merge should fail with duplicate functions"
1821 );
1822
1823 let errors = result.unwrap_err();
1824 assert_eq!(errors.len(), 2, "should have 2 errors for 2 duplicates");
1825 }
1826
1827 #[test]
1828 fn test_merge_symbols_with_struct_methods() {
1829 let sources = vec![
1831 SourceFile::new(
1832 "a.gruel",
1833 "struct Point { x: i32, fn get_x(self) -> i32 { self.x } } fn main() -> i32 { 0 }",
1834 FileId::new(1),
1835 ),
1836 SourceFile::new("b.gruel", "fn helper() -> i32 { 42 }", FileId::new(2)),
1837 ];
1838 let parsed = parse_all_files(&sources).unwrap();
1839 let result = merge_symbols(parsed);
1840 assert!(result.is_ok(), "struct methods should not cause conflicts");
1841 }
1842
1843 #[test]
1848 fn test_cross_file_function_call() {
1849 let sources = vec![
1851 SourceFile::new(
1852 "main.gruel",
1853 "fn main() -> i32 { helper() }",
1854 FileId::new(1),
1855 ),
1856 SourceFile::new("utils.gruel", "fn helper() -> i32 { 42 }", FileId::new(2)),
1857 ];
1858 let result = compile_multi_file_with_options(&sources, &CompileOptions::default());
1859 assert!(
1860 result.is_ok(),
1861 "cross-file function call should compile: {:?}",
1862 result.err()
1863 );
1864 }
1865
1866 #[test]
1867 fn test_cross_file_function_call_with_args() {
1868 let sources = vec![
1870 SourceFile::new(
1871 "main.gruel",
1872 "fn main() -> i32 { add(10, 32) }",
1873 FileId::new(1),
1874 ),
1875 SourceFile::new(
1876 "utils.gruel",
1877 "fn add(a: i32, b: i32) -> i32 { a + b }",
1878 FileId::new(2),
1879 ),
1880 ];
1881 let result = compile_multi_file_with_options(&sources, &CompileOptions::default());
1882 assert!(
1883 result.is_ok(),
1884 "cross-file function call with args should compile: {:?}",
1885 result.err()
1886 );
1887 }
1888
1889 #[test]
1890 fn test_cross_file_struct_usage() {
1891 let sources = vec![
1893 SourceFile::new(
1894 "main.gruel",
1895 "fn main() -> i32 { let p = Point { x: 1, y: 2 }; p.x + p.y }",
1896 FileId::new(1),
1897 ),
1898 SourceFile::new(
1899 "types.gruel",
1900 "struct Point { x: i32, y: i32 }",
1901 FileId::new(2),
1902 ),
1903 ];
1904 let result = compile_multi_file_with_options(&sources, &CompileOptions::default());
1905 assert!(
1906 result.is_ok(),
1907 "cross-file struct usage should compile: {:?}",
1908 result.err()
1909 );
1910 }
1911
1912 #[test]
1913 fn test_cross_file_struct_as_function_param() {
1914 let sources = vec![
1916 SourceFile::new(
1917 "main.gruel",
1918 "fn main() -> i32 { let p = Point { x: 10, y: 5 }; get_sum(p) }",
1919 FileId::new(1),
1920 ),
1921 SourceFile::new(
1922 "types.gruel",
1923 "struct Point { x: i32, y: i32 }",
1924 FileId::new(2),
1925 ),
1926 SourceFile::new(
1927 "utils.gruel",
1928 "fn get_sum(p: Point) -> i32 { p.x + p.y }",
1929 FileId::new(3),
1930 ),
1931 ];
1932 let result = compile_multi_file_with_options(&sources, &CompileOptions::default());
1933 assert!(
1934 result.is_ok(),
1935 "cross-file struct as function param should compile: {:?}",
1936 result.err()
1937 );
1938 }
1939
1940 #[test]
1941 fn test_cross_file_enum_usage() {
1942 let sources = vec![
1944 SourceFile::new(
1945 "main.gruel",
1946 r#"fn main() -> i32 {
1947 let c = Color::Red;
1948 match c { Color::Red => 1, Color::Green => 2, Color::Blue => 3 }
1949 }"#,
1950 FileId::new(1),
1951 ),
1952 SourceFile::new(
1953 "types.gruel",
1954 "enum Color { Red, Green, Blue }",
1955 FileId::new(2),
1956 ),
1957 ];
1958 let result = compile_multi_file_with_options(&sources, &CompileOptions::default());
1959 assert!(
1960 result.is_ok(),
1961 "cross-file enum usage should compile: {:?}",
1962 result.err()
1963 );
1964 }
1965
1966 #[test]
1967 fn test_cross_file_no_main_function() {
1968 let sources = vec![
1970 SourceFile::new("a.gruel", "fn foo() -> i32 { 1 }", FileId::new(1)),
1971 SourceFile::new("b.gruel", "fn bar() -> i32 { 2 }", FileId::new(2)),
1972 ];
1973 let result = compile_multi_file_with_options(&sources, &CompileOptions::default());
1974 assert!(result.is_err(), "should fail without main function");
1975
1976 let errors = result.unwrap_err();
1977 let err_msg = errors.first().unwrap().to_string();
1978 assert!(
1979 err_msg.contains("main") && err_msg.contains("function"),
1980 "error should mention missing main function: {}",
1981 err_msg
1982 );
1983 }
1984
1985 #[test]
1986 fn test_cross_file_duplicate_main() {
1987 let sources = vec![
1989 SourceFile::new("a.gruel", "fn main() -> i32 { 1 }", FileId::new(1)),
1990 SourceFile::new("b.gruel", "fn main() -> i32 { 2 }", FileId::new(2)),
1991 ];
1992 let result = compile_multi_file_with_options(&sources, &CompileOptions::default());
1993 assert!(result.is_err(), "should fail with duplicate main");
1994
1995 let errors = result.unwrap_err();
1996 let err_msg = errors.first().unwrap().to_string();
1997 assert!(
1998 err_msg.contains("main"),
1999 "error should mention duplicate main: {}",
2000 err_msg
2001 );
2002 }
2003
2004 #[test]
2005 fn test_cross_file_undefined_function() {
2006 let sources = vec![
2008 SourceFile::new(
2009 "main.gruel",
2010 "fn main() -> i32 { nonexistent() }",
2011 FileId::new(1),
2012 ),
2013 SourceFile::new("utils.gruel", "fn helper() -> i32 { 42 }", FileId::new(2)),
2014 ];
2015 let result = compile_multi_file_with_options(&sources, &CompileOptions::default());
2016 assert!(result.is_err(), "should fail with undefined function");
2017
2018 let errors = result.unwrap_err();
2019 let err_msg = errors.first().unwrap().to_string();
2020 assert!(
2021 err_msg.contains("nonexistent") || err_msg.contains("undefined"),
2022 "error should mention undefined function: {}",
2023 err_msg
2024 );
2025 }
2026
2027 #[test]
2028 fn test_cross_file_three_files_chain() {
2029 let sources = vec![
2031 SourceFile::new(
2032 "main.gruel",
2033 "fn main() -> i32 { compute(6, 7) }",
2034 FileId::new(1),
2035 ),
2036 SourceFile::new(
2037 "utils.gruel",
2038 "fn compute(a: i32, b: i32) -> i32 { multiply(a, b) }",
2039 FileId::new(2),
2040 ),
2041 SourceFile::new(
2042 "math.gruel",
2043 "fn multiply(x: i32, y: i32) -> i32 { x * y }",
2044 FileId::new(3),
2045 ),
2046 ];
2047 let result = compile_multi_file_with_options(&sources, &CompileOptions::default());
2048 assert!(
2049 result.is_ok(),
2050 "chain of cross-file calls should compile: {:?}",
2051 result.err()
2052 );
2053 }
2054
2055 #[test]
2056 fn test_cross_file_mutual_calls() {
2057 let sources = vec![
2059 SourceFile::new(
2060 "main.gruel",
2061 r#"fn main() -> i32 { is_even(4) }
2062 fn is_even(n: i32) -> i32 { if n == 0 { 1 } else { is_odd(n - 1) } }"#,
2063 FileId::new(1),
2064 ),
2065 SourceFile::new(
2066 "utils.gruel",
2067 "fn is_odd(n: i32) -> i32 { if n == 0 { 0 } else { is_even(n - 1) } }",
2068 FileId::new(2),
2069 ),
2070 ];
2071 let result = compile_multi_file_with_options(&sources, &CompileOptions::default());
2072 assert!(
2073 result.is_ok(),
2074 "mutual cross-file calls should compile: {:?}",
2075 result.err()
2076 );
2077 }
2078
2079 #[test]
2084 fn test_module_member_access() {
2085 let sources = vec![
2089 SourceFile::new(
2090 "main.gruel",
2091 r#"fn main() -> i32 {
2092 let math = @import("math.gruel");
2093 math.add(1, 2)
2094 }"#,
2095 FileId::new(1),
2096 ),
2097 SourceFile::new(
2098 "math.gruel",
2099 "fn add(a: i32, b: i32) -> i32 { a + b }",
2100 FileId::new(2),
2101 ),
2102 ];
2103 let result = compile_multi_file_with_options(&sources, &CompileOptions::default());
2104 assert!(
2105 result.is_ok(),
2106 "module member access should compile: {:?}",
2107 result.err()
2108 );
2109 }
2110
2111 #[test]
2112 fn test_module_member_access_multiple_functions() {
2113 let sources = vec![
2115 SourceFile::new(
2116 "main.gruel",
2117 r#"fn main() -> i32 {
2118 let math = @import("math.gruel");
2119 let sum = math.add(10, 20);
2120 let diff = math.sub(sum, 5);
2121 diff
2122 }"#,
2123 FileId::new(1),
2124 ),
2125 SourceFile::new(
2126 "math.gruel",
2127 r#"fn add(a: i32, b: i32) -> i32 { a + b }
2128 fn sub(a: i32, b: i32) -> i32 { a - b }"#,
2129 FileId::new(2),
2130 ),
2131 ];
2132 let result = compile_multi_file_with_options(&sources, &CompileOptions::default());
2133 assert!(
2134 result.is_ok(),
2135 "module with multiple functions should compile: {:?}",
2136 result.err()
2137 );
2138 }
2139
2140 #[test]
2141 fn test_module_undefined_function_error() {
2142 let sources = vec![
2144 SourceFile::new(
2145 "main.gruel",
2146 r#"fn main() -> i32 {
2147 let math = @import("math.gruel");
2148 math.nonexistent(1, 2)
2149 }"#,
2150 FileId::new(1),
2151 ),
2152 SourceFile::new(
2153 "math.gruel",
2154 "fn add(a: i32, b: i32) -> i32 { a + b }",
2155 FileId::new(2),
2156 ),
2157 ];
2158 let result = compile_multi_file_with_options(&sources, &CompileOptions::default());
2159 assert!(
2160 result.is_err(),
2161 "undefined module function should fail to compile"
2162 );
2163 let err = result.err().unwrap().to_string();
2164 assert!(
2165 err.contains("undefined function") || err.contains("nonexistent"),
2166 "error should mention undefined function: {}",
2167 err
2168 );
2169 }
2170}
2171
2172#[cfg(test)]
2187mod integration_tests {
2188 use super::*;
2189
2190 mod integer_types {
2195 use super::*;
2196
2197 #[test]
2198 fn signed_integer_return() {
2199 assert!(compile_to_air("fn main() -> i8 { 42 }").is_ok());
2200 assert!(compile_to_air("fn main() -> i16 { 42 }").is_ok());
2201 assert!(compile_to_air("fn main() -> i32 { 42 }").is_ok());
2202 assert!(compile_to_air("fn main() -> i64 { 42 }").is_ok());
2203 }
2204
2205 #[test]
2206 fn unsigned_integer_return() {
2207 assert!(compile_to_air("fn main() -> u8 { 42 }").is_ok());
2208 assert!(compile_to_air("fn main() -> u16 { 42 }").is_ok());
2209 assert!(compile_to_air("fn main() -> u32 { 42 }").is_ok());
2210 assert!(compile_to_air("fn main() -> u64 { 42 }").is_ok());
2211 }
2212
2213 #[test]
2214 fn integer_type_mismatch() {
2215 let result = compile_to_air("fn main() -> i32 { let x: i64 = 1; x }");
2216 assert!(result.is_err());
2217 let err = result.unwrap_err().to_string();
2218 assert!(err.contains("type mismatch") || err.contains("expected"));
2219 }
2220
2221 #[test]
2222 fn integer_literal_type_inference() {
2223 assert!(compile_to_air("fn main() -> i64 { 100 }").is_ok());
2225 assert!(compile_to_air("fn main() -> i32 { let x: i64 = 100; 0 }").is_ok());
2227 }
2228 }
2229
2230 mod boolean_type {
2235 use super::*;
2236
2237 #[test]
2238 fn boolean_literals() {
2239 assert!(compile_to_air("fn main() -> bool { true }").is_ok());
2240 assert!(compile_to_air("fn main() -> bool { false }").is_ok());
2241 }
2242
2243 #[test]
2244 fn boolean_in_condition() {
2245 assert!(compile_to_cfg("fn main() -> i32 { if true { 1 } else { 0 } }").is_ok());
2246 }
2247
2248 #[test]
2249 fn non_boolean_condition_rejected() {
2250 let result = compile_to_air("fn main() -> i32 { if 1 { 1 } else { 0 } }");
2251 assert!(result.is_err());
2252 }
2253 }
2254
2255 mod unit_type {
2260 use super::*;
2261
2262 #[test]
2263 fn unit_return_type() {
2264 assert!(compile_to_air("fn main() -> () { () }").is_ok());
2265 }
2266
2267 #[test]
2268 fn unit_in_expression() {
2269 assert!(compile_to_air("fn main() -> () { let _x = (); () }").is_ok());
2270 }
2271
2272 #[test]
2273 fn implicit_unit_return() {
2274 assert!(compile_to_air("fn foo() -> () { } fn main() -> i32 { 0 }").is_ok());
2275 }
2276 }
2277
2278 mod arithmetic {
2283 use super::*;
2284
2285 #[test]
2286 fn basic_addition() {
2287 assert!(compile_to_air("fn main() -> i32 { 1 + 2 }").is_ok());
2288 }
2289
2290 #[test]
2291 fn basic_subtraction() {
2292 assert!(compile_to_air("fn main() -> i32 { 5 - 3 }").is_ok());
2293 }
2294
2295 #[test]
2296 fn basic_multiplication() {
2297 assert!(compile_to_air("fn main() -> i32 { 3 * 4 }").is_ok());
2298 }
2299
2300 #[test]
2301 fn basic_division() {
2302 assert!(compile_to_air("fn main() -> i32 { 10 / 2 }").is_ok());
2303 }
2304
2305 #[test]
2306 fn basic_modulo() {
2307 assert!(compile_to_air("fn main() -> i32 { 10 % 3 }").is_ok());
2308 }
2309
2310 #[test]
2311 fn unary_negation() {
2312 assert!(compile_to_air("fn main() -> i32 { -42 }").is_ok());
2313 }
2314
2315 #[test]
2316 fn operator_precedence() {
2317 let state = compile_to_cfg("fn main() -> i32 { 1 + 2 * 3 }").unwrap();
2319 assert_eq!(state.functions.len(), 1);
2320 }
2321
2322 #[test]
2323 fn chained_operations() {
2324 assert!(compile_to_air("fn main() -> i32 { 1 + 2 + 3 + 4 }").is_ok());
2325 }
2326
2327 #[test]
2328 fn mixed_type_arithmetic_rejected() {
2329 let result = compile_to_air("fn main() -> i32 { 1 + true }");
2330 assert!(result.is_err());
2331 }
2332
2333 #[test]
2334 fn unsigned_arithmetic() {
2335 assert!(compile_to_air("fn main() -> u32 { 10 + 5 }").is_ok());
2336 assert!(compile_to_air("fn main() -> u32 { 10 - 5 }").is_ok());
2337 assert!(compile_to_air("fn main() -> u32 { 10 * 5 }").is_ok());
2338 }
2339 }
2340
2341 mod comparison {
2346 use super::*;
2347
2348 #[test]
2349 fn equality_comparison() {
2350 assert!(compile_to_air("fn main() -> bool { 1 == 1 }").is_ok());
2351 assert!(compile_to_air("fn main() -> bool { 1 != 2 }").is_ok());
2352 }
2353
2354 #[test]
2355 fn ordering_comparison() {
2356 assert!(compile_to_air("fn main() -> bool { 1 < 2 }").is_ok());
2357 assert!(compile_to_air("fn main() -> bool { 2 > 1 }").is_ok());
2358 assert!(compile_to_air("fn main() -> bool { 1 <= 2 }").is_ok());
2359 assert!(compile_to_air("fn main() -> bool { 2 >= 1 }").is_ok());
2360 }
2361
2362 #[test]
2363 fn boolean_equality() {
2364 assert!(compile_to_air("fn main() -> bool { true == true }").is_ok());
2365 assert!(compile_to_air("fn main() -> bool { true != false }").is_ok());
2366 }
2367
2368 #[test]
2369 fn comparison_returns_bool() {
2370 let result = compile_to_air("fn main() -> i32 { 1 < 2 }");
2371 assert!(result.is_err()); }
2373
2374 #[test]
2375 fn mixed_type_comparison_rejected() {
2376 let result = compile_to_air("fn main() -> bool { 1 == true }");
2377 assert!(result.is_err());
2378 }
2379 }
2380
2381 mod logical {
2386 use super::*;
2387
2388 #[test]
2389 fn logical_and() {
2390 assert!(compile_to_cfg("fn main() -> bool { true && false }").is_ok());
2391 }
2392
2393 #[test]
2394 fn logical_or() {
2395 assert!(compile_to_cfg("fn main() -> bool { true || false }").is_ok());
2396 }
2397
2398 #[test]
2399 fn logical_not() {
2400 assert!(compile_to_air("fn main() -> bool { !true }").is_ok());
2401 }
2402
2403 #[test]
2404 fn chained_logical() {
2405 assert!(compile_to_cfg("fn main() -> bool { true && false || true }").is_ok());
2406 }
2407
2408 #[test]
2409 fn logical_with_non_bool_rejected() {
2410 let result = compile_to_air("fn main() -> bool { 1 && true }");
2411 assert!(result.is_err());
2412 }
2413 }
2414
2415 mod bitwise {
2420 use super::*;
2421
2422 #[test]
2423 fn bitwise_and() {
2424 assert!(compile_to_air("fn main() -> i32 { 5 & 3 }").is_ok());
2425 }
2426
2427 #[test]
2428 fn bitwise_or() {
2429 assert!(compile_to_air("fn main() -> i32 { 5 | 3 }").is_ok());
2430 }
2431
2432 #[test]
2433 fn bitwise_xor() {
2434 assert!(compile_to_air("fn main() -> i32 { 5 ^ 3 }").is_ok());
2435 }
2436
2437 #[test]
2438 fn bitwise_not() {
2439 assert!(compile_to_air("fn main() -> i32 { ~5 }").is_ok());
2440 }
2441
2442 #[test]
2443 fn shift_left() {
2444 assert!(compile_to_air("fn main() -> i32 { 1 << 4 }").is_ok());
2445 }
2446
2447 #[test]
2448 fn shift_right() {
2449 assert!(compile_to_air("fn main() -> i32 { 16 >> 2 }").is_ok());
2450 }
2451
2452 #[test]
2453 fn bitwise_on_bool_rejected() {
2454 let result = compile_to_air("fn main() -> bool { true & false }");
2455 assert!(result.is_err());
2456 }
2457 }
2458
2459 mod if_expressions {
2464 use super::*;
2465
2466 #[test]
2467 fn basic_if_else() {
2468 assert!(compile_to_cfg("fn main() -> i32 { if true { 1 } else { 0 } }").is_ok());
2469 }
2470
2471 #[test]
2472 fn if_with_condition_expr() {
2473 assert!(compile_to_cfg("fn main() -> i32 { if 1 < 2 { 1 } else { 0 } }").is_ok());
2474 }
2475
2476 #[test]
2477 fn nested_if() {
2478 let src = "fn main() -> i32 { if true { if false { 1 } else { 2 } } else { 3 } }";
2479 assert!(compile_to_cfg(src).is_ok());
2480 }
2481
2482 #[test]
2483 fn if_branches_must_match_type() {
2484 let result = compile_to_air("fn main() -> i32 { if true { 1 } else { true } }");
2485 assert!(result.is_err());
2486 }
2487
2488 #[test]
2489 fn if_result_type_checked() {
2490 let result = compile_to_air("fn main() -> bool { if true { 1 } else { 0 } }");
2491 assert!(result.is_err());
2492 }
2493 }
2494
2495 mod match_expressions {
2500 use super::*;
2501
2502 #[test]
2503 fn match_on_integer() {
2504 let src = r#"
2505 fn main() -> i32 {
2506 let x = 1;
2507 match x {
2508 1 => 10,
2509 2 => 20,
2510 _ => 0,
2511 }
2512 }
2513 "#;
2514 assert!(compile_to_cfg(src).is_ok());
2515 }
2516
2517 #[test]
2518 fn match_on_boolean() {
2519 let src = r#"
2520 fn main() -> i32 {
2521 match true {
2522 true => 1,
2523 false => 0,
2524 }
2525 }
2526 "#;
2527 assert!(compile_to_cfg(src).is_ok());
2528 }
2529
2530 #[test]
2531 fn match_exhaustiveness_required() {
2532 let result = compile_to_air(
2534 r#"
2535 fn main() -> i32 {
2536 match 1 {
2537 1 => 10,
2538 }
2539 }
2540 "#,
2541 );
2542 assert!(result.is_err());
2543 }
2544
2545 #[test]
2546 fn match_branches_must_match_type() {
2547 let result = compile_to_air(
2548 r#"
2549 fn main() -> i32 {
2550 match true {
2551 true => 1,
2552 false => true,
2553 }
2554 }
2555 "#,
2556 );
2557 assert!(result.is_err());
2558 }
2559 }
2560
2561 mod loops {
2566 use super::*;
2567
2568 #[test]
2569 fn while_loop_basic() {
2570 let src = r#"
2571 fn main() -> i32 {
2572 let mut x = 0;
2573 while x < 10 {
2574 x = x + 1;
2575 }
2576 x
2577 }
2578 "#;
2579 assert!(compile_to_cfg(src).is_ok());
2580 }
2581
2582 #[test]
2583 fn while_with_break() {
2584 let src = r#"
2585 fn main() -> i32 {
2586 let mut x = 0;
2587 while true {
2588 x = x + 1;
2589 if x == 5 {
2590 break;
2591 }
2592 }
2593 x
2594 }
2595 "#;
2596 assert!(compile_to_cfg(src).is_ok());
2597 }
2598
2599 #[test]
2600 fn while_with_continue() {
2601 let src = r#"
2602 fn main() -> i32 {
2603 let mut x = 0;
2604 let mut sum = 0;
2605 while x < 10 {
2606 x = x + 1;
2607 if x == 5 {
2608 continue;
2609 }
2610 sum = sum + x;
2611 }
2612 sum
2613 }
2614 "#;
2615 assert!(compile_to_cfg(src).is_ok());
2616 }
2617
2618 #[test]
2619 fn break_outside_loop_rejected() {
2620 let result = compile_to_air("fn main() -> i32 { break; 0 }");
2621 assert!(result.is_err());
2622 }
2623
2624 #[test]
2625 fn continue_outside_loop_rejected() {
2626 let result = compile_to_air("fn main() -> i32 { continue; 0 }");
2627 assert!(result.is_err());
2628 }
2629 }
2630
2631 mod let_bindings {
2636 use super::*;
2637
2638 #[test]
2639 fn basic_let() {
2640 assert!(compile_to_air("fn main() -> i32 { let x = 42; x }").is_ok());
2641 }
2642
2643 #[test]
2644 fn let_with_type_annotation() {
2645 assert!(compile_to_air("fn main() -> i32 { let x: i32 = 42; x }").is_ok());
2646 }
2647
2648 #[test]
2649 fn mutable_let() {
2650 let src = "fn main() -> i32 { let mut x = 1; x = 2; x }";
2651 assert!(compile_to_air(src).is_ok());
2652 }
2653
2654 #[test]
2655 fn immutable_assignment_rejected() {
2656 let result = compile_to_air("fn main() -> i32 { let x = 1; x = 2; x }");
2657 assert!(result.is_err());
2658 }
2659
2660 #[test]
2661 fn shadowing_allowed() {
2662 let src = "fn main() -> i32 { let x = 1; let x = 2; x }";
2663 assert!(compile_to_air(src).is_ok());
2664 }
2665
2666 #[test]
2667 fn shadowing_can_change_type() {
2668 let src = "fn main() -> bool { let x = 1; let x = true; x }";
2669 assert!(compile_to_air(src).is_ok());
2670 }
2671
2672 #[test]
2673 fn undefined_variable_rejected() {
2674 let result = compile_to_air("fn main() -> i32 { x }");
2675 assert!(result.is_err());
2676 }
2677 }
2678
2679 mod functions {
2684 use super::*;
2685
2686 #[test]
2687 fn function_call() {
2688 let src = r#"
2689 fn add(a: i32, b: i32) -> i32 { a + b }
2690 fn main() -> i32 { add(1, 2) }
2691 "#;
2692 assert!(compile_to_air(src).is_ok());
2693 }
2694
2695 #[test]
2696 fn function_forward_reference() {
2697 let src = r#"
2698 fn main() -> i32 { foo() }
2699 fn foo() -> i32 { 42 }
2700 "#;
2701 assert!(compile_to_air(src).is_ok());
2702 }
2703
2704 #[test]
2705 fn recursion() {
2706 let src = r#"
2707 fn factorial(n: i32) -> i32 {
2708 if n <= 1 { 1 } else { n * factorial(n - 1) }
2709 }
2710 fn main() -> i32 { factorial(5) }
2711 "#;
2712 assert!(compile_to_cfg(src).is_ok());
2713 }
2714
2715 #[test]
2716 fn mutual_recursion() {
2717 let src = r#"
2718 fn is_even(n: i32) -> bool {
2719 if n == 0 { true } else { is_odd(n - 1) }
2720 }
2721 fn is_odd(n: i32) -> bool {
2722 if n == 0 { false } else { is_even(n - 1) }
2723 }
2724 fn main() -> i32 { if is_even(4) { 1 } else { 0 } }
2725 "#;
2726 assert!(compile_to_cfg(src).is_ok());
2727 }
2728
2729 #[test]
2730 fn wrong_argument_count_rejected() {
2731 let src = r#"
2732 fn add(a: i32, b: i32) -> i32 { a + b }
2733 fn main() -> i32 { add(1) }
2734 "#;
2735 let result = compile_to_air(src);
2736 assert!(result.is_err());
2737 }
2738
2739 #[test]
2740 fn wrong_argument_type_rejected() {
2741 let src = r#"
2742 fn foo(x: i32) -> i32 { x }
2743 fn main() -> i32 { foo(true) }
2744 "#;
2745 let result = compile_to_air(src);
2746 assert!(result.is_err());
2747 }
2748
2749 #[test]
2750 fn undefined_function_rejected() {
2751 let result = compile_to_air("fn main() -> i32 { unknown() }");
2752 assert!(result.is_err());
2753 }
2754
2755 #[test]
2756 fn return_type_mismatch_rejected() {
2757 let result = compile_to_air("fn main() -> i32 { true }");
2758 assert!(result.is_err());
2759 }
2760 }
2761
2762 mod structs {
2767 use super::*;
2768
2769 #[test]
2770 fn struct_definition() {
2771 let src = r#"
2772 struct Point { x: i32, y: i32 }
2773 fn main() -> i32 { 0 }
2774 "#;
2775 let result = compile_to_air(src).unwrap();
2776 let all_struct_ids = result.type_pool.all_struct_ids();
2779 assert_eq!(all_struct_ids.len(), 1);
2780 let point_name = result.interner.get_or_intern("Point");
2782 let point_interned = result.type_pool.get_struct_by_name(point_name);
2783 assert!(
2784 point_interned.is_some(),
2785 "Point struct should exist in pool"
2786 );
2787 }
2788
2789 #[test]
2790 fn struct_literal() {
2791 let src = r#"
2792 struct Point { x: i32, y: i32 }
2793 fn main() -> i32 {
2794 let _p = Point { x: 1, y: 2 };
2795 0
2796 }
2797 "#;
2798 assert!(compile_to_air(src).is_ok());
2799 }
2800
2801 #[test]
2802 fn struct_field_access() {
2803 let src = r#"
2804 struct Point { x: i32, y: i32 }
2805 fn main() -> i32 {
2806 let p = Point { x: 10, y: 20 };
2807 p.x + p.y
2808 }
2809 "#;
2810 assert!(compile_to_air(src).is_ok());
2811 }
2812
2813 #[test]
2814 fn struct_field_order_independent() {
2815 let src = r#"
2816 struct Point { x: i32, y: i32 }
2817 fn main() -> i32 {
2818 let p = Point { y: 2, x: 1 };
2819 p.x
2820 }
2821 "#;
2822 assert!(compile_to_air(src).is_ok());
2823 }
2824
2825 #[test]
2826 fn struct_unknown_field_rejected() {
2827 let src = r#"
2828 struct Point { x: i32, y: i32 }
2829 fn main() -> i32 {
2830 let p = Point { x: 1, z: 2 };
2831 0
2832 }
2833 "#;
2834 let result = compile_to_air(src);
2835 assert!(result.is_err());
2836 }
2837
2838 #[test]
2839 fn struct_equality() {
2840 let src = r#"
2841 struct Point { x: i32, y: i32 }
2842 fn main() -> bool {
2843 let a = Point { x: 1, y: 2 };
2844 let b = Point { x: 1, y: 2 };
2845 a == b
2846 }
2847 "#;
2848 assert!(compile_to_air(src).is_ok());
2849 }
2850
2851 #[test]
2852 fn struct_move_semantics() {
2853 let src = r#"
2858 struct Point {
2859 x: i32, y: i32,
2860 fn __drop(self) { @ignore_unused(self); }
2861 }
2862 fn consume(p: Point) -> i32 { p.x }
2863 fn main() -> i32 {
2864 let p = Point { x: 1, y: 2 };
2865 let _a = consume(p);
2866 p.x
2867 }
2868 "#;
2869 let result = compile_to_air(src);
2870 assert!(result.is_err());
2871 }
2872 }
2873
2874 mod enums {
2879 use super::*;
2880
2881 #[test]
2882 fn enum_definition() {
2883 let src = r#"
2884 enum Color { Red, Green, Blue }
2885 fn main() -> i32 { 0 }
2886 "#;
2887 assert!(compile_to_air(src).is_ok());
2888 }
2889
2890 #[test]
2891 fn enum_variant_access() {
2892 let src = r#"
2893 enum Color { Red, Green, Blue }
2894 fn main() -> i32 {
2895 let _c = Color::Red;
2896 0
2897 }
2898 "#;
2899 assert!(compile_to_air(src).is_ok());
2900 }
2901
2902 #[test]
2903 fn enum_match() {
2904 let src = r#"
2905 enum Color { Red, Green, Blue }
2906 fn main() -> i32 {
2907 let c = Color::Green;
2908 match c {
2909 Color::Red => 1,
2910 Color::Green => 2,
2911 Color::Blue => 3,
2912 }
2913 }
2914 "#;
2915 assert!(compile_to_cfg(src).is_ok());
2916 }
2917
2918 #[test]
2919 fn enum_comparison_via_match() {
2920 let src = r#"
2923 enum Color { Red, Green, Blue }
2924 fn eq(a: Color, b: Color) -> bool {
2925 match a {
2926 Color::Red => match b { Color::Red => true, _ => false },
2927 Color::Green => match b { Color::Green => true, _ => false },
2928 Color::Blue => match b { Color::Blue => true, _ => false },
2929 }
2930 }
2931 fn main() -> i32 { if eq(Color::Red, Color::Red) { 1 } else { 0 } }
2932 "#;
2933 assert!(compile_to_cfg(src).is_ok());
2934 }
2935
2936 #[test]
2937 fn unknown_enum_variant_rejected() {
2938 let src = r#"
2939 enum Color { Red, Green, Blue }
2940 fn main() -> i32 {
2941 let _c = Color::Yellow;
2942 0
2943 }
2944 "#;
2945 let result = compile_to_air(src);
2946 assert!(result.is_err());
2947 }
2948 }
2949
2950 mod arrays {
2955 use super::*;
2956
2957 #[test]
2958 fn array_literal() {
2959 let src = "fn main() -> i32 { let _arr: [i32; 3] = [1, 2, 3]; 0 }";
2960 assert!(compile_to_air(src).is_ok());
2961 }
2962
2963 #[test]
2964 fn array_indexing() {
2965 let src = "fn main() -> i32 { let arr: [i32; 3] = [1, 2, 3]; arr[1] }";
2966 assert!(compile_to_air(src).is_ok());
2967 }
2968
2969 #[test]
2970 fn array_element_assignment() {
2971 let src = r#"
2972 fn main() -> i32 {
2973 let mut arr: [i32; 3] = [1, 2, 3];
2974 arr[0] = 10;
2975 arr[0]
2976 }
2977 "#;
2978 assert!(compile_to_air(src).is_ok());
2979 }
2980
2981 #[test]
2982 fn array_wrong_length_rejected() {
2983 let src = "fn main() -> i32 { let _arr: [i32; 3] = [1, 2]; 0 }";
2984 let result = compile_to_air(src);
2985 assert!(result.is_err());
2986 }
2987
2988 #[test]
2989 fn array_mixed_types_rejected() {
2990 let src = "fn main() -> i32 { let _arr: [i32; 2] = [1, true]; 0 }";
2991 let result = compile_to_air(src);
2992 assert!(result.is_err());
2993 }
2994 }
2995
2996 mod blocks {
3011 use super::*;
3012
3013 #[test]
3014 fn block_returns_final_expression() {
3015 let src = "fn main() -> i32 { { 1; 2; 3 } }";
3016 assert!(compile_to_air(src).is_ok());
3017 }
3018
3019 #[test]
3020 fn block_with_let_bindings() {
3021 let src = "fn main() -> i32 { { let x = 1; let y = 2; x + y } }";
3022 assert!(compile_to_air(src).is_ok());
3023 }
3024
3025 #[test]
3026 fn nested_blocks() {
3027 let src = "fn main() -> i32 { { { { 42 } } } }";
3028 assert!(compile_to_air(src).is_ok());
3029 }
3030
3031 #[test]
3032 fn block_scoping() {
3033 let result = compile_to_air("fn main() -> i32 { { let x = 1; } x }");
3035 assert!(result.is_err());
3036 }
3037 }
3038
3039 mod never_type {
3044 use super::*;
3045
3046 #[test]
3047 fn return_is_never() {
3048 let src = "fn main() -> i32 { return 42; }";
3049 assert!(compile_to_cfg(src).is_ok());
3050 }
3051
3052 #[test]
3053 fn break_is_never() {
3054 let src = r#"
3055 fn main() -> i32 {
3056 while true {
3057 break;
3058 }
3059 0
3060 }
3061 "#;
3062 assert!(compile_to_cfg(src).is_ok());
3063 }
3064
3065 #[test]
3066 fn never_in_if_branch() {
3067 let src = "fn main() -> i32 { if true { 1 } else { return 2; } }";
3068 assert!(compile_to_cfg(src).is_ok());
3069 }
3070 }
3071
3072 mod intrinsics {
3077 use super::*;
3078
3079 #[test]
3080 fn size_of_intrinsic() {
3081 let src = "fn main() -> i32 { let n: usize = @size_of(i32); @cast(n) }";
3083 assert!(compile_to_air(src).is_ok());
3084 }
3085
3086 #[test]
3087 fn align_of_intrinsic() {
3088 let src = "fn main() -> i32 { let n: usize = @align_of(i64); @cast(n) }";
3090 assert!(compile_to_air(src).is_ok());
3091 }
3092 }
3093
3094 mod cfg_construction {
3099 use super::*;
3100
3101 #[test]
3102 fn cfg_has_correct_function_count() {
3103 let src = r#"
3104 fn foo() -> i32 { 1 }
3105 fn bar() -> i32 { 2 }
3106 fn main() -> i32 { foo() + bar() }
3107 "#;
3108 let state = compile_to_cfg(src).unwrap();
3109 assert_eq!(state.functions.len(), 3);
3110 }
3111
3112 #[test]
3113 fn cfg_branches_for_if() {
3114 let src = "fn main() -> i32 { if true { 1 } else { 0 } }";
3115 let state = compile_to_cfg(src).unwrap();
3116 let main_cfg = &state.functions[0].cfg;
3118 assert!(main_cfg.blocks().len() >= 3); }
3120
3121 #[test]
3122 fn cfg_loop_for_while() {
3123 let src = r#"
3124 fn main() -> i32 {
3125 let mut x = 0;
3126 while x < 10 { x = x + 1; }
3127 x
3128 }
3129 "#;
3130 let state = compile_to_cfg(src).unwrap();
3131 let main_cfg = &state.functions[0].cfg;
3132 assert!(main_cfg.blocks().len() >= 3); }
3134 }
3135
3136 mod error_messages {
3141 use super::*;
3142
3143 #[test]
3144 fn type_mismatch_error_is_descriptive() {
3145 let result = compile_to_air("fn main() -> i32 { true }");
3146 let err = result.unwrap_err().to_string();
3147 assert!(err.contains("type mismatch") || err.contains("expected"));
3148 assert!(err.contains("i32") || err.contains("bool"));
3149 }
3150
3151 #[test]
3152 fn undefined_variable_error_is_descriptive() {
3153 let result = compile_to_air("fn main() -> i32 { unknown_var }");
3154 let err = result.unwrap_err().to_string();
3155 assert!(err.contains("undefined") || err.contains("unknown"));
3156 }
3157
3158 #[test]
3159 fn missing_field_error_is_descriptive() {
3160 let src = r#"
3161 struct Point { x: i32, y: i32 }
3162 fn main() -> i32 {
3163 let p = Point { x: 1 };
3164 0
3165 }
3166 "#;
3167 let result = compile_to_air(src);
3168 let err = result.unwrap_err().to_string();
3169 assert!(err.contains("missing") || err.contains("field"));
3170 }
3171 }
3172
3173 mod warnings {
3178 use super::*;
3179
3180 #[test]
3181 fn unused_variable_warning() {
3182 let result = compile_to_air("fn main() -> i32 { let x = 42; 0 }").unwrap();
3183 assert_eq!(result.warnings.len(), 1);
3184 assert!(result.warnings[0].to_string().contains("unused"));
3185 }
3186
3187 #[test]
3188 fn underscore_prefix_suppresses_warning() {
3189 let result = compile_to_air("fn main() -> i32 { let _x = 42; 0 }").unwrap();
3190 assert_eq!(result.warnings.len(), 0);
3191 }
3192
3193 #[test]
3194 fn used_variable_no_warning() {
3195 let result = compile_to_air("fn main() -> i32 { let x = 42; x }").unwrap();
3196 assert_eq!(result.warnings.len(), 0);
3197 }
3198 }
3199
3200 mod edge_cases {
3205 use super::*;
3206
3207 #[test]
3208 fn empty_function_body() {
3209 assert!(compile_to_air("fn main() -> () { }").is_ok());
3210 }
3211
3212 #[test]
3213 fn deeply_nested_expressions() {
3214 let src = "fn main() -> i32 { ((((((1 + 2)))))) }";
3215 assert!(compile_to_air(src).is_ok());
3216 }
3217
3218 #[test]
3219 fn many_parameters() {
3220 let src = r#"
3221 fn many(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32) -> i32 {
3222 a + b + c + d + e + f
3223 }
3224 fn main() -> i32 { many(1, 2, 3, 4, 5, 6) }
3225 "#;
3226 assert!(compile_to_air(src).is_ok());
3227 }
3228
3229 #[test]
3230 fn long_chain_of_operations() {
3231 let src = "fn main() -> i32 { 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 }";
3232 assert!(compile_to_air(src).is_ok());
3233 }
3234
3235 #[test]
3236 fn multiple_functions_same_local_names() {
3237 let src = r#"
3238 fn foo() -> i32 { let x = 1; x }
3239 fn bar() -> i32 { let x = 2; x }
3240 fn main() -> i32 { foo() + bar() }
3241 "#;
3242 assert!(compile_to_air(src).is_ok());
3243 }
3244 }
3245
3246 mod multi_file_parsing {
3251 use super::*;
3252
3253 #[test]
3254 fn parse_single_file() {
3255 let sources = vec![SourceFile::new(
3256 "main.gruel",
3257 "fn main() -> i32 { 42 }",
3258 FileId::new(1),
3259 )];
3260 let program = parse_all_files(&sources).unwrap();
3261 assert_eq!(program.files.len(), 1);
3262 assert_eq!(program.files[0].path, "main.gruel");
3263 assert_eq!(program.files[0].file_id, FileId::new(1));
3264 }
3265
3266 #[test]
3267 fn parse_multiple_files() {
3268 let sources = vec![
3269 SourceFile::new(
3270 "main.gruel",
3271 "fn main() -> i32 { helper() }",
3272 FileId::new(1),
3273 ),
3274 SourceFile::new("utils.gruel", "fn helper() -> i32 { 42 }", FileId::new(2)),
3275 ];
3276 let program = parse_all_files(&sources).unwrap();
3277 assert_eq!(program.files.len(), 2);
3278
3279 let paths: Vec<_> = program.files.iter().map(|f| f.path.as_str()).collect();
3281 assert!(paths.contains(&"main.gruel"));
3282 assert!(paths.contains(&"utils.gruel"));
3283 }
3284
3285 #[test]
3286 fn parse_many_files_in_parallel() {
3287 let sources: Vec<_> = (1..=10)
3289 .map(|i| {
3290 SourceFile::new(
3291 Box::leak(format!("file{}.gruel", i).into_boxed_str()),
3293 Box::leak(format!("fn func{}() -> i32 {{ {} }}", i, i).into_boxed_str()),
3294 FileId::new(i as u32),
3295 )
3296 })
3297 .collect();
3298
3299 let program = parse_all_files(&sources).unwrap();
3300 assert_eq!(program.files.len(), 10);
3301
3302 for (i, file) in program.files.iter().enumerate() {
3304 assert!(!file.ast.items.is_empty(), "File {} has no items", i);
3305 }
3306 }
3307
3308 #[test]
3309 fn parse_error_in_single_file() {
3310 let sources = vec![SourceFile::new(
3311 "bad.gruel",
3312 "fn main( { }", FileId::new(1),
3314 )];
3315
3316 let result = parse_all_files(&sources);
3317 assert!(result.is_err());
3318
3319 let errors = result.unwrap_err();
3320 assert!(!errors.is_empty());
3321 }
3322
3323 #[test]
3324 fn parse_error_in_multiple_files() {
3325 let sources = vec![
3326 SourceFile::new("good.gruel", "fn good() -> i32 { 42 }", FileId::new(1)),
3327 SourceFile::new(
3328 "bad.gruel",
3329 "fn bad( { }", FileId::new(2),
3331 ),
3332 ];
3333
3334 let result = parse_all_files(&sources);
3335 assert!(result.is_err());
3336
3337 let errors = result.unwrap_err();
3339 assert!(!errors.is_empty());
3340 }
3341
3342 #[test]
3343 fn lexer_error_in_file() {
3344 let sources = vec![SourceFile::new(
3345 "lexer_error.gruel",
3346 "fn main() -> i32 { $ }", FileId::new(1),
3348 )];
3349
3350 let result = parse_all_files(&sources);
3351 assert!(result.is_err());
3352 }
3353
3354 #[test]
3355 fn interner_merges_across_files() {
3356 let sources = vec![
3357 SourceFile::new("a.gruel", "fn foo() -> i32 { 1 }", FileId::new(1)),
3358 SourceFile::new("b.gruel", "fn bar() -> i32 { 2 }", FileId::new(2)),
3359 ];
3360
3361 let program = parse_all_files(&sources).unwrap();
3362
3363 let has_foo = program.interner.iter().any(|(_, s)| s == "foo");
3365 let has_bar = program.interner.iter().any(|(_, s)| s == "bar");
3366
3367 assert!(has_foo, "Interner should contain 'foo'");
3368 assert!(has_bar, "Interner should contain 'bar'");
3369 }
3370
3371 #[test]
3372 fn empty_file_parses_ok() {
3373 let sources = vec![SourceFile::new("empty.gruel", "", FileId::new(1))];
3374
3375 let program = parse_all_files(&sources).unwrap();
3376 assert_eq!(program.files.len(), 1);
3377 assert!(program.files[0].ast.items.is_empty());
3378 }
3379
3380 #[test]
3381 fn file_ids_preserved() {
3382 let sources = vec![
3383 SourceFile::new("a.gruel", "fn a() -> i32 { 1 }", FileId::new(42)),
3384 SourceFile::new("b.gruel", "fn b() -> i32 { 2 }", FileId::new(99)),
3385 ];
3386
3387 let program = parse_all_files(&sources).unwrap();
3388
3389 let file_ids: Vec<_> = program.files.iter().map(|f| f.file_id).collect();
3390 assert!(file_ids.contains(&FileId::new(42)));
3391 assert!(file_ids.contains(&FileId::new(99)));
3392 }
3393 }
3394
3395 mod recursive_cfg_lowering {
3402 use super::*;
3403 use gruel_cfg::CfgBuilder;
3404 use gruel_rir::AstGen;
3405
3406 fn compile_with_recursive(source: &str) -> usize {
3411 let lexer = Lexer::new(source);
3412 let (tokens, interner) = lexer.tokenize().map_err(CompileErrors::from).unwrap();
3413 let parser = Parser::new(tokens, interner);
3414 let (ast, interner) = parser.parse().unwrap();
3415 let astgen = AstGen::new(&ast, &interner);
3416 let rir = astgen.generate();
3417
3418 let sema = Sema::new(&rir, &interner, PreviewFeatures::default());
3419 let sema_output = sema.analyze_all().unwrap();
3420
3421 let mut count = 0;
3422 for func in sema_output.functions {
3423 if func.air.return_type() == Type::COMPTIME_TYPE {
3425 continue;
3426 }
3427 let _cfg_output = CfgBuilder::build(&func, &sema_output.type_pool, &interner);
3428 count += 1;
3429 }
3430 count
3431 }
3432
3433 #[test]
3434 fn match_on_int_builds_cfg() {
3435 assert!(
3436 compile_with_recursive("fn main() -> i32 { match 1 { 1 => 10, 2 => 20, _ => 0 } }")
3437 >= 1
3438 );
3439 }
3440
3441 #[test]
3442 fn match_on_bool_builds_cfg() {
3443 assert!(
3444 compile_with_recursive("fn main() -> i32 { match true { true => 1, false => 0 } }")
3445 >= 1
3446 );
3447 }
3448
3449 #[test]
3450 fn match_on_unit_enum_builds_cfg() {
3451 assert!(
3452 compile_with_recursive(
3453 "enum Color { Red, Green, Blue }
3454 fn main() -> i32 {
3455 let c = Color::Red;
3456 match c {
3457 Color::Red => 1,
3458 Color::Green => 2,
3459 Color::Blue => 3,
3460 }
3461 }"
3462 ) >= 1
3463 );
3464 }
3465
3466 #[test]
3467 fn match_on_data_variant_builds_cfg() {
3468 assert!(
3469 compile_with_recursive(
3470 "enum Opt { Some(i32), None }
3471 fn main() -> i32 {
3472 let o = Opt::Some(5);
3473 match o {
3474 Opt::Some(x) => x,
3475 Opt::None => 0,
3476 }
3477 }"
3478 ) >= 1
3479 );
3480 }
3481
3482 #[test]
3483 fn match_on_struct_variant_builds_cfg() {
3484 assert!(
3485 compile_with_recursive(
3486 "enum Shape { Circle { radius: i32 }, Square { side: i32 } }
3487 fn main() -> i32 {
3488 let s = Shape::Circle { radius: 5 };
3489 match s {
3490 Shape::Circle { radius } => radius,
3491 Shape::Square { side } => side,
3492 }
3493 }"
3494 ) >= 1
3495 );
3496 }
3497
3498 #[test]
3499 fn match_with_rest_in_data_variant_builds_cfg() {
3500 assert!(
3501 compile_with_recursive(
3502 "enum T { Triple(i32, i32, i32) }
3503 fn main() -> i32 {
3504 let t = T::Triple(1, 2, 3);
3505 match t {
3506 T::Triple(x, ..) => x,
3507 }
3508 }"
3509 ) >= 1
3510 );
3511 }
3512
3513 #[test]
3514 fn match_with_rest_in_struct_variant_builds_cfg() {
3515 assert!(
3516 compile_with_recursive(
3517 "enum Pt { Coord { x: i32, y: i32 } }
3518 fn main() -> i32 {
3519 let p = Pt::Coord { x: 1, y: 2 };
3520 match p {
3521 Pt::Coord { x, .. } => x,
3522 }
3523 }"
3524 ) >= 1
3525 );
3526 }
3527
3528 #[test]
3529 fn match_tuple_literal_root_builds_cfg() {
3530 assert!(
3535 compile_with_recursive(
3536 "fn main() -> i32 {
3537 let t = (1, 2);
3538 match t {
3539 (1, 2) => 3,
3540 _ => 0,
3541 }
3542 }"
3543 ) >= 1
3544 );
3545 }
3546
3547 #[test]
3548 fn match_tuple_bool_mix_builds_cfg() {
3549 assert!(
3550 compile_with_recursive(
3551 "fn main() -> i32 {
3552 let t = (true, 5);
3553 match t {
3554 (true, 5) => 1,
3555 (false, _) => 2,
3556 _ => 0,
3557 }
3558 }"
3559 ) >= 1
3560 );
3561 }
3562
3563 #[test]
3567 fn match_tuple_with_binding_builds_cfg() {
3568 assert!(
3569 compile_with_recursive(
3570 "fn main() -> i32 {
3571 let t = (1, 2);
3572 match t {
3573 (a, 1) => a,
3574 _ => 0,
3575 }
3576 }"
3577 ) >= 1
3578 );
3579 }
3580
3581 #[test]
3582 fn match_tuple_all_bindings_builds_cfg() {
3583 assert!(
3584 compile_with_recursive(
3585 "fn main() -> i32 {
3586 let t = (1, 2);
3587 match t {
3588 (a, b) => a + b,
3589 }
3590 }"
3591 ) >= 1
3592 );
3593 }
3594 }
3595}