Files
RustAst/docs/Analysis_AST_Swallowing_Bugs.md
2026-03-09 11:49:17 +01:00

4.7 KiB

Analysis: AST Traversal & Symbol Resolution "Swallow" Bugs

Overview

The Myc compiler utilizes several passes that traverse the Abstract Syntax Tree (AST) at different stages (Untyped, Bound/Typed). A recurring pattern in the codebase was the use of non-exhaustive match arms, specifically _ => Ok(node) or _ => {}.

While convenient, this pattern created "Swallow Traps": when new node types were added to the language (like Again for tail-calls or Pipe for streaming), these passes would silently ignore them. This led to incomplete symbol registration and binder errors.

Identified Issues

1. LambdaCollector (Fixed)

Location: src/ast/compiler/lambda_collector.rs

  • Problem: This pass collects all global function definitions (lambdas) for the Specializer (monomorphization). It used _ => {}, missing nodes with children like Again, Pipe, and Destructure.
  • Impact: Functions defined inside an again block or a pipe lambda were not registered. The Specializer could not find their AST, leading to "Undefined variable" or specialization failures.
  • Fix: Refactored visit to use exhaustive matching. Every BoundKind variant is now explicitly handled. Nodes with children are traversed recursively; leaf nodes are explicitly ignored.

2. Global Discovery Pass (Current Bottleneck)

Location: src/ast/environment.rs -> discover_globals

  • Problem: This pass runs on the Untyped AST before binding. Its job is to find all def and macro declarations to populate the global symbol table.
  • Critical Failure: It currently only matches UntypedKind::Def, UntypedKind::MacroDecl, and UntypedKind::Block. It misses declarations hidden inside Again, Pipe, If, and Lambda bodies.
  • Impact: Even if the LambdaCollector is fixed, the Binder won't know a symbol is global if discover_globals didn't find it first. This explains why tests for Again and Pipe still fail with "Undefined variable".

3. Destructuring Def Bug (Discovered)

Location: src/ast/environment.rs -> discover_globals

  • Observation: discover_globals only registers a global if the Def target is a single Identifier.
  • Bug: (def [a b] [1 2]) is currently ignored for global registration. These variables are treated as local or remain undefined in the global scope.

4. VM: Evaluation & Pattern Matching

Location: src/ast/vm.rs

  • eval_core (L309): The main execution loop. It covers most variants but lacks an exhaustive check. New BoundKind variants will not be caught at compile-time.
  • unpack (L994): Used for parameter destructuring.
    match &pattern.kind {
        UntypedKind::Identifier(sym) => { ... }
        UntypedKind::Tuple { elements } => { ... }
        _ => Err("Invalid node in parameter pattern".to_string()),
    }
    
    Any new UntypedKind (like Record destructuring) will trigger a runtime error instead of a compile-time exhaustiveness check.
  • after_eval (Observer): Uses _ => {}, meaning new control-flow nodes might lack proper debug/trace logging.

5. Optimizer: Engine & Inlining

Location: src/ast/compiler/optimizer/engine.rs, inliner.rs

  • flatten_tuple (L693 in engine.rs):
    match &node.kind {
        BoundKind::Tuple { elements } => { ... }
        BoundKind::Nop => {}
        _ => into.push(node),
    }
    
    Incorrectly assumes that any unknown node is atomic. If a new sequence-like node is added, it won't be flattened, potentially breaking assumptions in the Optimizer's call-site logic.
  • Inliner Analysis: Uses _ => false for inlinability checks. This prevents new node types from being inlined, leading to sub-optimal code without any warning to the developer.

Regression Testing

A suite of integration tests has been added to src/integration_test.rs to reveal these gaps:

  • test_lambda_collector_misses_again_arguments: (Currently FAILED)
  • test_lambda_collector_misses_pipe_lambda: (Currently FAILED)
  • test_lambda_collector_misses_destructure_value: (Now PASSED after LambdaCollector fix)
  • test_lambda_collector_misses_get_field_rec: (PASSED, but requires verification against aggressive optimization)

Next Steps

  1. Refactor discover_globals: Apply the same exhaustive matching strategy used in the LambdaCollector to src/ast/environment.rs.
  2. Implement Pattern Traversal: Update discover_globals to extract all identifiers from destructuring patterns (e.g., in Def [a b] ...).
  3. Audit Other Passes: Systematically check type_checker.rs, optimizer/engine.rs, and vm.rs for remaining _ => swallow traps on node kinds.