vxx2 / vlib / v3 / tests / export_attr_codegen_test.v
535 lines · 448 sloc · 15.81 KB · 288feee4702b35beadb4037d02b96b5c8674156b
Raw
1import os
2
3const export_attr_vexe = @VEXE
4const export_attr_tests_dir = os.dir(@FILE)
5const export_attr_v3_dir = os.dir(export_attr_tests_dir)
6const export_attr_vlib_dir = os.dir(export_attr_v3_dir)
7const export_attr_v3_src = os.join_path(export_attr_v3_dir, 'v3.v')
8
9fn export_attr_build_v3() string {
10 v3_bin := os.join_path(os.temp_dir(), 'v3_export_attr_test_${os.getpid()}')
11 os.rm(v3_bin) or {}
12 build :=
13 os.execute('${export_attr_vexe} -gc none -path "${export_attr_vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${export_attr_v3_src}')
14 assert build.exit_code == 0, build.output
15 return v3_bin
16}
17
18fn export_attr_project(name string, files map[string]string) string {
19 root := os.join_path(os.temp_dir(), 'v3_export_attr_${name}_${os.getpid()}')
20 os.rmdir_all(root) or {}
21 mut rels := files.keys()
22 rels.sort()
23 for rel in rels {
24 path := os.join_path(root, rel)
25 os.mkdir_all(os.dir(path)) or { panic(err) }
26 os.write_file(path, files[rel]) or { panic(err) }
27 }
28 return root
29}
30
31fn export_attr_compile(v3_bin string, main_file string, output string) os.Result {
32 return os.execute('${v3_bin} ${main_file} -b c -o ${output}')
33}
34
35fn test_exported_imported_function_is_rooted_and_emitted_as_raw_symbol() {
36 v3_bin := export_attr_build_v3()
37 root := export_attr_project('imported_link', {
38 'main.v': 'module main
39
40import expmod
41
42fn C.raw_exported_answer() int
43
44fn take_callback(cb fn () int) int {
45 return cb()
46}
47
48fn main() {
49 println(C.raw_exported_answer().str())
50 println(expmod.exported_answer().str())
51 println(take_callback(expmod.exported_answer).str())
52}
53'
54 'expmod/mod.v': "module expmod
55
56@[export: 'raw_exported_answer']
57pub fn exported_answer() int {
58 return helper_used()
59}
60
61fn helper_used() int {
62 return 41
63}
64
65fn helper_unused() int {
66 return 99
67}
68"
69 })
70 bin_path := os.join_path(root, 'app')
71 compile := export_attr_compile(v3_bin, os.join_path(root, 'main.v'), bin_path)
72 assert compile.exit_code == 0, compile.output
73 run := os.execute(bin_path)
74 assert run.exit_code == 0, run.output
75 assert run.output.trim_space() == '41\n41\n41', run.output
76
77 c_code := os.read_file(bin_path + '.c') or { panic(err) }
78 assert c_code.contains('int expmod__exported_answer(void) {'), c_code
79 assert c_code.contains('int raw_exported_answer(void) {'), c_code
80 assert c_code.contains('return expmod__exported_answer();'), c_code
81 assert c_code.contains('raw_exported_answer()'), c_code
82 assert c_code.contains('take_callback(expmod__exported_answer)'), c_code
83 assert c_code.contains('expmod__helper_used('), c_code
84 assert !c_code.contains('expmod__helper_unused('), c_code
85}
86
87fn test_duplicate_export_name_is_rejected() {
88 v3_bin := export_attr_build_v3()
89 root := export_attr_project('duplicate', {
90 'main.v': "module main
91
92@[export: 'raw_duplicate']
93fn one() {}
94
95@[export: 'raw_duplicate']
96fn two() {}
97
98fn main() {}
99"
100 })
101 compile := export_attr_compile(v3_bin, os.join_path(root, 'main.v'), os.join_path(root, 'app'))
102 assert compile.exit_code != 0, compile.output
103 assert compile.output.contains('duplicate export name `raw_duplicate`'), compile.output
104}
105
106fn test_disabled_export_attr_does_not_register_raw_symbol() {
107 v3_bin := export_attr_build_v3()
108 root := export_attr_project('disabled_export', {
109 'main.v': "module main
110
111@[if missing_export_flag ?]
112@[export: 'raw_disabled_export']
113fn disabled_export() int {
114 return 2
115}
116
117@[export: 'raw_enabled_export']
118fn enabled_export() int {
119 return 7
120}
121
122fn main() {
123 println(enabled_export().str())
124}
125"
126 })
127 bin_path := os.join_path(root, 'app')
128 compile := export_attr_compile(v3_bin, os.join_path(root, 'main.v'), bin_path)
129 assert compile.exit_code == 0, compile.output
130 run := os.execute(bin_path)
131 assert run.exit_code == 0, run.output
132 assert run.output.trim_space() == '7', run.output
133
134 c_code := os.read_file(bin_path + '.c') or { panic(err) }
135 assert c_code.contains('int raw_enabled_export(void) {'), c_code
136 assert !c_code.contains('raw_disabled_export'), c_code
137}
138
139fn test_export_name_collision_with_runtime_symbol_is_rejected() {
140 v3_bin := export_attr_build_v3()
141 root := export_attr_project('runtime_collision', {
142 'main.v': "module main
143
144@[export: 'v_free']
145fn collides_with_runtime_free() {}
146
147fn main() {}
148"
149 })
150 compile := export_attr_compile(v3_bin, os.join_path(root, 'main.v'), os.join_path(root, 'app'))
151 assert compile.exit_code != 0, compile.output
152 assert compile.output.contains('export name `v_free`'), compile.output
153}
154
155fn test_export_name_collision_with_natural_symbol_is_rejected() {
156 v3_bin := export_attr_build_v3()
157 root := export_attr_project('natural_collision', {
158 'main.v': "module main
159
160@[export: 'natural_name']
161fn natural_name() int {
162 return 1
163}
164
165fn main() {}
166"
167 })
168 compile := export_attr_compile(v3_bin, os.join_path(root, 'main.v'), os.join_path(root, 'app'))
169 assert compile.exit_code != 0, compile.output
170 assert compile.output.contains('export name `natural_name`'), compile.output
171}
172
173fn test_export_name_collision_with_libc_remapped_natural_symbol_is_rejected() {
174 v3_bin := export_attr_build_v3()
175 root := export_attr_project('libc_remapped_natural_collision', {
176 'main.v': "module main
177
178fn rint() int {
179 return 1
180}
181
182@[export: 'v_rint']
183fn exported_rint_collision() int {
184 return 2
185}
186
187fn main() {}
188"
189 })
190 compile := export_attr_compile(v3_bin, os.join_path(root, 'main.v'), os.join_path(root, 'app'))
191 assert compile.exit_code != 0, compile.output
192 assert compile.output.contains('export name `v_rint`'), compile.output
193 assert compile.output.contains('collides with `rint`'), compile.output
194 assert !compile.output.contains('C compilation failed'), compile.output
195 assert !compile.output.contains('redefinition'), compile.output
196}
197
198fn test_export_main_collision_with_top_level_script_entry_is_rejected() {
199 v3_bin := export_attr_build_v3()
200 root := export_attr_project('top_level_script_export_main_collision', {
201 'main.v': "
202@[export: 'main']
203fn exported_entry() {}
204
205println('script')
206"
207 })
208 compile := export_attr_compile(v3_bin, os.join_path(root, 'main.v'), os.join_path(root, 'app'))
209 assert compile.exit_code != 0, compile.output
210 assert compile.output.contains('export name `main` for `exported_entry`'), compile.output
211 assert compile.output.contains('synthetic entry point `main`'), compile.output
212 assert !compile.output.contains('C compilation failed'), compile.output
213 assert !compile.output.contains('redefinition'), compile.output
214}
215
216fn test_export_main_collision_with_top_level_match_entry_is_rejected() {
217 v3_bin := export_attr_build_v3()
218 root := export_attr_project('top_level_match_export_main_collision', {
219 'main.v': "
220@[export: 'main']
221fn exported_entry() {}
222
223match 1 {
224 1 { println('one') }
225 else { println('other') }
226}
227"
228 })
229 compile := export_attr_compile(v3_bin, os.join_path(root, 'main.v'), os.join_path(root, 'app'))
230 assert compile.exit_code != 0, compile.output
231 assert compile.output.contains('export name `main` for `exported_entry`'), compile.output
232 assert compile.output.contains('synthetic entry point `main`'), compile.output
233 assert !compile.output.contains('C compilation failed'), compile.output
234 assert !compile.output.contains('redefinition'), compile.output
235}
236
237fn test_export_main_collision_with_test_harness_entry_is_rejected() {
238 v3_bin := export_attr_build_v3()
239 root := export_attr_project('test_harness_export_main_collision', {
240 'main_test.v': "
241@[export: 'main']
242fn exported_entry() {}
243
244fn test_ok() {}
245"
246 })
247 compile := export_attr_compile(v3_bin, os.join_path(root, 'main_test.v'), os.join_path(root,
248 'app'))
249 assert compile.exit_code != 0, compile.output
250 assert compile.output.contains('export name `main` for `exported_entry`'), compile.output
251 assert compile.output.contains('synthetic entry point `main`'), compile.output
252 assert !compile.output.contains('C compilation failed'), compile.output
253 assert !compile.output.contains('redefinition'), compile.output
254}
255
256fn test_export_main_in_non_main_test_module_does_not_report_synthetic_collision() {
257 v3_bin := export_attr_build_v3()
258 root := export_attr_project('test_harness_non_main_module_export_main', {
259 'foo_test.v': "module foo
260
261@[export: 'main']
262fn exported_entry() {}
263
264fn test_ok() {}
265"
266 })
267 compile := export_attr_compile(v3_bin, os.join_path(root, 'foo_test.v'), os.join_path(root,
268 'app'))
269 assert compile.exit_code != 0, compile.output
270 assert compile.output.contains('only module main test files are supported'), compile.output
271 assert !compile.output.contains('synthetic entry point `main`'), compile.output
272 assert !compile.output.contains('C compilation failed'), compile.output
273 assert !compile.output.contains('redefinition'), compile.output
274}
275
276fn test_invalid_export_names_are_rejected() {
277 v3_bin := export_attr_build_v3()
278 root := export_attr_project('invalid_names', {
279 'main.v': "module main
280
281@[export: '1bad']
282fn bad_digit() {}
283
284@[export: 'for']
285fn bad_keyword() {}
286
287fn main() {}
288"
289 })
290 compile := export_attr_compile(v3_bin, os.join_path(root, 'main.v'), os.join_path(root, 'app'))
291 assert compile.exit_code != 0, compile.output
292 assert compile.output.contains('invalid export name `1bad`'), compile.output
293 assert compile.output.contains('invalid export name `for`'), compile.output
294}
295
296fn test_invalid_imported_export_name_is_rejected_before_cgen() {
297 v3_bin := export_attr_build_v3()
298 root := export_attr_project('invalid_imported_name', {
299 'main.v': 'module main
300
301import badexp
302
303fn main() {
304 println(badexp.answer().str())
305}
306'
307 'badexp/badexp.v': "module badexp
308
309@[export: '1bad']
310pub fn answer() int {
311 return 1
312}
313"
314 })
315 compile := export_attr_compile(v3_bin, os.join_path(root, 'main.v'), os.join_path(root, 'app'))
316 assert compile.exit_code != 0
317 assert compile.output.contains('invalid export name `1bad` for `badexp.answer`'), compile.output
318}
319
320fn test_export_name_reserved_by_v3_c_preamble_is_rejected() {
321 v3_bin := export_attr_build_v3()
322 root := export_attr_project('preamble_reserved_name', {
323 'main.v': "module main
324
325@[export: 'bool']
326fn collides_with_preamble_bool() bool {
327 return true
328}
329
330@[export: 'string']
331fn collides_with_preamble_string() {}
332
333@[export: 'voidptr']
334fn collides_with_preamble_voidptr() {}
335
336@[export: 'i8']
337fn collides_with_preamble_i8() {}
338
339@[export: 'true']
340fn collides_with_preamble_true() {}
341
342@[export: 'Array']
343fn collides_with_runtime_array() {}
344
345@[export: 'map']
346fn collides_with_runtime_map() int {
347 values := map[string]int{
348 'a': 1
349 }
350 return values['a'] or { 0 }
351}
352
353@[export: 'DenseArray']
354fn collides_with_runtime_dense_array() {}
355
356@[export: 'SortedMap']
357fn collides_with_runtime_sorted_map() {}
358
359@[export: 'Optional']
360fn collides_with_runtime_optional() {}
361
362fn main() {}
363"
364 })
365 compile := export_attr_compile(v3_bin, os.join_path(root, 'main.v'), os.join_path(root, 'app'))
366 assert compile.exit_code != 0, compile.output
367 assert compile.output.contains('invalid export name `bool`'), compile.output
368 assert compile.output.contains('invalid export name `string`'), compile.output
369 assert compile.output.contains('invalid export name `voidptr`'), compile.output
370 assert compile.output.contains('invalid export name `i8`'), compile.output
371 assert compile.output.contains('invalid export name `true`'), compile.output
372 assert compile.output.contains('invalid export name `Array`'), compile.output
373 assert compile.output.contains('invalid export name `map`'), compile.output
374 assert compile.output.contains('invalid export name `DenseArray`'), compile.output
375 assert compile.output.contains('invalid export name `SortedMap`'), compile.output
376 assert compile.output.contains('invalid export name `Optional`'), compile.output
377}
378
379fn test_generic_export_is_rejected_fail_closed() {
380 v3_bin := export_attr_build_v3()
381 root := export_attr_project('generic_export', {
382 'main.v': "module main
383
384@[export: 'raw_generic']
385fn raw_generic[T](value T) T {
386 return value
387}
388
389fn main() {}
390"
391 })
392 compile := export_attr_compile(v3_bin, os.join_path(root, 'main.v'), os.join_path(root, 'app'))
393 assert compile.exit_code != 0, compile.output
394 assert compile.output.contains('generic function `raw_generic` cannot be exported'), compile.output
395}
396
397fn test_exported_function_must_name_params() {
398 v3_bin := export_attr_build_v3()
399 root := export_attr_project('unnamed_param', {
400 'main.v': "module main
401
402@[export: 'raw_unnamed']
403fn unnamed(int) int {
404 return 0
405}
406
407fn main() {}
408"
409 })
410 compile := export_attr_compile(v3_bin, os.join_path(root, 'main.v'), os.join_path(root, 'app'))
411 assert compile.exit_code != 0, compile.output
412 assert compile.output.contains('must name all parameters'), compile.output
413}
414
415fn test_export_wrapper_fixed_array_return_uses_return_wrapper_type() {
416 v3_bin := export_attr_build_v3()
417 root := export_attr_project('fixed_array_return', {
418 'main.v': "module main
419
420@[export: 'raw_numbers']
421fn numbers() [3]int {
422 mut values := [3]int{}
423 values[0] = 1
424 values[1] = 2
425 values[2] = 3
426 return values
427}
428
429fn main() {}
430"
431 })
432 bin_path := os.join_path(root, 'app')
433 compile := export_attr_compile(v3_bin, os.join_path(root, 'main.v'), bin_path)
434 assert compile.exit_code == 0, compile.output
435 c_code := os.read_file(bin_path + '.c') or { panic(err) }
436 assert c_code.contains('_v_ret_Array_fixed_int_3 numbers(void);'), c_code
437 assert c_code.contains('_v_ret_Array_fixed_int_3 raw_numbers(void);'), c_code
438 assert c_code.contains('_v_ret_Array_fixed_int_3 numbers(void) {'), c_code
439 assert c_code.contains('_v_ret_Array_fixed_int_3 raw_numbers(void) {'), c_code
440 assert c_code.contains('return numbers();'), c_code
441 assert !c_code.contains('\nArray_fixed_int_3 raw_numbers(void)'), c_code
442}
443
444fn test_export_wrapper_fn_pointer_return_uses_fn_pointer_typedef() {
445 v3_bin := export_attr_build_v3()
446 root := export_attr_project('fn_pointer_return', {
447 'main.v': "module main
448
449type Callback = fn () int
450
451fn callback_value() int {
452 return 7
453}
454
455@[export: 'raw_callback']
456fn callback() Callback {
457 return callback_value
458}
459
460fn main() {}
461"
462 })
463 bin_path := os.join_path(root, 'app')
464 compile := export_attr_compile(v3_bin, os.join_path(root, 'main.v'), bin_path)
465 assert compile.exit_code == 0, compile.output
466 c_code := os.read_file(bin_path + '.c') or { panic(err) }
467 assert c_code.contains('typedef int (*_fn_ptr_'), c_code
468 assert c_code.contains(' callback(void);'), c_code
469 assert c_code.contains(' raw_callback(void);'), c_code
470 assert c_code.contains(' raw_callback(void) {'), c_code
471 assert c_code.contains('return callback();'), c_code
472 assert !c_code.contains('fn_ptr:'), c_code
473}
474
475fn test_export_wrapper_for_veb_handler_forwards_implicit_ctx() {
476 v3_bin := export_attr_build_v3()
477 root := export_attr_project('veb_implicit_ctx_export_wrapper', {
478 'main.v': "module main
479
480import veb
481
482pub struct Context {
483 veb.Context
484}
485
486pub struct App {}
487
488@[export: 'raw_show']
489pub fn (app &App) show(id int) veb.Result {
490 _ = id
491 return veb.Result{}
492}
493
494fn main() {}
495"
496 })
497 c_path := os.join_path(root, 'app.c')
498 compile := export_attr_compile(v3_bin, os.join_path(root, 'main.v'), c_path)
499 assert compile.exit_code == 0, compile.output
500 c_code := os.read_file(c_path) or { panic(err) }
501 assert c_code.contains('veb__Result raw_show(App* app, Context* ctx, int id);'), c_code
502 assert c_code.contains('veb__Result raw_show(App* app, Context* ctx, int id) {'), c_code
503 assert c_code.contains('return App__show(app, ctx, id);'), c_code
504}
505
506fn test_export_wrapper_for_veb_handler_underscore_param_matches_implicit_ctx_position() {
507 v3_bin := export_attr_build_v3()
508 root := export_attr_project('veb_implicit_ctx_underscore_export_wrapper', {
509 'main.v': "module main
510
511import veb
512
513pub struct Context {
514 veb.Context
515}
516
517pub struct App {}
518
519@[export: 'raw_show_underscore']
520pub fn (app &App) show(_ int) veb.Result {
521 return veb.Result{}
522}
523
524fn main() {}
525"
526 })
527 c_path := os.join_path(root, 'app.c')
528 compile := export_attr_compile(v3_bin, os.join_path(root, 'main.v'), c_path)
529 assert compile.exit_code == 0, compile.output
530 c_code := os.read_file(c_path) or { panic(err) }
531 assert c_code.contains('veb__Result raw_show_underscore(App* app, Context* ctx, int _2);'), c_code
532 assert c_code.contains('veb__Result raw_show_underscore(App* app, Context* ctx, int _2) {'), c_code
533 assert c_code.contains('return App__show(app, ctx, _2);'), c_code
534 assert !c_code.contains('return App__show(app, ctx, _1);'), c_code
535}
536