| 1 | import os |
| 2 | |
| 3 | const vexe = @VEXE |
| 4 | const tests_dir = os.dir(@FILE) |
| 5 | const v3_dir = os.dir(tests_dir) |
| 6 | const vlib_dir = os.dir(v3_dir) |
| 7 | const v3_src = os.join_path(v3_dir, 'v3.v') |
| 8 | |
| 9 | // build_v3 builds v3 data for v3 tests. |
| 10 | fn build_v3() string { |
| 11 | v3_bin := os.join_path(os.temp_dir(), 'v3_generics_test') |
| 12 | build := os.execute('${vexe} -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}') |
| 13 | assert build.exit_code == 0, build.output |
| 14 | return v3_bin |
| 15 | } |
| 16 | |
| 17 | // run_selfhost_bad supports run selfhost bad handling for v3 tests. |
| 18 | fn run_selfhost_bad(v3_bin string, name string, src string, expected string) { |
| 19 | bad_src := os.join_path(os.temp_dir(), 'v3_gen_${name}.v') |
| 20 | os.write_file(bad_src, src) or { panic(err) } |
| 21 | bad_bin := os.join_path(os.temp_dir(), 'v3_gen_${name}') |
| 22 | result := os.execute('${v3_bin} ${bad_src} -selfhost -b c -o ${bad_bin}') |
| 23 | assert result.exit_code != 0, 'expected error for ${name}, but compilation succeeded' |
| 24 | assert result.output.contains(expected), 'expected "${expected}" in output for ${name}, got: ${result.output}' |
| 25 | assert !result.output.contains('C compilation failed') |
| 26 | } |
| 27 | |
| 28 | fn write_project_file(root string, rel string, src string) { |
| 29 | path := os.join_path(root, rel) |
| 30 | os.mkdir_all(os.dir(path)) or { panic(err) } |
| 31 | os.write_file(path, src) or { panic(err) } |
| 32 | } |
| 33 | |
| 34 | fn run_selfhost_project_bad(v3_bin string, name string, files map[string]string, input string, expected string) { |
| 35 | root := os.join_path(os.temp_dir(), 'v3_gen_${name}_project') |
| 36 | os.rmdir_all(root) or {} |
| 37 | os.mkdir_all(root) or { panic(err) } |
| 38 | for rel, src in files { |
| 39 | write_project_file(root, rel, src) |
| 40 | } |
| 41 | input_path := os.join_path(root, input) |
| 42 | bad_bin := os.join_path(os.temp_dir(), 'v3_gen_${name}') |
| 43 | result := os.execute('${v3_bin} ${input_path} -selfhost -b c -o ${bad_bin}') |
| 44 | assert result.exit_code != 0, 'expected error for ${name}, but compilation succeeded' |
| 45 | assert result.output.contains(expected), 'expected "${expected}" in output for ${name}, got: ${result.output}' |
| 46 | assert !result.output.contains('C compilation failed') |
| 47 | } |
| 48 | |
| 49 | // run_no_generic_error supports run no generic error handling for v3 tests. |
| 50 | fn run_no_generic_error(v3_bin string, name string, src string) { |
| 51 | src_file := os.join_path(os.temp_dir(), 'v3_gen_${name}.v') |
| 52 | os.write_file(src_file, src) or { panic(err) } |
| 53 | bin_file := os.join_path(os.temp_dir(), 'v3_gen_${name}') |
| 54 | result := os.execute('${v3_bin} ${src_file} -b c -o ${bin_file}') |
| 55 | assert !result.output.contains('unsupported generic'), '${name}: should not reject generics without -selfhost, got: ${result.output}' |
| 56 | } |
| 57 | |
| 58 | // run_generic_ok supports run generic ok handling for v3 tests. |
| 59 | fn run_generic_ok(v3_bin string, name string, src string, expected string) { |
| 60 | src_file := os.join_path(os.temp_dir(), 'v3_gen_${name}.v') |
| 61 | os.write_file(src_file, src) or { panic(err) } |
| 62 | bin_file := os.join_path(os.temp_dir(), 'v3_gen_${name}') |
| 63 | c_file := bin_file + '.c' |
| 64 | compile := os.execute('${v3_bin} ${src_file} -b c -o ${bin_file}') |
| 65 | // Check that v3 type checker and transform pass without errors |
| 66 | assert !compile.output.contains('unsupported generic'), '${name}: should not reject generics, got: ${compile.output}' |
| 67 | assert !compile.output.contains('type checker found'), '${name}: type checker errors: ${compile.output}' |
| 68 | // Verify C file was generated. The v3 pipeline can succeed even if cc fails due |
| 69 | // to pre-existing runtime issues. |
| 70 | assert os.exists(c_file), '${name}: C file not generated' |
| 71 | c_content := os.read_file(c_file) or { '' } |
| 72 | // The mangled generic function should appear in the generated C code |
| 73 | if expected.len > 0 { |
| 74 | assert c_content.len > 0, '${name}: empty C file' |
| 75 | } |
| 76 | } |
| 77 | |
| 78 | fn run_generic_exec(v3_bin string, name string, src string) string { |
| 79 | src_file := os.join_path(os.temp_dir(), 'v3_gen_${name}.v') |
| 80 | os.write_file(src_file, src) or { panic(err) } |
| 81 | bin_file := os.join_path(os.temp_dir(), 'v3_gen_${name}') |
| 82 | compile := os.execute('${v3_bin} ${src_file} -b c -o ${bin_file}') |
| 83 | assert compile.exit_code == 0, compile.output |
| 84 | run := os.execute(bin_file) |
| 85 | assert run.exit_code == 0, run.output |
| 86 | return run.output.trim_space() |
| 87 | } |
| 88 | |
| 89 | // test_generics_rejected_when_building_v validates this v3 regression case. |
| 90 | fn test_generics_rejected_when_building_v() { |
| 91 | v3_bin := build_v3() |
| 92 | // generic function |
| 93 | run_selfhost_bad(v3_bin, 'generic_fn', ' |
| 94 | fn id[T](x T) T { |
| 95 | return x |
| 96 | } |
| 97 | fn main() { |
| 98 | println(id[int](1)) |
| 99 | } |
| 100 | ', |
| 101 | 'unsupported generic') |
| 102 | |
| 103 | // generic struct |
| 104 | run_selfhost_bad(v3_bin, 'generic_struct', ' |
| 105 | struct Box[T] { |
| 106 | value T |
| 107 | } |
| 108 | fn main() { |
| 109 | b := Box[int]{value: 7} |
| 110 | println(b.value) |
| 111 | } |
| 112 | ', |
| 113 | 'unsupported generic') |
| 114 | |
| 115 | // generic struct with no generic fields |
| 116 | run_selfhost_bad(v3_bin, 'generic_struct_marker_only', ' |
| 117 | struct Phantom[T] { |
| 118 | value int |
| 119 | } |
| 120 | fn main() {} |
| 121 | ', |
| 122 | 'unsupported generic struct `Phantom`') |
| 123 | |
| 124 | run_selfhost_bad(v3_bin, 'generic_fn_marker_only', ' |
| 125 | fn unused[T]() {} |
| 126 | fn main() {} |
| 127 | ', |
| 128 | 'unsupported generic declaration `unused`') |
| 129 | |
| 130 | run_selfhost_bad(v3_bin, 'generic_c_fn_marker_only', ' |
| 131 | fn helper[T]() |
| 132 | fn main() {} |
| 133 | ', |
| 134 | 'unsupported generic declaration `helper`') |
| 135 | |
| 136 | run_selfhost_bad(v3_bin, 'generic_interface_marker_only', ' |
| 137 | interface I[T] {} |
| 138 | fn main() {} |
| 139 | ', |
| 140 | 'unsupported generic declaration `I`') |
| 141 | |
| 142 | run_selfhost_bad(v3_bin, 'generic_type_alias_marker_only', ' |
| 143 | type Alias[T] = int |
| 144 | fn main() {} |
| 145 | ', |
| 146 | 'unsupported generic declaration `Alias`') |
| 147 | |
| 148 | // generic method |
| 149 | run_selfhost_bad(v3_bin, 'generic_method', ' |
| 150 | struct Box[T] { |
| 151 | value T |
| 152 | } |
| 153 | fn (b Box[T]) get() T { |
| 154 | return b.value |
| 155 | } |
| 156 | fn main() { |
| 157 | b := Box[string]{value: "v3"} |
| 158 | println(b.get()) |
| 159 | } |
| 160 | ', |
| 161 | 'unsupported generic') |
| 162 | |
| 163 | // generic with option/result return |
| 164 | run_selfhost_bad(v3_bin, 'generic_option', ' |
| 165 | fn maybe[T](x T) ?T { |
| 166 | return x |
| 167 | } |
| 168 | fn main() { |
| 169 | x := maybe[int](1) or { 0 } |
| 170 | println(x) |
| 171 | } |
| 172 | ', |
| 173 | 'unsupported generic') |
| 174 | |
| 175 | // multiple type parameters |
| 176 | run_selfhost_bad(v3_bin, 'generic_multi_param', ' |
| 177 | fn pair[A, B](a A, b B) A { |
| 178 | return a |
| 179 | } |
| 180 | fn main() { |
| 181 | _ := pair[int, string](1, "ok") |
| 182 | } |
| 183 | ', |
| 184 | 'unsupported generic') |
| 185 | |
| 186 | // generic type application in params |
| 187 | run_selfhost_bad(v3_bin, 'generic_param_type', ' |
| 188 | fn takes_box(x Box[int]) {} |
| 189 | fn main() {} |
| 190 | ', |
| 191 | 'unsupported generic type application `Box[int]`') |
| 192 | |
| 193 | // generic struct field |
| 194 | run_selfhost_bad(v3_bin, 'generic_field_type', ' |
| 195 | struct Wrapper { |
| 196 | inner Box[string] |
| 197 | } |
| 198 | fn main() {} |
| 199 | ', |
| 200 | 'unsupported generic type application `Box[string]`') |
| 201 | |
| 202 | // generic return type |
| 203 | run_selfhost_bad(v3_bin, 'generic_return_type', ' |
| 204 | fn make_box() Box[int] { |
| 205 | return Box[int]{} |
| 206 | } |
| 207 | fn main() {} |
| 208 | ', |
| 209 | 'unsupported generic type application `Box[int]`') |
| 210 | |
| 211 | // generic sum type |
| 212 | run_selfhost_bad(v3_bin, 'generic_type_decl', ' |
| 213 | type Result[T] = T | string |
| 214 | fn main() {} |
| 215 | ', |
| 216 | 'unsupported generic') |
| 217 | |
| 218 | // generic interface |
| 219 | run_selfhost_bad(v3_bin, 'generic_interface', ' |
| 220 | interface Container[T] { |
| 221 | get() T |
| 222 | } |
| 223 | fn main() {} |
| 224 | ', |
| 225 | 'unsupported generic') |
| 226 | |
| 227 | run_selfhost_project_bad(v3_bin, 'imported_generic_struct', { |
| 228 | 'main.v': 'module main |
| 229 | |
| 230 | import badmod |
| 231 | |
| 232 | fn main() {} |
| 233 | ' |
| 234 | 'badmod/badmod.v': 'module badmod |
| 235 | |
| 236 | struct Phantom[T] { |
| 237 | value int |
| 238 | } |
| 239 | ' |
| 240 | }, 'main.v', 'unsupported generic struct `Phantom`') |
| 241 | } |
| 242 | |
| 243 | // test_generics_allowed_without_building_v validates this v3 regression case. |
| 244 | fn test_generics_allowed_without_building_v() { |
| 245 | v3_bin := build_v3() |
| 246 | // generic function — no "unsupported generic" error |
| 247 | run_no_generic_error(v3_bin, 'allow_generic_fn', ' |
| 248 | fn id[T](x T) T { |
| 249 | return x |
| 250 | } |
| 251 | fn main() { |
| 252 | _ := id[int](1) |
| 253 | } |
| 254 | ') |
| 255 | |
| 256 | // generic struct |
| 257 | run_no_generic_error(v3_bin, 'allow_generic_struct', ' |
| 258 | struct Box[T] { |
| 259 | value T |
| 260 | } |
| 261 | fn main() { |
| 262 | b := Box[int]{value: 7} |
| 263 | _ := b |
| 264 | } |
| 265 | ') |
| 266 | |
| 267 | // generic method |
| 268 | run_no_generic_error(v3_bin, 'allow_generic_method', ' |
| 269 | struct Box[T] { |
| 270 | value T |
| 271 | } |
| 272 | fn (b Box[T]) get() T { |
| 273 | return b.value |
| 274 | } |
| 275 | fn main() { |
| 276 | b := Box[string]{value: "v3"} |
| 277 | _ := b |
| 278 | } |
| 279 | ') |
| 280 | |
| 281 | // nested generic type |
| 282 | run_no_generic_error(v3_bin, 'allow_generic_nested', ' |
| 283 | struct Box[T] { |
| 284 | value T |
| 285 | } |
| 286 | fn main() { |
| 287 | b := Box[[]int]{value: [1, 2, 3]} |
| 288 | _ := b |
| 289 | } |
| 290 | ') |
| 291 | |
| 292 | // option/result generic |
| 293 | run_no_generic_error(v3_bin, 'allow_generic_option', ' |
| 294 | fn maybe[T](x T) ?T { |
| 295 | return x |
| 296 | } |
| 297 | fn main() { |
| 298 | x := maybe[int](1) or { 0 } |
| 299 | _ := x |
| 300 | } |
| 301 | ') |
| 302 | |
| 303 | // generic type in params/fields/returns |
| 304 | run_no_generic_error(v3_bin, 'allow_generic_type_app', ' |
| 305 | fn takes_box(x Box[int]) {} |
| 306 | fn main() {} |
| 307 | ') |
| 308 | |
| 309 | // generic interface |
| 310 | run_no_generic_error(v3_bin, 'allow_generic_interface', ' |
| 311 | interface Container[T] { |
| 312 | get() T |
| 313 | } |
| 314 | fn main() {} |
| 315 | ') |
| 316 | } |
| 317 | |
| 318 | // test_generics_compile_and_run validates generics compile and run behavior in v3 tests. |
| 319 | fn test_generics_compile_and_run() { |
| 320 | v3_bin := build_v3() |
| 321 | |
| 322 | // identity function with int and string |
| 323 | run_generic_ok(v3_bin, 'run_id_fn', ' |
| 324 | fn id[T](x T) T { |
| 325 | return x |
| 326 | } |
| 327 | fn main() { |
| 328 | println(id(123)) |
| 329 | println(id("ok")) |
| 330 | } |
| 331 | ', |
| 332 | '123\nok') |
| 333 | |
| 334 | // generic struct with field access |
| 335 | run_generic_ok(v3_bin, 'run_generic_struct', ' |
| 336 | struct Box[T] { |
| 337 | value T |
| 338 | } |
| 339 | fn main() { |
| 340 | b := Box[string]{value: "v3"} |
| 341 | println(b.value) |
| 342 | } |
| 343 | ', |
| 344 | 'v3') |
| 345 | |
| 346 | // A one-letter concrete type argument should not be treated as an unresolved |
| 347 | // generic placeholder when discovering struct specializations. |
| 348 | one_letter_src := os.join_path(os.temp_dir(), 'v3_gen_one_letter_concrete_arg.v') |
| 349 | os.write_file(one_letter_src, ' |
| 350 | struct A { |
| 351 | value int |
| 352 | } |
| 353 | |
| 354 | struct Box[T] { |
| 355 | value T |
| 356 | } |
| 357 | |
| 358 | fn main() { |
| 359 | b := Box[A]{ |
| 360 | value: A{ |
| 361 | value: 3 |
| 362 | } |
| 363 | } |
| 364 | _ := b |
| 365 | } |
| 366 | ') or { |
| 367 | panic(err) |
| 368 | } |
| 369 | one_letter_bin := os.join_path(os.temp_dir(), 'v3_gen_one_letter_concrete_arg') |
| 370 | one_letter_compile := os.execute('${v3_bin} ${one_letter_src} -b c -o ${one_letter_bin}') |
| 371 | assert !one_letter_compile.output.contains('unsupported generic'), one_letter_compile.output |
| 372 | assert !one_letter_compile.output.contains('type checker found'), one_letter_compile.output |
| 373 | one_letter_c := os.read_file(one_letter_bin + '.c') or { '' } |
| 374 | assert one_letter_c.contains('struct Box_A'), one_letter_c |
| 375 | |
| 376 | // transitive generic calls: outer[T] calls id[T] |
| 377 | run_generic_ok(v3_bin, 'run_transitive', ' |
| 378 | fn id[T](x T) T { |
| 379 | return x |
| 380 | } |
| 381 | fn outer[T](x T) T { |
| 382 | return id(x) |
| 383 | } |
| 384 | fn main() { |
| 385 | println(outer(42)) |
| 386 | } |
| 387 | ', |
| 388 | '42') |
| 389 | |
| 390 | // multi-param generic |
| 391 | run_generic_ok(v3_bin, 'run_multi_param', ' |
| 392 | fn first[A, B](a A, b B) A { |
| 393 | return a |
| 394 | } |
| 395 | fn main() { |
| 396 | println(first(99, "ignored")) |
| 397 | } |
| 398 | ', |
| 399 | '99') |
| 400 | |
| 401 | // multi-param generic struct fields |
| 402 | run_generic_ok(v3_bin, 'run_multi_param_struct_fields', ' |
| 403 | struct Pair[X, Y] { |
| 404 | left X |
| 405 | right Y |
| 406 | } |
| 407 | fn main() { |
| 408 | p := Pair[int, string]{left: 1, right: "ok"} |
| 409 | println(p.right) |
| 410 | } |
| 411 | ', |
| 412 | 'ok') |
| 413 | |
| 414 | // infer from array element type |
| 415 | run_generic_ok(v3_bin, 'run_array_infer', ' |
| 416 | fn length[T](xs []T) int { |
| 417 | return xs.len |
| 418 | } |
| 419 | fn main() { |
| 420 | println(length([1, 2, 3])) |
| 421 | } |
| 422 | ', |
| 423 | '3') |
| 424 | |
| 425 | selector_convert_out := run_generic_exec(v3_bin, 'selector_method_arg_infer', ' |
| 426 | struct Box[T] { |
| 427 | value T |
| 428 | } |
| 429 | |
| 430 | fn (b Box[T]) convert[U](value U) U { |
| 431 | return value |
| 432 | } |
| 433 | |
| 434 | fn main() { |
| 435 | b := Box[int]{value: 1} |
| 436 | println(b.convert("ok")) |
| 437 | } |
| 438 | ') |
| 439 | assert selector_convert_out == 'ok' |
| 440 | } |
| 441 | |