v4 / vlib / v3 / tests / generics_test.v
440 lines · 393 sloc · 10.52 KB · 288feee4702b35beadb4037d02b96b5c8674156b
Raw
1import os
2
3const vexe = @VEXE
4const tests_dir = os.dir(@FILE)
5const v3_dir = os.dir(tests_dir)
6const vlib_dir = os.dir(v3_dir)
7const v3_src = os.join_path(v3_dir, 'v3.v')
8
9// build_v3 builds v3 data for v3 tests.
10fn 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.
18fn 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
28fn 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
34fn 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.
50fn 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.
59fn 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
78fn 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.
90fn test_generics_rejected_when_building_v() {
91 v3_bin := build_v3()
92 // generic function
93 run_selfhost_bad(v3_bin, 'generic_fn', '
94fn id[T](x T) T {
95 return x
96}
97fn main() {
98 println(id[int](1))
99}
100',
101 'unsupported generic')
102
103 // generic struct
104 run_selfhost_bad(v3_bin, 'generic_struct', '
105struct Box[T] {
106 value T
107}
108fn 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', '
117struct Phantom[T] {
118 value int
119}
120fn main() {}
121',
122 'unsupported generic struct `Phantom`')
123
124 run_selfhost_bad(v3_bin, 'generic_fn_marker_only', '
125fn unused[T]() {}
126fn main() {}
127 ',
128 'unsupported generic declaration `unused`')
129
130 run_selfhost_bad(v3_bin, 'generic_c_fn_marker_only', '
131fn helper[T]()
132fn main() {}
133 ',
134 'unsupported generic declaration `helper`')
135
136 run_selfhost_bad(v3_bin, 'generic_interface_marker_only', '
137interface I[T] {}
138fn main() {}
139 ',
140 'unsupported generic declaration `I`')
141
142 run_selfhost_bad(v3_bin, 'generic_type_alias_marker_only', '
143type Alias[T] = int
144fn main() {}
145',
146 'unsupported generic declaration `Alias`')
147
148 // generic method
149 run_selfhost_bad(v3_bin, 'generic_method', '
150struct Box[T] {
151 value T
152}
153fn (b Box[T]) get() T {
154 return b.value
155}
156fn 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', '
165fn maybe[T](x T) ?T {
166 return x
167}
168fn 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', '
177fn pair[A, B](a A, b B) A {
178 return a
179}
180fn 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', '
188fn takes_box(x Box[int]) {}
189fn main() {}
190',
191 'unsupported generic type application `Box[int]`')
192
193 // generic struct field
194 run_selfhost_bad(v3_bin, 'generic_field_type', '
195struct Wrapper {
196 inner Box[string]
197}
198fn main() {}
199',
200 'unsupported generic type application `Box[string]`')
201
202 // generic return type
203 run_selfhost_bad(v3_bin, 'generic_return_type', '
204fn make_box() Box[int] {
205 return Box[int]{}
206}
207fn main() {}
208',
209 'unsupported generic type application `Box[int]`')
210
211 // generic sum type
212 run_selfhost_bad(v3_bin, 'generic_type_decl', '
213type Result[T] = T | string
214fn main() {}
215',
216 'unsupported generic')
217
218 // generic interface
219 run_selfhost_bad(v3_bin, 'generic_interface', '
220interface Container[T] {
221 get() T
222}
223fn main() {}
224',
225 'unsupported generic')
226
227 run_selfhost_project_bad(v3_bin, 'imported_generic_struct', {
228 'main.v': 'module main
229
230import badmod
231
232fn main() {}
233'
234 'badmod/badmod.v': 'module badmod
235
236struct 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.
244fn 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', '
248fn id[T](x T) T {
249 return x
250}
251fn main() {
252 _ := id[int](1)
253}
254')
255
256 // generic struct
257 run_no_generic_error(v3_bin, 'allow_generic_struct', '
258struct Box[T] {
259 value T
260}
261fn 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', '
269struct Box[T] {
270 value T
271}
272fn (b Box[T]) get() T {
273 return b.value
274}
275fn 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', '
283struct Box[T] {
284 value T
285}
286fn 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', '
294fn maybe[T](x T) ?T {
295 return x
296}
297fn 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', '
305fn takes_box(x Box[int]) {}
306fn main() {}
307')
308
309 // generic interface
310 run_no_generic_error(v3_bin, 'allow_generic_interface', '
311interface Container[T] {
312 get() T
313}
314fn main() {}
315')
316}
317
318// test_generics_compile_and_run validates generics compile and run behavior in v3 tests.
319fn 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', '
324fn id[T](x T) T {
325 return x
326}
327fn 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', '
336struct Box[T] {
337 value T
338}
339fn 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, '
350struct A {
351 value int
352}
353
354struct Box[T] {
355 value T
356}
357
358fn 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', '
378fn id[T](x T) T {
379 return x
380}
381fn outer[T](x T) T {
382 return id(x)
383}
384fn main() {
385 println(outer(42))
386}
387',
388 '42')
389
390 // multi-param generic
391 run_generic_ok(v3_bin, 'run_multi_param', '
392fn first[A, B](a A, b B) A {
393 return a
394}
395fn 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', '
403struct Pair[X, Y] {
404 left X
405 right Y
406}
407fn 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', '
416fn length[T](xs []T) int {
417 return xs.len
418}
419fn 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', '
426struct Box[T] {
427 value T
428}
429
430fn (b Box[T]) convert[U](value U) U {
431 return value
432}
433
434fn main() {
435 b := Box[int]{value: 1}
436 println(b.convert("ok"))
437}
438')
439 assert selector_convert_out == 'ok'
440}
441