windjammer 0.48.0

A simple language inspired by Go, Ruby, and Elixir that transpiles to Rust - 80% of Rust's power with 20% of the complexity
Documentation
#![cfg(not(any(
    feature = "parser_tests",
    feature = "analyzer_tests",
    feature = "codegen_tests",
    feature = "interpreter_tests",
    feature = "conformance_tests",
    feature = "integration_tests",
)))]

/// TDD Test: Method Receiver Mutability Inference
///
/// Problem: When parameter calls mutating methods, compiler should infer &mut
///
/// Example:
/// ```windjammer
/// struct Loader {
///     pub data: Vec<String>
/// }
///
/// impl Loader {
///     pub fn add(&mut self, item: String) {
///         self.data.push(item)
///     }
/// }
///
/// fn process(loader: Loader) {  // Should infer: &mut Loader
///     loader.add("test")         // Calls mutating method
/// }
/// ```
use std::fs;
use std::process::Command;

#[test]
fn test_method_mut_borrow_inference() {
    let source = r#"
struct Loader {
    pub data: Vec<string>
}

impl Loader {
    pub fn new() -> Loader {
        Loader { data: Vec::new() }
    }
    
    pub fn add(self, item: string) {
        self.data.push(item)
    }
}

fn process(loader: Loader) {
    loader.add("test")
    loader.add("another")
}

fn main() {
    let mut ldr = Loader::new()
    process(ldr)
}
"#;

    // Use tempfile::TempDir for proper isolation (prevents test collisions)
    let test_dir_handle = tempfile::tempdir().unwrap();
    let test_dir = test_dir_handle.path();

    let wj_file = test_dir.join("test.wj");
    fs::write(&wj_file, source).unwrap();

    let out_dir = test_dir.join("out");

    let wj_binary = env!("CARGO_BIN_EXE_wj");
    let _output = Command::new(wj_binary)
        .arg("build")
        .arg("--no-cargo")
        .arg(&wj_file)
        .arg("--target")
        .arg("rust")
        .arg("--output")
        .arg(&out_dir)
        .output()
        .expect("Failed to run wj compiler");

    let rust_file = out_dir.join("test.rs");
    let generated = fs::read_to_string(&rust_file).expect("Failed to read generated Rust file");

    println!("Generated code:\n{}", generated);

    // Compile with rustc
    let rustc_output = Command::new("rustc")
        .arg(&rust_file)
        .arg("--crate-type")
        .arg("bin")
        .arg("--edition")
        .arg("2021")
        .arg("-o")
        .arg(test_dir.join("test_bin"))
        .output()
        .expect("Failed to run rustc");

    if !rustc_output.status.success() {
        let stderr = String::from_utf8_lossy(&rustc_output.stderr);
        panic!(
            "Compilation failed:\n{}\n\nGenerated code:\n{}",
            stderr, generated
        );
    }

    // THE WINDJAMMER WAY: Automatic ownership inference!
    // User writes `loader: Loader` (no & or &mut)
    // Compiler infers `&mut Loader` because loader.add() mutates
    // This is "Compiler does the hard work" - 80% of Rust's power, 20% of complexity
    //
    // PHILOSOPHY: Ownership inference ≠ mutability inference
    // - Ownership (`&`, `&mut`, owned): INFERRED automatically ✅
    // - Mutability (`let mut`): EXPLICIT always (safety guardrail) ✅
    assert!(
        generated.contains("fn process(loader: &mut Loader)"),
        "process() should infer &mut for mutated parameter (automatic ownership inference)"
    );

    // Verify call site also adds &mut automatically
    assert!(
        generated.contains("process(&mut ldr)"),
        "Call site should automatically add &mut"
    );

    // TempDir automatically cleans up on drop
}

#[test]
fn test_multiple_mut_method_calls() {
    // Test with struct that has multiple mutating methods
    let source = r#"
struct Config {
    pub values: Vec<string>
}

impl Config {
    pub fn new() -> Config {
        Config { values: Vec::new() }
    }
    
    pub fn set(self, key: string) {
        self.values.push(key)
    }
    
    pub fn clear(self) {
        self.values.clear()
    }
}

fn setup(config: Config) {
    config.set("width")
    config.set("height")
    config.clear()
}

fn main() {
    let mut cfg = Config::new()
    setup(cfg)
}
"#;

    // Use tempfile::TempDir for proper isolation (prevents test collisions)
    let test_dir_handle = tempfile::tempdir().unwrap();
    let test_dir = test_dir_handle.path();

    let wj_file = test_dir.join("test.wj");
    fs::write(&wj_file, source).unwrap();

    let out_dir = test_dir.join("out");

    let wj_binary = env!("CARGO_BIN_EXE_wj");
    let _output = Command::new(wj_binary)
        .arg("build")
        .arg("--no-cargo")
        .arg(&wj_file)
        .arg("--target")
        .arg("rust")
        .arg("--output")
        .arg(&out_dir)
        .output()
        .expect("Failed to run wj compiler");

    let rust_file = out_dir.join("test.rs");
    let generated = fs::read_to_string(&rust_file).expect("Failed to read generated Rust file");

    println!("Generated code:\n{}", generated);

    let rustc_output = Command::new("rustc")
        .arg(&rust_file)
        .arg("--crate-type")
        .arg("bin")
        .arg("--edition")
        .arg("2021")
        .arg("-o")
        .arg(test_dir.join("test_bin"))
        .output()
        .expect("Failed to run rustc");

    if !rustc_output.status.success() {
        let stderr = String::from_utf8_lossy(&rustc_output.stderr);
        panic!(
            "Compilation failed:\n{}\n\nGenerated code:\n{}",
            stderr, generated
        );
    }

    // THE WINDJAMMER WAY: Automatic ownership inference!
    // User writes `config: Config` (no & or &mut)
    // Compiler infers `&mut Config` because config.set/clear() mutate
    assert!(
        generated.contains("fn setup(config: &mut Config)"),
        "setup() should infer &mut for mutated parameter (automatic ownership inference)"
    );

    // Verify call site also adds &mut automatically
    assert!(
        generated.contains("setup(&mut cfg)"),
        "Call site should automatically add &mut"
    );

    // TempDir automatically cleans up on drop
}