1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#![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 Tests: str vs String codegen consistency
//!
//! Bug: Windjammer compiler generates `str` (invalid - unsized) where Rust expects
//! `&str` or `String`, causing E0308 type mismatches.
//!
//! Root cause: Type::Custom("str") maps to "str" but Rust's `str` is unsized -
//! valid types are &str (borrowed) or String (owned).
//!
//! ## Audit: str/String Mapping vs Rust Semantics (verified 2026-03-14)
//!
//! | Context | Windjammer | Rust Output | Rust Rule |
//! |-------------------|------------|---------------|------------------------------------|
//! | Function param | str | &str | Borrowed, str unsized |
//! | Struct field | str | String | type_to_rust_for_field |
//! | String literal | "hello" | &str (inferred)| Literals are &'static str |
//! | Option<str> | Option<str>| Option<String>| No lifetimes in containers |
//! | Vec<str> | Vec<str> | Vec<String> | Owned in containers |
//! | Result<str, E> | Result<str,E> | Result<String,E> | Owned in containers |
//! | HashMap<str,str> | HashMap<K,V>| HashMap<String,String> | Owned, no lifetimes |
//! | Box<str> | Box<str> | Box<str> | Box holds unsized (don't use &str) |
//! | &str (reference) | &str | &str | Don't double-ref to &&str |
//! | Return type str | -> str | -> &str | type_to_rust; fn->String for owned |
//!
//! **Critical: fn -> str**: type_to_rust yields `-> &str`. Valid when returning
//! literals or borrowed input. For owned/new strings, use `-> String` (String).
use windjammer::codegen::rust::types::type_to_rust;
use windjammer::parser::Type;
// =============================================================================
// Unit tests for type_to_rust - str/String mapping
// =============================================================================
/// str parameter type: Windjammer `str` must become Rust `&str` (str is unsized)
#[test]
fn test_string_parameter_type_str() {
let ty = Type::Custom("str".to_string());
let rust = type_to_rust(&ty);
assert_eq!(
rust, "&str",
"Type::Custom(\"str\") should emit &str, got {}",
rust
);
}
/// string parameter type: Windjammer `string` must become Rust `String`
#[test]
fn test_string_parameter_type_string() {
let ty = Type::String;
let rust = type_to_rust(&ty);
assert_eq!(
rust, "String",
"Type::String should emit String, got {}",
rust
);
}
/// Type::Custom("string") -> String (for type aliases)
#[test]
fn test_custom_string_emits_string() {
let ty = Type::Custom("string".to_string());
let rust = type_to_rust(&ty);
assert_eq!(
rust, "String",
"Type::Custom(\"string\") should emit String, got {}",
rust
);
}
/// Struct field str: when used in struct, str is unsized - need String
/// type_to_rust doesn't have context; we map str -> &str for params.
/// For struct fields, the fix is in types.rs: str -> String when in field context.
/// For now, str -> &str fixes the param case. Struct fields with str need
/// String - we handle that in the Option<str> case: Option<str> -> Option<String>
#[test]
fn test_string_field_type_str_becomes_string() {
// Option<str> - str is unsized, Option<str> invalid. Must be Option<String>
let ty = Type::Option(Box::new(Type::Custom("str".to_string())));
let rust = type_to_rust(&ty);
assert_eq!(
rust, "Option<String>",
"Option<str> should emit Option<String>, got {}",
rust
);
}
/// String literal type: "hello" in Windjammer - the expression type is
/// inferred. type_to_rust(Type::String) = "String" (correct for owned)
#[test]
fn test_string_literal_returns_string() {
// Type::String -> String (for variables, return types)
assert_eq!(type_to_rust(&Type::String), "String");
}
/// &str reference: Type::Reference(Box::new(Type::Custom("str")))
#[test]
fn test_reference_to_str() {
let ty = Type::Reference(Box::new(Type::Custom("str".to_string())));
let rust = type_to_rust(&ty);
assert_eq!(rust, "&str", "&str should stay &str, got {}", rust);
}
// =============================================================================
// Edge case tests (audit checklist items)
// =============================================================================
/// HashMap<str, str> -> HashMap<String, String> (owned in containers, no lifetimes)
#[test]
fn test_hashmap_str_str_becomes_string_string() {
let ty = Type::Parameterized(
"HashMap".to_string(),
vec![
Type::Custom("str".to_string()),
Type::Custom("str".to_string()),
],
);
let rust = type_to_rust(&ty);
assert_eq!(
rust, "HashMap<String, String>",
"HashMap<str, str> should emit HashMap<String, String>, got {}",
rust
);
}
/// HashMap<String, str> -> HashMap<String, String> (value str becomes String)
#[test]
fn test_hashmap_string_str_becomes_string_string() {
let ty = Type::Parameterized(
"HashMap".to_string(),
vec![Type::String, Type::Custom("str".to_string())],
);
let rust = type_to_rust(&ty);
assert_eq!(
rust, "HashMap<String, String>",
"HashMap<String, str> value should become String, got {}",
rust
);
}
/// Box<str> -> Box<str> (Box can hold unsized str, don't convert to Box<&str>)
#[test]
fn test_box_str_stays_box_str() {
let ty = Type::Parameterized("Box".to_string(), vec![Type::Custom("str".to_string())]);
let rust = type_to_rust(&ty);
assert_eq!(
rust, "Box<str>",
"Box<str> should emit Box<str> (valid in Rust), got {}",
rust
);
}
/// Return type str: type_to_rust gives &str (params/return context - no field context)
/// Note: fn -> &str works for literals/borrowed; fn -> String for owned.
/// type_to_rust has no context, so str -> &str. Return type analysis is in function_generation.
#[test]
fn test_return_type_str_emits_ampersand_str() {
let ty = Type::Custom("str".to_string());
let rust = type_to_rust(&ty);
assert_eq!(
rust, "&str",
"Bare str in type position (e.g. return) -> &str, got {}",
rust
);
}