#![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",
))]
use std::process::Command;
fn compile_wj_to_rs(source: &str) -> (bool, String, String) {
let dir = tempfile::tempdir().expect("create temp dir");
let input = dir.path().join("test.wj");
std::fs::write(&input, source).unwrap();
let output = Command::new(env!("CARGO_BIN_EXE_wj"))
.arg("build")
.arg("--target=rust")
.arg("--no-cargo")
.arg(&input)
.arg("--output")
.arg(dir.path().join("out"))
.output()
.expect("wj build failed");
let success = output.status.success();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
let rs_path = dir.path().join("out").join("test.rs");
let generated = std::fs::read_to_string(&rs_path).unwrap_or_default();
std::mem::forget(dir);
(success, generated, stderr)
}
#[test]
fn test_user_defined_readonly_method_infers_ref_self() {
let source = r#"
struct Player {
name: string,
health: i32,
}
impl Player {
fn new(name: string, health: i32) -> Player {
Player { name, health }
}
fn status(self) -> i32 {
self.health
}
fn name(self) -> string {
self.name
}
fn is_alive(self) -> bool {
self.health > 0
}
}
fn main() {
let player = Player::new("hero".to_string(), 100)
println!("{}", player.status())
println!("{}", player.is_alive())
}
"#;
let (success, generated, stderr) = compile_wj_to_rs(source);
assert!(success, "Compilation failed: {}", stderr);
assert!(
generated.contains("fn status(&self)"),
"status() should use &self since it only reads self.health.\nGenerated:\n{}",
generated
);
assert!(
generated.contains("fn is_alive(&self)"),
"is_alive() should use &self since it only reads self.health.\nGenerated:\n{}",
generated
);
assert!(
generated.contains("fn name(&self)"),
"name() should use &self (codegen auto-clones non-Copy return).\nGenerated:\n{}",
generated
);
}
#[test]
fn test_user_defined_readonly_method_with_option_field() {
let source = r#"
struct TreeNode {
value: i32,
children: Option<Vec<TreeNode>>,
}
impl TreeNode {
fn new() -> TreeNode {
TreeNode { value: 0, children: None }
}
fn is_leaf(self) -> bool {
self.children.is_none()
}
fn value(self) -> i32 {
self.value
}
}
fn main() {
let node = TreeNode::new()
assert!(node.is_leaf())
}
"#;
let (success, generated, stderr) = compile_wj_to_rs(source);
assert!(success, "Compilation failed: {}", stderr);
assert!(
generated.contains("fn is_leaf(&self)"),
"is_leaf() should use &self, not &mut self.\nGenerated:\n{}",
generated
);
}
#[test]
fn test_mutating_method_still_gets_mut_self() {
let source = r#"
struct TreeNode {
value: i32,
children: Option<Vec<TreeNode>>,
}
impl TreeNode {
fn new() -> TreeNode {
TreeNode { value: 0, children: None }
}
fn subdivide(self) {
let mut kids = Vec::new()
kids.push(TreeNode::new())
self.children = Some(kids)
}
}
fn main() {
let mut node = TreeNode::new()
node.subdivide()
}
"#;
let (success, generated, stderr) = compile_wj_to_rs(source);
assert!(success, "Compilation failed: {}", stderr);
assert!(
generated.contains("fn subdivide(&mut self)"),
"subdivide() should use &mut self since it assigns to self.children.\nGenerated:\n{}",
generated
);
}
#[test]
fn test_method_calling_custom_readonly_method_on_field() {
let source = r#"
struct Config {
max_size: i32,
}
impl Config {
fn new() -> Config {
Config { max_size: 100 }
}
fn is_valid(self) -> bool {
self.max_size > 0
}
}
struct App {
config: Config,
name: string,
}
impl App {
fn new() -> App {
App { config: Config::new(), name: "test".to_string() }
}
fn validate(self) -> bool {
self.config.is_valid()
}
}
fn main() {
let app = App::new()
println!("{}", app.validate())
}
"#;
let (success, generated, stderr) = compile_wj_to_rs(source);
assert!(success, "Compilation failed: {}", stderr);
assert!(
generated.contains("fn validate(&self)"),
"validate() should use &self since is_valid() is readonly.\nGenerated:\n{}",
generated
);
}