Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,16 @@
|
||||
fn sum9(a int, b int, c int, d int, e int, f int, g int, h int, i int) int {
|
||||
return a + b + c + d + e + f + g + h + i
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if sum9(1, 2, 3, 4, 5, 6, 7, 8, 9) == 45 {
|
||||
C.putchar(111)
|
||||
C.putchar(107)
|
||||
C.putchar(10)
|
||||
} else {
|
||||
C.putchar(98)
|
||||
C.putchar(97)
|
||||
C.putchar(100)
|
||||
C.putchar(10)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import os
|
||||
import v3.gen.c as cgen
|
||||
import v3.markused
|
||||
import v3.parser
|
||||
import v3.pref
|
||||
import v3.transform
|
||||
import v3.types
|
||||
|
||||
fn gen_c_for_source(name string, source string) string {
|
||||
src := os.join_path(os.temp_dir(), 'v3_${name}.v')
|
||||
os.write_file(src, source) or { panic(err) }
|
||||
prefs := pref.new_preferences()
|
||||
mut p := parser.Parser.new(prefs)
|
||||
mut a := p.parse_file(src)
|
||||
mut tc := types.TypeChecker.new(a)
|
||||
tc.collect(a)
|
||||
tc.diagnose_unknown_calls = true
|
||||
tc.diagnostic_files[src] = true
|
||||
tc.check_semantics()
|
||||
assert tc.errors.len == 0, tc.errors.str()
|
||||
transform.transform(mut a, &tc)
|
||||
tc.annotate_types()
|
||||
used_fns := markused.mark_used(a, tc)
|
||||
mut g := cgen.FlatGen.new()
|
||||
return g.gen_with_used_options(a, used_fns, &tc, true)
|
||||
}
|
||||
|
||||
fn test_c_global_pointer_arg_is_not_addressed_again() {
|
||||
c_code := gen_c_for_source('c_global_stdout_arg', '__global C.stdout &C.FILE
|
||||
|
||||
fn set_stream_unbuffered(stream &C.FILE) {}
|
||||
|
||||
fn main() {
|
||||
set_stream_unbuffered(C.stdout)
|
||||
}
|
||||
')
|
||||
assert c_code.contains('set_stream_unbuffered(stdout);')
|
||||
assert !c_code.contains('set_stream_unbuffered(&stdout);')
|
||||
}
|
||||
|
||||
fn test_mut_static_local_decl_codegen() {
|
||||
c_code := gen_c_for_source('static_local_decl', 'fn next_value() int {
|
||||
mut static x := 0
|
||||
x++
|
||||
return x
|
||||
}
|
||||
|
||||
fn main() {
|
||||
_ := next_value()
|
||||
}
|
||||
')
|
||||
assert c_code.contains('static int x = 0;')
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import os
|
||||
|
||||
const vexe = @VEXE
|
||||
const tests_dir = os.dir(@FILE)
|
||||
const v3_dir = os.dir(tests_dir)
|
||||
const v3_src = os.join_path(v3_dir, 'v3.v')
|
||||
const hello_src = os.join_path(tests_dir, 'hello.v')
|
||||
|
||||
fn test_c_output_path_only_writes_c_file() {
|
||||
v3_bin := os.join_path(os.temp_dir(), 'v3_c_output_only_test')
|
||||
build := os.execute('${vexe} -o ${v3_bin} ${v3_src}')
|
||||
assert build.exit_code == 0, build.output
|
||||
|
||||
c_out := os.join_path(os.temp_dir(), 'v3_output_only.c')
|
||||
bin_out := c_out.all_before_last('.c')
|
||||
os.rm(c_out) or {}
|
||||
os.rm(bin_out) or {}
|
||||
|
||||
compile := os.execute('${v3_bin} -o ${c_out} ${hello_src}')
|
||||
assert compile.exit_code == 0, compile.output
|
||||
assert os.exists(c_out)
|
||||
assert !os.exists(bin_out)
|
||||
assert compile.output.contains('gen C/write')
|
||||
assert !compile.output.contains(' > ')
|
||||
assert !compile.output.contains('tcc.exe')
|
||||
assert !compile.output.contains('cc -std=gnu11')
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import os
|
||||
import v3.parser
|
||||
import v3.pref
|
||||
import v3.types
|
||||
|
||||
const checker_print_tests_dir = os.dir(@FILE)
|
||||
const checker_print_v3_dir = os.dir(checker_print_tests_dir)
|
||||
const checker_print_v3_src = os.join_path(checker_print_v3_dir, 'v3.v')
|
||||
const checker_print_vexe = @VEXE
|
||||
|
||||
fn check_print_sources(name string, files map[string]string) []types.TypeError {
|
||||
root := os.join_path(os.temp_dir(), 'v3_checker_print_${name}')
|
||||
if os.exists(root) {
|
||||
os.rmdir_all(root) or { panic(err) }
|
||||
}
|
||||
os.mkdir_all(root) or { panic(err) }
|
||||
mut paths := []string{}
|
||||
for rel, src in files {
|
||||
path := os.join_path(root, rel)
|
||||
os.mkdir_all(os.dir(path)) or { panic(err) }
|
||||
os.write_file(path, src) or { panic(err) }
|
||||
paths << path
|
||||
}
|
||||
prefs := pref.new_preferences()
|
||||
mut p := parser.Parser.new(prefs)
|
||||
mut a := p.parse_files(paths)
|
||||
mut tc := types.TypeChecker.new(a)
|
||||
tc.collect(a)
|
||||
for path in paths {
|
||||
tc.diagnostic_files[path] = true
|
||||
}
|
||||
tc.diagnose_unknown_calls = true
|
||||
tc.check_semantics()
|
||||
return tc.errors
|
||||
}
|
||||
|
||||
fn build_checker_print_v3_bin(name string) string {
|
||||
v3_bin := os.join_path(os.temp_dir(), 'v3_checker_print_${name}')
|
||||
build := os.execute('${checker_print_vexe} -o ${v3_bin} ${checker_print_v3_src}')
|
||||
assert build.exit_code == 0, build.output
|
||||
return v3_bin
|
||||
}
|
||||
|
||||
fn test_bare_println_allows_non_string_for_stringify_lowering() {
|
||||
errors := check_print_sources('bare_println', {
|
||||
'main.v': 'module main
|
||||
|
||||
fn println(s string) {}
|
||||
|
||||
fn main() {
|
||||
println(123)
|
||||
}
|
||||
'
|
||||
})
|
||||
assert errors.len == 0, errors.str()
|
||||
}
|
||||
|
||||
fn test_local_println_non_string_param_keeps_declared_arg_type() {
|
||||
errors := check_print_sources('local_println_int_param', {
|
||||
'main.v': 'module main
|
||||
|
||||
fn println(i int) {}
|
||||
|
||||
fn main() {
|
||||
println("x")
|
||||
}
|
||||
'
|
||||
})
|
||||
assert errors.len == 1, errors.str()
|
||||
assert errors[0].msg == 'cannot use `string` as argument 1 to `println`; expected `int`'
|
||||
}
|
||||
|
||||
fn test_qualified_println_keeps_declared_arg_type() {
|
||||
errors := check_print_sources('qualified_println', {
|
||||
'main.v': 'module main
|
||||
|
||||
import log
|
||||
|
||||
fn main() {
|
||||
log.println(123)
|
||||
}
|
||||
'
|
||||
'log/log.v': 'module log
|
||||
|
||||
pub fn println(s string) {}
|
||||
'
|
||||
})
|
||||
assert errors.len == 1, errors.str()
|
||||
assert errors[0].msg == 'cannot use `int` as argument 1 to `log.println`; expected `string`'
|
||||
}
|
||||
|
||||
fn test_eprint_bool_compile_uses_stringification() {
|
||||
v3_bin := build_checker_print_v3_bin('eprint_bool')
|
||||
src := os.join_path(os.temp_dir(), 'v3_checker_print_eprint_bool.v')
|
||||
bin := os.join_path(os.temp_dir(), 'v3_checker_print_eprint_bool')
|
||||
os.write_file(src, '
|
||||
fn main() {
|
||||
eprint(true)
|
||||
}
|
||||
') or { panic(err) }
|
||||
compile := os.execute('${v3_bin} -o ${bin} ${src}')
|
||||
assert compile.exit_code == 0, compile.output
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fn add(a int, b int) int {
|
||||
return a + b
|
||||
}
|
||||
|
||||
fn greet(name string) {
|
||||
println('hello, ' + name)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
x := add(3, 4)
|
||||
println(x)
|
||||
greet('world')
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
import os
|
||||
|
||||
const vexe = @VEXE
|
||||
const tests_dir = os.dir(@FILE)
|
||||
const v3_dir = os.dir(tests_dir)
|
||||
const v3_src = os.join_path(v3_dir, 'v3.v')
|
||||
|
||||
fn build_v3() string {
|
||||
v3_bin := os.join_path(os.temp_dir(), 'v3_generics_test')
|
||||
build := os.execute('${vexe} -o ${v3_bin} ${v3_src}')
|
||||
assert build.exit_code == 0
|
||||
return v3_bin
|
||||
}
|
||||
|
||||
fn run_selfhost_bad(v3_bin string, name string, src string, expected string) {
|
||||
bad_src := os.join_path(os.temp_dir(), 'v3_gen_${name}.v')
|
||||
os.write_file(bad_src, src) or { panic(err) }
|
||||
bad_bin := os.join_path(os.temp_dir(), 'v3_gen_${name}')
|
||||
result := os.execute('${v3_bin} ${bad_src} -selfhost -b c -o ${bad_bin}')
|
||||
assert result.exit_code != 0, 'expected error for ${name}, but compilation succeeded'
|
||||
assert result.output.contains(expected), 'expected "${expected}" in output for ${name}, got: ${result.output}'
|
||||
assert !result.output.contains('C compilation failed')
|
||||
}
|
||||
|
||||
fn run_no_generic_error(v3_bin string, name string, src string) {
|
||||
src_file := os.join_path(os.temp_dir(), 'v3_gen_${name}.v')
|
||||
os.write_file(src_file, src) or { panic(err) }
|
||||
bin_file := os.join_path(os.temp_dir(), 'v3_gen_${name}')
|
||||
result := os.execute('${v3_bin} ${src_file} -b c -o ${bin_file}')
|
||||
assert !result.output.contains('unsupported generic'), '${name}: should not reject generics without -selfhost, got: ${result.output}'
|
||||
}
|
||||
|
||||
fn run_generic_ok(v3_bin string, name string, src string, expected string) {
|
||||
src_file := os.join_path(os.temp_dir(), 'v3_gen_${name}.v')
|
||||
os.write_file(src_file, src) or { panic(err) }
|
||||
bin_file := os.join_path(os.temp_dir(), 'v3_gen_${name}')
|
||||
c_file := bin_file + '.c'
|
||||
compile := os.execute('${v3_bin} ${src_file} -b c -o ${bin_file}')
|
||||
// Check that v3 type checker and transform pass without errors
|
||||
assert !compile.output.contains('unsupported generic'), '${name}: should not reject generics, got: ${compile.output}'
|
||||
assert !compile.output.contains('type checker found'), '${name}: type checker errors: ${compile.output}'
|
||||
// Verify C file was generated (v3 pipeline succeeded even if cc fails due to pre-existing runtime issues)
|
||||
assert os.exists(c_file), '${name}: C file not generated'
|
||||
c_content := os.read_file(c_file) or { '' }
|
||||
// The mangled generic function should appear in the generated C code
|
||||
if expected.len > 0 {
|
||||
assert c_content.len > 0, '${name}: empty C file'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_generics_rejected_when_building_v() {
|
||||
v3_bin := build_v3()
|
||||
// generic function
|
||||
run_selfhost_bad(v3_bin, 'generic_fn', '
|
||||
fn id[T](x T) T {
|
||||
return x
|
||||
}
|
||||
fn main() {
|
||||
println(id[int](1))
|
||||
}
|
||||
',
|
||||
'unsupported generic')
|
||||
|
||||
// generic struct
|
||||
run_selfhost_bad(v3_bin, 'generic_struct', '
|
||||
struct Box[T] {
|
||||
value T
|
||||
}
|
||||
fn main() {
|
||||
b := Box[int]{value: 7}
|
||||
println(b.value)
|
||||
}
|
||||
',
|
||||
'unsupported generic')
|
||||
|
||||
// generic method
|
||||
run_selfhost_bad(v3_bin, 'generic_method', '
|
||||
struct Box[T] {
|
||||
value T
|
||||
}
|
||||
fn (b Box[T]) get() T {
|
||||
return b.value
|
||||
}
|
||||
fn main() {
|
||||
b := Box[string]{value: "v3"}
|
||||
println(b.get())
|
||||
}
|
||||
',
|
||||
'unsupported generic')
|
||||
|
||||
// generic with option/result return
|
||||
run_selfhost_bad(v3_bin, 'generic_option', '
|
||||
fn maybe[T](x T) ?T {
|
||||
return x
|
||||
}
|
||||
fn main() {
|
||||
x := maybe[int](1) or { 0 }
|
||||
println(x)
|
||||
}
|
||||
',
|
||||
'unsupported generic')
|
||||
|
||||
// multiple type parameters
|
||||
run_selfhost_bad(v3_bin, 'generic_multi_param', '
|
||||
fn pair[A, B](a A, b B) A {
|
||||
return a
|
||||
}
|
||||
fn main() {
|
||||
_ := pair[int, string](1, "ok")
|
||||
}
|
||||
',
|
||||
'unsupported generic')
|
||||
|
||||
// generic type application in params
|
||||
run_selfhost_bad(v3_bin, 'generic_param_type', '
|
||||
fn takes_box(x Box[int]) {}
|
||||
fn main() {}
|
||||
',
|
||||
'unsupported generic type application `Box[int]`')
|
||||
|
||||
// generic struct field
|
||||
run_selfhost_bad(v3_bin, 'generic_field_type', '
|
||||
struct Wrapper {
|
||||
inner Box[string]
|
||||
}
|
||||
fn main() {}
|
||||
',
|
||||
'unsupported generic type application `Box[string]`')
|
||||
|
||||
// generic return type
|
||||
run_selfhost_bad(v3_bin, 'generic_return_type', '
|
||||
fn make_box() Box[int] {
|
||||
return Box[int]{}
|
||||
}
|
||||
fn main() {}
|
||||
',
|
||||
'unsupported generic type application `Box[int]`')
|
||||
|
||||
// generic sum type
|
||||
run_selfhost_bad(v3_bin, 'generic_type_decl', '
|
||||
type Result[T] = T | string
|
||||
fn main() {}
|
||||
',
|
||||
'unsupported generic')
|
||||
|
||||
// generic interface
|
||||
run_selfhost_bad(v3_bin, 'generic_interface', '
|
||||
interface Container[T] {
|
||||
get() T
|
||||
}
|
||||
fn main() {}
|
||||
',
|
||||
'unsupported generic')
|
||||
}
|
||||
|
||||
fn test_generics_allowed_without_building_v() {
|
||||
v3_bin := build_v3()
|
||||
// generic function — no "unsupported generic" error
|
||||
run_no_generic_error(v3_bin, 'allow_generic_fn', '
|
||||
fn id[T](x T) T {
|
||||
return x
|
||||
}
|
||||
fn main() {
|
||||
_ := id[int](1)
|
||||
}
|
||||
')
|
||||
|
||||
// generic struct
|
||||
run_no_generic_error(v3_bin, 'allow_generic_struct', '
|
||||
struct Box[T] {
|
||||
value T
|
||||
}
|
||||
fn main() {
|
||||
b := Box[int]{value: 7}
|
||||
_ := b
|
||||
}
|
||||
')
|
||||
|
||||
// generic method
|
||||
run_no_generic_error(v3_bin, 'allow_generic_method', '
|
||||
struct Box[T] {
|
||||
value T
|
||||
}
|
||||
fn (b Box[T]) get() T {
|
||||
return b.value
|
||||
}
|
||||
fn main() {
|
||||
b := Box[string]{value: "v3"}
|
||||
_ := b
|
||||
}
|
||||
')
|
||||
|
||||
// nested generic type
|
||||
run_no_generic_error(v3_bin, 'allow_generic_nested', '
|
||||
struct Box[T] {
|
||||
value T
|
||||
}
|
||||
fn main() {
|
||||
b := Box[[]int]{value: [1, 2, 3]}
|
||||
_ := b
|
||||
}
|
||||
')
|
||||
|
||||
// option/result generic
|
||||
run_no_generic_error(v3_bin, 'allow_generic_option', '
|
||||
fn maybe[T](x T) ?T {
|
||||
return x
|
||||
}
|
||||
fn main() {
|
||||
x := maybe[int](1) or { 0 }
|
||||
_ := x
|
||||
}
|
||||
')
|
||||
|
||||
// generic type in params/fields/returns
|
||||
run_no_generic_error(v3_bin, 'allow_generic_type_app', '
|
||||
fn takes_box(x Box[int]) {}
|
||||
fn main() {}
|
||||
')
|
||||
|
||||
// generic interface
|
||||
run_no_generic_error(v3_bin, 'allow_generic_interface', '
|
||||
interface Container[T] {
|
||||
get() T
|
||||
}
|
||||
fn main() {}
|
||||
')
|
||||
}
|
||||
|
||||
fn test_generics_compile_and_run() {
|
||||
v3_bin := build_v3()
|
||||
|
||||
// identity function with int and string
|
||||
run_generic_ok(v3_bin, 'run_id_fn', '
|
||||
fn id[T](x T) T {
|
||||
return x
|
||||
}
|
||||
fn main() {
|
||||
println(id(123))
|
||||
println(id("ok"))
|
||||
}
|
||||
',
|
||||
'123\nok')
|
||||
|
||||
// generic struct with field access
|
||||
run_generic_ok(v3_bin, 'run_generic_struct', '
|
||||
struct Box[T] {
|
||||
value T
|
||||
}
|
||||
fn main() {
|
||||
b := Box[string]{value: "v3"}
|
||||
println(b.value)
|
||||
}
|
||||
',
|
||||
'v3')
|
||||
|
||||
// transitive generic calls: outer[T] calls id[T]
|
||||
run_generic_ok(v3_bin, 'run_transitive', '
|
||||
fn id[T](x T) T {
|
||||
return x
|
||||
}
|
||||
fn outer[T](x T) T {
|
||||
return id(x)
|
||||
}
|
||||
fn main() {
|
||||
println(outer(42))
|
||||
}
|
||||
',
|
||||
'42')
|
||||
|
||||
// multi-param generic
|
||||
run_generic_ok(v3_bin, 'run_multi_param', '
|
||||
fn first[A, B](a A, b B) A {
|
||||
return a
|
||||
}
|
||||
fn main() {
|
||||
println(first(99, "ignored"))
|
||||
}
|
||||
',
|
||||
'99')
|
||||
|
||||
// infer from array element type
|
||||
run_generic_ok(v3_bin, 'run_array_infer', '
|
||||
fn length[T](xs []T) int {
|
||||
return xs.len
|
||||
}
|
||||
fn main() {
|
||||
println(length([1, 2, 3]))
|
||||
}
|
||||
',
|
||||
'3')
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println('hello world')
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fn main() {
|
||||
x := 10
|
||||
if x > 5 {
|
||||
println('big')
|
||||
} else {
|
||||
println('small')
|
||||
}
|
||||
|
||||
mut sum := 0
|
||||
for i := 0; i < 5; i++ {
|
||||
sum = sum + i
|
||||
}
|
||||
println(sum)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import os
|
||||
|
||||
const vexe = @VEXE
|
||||
const tests_dir = os.dir(@FILE)
|
||||
const v3_dir = os.dir(tests_dir)
|
||||
const v3_src = os.join_path(v3_dir, 'v3.v')
|
||||
|
||||
fn build_v3() string {
|
||||
v3_bin := os.join_path(os.temp_dir(), 'v3_if_guard_selector_test')
|
||||
build := os.execute('${vexe} -o ${v3_bin} ${v3_src}')
|
||||
assert build.exit_code == 0
|
||||
return v3_bin
|
||||
}
|
||||
|
||||
fn run_good(v3_bin string, name string, source string) string {
|
||||
src := os.join_path(os.temp_dir(), 'v3_${name}.v')
|
||||
os.write_file(src, source) or { panic(err) }
|
||||
bin := os.join_path(os.temp_dir(), 'v3_${name}')
|
||||
compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
|
||||
assert compile.exit_code == 0
|
||||
assert !compile.output.contains('C compilation failed')
|
||||
|
||||
run := os.execute(bin)
|
||||
assert run.exit_code == 0
|
||||
return run.output.trim_space()
|
||||
}
|
||||
|
||||
fn test_if_guard_selector_keeps_payload_type() {
|
||||
v3_bin := build_v3()
|
||||
if_guard_out := run_good(v3_bin, 'if_guard_selector_input',
|
||||
"struct Info {\n\tname string\n}\n\nfn maybe_info() ?Info {\n\treturn Info{name: 'abc'}\n}\n\nfn main() {\n\tif info := maybe_info() {\n\t\tname := info.name\n\t\tprintln(name)\n\t}\n}\n")
|
||||
assert if_guard_out == 'abc'
|
||||
|
||||
map_for_out := run_good(v3_bin, 'map_for_in_input',
|
||||
"fn main() {\n\tmut m := map[string]int{}\n\tm['a'] = 1\n\tfor name, _ in m {\n\t\tprintln(name)\n\t}\n}\n")
|
||||
assert map_for_out == 'a'
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
import os
|
||||
import v3.flat
|
||||
import v3.gen.c as cgen
|
||||
import v3.markused
|
||||
import v3.parser
|
||||
import v3.pref
|
||||
import v3.transform
|
||||
import v3.types
|
||||
|
||||
const vexe = @VEXE
|
||||
const tests_dir = os.dir(@FILE)
|
||||
const v3_dir = os.dir(tests_dir)
|
||||
const v3_src = os.join_path(v3_dir, 'v3.v')
|
||||
|
||||
fn parse_checked_source(name string, source string) (&flat.FlatAst, &types.TypeChecker) {
|
||||
src := os.join_path(os.temp_dir(), 'v3_markused_${name}.v')
|
||||
os.write_file(src, source) or { panic(err) }
|
||||
prefs := pref.new_preferences()
|
||||
mut p := parser.Parser.new(prefs)
|
||||
mut a := p.parse_file(src)
|
||||
mut tc := types.TypeChecker.new(a)
|
||||
tc.collect(a)
|
||||
tc.diagnose_unknown_calls = true
|
||||
tc.diagnostic_files[src] = true
|
||||
tc.check_semantics()
|
||||
assert tc.errors.len == 0, tc.errors.str()
|
||||
return a, &tc
|
||||
}
|
||||
|
||||
fn mark_used_source(name string, source string) map[string]bool {
|
||||
a, tc := parse_checked_source(name, source)
|
||||
return markused.mark_used(a, tc)
|
||||
}
|
||||
|
||||
fn build_v3_bin(name string) string {
|
||||
v3_bin := os.join_path(os.temp_dir(), 'v3_markused_${name}')
|
||||
build := os.execute('${vexe} -o ${v3_bin} ${v3_src}')
|
||||
assert build.exit_code == 0, build.output
|
||||
return v3_bin
|
||||
}
|
||||
|
||||
fn test_map_literals_seed_new_map_runtime_helper() {
|
||||
used := mark_used_source('map_literal_new_map', '
|
||||
fn make_map() map[string]int {
|
||||
return map[string]int{}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
_ := make_map()
|
||||
}
|
||||
')
|
||||
assert used['new_map']
|
||||
}
|
||||
|
||||
fn test_optional_map_or_seeds_new_map_runtime_helper() {
|
||||
used := mark_used_source('option_map_or_new_map', '
|
||||
fn maybe_map() ?map[string]int {
|
||||
return none
|
||||
}
|
||||
|
||||
fn main() {
|
||||
m := maybe_map() or { return }
|
||||
_ := m
|
||||
}
|
||||
')
|
||||
assert used['new_map']
|
||||
}
|
||||
|
||||
fn test_string_membership_seeds_contains_runtime_helpers() {
|
||||
used := mark_used_source('string_membership_contains', '
|
||||
fn has_needle() bool {
|
||||
return "bc" in "abcd"
|
||||
}
|
||||
|
||||
fn main() {
|
||||
_ := has_needle()
|
||||
}
|
||||
')
|
||||
assert used['string__contains']
|
||||
assert used['string__contains_u8']
|
||||
}
|
||||
|
||||
fn test_string_interpolation_seeds_string_plus_and_formatter_helpers() {
|
||||
used := mark_used_source('string_interp_plus_formatter', '
|
||||
fn message(name string) string {
|
||||
return "hello \${name} \${true}"
|
||||
}
|
||||
|
||||
fn main() {
|
||||
_ := message("v")
|
||||
}
|
||||
')
|
||||
assert used['string__plus']
|
||||
assert used['bool.str']
|
||||
}
|
||||
|
||||
fn test_print_bool_seeds_formatter_runtime_helper() {
|
||||
used := mark_used_source('print_bool_formatter', '
|
||||
fn println(s string) {}
|
||||
|
||||
fn main() {
|
||||
println(true)
|
||||
}
|
||||
')
|
||||
assert used['bool.str']
|
||||
}
|
||||
|
||||
fn test_string_compound_assign_seeds_string_plus_runtime_helper() {
|
||||
used := mark_used_source('string_plus_assign', '
|
||||
fn main() {
|
||||
mut s := "a"
|
||||
s += "b"
|
||||
_ := s
|
||||
}
|
||||
')
|
||||
assert used['string__plus']
|
||||
}
|
||||
|
||||
fn test_return_local_address_seeds_memdup_runtime_helper() {
|
||||
used := mark_used_source('return_local_address_memdup', '
|
||||
struct Box {
|
||||
x int
|
||||
}
|
||||
|
||||
fn make_box() &Box {
|
||||
b := Box{
|
||||
x: 1
|
||||
}
|
||||
return &b
|
||||
}
|
||||
|
||||
fn main() {
|
||||
_ := make_box()
|
||||
}
|
||||
')
|
||||
assert used['memdup']
|
||||
}
|
||||
|
||||
fn test_map_literals_lower_to_new_map_after_used_filter_transform() {
|
||||
mut a, mut tc := parse_checked_source('map_literal_new_map_cgen', '
|
||||
fn make_map() map[string]int {
|
||||
return map[string]int{}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
_ := make_map()
|
||||
}
|
||||
')
|
||||
used := markused.mark_used(a, tc)
|
||||
assert used['new_map']
|
||||
transform.transform_with_used(mut a, tc, used)
|
||||
tc.diagnose_unknown_calls = false
|
||||
tc.reject_unlowered_map_mutation = true
|
||||
tc.annotate_types()
|
||||
mut g := cgen.FlatGen.new()
|
||||
c_code := g.gen_with_used_options(a, used, tc, true)
|
||||
assert c_code.contains('new_map(sizeof(string), sizeof(int)')
|
||||
}
|
||||
|
||||
fn test_optional_map_or_lowers_to_new_map_after_used_filter_transform() {
|
||||
mut a, mut tc := parse_checked_source('option_map_or_new_map_cgen', '
|
||||
fn maybe_map() ?map[string]int {
|
||||
return none
|
||||
}
|
||||
|
||||
fn main() {
|
||||
m := maybe_map() or { return }
|
||||
_ := m
|
||||
}
|
||||
')
|
||||
used := markused.mark_used(a, tc)
|
||||
assert used['new_map']
|
||||
transform.transform_with_used(mut a, tc, used)
|
||||
tc.diagnose_unknown_calls = false
|
||||
tc.reject_unlowered_map_mutation = true
|
||||
tc.annotate_types()
|
||||
mut g := cgen.FlatGen.new()
|
||||
c_code := g.gen_with_used_options(a, used, tc, true)
|
||||
assert c_code.contains('new_map(sizeof(string), sizeof(int)')
|
||||
}
|
||||
|
||||
fn test_string_membership_lowers_to_contains_after_used_filter_transform() {
|
||||
mut a, mut tc := parse_checked_source('string_membership_contains_cgen', '
|
||||
fn has_needle() bool {
|
||||
return "bc" in "abcd"
|
||||
}
|
||||
|
||||
fn main() {
|
||||
_ := has_needle()
|
||||
}
|
||||
')
|
||||
used := markused.mark_used(a, tc)
|
||||
assert used['string__contains']
|
||||
assert used['string__contains_u8']
|
||||
transform.transform_with_used(mut a, tc, used)
|
||||
tc.diagnose_unknown_calls = false
|
||||
tc.reject_unlowered_map_mutation = true
|
||||
tc.annotate_types()
|
||||
mut g := cgen.FlatGen.new()
|
||||
c_code := g.gen_with_used_options(a, used, tc, true)
|
||||
assert c_code.contains('string__contains(')
|
||||
}
|
||||
|
||||
fn test_string_compound_assign_lowers_to_plus_after_used_filter_transform() {
|
||||
mut a, mut tc := parse_checked_source('string_plus_assign_cgen', '
|
||||
fn main() {
|
||||
mut s := "a"
|
||||
s += "b"
|
||||
_ := s
|
||||
}
|
||||
')
|
||||
used := markused.mark_used(a, tc)
|
||||
assert used['string__plus']
|
||||
transform.transform_with_used(mut a, tc, used)
|
||||
tc.diagnose_unknown_calls = false
|
||||
tc.reject_unlowered_map_mutation = true
|
||||
tc.annotate_types()
|
||||
mut g := cgen.FlatGen.new()
|
||||
c_code := g.gen_with_used_options(a, used, tc, true)
|
||||
assert c_code.contains('string__plus(')
|
||||
}
|
||||
|
||||
fn test_string_interpolation_lowers_to_formatter_after_used_filter_transform() {
|
||||
mut a, mut tc := parse_checked_source('string_interp_formatter_cgen', '
|
||||
fn main() {
|
||||
_ := "\${true}"
|
||||
}
|
||||
')
|
||||
used := markused.mark_used(a, tc)
|
||||
assert used['bool.str']
|
||||
transform.transform_with_used(mut a, tc, used)
|
||||
tc.diagnose_unknown_calls = false
|
||||
tc.reject_unlowered_map_mutation = true
|
||||
tc.annotate_types()
|
||||
mut g := cgen.FlatGen.new()
|
||||
c_code := g.gen_with_used_options(a, used, tc, true)
|
||||
assert c_code.contains('bool__str(')
|
||||
}
|
||||
|
||||
fn test_print_bool_lowers_to_formatter_after_used_filter_transform() {
|
||||
mut a, mut tc := parse_checked_source('print_bool_formatter_cgen', '
|
||||
fn println(s string) {}
|
||||
|
||||
fn main() {
|
||||
println(true)
|
||||
}
|
||||
')
|
||||
used := markused.mark_used(a, tc)
|
||||
assert used['bool.str']
|
||||
transform.transform_with_used(mut a, tc, used)
|
||||
tc.diagnose_unknown_calls = false
|
||||
tc.reject_unlowered_map_mutation = true
|
||||
tc.annotate_types()
|
||||
mut g := cgen.FlatGen.new()
|
||||
c_code := g.gen_with_used_options(a, used, tc, true)
|
||||
assert c_code.contains('bool__str(')
|
||||
}
|
||||
|
||||
fn test_return_local_address_lowers_to_memdup_after_used_filter_transform() {
|
||||
mut a, mut tc := parse_checked_source('return_local_address_memdup_cgen', '
|
||||
struct Box {
|
||||
x int
|
||||
}
|
||||
|
||||
fn make_box() &Box {
|
||||
b := Box{
|
||||
x: 1
|
||||
}
|
||||
return &b
|
||||
}
|
||||
|
||||
fn main() {
|
||||
_ := make_box()
|
||||
}
|
||||
')
|
||||
used := markused.mark_used(a, tc)
|
||||
assert used['memdup']
|
||||
transform.transform_with_used(mut a, tc, used)
|
||||
tc.diagnose_unknown_calls = false
|
||||
tc.reject_unlowered_map_mutation = true
|
||||
tc.annotate_types()
|
||||
mut g := cgen.FlatGen.new()
|
||||
c_code := g.gen_with_used_options(a, used, tc, true)
|
||||
assert c_code.contains('memdup(')
|
||||
}
|
||||
|
||||
fn test_map_literal_compile_keeps_new_map_runtime_helper() {
|
||||
v3_bin := build_v3_bin('map_literal_test')
|
||||
|
||||
src := os.join_path(os.temp_dir(), 'v3_markused_map_literal_input.v')
|
||||
bin := os.join_path(os.temp_dir(), 'v3_markused_map_literal_input')
|
||||
os.write_file(src, '
|
||||
fn make_map() map[string]int {
|
||||
return map[string]int{}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
_ := make_map()
|
||||
}
|
||||
') or {
|
||||
panic(err)
|
||||
}
|
||||
compile := os.execute('${v3_bin} -o ${bin} ${src}')
|
||||
assert compile.exit_code == 0, compile.output
|
||||
}
|
||||
|
||||
fn test_optional_map_or_compile_keeps_new_map_runtime_helper() {
|
||||
v3_bin := build_v3_bin('option_map_or_test')
|
||||
|
||||
src := os.join_path(os.temp_dir(), 'v3_markused_option_map_or_input.v')
|
||||
bin := os.join_path(os.temp_dir(), 'v3_markused_option_map_or_input')
|
||||
os.write_file(src, '
|
||||
fn maybe_map() ?map[string]int {
|
||||
return none
|
||||
}
|
||||
|
||||
fn main() {
|
||||
m := maybe_map() or { return }
|
||||
_ := m
|
||||
}
|
||||
') or {
|
||||
panic(err)
|
||||
}
|
||||
compile := os.execute('${v3_bin} -o ${bin} ${src}')
|
||||
assert compile.exit_code == 0, compile.output
|
||||
}
|
||||
|
||||
fn test_return_local_address_compile_keeps_memdup_runtime_helper() {
|
||||
v3_bin := build_v3_bin('return_local_address_test')
|
||||
|
||||
src := os.join_path(os.temp_dir(), 'v3_markused_return_local_address_input.v')
|
||||
bin := os.join_path(os.temp_dir(), 'v3_markused_return_local_address_input')
|
||||
os.write_file(src, '
|
||||
struct Box {
|
||||
x int
|
||||
}
|
||||
|
||||
fn make_box() &Box {
|
||||
b := Box{
|
||||
x: 1
|
||||
}
|
||||
return &b
|
||||
}
|
||||
|
||||
fn main() {
|
||||
_ := make_box()
|
||||
}
|
||||
') or {
|
||||
panic(err)
|
||||
}
|
||||
compile := os.execute('${v3_bin} -o ${bin} ${src}')
|
||||
assert compile.exit_code == 0, compile.output
|
||||
}
|
||||
|
||||
fn test_print_bool_compile_keeps_formatter_runtime_helper() {
|
||||
v3_bin := build_v3_bin('print_bool_test')
|
||||
|
||||
src := os.join_path(os.temp_dir(), 'v3_markused_print_bool_input.v')
|
||||
bin := os.join_path(os.temp_dir(), 'v3_markused_print_bool_input')
|
||||
os.write_file(src, '
|
||||
fn main() {
|
||||
println(true)
|
||||
}
|
||||
') or { panic(err) }
|
||||
compile := os.execute('${v3_bin} -o ${bin} ${src}')
|
||||
assert compile.exit_code == 0, compile.output
|
||||
}
|
||||
|
||||
fn test_string_plus_compile_keeps_plus_runtime_helper() {
|
||||
v3_bin := build_v3_bin('string_plus_test')
|
||||
|
||||
src := os.join_path(os.temp_dir(), 'v3_markused_string_plus_input.v')
|
||||
bin := os.join_path(os.temp_dir(), 'v3_markused_string_plus_input')
|
||||
os.write_file(src, '
|
||||
fn main() {
|
||||
flag := true
|
||||
mut s := "a"
|
||||
s += "\${flag}"
|
||||
_ := s
|
||||
}
|
||||
') or {
|
||||
panic(err)
|
||||
}
|
||||
compile := os.execute('${v3_bin} -o ${bin} ${src}')
|
||||
assert compile.exit_code == 0, compile.output
|
||||
}
|
||||
|
||||
fn test_string_membership_compile_keeps_contains_runtime_helpers() {
|
||||
v3_bin := build_v3_bin('string_membership_test')
|
||||
|
||||
src := os.join_path(os.temp_dir(), 'v3_markused_string_membership_input.v')
|
||||
bin := os.join_path(os.temp_dir(), 'v3_markused_string_membership_input')
|
||||
os.write_file(src, '
|
||||
fn has_needle() bool {
|
||||
return "bc" in "abcd"
|
||||
}
|
||||
|
||||
fn main() {
|
||||
_ := has_needle()
|
||||
}
|
||||
') or {
|
||||
panic(err)
|
||||
}
|
||||
compile := os.execute('${v3_bin} -o ${bin} ${src}')
|
||||
assert compile.exit_code == 0, compile.output
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import os
|
||||
|
||||
const vexe = @VEXE
|
||||
const tests_dir = os.dir(@FILE)
|
||||
const v3_dir = os.dir(tests_dir)
|
||||
const v3_src = os.join_path(v3_dir, 'v3.v')
|
||||
|
||||
fn build_v3() string {
|
||||
v3_bin := os.join_path(os.temp_dir(), 'v3_option_arg_codegen_test')
|
||||
build := os.execute('${vexe} -o ${v3_bin} ${v3_src}')
|
||||
assert build.exit_code == 0
|
||||
return v3_bin
|
||||
}
|
||||
|
||||
fn run_good(v3_bin string, name string, source string) string {
|
||||
src := os.join_path(os.temp_dir(), 'v3_${name}.v')
|
||||
os.write_file(src, source) or { panic(err) }
|
||||
bin := os.join_path(os.temp_dir(), 'v3_${name}')
|
||||
compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
|
||||
assert compile.exit_code == 0
|
||||
assert !compile.output.contains('C compilation failed')
|
||||
|
||||
run := os.execute(bin)
|
||||
assert run.exit_code == 0
|
||||
return run.output.trim_space()
|
||||
}
|
||||
|
||||
fn test_optional_argument_codegen_wraps_values_and_none() {
|
||||
v3_bin := build_v3()
|
||||
out := run_good(v3_bin, 'option_arg_codegen_input',
|
||||
"struct Info {\n\tvalue int\n}\n\nfn pass_int(x ?int) int {\n\treturn 1\n}\n\nfn pass_info(x ?Info) int {\n\treturn 1\n}\n\nfn main() {\n\tassert pass_int(3) == 1\n\tassert pass_int(none) == 1\n\tassert pass_info(Info{value: 4}) == 1\n\tassert pass_info(none) == 1\n\tprintln('ok')\n}\n")
|
||||
assert out == 'ok'
|
||||
}
|
||||
|
||||
fn test_optional_if_expr_codegen_initializes_optional_temp() {
|
||||
v3_bin := build_v3()
|
||||
out := run_good(v3_bin, 'optional_if_expr_codegen_input',
|
||||
"fn none_int() ?int {\n\treturn none\n}\n\nfn some_int() ?int {\n\treturn 7\n}\n\nfn choose(flag bool) ?int {\n\tvalue := if flag {\n\t\tnone_int()\n\t} else {\n\t\tsome_int()\n\t}\n\treturn value\n}\n\nfn main() {\n\tgood := choose(false) or { 0 }\n\tnone_value := choose(true) or { -1 }\n\tassert good == 7\n\tassert none_value == -1\n\tprintln('ok')\n}\n")
|
||||
assert out == 'ok'
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import os
|
||||
import strings
|
||||
|
||||
fn build_parallel_v3() string {
|
||||
vexe := @VEXE
|
||||
v3_src := os.join_path(@VEXEROOT, 'vlib', 'v3', 'v3.v')
|
||||
v3_bin := os.join_path(os.temp_dir(), 'v3_parallel_cgen_test')
|
||||
build := os.execute('${vexe} -d parallel -o ${v3_bin} ${v3_src}')
|
||||
assert build.exit_code == 0, build.output
|
||||
return v3_bin
|
||||
}
|
||||
|
||||
fn write_parallel_module_init_project(name string) string {
|
||||
project_dir := os.join_path(os.temp_dir(), 'v3_${name}')
|
||||
os.rmdir_all(project_dir) or {}
|
||||
os.mkdir_all(os.join_path(project_dir, 'moda')) or { panic(err) }
|
||||
|
||||
mut main_src := strings.new_builder(64_000)
|
||||
main_src.writeln('module main')
|
||||
main_src.writeln('')
|
||||
main_src.writeln('import moda')
|
||||
main_src.writeln('')
|
||||
for i in 0 .. 1050 {
|
||||
main_src.writeln('fn unused_${i}() int {')
|
||||
main_src.writeln('\treturn ${i}')
|
||||
main_src.writeln('}')
|
||||
main_src.writeln('')
|
||||
}
|
||||
main_src.writeln('fn main() {')
|
||||
main_src.writeln('\tprintln(int_str(moda.value()))')
|
||||
main_src.writeln('}')
|
||||
os.write_file(os.join_path(project_dir, 'main.v'), main_src.str()) or { panic(err) }
|
||||
|
||||
os.write_file(os.join_path(project_dir, 'moda', 'moda.v'), 'module moda
|
||||
|
||||
__global x int
|
||||
|
||||
fn init() {
|
||||
x = 7
|
||||
}
|
||||
|
||||
pub fn value() int {
|
||||
return x
|
||||
}
|
||||
') or {
|
||||
panic(err)
|
||||
}
|
||||
return os.join_path(project_dir, 'main.v')
|
||||
}
|
||||
|
||||
fn test_parallel_cgen_main_emits_module_init_call() {
|
||||
v3_bin := build_parallel_v3()
|
||||
main_path := write_parallel_module_init_project('parallel_module_init')
|
||||
c_out := os.join_path(os.temp_dir(), 'v3_parallel_module_init.c')
|
||||
compile := os.execute('VJOBS=2 ${v3_bin} ${main_path} -o ${c_out}')
|
||||
assert compile.exit_code == 0, compile.output
|
||||
assert compile.output.contains('gen C/write (parallel)'), compile.output
|
||||
c_code := os.read_file(c_out) or { panic(err) }
|
||||
assert c_code.all_after('int main').contains('_vinit();')
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import os
|
||||
|
||||
const vexe = @VEXE
|
||||
const tests_dir = os.dir(@FILE)
|
||||
const v3_dir = os.dir(tests_dir)
|
||||
const v3_src = os.join_path(v3_dir, 'v3.v')
|
||||
|
||||
fn build_v3() string {
|
||||
v3_bin := os.join_path(os.temp_dir(), 'v3_params_struct_codegen_test')
|
||||
build := os.execute('${vexe} -o ${v3_bin} ${v3_src}')
|
||||
assert build.exit_code == 0
|
||||
return v3_bin
|
||||
}
|
||||
|
||||
fn run_good(v3_bin string, name string, source string) string {
|
||||
src := os.join_path(os.temp_dir(), 'v3_${name}.v')
|
||||
os.write_file(src, source) or { panic(err) }
|
||||
bin := os.join_path(os.temp_dir(), 'v3_${name}')
|
||||
compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
|
||||
assert compile.exit_code == 0
|
||||
assert !compile.output.contains('C compilation failed')
|
||||
|
||||
run := os.execute(bin)
|
||||
assert run.exit_code == 0
|
||||
return run.output.trim_space()
|
||||
}
|
||||
|
||||
// A `@[params]` struct argument can be supplied as trailing `key: value` call args.
|
||||
// Regression: the args used to be dropped, generating `make(1, )` (empty arg, trailing comma).
|
||||
fn test_params_struct_call_args_codegen() {
|
||||
v3_bin := build_v3()
|
||||
source := "@[params]\nstruct Cfg {\n\ta int\n\tb string\n\tc bool\n}\n\nfn make(x int, cfg Cfg) string {\n\treturn '\${x}|\${cfg.a}|\${cfg.b}|\${cfg.c}'\n}\n\nfn main() {\n\tprintln(make(1, a: 10, b: 'hi', c: true))\n\tprintln(make(2, b: 'yo'))\n\tprintln(make(3, Cfg{a: 5, b: 'z', c: true}))\n}\n"
|
||||
out := run_good(v3_bin, 'params_struct_codegen_input', source)
|
||||
assert out == '1|10|hi|true\n2|0|yo|false\n3|5|z|true'
|
||||
}
|
||||
|
||||
// The standard-library call that originally exposed the bug:
|
||||
// `strconv.atof64(s, allow_extra_chars: true)` reachable via `string.f64()`.
|
||||
// Previously this generated `strconv__atof64(s, )` (dropped param, trailing comma),
|
||||
// which failed C compilation. We only assert it compiles and runs here; atof64's
|
||||
// numeric accuracy is covered elsewhere.
|
||||
fn test_string_f64_params_struct_codegen() {
|
||||
v3_bin := build_v3()
|
||||
source := "fn main() {\n\tprintln('\${'3.0'.f64() > 0.0}')\n\tprintln('\${'2.0'.f32() > 0.0}')\n}\n"
|
||||
out := run_good(v3_bin, 'string_f64_params_input', source)
|
||||
assert out == 'true\ntrue'
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import os
|
||||
import v3.flat
|
||||
import v3.parser
|
||||
import v3.pref
|
||||
|
||||
fn parse_parser_regression_source(name string, source string) &flat.FlatAst {
|
||||
src := os.join_path(os.temp_dir(), 'v3_${name}.v')
|
||||
os.write_file(src, source) or { panic(err) }
|
||||
mut prefs := pref.new_preferences()
|
||||
mut p := parser.Parser.new(prefs)
|
||||
p.parse_into(src)
|
||||
return p.a
|
||||
}
|
||||
|
||||
fn interface_method_param_types(a &flat.FlatAst, iface string, method string) []string {
|
||||
for node in a.nodes {
|
||||
if node.kind != .interface_decl || node.value != iface {
|
||||
continue
|
||||
}
|
||||
for i in 0 .. node.children_count {
|
||||
field := a.child_node(&node, i)
|
||||
if field.kind != .interface_field || field.value != method {
|
||||
continue
|
||||
}
|
||||
mut params := []string{}
|
||||
for j in 0 .. field.children_count {
|
||||
param := a.child_node(field, j)
|
||||
if param.kind == .param {
|
||||
params << param.typ
|
||||
}
|
||||
}
|
||||
return params
|
||||
}
|
||||
}
|
||||
return []string{}
|
||||
}
|
||||
|
||||
fn test_interface_method_generic_type_only_param_is_not_parsed_as_name() {
|
||||
a := parse_parser_regression_source('interface_generic_param',
|
||||
'const max_len = 16\nstruct Result[T] {}\nstruct Node {}\n\ninterface Sink {\n\tput(Result[int])\n\tappend(values []int)\n\tvisit(node &Node)\n\tread(buf [max_len]u8)\n}\n')
|
||||
assert interface_method_param_types(a, 'Sink', 'put') == ['Result[int]']
|
||||
assert interface_method_param_types(a, 'Sink', 'append') == ['[]int']
|
||||
assert interface_method_param_types(a, 'Sink', 'visit') == ['&Node']
|
||||
assert interface_method_param_types(a, 'Sink', 'read') == ['[max_len]u8']
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import os
|
||||
|
||||
const vexe = @VEXE
|
||||
const tests_dir = os.dir(@FILE)
|
||||
const v3_dir = os.dir(tests_dir)
|
||||
const v3_src = os.join_path(v3_dir, 'v3.v')
|
||||
const hello_src = os.join_path(tests_dir, 'hello.v')
|
||||
|
||||
fn test_prod_flag_before_input_uses_optimized_c_compile() {
|
||||
v3_bin := os.join_path(os.temp_dir(), 'v3_prod_flag_test')
|
||||
build := os.execute('${vexe} -o ${v3_bin} ${v3_src}')
|
||||
assert build.exit_code == 0, build.output
|
||||
|
||||
out_bin := os.join_path(os.temp_dir(), 'v3_prod_hello')
|
||||
compile := os.execute('${v3_bin} -prod ${hello_src} -o ${out_bin}')
|
||||
assert compile.exit_code == 0, compile.output
|
||||
assert compile.output.contains('cc -std=gnu11 -O2')
|
||||
assert !compile.output.contains('tcc.exe')
|
||||
|
||||
run := os.execute(out_bin)
|
||||
assert run.exit_code == 0, run.output
|
||||
assert run.output.trim_space() == 'hello world'
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import os
|
||||
import v3.flat
|
||||
import v3.parser
|
||||
import v3.pref
|
||||
import v3.ssa
|
||||
import v3.types
|
||||
|
||||
fn parse_checked(name string, source string) (&flat.FlatAst, &types.TypeChecker) {
|
||||
src := os.join_path(os.temp_dir(), 'v3_ssa_buildopts_${name}.v')
|
||||
os.write_file(src, source) or { panic(err) }
|
||||
prefs := pref.new_preferences()
|
||||
mut p := parser.Parser.new(prefs)
|
||||
mut a := p.parse_file(src)
|
||||
mut tc := types.TypeChecker.new(a)
|
||||
tc.collect(a)
|
||||
tc.annotate_types()
|
||||
assert tc.errors.len == 0
|
||||
return a, &tc
|
||||
}
|
||||
|
||||
fn func_named(m &ssa.Module, name string) ssa.Function {
|
||||
for f in m.funcs {
|
||||
if f.name == name {
|
||||
return f
|
||||
}
|
||||
}
|
||||
assert false, 'function ${name} not found'
|
||||
return ssa.Function{}
|
||||
}
|
||||
|
||||
const prog = 'fn helper() int {\n\treturn 41\n}\n\nfn other() int {\n\treturn 7\n}\n\nfn main() {\n\t_ := helper() + other()\n}\n'
|
||||
|
||||
fn test_module_name_is_set() {
|
||||
a, tc := parse_checked('modname', prog)
|
||||
m := ssa.build_with_used(a, map[string]bool{}, tc)
|
||||
assert m.name == 'main'
|
||||
}
|
||||
|
||||
fn test_default_build_materializes_bodies() {
|
||||
a, tc := parse_checked('default', prog)
|
||||
m := ssa.build_with_used(a, map[string]bool{}, tc)
|
||||
// helper has a real body (blocks) and is not a prototype.
|
||||
helper := func_named(m, 'helper')
|
||||
assert helper.blocks.len > 0
|
||||
assert !helper.is_prototype
|
||||
}
|
||||
|
||||
fn test_skip_fn_bodies_marks_prototypes() {
|
||||
a, tc := parse_checked('skipbodies', prog)
|
||||
m := ssa.build_with_options(a, map[string]bool{}, tc, ssa.BuildOptions{
|
||||
skip_fn_bodies: true
|
||||
})
|
||||
// User functions are registered but bodyless prototypes.
|
||||
helper := func_named(m, 'helper')
|
||||
assert helper.blocks.len == 0
|
||||
assert helper.is_prototype
|
||||
other := func_named(m, 'other')
|
||||
assert other.is_prototype
|
||||
}
|
||||
|
||||
fn test_hot_fn_builds_only_target() {
|
||||
a, tc := parse_checked('hotfn', prog)
|
||||
m := ssa.build_with_options(a, map[string]bool{}, tc, ssa.BuildOptions{
|
||||
hot_fn: 'helper'
|
||||
})
|
||||
helper := func_named(m, 'helper')
|
||||
assert helper.blocks.len > 0 // hot target built
|
||||
assert !helper.is_prototype
|
||||
other := func_named(m, 'other')
|
||||
assert other.blocks.len == 0 // non-target stubbed
|
||||
assert other.is_prototype
|
||||
}
|
||||
|
||||
fn test_c_externs_are_external_prototypes() {
|
||||
a, tc := parse_checked('externs', prog)
|
||||
m := ssa.build_with_used(a, map[string]bool{}, tc)
|
||||
mut found_extern := false
|
||||
for f in m.funcs {
|
||||
if f.is_c_extern {
|
||||
found_extern = true
|
||||
assert f.is_prototype
|
||||
assert f.linkage == .external
|
||||
}
|
||||
}
|
||||
assert found_extern
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
import os
|
||||
import v3.flat
|
||||
import v3.parser
|
||||
import v3.pref
|
||||
import v3.ssa
|
||||
import v3.types
|
||||
|
||||
fn parse_checked_source(name string, source string) (&flat.FlatAst, &types.TypeChecker) {
|
||||
src := os.join_path(os.temp_dir(), 'v3_ssa_builder_${name}.v')
|
||||
os.write_file(src, source) or { panic(err) }
|
||||
prefs := pref.new_preferences()
|
||||
mut p := parser.Parser.new(prefs)
|
||||
mut a := p.parse_file(src)
|
||||
mut tc := types.TypeChecker.new(a)
|
||||
tc.collect(a)
|
||||
tc.annotate_types()
|
||||
assert tc.errors.len == 0
|
||||
return a, &tc
|
||||
}
|
||||
|
||||
fn build_source(name string, source string) &ssa.Module {
|
||||
return build_source_with_used(name, source, map[string]bool{})
|
||||
}
|
||||
|
||||
fn build_source_with_used(name string, source string, used_fns map[string]bool) &ssa.Module {
|
||||
a, tc := parse_checked_source(name, source)
|
||||
return ssa.build_with_used(a, used_fns, tc)
|
||||
}
|
||||
|
||||
fn find_func(m &ssa.Module, name string) ssa.Function {
|
||||
for f in m.funcs {
|
||||
if f.name == name {
|
||||
return f
|
||||
}
|
||||
}
|
||||
assert false
|
||||
return ssa.Function{}
|
||||
}
|
||||
|
||||
fn func_instrs(m &ssa.Module, name string) []ssa.Instruction {
|
||||
f := find_func(m, name)
|
||||
mut instrs := []ssa.Instruction{}
|
||||
for block_id in f.blocks {
|
||||
for val_id in m.blocks[block_id].instrs {
|
||||
val := m.values[val_id]
|
||||
if val.kind == .instruction {
|
||||
instrs << m.instrs[val.index]
|
||||
}
|
||||
}
|
||||
}
|
||||
return instrs
|
||||
}
|
||||
|
||||
fn ret_operand(m &ssa.Module, name string) ssa.ValueID {
|
||||
for instr in func_instrs(m, name) {
|
||||
if instr.op == .ret && instr.operands.len == 1 {
|
||||
return instr.operands[0]
|
||||
}
|
||||
}
|
||||
assert false
|
||||
return ssa.ValueID(0)
|
||||
}
|
||||
|
||||
fn has_call_to(m &ssa.Module, fn_name string, callee string) bool {
|
||||
for instr in func_instrs(m, fn_name) {
|
||||
if instr.op != .call || instr.operands.len == 0 {
|
||||
continue
|
||||
}
|
||||
fn_ref := m.values[instr.operands[0]]
|
||||
if fn_ref.kind == .func_ref && fn_ref.name == callee {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fn has_instr_op(m &ssa.Module, fn_name string, op ssa.OpCode) bool {
|
||||
for instr in func_instrs(m, fn_name) {
|
||||
if instr.op == op {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fn has_alloca_len_const(m &ssa.Module, fn_name string, len string) bool {
|
||||
for instr in func_instrs(m, fn_name) {
|
||||
if instr.op != .alloca || instr.operands.len == 0 {
|
||||
continue
|
||||
}
|
||||
len_value := m.values[instr.operands[0]]
|
||||
if len_value.kind == .constant && len_value.name == len {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fn test_if_expression_builds_phi() {
|
||||
m := build_source('if_phi', '
|
||||
fn pick(flag bool) int {
|
||||
return if flag {
|
||||
1
|
||||
} else {
|
||||
2
|
||||
}
|
||||
}
|
||||
')
|
||||
mut found_phi := false
|
||||
for instr in func_instrs(m, 'pick') {
|
||||
if instr.op == .phi {
|
||||
found_phi = true
|
||||
assert instr.operands.len == 4
|
||||
}
|
||||
}
|
||||
assert found_phi
|
||||
}
|
||||
|
||||
fn test_match_expression_builds_phi() {
|
||||
m := build_source('match_phi', '
|
||||
fn pick(x int) int {
|
||||
return match x {
|
||||
1 {
|
||||
10
|
||||
}
|
||||
2, 3 {
|
||||
20
|
||||
}
|
||||
else {
|
||||
30
|
||||
}
|
||||
}
|
||||
}
|
||||
')
|
||||
mut found_phi := false
|
||||
for instr in func_instrs(m, 'pick') {
|
||||
if instr.op == .phi {
|
||||
found_phi = true
|
||||
assert instr.operands.len == 6
|
||||
}
|
||||
}
|
||||
assert found_phi
|
||||
}
|
||||
|
||||
fn test_string_infix_lowers_to_runtime_calls() {
|
||||
m := build_source('string_infix', '
|
||||
fn join(a string, b string) string {
|
||||
return a + b
|
||||
}
|
||||
|
||||
fn same(a string, b string) bool {
|
||||
return a == b
|
||||
}
|
||||
')
|
||||
assert has_call_to(m, 'join', 'string__plus')
|
||||
assert has_call_to(m, 'same', 'string__eq')
|
||||
}
|
||||
|
||||
fn test_c_fn_decl_registers_extern_signature() {
|
||||
m := build_source('c_fn_decl', '
|
||||
fn C.abs(x int) int
|
||||
|
||||
fn main() {
|
||||
_ := C.abs(-3)
|
||||
}
|
||||
')
|
||||
f := find_func(m, 'C.abs')
|
||||
assert f.is_c_extern
|
||||
ret_type := m.type_store.types[f.typ]
|
||||
assert ret_type.kind == .int_t
|
||||
assert ret_type.width == 32
|
||||
}
|
||||
|
||||
fn test_function_parameter_call_lowers_to_call_indirect() {
|
||||
m := build_source('call_indirect', '
|
||||
fn add(a int, b int) int {
|
||||
return a + b
|
||||
}
|
||||
|
||||
fn apply(f fn (int, int) int, x int, y int) int {
|
||||
return f(x, y)
|
||||
}
|
||||
')
|
||||
mut found_indirect := false
|
||||
for instr in func_instrs(m, 'apply') {
|
||||
if instr.op == .call_indirect {
|
||||
found_indirect = true
|
||||
assert instr.operands.len == 3
|
||||
}
|
||||
}
|
||||
assert found_indirect
|
||||
}
|
||||
|
||||
fn test_label_and_goto_lower_to_jump_blocks() {
|
||||
m := build_source('label_goto', '
|
||||
fn jumpy() int {
|
||||
mut x := 0
|
||||
goto done
|
||||
x = 1
|
||||
done:
|
||||
return x
|
||||
}
|
||||
')
|
||||
mut found_jump := false
|
||||
for instr in func_instrs(m, 'jumpy') {
|
||||
if instr.op == .jmp {
|
||||
found_jump = true
|
||||
}
|
||||
}
|
||||
assert found_jump
|
||||
}
|
||||
|
||||
fn test_const_identifier_lowers_to_const_value() {
|
||||
m := build_source('const_ident', '
|
||||
const answer = 42
|
||||
|
||||
fn get_answer() int {
|
||||
return answer
|
||||
}
|
||||
')
|
||||
ret := m.values[ret_operand(m, 'get_answer')]
|
||||
assert ret.kind == .constant
|
||||
assert ret.name == '42'
|
||||
}
|
||||
|
||||
fn test_enum_selector_lowers_to_const_value() {
|
||||
m := build_source('enum_selector', '
|
||||
enum Color {
|
||||
red
|
||||
green = 5
|
||||
blue
|
||||
}
|
||||
|
||||
fn get_blue() int {
|
||||
return int(Color.blue)
|
||||
}
|
||||
')
|
||||
ret_id := ret_operand(m, 'get_blue')
|
||||
ret := m.values[ret_id]
|
||||
if ret.kind == .constant {
|
||||
assert ret.name == '6'
|
||||
} else {
|
||||
assert ret.kind == .instruction
|
||||
instr := m.instrs[ret.index]
|
||||
assert instr.operands.len > 0
|
||||
cast_input := m.values[instr.operands[0]]
|
||||
assert cast_input.kind == .constant
|
||||
assert cast_input.name == '6'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_float_literal_uses_float_type() {
|
||||
m := build_source('float_literal', '
|
||||
fn get_float() f64 {
|
||||
return 1.25
|
||||
}
|
||||
')
|
||||
ret := m.values[ret_operand(m, 'get_float')]
|
||||
typ := m.type_store.types[ret.typ]
|
||||
assert typ.kind == .float_t
|
||||
assert typ.width == 64
|
||||
}
|
||||
|
||||
fn test_integer_casts_emit_conversion_ops() {
|
||||
m := build_source('int_casts', '
|
||||
fn narrow(x i64) u8 {
|
||||
return u8(x)
|
||||
}
|
||||
|
||||
fn widen(x u8) i64 {
|
||||
return i64(x)
|
||||
}
|
||||
|
||||
fn widen_signed(x int) i64 {
|
||||
return i64(x)
|
||||
}
|
||||
')
|
||||
mut found_trunc := false
|
||||
for instr in func_instrs(m, 'narrow') {
|
||||
if instr.op == .trunc {
|
||||
found_trunc = true
|
||||
assert m.type_store.types[instr.typ].width == 8
|
||||
}
|
||||
}
|
||||
assert found_trunc
|
||||
|
||||
mut found_zext := false
|
||||
for instr in func_instrs(m, 'widen') {
|
||||
if instr.op == .zext {
|
||||
found_zext = true
|
||||
assert m.type_store.types[instr.typ].width == 64
|
||||
}
|
||||
}
|
||||
assert found_zext
|
||||
assert has_instr_op(m, 'widen_signed', .sext)
|
||||
}
|
||||
|
||||
fn test_unsigned_integer_infix_uses_unsigned_ops() {
|
||||
m := build_source('unsigned_ops', '
|
||||
fn div_u(x u32, y u32) u32 {
|
||||
return x / y
|
||||
}
|
||||
|
||||
fn rem_u(x u32, y u32) u32 {
|
||||
return x % y
|
||||
}
|
||||
|
||||
fn cmp_u(x u32, y u32) bool {
|
||||
return x < y
|
||||
}
|
||||
|
||||
fn cmp_s(x int, y int) bool {
|
||||
return x < y
|
||||
}
|
||||
')
|
||||
assert has_instr_op(m, 'div_u', .udiv)
|
||||
assert has_instr_op(m, 'rem_u', .urem)
|
||||
assert has_instr_op(m, 'cmp_u', .ult)
|
||||
assert has_instr_op(m, 'cmp_s', .lt)
|
||||
assert !has_instr_op(m, 'cmp_s', .ult)
|
||||
}
|
||||
|
||||
fn test_fixed_array_const_length_is_resolved() {
|
||||
mut used_fns := map[string]bool{}
|
||||
used_fns['make_array'] = true
|
||||
used_fns['main.make_array'] = true
|
||||
m := build_source_with_used('fixed_array_const_len', '
|
||||
const fixed_len = 4
|
||||
|
||||
fn make_array() int {
|
||||
mut values := [fixed_len]int{}
|
||||
values[0] = 7
|
||||
return values[0]
|
||||
}
|
||||
',
|
||||
used_fns)
|
||||
assert has_alloca_len_const(m, 'make_array', '4')
|
||||
}
|
||||
|
||||
fn test_string_range_does_not_lower_to_array_slice() {
|
||||
m := build_source('string_range', '
|
||||
fn cut(s string) string {
|
||||
return s[1..3]
|
||||
}
|
||||
')
|
||||
assert !has_call_to(m, 'cut', 'array_slice')
|
||||
}
|
||||
|
||||
fn test_array_first_last_load_elements() {
|
||||
m := build_source('array_first_last', '
|
||||
fn first_value(values []int) int {
|
||||
return values.first()
|
||||
}
|
||||
|
||||
fn last_value(values []int) int {
|
||||
return values.last()
|
||||
}
|
||||
')
|
||||
assert has_call_to(m, 'first_value', 'array_get')
|
||||
assert has_call_to(m, 'last_value', 'array_get')
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import v3.ssa
|
||||
import v3.ssa.optimize
|
||||
|
||||
fn test_optimize_verifies_and_preserves_valid_ssa() {
|
||||
mut m := ssa.Module.new()
|
||||
i64_type := m.type_store.get_int(64)
|
||||
func_id := m.new_function('main', i64_type)
|
||||
entry := m.add_block(func_id, 'entry')
|
||||
two := m.get_or_add_const(i64_type, '2')
|
||||
three := m.get_or_add_const(i64_type, '3')
|
||||
add := m.add_instr(.add, entry, i64_type, [two, three])
|
||||
m.add_instr(.ret, entry, ssa.TypeID(0), [add])
|
||||
|
||||
optimize.optimize(mut m)
|
||||
|
||||
assert m.funcs[func_id].blocks == [entry]
|
||||
assert m.blocks[entry].instrs.len == 1
|
||||
ret_id := m.blocks[entry].instrs[0]
|
||||
ret := m.instrs[m.values[ret_id].index]
|
||||
assert ret.op == .ret
|
||||
assert ret.operands.len == 1
|
||||
ret_operand := m.values[ret.operands[0]]
|
||||
assert ret_operand.kind == .constant
|
||||
assert ret_operand.name == '5'
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
import v3.ssa
|
||||
import v3.ssa.optimize
|
||||
|
||||
// --- helpers ---------------------------------------------------------------
|
||||
|
||||
fn count_op(m &ssa.Module, func_id int, op ssa.OpCode) int {
|
||||
mut n := 0
|
||||
for blk_id in m.funcs[func_id].blocks {
|
||||
for val_id in m.blocks[blk_id].instrs {
|
||||
val := m.values[val_id]
|
||||
if val.kind == .instruction && m.instrs[val.index].op == op {
|
||||
n++
|
||||
}
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// --- TypeStore: arrays, tuples, unsigned ----------------------------------
|
||||
|
||||
fn test_type_store_arrays_tuples_unsigned() {
|
||||
mut ts := ssa.TypeStore.new()
|
||||
s32 := ts.get_int(32)
|
||||
un32 := ts.get_uint(32)
|
||||
// Signed and unsigned of the same width must be distinct types.
|
||||
assert s32 != un32
|
||||
assert !ts.types[s32].is_unsigned
|
||||
assert ts.types[un32].is_unsigned
|
||||
|
||||
arr := ts.get_array(s32, 4)
|
||||
arr2 := ts.get_array(s32, 4)
|
||||
assert arr == arr2 // cached
|
||||
assert ts.types[arr].kind == .array_t
|
||||
assert ts.types[arr].len == 4
|
||||
assert ts.types[arr].elem_type == s32
|
||||
|
||||
tup := ts.get_tuple([s32, un32])
|
||||
assert ts.types[tup].kind == .struct_t
|
||||
assert ts.types[tup].fields == [s32, un32]
|
||||
}
|
||||
|
||||
fn test_array_type_sizing() {
|
||||
mut m := ssa.Module.new()
|
||||
s32 := m.type_store.get_int(32)
|
||||
arr := m.type_store.get_array(s32, 5)
|
||||
// Fixed array of 5 x i32 = 20 bytes, aligned to element alignment (4).
|
||||
assert m.type_size(arr) == 20
|
||||
assert m.type_align(arr) == 4
|
||||
}
|
||||
|
||||
// --- algebraic simplification ---------------------------------------------
|
||||
|
||||
fn test_algebraic_add_zero() {
|
||||
mut m := ssa.Module.new()
|
||||
i64t := m.type_store.get_int(64)
|
||||
func_id := m.new_function('add_zero', i64t)
|
||||
x := m.add_value(.argument, i64t, 'x', 0)
|
||||
m.func_add_param(func_id, x)
|
||||
entry := m.add_block(func_id, 'entry')
|
||||
zero := m.get_or_add_const(i64t, '0')
|
||||
r := m.add_instr(.add, entry, i64t, [x, zero])
|
||||
m.add_instr(.ret, entry, ssa.TypeID(0), [r])
|
||||
|
||||
optimize.optimize(mut m)
|
||||
|
||||
// x + 0 -> x, so the add is gone and ret returns the argument directly.
|
||||
assert count_op(m, func_id, .add) == 0
|
||||
ret_id := m.blocks[m.funcs[func_id].blocks[0]].instrs.last()
|
||||
ret := m.instrs[m.values[ret_id].index]
|
||||
assert m.values[ret.operands[0]].kind == .argument
|
||||
}
|
||||
|
||||
fn test_algebraic_mul_two_to_shift() {
|
||||
mut m := ssa.Module.new()
|
||||
i64t := m.type_store.get_int(64)
|
||||
func_id := m.new_function('mul2', i64t)
|
||||
x := m.add_value(.argument, i64t, 'x', 0)
|
||||
m.func_add_param(func_id, x)
|
||||
entry := m.add_block(func_id, 'entry')
|
||||
two := m.get_or_add_const(i64t, '2')
|
||||
r := m.add_instr(.mul, entry, i64t, [x, two])
|
||||
m.add_instr(.ret, entry, ssa.TypeID(0), [r])
|
||||
|
||||
optimize.optimize(mut m)
|
||||
|
||||
// x * 2 -> x << 1
|
||||
assert count_op(m, func_id, .mul) == 0
|
||||
assert count_op(m, func_id, .shl) == 1
|
||||
}
|
||||
|
||||
fn test_algebraic_sub_self_is_zero() {
|
||||
mut m := ssa.Module.new()
|
||||
i64t := m.type_store.get_int(64)
|
||||
func_id := m.new_function('sub_self', i64t)
|
||||
x := m.add_value(.argument, i64t, 'x', 0)
|
||||
m.func_add_param(func_id, x)
|
||||
entry := m.add_block(func_id, 'entry')
|
||||
r := m.add_instr(.sub, entry, i64t, [x, x])
|
||||
m.add_instr(.ret, entry, ssa.TypeID(0), [r])
|
||||
|
||||
optimize.optimize(mut m)
|
||||
|
||||
assert count_op(m, func_id, .sub) == 0
|
||||
ret_id := m.blocks[m.funcs[func_id].blocks[0]].instrs.last()
|
||||
ret := m.instrs[m.values[ret_id].index]
|
||||
ret_operand := m.values[ret.operands[0]]
|
||||
assert ret_operand.kind == .constant
|
||||
assert ret_operand.name == '0'
|
||||
}
|
||||
|
||||
// --- DCE side-effect preservation -----------------------------------------
|
||||
|
||||
fn test_dce_preserves_side_effecting_ops() {
|
||||
mut m := ssa.Module.new()
|
||||
i64t := m.type_store.get_int(64)
|
||||
func_id := m.new_function('side_fx', i64t)
|
||||
entry := m.add_block(func_id, 'entry')
|
||||
// A pure dead add that should be removed.
|
||||
a := m.get_or_add_const(i64t, '7')
|
||||
b := m.get_or_add_const(i64t, '9')
|
||||
m.add_instr(.add, entry, i64t, [a, b])
|
||||
// Side-effecting ops with no uses that must be preserved.
|
||||
m.add_instr(.fence, entry, ssa.TypeID(0), [])
|
||||
m.add_instr(.cmpxchg, entry, i64t, [a])
|
||||
m.add_instr(.ret, entry, ssa.TypeID(0), [])
|
||||
|
||||
optimize.optimize(mut m)
|
||||
|
||||
assert count_op(m, func_id, .add) == 0 // dead pure op removed
|
||||
assert count_op(m, func_id, .fence) == 1 // preserved
|
||||
assert count_op(m, func_id, .cmpxchg) == 1 // preserved
|
||||
}
|
||||
|
||||
// --- structured verifier ---------------------------------------------------
|
||||
|
||||
fn test_structured_verifier_accepts_valid_module() {
|
||||
mut m := ssa.Module.new()
|
||||
i64t := m.type_store.get_int(64)
|
||||
func_id := m.new_function('valid', i64t)
|
||||
entry := m.add_block(func_id, 'entry')
|
||||
c := m.get_or_add_const(i64t, '1')
|
||||
m.add_instr(.ret, entry, ssa.TypeID(0), [c])
|
||||
|
||||
optimize.optimize(mut m)
|
||||
errors := optimize.verify(m)
|
||||
// No structural errors on a well-formed module.
|
||||
for e in errors {
|
||||
assert !e.msg.contains('invalid')
|
||||
assert !e.msg.contains('not in its')
|
||||
}
|
||||
}
|
||||
|
||||
fn test_structured_verifier_accepts_prototype_declaration() {
|
||||
mut m := ssa.Module.new()
|
||||
i64t := m.type_store.get_int(64)
|
||||
// A prototype/extern function with no body must be accepted (not fatal).
|
||||
proto := m.new_function('extern_decl', i64t)
|
||||
m.func_set_prototype(proto, true)
|
||||
m.func_set_c_extern(proto, true)
|
||||
// Should not panic.
|
||||
optimize.verify_and_panic(m, 'prototype test')
|
||||
assert true
|
||||
}
|
||||
|
||||
// --- mem2reg + phi elimination (IR level) ---------------------------------
|
||||
|
||||
fn test_mem2reg_promotes_diamond_slot() {
|
||||
mut m := ssa.Module.new()
|
||||
i64t := m.type_store.get_int(64)
|
||||
i1 := m.type_store.get_int(1)
|
||||
ptr_i64 := m.type_store.get_ptr(i64t)
|
||||
func_id := m.new_function('diamond', i64t)
|
||||
cond := m.add_value(.argument, i1, 'cond', 0)
|
||||
m.func_add_param(func_id, cond)
|
||||
|
||||
entry := m.add_block(func_id, 'entry')
|
||||
then_blk := m.add_block(func_id, 'then')
|
||||
else_blk := m.add_block(func_id, 'else')
|
||||
merge := m.add_block(func_id, 'merge')
|
||||
|
||||
slot := m.add_instr(.alloca, entry, ptr_i64, [])
|
||||
m.add_instr(.br, entry, ssa.TypeID(0), [cond, ssa.ValueID(then_blk), ssa.ValueID(else_blk)])
|
||||
|
||||
ten := m.get_or_add_const(i64t, '10')
|
||||
m.add_instr(.store, then_blk, ssa.TypeID(0), [ten, slot])
|
||||
m.add_instr(.jmp, then_blk, ssa.TypeID(0), [ssa.ValueID(merge)])
|
||||
|
||||
twenty := m.get_or_add_const(i64t, '20')
|
||||
m.add_instr(.store, else_blk, ssa.TypeID(0), [twenty, slot])
|
||||
m.add_instr(.jmp, else_blk, ssa.TypeID(0), [ssa.ValueID(merge)])
|
||||
|
||||
loaded := m.add_instr(.load, merge, i64t, [slot])
|
||||
m.add_instr(.ret, merge, ssa.TypeID(0), [loaded])
|
||||
|
||||
optimize.optimize_with_options(mut m, optimize.OptimizeOptions{
|
||||
mem2reg: true
|
||||
eliminate_phis: true
|
||||
})
|
||||
|
||||
// The scalar slot is fully promoted: no alloca/load/store remain.
|
||||
assert count_op(m, func_id, .alloca) == 0
|
||||
assert count_op(m, func_id, .load) == 0
|
||||
assert count_op(m, func_id, .store) == 0
|
||||
// Phi was lowered to assign copies in the predecessor blocks.
|
||||
assert count_op(m, func_id, .assign) >= 1
|
||||
// And no phi survives after elimination.
|
||||
assert count_op(m, func_id, .phi) == 0
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import v3.ssa
|
||||
import v3.ssa.optimize
|
||||
|
||||
// Build a function body inside a worker module and merge it back, exercising the
|
||||
// raw-block-id operand remapping (branch/phi block operands must shift by the
|
||||
// block offset, value operands by the value offset).
|
||||
fn test_worker_clone_merge_remaps_block_and_value_operands() {
|
||||
mut m := ssa.Module.new()
|
||||
i64t := m.type_store.get_int(64)
|
||||
// A pre-existing (seed) constant and function registered serially.
|
||||
_ := m.get_or_add_const(i64t, '111')
|
||||
fidx := m.new_function('compute', i64t)
|
||||
|
||||
// Clone a worker and snapshot the seed sizes.
|
||||
mut w := m.new_worker_module()
|
||||
seed_values := m.values.len
|
||||
seed_instrs := m.instrs.len
|
||||
seed_blocks := m.blocks.len
|
||||
seed_types := m.type_store.types.len
|
||||
seed_funcs := m.funcs.len
|
||||
|
||||
// Build a diamond + merge phi entirely inside the worker.
|
||||
entry := w.add_block(fidx, 'entry')
|
||||
then_b := w.add_block(fidx, 'then')
|
||||
else_b := w.add_block(fidx, 'else')
|
||||
merge := w.add_block(fidx, 'merge')
|
||||
|
||||
cond := w.get_or_add_const(w.type_store.get_int(1), '1')
|
||||
w.add_instr(.br, entry, ssa.TypeID(0), [cond, ssa.ValueID(then_b), ssa.ValueID(else_b)])
|
||||
|
||||
ten := w.get_or_add_const(i64t, '10')
|
||||
w.add_instr(.jmp, then_b, ssa.TypeID(0), [ssa.ValueID(merge)])
|
||||
twenty := w.get_or_add_const(i64t, '20')
|
||||
w.add_instr(.jmp, else_b, ssa.TypeID(0), [ssa.ValueID(merge)])
|
||||
|
||||
// phi: [ten, then_b, twenty, else_b] (odd operands are raw block ids)
|
||||
phi := w.add_instr(.phi, merge, i64t, [ten, ssa.ValueID(then_b), twenty, ssa.ValueID(else_b)])
|
||||
w.add_instr(.ret, merge, ssa.TypeID(0), [phi])
|
||||
|
||||
fd := ssa.FuncSSAData{
|
||||
func_idx: fidx
|
||||
blocks: w.funcs[fidx].blocks
|
||||
params: w.funcs[fidx].params
|
||||
}
|
||||
|
||||
m.merge_worker_module(w, [fd], seed_values, seed_instrs, seed_blocks, seed_types, seed_funcs)
|
||||
|
||||
// The function now has its 4 blocks, all parented correctly.
|
||||
assert m.funcs[fidx].blocks.len == 4
|
||||
for blk_id in m.funcs[fidx].blocks {
|
||||
assert blk_id >= 0 && blk_id < m.blocks.len
|
||||
assert m.blocks[blk_id].parent == fidx
|
||||
}
|
||||
|
||||
// The merged branch must target real blocks inside the function (this fails if
|
||||
// block operands were remapped as values).
|
||||
entry_id := m.funcs[fidx].blocks[0]
|
||||
term_id := m.blocks[entry_id].instrs.last()
|
||||
term := m.instrs[m.values[term_id].index]
|
||||
assert term.op == .br
|
||||
for oi in [1, 2] {
|
||||
tgt := int(term.operands[oi])
|
||||
assert tgt >= 0 && tgt < m.blocks.len
|
||||
assert tgt in m.funcs[fidx].blocks
|
||||
}
|
||||
|
||||
// The whole thing must verify and optimize cleanly (optimize() panics on a
|
||||
// broken CFG / dangling phi predecessor, so reaching the asserts proves the
|
||||
// remapping is consistent).
|
||||
optimize.optimize_with_options(mut m, optimize.OptimizeOptions{
|
||||
mem2reg: false
|
||||
eliminate_phis: true
|
||||
})
|
||||
errors := optimize.verify(m)
|
||||
for e in errors {
|
||||
assert !e.msg.contains('invalid')
|
||||
assert !e.msg.contains('outside function')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fn exclaim(s string) string {
|
||||
return s + '!'
|
||||
}
|
||||
|
||||
fn main() {
|
||||
a := 'hello'
|
||||
b := 'world'
|
||||
println(a + ' ' + b)
|
||||
println(exclaim('v3 works'))
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import os
|
||||
import v3.flat
|
||||
import v3.parser
|
||||
import v3.pref
|
||||
import v3.transform
|
||||
import v3.types
|
||||
|
||||
fn parse_and_transform(source string) &flat.FlatAst {
|
||||
src := os.join_path(os.temp_dir(), 'v3_struct_default_transform_test.v')
|
||||
os.write_file(src, source) or { panic(err) }
|
||||
prefs := pref.new_preferences()
|
||||
mut p := parser.Parser.new(prefs)
|
||||
mut a := p.parse_file(src)
|
||||
mut tc := types.TypeChecker.new(a)
|
||||
tc.collect(a)
|
||||
tc.annotate_types()
|
||||
transform.transform(mut a, &tc)
|
||||
return a
|
||||
}
|
||||
|
||||
fn transformed_struct_init(a &flat.FlatAst, fn_name string) flat.Node {
|
||||
for node in a.nodes {
|
||||
if node.kind != .fn_decl || node.value != fn_name {
|
||||
continue
|
||||
}
|
||||
for i in 0 .. node.children_count {
|
||||
stmt := a.child_node(&node, i)
|
||||
if stmt.kind != .decl_assign || stmt.children_count < 2 {
|
||||
continue
|
||||
}
|
||||
rhs := a.child_node(stmt, 1)
|
||||
if rhs.kind == .struct_init {
|
||||
return *rhs
|
||||
}
|
||||
}
|
||||
}
|
||||
assert false
|
||||
return flat.Node{}
|
||||
}
|
||||
|
||||
fn find_field(a &flat.FlatAst, node flat.Node, name string) flat.Node {
|
||||
for i in 0 .. node.children_count {
|
||||
field := a.child_node(&node, i)
|
||||
if field.kind == .field_init && field.value == name {
|
||||
return *field
|
||||
}
|
||||
}
|
||||
assert false
|
||||
return flat.Node{}
|
||||
}
|
||||
|
||||
fn call_name(a &flat.FlatAst, call flat.Node) string {
|
||||
if call.kind != .call || call.children_count == 0 {
|
||||
return ''
|
||||
}
|
||||
return a.child_node(&call, 0).value
|
||||
}
|
||||
|
||||
fn test_transform_fills_missing_struct_defaults() {
|
||||
mut a := parse_and_transform("
|
||||
struct Person {
|
||||
name string = 'v' + '3'
|
||||
age int = 42
|
||||
}
|
||||
|
||||
fn main() {
|
||||
person := Person{age: 7}
|
||||
}
|
||||
")
|
||||
init := transformed_struct_init(a, 'main')
|
||||
assert init.value == 'Person'
|
||||
assert init.children_count == 2
|
||||
|
||||
name_field := find_field(a, init, 'name')
|
||||
name_value := a.child_node(&name_field, 0)
|
||||
assert name_value.kind == .string_literal
|
||||
assert name_value.value == 'v3'
|
||||
|
||||
age_field := find_field(a, init, 'age')
|
||||
age_value := a.child_node(&age_field, 0)
|
||||
assert age_value.kind == .int_literal
|
||||
assert age_value.value == '7'
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,128 @@
|
||||
#!/usr/local/bin/v
|
||||
|
||||
import os
|
||||
|
||||
const vexe = @VEXE
|
||||
const v1exe = 'v'
|
||||
const tests_dir = os.dir(@FILE)
|
||||
const v3_dir = os.dir(tests_dir)
|
||||
const test_v = os.join_path(tests_dir, 'test_all_lang_features.v')
|
||||
const v3_src = os.join_path(v3_dir, 'v3.v')
|
||||
|
||||
fn run(cmd string) os.Result {
|
||||
println('> ${cmd}')
|
||||
return os.execute(cmd)
|
||||
}
|
||||
|
||||
fn build_v3() string {
|
||||
v3_bin := os.join_path(os.temp_dir(), 'v3_test_runner')
|
||||
cmd := '${vexe} -o ${v3_bin} ${v3_src}'
|
||||
r := run(cmd)
|
||||
if r.exit_code != 0 {
|
||||
eprintln('FAIL: could not build v3')
|
||||
eprintln(r.output)
|
||||
exit(1)
|
||||
}
|
||||
return v3_bin
|
||||
}
|
||||
|
||||
fn run_v1() string {
|
||||
v1_bin := '${os.temp_dir()}/v1_test'
|
||||
r := run('${v1exe} -enable-globals -o ${v1_bin} ${test_v}')
|
||||
if r.exit_code != 0 {
|
||||
eprintln('FAIL: global v compilation failed')
|
||||
eprintln(r.output)
|
||||
exit(1)
|
||||
}
|
||||
r2 := run(v1_bin)
|
||||
if r2.exit_code != 0 {
|
||||
eprintln('FAIL: global v binary crashed (exit ${r2.exit_code})')
|
||||
exit(1)
|
||||
}
|
||||
return r2.output
|
||||
}
|
||||
|
||||
fn run_v3_c(v3_bin string) string {
|
||||
v3c_bin := '${os.temp_dir()}/v3c_test'
|
||||
r := run('${v3_bin} ${test_v} -b c -o ${v3c_bin}')
|
||||
if r.exit_code != 0 {
|
||||
eprintln('FAIL: v3 C backend compilation failed')
|
||||
eprintln(r.output)
|
||||
exit(1)
|
||||
}
|
||||
r2 := run(v3c_bin)
|
||||
if r2.exit_code != 0 {
|
||||
eprintln('FAIL: v3 C binary crashed (exit ${r2.exit_code})')
|
||||
exit(1)
|
||||
}
|
||||
return r2.output
|
||||
}
|
||||
|
||||
fn run_v3_arm64(v3_bin string) string {
|
||||
v3arm_bin := '${os.temp_dir()}/v3arm_test'
|
||||
r := run('${v3_bin} ${test_v} -b arm64 -o ${v3arm_bin}')
|
||||
if r.exit_code != 0 {
|
||||
eprintln('FAIL: v3 arm64 backend compilation failed')
|
||||
eprintln(r.output)
|
||||
exit(1)
|
||||
}
|
||||
r2 := run(v3arm_bin)
|
||||
if r2.exit_code != 0 {
|
||||
eprintln('FAIL: v3 arm64 binary crashed (exit ${r2.exit_code})')
|
||||
exit(1)
|
||||
}
|
||||
return r2.output
|
||||
}
|
||||
|
||||
// Build v3
|
||||
v3_bin := build_v3()
|
||||
println('built v3: ${v3_bin}')
|
||||
|
||||
// Run all three
|
||||
v1_out := run_v1()
|
||||
println('v1: OK')
|
||||
|
||||
v3c_out := run_v3_c(v3_bin)
|
||||
println('v3 C: OK')
|
||||
|
||||
v3arm_out := run_v3_arm64(v3_bin)
|
||||
println('v3 arm64: OK')
|
||||
|
||||
// Compare
|
||||
mut ok := true
|
||||
|
||||
if v3c_out != v1_out {
|
||||
eprintln('FAIL: v3 C output differs from v1')
|
||||
v1_lines := v1_out.split_into_lines()
|
||||
v3c_lines := v3c_out.split_into_lines()
|
||||
for i in 0 .. v1_lines.len {
|
||||
if i < v3c_lines.len && v1_lines[i] != v3c_lines[i] {
|
||||
eprintln(' line ${i + 1}: v1="${v1_lines[i]}" v3c="${v3c_lines[i]}"')
|
||||
}
|
||||
}
|
||||
if v1_lines.len != v3c_lines.len {
|
||||
eprintln(' v1: ${v1_lines.len} lines, v3c: ${v3c_lines.len} lines')
|
||||
}
|
||||
ok = false
|
||||
}
|
||||
|
||||
if v3arm_out != v1_out {
|
||||
eprintln('FAIL: v3 arm64 output differs from v1')
|
||||
v1_lines := v1_out.split_into_lines()
|
||||
v3arm_lines := v3arm_out.split_into_lines()
|
||||
for i in 0 .. v1_lines.len {
|
||||
if i < v3arm_lines.len && v1_lines[i] != v3arm_lines[i] {
|
||||
eprintln(' line ${i + 1}: v1="${v1_lines[i]}" arm64="${v3arm_lines[i]}"')
|
||||
}
|
||||
}
|
||||
if v1_lines.len != v3arm_lines.len {
|
||||
eprintln(' v1: ${v1_lines.len} lines, arm64: ${v3arm_lines.len} lines')
|
||||
}
|
||||
ok = false
|
||||
}
|
||||
|
||||
if ok {
|
||||
println('=== ALL BACKENDS MATCH (${v1_out.split_into_lines().len} lines) ===')
|
||||
} else {
|
||||
exit(1)
|
||||
}
|
||||
@@ -0,0 +1,494 @@
|
||||
import os
|
||||
import v3.flat
|
||||
import v3.parser
|
||||
import v3.pref
|
||||
import v3.transform
|
||||
import v3.types
|
||||
|
||||
fn parse_transform_source(source string) &flat.FlatAst {
|
||||
src := os.join_path(os.temp_dir(), 'v3_transformer_parity_test.v')
|
||||
return parse_transform_file(src, source)
|
||||
}
|
||||
|
||||
fn parse_transform_file(src string, source string) &flat.FlatAst {
|
||||
os.write_file(src, source) or { panic(err) }
|
||||
prefs := pref.new_preferences()
|
||||
mut p := parser.Parser.new(prefs)
|
||||
mut a := p.parse_file(src)
|
||||
mut tc := types.TypeChecker.new(a)
|
||||
tc.collect(a)
|
||||
tc.annotate_types()
|
||||
transform.transform(mut a, &tc)
|
||||
return a
|
||||
}
|
||||
|
||||
fn find_fn(a &flat.FlatAst, name string) flat.Node {
|
||||
for node in a.nodes {
|
||||
if node.kind == .fn_decl && node.value == name {
|
||||
return node
|
||||
}
|
||||
}
|
||||
assert false
|
||||
return flat.Node{}
|
||||
}
|
||||
|
||||
fn first_decl_rhs(a &flat.FlatAst, fn_name string) flat.Node {
|
||||
f := find_fn(a, fn_name)
|
||||
for i in 0 .. f.children_count {
|
||||
stmt := a.child_node(&f, i)
|
||||
if stmt.kind == .decl_assign && stmt.children_count == 2 {
|
||||
return *a.child_node(stmt, 1)
|
||||
}
|
||||
}
|
||||
assert false
|
||||
return flat.Node{}
|
||||
}
|
||||
|
||||
fn decl_rhs(a &flat.FlatAst, fn_name string, name string) flat.Node {
|
||||
f := find_fn(a, fn_name)
|
||||
for i in 0 .. f.children_count {
|
||||
stmt := a.child_node(&f, i)
|
||||
if stmt.kind == .decl_assign && stmt.children_count == 2 {
|
||||
lhs := a.child_node(stmt, 0)
|
||||
if lhs.kind == .ident && lhs.value == name {
|
||||
return *a.child_node(stmt, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
assert false
|
||||
return flat.Node{}
|
||||
}
|
||||
|
||||
fn first_return_expr(a &flat.FlatAst, fn_name string) flat.Node {
|
||||
f := find_fn(a, fn_name)
|
||||
for i in 0 .. f.children_count {
|
||||
stmt := a.child_node(&f, i)
|
||||
if stmt.kind == .return_stmt && stmt.children_count > 0 {
|
||||
return *a.child_node(stmt, 0)
|
||||
}
|
||||
}
|
||||
assert false
|
||||
return flat.Node{}
|
||||
}
|
||||
|
||||
fn count_kind(a &flat.FlatAst, id flat.NodeId, kind flat.NodeKind) int {
|
||||
if int(id) < 0 {
|
||||
return 0
|
||||
}
|
||||
node := a.nodes[int(id)]
|
||||
mut total := if node.kind == kind { 1 } else { 0 }
|
||||
for i in 0 .. node.children_count {
|
||||
total += count_kind(a, a.child(&node, i), kind)
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
fn count_call_name(a &flat.FlatAst, id flat.NodeId, name string) int {
|
||||
if int(id) < 0 {
|
||||
return 0
|
||||
}
|
||||
node := a.nodes[int(id)]
|
||||
mut total := 0
|
||||
if node.kind == .call && node.children_count > 0 {
|
||||
fn_node := a.child_node(&node, 0)
|
||||
if fn_node.kind == .ident && fn_node.value == name {
|
||||
total++
|
||||
}
|
||||
}
|
||||
for i in 0 .. node.children_count {
|
||||
total += count_call_name(a, a.child(&node, i), name)
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
fn count_infix_op(a &flat.FlatAst, id flat.NodeId, op flat.Op) int {
|
||||
if int(id) < 0 {
|
||||
return 0
|
||||
}
|
||||
node := a.nodes[int(id)]
|
||||
mut total := if node.kind == .infix && node.op == op { 1 } else { 0 }
|
||||
for i in 0 .. node.children_count {
|
||||
total += count_infix_op(a, a.child(&node, i), op)
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
fn count_wide_decl_assigns(a &flat.FlatAst, id flat.NodeId) int {
|
||||
if int(id) < 0 {
|
||||
return 0
|
||||
}
|
||||
node := a.nodes[int(id)]
|
||||
mut total := if node.kind == .decl_assign && node.children_count > 2 { 1 } else { 0 }
|
||||
for i in 0 .. node.children_count {
|
||||
total += count_wide_decl_assigns(a, a.child(&node, i))
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
fn count_wide_assigns(a &flat.FlatAst, id flat.NodeId) int {
|
||||
if int(id) < 0 {
|
||||
return 0
|
||||
}
|
||||
node := a.nodes[int(id)]
|
||||
mut total := if node.kind == .assign && node.children_count > 2 { 1 } else { 0 }
|
||||
for i in 0 .. node.children_count {
|
||||
total += count_wide_assigns(a, a.child(&node, i))
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
fn count_selector_value(a &flat.FlatAst, id flat.NodeId, value string) int {
|
||||
if int(id) < 0 {
|
||||
return 0
|
||||
}
|
||||
node := a.nodes[int(id)]
|
||||
mut total := if node.kind == .selector && node.value == value { 1 } else { 0 }
|
||||
for i in 0 .. node.children_count {
|
||||
total += count_selector_value(a, a.child(&node, i), value)
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
fn test_typeof_expression_lowers_to_string_literal() {
|
||||
a := parse_transform_source('
|
||||
fn main() {
|
||||
name := typeof(123)
|
||||
}
|
||||
')
|
||||
main_fn := find_fn(a, 'main')
|
||||
for i in 0 .. main_fn.children_count {
|
||||
stmt := a.child_node(&main_fn, i)
|
||||
if stmt.kind == .decl_assign && stmt.children_count == 2 {
|
||||
rhs := a.child_node(stmt, 1)
|
||||
assert rhs.kind == .string_literal
|
||||
assert rhs.value == 'int literal'
|
||||
return
|
||||
}
|
||||
}
|
||||
assert false
|
||||
}
|
||||
|
||||
fn test_vmodroot_lowers_to_nearest_vmod_dir() {
|
||||
root := os.join_path(os.temp_dir(), 'v3_vmodroot_transformer_parity')
|
||||
os.mkdir_all(root) or { panic(err) }
|
||||
os.write_file(os.join_path(root, 'v.mod'), 'Module { name: "parity" }') or { panic(err) }
|
||||
src := os.join_path(root, 'main.v')
|
||||
a := parse_transform_file(src, '
|
||||
fn main() {
|
||||
root := @VMODROOT
|
||||
}
|
||||
')
|
||||
rhs := first_decl_rhs(a, 'main')
|
||||
assert rhs.kind == .string_literal
|
||||
assert rhs.value == root
|
||||
}
|
||||
|
||||
fn test_return_match_lowers_to_explicit_branch_returns() {
|
||||
a := parse_transform_source('
|
||||
fn choose(x int) int {
|
||||
return match x {
|
||||
0 {
|
||||
10
|
||||
}
|
||||
else {
|
||||
20
|
||||
}
|
||||
}
|
||||
}
|
||||
')
|
||||
choose_fn := find_fn(a, 'choose')
|
||||
mut match_count := 0
|
||||
mut return_count := 0
|
||||
for i in 0 .. choose_fn.children_count {
|
||||
child_id := a.child(&choose_fn, i)
|
||||
match_count += count_kind(a, child_id, .match_stmt)
|
||||
return_count += count_kind(a, child_id, .return_stmt)
|
||||
}
|
||||
assert match_count == 0
|
||||
assert return_count == 2
|
||||
}
|
||||
|
||||
fn test_return_if_keeps_or_lowering_inside_branch() {
|
||||
a := parse_transform_source('
|
||||
fn maybe_int() ?int {
|
||||
return 8
|
||||
}
|
||||
|
||||
fn choose(flag bool) int {
|
||||
return if flag {
|
||||
maybe_int() or {
|
||||
1
|
||||
}
|
||||
} else {
|
||||
2
|
||||
}
|
||||
}
|
||||
')
|
||||
choose_fn := find_fn(a, 'choose')
|
||||
mut body_ids := []flat.NodeId{}
|
||||
for i in 0 .. choose_fn.children_count {
|
||||
child_id := a.child(&choose_fn, i)
|
||||
child := a.nodes[int(child_id)]
|
||||
if child.kind != .param {
|
||||
body_ids << child_id
|
||||
}
|
||||
}
|
||||
assert body_ids.len == 1
|
||||
assert a.nodes[int(body_ids[0])].kind == .if_expr
|
||||
}
|
||||
|
||||
fn test_if_expr_value_lowers_to_temp_and_branch_assigns() {
|
||||
a := parse_transform_source('
|
||||
fn main() {
|
||||
x := if true {
|
||||
1
|
||||
} else {
|
||||
2
|
||||
}
|
||||
}
|
||||
')
|
||||
rhs := decl_rhs(a, 'main', 'x')
|
||||
assert rhs.kind == .ident
|
||||
assert rhs.value.starts_with('__if_val_')
|
||||
main_fn := find_fn(a, 'main')
|
||||
mut if_count := 0
|
||||
mut assign_count := 0
|
||||
for i in 0 .. main_fn.children_count {
|
||||
child_id := a.child(&main_fn, i)
|
||||
if_count += count_kind(a, child_id, .if_expr)
|
||||
assign_count += count_kind(a, child_id, .assign)
|
||||
}
|
||||
assert if_count == 1
|
||||
assert assign_count == 2
|
||||
}
|
||||
|
||||
fn test_if_expr_call_arg_lowers_before_call() {
|
||||
a := parse_transform_source('
|
||||
fn use(x int) int {
|
||||
return x
|
||||
}
|
||||
|
||||
fn main() {
|
||||
x := use(if true { 1 } else { 2 })
|
||||
}
|
||||
')
|
||||
rhs := decl_rhs(a, 'main', 'x')
|
||||
assert rhs.kind == .call
|
||||
assert rhs.children_count == 2
|
||||
arg := a.child_node(&rhs, 1)
|
||||
assert arg.kind == .ident
|
||||
assert arg.value.starts_with('__if_val_')
|
||||
}
|
||||
|
||||
fn test_nested_if_expr_branch_lowers_to_outer_temp_assignment() {
|
||||
a := parse_transform_source('
|
||||
fn main() {
|
||||
x := if true {
|
||||
if false {
|
||||
1
|
||||
} else {
|
||||
2
|
||||
}
|
||||
} else {
|
||||
3
|
||||
}
|
||||
}
|
||||
')
|
||||
rhs := decl_rhs(a, 'main', 'x')
|
||||
assert rhs.kind == .ident
|
||||
assert rhs.value.starts_with('__if_val_')
|
||||
main_fn := find_fn(a, 'main')
|
||||
mut if_count := 0
|
||||
mut assign_count := 0
|
||||
for i in 0 .. main_fn.children_count {
|
||||
child_id := a.child(&main_fn, i)
|
||||
if_count += count_kind(a, child_id, .if_expr)
|
||||
assign_count += count_kind(a, child_id, .assign)
|
||||
}
|
||||
assert if_count == 2
|
||||
assert assign_count == 4
|
||||
}
|
||||
|
||||
fn test_multi_return_decl_lowers_to_temp_field_decls() {
|
||||
a := parse_transform_source("
|
||||
fn pair() (int, string) {
|
||||
return 1, 'ok'
|
||||
}
|
||||
|
||||
fn main() {
|
||||
a, b := pair()
|
||||
}
|
||||
")
|
||||
main_fn := find_fn(a, 'main')
|
||||
mut wide_decl_count := 0
|
||||
for i in 0 .. main_fn.children_count {
|
||||
wide_decl_count += count_wide_decl_assigns(a, a.child(&main_fn, i))
|
||||
}
|
||||
assert wide_decl_count == 0
|
||||
a_rhs := decl_rhs(a, 'main', 'a')
|
||||
b_rhs := decl_rhs(a, 'main', 'b')
|
||||
assert a_rhs.kind == .selector
|
||||
assert a_rhs.value == 'arg0'
|
||||
assert b_rhs.kind == .selector
|
||||
assert b_rhs.value == 'arg1'
|
||||
}
|
||||
|
||||
fn test_multi_return_assign_lowers_to_temp_field_assigns() {
|
||||
a := parse_transform_source("
|
||||
fn pair() (int, string) {
|
||||
return 1, 'ok'
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut a := 0
|
||||
mut b := ''
|
||||
a, b = pair()
|
||||
}
|
||||
")
|
||||
main_fn := find_fn(a, 'main')
|
||||
mut wide_assign_count := 0
|
||||
mut arg0_count := 0
|
||||
mut arg1_count := 0
|
||||
for i in 0 .. main_fn.children_count {
|
||||
child_id := a.child(&main_fn, i)
|
||||
wide_assign_count += count_wide_assigns(a, child_id)
|
||||
arg0_count += count_selector_value(a, child_id, 'arg0')
|
||||
arg1_count += count_selector_value(a, child_id, 'arg1')
|
||||
}
|
||||
assert wide_assign_count == 0
|
||||
assert arg0_count == 1
|
||||
assert arg1_count == 1
|
||||
}
|
||||
|
||||
fn test_assoc_expr_lowers_to_temp_and_field_assigns() {
|
||||
a := parse_transform_source('
|
||||
struct Point {
|
||||
x int
|
||||
y int
|
||||
}
|
||||
|
||||
fn main() {
|
||||
p := Point{x: 1, y: 2}
|
||||
q := Point{...p, y: 3}
|
||||
}
|
||||
')
|
||||
rhs := decl_rhs(a, 'main', 'q')
|
||||
assert rhs.kind == .ident
|
||||
assert rhs.value.starts_with('__assoc_')
|
||||
main_fn := find_fn(a, 'main')
|
||||
mut assoc_count := 0
|
||||
mut assign_count := 0
|
||||
for i in 0 .. main_fn.children_count {
|
||||
child_id := a.child(&main_fn, i)
|
||||
assoc_count += count_kind(a, child_id, .assoc)
|
||||
assign_count += count_kind(a, child_id, .assign)
|
||||
}
|
||||
assert assoc_count == 0
|
||||
assert assign_count == 1
|
||||
}
|
||||
|
||||
fn test_return_assoc_expr_lowers_before_return() {
|
||||
a := parse_transform_source('
|
||||
struct Point {
|
||||
x int
|
||||
y int
|
||||
}
|
||||
|
||||
fn moved(p Point) Point {
|
||||
return Point{...p, x: 4}
|
||||
}
|
||||
')
|
||||
ret := first_return_expr(a, 'moved')
|
||||
assert ret.kind == .ident
|
||||
assert ret.value.starts_with('__assoc_')
|
||||
moved_fn := find_fn(a, 'moved')
|
||||
mut assoc_count := 0
|
||||
for i in 0 .. moved_fn.children_count {
|
||||
assoc_count += count_kind(a, a.child(&moved_fn, i), .assoc)
|
||||
}
|
||||
assert assoc_count == 0
|
||||
}
|
||||
|
||||
fn test_array_append_stmt_lowers_to_runtime_push() {
|
||||
a := parse_transform_source('
|
||||
fn main() {
|
||||
mut xs := []int{}
|
||||
xs << 3
|
||||
}
|
||||
')
|
||||
main_fn := find_fn(a, 'main')
|
||||
mut push_count := 0
|
||||
mut left_shift_count := 0
|
||||
for i in 0 .. main_fn.children_count {
|
||||
child_id := a.child(&main_fn, i)
|
||||
push_count += count_call_name(a, child_id, 'array_push')
|
||||
left_shift_count += count_infix_op(a, child_id, .left_shift)
|
||||
}
|
||||
assert push_count == 1
|
||||
assert left_shift_count == 0
|
||||
}
|
||||
|
||||
fn test_array_append_many_stmt_lowers_to_runtime_push_many() {
|
||||
a := parse_transform_source('
|
||||
fn main() {
|
||||
mut xs := []int{}
|
||||
ys := []int{}
|
||||
xs << ys
|
||||
}
|
||||
')
|
||||
main_fn := find_fn(a, 'main')
|
||||
mut push_many_count := 0
|
||||
mut left_shift_count := 0
|
||||
for i in 0 .. main_fn.children_count {
|
||||
child_id := a.child(&main_fn, i)
|
||||
push_many_count += count_call_name(a, child_id, 'array_push_many')
|
||||
left_shift_count += count_infix_op(a, child_id, .left_shift)
|
||||
}
|
||||
assert push_many_count == 1
|
||||
assert left_shift_count == 0
|
||||
}
|
||||
|
||||
fn test_or_expr_lowers_to_temp_and_if() {
|
||||
a := parse_transform_source('
|
||||
fn maybe_int() ?int {
|
||||
return 3
|
||||
}
|
||||
|
||||
fn main() {
|
||||
x := maybe_int() or {
|
||||
7
|
||||
}
|
||||
}
|
||||
')
|
||||
main_fn := find_fn(a, 'main')
|
||||
mut or_count := 0
|
||||
mut if_count := 0
|
||||
for i in 0 .. main_fn.children_count {
|
||||
child_id := a.child(&main_fn, i)
|
||||
or_count += count_kind(a, child_id, .or_expr)
|
||||
if_count += count_kind(a, child_id, .if_expr)
|
||||
}
|
||||
assert or_count == 0
|
||||
assert if_count == 1
|
||||
}
|
||||
|
||||
fn test_map_index_or_lowers_to_get_check() {
|
||||
a := parse_transform_source('
|
||||
fn main() {
|
||||
mut m := map[string]int{}
|
||||
x := m["a"] or {
|
||||
7
|
||||
}
|
||||
}
|
||||
')
|
||||
main_fn := find_fn(a, 'main')
|
||||
mut or_count := 0
|
||||
mut get_check_count := 0
|
||||
for i in 0 .. main_fn.children_count {
|
||||
child_id := a.child(&main_fn, i)
|
||||
or_count += count_kind(a, child_id, .or_expr)
|
||||
get_check_count += count_call_name(a, child_id, 'map__get_check')
|
||||
}
|
||||
assert or_count == 0
|
||||
assert get_check_count == 1
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
import os
|
||||
|
||||
const vexe = @VEXE
|
||||
const tests_dir = os.dir(@FILE)
|
||||
const v3_dir = os.dir(tests_dir)
|
||||
const v3_src = os.join_path(v3_dir, 'v3.v')
|
||||
|
||||
fn build_v3() string {
|
||||
v3_bin := os.join_path(os.temp_dir(), 'v3_type_checker_errors_test')
|
||||
build := os.execute('${vexe} -o ${v3_bin} ${v3_src}')
|
||||
assert build.exit_code == 0
|
||||
return v3_bin
|
||||
}
|
||||
|
||||
fn run_bad(v3_bin string, name string, src string, expected string) {
|
||||
bad_src := os.join_path(os.temp_dir(), 'v3_${name}.v')
|
||||
os.write_file(bad_src, src) or { panic(err) }
|
||||
bad_bin := os.join_path(os.temp_dir(), 'v3_${name}')
|
||||
result := os.execute('${v3_bin} ${bad_src} -b c -o ${bad_bin}')
|
||||
assert result.exit_code != 0
|
||||
assert result.output.contains(expected)
|
||||
assert !result.output.contains('C compilation failed')
|
||||
}
|
||||
|
||||
fn run_good(v3_bin string, name string, src string) string {
|
||||
good_src := os.join_path(os.temp_dir(), 'v3_${name}.v')
|
||||
os.write_file(good_src, src) or { panic(err) }
|
||||
good_bin := os.join_path(os.temp_dir(), 'v3_${name}')
|
||||
compile := os.execute('${v3_bin} ${good_src} -b c -o ${good_bin}')
|
||||
assert compile.exit_code == 0
|
||||
assert !compile.output.contains('C compilation failed')
|
||||
run := os.execute(good_bin)
|
||||
assert run.exit_code == 0
|
||||
return run.output.trim_space()
|
||||
}
|
||||
|
||||
fn write_project_file(root string, rel string, src string) {
|
||||
path := os.join_path(root, rel)
|
||||
os.mkdir_all(os.dir(path)) or { panic(err) }
|
||||
os.write_file(path, src) or { panic(err) }
|
||||
}
|
||||
|
||||
fn run_bad_project(v3_bin string, name string, files map[string]string, input string, expected string) {
|
||||
root := os.join_path(os.temp_dir(), 'v3_${name}_project')
|
||||
if os.exists(root) {
|
||||
os.rmdir_all(root) or { panic(err) }
|
||||
}
|
||||
os.mkdir_all(root) or { panic(err) }
|
||||
for rel, src in files {
|
||||
write_project_file(root, rel, src)
|
||||
}
|
||||
input_path := if input.len == 0 { root } else { os.join_path(root, input) }
|
||||
bad_bin := os.join_path(os.temp_dir(), 'v3_${name}')
|
||||
result := os.execute('${v3_bin} ${input_path} -b c -o ${bad_bin}')
|
||||
assert result.exit_code != 0
|
||||
assert result.output.contains(expected)
|
||||
assert !result.output.contains('C compilation failed')
|
||||
}
|
||||
|
||||
fn run_good_project(v3_bin string, name string, files map[string]string, input string) string {
|
||||
root := os.join_path(os.temp_dir(), 'v3_${name}_project')
|
||||
if os.exists(root) {
|
||||
os.rmdir_all(root) or { panic(err) }
|
||||
}
|
||||
os.mkdir_all(root) or { panic(err) }
|
||||
for rel, src in files {
|
||||
write_project_file(root, rel, src)
|
||||
}
|
||||
input_path := if input.len == 0 { root } else { os.join_path(root, input) }
|
||||
good_bin := os.join_path(os.temp_dir(), 'v3_${name}')
|
||||
compile := os.execute('${v3_bin} ${input_path} -b c -o ${good_bin}')
|
||||
assert compile.exit_code == 0
|
||||
assert !compile.output.contains('C compilation failed')
|
||||
run := os.execute(good_bin)
|
||||
assert run.exit_code == 0
|
||||
return run.output.trim_space()
|
||||
}
|
||||
|
||||
fn gen_c_project(v3_bin string, name string, files map[string]string, input string) string {
|
||||
root := os.join_path(os.temp_dir(), 'v3_${name}_project')
|
||||
if os.exists(root) {
|
||||
os.rmdir_all(root) or { panic(err) }
|
||||
}
|
||||
os.mkdir_all(root) or { panic(err) }
|
||||
for rel, src in files {
|
||||
write_project_file(root, rel, src)
|
||||
}
|
||||
input_path := if input.len == 0 { root } else { os.join_path(root, input) }
|
||||
c_out := os.join_path(os.temp_dir(), 'v3_${name}.c')
|
||||
os.rm(c_out) or {}
|
||||
compile := os.execute('${v3_bin} ${input_path} -o ${c_out}')
|
||||
assert compile.exit_code == 0
|
||||
assert os.exists(c_out)
|
||||
return os.read_file(c_out) or { panic(err) }
|
||||
}
|
||||
|
||||
fn test_type_checker_reports_core_semantic_errors() {
|
||||
v3_bin := build_v3()
|
||||
run_bad(v3_bin, 'bad_assignment', "fn main() {\n\tmut x := 1\n\tx = 'bad'\n}\n",
|
||||
'cannot assign `string` to `int`')
|
||||
run_bad(v3_bin, 'bad_return', "fn f() int {\n\treturn 'bad'\n}\nfn main() {}\n",
|
||||
'cannot return `string` as `int`')
|
||||
run_bad(v3_bin, 'bad_call_arg', "fn takes_int(x int) {}\nfn main() {\n\ttakes_int('bad')\n}\n",
|
||||
'cannot use `string` as argument 1 to `takes_int`; expected `int`')
|
||||
run_bad(v3_bin, 'bad_field',
|
||||
'struct Foo {\n\tx int\n}\nfn main() {\n\tf := Foo{}\n\t_ := f.y\n}\n',
|
||||
'unknown field `y` on `Foo`')
|
||||
run_bad(v3_bin, 'bad_index', 'fn main() {\n\tx := 1\n\t_ := x[0]\n}\n', 'cannot index `int`')
|
||||
run_bad(v3_bin, 'bad_condition', "fn main() {\n\tif 'bad' {}\n}\n",
|
||||
'if condition must be `bool`, not `string`')
|
||||
run_bad(v3_bin, 'bad_zero_arg_call', 'fn f() {}\nfn main() {\n\tf(1)\n}\n',
|
||||
'argument count mismatch for `f`: expected 0, got 1')
|
||||
run_bad(v3_bin, 'bad_int_condition', "fn main() {\n\tif 1 {\n\t\tprintln('bad')\n\t}\n}\n",
|
||||
'if condition must be `bool`, not `int`')
|
||||
run_bad(v3_bin, 'bad_missing_return',
|
||||
'fn f(x bool) int {\n\tif x {\n\t\treturn 1\n\t}\n}\nfn main() {}\n',
|
||||
'missing return at end of function `f`')
|
||||
run_bad(v3_bin, 'bad_interface_method_set',
|
||||
'interface Speaker {\n\tspeak() string\n}\nstruct Person {}\nfn takes_speaker(s Speaker) {}\nfn main() {\n\ttakes_speaker(Person{})\n}\n',
|
||||
'cannot use `Person` as argument 1 to `takes_speaker`; expected `Speaker`')
|
||||
run_bad(v3_bin, 'bad_fn_value_call',
|
||||
"fn add(a int, b int) int {\n\treturn a + b\n}\nfn main() {\n\tf := add\n\t_ := f('bad', 4)\n}\n",
|
||||
'cannot use `string` as argument 1 to `f`; expected `int`')
|
||||
run_bad(v3_bin, 'bad_struct_default', "struct Foo {\n\tx int = 'bad'\n}\nfn main() {}\n",
|
||||
'cannot initialize field `x` with `string`; expected `int`')
|
||||
run_bad(v3_bin, 'bad_enum_value', "enum Color {\n\tred = 'bad'\n}\nfn main() {}\n",
|
||||
'enum field `red` value must be integer, not `string`')
|
||||
run_bad(v3_bin, 'bad_enum_shorthand',
|
||||
'enum Color {\n\tred\n}\nfn paint(c Color) {}\nfn main() {\n\tpaint(.blue)\n}\n',
|
||||
'unknown enum field `blue` for `Color`')
|
||||
run_bad(v3_bin, 'bad_interface_field',
|
||||
'interface Named {\n\tname string\n}\nstruct Person {}\nfn takes_named(n Named) {}\nfn main() {\n\ttakes_named(Person{})\n}\n',
|
||||
'cannot use `Person` as argument 1 to `takes_named`; expected `Named`')
|
||||
run_bad(v3_bin, 'bad_sum_missing_shared_field',
|
||||
'struct A {\n\tid int\n}\nstruct B {\n\tname string\n}\ntype Node = A | B\nfn main() {\n\tn := Node(A{\n\t\tid: 1\n\t})\n\t_ := n.id\n}\n',
|
||||
'unknown field `id` on `Node`')
|
||||
run_bad(v3_bin, 'bad_sum_and_smartcast_rhs',
|
||||
'struct Cat {\n\tage int\n}\nstruct Dog {\n\ttricks int\n}\ntype Animal = Cat | Dog\nfn main() {\n\ta := Animal(Cat{\n\t\tage: 2\n\t})\n\tif a is Cat && a.tricks == 2 {}\n}\n',
|
||||
'unknown field `tricks` on `Cat`')
|
||||
run_bad(v3_bin, 'bad_sum_is_variant',
|
||||
'struct Cat {\n\tage int\n}\nstruct Dog {\n\ttricks int\n}\nstruct Bird {\n\twings int\n}\ntype Animal = Cat | Dog\nfn main() {\n\ta := Animal(Cat{\n\t\tage: 2\n\t})\n\tif a is Bird {}\n}\n',
|
||||
'`Bird` is not a variant of sum type `Animal`')
|
||||
run_bad(v3_bin, 'bad_sum_match_variant',
|
||||
'struct Cat {\n\tage int\n}\nstruct Dog {\n\ttricks int\n}\nstruct Bird {\n\twings int\n}\ntype Animal = Cat | Dog\nfn main() {\n\ta := Animal(Cat{\n\t\tage: 2\n\t})\n\tmatch a {\n\t\tBird {}\n\t\telse {}\n\t}\n}\n',
|
||||
'`Bird` is not a variant of sum type `Animal`')
|
||||
run_bad(v3_bin, 'bad_unknown_decl_type', 'fn f(x Missing) {}\nfn main() {}\n',
|
||||
'unknown type `Missing`')
|
||||
run_bad(v3_bin, 'bad_generic_param',
|
||||
'fn id[T](x T) T {\n\treturn x\n}\nfn main() {\n\t_ := id(1)\n}\n',
|
||||
'unsupported generic type parameter `T`')
|
||||
run_bad(v3_bin, 'bad_generic_type_application', 'fn takes_box(x Box[int]) {}\nfn main() {}\n',
|
||||
'unsupported generic type application `Box[int]`')
|
||||
run_bad_project(v3_bin, 'bad_bare_imported_call', {
|
||||
'main.v': 'module main\n\nimport moda\n\nfn main() {\n\t_ := answer()\n}\n'
|
||||
'moda/moda.v': 'module moda\n\nfn answer() int {\n\treturn 7\n}\n'
|
||||
}, 'main.v', 'unknown function `answer`')
|
||||
run_bad_project(v3_bin, 'bad_import_leak', {
|
||||
'main.v': 'module main\n\nimport moda\n\nfn main() {}\n'
|
||||
'other.v': 'module main\n\nfn use_it() int {\n\treturn moda.answer()\n}\n'
|
||||
'moda/moda.v': 'module moda\n\nfn answer() int {\n\treturn 7\n}\n'
|
||||
}, '', 'unknown function `moda.answer`')
|
||||
alias_out := run_good(v3_bin, 'alias_method',
|
||||
'type UserId = int\n\nfn (id UserId) str() string {\n\treturn int_str(int(id))\n}\n\nfn main() {\n\tid := UserId(1)\n\tprintln(id.str())\n}\n')
|
||||
assert alias_out == '1'
|
||||
map_mutation_out := run_good(v3_bin, 'map_mutation_lowering',
|
||||
"fn main() {\n\tmut m := map[string]int{}\n\tm['a'] = 1\n\tm['a'] += 2\n\tm['a']++\n\tm['a'] -= 1\n\tprintln(int_str(m['a']))\n}\n")
|
||||
assert map_mutation_out == '3'
|
||||
map_array_append_out := run_good(v3_bin, 'map_array_append_lowering',
|
||||
"fn main() {\n\tmut m := map[string][]int{}\n\tm['a'] << 1\n\tm['a'] << 2\n\tprintln(int_str(m['a'].len))\n}\n")
|
||||
assert map_array_append_out == '2'
|
||||
assoc_selector_out := run_good(v3_bin, 'assoc_selector_type',
|
||||
'struct Point {\n\tx int\n\ty int\n}\n\nfn main() {\n\tp := Point{\n\t\tx: 10\n\t\ty: 20\n\t}\n\tq := Point{\n\t\t...p\n\t\tx: 99\n\t}\n\tprintln(int_str(q.x + q.y))\n}\n')
|
||||
assert assoc_selector_out == '119'
|
||||
array_literal_push_many_out := run_good(v3_bin, 'array_literal_push_many',
|
||||
'fn main() {\n\tmut xs := [1, 2]\n\tys := [3, 4]\n\txs << ys\n\txs << [5, 6]\n\tprintln(int_str(xs.len))\n}\n')
|
||||
assert array_literal_push_many_out == '6'
|
||||
nested_array_push_out := run_good(v3_bin, 'nested_array_push',
|
||||
'fn main() {\n\tmut xs := [][]int{}\n\ty := [1, 2]\n\txs << y\n\tprintln(int_str(xs.len))\n\tprintln(int_str(xs[0][1]))\n}\n')
|
||||
assert nested_array_push_out == '1\n2'
|
||||
mut_array_for_out := run_good(v3_bin, 'mut_array_for_in',
|
||||
'struct Item {\nmut:\n\tn int\n}\n\nfn main() {\n\tmut xs := []Item{}\n\txs << Item{n: 1}\n\tfor mut item in xs {\n\t\titem.n = 7\n\t}\n\tprintln(int_str(xs[0].n))\n}\n')
|
||||
assert mut_array_for_out == '7'
|
||||
mut_array_for_scalar_out := run_good(v3_bin, 'mut_array_for_scalar_updates',
|
||||
'fn main() {\n\tmut xs := []int{}\n\txs << 1\n\txs << 2\n\tfor mut x in xs {\n\t\tx++\n\t\tx += 10\n\t}\n\tprintln(int_str(xs[0]))\n\tprintln(int_str(xs[1]))\n}\n')
|
||||
assert mut_array_for_scalar_out == '12\n13'
|
||||
const_forward_out := run_good(v3_bin, 'const_forward',
|
||||
'const first_value = second_value\nconst second_value = 2\nfn main() {\n\tprintln(int_str(first_value))\n}\n')
|
||||
assert const_forward_out == '2'
|
||||
imported_call_out := run_good_project(v3_bin, 'qualified_import_call', {
|
||||
'main.v': 'module main\n\nimport moda\n\nfn main() {\n\tprintln(int_str(moda.answer()))\n}\n'
|
||||
'moda/moda.v': 'module moda\n\nfn answer() int {\n\treturn 7\n}\n'
|
||||
}, 'main.v')
|
||||
assert imported_call_out == '7'
|
||||
comptime_type_chain_out := run_good_project(v3_bin, 'comptime_type_chain_keeps_later_decls', {
|
||||
'main.v': 'module main\n\nimport moda\n\nfn main() {\n\tprintln(int_str(moda.after_chain()))\n}\n'
|
||||
'moda/moda.v': 'module moda\n\nfn choose[T](x T) T {\n\t$if T is u8 {\n\t\treturn x\n\t} $else $if T is int {\n\t\treturn x\n\t} $else {}\n\treturn x\n}\n\nfn after_chain() int {\n\treturn 7\n}\n'
|
||||
}, 'main.v')
|
||||
assert comptime_type_chain_out == '7'
|
||||
builder_out := run_good(v3_bin, 'strings_builder_from_vlib',
|
||||
"import strings\n\nfn main() {\n\tmut sb := strings.new_builder(16)\n\tsb.write_string('ok')\n\tsb.write_u8(u8(33))\n\tprintln(sb.last_n(3))\n\tprintln(sb.str())\n}\n")
|
||||
assert builder_out == 'ok!\nok!'
|
||||
init_order_out := run_good_project(v3_bin, 'module_init_order', {
|
||||
'main.v': 'module main\n\nimport moda\n\n__global seen int\n\nfn init() {\n\tseen = moda.value()\n}\n\nfn main() {\n\tprintln(int_str(seen))\n\tprintln(int_str(moda.value()))\n}\n'
|
||||
'moda/moda.v': 'module moda\n\n__global flag int\n\nfn init() {\n\tflag = 41\n}\n\nfn value() int {\n\treturn flag\n}\n'
|
||||
}, 'main.v')
|
||||
assert init_order_out == '41\n41'
|
||||
hier_init_order_out := run_good_project(v3_bin, 'hierarchical_module_init_order', {
|
||||
'main.v': 'module main\n\nimport parent.child\n\n__global seen int\n\nfn init() {\n\tseen = child.value()\n}\n\nfn main() {\n\tprintln(int_str(seen))\n\tprintln(int_str(child.value()))\n}\n'
|
||||
'parent/child/child.v': 'module child\n\n__global flag int\n\nfn init() {\n\tflag = 41\n}\n\npub fn value() int {\n\treturn flag\n}\n'
|
||||
}, 'main.v')
|
||||
assert hier_init_order_out == '41\n41'
|
||||
transitive_init_order_out := run_good_project(v3_bin, 'transitive_module_init_order', {
|
||||
'main.v': 'module main\n\nimport moda\n\n__global seen int\n\nfn init() {\n\tseen = moda.value()\n}\n\nfn main() {\n\tprintln(int_str(seen))\n\tprintln(int_str(moda.value()))\n}\n'
|
||||
'moda/moda.v': 'module moda\n\nimport modb\n\npub fn value() int {\n\treturn modb.value()\n}\n'
|
||||
'modb/modb.v': 'module modb\n\n__global flag int\n\nfn init() {\n\tflag = 41\n}\n\npub fn value() int {\n\treturn flag\n}\n'
|
||||
}, 'main.v')
|
||||
assert transitive_init_order_out == '41\n41'
|
||||
global_amp_out := run_good(v3_bin, 'global_amp_initializers',
|
||||
'struct Point {\n\tx int\n\ty int\n}\n\ninterface Reader {\n\tn int\n\tread() int\n}\n\nstruct Box {\n\tn int\n}\n\nfn (b Box) read() int {\n\treturn b.n + 1\n}\n\n__global (\n\tbase_point = Point{x: 1, y: 2}\n\tassoc_point = &Point{...base_point, y: 5}\n\treader_box = Box{n: 7}\n\treader_ref = &Reader(reader_box)\n)\n\nfn main() {\n\tprintln(int_str(assoc_point.y))\n\tprintln(int_str(reader_ref.n))\n\tprintln(int_str(reader_ref.read()))\n}\n')
|
||||
assert global_amp_out == '5\n7\n8'
|
||||
disabled_if_out := run_good(v3_bin, 'disabled_if_call_elides_args',
|
||||
'__global hit int\n\n@[if trace ?]\nfn trace(x int) {}\n\nfn side_effect() int {\n\thit = 99\n\treturn 1\n}\n\nfn main() {\n\ttrace(side_effect())\n\tprintln(int_str(hit))\n}\n')
|
||||
assert disabled_if_out == '0'
|
||||
disabled_if_alias_out := run_good_project(v3_bin, 'disabled_if_alias_call_elides_args', {
|
||||
'main.v': 'module main\n\nimport moda as m\n\n__global hit int\n\nfn side_effect() int {\n\thit = 99\n\treturn 1\n}\n\nfn main() {\n\tm.trace(side_effect())\n\tprintln(int_str(hit))\n}\n'
|
||||
'moda/moda.v': 'module moda\n\n@[if trace ?]\npub fn trace(x int) {}\n'
|
||||
}, 'main.v')
|
||||
assert disabled_if_alias_out == '0'
|
||||
disabled_if_method_out := run_good_project(v3_bin, 'disabled_if_method_call_elides_args', {
|
||||
'main.v': 'module main\n\nimport moda\n\nfn main() {\n\tprintln(int_str(moda.run()))\n}\n'
|
||||
'moda/moda.v': 'module moda\n\n__global hit int\n\nstruct Tracer {}\n\n@[if trace ?]\nfn (t Tracer) trace(x int) {}\n\nfn side_effect() int {\n\thit = 99\n\treturn 1\n}\n\npub fn run() int {\n\tt := Tracer{}\n\tt.trace(side_effect())\n\treturn hit\n}\n'
|
||||
}, 'main.v')
|
||||
assert disabled_if_method_out == '0'
|
||||
disabled_operator_out := run_good(v3_bin, 'disabled_if_operator_call_elides_args',
|
||||
'__global hit int\n\nstruct Number {\n\tn int\n}\n\n@[if trace ?]\nfn (a Number) + (b Number) Number {\n\treturn Number{n: a.n + b.n}\n}\n\nfn side_effect() Number {\n\thit = 99\n\treturn Number{n: 2}\n}\n\nfn main() {\n\ta := Number{n: 1}\n\t_ := a + side_effect()\n\tprintln(int_str(hit))\n}\n')
|
||||
assert disabled_operator_out == '0'
|
||||
disabled_if_non_fn_out := run_good(v3_bin, 'disabled_if_non_fn_decl_skipped',
|
||||
'@[if trace ?]\nstruct DisabledStruct {\n\tbad MissingDisabledType\n}\n\nstruct EnabledStruct {\n\tvalue int\n}\n\nfn main() {\n\tprintln(int_str(EnabledStruct{value: 7}.value))\n}\n')
|
||||
assert disabled_if_non_fn_out == '7'
|
||||
function_defer_loop_out := run_good(v3_bin, 'function_defer_runs_each_loop_execution',
|
||||
'__global hit int\n\nfn run() {\n\tfor _ in 0 .. 3 {\n\t\tdefer(fn) {\n\t\t\thit += 100\n\t\t}\n\t}\n}\n\nfn main() {\n\trun()\n\tprintln(int_str(hit))\n}\n')
|
||||
assert function_defer_loop_out == '300'
|
||||
cross_module_array_append_c := gen_c_project(v3_bin, 'array_append_distinct_module_types', {
|
||||
'main.v': 'module main\n\nimport moda\nimport modb\n\nfn main() {\n\tmut xs := []moda.Foo{}\n\tys := []modb.Foo{}\n\txs << ys\n}\n'
|
||||
'moda/moda.v': 'module moda\n\nstruct Foo {\n\ta int\n}\n'
|
||||
'modb/modb.v': 'module modb\n\nstruct Foo {\n\tb int\n}\n'
|
||||
}, 'main.v')
|
||||
assert !cross_module_array_append_c.contains('array_push_many(&xs')
|
||||
assert cross_module_array_append_c.contains('array_push(&xs')
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import os
|
||||
|
||||
const vexe = @VEXE
|
||||
const tests_dir = os.dir(@FILE)
|
||||
const v3_dir = os.dir(tests_dir)
|
||||
const v3_src = os.join_path(v3_dir, 'v3.v')
|
||||
|
||||
fn build_v3() string {
|
||||
v3_bin := os.join_path(os.temp_dir(), 'v3_union_byte_contains_codegen_test')
|
||||
build := os.execute('${vexe} -o ${v3_bin} ${v3_src}')
|
||||
assert build.exit_code == 0
|
||||
return v3_bin
|
||||
}
|
||||
|
||||
fn run_good(v3_bin string, name string, source string) string {
|
||||
src := os.join_path(os.temp_dir(), 'v3_${name}.v')
|
||||
os.write_file(src, source) or { panic(err) }
|
||||
bin := os.join_path(os.temp_dir(), 'v3_${name}')
|
||||
compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
|
||||
assert compile.exit_code == 0
|
||||
assert !compile.output.contains('C compilation failed')
|
||||
|
||||
run := os.execute(bin)
|
||||
assert run.exit_code == 0
|
||||
return run.output.trim_space()
|
||||
}
|
||||
|
||||
fn test_union_aliasing_and_byte_array_contains_codegen() {
|
||||
v3_bin := build_v3()
|
||||
out := run_good(v3_bin, 'union_byte_contains_codegen_input',
|
||||
"union Bits {\nmut:\n\tf f64\n\tu u64\n}\n\nfn main() {\n\tmut u := Bits{}\n\tu.f = 12.5\n\tif u.u != u64(0) {\n\t\tprintln('union ok')\n\t} else {\n\t\tprintln('union bad')\n\t}\n\tmut bytes := []u8{len: 4, init: 0}\n\tbytes[1] = `.`\n\tif `.` in bytes {\n\t\tprintln('byte contains ok')\n\t} else {\n\t\tprintln('byte contains bad')\n\t}\n\tprintln('${12.5}')\n}\n")
|
||||
assert out == 'union ok\nbyte contains ok\n12.5'
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import os
|
||||
|
||||
const vexe = @VEXE
|
||||
const tests_dir = os.dir(@FILE)
|
||||
const v3_dir = os.dir(tests_dir)
|
||||
const v3_src = os.join_path(v3_dir, 'v3.v')
|
||||
|
||||
fn test_unknown_function_stops_in_type_checker() {
|
||||
v3_bin := os.join_path(os.temp_dir(), 'v3_unknown_fn_error_test')
|
||||
build := os.execute('${vexe} -o ${v3_bin} ${v3_src}')
|
||||
assert build.exit_code == 0
|
||||
|
||||
bad_src := os.join_path(os.temp_dir(), 'v3_unknown_fn_error_input.v')
|
||||
os.write_file(bad_src, 'fn main() {\n\tx := missing_fn()\n\tprintln(x)\n}\n')!
|
||||
bad_bin := os.join_path(os.temp_dir(), 'v3_unknown_fn_error_input')
|
||||
result := os.execute('${v3_bin} ${bad_src} -b c -o ${bad_bin}')
|
||||
assert result.exit_code != 0
|
||||
assert result.output.contains('unknown function `missing_fn`')
|
||||
assert !result.output.contains('C compilation failed')
|
||||
}
|
||||
Reference in new issue
Block a user