Skip to main content

gruel_compiler/
lib.rs

1//! Gruel compiler driver.
2//!
3//! This crate orchestrates the compilation pipeline:
4//! Source -> Lexer -> Parser -> AstGen -> Sema -> CodeGen -> ELF
5//!
6//! It re-exports types from the component crates for convenience.
7//!
8//! # Diagnostic Formatting
9//!
10//! The [`MultiFileFormatter`] provides a clean API for formatting errors and warnings:
11//!
12//! ```ignore
13//! use gruel_compiler::{MultiFileFormatter, SourceInfo, FileId};
14//!
15//! let sources = vec![(FileId::new(1), SourceInfo::new(&source, "example.gruel"))];
16//! let formatter = MultiFileFormatter::new(sources);
17//!
18//! // Format an error
19//! let error_output = formatter.format_error(&error);
20//! eprintln!("{}", error_output);
21//! ```
22//!
23//! # Tracing
24//!
25//! This crate is instrumented with `tracing` spans for performance analysis.
26//! Use `--log-level info` or `--time-passes` to see timing information.
27
28mod 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
58// Re-export commonly used types
59pub 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// ============================================================================
75// Multi-file Compilation Types
76// ============================================================================
77
78/// A source file with its path and content.
79///
80/// Used for multi-file compilation to associate source content with file paths.
81#[derive(Debug, Clone)]
82pub struct SourceFile<'a> {
83    /// Path to the source file (used for error messages).
84    pub path: &'a str,
85    /// Source code content.
86    pub source: &'a str,
87    /// Unique identifier for this file.
88    pub file_id: FileId,
89}
90
91impl<'a> SourceFile<'a> {
92    /// Create a new source file.
93    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/// Result of parsing a single file.
103///
104/// Contains the AST and interner from parsing. The interner contains all
105/// string literals and identifiers interned during lexing.
106#[derive(Debug)]
107pub struct ParsedFile {
108    /// Path to the source file.
109    pub path: String,
110    /// File identifier for error reporting.
111    pub file_id: FileId,
112    /// The parsed abstract syntax tree.
113    pub ast: Ast,
114    /// String interner from lexing.
115    pub interner: ThreadedRodeo,
116}
117
118/// Result of parsing all source files.
119///
120/// Contains all parsed files and a merged interner for use in later phases.
121#[derive(Debug)]
122pub struct ParsedProgram {
123    /// Parsed files with their ASTs.
124    pub files: Vec<ParsedFile>,
125    /// Merged interner containing all symbols from all files.
126    pub interner: ThreadedRodeo,
127}
128
129/// Parse multiple source files with a shared interner.
130///
131/// Each file is lexed and parsed sequentially with a single shared interner.
132/// This ensures Spur values are consistent across all files, enabling cross-file
133/// symbol resolution during semantic analysis.
134///
135/// Note: This uses sequential parsing rather than parallel to share the interner.
136/// A future optimization could add parallel parsing with interner merging and
137/// AST Spur remapping.
138///
139/// # Arguments
140///
141/// * `sources` - Slice of source files to parse
142///
143/// # Returns
144///
145/// A `ParsedProgram` containing all parsed files and the shared interner,
146/// or errors from any file that failed to parse.
147///
148/// # Example
149///
150/// ```ignore
151/// use gruel_compiler::{SourceFile, parse_all_files};
152/// use gruel_util::FileId;
153///
154/// let sources = vec![
155///     SourceFile::new("main.gruel", "fn main() -> i32 { 0 }", FileId::new(1)),
156///     SourceFile::new("utils.gruel", "fn helper() -> i32 { 42 }", FileId::new(2)),
157/// ];
158/// let program = parse_all_files(&sources)?;
159/// ```
160pub fn parse_all_files(sources: &[SourceFile<'_>]) -> MultiErrorResult<ParsedProgram> {
161    parse_all_files_with_preview(sources, &PreviewFeatures::default())
162}
163
164/// Parse all files with an explicit set of preview features for the parser (ADR-0049).
165pub fn parse_all_files_with_preview(
166    sources: &[SourceFile<'_>],
167    preview_features: &PreviewFeatures,
168) -> MultiErrorResult<ParsedProgram> {
169    // Parse all files sequentially with a shared interner
170    // This ensures Spur values are consistent across files for cross-file symbol resolution
171    let mut parsed_files = Vec::with_capacity(sources.len());
172    let mut interner = ThreadedRodeo::new();
173
174    for source in sources {
175        // Create lexer with shared interner and file ID for proper error reporting
176        let lexer = Lexer::with_interner_and_file_id(source.source, interner, source.file_id);
177
178        // Tokenize - propagate error immediately (interner is consumed)
179        let (tokens, returned_interner) = lexer.tokenize().map_err(CompileErrors::from)?;
180        interner = returned_interner;
181
182        // Parse the tokens - propagate error immediately (interner is consumed)
183        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            // Note: interner is shared, but we store a dummy here for API compatibility
194            // The real interner is in the returned ParsedProgram
195            interner: ThreadedRodeo::new(),
196        });
197    }
198
199    Ok(ParsedProgram {
200        files: parsed_files,
201        interner,
202    })
203}
204
205/// Result of merging symbols from multiple parsed files.
206///
207/// Contains a merged AST with all items from all files and the merged interner.
208/// Used as input to RIR generation for multi-file compilation.
209#[derive(Debug)]
210pub struct MergedProgram {
211    /// The merged AST containing items from all files.
212    pub ast: Ast,
213    /// Merged interner containing all symbols from all files.
214    pub interner: ThreadedRodeo,
215}
216
217/// Result of validating and generating RIR from multiple parsed files.
218///
219/// This is the parallel-optimized path: RIR is generated per-file in parallel,
220/// then merged. Used by `compile_multi_file_with_options`.
221pub struct ValidatedProgram {
222    /// The merged RIR from all files.
223    pub rir: Rir,
224    /// Merged interner containing all symbols from all files.
225    pub interner: ThreadedRodeo,
226    /// Maps FileId to source file path (for module resolution).
227    pub file_paths: rustc_hash::FxHashMap<FileId, String>,
228}
229
230/// Information about a symbol definition for duplicate detection.
231#[derive(Debug, Clone)]
232struct SymbolDef {
233    /// Span of the first definition.
234    span: Span,
235    /// File path where the first definition was found.
236    file_path: String,
237}
238
239/// Merge symbols from all parsed files into a unified program.
240///
241/// This function:
242/// 1. Combines all items from all files into a single merged AST
243/// 2. Detects duplicate function, struct, and enum definitions
244/// 3. Reports errors with both locations for any duplicates found
245///
246/// # Arguments
247///
248/// * `program` - The parsed program containing all files
249///
250/// # Returns
251///
252/// A `MergedProgram` ready for RIR generation, or errors if duplicates are found.
253///
254/// # Example
255///
256/// ```ignore
257/// use gruel_compiler::{parse_all_files, merge_symbols, SourceFile};
258/// use gruel_util::FileId;
259///
260/// let sources = vec![
261///     SourceFile::new("main.gruel", "fn main() -> i32 { helper() }", FileId::new(1)),
262///     SourceFile::new("utils.gruel", "fn helper() -> i32 { 42 }", FileId::new(2)),
263/// ];
264/// let parsed = parse_all_files(&sources)?;
265/// let merged = merge_symbols(parsed)?;
266/// // merged.ast now contains both functions
267/// ```
268pub 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    // Track seen symbols for duplicate detection.
274    // Key: symbol name (resolved string), Value: first definition info
275    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    // Collect all items and detect duplicates
280    let mut all_items = Vec::new();
281    let mut errors: Vec<CompileError> = Vec::new();
282
283    // Use the shared interner for resolving all symbol names
284    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                    // Use shared interner for consistent Spur resolution
291                    let name = interner.resolve(&func.name.name).to_string();
292                    if let Some(first) = functions.get(&name) {
293                        // Duplicate function definition
294                        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                    // Use shared interner for consistent Spur resolution
314                    let name = interner.resolve(&s.name.name).to_string();
315                    if let Some(first) = structs.get(&name) {
316                        // Duplicate struct definition
317                        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                        // Struct name conflicts with enum
327                        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                    // Use shared interner for consistent Spur resolution
350                    let name = interner.resolve(&e.name.name).to_string();
351                    if let Some(first) = enums.get(&name) {
352                        // Duplicate enum definition
353                        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                        // Enum name conflicts with struct
363                        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                    // Interfaces (ADR-0056) are validated in Sema. Cross-file
386                    // duplicate detection is added when interfaces become a
387                    // namespace alongside structs/enums in Phase 2.
388                }
389                Item::Derive(_) => {
390                    // Derives (ADR-0058) are validated in Sema; cross-file
391                    // duplicate detection follows the interface model.
392                }
393                Item::Const(_) => {
394                    // Const declarations are validated in Sema; cross-file
395                    // duplicate detection happens in the declarations phase.
396                }
397                Item::LinkExtern(_) => {
398                    // ADR-0085: extern fn declarations live on the RIR
399                    // side-table; cross-file duplicate detection happens
400                    // in sema (the registered symbol-name set there is
401                    // the source of truth across the whole compilation).
402                }
403                Item::Error(_) => {
404                    // Error nodes from parser recovery are skipped - errors were already reported
405                }
406            }
407            all_items.push(item.clone());
408        }
409    }
410
411    // If there are any duplicate definitions, return all errors
412    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
432/// Validate symbols and generate RIR in parallel for multi-file compilation.
433///
434/// This is the optimized path for multi-file compilation:
435/// 1. Validates that there are no duplicate symbol definitions across files
436/// 2. Generates RIR for each file in parallel using Rayon
437/// 3. Merges the per-file RIRs into a single RIR with renumbered references
438///
439/// This is more efficient than the sequential path for projects with many files,
440/// as RIR generation is embarrassingly parallel (no cross-file dependencies
441/// at the RIR level).
442///
443/// # Arguments
444///
445/// * `program` - The parsed program containing all files and shared interner
446///
447/// # Returns
448///
449/// A `ValidatedProgram` containing the merged RIR, or errors if duplicates are found.
450pub 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    // Step 0: Build file_id -> path mapping for module resolution
462    let file_paths: HashMap<FileId, String> = program
463        .files
464        .iter()
465        .map(|f| (f.file_id, f.path.clone()))
466        .collect();
467
468    // Step 1: Validate symbols for duplicates (same logic as merge_symbols)
469    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                    // Interfaces (ADR-0056) are validated in Sema; cross-file
568                    // duplicate detection is added in Phase 2.
569                }
570                Item::Derive(_) => {
571                    // Derives (ADR-0058) are validated in Sema; cross-file
572                    // duplicate detection follows the interface model.
573                }
574                Item::Const(_) => {
575                    // Validated in Sema
576                }
577                Item::LinkExtern(_) => {
578                    // ADR-0085: extern fn declarations live on the RIR
579                    // side-table; cross-file duplicate detection happens
580                    // in sema (the registered symbol-name set there is
581                    // the source of truth across the whole compilation).
582                }
583                Item::Error(_) => {
584                    // Error nodes from parser recovery are skipped
585                }
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    // Step 2: Generate RIR per-file in parallel
602    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    // Step 3: Merge RIRs
617    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/// Configuration options for compilation.
632///
633/// Controls target architecture, linker selection, optimization level, and feature flags.
634///
635/// # Example
636///
637/// ```ignore
638/// let options = CompileOptions {
639///     target: Target::host(),
640///     linker: LinkerMode::System("cc".to_string()),
641///     opt_level: OptLevel::O1,
642///     preview_features: PreviewFeatures::default(),
643///     jobs: 0, // 0 = auto-detect
644/// };
645/// let output = compile_with_options(source, &options)?;
646/// ```
647#[derive(Debug, Clone)]
648pub struct CompileOptions {
649    /// The target architecture and OS.
650    pub target: Target,
651    /// Which linker to use.
652    pub linker: LinkerMode,
653    /// Optimization level.
654    pub opt_level: OptLevel,
655    /// Enabled preview features.
656    pub preview_features: PreviewFeatures,
657    /// Number of parallel jobs (0 = auto-detect, use all cores).
658    pub jobs: usize,
659    /// When true, suppress the on-the-fly stderr print of comptime `@dbg`
660    /// output. The output is still collected on `SemaOutput.comptime_dbg_output`
661    /// and a warning is still emitted for each call. Used by fuzz harnesses.
662    pub capture_comptime_dbg: bool,
663    /// Optional path to the on-disk cache directory (ADR-0074). When
664    /// `Some`, the parse / AIR / LLVM-IR caches live here. When `None`,
665    /// no cache is consulted or written. The CLI driver populates this
666    /// to a workspace-relative default unless `--no-cache` is given.
667    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/// A function with its typed IR (AIR) and control flow graph (CFG).
685///
686/// This combines the output of semantic analysis with CFG construction.
687#[derive(Debug)]
688pub struct FunctionWithCfg {
689    /// The analyzed function from semantic analysis.
690    pub analyzed: AnalyzedFunction,
691    /// The control flow graph built from the AIR.
692    pub cfg: Cfg,
693}
694
695/// Bundle of inputs handed to the backend.
696///
697/// All fields come from frontend output; the backend reads them but does not
698/// own or mutate them. Bundling avoids 8-arg signatures across each pipeline
699/// stage (`compile_backend`, `generate_llvm_objects_and_link`,
700/// `generate_llvm_ir`).
701pub 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    /// ADR-0085 + ADR-0086: deduplicated library names from every
711    /// `link_extern("…")` / `static_link_extern("…")` block, paired
712    /// with the per-library linkage mode. Linker emits `-l<name>` for
713    /// dynamic libs and `-Wl,-Bstatic … -Wl,-Bdynamic` (ELF) /
714    /// `-Wl,-search_paths_first -l<name>` (Mach-O) for static libs.
715    pub extra_link_libraries: &'a [(String, crate::link::LinkMode)],
716}
717
718impl<'a> BackendInputs<'a> {
719    /// Convert to LLVM `CodegenInputs`. Returns the cfgs vec separately so
720    /// the caller can keep it alive for the borrow.
721    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
735/// Intermediate compilation state after frontend processing.
736///
737/// This allows inspection of the IR at each stage, useful for
738/// debugging and the `--emit` CLI flags.
739pub struct CompileState {
740    /// The abstract syntax tree.
741    pub ast: Ast,
742    /// String interner used during compilation.
743    pub interner: ThreadedRodeo,
744    /// The untyped IR (RIR).
745    pub rir: Rir,
746    /// Analyzed functions with typed IR and control flow graphs.
747    pub functions: Vec<FunctionWithCfg>,
748    /// Type intern pool containing all struct and enum definitions.
749    pub type_pool: TypeInternPool,
750    /// String literals indexed by their string_const index.
751    pub strings: Vec<String>,
752    /// Byte-blob literals indexed by their bytes_const index (`@embed_file`).
753    pub bytes: Vec<Vec<u8>>,
754    /// Warnings collected during compilation.
755    pub warnings: Vec<CompileWarning>,
756    /// Lines of `@dbg` output collected during comptime evaluation.
757    pub comptime_dbg_output: Vec<String>,
758    /// Interface definitions (ADR-0056), indexed by InterfaceId.0.
759    pub interface_defs: Vec<gruel_air::InterfaceDef>,
760    /// (StructId, InterfaceId) → conformance witness; codegen uses this to
761    /// emit one vtable global per pair.
762    pub interface_vtables: gruel_air::InterfaceVtables,
763}
764
765/// Output from successful compilation.
766///
767/// Contains the compiled executable binary and any warnings generated
768/// during compilation. The binary format depends on the target platform
769/// (ELF for Linux, Mach-O for macOS).
770#[derive(Debug)]
771pub struct CompileOutput {
772    /// The compiled ELF binary.
773    pub elf: Vec<u8>,
774    /// Warnings generated during compilation.
775    pub warnings: Vec<CompileWarning>,
776}
777
778/// ADR-0078 Phase 1: lex+parse the embedded prelude using `interner` and
779/// prepend its top-level items to `ast`. Useful for callers that bypass
780/// `CompilationUnit::parse` (lib tests, `--emit`-only flows that already
781/// have a parsed AST in hand) but still want the prelude-resident
782/// declarations (`Option`, `Result`, `Arch`, `Os`, `Drop`/`Copy`/`Clone`,
783/// etc.) to be visible during sema.
784///
785/// The frontend entry points (`compile_frontend_from_ast_*`) deliberately
786/// do *not* call this — the multi-file driver merges the prelude with all
787/// other parsed files via `merge_symbols` before invoking the frontend.
788/// Calling this twice on an already-merged AST would produce duplicate
789/// definitions.
790pub 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    // For tests/callers that bypass `CompilationUnit::parse`, inline the
798    // prelude submodules directly: the @import-based root (`_prelude.gruel`)
799    // would require a working module-resolution path with `file_paths`
800    // populated, which test fixtures generally don't set up. Inlining the
801    // submodule pub items at the top level matches the runtime behavior
802    // (those items end up globally visible regardless). Note we skip
803    // `other_std_files` (like `_std.gruel`) because those have @imports
804    // that won't resolve in a bare-AST test environment.
805    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
830/// Compile source code through all frontend phases (up to but not including codegen).
831///
832/// This runs: lexing → parsing → AST to RIR → semantic analysis → CFG construction.
833/// Returns the compile state which can be inspected for debugging.
834///
835/// This function collects errors from multiple functions instead of stopping at the
836/// first error, allowing users to see all issues at once.
837///
838/// Uses default optimization level (O0) and no preview features. For custom options,
839/// use [`compile_frontend_with_options`].
840pub fn compile_frontend(source: &str) -> MultiErrorResult<CompileState> {
841    compile_frontend_with_options(source, &PreviewFeatures::default())
842}
843
844/// Compile source code through all frontend phases.
845///
846/// This runs: lexing → parsing → AST to RIR → semantic analysis → CFG construction.
847/// Returns the compile state which can be inspected for debugging.
848///
849/// This function collects errors from multiple functions instead of stopping at the
850/// first error, allowing users to see all issues at once.
851pub 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
858/// Like [`compile_frontend_with_options`], but additionally lets the caller
859/// suppress the on-the-fly stderr print of comptime `@dbg` output. When
860/// `suppress_comptime_dbg_print` is true, output is only collected in the
861/// `SemaOutput.comptime_dbg_output` buffer. Used by fuzz harnesses that
862/// consume the buffer directly and don't want noise on stderr.
863pub 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    // Lexing - errors here are fatal (can't continue without tokens)
871    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    // Parsing - errors here are fatal (can't continue without AST)
880    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
898/// Compile from an already-parsed AST through all remaining frontend phases.
899///
900/// This runs: AST to RIR → semantic analysis → CFG construction.
901/// Use this when you already have a parsed AST (e.g., for `--emit` modes that
902/// need both AST output and later stage output without double-parsing).
903///
904/// Uses no preview features. For custom options, use [`compile_frontend_from_ast_with_options`].
905pub 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
912/// Compile from an already-parsed AST through all remaining frontend phases.
913///
914/// This runs: AST to RIR → semantic analysis → CFG construction.
915/// Use this when you already have a parsed AST (e.g., for `--emit` modes that
916/// need both AST output and later stage output without double-parsing).
917///
918/// This function collects errors from multiple functions instead of stopping at the
919/// first error, allowing users to see all issues at once.
920pub 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
928/// Like [`compile_frontend_from_ast_with_options`], but additionally lets the
929/// caller suppress the on-the-fly stderr print of comptime `@dbg` output.
930pub 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
945/// Like [`compile_frontend_from_ast_with_options_full`], but additionally
946/// accepts the compile target so `@target_arch()` / `@target_os()` reflect
947/// it (ADR-0077). Use this from `--emit` paths that already have a target
948/// in hand. The non-`_target` variants default to the host.
949pub 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
966/// Full-control variant: like
967/// [`compile_frontend_from_ast_with_options_full_target`] but additionally
968/// threads a `file_id → path` map into sema so `@import("...")` resolution
969/// can find sibling modules. The LSP needs this on the per-root compile path
970/// — `parse_all_files_with_preview` doesn't populate sema's `file_paths` map
971/// on its own.
972pub 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    // AST to RIR (untyped IR)
981    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    // Semantic analysis (RIR to AIR) - this now collects multiple errors
990    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    // Synthesize drop glue functions for structs that need them
1006    let drop_glue_functions = drop_glue::synthesize_drop_glue(&sema_output.type_pool, &interner);
1007    // ADR-0079 retired clone glue: the prelude `derive Clone` body
1008    // emits the clone method via the standard derive-expansion path.
1009
1010    // Combine user functions with synthesized drop glue functions
1011    // Filter out comptime-only functions (those returning `type`) as they don't generate runtime code
1012    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    // Build CFGs from AIR (one per function) in parallel, collecting warnings
1020    let (functions, warnings) = {
1021        let _span = info_span!("cfg_construction").entered();
1022
1023        // Build CFGs in parallel - each function is independent
1024        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        // Unzip the results and collect all warnings
1040        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
1069/// Compile from already-generated RIR through remaining frontend phases.
1070///
1071/// This runs: semantic analysis → CFG construction → optimization.
1072/// This is the optimized path used by parallel multi-file compilation, where
1073/// RIR has already been generated per-file in parallel and merged.
1074///
1075/// # Arguments
1076///
1077/// * `rir` - The RIR (already merged if from multiple files)
1078/// * `interner` - The shared string interner
1079/// * `opt_level` - Optimization level
1080/// * `preview_features` - Enabled preview features
1081///
1082/// # Returns
1083///
1084/// A `CompileStateFromRir` containing the compilation state.
1085pub 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
1098/// Compile frontend from RIR with file paths for module resolution.
1099///
1100/// This is the full version that accepts file_id -> path mapping for
1101/// multi-file compilation with module imports.
1102pub 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    // Semantic analysis (RIR to AIR)
1109    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    // Synthesize drop glue functions for structs that need them
1123    let drop_glue_functions = drop_glue::synthesize_drop_glue(&sema_output.type_pool, &interner);
1124    // ADR-0079 retired clone glue: the prelude `derive Clone` body
1125    // emits the clone method via the standard derive-expansion path.
1126
1127    // Combine user functions with synthesized drop glue functions
1128    // Filter out comptime-only functions (those returning `type`) as they don't generate runtime code
1129    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    // Build CFGs from AIR (one per function) in parallel, collecting warnings
1137    let (functions, warnings) = {
1138        let _span = info_span!("cfg_construction").entered();
1139
1140        // Build CFGs in parallel - each function is independent
1141        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        // Unzip the results and collect all warnings
1157        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
1185/// Intermediate compilation state after frontend processing from RIR.
1186///
1187/// Similar to `CompileState` but without the AST (since we started from RIR directly
1188/// in the parallel compilation path).
1189pub struct CompileStateFromRir {
1190    /// String interner used during compilation.
1191    pub interner: ThreadedRodeo,
1192    /// The untyped IR (RIR).
1193    pub rir: Rir,
1194    /// Analyzed functions with typed IR and control flow graphs.
1195    pub functions: Vec<FunctionWithCfg>,
1196    /// Type intern pool containing all struct and enum definitions.
1197    pub type_pool: TypeInternPool,
1198    /// String literals indexed by their string_const index.
1199    pub strings: Vec<String>,
1200    /// Byte-blob literals indexed by their bytes_const index (`@embed_file`).
1201    pub bytes: Vec<Vec<u8>>,
1202    /// Warnings collected during compilation.
1203    pub warnings: Vec<CompileWarning>,
1204    /// Lines of `@dbg` output collected during comptime evaluation.
1205    pub comptime_dbg_output: Vec<String>,
1206    /// Interface definitions (ADR-0056), indexed by InterfaceId.0.
1207    pub interface_defs: Vec<gruel_air::InterfaceDef>,
1208    /// (StructId, InterfaceId) → conformance witness; codegen uses this to
1209    /// emit one vtable global per pair.
1210    pub interface_vtables: gruel_air::InterfaceVtables,
1211}
1212
1213/// Compile source code to an ELF binary.
1214///
1215/// This is the main entry point for compilation.
1216/// Returns the ELF binary along with any warnings generated during compilation.
1217///
1218/// This function collects errors from multiple functions instead of stopping at the
1219/// first error, allowing users to see all issues at once.
1220pub fn compile(source: &str) -> MultiErrorResult<CompileOutput> {
1221    compile_with_options(source, &CompileOptions::default())
1222}
1223
1224/// Compile source code to an ELF binary with the given options.
1225///
1226/// This allows specifying the target architecture, optimization level, and other compilation options.
1227///
1228/// This function collects errors from multiple functions instead of stopping at the
1229/// first error, allowing users to see all issues at once.
1230pub fn compile_with_options(
1231    source: &str,
1232    options: &CompileOptions,
1233) -> MultiErrorResult<CompileOutput> {
1234    // Delegate to multi-file compilation with a single source file
1235    let sources = vec![SourceFile::new("<source>", source, FileId::new(1))];
1236    compile_multi_file_with_options(&sources, options)
1237}
1238
1239/// Compile multiple source files to an ELF binary.
1240///
1241/// This is the main entry point for multi-file compilation. It:
1242/// 1. Parses all files in parallel
1243/// 2. Merges symbols into a unified program
1244/// 3. Performs semantic analysis across all files
1245/// 4. Generates code for the combined program
1246///
1247/// Cross-file references (function calls, struct/enum usage) are resolved during
1248/// semantic analysis since all symbols are visible in the merged program.
1249///
1250/// # Arguments
1251///
1252/// * `sources` - Slice of source files to compile
1253/// * `options` - Compilation options (target, linker, optimization level, etc.)
1254///
1255/// # Returns
1256///
1257/// A `CompileOutput` containing the ELF binary and any warnings,
1258/// or errors if compilation fails.
1259///
1260/// # Example
1261///
1262/// ```ignore
1263/// use gruel_compiler::{SourceFile, CompileOptions, compile_multi_file_with_options};
1264/// use gruel_util::FileId;
1265///
1266/// let sources = vec![
1267///     SourceFile::new("main.gruel", "fn main() -> i32 { helper() }", FileId::new(1)),
1268///     SourceFile::new("utils.gruel", "fn helper() -> i32 { 42 }", FileId::new(2)),
1269/// ];
1270/// let options = CompileOptions::default();
1271/// let output = compile_multi_file_with_options(&sources, &options)?;
1272/// ```
1273pub fn compile_multi_file_with_options(
1274    sources: &[SourceFile<'_>],
1275    options: &CompileOptions,
1276) -> MultiErrorResult<CompileOutput> {
1277    // Configure Rayon's global thread pool based on the jobs setting.
1278    // This must happen before any parallel operations.
1279    // 0 means auto-detect (use all cores), which is Rayon's default.
1280    if options.jobs > 0 {
1281        // Ignore the error if the pool has already been initialized (e.g., in tests).
1282        // This is safe because we're just trying to set the thread count.
1283        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    // Use CompilationUnit for the entire pipeline
1298    let mut unit = CompilationUnit::new(sources.to_vec(), options.clone());
1299    unit.run_all()
1300}
1301
1302// ============================================================================
1303// Unified Backend (Codegen + Linking)
1304// ============================================================================
1305
1306/// Compile analyzed functions to a binary.
1307///
1308/// This is the unified backend that handles both architectures. It:
1309/// 1. Generates machine code for each function in parallel
1310/// 2. Creates object files with relocations
1311/// 3. Links them into an executable
1312///
1313/// This function is used by `CompilationUnit::compile()` and the legacy
1314/// compile functions.
1315pub fn compile_backend(
1316    inputs: &BackendInputs<'_>,
1317    options: &CompileOptions,
1318    warnings: &[CompileWarning],
1319) -> MultiErrorResult<CompileOutput> {
1320    // Check for main function
1321    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
1332/// Generate a single LLVM object file from all functions and link it.
1333///
1334/// Unlike the native backends, which produce one object file per function,
1335/// the LLVM backend compiles all functions into a single LLVM module and emits
1336/// one object file. Linking always uses the system linker because LLVM emits
1337/// platform-native object formats (ELF on Linux, Mach-O on macOS).
1338fn 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    // LLVM produces a single object file; wrap it as a one-element slice.
1351    let object_files = vec![object_bytes];
1352
1353    // The LLVM backend always uses the system linker. If the user specified
1354    // --linker internal, fall back to "cc" (the platform's default C compiler).
1355    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
1368/// Generate LLVM textual IR from analyzed functions.
1369///
1370/// Returns the LLVM IR in human-readable `.ll` format. Used by `--emit asm`
1371/// to produce inspectable IR in place of native assembly.
1372pub 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// ============================================================================
1379// Test Helper Functions
1380// ============================================================================
1381
1382/// Output from semantic analysis (compile_to_air).
1383///
1384/// This struct provides access to the typed IR (AIR) for each function,
1385/// useful for testing semantic analysis without generating machine code.
1386#[derive(Debug)]
1387pub struct AirOutput {
1388    /// The abstract syntax tree.
1389    pub ast: Ast,
1390    /// String interner used during compilation.
1391    pub interner: ThreadedRodeo,
1392    /// The untyped IR (RIR).
1393    pub rir: Rir,
1394    /// Analyzed functions with typed IR.
1395    pub functions: Vec<AnalyzedFunction>,
1396    /// Type intern pool containing all struct and enum definitions.
1397    pub type_pool: TypeInternPool,
1398    /// String literals.
1399    pub strings: Vec<String>,
1400    /// Byte-blob literals (`@embed_file`).
1401    pub bytes: Vec<Vec<u8>>,
1402    /// Warnings collected during analysis.
1403    pub warnings: Vec<CompileWarning>,
1404}
1405
1406/// Compile source code up to AIR (typed IR) without building CFG.
1407///
1408/// This is a test helper that runs: lexing → parsing → AST to RIR → semantic analysis.
1409/// It stops before CFG construction, making it fast for testing type checking
1410/// and semantic analysis.
1411///
1412/// # Example
1413///
1414/// ```ignore
1415/// use gruel_compiler::compile_to_air;
1416///
1417/// let result = compile_to_air("fn main() -> i32 { 1 + 2 * 3 }");
1418/// assert!(result.is_ok());
1419/// ```
1420pub fn compile_to_air(source: &str) -> MultiErrorResult<AirOutput> {
1421    // Lexing
1422    let lexer = Lexer::new(source);
1423    let (tokens, interner) = lexer.tokenize().map_err(CompileErrors::from)?;
1424
1425    // Parsing
1426    let parser = Parser::new(tokens, interner);
1427    let (ast, interner) = parser.parse()?;
1428
1429    // AST to RIR (untyped IR)
1430    let astgen = AstGen::new(&ast, &interner);
1431    let rir = astgen.generate();
1432
1433    // Semantic analysis (RIR to AIR)
1434    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
1449/// Compile source code up to CFG (control flow graph).
1450///
1451/// This is an alias for `compile_frontend` that provides a more intuitive name
1452/// for test code. It runs the full frontend pipeline:
1453/// lexing → parsing → AST to RIR → semantic analysis → CFG construction.
1454///
1455/// # Example
1456///
1457/// ```ignore
1458/// use gruel_compiler::compile_to_cfg;
1459///
1460/// let result = compile_to_cfg("fn main() -> i32 { if true { 1 } else { 2 } }");
1461/// assert!(result.is_ok());
1462/// let state = result.unwrap();
1463/// assert_eq!(state.functions.len(), 1);
1464/// ```
1465pub 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        // Should produce a valid executable (ELF on Linux, Mach-O on macOS)
1477        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    /// ADR-0077 Phase 4: `@target_arch()` reflects the *compile* target
1493    /// rather than the host. We compile a program that exits with a
1494    /// distinct value per arch, set the target to a non-host triple
1495    /// (X86_64 vs Aarch64), and verify sema folded the intrinsic to the
1496    /// requested arch's variant index. We assert via the LLVM IR (the
1497    /// `ret i32 N` for `main`) so the test doesn't need a working
1498    /// cross-target backend at object-emit time.
1499    #[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        // ADR-0078 Phase 3: `Arch` lives in the prelude, so this test (which
1522        // bypasses `CompilationUnit::parse`) has to prepend the prelude
1523        // explicitly.
1524        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        // After full optimization, main folds to a single `ret i32 2`
1545        // (Aarch64's variant index).
1546        assert!(
1547            ir.contains("ret i32 2"),
1548            "expected `ret i32 2` (Aarch64 = 2) in IR, got:\n{}",
1549            ir,
1550        );
1551    }
1552
1553    /// ADR-0077 Phase 3: the wired-in target reaches LLVM, so the emitted
1554    /// LLVM IR carries the requested triple. We assert against the IR
1555    /// rather than a real cross-compiled object so the test passes even on
1556    /// hosts whose LLVM build doesn't include a non-host backend (the IR
1557    /// step doesn't need a backend at -O0).
1558    #[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        // -O1 forces a TargetMachine to be created (the only path that
1574        // applies the triple to the module's metadata via set_triple).
1575        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        // Test that errors from multiple functions are collected together
1613        // Use examples that both result in type mismatch errors
1614        // Note: Functions must be called from main() to be analyzed (lazy analysis)
1615        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        // Should have at least 2 errors (one from foo, one from bar)
1627        assert!(
1628            errors.len() >= 2,
1629            "expected at least 2 errors, got {}",
1630            errors.len()
1631        );
1632
1633        // All errors should be type mismatches (returning bool where i32 expected)
1634        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        // Use examples that both result in type mismatch errors
1646        // Note: Functions must be called from main() to be analyzed (lazy analysis)
1647        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        // Display should show both errors
1658        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        // Single error should still be collected and returned properly
1674        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    // ========================================================================
1691    // Multi-file Symbol Merging Tests
1692    // ========================================================================
1693
1694    #[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        // Struct and enum with the same name should conflict
1776        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        // Multiple duplicates should report multiple errors
1804        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        // Structs with inline methods from different files should be allowed
1830        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    // ========================================================================
1844    // Cross-File Semantic Analysis Tests
1845    // ========================================================================
1846
1847    #[test]
1848    fn test_cross_file_function_call() {
1849        // Function in main.gruel calls function in utils.gruel
1850        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        // Function in main.gruel calls function in utils.gruel with arguments
1869        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        // Struct defined in types.gruel, used in main.gruel
1892        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        // Struct defined in types.gruel, function in utils.gruel takes it as param
1915        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        // Enum defined in types.gruel, used in main.gruel
1943        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        // No main function in any file
1969        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        // main() defined in multiple files
1988        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        // main.gruel calls function that doesn't exist
2007        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        // main.gruel -> utils.gruel -> math.gruel chain of calls
2030        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        // Two files calling each other (mutual recursion possible)
2058        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    // ========================================================================
2080    // Module Import Tests
2081    // ========================================================================
2082
2083    #[test]
2084    fn test_module_member_access() {
2085        // Test that @import returns a module type and member access works
2086        // Note: In Phase 1, all files are merged into the same namespace,
2087        // so math.add() looks up "add" in the global function table.
2088        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        // Test accessing multiple functions from an imported module
2114        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        // Test that accessing an undefined function in a module produces an error
2143        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// ============================================================================
2173// Integration Unit Tests
2174// ============================================================================
2175//
2176// These tests verify the compilation pipeline without execution. They test:
2177// - Type checking and semantic analysis
2178// - CFG construction
2179// - Error message quality
2180//
2181// Benefits:
2182// - Fast: No file I/O, no process spawning, no execution
2183// - Comprehensive: Tests full parse→sema→codegen pipeline
2184// - Debuggable: Can inspect intermediate IRs in tests
2185
2186#[cfg(test)]
2187mod integration_tests {
2188    use super::*;
2189
2190    // ========================================================================
2191    // Integer Types
2192    // ========================================================================
2193
2194    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            // Type inferred from return type
2224            assert!(compile_to_air("fn main() -> i64 { 100 }").is_ok());
2225            // Type inferred from annotation
2226            assert!(compile_to_air("fn main() -> i32 { let x: i64 = 100; 0 }").is_ok());
2227        }
2228    }
2229
2230    // ========================================================================
2231    // Boolean Type
2232    // ========================================================================
2233
2234    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    // ========================================================================
2256    // Unit Type
2257    // ========================================================================
2258
2259    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    // ========================================================================
2279    // Arithmetic Operations
2280    // ========================================================================
2281
2282    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            // Multiplication before addition
2318            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    // ========================================================================
2342    // Comparison Operations
2343    // ========================================================================
2344
2345    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()); // Type mismatch: bool vs i32
2372        }
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    // ========================================================================
2382    // Logical Operations
2383    // ========================================================================
2384
2385    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    // ========================================================================
2416    // Bitwise Operations
2417    // ========================================================================
2418
2419    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    // ========================================================================
2460    // Control Flow - If Expressions
2461    // ========================================================================
2462
2463    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    // ========================================================================
2496    // Control Flow - Match Expressions
2497    // ========================================================================
2498
2499    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            // Missing case should error
2533            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    // ========================================================================
2562    // Control Flow - Loops
2563    // ========================================================================
2564
2565    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    // ========================================================================
2632    // Let Bindings
2633    // ========================================================================
2634
2635    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    // ========================================================================
2680    // Functions
2681    // ========================================================================
2682
2683    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    // ========================================================================
2763    // Structs
2764    // ========================================================================
2765
2766    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            // ADR-0081: BUILTIN_TYPES is empty; the only struct is the
2777            // user-defined Point.
2778            let all_struct_ids = result.type_pool.all_struct_ids();
2779            assert_eq!(all_struct_ids.len(), 1);
2780            // Verify Point is present
2781            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            // After moving a struct, it should not be usable. ADR-0083:
2854            // a struct of all-Copy fields would now infer Copy, so we
2855            // attach a `fn __drop(self)` to keep `Point` non-Copy
2856            // (Drop ⊥ Copy).
2857            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    // ========================================================================
2875    // Enums
2876    // ========================================================================
2877
2878    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            // Enum equality comparison is done via match, not ==
2921            // (== is not yet implemented for enums)
2922            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    // ========================================================================
2951    // Arrays
2952    // ========================================================================
2953
2954    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    // ========================================================================
2997    // Strings
2998    // ========================================================================
2999
3000    // ADR-0081: String moved out of `BUILTIN_TYPES` into the prelude
3001    // (`prelude/string.gruel`). `compile_to_air` deliberately skips
3002    // prelude loading, so it can no longer lower string-literal source
3003    // here. End-to-end string-literal coverage lives in
3004    // `crates/gruel-spec/cases/types/strings.toml`.
3005
3006    // ========================================================================
3007    // Block Expressions
3008    // ========================================================================
3009
3010    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            // Variable should not be accessible outside block
3034            let result = compile_to_air("fn main() -> i32 { { let x = 1; } x }");
3035            assert!(result.is_err());
3036        }
3037    }
3038
3039    // ========================================================================
3040    // Never Type
3041    // ========================================================================
3042
3043    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    // ========================================================================
3073    // Type Intrinsics
3074    // ========================================================================
3075
3076    mod intrinsics {
3077        use super::*;
3078
3079        #[test]
3080        fn size_of_intrinsic() {
3081            // @size_of returns usize
3082            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            // @align_of returns usize
3089            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    // ========================================================================
3095    // CFG Construction
3096    // ========================================================================
3097
3098    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            // CFG should have multiple blocks for branching
3117            let main_cfg = &state.functions[0].cfg;
3118            assert!(main_cfg.blocks().len() >= 3); // entry, then, else, merge
3119        }
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); // header, body, exit
3133        }
3134    }
3135
3136    // ========================================================================
3137    // Error Messages
3138    // ========================================================================
3139
3140    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    // ========================================================================
3174    // Warnings
3175    // ========================================================================
3176
3177    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    // ========================================================================
3201    // Edge Cases
3202    // ========================================================================
3203
3204    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    // ========================================================================
3247    // Multi-file Parsing
3248    // ========================================================================
3249
3250    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            // Check that both files were parsed
3280            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            // Create 10 files to exercise parallel parsing
3288            let sources: Vec<_> = (1..=10)
3289                .map(|i| {
3290                    SourceFile::new(
3291                        // Leak the string to get a &'static str
3292                        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            // All functions should be in their respective ASTs
3303            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( { }", // Missing closing paren
3313                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( { }", // Parse error
3330                    FileId::new(2),
3331                ),
3332            ];
3333
3334            let result = parse_all_files(&sources);
3335            assert!(result.is_err());
3336
3337            // The error should still report, and we should get at least one error
3338            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 { $ }", // '$' is not a valid token
3347                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            // The merged interner should contain both "foo" and "bar"
3364            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    // ------------------------------------------------------------------
3396    // ADR-0051 Phase 3: end-to-end sema + CFG with recursive AirPattern.
3397    // Exercises the recursive lowering path on the existing flat RIR
3398    // shape; phase 4 will unlock true nesting (tuple / struct match
3399    // roots, nested variant fields).
3400    // ------------------------------------------------------------------
3401    mod recursive_cfg_lowering {
3402        use super::*;
3403        use gruel_cfg::CfgBuilder;
3404        use gruel_rir::AstGen;
3405
3406        /// Run the whole pipeline (sema + CFG per function) with the
3407        /// ADR-0051 recursive lowering flag enabled, panicking on any
3408        /// compile error or CFG construction failure. Returns the number
3409        /// of functions successfully built.
3410        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                // Skip comptime-only functions.
3424                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            // ADR-0051 Phase 4b: top-level tuple pattern with literal
3531            // leaves. Previously this shape would have gone through the
3532            // astgen tuple-match elaborator; with the flag on it lowers
3533            // via recursive AirPattern::Tuple + CFG cascading dispatch.
3534            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        /// ADR-0051 Phase 4c: binding leaves inside tuple arms emit
3564        /// StorageLive + FieldGet + Alloc at sema time, with the
3565        /// binding registered in the arm scope so the body resolves.
3566        #[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}