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(any(
    not(any(
        feature = "parser_tests",
        feature = "analyzer_tests",
        feature = "codegen_tests",
        feature = "interpreter_tests",
        feature = "conformance_tests",
        feature = "integration_tests",
    )),
    feature = "analyzer_tests",
))]

//! TDD: Function parameter float type propagation
//!
//! Bug: Function parameters with explicit f32/f64 type aren't propagating to float
//! literals used with them. Example: `fn update(self, dt: f32) { dt * 1000.0 }`
//! generates 1000.0 as f64 instead of f32.
//!
//! Root Cause: Function parameters are registered in var_types but the constraint
//! propagation may not be working correctly for impl methods.

use tempfile::TempDir;
use windjammer::compiler::build_project;
use windjammer::CompilationTarget;

#[path = "common/test_utils.rs"]
mod test_utils;

#[test]
fn test_f32_param_propagates_to_literals() {
    // Matches a settings module update(dt: f32) method pattern
    let source = r#"
pub struct PerformanceStats {
    pub frame_count: u64,
    pub total_time: f32,
    pub fps: f32,
    pub frame_time_ms: f32,
}

impl PerformanceStats {
    pub fn update(self, dt: f32) {
        self.frame_time_ms = dt * 1000.0
        self.fps = if dt > 0.0 { 1.0 / dt } else { 0.0 }
    }
}
"#;

    let rust = test_utils::compile_single(source);

    // Literals should be inferred as f32 from dt: f32
    assert!(
        rust.contains("1000.0_f32") || rust.contains("1000.0f32"),
        "1000.0 should be f32 when used with dt: f32, got:\n{}",
        rust
    );
    assert!(
        rust.contains("1.0_f32") || rust.contains("1.0f32"),
        "1.0 should be f32 when used with dt: f32, got:\n{}",
        rust
    );
    assert!(
        rust.contains("0.0_f32") || rust.contains("0.0f32"),
        "0.0 in else branch should be f32, got:\n{}",
        rust
    );
}

#[test]
fn test_f64_param_propagates_to_literals() {
    let source = r#"
pub fn compute(timestamp: f64) {
    let delta = timestamp - 1000.0
}
"#;

    let rust = test_utils::compile_single(source);
    assert!(
        rust.contains("1000.0_f64") || rust.contains("1000.0f64"),
        "1000.0 should be f64 when used with timestamp: f64, got:\n{}",
        rust
    );
}

/// Multi-file build: mimics a multi-file settings module structure
#[test]
fn test_f32_param_in_multi_file_build() {
    use std::fs;

    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let test_dir = temp_dir.path();
    let settings_dir = test_dir.join("settings");
    fs::create_dir_all(&settings_dir).expect("Failed to create settings dir");

    // Create mod.wj for a multi-file module
    let mod_content = r#"
pub mod performance
"#;
    fs::write(settings_dir.join("mod.wj"), mod_content).expect("Failed to write mod.wj");

    // Create performance.wj with an update(dt: f32) method
    let perf_content = r#"
pub struct PerformanceStats {
    pub frame_count: u64,
    pub total_time: f32,
    pub fps: f32,
    pub frame_time_ms: f32,
}

impl PerformanceStats {
    pub fn update(self, dt: f32) {
        self.frame_time_ms = dt * 1000.0
        self.fps = if dt > 0.0 { 1.0 / dt } else { 0.0 }
    }
}
"#;
    fs::write(settings_dir.join("performance.wj"), perf_content)
        .expect("Failed to write performance.wj");

    let output_dir = test_dir.join("build");
    fs::create_dir_all(&output_dir).expect("Failed to create output dir");

    // Build from directory (multi-file project build)
    let result = build_project(&settings_dir, &output_dir, CompilationTarget::Rust, true);

    let rust = match result {
        Ok(()) => {
            let perf_rs = output_dir.join("performance.rs");
            if perf_rs.exists() {
                fs::read_to_string(&perf_rs).unwrap_or_default()
            } else {
                // Try settings/performance.rs
                fs::read_to_string(output_dir.join("settings").join("performance.rs"))
                    .unwrap_or_default()
            }
        }
        Err(e) => panic!("Build failed: {}", e),
    };

    assert!(
        rust.contains("1000.0_f32") || rust.contains("1000.0f32"),
        "Multi-file: 1000.0 should be f32 when used with dt: f32, got:\n{}",
        rust
    );
    assert!(
        rust.contains("1.0_f32") || rust.contains("1.0f32"),
        "Multi-file: 1.0 should be f32, got:\n{}",
        rust
    );
    assert!(
        rust.contains("0.0_f32") || rust.contains("0.0f32"),
        "Multi-file: 0.0 in else branch should be f32, got:\n{}",
        rust
    );
}