diff --git a/.github/workflows/sdl_ci.yml b/.github/workflows/sdl_ci.yml index aadad3af22..f7f14ef780 100644 --- a/.github/workflows/sdl_ci.yml +++ b/.github/workflows/sdl_ci.yml @@ -40,6 +40,7 @@ jobs: - name: Build sdl examples run: | + v shader sdl/examples/sdl_opengl_and_sokol for example in sdl/examples/*; do echo "v $example" v "$example"; diff --git a/README.md b/README.md index 0689012a35..2bea39eac9 100644 --- a/README.md +++ b/README.md @@ -60,23 +60,40 @@ Unlike many other languages, V is not going to be always changing, with new feat being introduced and old features modified. It is always going to be a small and simple language, very similar to the way it is right now. -## Installing V from source +## Installing V - from source *(preferred method)* -### Linux, macOS, Windows, *BSD, Solaris, WSL, Android, Raspbian +### Linux, macOS, Windows, *BSD, Solaris, WSL, Android, etc. +Usually installing V is quite simple if you have an environment that already has a +functional `git` installation. + +* *(* ***PLEASE NOTE:*** *If you run into any trouble or you have a different operating +system or Linux distribution that doesn't install or work immediately, please see +[Installation Issues](https://github.com/vlang/v/discussions/categories/installation-issues) +and search for your OS and problem. If you can't find your problem, please add it to an +existing discussion if one exists for your OS, or create a new one if a main discussion +doesn't yet exist for your OS.)* + + +To get started, simply try to execute the following in your terminal/shell: ```bash git clone https://github.com/vlang/v cd v make +# HINT: Using Windows?: run make.bat in the cmd.exe shell ``` -That's it! Now you have a V executable at `[path to V repo]/v`. +That should be it and you should find your V executable at `[path to V repo]/v`. `[path to V repo]` can be anywhere. -(On Windows `make` means running `make.bat`, so make sure you use `cmd.exe`) +(As in the hint above, on Windows `make` means running `make.bat`, so make sure you use +the `cmd.exe` terminal.) Now you can try `./v run examples/hello_world.v` (`v.exe` on Windows). +* *Trouble? Please see the note above and link to +[Installation Issues](https://github.com/vlang/v/discussions/categories/installation-issues) for help.* + V is constantly being updated. To update V, simply run: ```bash diff --git a/cmd/tools/vbug.v b/cmd/tools/vbug.v index 4d91cb6e6c..f5cd708452 100644 --- a/cmd/tools/vbug.v +++ b/cmd/tools/vbug.v @@ -118,7 +118,7 @@ fn main() { build_output := get_v_build_output(is_verbose, is_yes, file_path) // ask the user if he wants to submit even after an error if !is_yes && (vdoctor_output == '' || file_content == '' || build_output == '') { - confirm_or_exit('An error occured retrieving the information, do you want to continue?') + confirm_or_exit('An error occurred retrieving the information, do you want to continue?') } expected_result := readline.read_line('What did you expect to see? ') or { diff --git a/cmd/tools/vfmt.v b/cmd/tools/vfmt.v index c8cecaf07b..7412bfd21b 100644 --- a/cmd/tools/vfmt.v +++ b/cmd/tools/vfmt.v @@ -27,6 +27,8 @@ struct FormatOptions { is_verify bool // exit(1) if the file is not vfmt'ed is_worker bool // true *only* in the worker processes. Note: workers can crash. is_backup bool // make a `file.v.bak` copy *before* overwriting a `file.v` in place with `-w` +mut: + diff_cmd string // filled in when -diff or -verify is passed } const ( @@ -201,36 +203,23 @@ fn print_compiler_options(compiler_params &pref.Preferences) { eprintln(' is_script: $compiler_params.is_script ') } -fn (foptions &FormatOptions) post_process_file(file string, formatted_file_path string) ? { +fn (mut foptions FormatOptions) find_diff_cmd() string { + if foptions.diff_cmd != '' { + return foptions.diff_cmd + } + if foptions.is_verify || foptions.is_diff { + foptions.diff_cmd = diff.find_working_diff_command() or { + eprintln(err) + exit(1) + } + } + return foptions.diff_cmd +} + +fn (mut foptions FormatOptions) post_process_file(file string, formatted_file_path string) ? { if formatted_file_path.len == 0 { return } - if foptions.is_diff { - diff_cmd := diff.find_working_diff_command() or { - eprintln(err) - return - } - if foptions.is_verbose { - eprintln('Using diff command: $diff_cmd') - } - diff := diff.color_compare_files(diff_cmd, file, formatted_file_path) - if diff.len > 0 { - println(diff) - } - return - } - if foptions.is_verify { - diff_cmd := diff.find_working_diff_command() or { - eprintln(err) - return - } - x := diff.color_compare_files(diff_cmd, file, formatted_file_path) - if x.len != 0 { - println("$file is not vfmt'ed") - return error('') - } - return - } fc := os.read_file(file) or { eprintln('File $file could not be read') return @@ -240,6 +229,31 @@ fn (foptions &FormatOptions) post_process_file(file string, formatted_file_path return } is_formatted_different := fc != formatted_fc + if foptions.is_diff { + if !is_formatted_different { + return + } + diff_cmd := foptions.find_diff_cmd() + if foptions.is_verbose { + eprintln('Using diff command: $diff_cmd') + } + diff := diff.color_compare_files(diff_cmd, file, formatted_file_path) + if diff.len > 0 { + println(diff) + } + return + } + if foptions.is_verify { + if !is_formatted_different { + return + } + x := diff.color_compare_files(foptions.find_diff_cmd(), file, formatted_file_path) + if x.len != 0 { + println("$file is not vfmt'ed") + return error('') + } + return + } if foptions.is_c { if is_formatted_different { eprintln('File is not formatted: $file') diff --git a/cmd/tools/vgret.v b/cmd/tools/vgret.v index c74f8e955c..b9f575ec62 100644 --- a/cmd/tools/vgret.v +++ b/cmd/tools/vgret.v @@ -354,7 +354,11 @@ fn vexe() string { } fn new_config(root_path string, toml_config string) ?Config { - doc := toml.parse(toml_config) ? + doc := if os.is_file(toml_config) { + toml.parse_file(toml_config) ? + } else { + toml.parse_text(toml_config) ? + } path := os.real_path(root_path).trim_right('/') diff --git a/cmd/tools/vrepl.v b/cmd/tools/vrepl.v index f3a5571138..bbbd7bcb38 100644 --- a/cmd/tools/vrepl.v +++ b/cmd/tools/vrepl.v @@ -208,11 +208,21 @@ fn (mut r Repl) parse_import(line string) { } } +fn highlight_console_command(command string) string { + return term.bright_white(term.bright_bg_black(' $command ')) +} + +fn highlight_repl_command(command string) string { + return term.bright_white(term.bg_blue(' $command ')) +} + fn print_welcome_screen() { - cmd_exit := term.highlight_command('exit') - cmd_help := term.highlight_command('v help') - file_main := term.highlight_command('main.v') - cmd_run := term.highlight_command('v run main.v') + cmd_exit := highlight_repl_command('exit') + cmd_list := highlight_repl_command('list') + cmd_help := highlight_repl_command('help') + cmd_v_help := highlight_console_command('v help') + cmd_v_run := highlight_console_command('v run main.v') + file_main := highlight_console_command('main.v') vbar := term.bright_green('|') width, _ := term.get_terminal_size() // get the size of the terminal vlogo := [ @@ -224,11 +234,11 @@ fn print_welcome_screen() { term.bright_blue(r' \__/ '), ] help_text := [ - 'Welcome to the V REPL (for help with V itself, type $cmd_exit, then run $cmd_help).', + 'Welcome to the V REPL (for help with V itself, type $cmd_exit, then run $cmd_v_help).', 'Note: the REPL is highly experimental. For best V experience, use a text editor, ', - 'save your code in a $file_main file and execute: $cmd_run', - version.full_v_version(false), - 'Use Ctrl-C or ${term.highlight_command('exit')} to exit, or ${term.highlight_command('help')} to see other available commands', + 'save your code in a $file_main file and execute: $cmd_v_run', + '${version.full_v_version(false)} . Use $cmd_list to see the accumulated program so far.', + 'Use Ctrl-C or $cmd_exit to exit, or $cmd_help to see other available commands.', ] if width >= 97 { eprintln('${vlogo[0]}') @@ -480,6 +490,9 @@ fn main() { println(' ... where vexepath is the full path to the v executable file') return } + if !is_stdin_a_pipe { + os.setenv('VCOLORS', 'always', true) + } run_repl(replfolder, replprefix) } diff --git a/cmd/v/help/build-c.txt b/cmd/v/help/build-c.txt index 11c7504037..8627eb48d7 100644 --- a/cmd/v/help/build-c.txt +++ b/cmd/v/help/build-c.txt @@ -25,7 +25,7 @@ see also `v help build`. -cstrict Turn on additional C warnings. This slows down compilation - slightly (~10-10% for gcc), but sometimes provides better diagnosis. + slightly (~10% for gcc), but sometimes provides better diagnosis. -showcc Prints the C command that is used to build the program. @@ -97,7 +97,8 @@ see also `v help build`. systems also (although we do not test it as regularly as for the above): `android`, `ios`, `freebsd`, `openbsd`, `netbsd`, `dragonfly`, - `solaris`, `serenity`, `haiku`, `vinix` + `solaris`, `serenity`, `haiku`, `vinix`, + `wasm32`, `wasm32-wasi`, `wasm32-emscripten` Note that V has the concept of platform files, i.e. files ending with `_platform.c.v`, and usually only the matching files are used in diff --git a/doc/docs.md b/doc/docs.md index 94133b2a7d..3629d8ff8f 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -624,6 +624,9 @@ println('[${int(x):010}]') // pad with zeros on the left => [0000000123] println('[${int(x):b}]') // output as binary => [1111011] println('[${int(x):o}]') // output as octal => [173] println('[${int(x):X}]') // output as uppercase hex => [7B] + +println('[${10.0000:.2}]') // remove insignificant 0s at the end => [10] +println('[${10.0000:.2f}]') // do show the 0s at the end, even though they do not change the number => [10.00] ``` ### String operators diff --git a/examples/toml.v b/examples/toml.v index 303aa5ede1..a245cf3510 100644 --- a/examples/toml.v +++ b/examples/toml.v @@ -38,7 +38,7 @@ hosts = [ ]' fn main() { - doc := toml.parse(toml_text) or { panic(err) } + doc := toml.parse_text(toml_text) or { panic(err) } title := doc.value('title').string() println('title: "$title"') ip := doc.value('servers.alpha.ip').string() diff --git a/examples/ttf_font/example_ttf.v b/examples/ttf_font/example_ttf.v index d22ea84e62..2edb417f20 100644 --- a/examples/ttf_font/example_ttf.v +++ b/examples/ttf_font/example_ttf.v @@ -144,7 +144,7 @@ fn main() { // TTF render 0 Frame counter app.ttf_render << &ttf.TTF_render_Sokol{ bmp: &ttf.BitMap{ - tf: &(app.tf[0]) + tf: &app.tf[0] buf: unsafe { malloc_noscan(32000000) } buf_size: (32000000) color: 0xFF0000FF @@ -155,7 +155,7 @@ fn main() { // TTF render 1 Text Block app.ttf_render << &ttf.TTF_render_Sokol{ bmp: &ttf.BitMap{ - tf: &(app.tf[1]) + tf: &app.tf[1] // color : 0xFF0000_10 // style: .raw // use_font_metrics: true @@ -164,7 +164,7 @@ fn main() { // TTF mouse position render app.ttf_render << &ttf.TTF_render_Sokol{ bmp: &ttf.BitMap{ - tf: &(app.tf[0]) + tf: &app.tf[0] } } // setup sokol_gfx diff --git a/examples/vweb/vweb_assets/assets/index.css b/examples/vweb/vweb_assets/assets/index.css index 4ad9eb895f..f5b7ea6dd5 100644 --- a/examples/vweb/vweb_assets/assets/index.css +++ b/examples/vweb/vweb_assets/assets/index.css @@ -17,3 +17,9 @@ img.logo { float: left; width: 10em; } + +html { + display:block; +} + + diff --git a/examples/vweb/vweb_assets/index.html b/examples/vweb/vweb_assets/index.html index 6635329ad0..668bdb3d49 100644 --- a/examples/vweb/vweb_assets/index.html +++ b/examples/vweb/vweb_assets/index.html @@ -1,6 +1,7 @@
@title + @css 'index.css'
diff --git a/examples/vweb/vweb_assets/vweb_assets.v b/examples/vweb/vweb_assets/vweb_assets.v index 058f45922b..1033f7ea8f 100644 --- a/examples/vweb/vweb_assets/vweb_assets.v +++ b/examples/vweb/vweb_assets/vweb_assets.v @@ -1,5 +1,6 @@ module main +import os import vweb // import vweb.assets import time @@ -16,6 +17,7 @@ fn main() { mut app := &App{} app.serve_static('/favicon.ico', 'favicon.ico') // Automatically make available known static mime types found in given directory. + os.chdir(os.dir(os.executable())) ? app.handle_static('assets', true) vweb.run(app, port) } diff --git a/vlib/builtin/array.v b/vlib/builtin/array.v index 74f942cfc0..e29e50d478 100644 --- a/vlib/builtin/array.v +++ b/vlib/builtin/array.v @@ -124,7 +124,7 @@ fn (mut a array) ensure_cap(required int) { cap *= 2 } new_size := cap * a.element_size - new_data := vcalloc(new_size) + new_data := unsafe { malloc(new_size) } if a.data != voidptr(0) { unsafe { vmemcpy(new_data, a.data, a.len * a.element_size) } // TODO: the old data may be leaked when no GC is used (ref-counting?) @@ -402,12 +402,13 @@ pub fn (mut a array) pop() voidptr { a.len = new_len // Note: a.cap is not changed here *on purpose*, so that // further << ops on that array will be more efficient. - return unsafe { memdup(last_elem, a.element_size) } + return last_elem } // delete_last efficiently deletes the last element of the array. // It does it simply by reducing the length of the array by 1. // If the array is empty, this will panic. +// See also: [trim](#array.trim) pub fn (mut a array) delete_last() { // copy pasting code for performance $if !no_bounds_checking ? { diff --git a/vlib/builtin/string_interpolation.v b/vlib/builtin/string_interpolation.v index b71e380c2f..ec396e2664 100644 --- a/vlib/builtin/string_interpolation.v +++ b/vlib/builtin/string_interpolation.v @@ -660,7 +660,7 @@ pub fn str_intp(data_len int, in_data voidptr) string { mut res := strings.new_builder(256) input_base := &StrIntpData(in_data) for i := 0; i < data_len; i++ { - data := unsafe { &(input_base[i]) } + data := unsafe { &input_base[i] } // avoid empty strings if data.str.len != 0 { res.write_string(data.str) diff --git a/vlib/builtin/wasm_bare/libc_impl.v b/vlib/builtin/wasm_bare/libc_impl.v index bd94945fbd..26f17bd156 100644 --- a/vlib/builtin/wasm_bare/libc_impl.v +++ b/vlib/builtin/wasm_bare/libc_impl.v @@ -5,7 +5,7 @@ module builtin [unsafe] pub fn __malloc(size usize) voidptr { unsafe { - return malloc(int(size)) + return C.malloc(int(size)) } } diff --git a/vlib/rand/rand.v b/vlib/rand/rand.v index 944776ad79..bf2abc0641 100644 --- a/vlib/rand/rand.v +++ b/vlib/rand/rand.v @@ -467,3 +467,20 @@ pub fn hex(len int) string { pub fn ascii(len int) string { return string_from_set(rand.ascii_chars, len) } + +// shuffle randomly permutates the elements in `a`. +pub fn shuffle(mut a []T) { + len := a.len + for i in 0 .. len { + si := i + intn(len - i) or { len } + a[si], a[i] = a[i], a[si] + } +} + +// shuffle_clone returns a random permutation of the elements in `a`. +// The permutation is done on a fresh clone of `a`, so `a` remains unchanged. +pub fn shuffle_clone(a []T) []T { + mut res := a.clone() + shuffle(mut res) + return res +} diff --git a/vlib/rand/random_numbers_test.v b/vlib/rand/random_numbers_test.v index f5f1d61697..e2735339c5 100644 --- a/vlib/rand/random_numbers_test.v +++ b/vlib/rand/random_numbers_test.v @@ -317,3 +317,55 @@ fn test_new_global_rng() { rand.set_rng(old) } + +fn test_shuffle() { + mut arrays := [][]int{} + arrays << [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + arrays << [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] + for seed in seeds { + a := get_n_random_ints(seed, 10) + arrays << a + } + // + mut digits := []map[int]int{len: 10} + for digit in 0 .. 10 { + digits[digit] = {} + for idx in 0 .. 10 { + digits[digit][idx] = 0 + } + } + for mut a in arrays { + o := a.clone() + for _ in 0 .. 100 { + rand.shuffle(mut a) + assert *a != o + for idx in 0 .. 10 { + digits[idx][a[idx]]++ + } + } + } + for digit in 1 .. 10 { + assert digits[0] != digits[digit] + } + for digit in 0 .. 10 { + for idx in 0 .. 10 { + assert digits[digit][idx] > 10 + } + // eprintln('digits[$digit]: ${digits[digit]}') + } +} + +fn test_shuffle_clone() { + original := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + mut a := original.clone() + mut results := [][]int{} + for _ in 0 .. 10 { + results << rand.shuffle_clone(a) + } + assert original == a + for idx in 1 .. 10 { + assert results[idx].len == 10 + assert results[idx] != results[0] + assert results[idx] != original + } +} diff --git a/vlib/strconv/format_mem.c.v b/vlib/strconv/format_mem.c.v index c2229d534d..6ef5762d03 100644 --- a/vlib/strconv/format_mem.c.v +++ b/vlib/strconv/format_mem.c.v @@ -158,29 +158,36 @@ pub fn f64_to_str_lnd1(f f64, dec_digit int) string { // get sign and decimal parts for c in s { - if c == `-` { - sgn = -1 - i++ - } else if c == `+` { - sgn = 1 - i++ - } else if c >= `0` && c <= `9` { - b[i1] = c - i1++ - i++ - } else if c == `.` { - if sgn > 0 { - d_pos = i - } else { - d_pos = i - 1 + match c { + `-` { + sgn = -1 + i++ + } + `+` { + sgn = 1 + i++ + } + `0`...`9` { + b[i1] = c + i1++ + i++ + } + `.` { + if sgn > 0 { + d_pos = i + } else { + d_pos = i - 1 + } + i++ + } + `e` { + i++ + break + } + else { + s.free() + return '[Float conversion error!!]' } - i++ - } else if c == `e` { - i++ - break - } else { - s.free() - return '[Float conversion error!!]' } } b[i1] = 0 @@ -274,10 +281,13 @@ pub fn f64_to_str_lnd1(f f64, dec_digit int) string { // println("r_i-d_pos: ${r_i - d_pos}") if dot_res_sp >= 0 { - if (r_i - dot_res_sp) > dec_digit { - r_i = dot_res_sp + dec_digit + 1 - } + r_i = dot_res_sp + dec_digit + 1 res[r_i] = 0 + for c1 in 1 .. dec_digit + 1 { + if res[r_i - c1] == 0 { + res[r_i - c1] = `0` + } + } // println("result: [${tos(&res[0],r_i)}]") tmp_res := tos(res.data, r_i).clone() res.free() diff --git a/vlib/strings/builder.c.v b/vlib/strings/builder.c.v index 5271d46c8c..2e7e7b1bb0 100644 --- a/vlib/strings/builder.c.v +++ b/vlib/strings/builder.c.v @@ -176,6 +176,28 @@ pub fn (mut b Builder) str() string { return s } +// ensure_cap ensures that the buffer has enough space for at least `n` bytes by growing the buffer if necessary +pub fn (mut b Builder) ensure_cap(n int) { + // code adapted from vlib/builtin/array.v + if n <= b.cap { + return + } + + new_data := vcalloc(n * b.element_size) + if b.data != voidptr(0) { + unsafe { vmemcpy(new_data, b.data, b.len * b.element_size) } + // TODO: the old data may be leaked when no GC is used (ref-counting?) + if b.flags.has(.noslices) { + unsafe { free(b.data) } + } + } + unsafe { + b.data = new_data + b.offset = 0 + b.cap = n + } +} + // free is for manually freeing the contents of the buffer [unsafe] pub fn (mut b Builder) free() { diff --git a/vlib/strings/builder_test.v b/vlib/strings/builder_test.v index 1bcc54a058..151e1b989a 100644 --- a/vlib/strings/builder_test.v +++ b/vlib/strings/builder_test.v @@ -112,3 +112,18 @@ fn test_write_runes() { x := sb.str() assert x == 'hello world' } + +fn test_ensure_cap() { + mut sb := strings.new_builder(0) + assert sb.cap == 0 + sb.ensure_cap(10) + assert sb.cap == 10 + sb.ensure_cap(10) + assert sb.cap == 10 + sb.ensure_cap(15) + assert sb.cap == 15 + sb.ensure_cap(10) + assert sb.cap == 15 + sb.ensure_cap(-1) + assert sb.cap == 15 +} diff --git a/vlib/sync/thread_default.c.v b/vlib/sync/thread_default.c.v new file mode 100644 index 0000000000..81bcf105e8 --- /dev/null +++ b/vlib/sync/thread_default.c.v @@ -0,0 +1,15 @@ +module sync + +fn C.pthread_self() usize + +// thread_id returns a unique identifier for the caller thread. +// All *currently* running threads in the same process, will have *different* thread identifiers. +// Note: if a thread finishes, and another starts, the identifier of the old thread may be +// reused for the newly started thread. +// In other words, thread IDs are guaranteed to be unique only within a process. +// A thread ID may be reused after a terminated thread has been joined (with `t.wait()`), +// or when the thread has terminated. + +pub fn thread_id() u64 { + return u64(C.pthread_self()) +} diff --git a/vlib/sync/thread_test.v b/vlib/sync/thread_test.v new file mode 100644 index 0000000000..797d7e9ea6 --- /dev/null +++ b/vlib/sync/thread_test.v @@ -0,0 +1,22 @@ +import sync + +fn simple_thread() u64 { + tid := sync.thread_id() + eprintln('simple_thread thread_id: $tid.hex()') + return tid +} + +fn test_sync_thread_id() { + mtid := sync.thread_id() + eprintln('main thread_id: $sync.thread_id().hex()') + x := go simple_thread() + y := go simple_thread() + xtid := x.wait() + ytid := y.wait() + eprintln('main thread_id: $sync.thread_id().hex()') + dump(xtid.hex()) + dump(ytid.hex()) + assert mtid != xtid + assert mtid != ytid + assert xtid != ytid +} diff --git a/vlib/sync/thread_windows.c.v b/vlib/sync/thread_windows.c.v new file mode 100644 index 0000000000..33241d5a0f --- /dev/null +++ b/vlib/sync/thread_windows.c.v @@ -0,0 +1,7 @@ +module sync + +fn C.GetCurrentThreadId() u32 + +pub fn thread_id() u64 { + return u64(C.GetCurrentThreadId()) +} diff --git a/vlib/toml/README.md b/vlib/toml/README.md index cdcc475039..8fd07c838f 100644 --- a/vlib/toml/README.md +++ b/vlib/toml/README.md @@ -8,7 +8,8 @@ Parsing files or `string`s containing TOML is easy. Simply import the `toml` module and do: ```v ignore -doc := toml.parse() or { panic(err) } +doc1 := toml.parse_text() or { panic(err) } +doc2 := toml.parse_file() or { panic(err) } ``` ## Example @@ -54,7 +55,7 @@ hosts = [ ]' fn main() { - doc := toml.parse(toml_text) or { panic(err) } + doc := toml.parse_text(toml_text) or { panic(err) } title := doc.value('title').string() println('title: "$title"') ip := doc.value('servers.alpha.ip').string() @@ -91,7 +92,7 @@ array = [ ] ' -doc := toml.parse(toml_text) or { panic(err) } +doc := toml.parse_text(toml_text) or { panic(err) } assert doc.value('val').bool() == true assert doc.value('table.array[0].a').string() == 'A' @@ -142,6 +143,6 @@ array = [ ] ' -doc := toml.parse(toml_text) or { panic(err) } +doc := toml.parse_text(toml_text) or { panic(err) } assert to.json(doc) == '{ "val": true, "table": { "array": [ { "a": "A" }, { "b": "B" } ] } }' ``` diff --git a/vlib/toml/checker/checker.v b/vlib/toml/checker/checker.v index 7c25c12ec7..0f22274bcf 100644 --- a/vlib/toml/checker/checker.v +++ b/vlib/toml/checker/checker.v @@ -415,7 +415,7 @@ pub fn (c Checker) check_quoted(q ast.Quoted) ? { // \UXXXXXXXX - Unicode (U+XXXXXXXX) fn (c Checker) check_quoted_escapes(q ast.Quoted) ? { // Setup a scanner in stack memory for easier navigation. - mut s := scanner.new_simple(q.text) ? + mut s := scanner.new_simple_text(q.text) ? // See https://toml.io/en/v1.0.0#string for more info on string types. is_basic := q.quote == `\"` @@ -552,7 +552,7 @@ fn (c Checker) check_unicode_escape(esc_unicode string) ? { pub fn (c Checker) check_comment(comment ast.Comment) ? { lit := comment.text // Setup a scanner in stack memory for easier navigation. - mut s := scanner.new_simple(lit) ? + mut s := scanner.new_simple_text(lit) ? for { ch := s.next() if ch == scanner.end_of_text { diff --git a/vlib/toml/decoder/decoder.v b/vlib/toml/decoder/decoder.v index cfe19e33b4..2a5e47b166 100644 --- a/vlib/toml/decoder/decoder.v +++ b/vlib/toml/decoder/decoder.v @@ -84,7 +84,7 @@ pub fn decode_quoted_escapes(mut q ast.Quoted) ? { return } - mut s := scanner.new_simple(q.text) ? + mut s := scanner.new_simple_text(q.text) ? q.text = q.text.replace('\\"', '"') for { diff --git a/vlib/toml/input/input.v b/vlib/toml/input/input.v index c2490c0cce..cdce8227c1 100644 --- a/vlib/toml/input/input.v +++ b/vlib/toml/input/input.v @@ -15,6 +15,10 @@ pub: // auto_config returns an, automatic determined, input Config based on heuristics // found in `toml` +// One example of several of why it's deprecated: +// https://discord.com/channels/592103645835821068/592114487759470596/954101934988615721 +[deprecated: 'will be removed and not replaced due to flaky heuristics that leads to hard to find bugs'] +[deprecated_after: '2022-06-18'] pub fn auto_config(toml string) ?Config { mut config := Config{} if !toml.contains('\n') && os.is_file(toml) { @@ -32,7 +36,7 @@ pub fn auto_config(toml string) ?Config { // validate returns an optional error if more than one of the fields // in `Config` has a non-default value (empty string). -pub fn (c Config) validate() ? { +fn (c Config) validate() ? { if c.file_path != '' && c.text != '' { error(@MOD + '.' + @FN + ' ${typeof(c).name} should contain only one of the fields `file_path` OR `text` filled out') @@ -42,9 +46,12 @@ pub fn (c Config) validate() ? { } } +// read_input returns either Config.text or the read file contents of Config.file_path +// depending on which one is not empty. pub fn (c Config) read_input() ?string { + c.validate() ? mut text := c.text - if os.is_file(c.file_path) { + if text == '' && os.is_file(c.file_path) { text = os.read_file(c.file_path) or { return error(@MOD + '.' + @STRUCT + '.' + @FN + ' Could not read "$c.file_path": "$err.msg()"') diff --git a/vlib/toml/scanner/scanner.v b/vlib/toml/scanner/scanner.v index 0bda8081f0..31bc4fdc3d 100644 --- a/vlib/toml/scanner/scanner.v +++ b/vlib/toml/scanner/scanner.v @@ -3,7 +3,6 @@ // that can be found in the LICENSE file. module scanner -import os import math import toml.input import toml.token @@ -47,28 +46,47 @@ pub: tokenize_formatting bool = true // if true, generate tokens for `\n`, ` `, `\t`, `\r` etc. } -// new_scanner returns a new *heap* allocated `Scanner` instance. +// new_scanner returns a new *heap* allocated `Scanner` instance, based on the file in config.input.file_path, +// or based on the text in config.input.text . pub fn new_scanner(config Config) ?&Scanner { - config.input.validate() ? - mut text := config.input.text - file_path := config.input.file_path - if os.is_file(file_path) { - text = os.read_file(file_path) or { - return error(@MOD + '.' + @STRUCT + '.' + @FN + - ' Could not read "$file_path": "$err.msg()"') - } - } mut s := &Scanner{ config: config - text: text + text: config.input.read_input() ? } return s } -// returns a new *stack* allocated `Scanner` instance. -pub fn new_simple(toml_input string) ?Scanner { +// new_simple returns a new *stack* allocated `Scanner` instance. +pub fn new_simple(config Config) ?Scanner { + return Scanner{ + config: config + text: config.input.read_input() ? + } +} + +// new_simple_text returns a new *stack* allocated `Scanner` instance +// ready for parsing TOML in `text`. +pub fn new_simple_text(text string) ?Scanner { + in_config := input.Config{ + text: text + } config := Config{ - input: input.auto_config(toml_input) ? + input: in_config + } + return Scanner{ + config: config + text: config.input.read_input() ? + } +} + +// new_simple_file returns a new *stack* allocated `Scanner` instance +// ready for parsing TOML in file read from `path`. +pub fn new_simple_file(path string) ?Scanner { + in_config := input.Config{ + file_path: path + } + config := Config{ + input: in_config } return Scanner{ config: config diff --git a/vlib/toml/tests/array_of_tables_1_level_test.v b/vlib/toml/tests/array_of_tables_1_level_test.v index 5a0495d4ab..971db6fb3e 100644 --- a/vlib/toml/tests/array_of_tables_1_level_test.v +++ b/vlib/toml/tests/array_of_tables_1_level_test.v @@ -18,7 +18,7 @@ color = "gray"' ) fn test_tables() { - mut toml_doc := toml.parse(toml_table_text) or { panic(err) } + mut toml_doc := toml.parse_text(toml_table_text) or { panic(err) } toml_json := to.json(toml_doc) diff --git a/vlib/toml/tests/array_of_tables_2_level_test.v b/vlib/toml/tests/array_of_tables_2_level_test.v index ae73b8cfe9..4ad2e94837 100644 --- a/vlib/toml/tests/array_of_tables_2_level_test.v +++ b/vlib/toml/tests/array_of_tables_2_level_test.v @@ -22,13 +22,13 @@ name = "Born in the USA" name = "Dancing in the Dark"' ) -fn test_nested_array_of_tables() { - mut toml_doc := toml.parse(toml_text) or { panic(err) } +const fprefix = os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.')) + +fn test_nested_array_of_tables() ? { + mut toml_doc := toml.parse_text(toml_text) ? toml_json := to.json(toml_doc) - eprintln(toml_json) - assert toml_json == os.read_file( - os.real_path(os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.'))) + - '.out') or { panic(err) } + + assert toml_json == os.read_file(fprefix + '.out') ? } diff --git a/vlib/toml/tests/array_of_tables_array_test.v b/vlib/toml/tests/array_of_tables_array_test.v index 5a005c9884..32671dc000 100644 --- a/vlib/toml/tests/array_of_tables_array_test.v +++ b/vlib/toml/tests/array_of_tables_array_test.v @@ -14,7 +14,7 @@ const ( ) fn test_nested_array_of_tables() { - mut toml_doc := toml.parse(toml_text) or { panic(err) } + mut toml_doc := toml.parse_text(toml_text) or { panic(err) } toml_json := to.json(toml_doc) diff --git a/vlib/toml/tests/array_of_tables_edge_case_1_test.v b/vlib/toml/tests/array_of_tables_edge_case_1_test.v index f4f18dac29..92e5e66235 100644 --- a/vlib/toml/tests/array_of_tables_edge_case_1_test.v +++ b/vlib/toml/tests/array_of_tables_edge_case_1_test.v @@ -6,7 +6,7 @@ fn test_array_of_tables_edge_case_file() { toml_file := os.real_path(os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.'))) + '.toml' - toml_doc := toml.parse(toml_file) or { panic(err) } + toml_doc := toml.parse_file(toml_file) or { panic(err) } toml_json := to.json(toml_doc) out_file := diff --git a/vlib/toml/tests/array_of_tables_edge_case_2_test.v b/vlib/toml/tests/array_of_tables_edge_case_2_test.v index f4f18dac29..0c30e8bfc9 100644 --- a/vlib/toml/tests/array_of_tables_edge_case_2_test.v +++ b/vlib/toml/tests/array_of_tables_edge_case_2_test.v @@ -2,17 +2,14 @@ import os import toml import toml.to -fn test_array_of_tables_edge_case_file() { - toml_file := - os.real_path(os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.'))) + - '.toml' - toml_doc := toml.parse(toml_file) or { panic(err) } +const fprefix = os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.')) + +fn test_array_of_tables_edge_case_file() ? { + toml_doc := toml.parse_file(os.real_path(fprefix + '.toml')) ? toml_json := to.json(toml_doc) - out_file := - os.real_path(os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.'))) + - '.out' - out_file_json := os.read_file(out_file) or { panic(err) } + + out_file_json := os.read_file(os.real_path(fprefix + '.out')) ? println(toml_json) assert toml_json == out_file_json } diff --git a/vlib/toml/tests/compact_test.v b/vlib/toml/tests/compact_test.v index c9ebc5b7f1..d662749261 100644 --- a/vlib/toml/tests/compact_test.v +++ b/vlib/toml/tests/compact_test.v @@ -29,7 +29,7 @@ hosts = [ ]' fn test_parse_compact_text() { - toml_doc := toml.parse(toml_text) or { panic(err) } + toml_doc := toml.parse_text(toml_text) or { panic(err) } title := toml_doc.value('title') assert title == toml.Any('TOML Example') diff --git a/vlib/toml/tests/crlf_test.v b/vlib/toml/tests/crlf_test.v index ac95c8f9d3..07960c9385 100644 --- a/vlib/toml/tests/crlf_test.v +++ b/vlib/toml/tests/crlf_test.v @@ -4,7 +4,7 @@ fn test_crlf() { str_value := 'test string' mut toml_txt := 'crlf_string = "test string"\r\n # Comment with CRLF is not allowed' - toml_doc := toml.parse(toml_txt) or { panic(err) } + toml_doc := toml.parse_text(toml_txt) or { panic(err) } value := toml_doc.value('crlf_string') assert value == toml.Any(str_value) @@ -16,7 +16,7 @@ fn test_crlf_is_parsable_just_like_lf() ? { crlf_content := '# a comment\r\ntitle = "TOML Example"\r\n[database]\r\nserver = "192.168.1.1"\r\nports = [ 8000, 8001, 8002 ]\r\n' all := [crlf_content, crlf_content.replace('\r\n', '\n')] for content in all { - res := toml.parse(content) ? + res := toml.parse_text(content) ? assert res.value('title') == toml.Any('TOML Example') assert (res.value('database') as map[string]toml.Any)['server'] ? == toml.Any('192.168.1.1') } diff --git a/vlib/toml/tests/datetime_test.v b/vlib/toml/tests/datetime_test.v index 61d0269e06..4d656566c3 100644 --- a/vlib/toml/tests/datetime_test.v +++ b/vlib/toml/tests/datetime_test.v @@ -16,7 +16,7 @@ fn test_dates() { lt1 = 07:32:00 lt2 = 00:32:00.999999 ' - toml_doc := toml.parse(toml_txt) or { panic(err) } + toml_doc := toml.parse_text(toml_txt) or { panic(err) } // Re-use vars mut odt_time := toml.DateTime{'1979-05-27T07:32:00Z'} diff --git a/vlib/toml/tests/default_to_test.v b/vlib/toml/tests/default_to_test.v index 2bf2283f43..d4634d1925 100644 --- a/vlib/toml/tests/default_to_test.v +++ b/vlib/toml/tests/default_to_test.v @@ -3,7 +3,7 @@ import toml fn test_default_to() { default_value := 4321 mut toml_txt := 'var = 1234' - toml_doc := toml.parse(toml_txt) or { panic(err) } + toml_doc := toml.parse_text(toml_txt) or { panic(err) } value := toml_doc.value('tar').default_to(default_value).int() assert value == default_value } diff --git a/vlib/toml/tests/json_encoding_test.v b/vlib/toml/tests/json_encoding_test.v index bc58383c74..9368820348 100644 --- a/vlib/toml/tests/json_encoding_test.v +++ b/vlib/toml/tests/json_encoding_test.v @@ -2,17 +2,14 @@ import os import toml import toml.to -fn test_parse() { - toml_file := - os.real_path(os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.'))) + - '.toml' - toml_doc := toml.parse(toml_file) or { panic(err) } +const fprefix = os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.')) + +fn test_parse() ? { + toml_doc := toml.parse_file(os.real_path(fprefix + '.toml')) ? toml_json := to.json(toml_doc) - out_file := - os.real_path(os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.'))) + - '.out' - out_file_json := os.read_file(out_file) or { panic(err) } println(toml_json) + + out_file_json := os.read_file(os.real_path(fprefix + '.out')) ? assert toml_json == out_file_json } diff --git a/vlib/toml/tests/json_test.v b/vlib/toml/tests/json_test.v index bc58383c74..15b4190328 100644 --- a/vlib/toml/tests/json_test.v +++ b/vlib/toml/tests/json_test.v @@ -6,7 +6,7 @@ fn test_parse() { toml_file := os.real_path(os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.'))) + '.toml' - toml_doc := toml.parse(toml_file) or { panic(err) } + toml_doc := toml.parse_file(toml_file) or { panic(err) } toml_json := to.json(toml_doc) out_file := diff --git a/vlib/toml/tests/key_test.v b/vlib/toml/tests/key_test.v index d624c4d4fd..ceb3c8906b 100644 --- a/vlib/toml/tests/key_test.v +++ b/vlib/toml/tests/key_test.v @@ -6,7 +6,7 @@ fn test_keys() { toml_file := os.real_path(os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.'))) + '.toml' - toml_doc := toml.parse(toml_file) or { panic(err) } + toml_doc := toml.parse_file(toml_file) or { panic(err) } mut value := toml_doc.value('34-11') assert value.int() == 23 diff --git a/vlib/toml/tests/large_toml_file_test.v b/vlib/toml/tests/large_toml_file_test.v index 51daa01dc3..3bb4c30440 100644 --- a/vlib/toml/tests/large_toml_file_test.v +++ b/vlib/toml/tests/large_toml_file_test.v @@ -15,7 +15,7 @@ fn test_large_file() { '.toml' if os.exists(toml_file) { println('Testing parsing of large (${os.file_size(toml_file)} bytes) "$toml_file"...') - toml_doc := toml.parse(toml_file) or { panic(err) } + toml_doc := toml.parse_file(toml_file) or { panic(err) } println('OK [1/1] "$toml_file"...') // So it can be checked with `v -stats test ...` } } diff --git a/vlib/toml/tests/nested_test.v b/vlib/toml/tests/nested_test.v index dd0390e415..82950ef6de 100644 --- a/vlib/toml/tests/nested_test.v +++ b/vlib/toml/tests/nested_test.v @@ -25,7 +25,7 @@ enabled = true ' fn test_parse() { - toml_doc := toml.parse(toml_text) or { panic(err) } + toml_doc := toml.parse_text(toml_text) or { panic(err) } // dump(toml_doc.ast) // assert false diff --git a/vlib/toml/tests/quoted_keys_test.v b/vlib/toml/tests/quoted_keys_test.v index 506668eada..3625e6e2fc 100644 --- a/vlib/toml/tests/quoted_keys_test.v +++ b/vlib/toml/tests/quoted_keys_test.v @@ -3,7 +3,7 @@ import toml fn test_quoted_keys() { str_value := 'V rocks!' toml_txt := 'a."b.c" = "V rocks!"' - toml_doc := toml.parse(toml_txt) or { panic(err) } + toml_doc := toml.parse_text(toml_txt) or { panic(err) } value := toml_doc.value('a."b.c"') assert value == toml.Any(str_value) diff --git a/vlib/toml/tests/reflect_test.v b/vlib/toml/tests/reflect_test.v index d166a637dd..e2a1bd1562 100644 --- a/vlib/toml/tests/reflect_test.v +++ b/vlib/toml/tests/reflect_test.v @@ -50,7 +50,7 @@ mut: } fn test_reflect() { - toml_doc := toml.parse(toml_text) or { panic(err) } + toml_doc := toml.parse_text(toml_text) or { panic(err) } mut user := toml_doc.reflect() user.bio = toml_doc.value('bio').reflect() diff --git a/vlib/toml/tests/spaced_keys_test.v b/vlib/toml/tests/spaced_keys_test.v index f76efdf3d5..b9ea4f4685 100644 --- a/vlib/toml/tests/spaced_keys_test.v +++ b/vlib/toml/tests/spaced_keys_test.v @@ -12,7 +12,7 @@ fn test_spaced_keys() { [ tube . test . "test.test" ] h . "i.j." . "k" = "Cryptic" ' - toml_doc := toml.parse(toml_txt) or { panic(err) } + toml_doc := toml.parse_text(toml_txt) or { panic(err) } mut value := toml_doc.value('a."b.c"[0].d.e') assert value == toml.Any(str_value) assert value as string == str_value diff --git a/vlib/toml/tests/strings_test.v b/vlib/toml/tests/strings_test.v index a96b2e6574..76548de0ac 100644 --- a/vlib/toml/tests/strings_test.v +++ b/vlib/toml/tests/strings_test.v @@ -33,7 +33,7 @@ long = "\U000003B4"' ) fn test_multiline_strings() { - mut toml_doc := toml.parse(toml_multiline_text_1) or { panic(err) } + mut toml_doc := toml.parse_text(toml_multiline_text_1) or { panic(err) } mut value := toml_doc.value('multi1') assert value.string() == 'one' @@ -44,7 +44,7 @@ fn test_multiline_strings() { value = toml_doc.value('multi4') assert value.string() == 'one\ntwo\nthree\nfour\n' - toml_doc = toml.parse(toml_multiline_text_2) or { panic(err) } + toml_doc = toml.parse_text(toml_multiline_text_2) or { panic(err) } value = toml_doc.value('multi1') assert value.string() == 'one' value = toml_doc.value('multi2') @@ -57,7 +57,7 @@ fn test_multiline_strings() { toml_file := os.real_path(os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.'))) + '.toml' - toml_doc = toml.parse(toml_file) or { panic(err) } + toml_doc = toml.parse_file(toml_file) or { panic(err) } value = toml_doc.value('lit_one') assert value.string() == "'one quote'" value = toml_doc.value('lit_two') @@ -69,7 +69,7 @@ fn test_multiline_strings() { } fn test_unicode_escapes() { - mut toml_doc := toml.parse(toml_unicode_escapes) or { panic(err) } + mut toml_doc := toml.parse_text(toml_unicode_escapes) or { panic(err) } mut value := toml_doc.value('short') assert value.string() == '\u03B4' // <- This escape is handled by V @@ -81,7 +81,7 @@ fn test_literal_strings() { toml_file := os.real_path(os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.'))) + '.toml' - toml_doc := toml.parse(toml_file) or { panic(err) } + toml_doc := toml.parse_file(toml_file) or { panic(err) } assert toml_doc.value('lit1').string() == r'\' // '\' assert toml_doc.value('lit2').string() == r'\\' // '\\' diff --git a/vlib/toml/tests/table_test.v b/vlib/toml/tests/table_test.v index 43ade350b4..c10d6a1167 100644 --- a/vlib/toml/tests/table_test.v +++ b/vlib/toml/tests/table_test.v @@ -27,7 +27,7 @@ T = {a.b=2}' ) fn test_tables() { - mut toml_doc := toml.parse(toml_table_text) or { panic(err) } + mut toml_doc := toml.parse_text(toml_table_text) or { panic(err) } mut value := toml_doc.value('inline.a.b') assert value.int() == 42 diff --git a/vlib/toml/tests/toml_bom_test.v b/vlib/toml/tests/toml_bom_test.v index fda33f63a1..9ccf1fc614 100644 --- a/vlib/toml/tests/toml_bom_test.v +++ b/vlib/toml/tests/toml_bom_test.v @@ -17,7 +17,7 @@ const ( ) fn test_toml_with_bom() { - toml_doc := toml.parse(toml_text_with_utf8_bom) or { panic(err) } + toml_doc := toml.parse_text(toml_text_with_utf8_bom) or { panic(err) } toml_json := to.json(toml_doc) title := toml_doc.value('title') @@ -36,13 +36,13 @@ fn test_toml_with_bom() { // Re-cycle bad_toml_doc mut bad_toml_doc := empty_toml_document - bad_toml_doc = toml.parse(toml_text_with_utf16_bom) or { + bad_toml_doc = toml.parse_text(toml_text_with_utf16_bom) or { println(' $err.msg()') assert true empty_toml_document } - bad_toml_doc = toml.parse(toml_text_with_utf32_bom) or { + bad_toml_doc = toml.parse_text(toml_text_with_utf32_bom) or { println(' $err.msg()') assert true empty_toml_document diff --git a/vlib/toml/tests/toml_memory_corruption_test.v b/vlib/toml/tests/toml_memory_corruption_test.v index 9e0c518e79..b54e9398bb 100644 --- a/vlib/toml/tests/toml_memory_corruption_test.v +++ b/vlib/toml/tests/toml_memory_corruption_test.v @@ -7,7 +7,7 @@ const toml_text = os.read_file(os.real_path(os.join_path(os.dir(@FILE), 'testdat '.toml') or { panic(err) } fn test_toml_known_memory_corruption() { - toml_doc := toml.parse(toml_text) or { panic(err) } + toml_doc := toml.parse_text(toml_text) or { panic(err) } owner := toml_doc.value('owner') as map[string]toml.Any any_name := owner.value('name') @@ -34,7 +34,7 @@ fn test_toml_known_memory_corruption_2() { lt1 = 07:32:00 lt2 = 00:32:00.999999 ' - toml_doc := toml.parse(toml_txt) or { panic(err) } + toml_doc := toml.parse_text(toml_txt) or { panic(err) } // ldt1 test section odt_time := toml.DateTime{'1979-05-27T07:32:00'} diff --git a/vlib/toml/tests/toml_test.v b/vlib/toml/tests/toml_test.v index 51f1dd4310..ceb08f475d 100644 --- a/vlib/toml/tests/toml_test.v +++ b/vlib/toml/tests/toml_test.v @@ -9,7 +9,7 @@ const toml_text = os.read_file( fn test_toml() { // File containing the complete text from the example in the official TOML project README.md: // https://github.com/toml-lang/toml/blob/3b11f6921da7b6f5db37af039aa021fee450c091/README.md#Example - toml_doc := toml.parse(toml_text) or { panic(err) } + toml_doc := toml.parse_text(toml_text) or { panic(err) } toml_json := to.json(toml_doc) // NOTE Kept for easier debugging: @@ -98,7 +98,7 @@ fn test_toml_parse_text() { } fn test_toml_parse() { - toml_doc := toml.parse(toml_text) or { panic(err) } + toml_doc := toml.parse_text(toml_text) or { panic(err) } toml_json := to.json(toml_doc) assert toml_json == os.read_file( os.real_path(os.join_path(os.dir(@FILE), 'testdata', os.file_name(@FILE).all_before_last('.'))) + diff --git a/vlib/toml/tests/toml_types_test.v b/vlib/toml/tests/toml_types_test.v index c870d6970d..2564299ac6 100644 --- a/vlib/toml/tests/toml_types_test.v +++ b/vlib/toml/tests/toml_types_test.v @@ -4,7 +4,7 @@ import strconv fn test_string() { str_value := 'test string' toml_txt := 'string = "test string"' - toml_doc := toml.parse(toml_txt) or { panic(err) } + toml_doc := toml.parse_text(toml_txt) or { panic(err) } value := toml_doc.value('string') assert value == toml.Any(str_value) @@ -14,7 +14,7 @@ fn test_string() { fn test_i64() { toml_txt := 'i64 = 120' - toml_doc := toml.parse(toml_txt) or { panic(err) } + toml_doc := toml.parse_text(toml_txt) or { panic(err) } value := toml_doc.value('i64') assert value == toml.Any(i64(120)) @@ -26,7 +26,7 @@ fn test_bool() { toml_txt := ' bool_true = true bool_false = false' - toml_doc := toml.parse(toml_txt) or { panic(err) } + toml_doc := toml.parse_text(toml_txt) or { panic(err) } value_true := toml_doc.value('bool_true') assert value_true == toml.Any(true) @@ -46,7 +46,7 @@ bool_false = false' fn test_bool_key_is_not_value() { toml_txt := 'true = true false = false' - toml_doc := toml.parse(toml_txt) or { panic(err) } + toml_doc := toml.parse_text(toml_txt) or { panic(err) } value_true := toml_doc.value('true') assert value_true == toml.Any(true) @@ -64,7 +64,7 @@ false = false' fn test_single_letter_key() { toml_txt := '[v] open_sourced = "Jun 22 2019 20:20:28"' - toml_doc := toml.parse(toml_txt) or { panic(err) } + toml_doc := toml.parse_text(toml_txt) or { panic(err) } value := toml_doc.value('v.open_sourced').string() assert value == 'Jun 22 2019 20:20:28' @@ -74,7 +74,7 @@ fn test_hex_values() { // Regression test // '0xb' is carefully chosen to include the 'b' character that also denotes binary via 0b prefix. toml_txt := 'hex = 0xb' - toml_doc := toml.parse(toml_txt) or { panic(err) } + toml_doc := toml.parse_text(toml_txt) or { panic(err) } value := toml_doc.value('hex') assert value as i64 == 11 @@ -85,7 +85,7 @@ fn test_comment_as_last_value() { toml_txt := ' test = 42 # this line has comment as last thing' - toml_doc := toml.parse(toml_txt) or { panic(err) } + toml_doc := toml.parse_text(toml_txt) or { panic(err) } value := toml_doc.value('test') assert value as i64 == 42 @@ -93,31 +93,31 @@ test = 42 } fn test_nan_and_inf_values() { - mut toml_doc := toml.parse('nan = nan') or { panic(err) } + mut toml_doc := toml.parse_text('nan = nan') or { panic(err) } mut value := toml_doc.value('nan') assert value.string() == 'nan' - toml_doc = toml.parse('nan = nan#comment') or { panic(err) } + toml_doc = toml.parse_text('nan = nan#comment') or { panic(err) } value = toml_doc.value('nan') assert value.string() == 'nan' - toml_doc = toml.parse('nan = -nan') or { panic(err) } + toml_doc = toml.parse_text('nan = -nan') or { panic(err) } value = toml_doc.value('nan') assert value.string() == 'nan' - toml_doc = toml.parse('nan = +nan') or { panic(err) } + toml_doc = toml.parse_text('nan = +nan') or { panic(err) } value = toml_doc.value('nan') assert value.string() == 'nan' - toml_doc = toml.parse('inf = inf') or { panic(err) } + toml_doc = toml.parse_text('inf = inf') or { panic(err) } value = toml_doc.value('inf') assert value.u64() == strconv.double_plus_infinity - toml_doc = toml.parse('inf = +inf') or { panic(err) } + toml_doc = toml.parse_text('inf = +inf') or { panic(err) } value = toml_doc.value('inf') assert value.u64() == strconv.double_plus_infinity - toml_doc = toml.parse('inf = -inf') or { panic(err) } + toml_doc = toml.parse_text('inf = -inf') or { panic(err) } value = toml_doc.value('inf') assert value.u64() == strconv.double_minus_infinity } diff --git a/vlib/toml/tests/value_query_test.v b/vlib/toml/tests/value_query_test.v index 1c8312368a..b7d6d76bea 100644 --- a/vlib/toml/tests/value_query_test.v +++ b/vlib/toml/tests/value_query_test.v @@ -50,7 +50,7 @@ colors = [ ) fn test_value_query_in_array() { - toml_doc := toml.parse(toml_text) or { panic(err) } + toml_doc := toml.parse_text(toml_text) or { panic(err) } mut value := toml_doc.value('themes[0].colors[1]').string() assert value == 'black' value = toml_doc.value('themes[1].colors[0]').string() @@ -67,7 +67,7 @@ fn test_value_query_in_array() { } fn test_any_value_query() { - toml_doc := toml.parse(toml_text) or { panic(err) } + toml_doc := toml.parse_text(toml_text) or { panic(err) } themes := toml_doc.value('themes') assert themes.value('[0].colors[0]').string() == 'red' @@ -94,7 +94,7 @@ fn test_any_value_query() { } fn test_inf_and_nan_query() { - toml_doc := toml.parse(toml_text) or { panic(err) } + toml_doc := toml.parse_text(toml_text) or { panic(err) } value := toml_doc.value('values.nan').string() assert value == 'nan' @@ -106,7 +106,7 @@ fn test_inf_and_nan_query() { } fn test_any_value_query_2() { - toml_doc := toml.parse(toml_text_2) or { panic(err) } + toml_doc := toml.parse_text(toml_text_2) or { panic(err) } defaults := toml_doc.value('defaults') assert defaults.value('run.flags[0]').string() == '-f 1' assert defaults.value('env[0].RUN_TIME').int() == 5 diff --git a/vlib/toml/toml.v b/vlib/toml/toml.v index 5259c2f149..43cd4eb4f5 100644 --- a/vlib/toml/toml.v +++ b/vlib/toml/toml.v @@ -108,6 +108,8 @@ pub fn parse_text(text string) ?Doc { // parse parses the TOML document provided in `toml`. // parse automatically try to determine if the type of `toml` is a file or text. // For explicit parsing of input types see `parse_file` or `parse_text`. +[deprecated: 'use parse_file or parse_text instead'] +[deprecated_after: '2022-06-18'] pub fn parse(toml string) ?Doc { mut input_config := input.auto_config(toml) ? scanner_config := scanner.Config{ diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index b0a97f7b6a..a983a61e28 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -557,6 +557,7 @@ pub mut: name string // left.name() is_method bool is_field bool // temp hack, remove ASAP when re-impl CallExpr / Selector (joe) + is_fn_var bool // fn variable is_keep_alive bool // GC must not free arguments before fn returns is_noreturn bool // whether the function/method is marked as [noreturn] is_ctor_new bool // if JS ctor calls requires `new` before call, marked as `[use_new]` in V @@ -568,6 +569,7 @@ pub mut: left_type Type // type of `user` receiver_type Type // User return_type Type + fn_var_type Type // fn variable type should_be_skipped bool // true for calls to `[if someflag?]` functions, when there is no `-d someflag` concrete_types []Type // concrete types, e.g. concrete_list_pos token.Pos @@ -1692,10 +1694,10 @@ pub: [inline] pub fn (expr Expr) is_blank_ident() bool { - match expr { - Ident { return expr.kind == .blank_ident } - else { return false } + if expr is Ident { + return expr.kind == .blank_ident } + return false } pub fn (expr Expr) pos() token.Pos { diff --git a/vlib/v/ast/types.v b/vlib/v/ast/types.v index b213a5fb43..51932ead65 100644 --- a/vlib/v/ast/types.v +++ b/vlib/v/ast/types.v @@ -257,10 +257,6 @@ fn (ts TypeSymbol) dbg_common(mut res []string) { res << 'language: $ts.language' } -pub fn (t Type) str() string { - return 'ast.Type(0x$t.hex() = ${u32(t)})' -} - pub fn (t &Table) type_str(typ Type) string { sym := t.sym(typ) return sym.name @@ -980,6 +976,12 @@ pub fn (mytable &Table) type_to_code(t Type) string { } } +// clean type name from generics form. From Type -> Type +pub fn (t &Table) clean_generics_type_str(typ Type) string { + result := t.type_to_str(typ) + return result.all_before('<') +} + // import_aliases is a map of imported symbol aliases 'module.Type' => 'Type' pub fn (t &Table) type_to_str_using_aliases(typ Type, import_aliases map[string]string) string { sym := t.sym(typ) @@ -1186,7 +1188,7 @@ pub fn (t &Table) fn_signature_using_aliases(func &Fn, import_aliases map[string // TODO write receiver } if !opts.type_only { - sb.write_string('$func.name') + sb.write_string(func.name) } sb.write_string('(') start := int(opts.skip_receiver) @@ -1203,22 +1205,38 @@ pub fn (t &Table) fn_signature_using_aliases(func &Fn, import_aliases map[string sb.write_string('mut ') } if !opts.type_only { - sb.write_string('$param.name ') + sb.write_string(param.name) + sb.write_string(' ') } styp := t.type_to_str_using_aliases(typ, import_aliases) if i == func.params.len - 1 && func.is_variadic { - sb.write_string('...$styp') + sb.write_string('...') + sb.write_string(styp) } else { - sb.write_string('$styp') + sb.write_string(styp) } } sb.write_string(')') if func.return_type != ast.void_type { - sb.write_string(' ${t.type_to_str_using_aliases(func.return_type, import_aliases)}') + sb.write_string(' ') + sb.write_string(t.type_to_str_using_aliases(func.return_type, import_aliases)) } return sb.str() } +// Get the name of the complete quanlified name of the type +// without the generic parts. +pub fn (t &TypeSymbol) symbol_name_except_generic() string { + // main.Abc + mut embed_name := t.name + // remove generic part from name + // main.Abc => main.Abc + if embed_name.contains('<') { + embed_name = embed_name.all_before('<') + } + return embed_name +} + pub fn (t &TypeSymbol) embed_name() string { // main.Abc => Abc mut embed_name := t.name.split('.').last() diff --git a/vlib/v/checker/check_types.v b/vlib/v/checker/check_types.v index 355a89d86d..48cc1a5a1a 100644 --- a/vlib/v/checker/check_types.v +++ b/vlib/v/checker/check_types.v @@ -188,7 +188,39 @@ pub fn (mut c Checker) check_expected_call_arg(got ast.Type, expected_ ast.Type, return } } - return error('cannot use `${c.table.type_to_str(got.clear_flag(.variadic))}` as `${c.table.type_to_str(expected.clear_flag(.variadic))}`') + + // Check on Generics types, there are some case where we have the following case + // `&Type == &Type<>`. This is a common case we are implementing a function + // with generic parameters like `compare(bst Bst node) {}` + got_typ_sym := c.table.sym(got) + got_typ_str := c.table.type_to_str(got.clear_flag(.variadic)) + expected_typ_sym := c.table.sym(expected_) + expected_typ_str := c.table.type_to_str(expected.clear_flag(.variadic)) + + if got_typ_sym.symbol_name_except_generic() == expected_typ_sym.symbol_name_except_generic() { + // Check if we are making a comparison between two different types of + // the same type like `Type and &Type<>` + if (got.is_ptr() != expected.is_ptr()) || !c.check_same_module(got, expected) { + return error('cannot use `$got_typ_str` as `$expected_typ_str`') + } + return + } + return error('cannot use `$got_typ_str` as `$expected_typ_str`') +} + +// helper method to check if the type is of the same module. +// FIXME(vincenzopalazzo) This is a work around to the issue +// explained in the https://github.com/vlang/v/pull/13718#issuecomment-1074517800 +fn (c Checker) check_same_module(got ast.Type, expected ast.Type) bool { + clean_got_typ := c.table.clean_generics_type_str(got.clear_flag(.variadic)).all_before('<') + clean_expected_typ := c.table.clean_generics_type_str(expected.clear_flag(.variadic)).all_before('<') + if clean_got_typ == clean_expected_typ { + return true + // The following if confition should catch the bugs descripted in the issue + } else if clean_expected_typ.all_after('.') == clean_got_typ.all_after('.') { + return true + } + return false } pub fn (mut c Checker) check_basic(got ast.Type, expected ast.Type) bool { @@ -580,7 +612,12 @@ pub fn (mut c Checker) string_inter_lit(mut node ast.StringInterLiteral) ast.Typ } c.fail_if_unreadable(expr, ftyp, 'interpolation object') node.expr_types << ftyp - typ := c.table.unalias_num_type(ftyp) + ftyp_sym := c.table.sym(ftyp) + typ := if ftyp_sym.kind == .alias && !ftyp_sym.has_method('str') { + c.table.unalias_num_type(ftyp) + } else { + ftyp + } mut fmt := node.fmts[i] // analyze and validate format specifier if fmt !in [`E`, `F`, `G`, `e`, `f`, `g`, `d`, `u`, `x`, `X`, `o`, `c`, `s`, `S`, `p`, diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 1d0d4fcded..bb8f1b453e 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -91,9 +91,10 @@ pub mut: inside_fn_arg bool // `a`, `b` in `a.f(b)` inside_ct_attr bool // true inside `[if expr]` inside_comptime_for_field bool - skip_flags bool // should `#flag` and `#include` be skipped - fn_level int // 0 for the top level, 1 for `fn abc() {}`, 2 for a nested fn, etc - smartcast_mut_pos token.Pos + skip_flags bool // should `#flag` and `#include` be skipped + fn_level int // 0 for the top level, 1 for `fn abc() {}`, 2 for a nested fn, etc + smartcast_mut_pos token.Pos // match mut foo, if mut foo is Foo + smartcast_cond_pos token.Pos // match cond ct_cond_stack []ast.Expr mut: stmt_level int // the nesting level inside each stmts list; @@ -594,12 +595,20 @@ pub fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { match mut node.left { ast.Ident, ast.SelectorExpr { if node.left.is_mut { - c.error('remove unnecessary `mut`', node.left.mut_pos) + c.error('the `mut` keyword is invalid here', node.left.mut_pos) } } else {} } } + match mut node.right { + ast.Ident, ast.SelectorExpr { + if node.right.is_mut { + c.error('the `mut` keyword is invalid here', node.right.mut_pos) + } + } + else {} + } eq_ne := node.op in [.eq, .ne] // Single side check // Place these branches according to ops' usage frequency to accelerate. @@ -1522,41 +1531,36 @@ fn (mut c Checker) check_or_last_stmt(stmt ast.Stmt, ret_type ast.Type, expr_ret stmt.pos) } } - } else { - match stmt { - ast.ExprStmt { - match stmt.expr { - ast.IfExpr { - for branch in stmt.expr.branches { - last_stmt := branch.stmts[branch.stmts.len - 1] - c.check_or_last_stmt(last_stmt, ret_type, expr_return_type) - } - } - ast.MatchExpr { - for branch in stmt.expr.branches { - last_stmt := branch.stmts[branch.stmts.len - 1] - c.check_or_last_stmt(last_stmt, ret_type, expr_return_type) - } - } - else { - if stmt.typ == ast.void_type { - return - } - if is_noreturn_callexpr(stmt.expr) { - return - } - if c.check_types(stmt.typ, expr_return_type) { - return - } - // opt_returning_string() or { ... 123 } - type_name := c.table.type_to_str(stmt.typ) - expr_return_type_name := c.table.type_to_str(expr_return_type) - c.error('the default expression type in the `or` block should be `$expr_return_type_name`, instead you gave a value of type `$type_name`', - stmt.expr.pos()) - } + } else if stmt is ast.ExprStmt { + match stmt.expr { + ast.IfExpr { + for branch in stmt.expr.branches { + last_stmt := branch.stmts[branch.stmts.len - 1] + c.check_or_last_stmt(last_stmt, ret_type, expr_return_type) } } - else {} + ast.MatchExpr { + for branch in stmt.expr.branches { + last_stmt := branch.stmts[branch.stmts.len - 1] + c.check_or_last_stmt(last_stmt, ret_type, expr_return_type) + } + } + else { + if stmt.typ == ast.void_type { + return + } + if is_noreturn_callexpr(stmt.expr) { + return + } + if c.check_types(stmt.typ, expr_return_type) { + return + } + // opt_returning_string() or { ... 123 } + type_name := c.table.type_to_str(stmt.typ) + expr_return_type_name := c.table.type_to_str(expr_return_type) + c.error('the default expression type in the `or` block should be `$expr_return_type_name`, instead you gave a value of type `$type_name`', + stmt.expr.pos()) + } } } } @@ -1761,6 +1765,10 @@ pub fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type { c.note('smartcasting requires either an immutable value, or an explicit mut keyword before the value', c.smartcast_mut_pos) } + if c.smartcast_cond_pos != token.Pos{} { + c.note('smartcast can only be used on the ident or selector, e.g. match foo, match foo.bar', + c.smartcast_cond_pos) + } c.error(unknown_field_msg, node.pos) } return ast.void_type @@ -2089,6 +2097,10 @@ fn (mut c Checker) global_decl(mut node ast.GlobalDecl) { c.error('unknown type `$sym.name`', field.typ_pos) } if field.has_expr { + if field.expr is ast.AnonFn && field.name == 'main' { + c.error('the `main` function is the program entry point, cannot redefine it', + field.pos) + } field.typ = c.expr(field.expr) mut v := c.file.global_scope.find_global(field.name) or { panic('internal compiler error - could not find global in scope') @@ -3349,7 +3361,9 @@ fn (mut c Checker) smartcast(expr_ ast.Expr, cur_type ast.Type, to_type_ ast.Typ c.smartcast_mut_pos = expr.pos } } - else {} + else { + c.smartcast_cond_pos = expr.pos() + } } } @@ -3765,6 +3779,9 @@ pub fn (mut c Checker) index_expr(mut node ast.IndexExpr) ast.Type { && typ !in [ast.byteptr_type, ast.charptr_type] && !typ.has_flag(.variadic) { c.error('type `$typ_sym.name` does not support indexing', node.pos) } + if typ.has_flag(.optional) { + c.error('type `?$typ_sym.name` is optional, it does not support indexing', node.left.pos()) + } if typ_sym.kind == .string && !typ.is_ptr() && node.is_setter { c.error('cannot assign to s[i] since V strings are immutable\n' + '(note, that variables may be mutable but string values are always immutable, like in Go and Java)', diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 15177508fe..355be70ecf 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -352,32 +352,12 @@ fn (mut c Checker) anon_fn(mut node ast.AnonFn) ast.Type { } pub fn (mut c Checker) call_expr(mut node ast.CallExpr) ast.Type { - // First check everything that applies to both fns and methods // TODO merge logic from method_call and fn_call - /* - for i, call_arg in node.args { - if call_arg.is_mut { - c.fail_if_immutable(call_arg.expr) - if !arg.is_mut { - tok := call_arg.share.str() - c.error('`$node.name` parameter `$arg.name` is not `$tok`, `$tok` is not needed`', - call_arg.expr.pos()) - } else if arg.typ.share() != call_arg.share { - c.error('wrong shared type', call_arg.expr.pos()) - } - } else { - if arg.is_mut && (!call_arg.is_mut || arg.typ.share() != call_arg.share) { - tok := call_arg.share.str() - c.error('`$node.name` parameter `$arg.name` is `$tok`, you need to provide `$tok` e.g. `$tok arg${i+1}`', - call_arg.expr.pos()) - } - } - } - */ - // Now call `method_call` or `fn_call` for specific checks. + // First check everything that applies to both fns and methods old_inside_fn_arg := c.inside_fn_arg c.inside_fn_arg = true mut continue_check := true + // Now call `method_call` or `fn_call` for specific checks. typ := if node.is_method { c.method_call(mut node) } else { @@ -620,9 +600,13 @@ pub fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) match obj { ast.GlobalField { typ = obj.typ + node.is_fn_var = true + node.fn_var_type = typ } ast.Var { typ = if obj.smartcasts.len != 0 { obj.smartcasts.last() } else { obj.typ } + node.is_fn_var = true + node.fn_var_type = typ } else {} } @@ -810,7 +794,8 @@ pub fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) call_arg.expr.pos()) } else { if param.typ.share() != call_arg.share { - c.error('wrong shared type', call_arg.expr.pos()) + c.error('wrong shared type `$call_arg.share.str()`, expected: `$param.typ.share().str()`', + call_arg.expr.pos()) } if to_lock != '' && !param.typ.has_flag(.shared_f) { c.error('$to_lock is `shared` and must be `lock`ed to be passed as `mut`', @@ -1258,10 +1243,6 @@ pub fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type { final_arg_sym = c.table.sym(final_arg_typ) } if exp_arg_typ.has_flag(.generic) { - if concrete_types.len == 0 { - continue - } - if exp_utyp := c.table.resolve_generic_to_concrete(exp_arg_typ, method.generic_names, concrete_types) { @@ -1299,7 +1280,8 @@ pub fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type { arg.expr.pos()) } else { if param_share != arg.share { - c.error('wrong shared type', arg.expr.pos()) + c.error('wrong shared type `$arg.share.str()`, expected: `$param_share.str()`', + arg.expr.pos()) } if to_lock != '' && param_share != .shared_t { c.error('$to_lock is `shared` and must be `lock`ed to be passed as `mut`', diff --git a/vlib/v/checker/if.v b/vlib/v/checker/if.v index 4660ac3287..f50fa265af 100644 --- a/vlib/v/checker/if.v +++ b/vlib/v/checker/if.v @@ -152,9 +152,8 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type { } else { c.stmts(branch.stmts) } - if c.smartcast_mut_pos != token.Pos{} { - c.smartcast_mut_pos = token.Pos{} - } + c.smartcast_mut_pos = token.Pos{} + c.smartcast_cond_pos = token.Pos{} } if expr_required { if branch.stmts.len > 0 && branch.stmts[branch.stmts.len - 1] is ast.ExprStmt { diff --git a/vlib/v/checker/match.v b/vlib/v/checker/match.v index 81e9ef937f..b3eccd7374 100644 --- a/vlib/v/checker/match.v +++ b/vlib/v/checker/match.v @@ -43,9 +43,8 @@ pub fn (mut c Checker) match_expr(mut node ast.MatchExpr) ast.Type { } else { c.stmts(branch.stmts) } - if c.smartcast_mut_pos != token.Pos{} { - c.smartcast_mut_pos = token.Pos{} - } + c.smartcast_mut_pos = token.Pos{} + c.smartcast_cond_pos = token.Pos{} if node.is_expr { if branch.stmts.len > 0 { // ignore last statement - workaround @@ -66,37 +65,36 @@ pub fn (mut c Checker) match_expr(mut node ast.MatchExpr) ast.Type { // If the last statement is an expression, return its type if branch.stmts.len > 0 { mut stmt := branch.stmts[branch.stmts.len - 1] - match mut stmt { - ast.ExprStmt { - if node.is_expr { - c.expected_type = node.expected_type + if mut stmt is ast.ExprStmt { + if node.is_expr { + c.expected_type = node.expected_type + } + expr_type := c.expr(stmt.expr) + if first_iteration { + if node.is_expr && (node.expected_type.has_flag(.optional) + || c.table.type_kind(node.expected_type) == .sum_type) { + ret_type = node.expected_type + } else { + ret_type = expr_type } - expr_type := c.expr(stmt.expr) - if first_iteration { - if node.is_expr && (node.expected_type.has_flag(.optional) - || c.table.type_kind(node.expected_type) == .sum_type) { - ret_type = node.expected_type - } else { - ret_type = expr_type - } - stmt.typ = expr_type - } else if node.is_expr && ret_type.idx() != expr_type.idx() { - if !c.check_types(ret_type, expr_type) - && !c.check_types(expr_type, ret_type) { - ret_sym := c.table.sym(ret_type) - is_noreturn := is_noreturn_callexpr(stmt.expr) - if !(node.is_expr && ret_sym.kind == .sum_type) && !is_noreturn { - c.error('return type mismatch, it should be `$ret_sym.name`', - stmt.expr.pos()) - } + stmt.typ = expr_type + } else if node.is_expr && ret_type.idx() != expr_type.idx() { + if !c.check_types(ret_type, expr_type) && !c.check_types(expr_type, ret_type) { + ret_sym := c.table.sym(ret_type) + is_noreturn := is_noreturn_callexpr(stmt.expr) + if !(node.is_expr && ret_sym.kind == .sum_type + && (ret_type.has_flag(.generic) + || c.table.is_sumtype_or_in_variant(ret_type, expr_type))) + && !is_noreturn { + c.error('return type mismatch, it should be `$ret_sym.name`', + stmt.expr.pos()) } } } - else { - if node.is_expr && ret_type != ast.void_type { - c.error('`match` expression requires an expression as the last statement of every branch', - stmt.pos) - } + } else { + if node.is_expr && ret_type != ast.void_type { + c.error('`match` expression requires an expression as the last statement of every branch', + stmt.pos) } } } @@ -119,10 +117,6 @@ pub fn (mut c Checker) match_expr(mut node ast.MatchExpr) ast.Type { c.returns = false } } - // if ret_type != ast.void_type { - // node.is_expr = c.expected_type != ast.void_type - // node.expected_type = c.expected_type - // } node.return_type = ret_type cond_var := c.get_base_name(&node.cond) if cond_var != '' { @@ -241,7 +235,11 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSym if expr_type !in cond_type_sym.info.variants { expr_str := c.table.type_to_str(expr_type) expect_str := c.table.type_to_str(node.cond_type) - c.error('`$expect_str` has no variant `$expr_str`', expr.pos()) + sumtype_variant_names := cond_type_sym.info.variants.map(c.table.type_to_str_using_aliases(it, + {})) + suggestion := util.new_suggestion(expr_str, sumtype_variant_names) + c.error(suggestion.say('`$expect_str` has no variant `$expr_str`'), + expr.pos()) } } else if cond_type_sym.info is ast.Alias && expr_type_sym.info is ast.Struct { expr_str := c.table.type_to_str(expr_type) diff --git a/vlib/v/checker/struct.v b/vlib/v/checker/struct.v index acd2f69c36..bca2af63e8 100644 --- a/vlib/v/checker/struct.v +++ b/vlib/v/checker/struct.v @@ -55,6 +55,16 @@ pub fn (mut c Checker) struct_decl(mut node ast.StructDecl) { field.type_pos) } } + field_sym := c.table.sym(field.typ) + if field_sym.kind == .function { + fn_info := field_sym.info as ast.FnType + c.ensure_type_exists(fn_info.func.return_type, fn_info.func.return_type_pos) or { + return + } + for param in fn_info.func.params { + c.ensure_type_exists(param.typ, param.type_pos) or { return } + } + } } if sym.kind == .struct_ { info := sym.info as ast.Struct @@ -324,6 +334,15 @@ pub fn (mut c Checker) struct_init(mut node ast.StructInit) ast.Type { field.pos) } } + if field_type_sym.kind == .function && field_type_sym.language == .v { + pos := field.expr.pos() + if mut field.expr is ast.AnonFn { + if field.expr.decl.no_body { + c.error('cannot initialize the fn field with anonymous fn that does not have a body', + pos) + } + } + } node.fields[i].typ = expr_type node.fields[i].expected_type = field_info.typ diff --git a/vlib/v/checker/tests/amod/amod.v b/vlib/v/checker/tests/amod/amod.v new file mode 100644 index 0000000000..5392fc0f2b --- /dev/null +++ b/vlib/v/checker/tests/amod/amod.v @@ -0,0 +1,5 @@ +module amod + +pub struct Xyz {} + +pub struct Bcg {} diff --git a/vlib/v/checker/tests/generic_parameter_on_method.out b/vlib/v/checker/tests/generic_parameter_on_method.out new file mode 100644 index 0000000000..4d82cccbdf --- /dev/null +++ b/vlib/v/checker/tests/generic_parameter_on_method.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/generic_parameter_on_method.vv:15:15: error: cannot use `&Type` as `Type<>` in argument 1 to `ContainerType.contains` + 13 | fn main() { + 14 | con := ContainerType{typ: &Type{0}} + 15 | con.contains(con.typ) + | ~~~~~~~ + 16 | println(con) + 17 | } diff --git a/vlib/v/checker/tests/generic_parameter_on_method.vv b/vlib/v/checker/tests/generic_parameter_on_method.vv new file mode 100644 index 0000000000..719dbe892e --- /dev/null +++ b/vlib/v/checker/tests/generic_parameter_on_method.vv @@ -0,0 +1,17 @@ +struct Type { + value T +} + +struct ContainerType { + typ &Type +} + +fn (instance &ContainerType) contains(typ Type) { + println(typ) +} + +fn main() { + con := ContainerType{typ: &Type{0}} + con.contains(con.typ) + println(con) +} diff --git a/vlib/v/checker/tests/globals/redefine_main.out b/vlib/v/checker/tests/globals/redefine_main.out new file mode 100644 index 0000000000..f7a2d410dc --- /dev/null +++ b/vlib/v/checker/tests/globals/redefine_main.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/globals/redefine_main.vv:1:10: error: the `main` function is the program entry point, cannot redefine it + 1 | __global main = fn () int { return 22 } + | ~~~~ diff --git a/vlib/v/checker/tests/globals/redefine_main.vv b/vlib/v/checker/tests/globals/redefine_main.vv new file mode 100644 index 0000000000..c8786bc304 --- /dev/null +++ b/vlib/v/checker/tests/globals/redefine_main.vv @@ -0,0 +1 @@ +__global main = fn () int { return 22 } diff --git a/vlib/v/checker/tests/incorrect_smartcast2_err.out b/vlib/v/checker/tests/incorrect_smartcast2_err.out new file mode 100644 index 0000000000..c3e813e244 --- /dev/null +++ b/vlib/v/checker/tests/incorrect_smartcast2_err.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/incorrect_smartcast2_err.vv:24:9: notice: smartcast can only be used on the ident or selector, e.g. match foo, match foo.bar + 22 | + 23 | fn doesntwork(v []Either) { + 24 | match v[0] { + | ~~~ + 25 | Left { + 26 | println(v[0].error) +vlib/v/checker/tests/incorrect_smartcast2_err.vv:26:17: error: field `error` does not exist or have the same type in all sumtype variants + 24 | match v[0] { + 25 | Left { + 26 | println(v[0].error) + | ~~~~~ + 27 | } + 28 | else {} diff --git a/vlib/v/checker/tests/incorrect_smartcast2_err.vv b/vlib/v/checker/tests/incorrect_smartcast2_err.vv new file mode 100644 index 0000000000..65b4c5b5d3 --- /dev/null +++ b/vlib/v/checker/tests/incorrect_smartcast2_err.vv @@ -0,0 +1,32 @@ +struct Left { + error E +} + +struct Right { + inner T +} + +type Either = + Left | + Right + +fn works(v []Either) { + first := v[0] + match first { + Left { + println(first.error) + } + else {} + } +} + +fn doesntwork(v []Either) { + match v[0] { + Left { + println(v[0].error) + } + else {} + } +} + +fn main() {} diff --git a/vlib/v/checker/tests/index_of_optional_err.out b/vlib/v/checker/tests/index_of_optional_err.out new file mode 100644 index 0000000000..a3628501d8 --- /dev/null +++ b/vlib/v/checker/tests/index_of_optional_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/index_of_optional_err.vv:6:7: error: type `?[]int` is optional, it does not support indexing + 4 | + 5 | fn main() { + 6 | a := abc()[0] or { 5 } + | ~~~~~ + 7 | dump(a) + 8 | } diff --git a/vlib/v/checker/tests/index_of_optional_err.vv b/vlib/v/checker/tests/index_of_optional_err.vv new file mode 100644 index 0000000000..06bc943aae --- /dev/null +++ b/vlib/v/checker/tests/index_of_optional_err.vv @@ -0,0 +1,8 @@ +fn abc() ?[]int { + return [1, 2, 3] +} + +fn main() { + a := abc()[0] or { 5 } + dump(a) +} diff --git a/vlib/v/checker/tests/invalid_mut.out b/vlib/v/checker/tests/invalid_mut.out new file mode 100644 index 0000000000..9747d4e444 --- /dev/null +++ b/vlib/v/checker/tests/invalid_mut.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/invalid_mut.vv:3:5: error: the `mut` keyword is invalid here + 1 | fn main() { + 2 | mut x := 0 + 3 | if mut x == 0 { + | ~~~ + 4 | println(true) + 5 | } +vlib/v/checker/tests/invalid_mut.vv:6:10: error: the `mut` keyword is invalid here + 4 | println(true) + 5 | } + 6 | if 0 == mut x { + | ~~~ + 7 | println(true) + 8 | } diff --git a/vlib/v/parser/tests/unnecessary_mut.vv b/vlib/v/checker/tests/invalid_mut.vv similarity index 65% rename from vlib/v/parser/tests/unnecessary_mut.vv rename to vlib/v/checker/tests/invalid_mut.vv index 6c7a470ed5..d4b27b2ca9 100644 --- a/vlib/v/parser/tests/unnecessary_mut.vv +++ b/vlib/v/checker/tests/invalid_mut.vv @@ -3,5 +3,8 @@ fn main() { if mut x == 0 { println(true) } + if 0 == mut x { + println(true) + } _ = x } diff --git a/vlib/v/checker/tests/match_invalid_type.out b/vlib/v/checker/tests/match_invalid_type.out index 4c6b98b836..fc7f5f8a57 100644 --- a/vlib/v/checker/tests/match_invalid_type.out +++ b/vlib/v/checker/tests/match_invalid_type.out @@ -1,4 +1,5 @@ -vlib/v/checker/tests/match_invalid_type.vv:5:3: error: `IoS` has no variant `byte` +vlib/v/checker/tests/match_invalid_type.vv:5:3: error: `IoS` has no variant `byte`. +2 possibilities: `int`, `string`. 3 | fn sum() { 4 | match IoS(1) { 5 | byte { diff --git a/vlib/v/checker/tests/match_return_sumtype_mismatch_err.out b/vlib/v/checker/tests/match_return_sumtype_mismatch_err.out new file mode 100644 index 0000000000..25086c8629 --- /dev/null +++ b/vlib/v/checker/tests/match_return_sumtype_mismatch_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/match_return_sumtype_mismatch_err.vv:15:11: error: return type mismatch, it should be `Myt` + 13 | return match b { + 14 | true { St('TRUE') } + 15 | false { `F` } + | ~~~ + 16 | } + 17 | } diff --git a/vlib/v/checker/tests/match_return_sumtype_mismatch_err.vv b/vlib/v/checker/tests/match_return_sumtype_mismatch_err.vv new file mode 100644 index 0000000000..3f5a1a1d47 --- /dev/null +++ b/vlib/v/checker/tests/match_return_sumtype_mismatch_err.vv @@ -0,0 +1,19 @@ +type St = string +type Ru = rune +type Myt = Ru | St + +fn myt_t1(b bool) Myt { + match b { + true { return St('TRUE') } + false { return Ru(`F`) } + } +} + +fn myt_t2(b bool) Myt { + return match b { + true { St('TRUE') } + false { `F` } + } +} + +fn main() {} diff --git a/vlib/v/checker/tests/struct_field_init_with_nobody_anon_fn_err.out b/vlib/v/checker/tests/struct_field_init_with_nobody_anon_fn_err.out new file mode 100644 index 0000000000..3602a1fcc2 --- /dev/null +++ b/vlib/v/checker/tests/struct_field_init_with_nobody_anon_fn_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/struct_field_init_with_nobody_anon_fn_err.vv:7:7: error: cannot initialize the fn field with anonymous fn that does not have a body + 5 | fn main() { + 6 | _ = App{ + 7 | cb: fn(x int) // Note the missing `{}` (the function body) here + | ~~~~~~~~~ + 8 | } + 9 | } diff --git a/vlib/v/checker/tests/struct_field_init_with_nobody_anon_fn_err.vv b/vlib/v/checker/tests/struct_field_init_with_nobody_anon_fn_err.vv new file mode 100644 index 0000000000..fabdfce29e --- /dev/null +++ b/vlib/v/checker/tests/struct_field_init_with_nobody_anon_fn_err.vv @@ -0,0 +1,9 @@ +struct App { + cb fn(x int) // the function signature doesn't make a difference +} + +fn main() { + _ = App{ + cb: fn(x int) // Note the missing `{}` (the function body) here + } +} diff --git a/vlib/v/checker/tests/sumtype_has_no_variant_suggestion.out b/vlib/v/checker/tests/sumtype_has_no_variant_suggestion.out new file mode 100644 index 0000000000..6697412807 --- /dev/null +++ b/vlib/v/checker/tests/sumtype_has_no_variant_suggestion.out @@ -0,0 +1,8 @@ +vlib/v/checker/tests/sumtype_has_no_variant_suggestion.vv:14:5: error: `Abc` has no variant `amod.NonExisting`. +5 possibilities: `amod.Bcg`, `amod.Xyz`, `AnotherStruct`, `Struct1`, `ThirdStruct`. + 12 | a := Abc(Struct1{}) + 13 | match a { + 14 | x.NonExisting { println('----') } + | ~~~~~~~~~~~ + 15 | else {} + 16 | } diff --git a/vlib/v/checker/tests/sumtype_has_no_variant_suggestion.vv b/vlib/v/checker/tests/sumtype_has_no_variant_suggestion.vv new file mode 100644 index 0000000000..240c1fa415 --- /dev/null +++ b/vlib/v/checker/tests/sumtype_has_no_variant_suggestion.vv @@ -0,0 +1,17 @@ +import v.checker.tests.amod as x + +struct Struct1 {} + +struct AnotherStruct {} + +struct ThirdStruct {} + +type Abc = AnotherStruct | Struct1 | ThirdStruct | x.Bcg | x.Xyz + +fn main() { + a := Abc(Struct1{}) + match a { + x.NonExisting { println('----') } + else {} + } +} diff --git a/vlib/v/checker/tests/unknown_type_in_anon_fn.out b/vlib/v/checker/tests/unknown_type_in_anon_fn.out new file mode 100644 index 0000000000..2045ddbba6 --- /dev/null +++ b/vlib/v/checker/tests/unknown_type_in_anon_fn.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/unknown_type_in_anon_fn.vv:5:10: error: unknown type `Another` + 3 | struct Struc{ + 4 | mut: + 5 | f fn (s Another, i int) ? + | ~~~~~~~ + 6 | } + 7 | diff --git a/vlib/v/checker/tests/unknown_type_in_anon_fn.vv b/vlib/v/checker/tests/unknown_type_in_anon_fn.vv new file mode 100644 index 0000000000..f03742cc4d --- /dev/null +++ b/vlib/v/checker/tests/unknown_type_in_anon_fn.vv @@ -0,0 +1,8 @@ +module main + +struct Struc{ +mut: + f fn (s Another, i int) ? +} + +fn main() {} diff --git a/vlib/v/checker/tests/unnecessary_parenthesis_of_reference.out b/vlib/v/checker/tests/unnecessary_parenthesis_of_reference.out new file mode 100644 index 0000000000..3aa279de02 --- /dev/null +++ b/vlib/v/checker/tests/unnecessary_parenthesis_of_reference.out @@ -0,0 +1,35 @@ +vlib/v/checker/tests/unnecessary_parenthesis_of_reference.vv:29:10: notice: unnecessary `()`, use `&Quad{....}` instead of `&(Quad{....})` + 27 | // ritorna una nuova Quadrica somma del ricevente e di un'altra + 28 | fn (q &Quad) add(other &Quad) &Quad { + 29 | return &(Quad{q.x + other.x, q.y + other.y, q.z + other.z, q.w + other.w}) + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 30 | } + 31 | +vlib/v/checker/tests/unnecessary_parenthesis_of_reference.vv:34:10: notice: unnecessary `()`, use `&Quad{....}` instead of `&(Quad{....})` + 32 | // ritorna una nuova Quadrica differenza tra il ricevente e un'altra + 33 | fn (q &Quad) sub(other &Quad) &Quad { + 34 | return &(Quad{q.x - other.x, q.y - other.y, q.z - other.z, q.w - other.w}) + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 35 | } + 36 | +vlib/v/checker/tests/unnecessary_parenthesis_of_reference.vv:39:10: notice: unnecessary `()`, use `&Quad{....}` instead of `&(Quad{....})` + 37 | // ritorna una nuova Quadrica ottenuta negando il ricevente + 38 | fn (q &Quad) neg() &Quad { + 39 | return &(Quad{-q.x, -q.y, -q.z, -q.w}) + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 40 | } + 41 | +vlib/v/checker/tests/unnecessary_parenthesis_of_reference.vv:44:10: notice: unnecessary `()`, use `&Quad{....}` instead of `&(Quad{....})` + 42 | // ritorna una nuova Quadrica ottenuta moltiplicando il ricevente per una costante + 43 | fn (q &Quad) mult(factor f64) &Quad { + 44 | return &(Quad{q.x * factor, q.y * factor, q.z * factor, q.w * factor}) + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 45 | } + 46 | +vlib/v/checker/tests/unnecessary_parenthesis_of_reference.vv:49:10: notice: unnecessary `()`, use `&Quad{....}` instead of `&(Quad{....})` + 47 | // ritorna una nuova Quadrica ottenuta dividendo il ricevente per una costante + 48 | fn (q &Quad) div(factor f64) &Quad { + 49 | return &(Quad{q.x / factor, q.y / factor, q.z / factor, q.w / factor}) + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 50 | } + 51 | diff --git a/vlib/v/checker/tests/unnecessary_parenthesis_of_reference.vv b/vlib/v/checker/tests/unnecessary_parenthesis_of_reference.vv new file mode 100644 index 0000000000..1fe1a36986 --- /dev/null +++ b/vlib/v/checker/tests/unnecessary_parenthesis_of_reference.vv @@ -0,0 +1,69 @@ +struct Quad { +mut: + x f64 + y f64 + z f64 + w f64 +} + +fn (q Quad) get(i int) f64 { + return match i { + 0 { q.x } + 1 { q.y } + 2 { q.z } + else { q.w } + } +} + +fn (mut q Quad) set(i int, v f64) { + match i { + 0 { q.x = v } + 1 { q.y = v } + 2 { q.z = v } + else { q.w = v } + } +} + +// ritorna una nuova Quadrica somma del ricevente e di un'altra +fn (q &Quad) add(other &Quad) &Quad { + return &(Quad{q.x + other.x, q.y + other.y, q.z + other.z, q.w + other.w}) +} + +// ritorna una nuova Quadrica differenza tra il ricevente e un'altra +fn (q &Quad) sub(other &Quad) &Quad { + return &(Quad{q.x - other.x, q.y - other.y, q.z - other.z, q.w - other.w}) +} + +// ritorna una nuova Quadrica ottenuta negando il ricevente +fn (q &Quad) neg() &Quad { + return &(Quad{-q.x, -q.y, -q.z, -q.w}) +} + +// ritorna una nuova Quadrica ottenuta moltiplicando il ricevente per una costante +fn (q &Quad) mult(factor f64) &Quad { + return &(Quad{q.x * factor, q.y * factor, q.z * factor, q.w * factor}) +} + +// ritorna una nuova Quadrica ottenuta dividendo il ricevente per una costante +fn (q &Quad) div(factor f64) &Quad { + return &(Quad{q.x / factor, q.y / factor, q.z / factor, q.w / factor}) +} + +fn main() { + mut n := Quad{1, 2, 3, 4} + + println(n) + println(n.get(0)) + println(n.get(1)) + println(n.get(2)) + println(n.get(3)) + n.set(0, 5) + n.set(1, 6) + n.set(2, 7) + n.set(3, 8) + println(n) + println(n.get(0)) + println(n.get(1)) + println(n.get(2)) + println(n.get(3)) +} diff --git a/vlib/v/gen/c/auto_free_methods.v b/vlib/v/gen/c/auto_free_methods.v index a30c6175ac..695e230143 100644 --- a/vlib/v/gen/c/auto_free_methods.v +++ b/vlib/v/gen/c/auto_free_methods.v @@ -7,15 +7,14 @@ import strings fn (mut g Gen) get_free_method(typ ast.Type) string { g.autofree_methods[typ] = true - styp := g.typ(typ).replace('*', '') mut sym := g.table.sym(g.unwrap_generic(typ)) - mut fn_name := styp_to_free_fn_name(styp) if mut sym.info is ast.Alias { if sym.info.is_import { sym = g.table.sym(sym.info.parent_type) } } - + styp := g.typ(typ).replace('*', '') + fn_name := styp_to_free_fn_name(styp) if sym.has_method_with_generic_parent('free') { return fn_name } @@ -30,21 +29,23 @@ fn (mut g Gen) gen_free_methods() { fn (mut g Gen) gen_free_method(typ ast.Type) string { styp := g.typ(typ).replace('*', '') - mut sym := g.table.sym(g.unwrap_generic(typ)) mut fn_name := styp_to_free_fn_name(styp) - if typ in g.generated_free_methods { + deref_typ := typ.set_nr_muls(0) + if deref_typ in g.generated_free_methods { return fn_name } - g.generated_free_methods[typ] = true + g.generated_free_methods[deref_typ] = true + + mut sym := g.table.sym(g.unwrap_generic(typ)) if mut sym.info is ast.Alias { if sym.info.is_import { sym = g.table.sym(sym.info.parent_type) } } - if sym.has_method_with_generic_parent('free') { return fn_name } + match mut sym.info { ast.Struct { g.gen_free_for_struct(sym.info, styp, fn_name) diff --git a/vlib/v/gen/c/auto_str_methods.v b/vlib/v/gen/c/auto_str_methods.v index 982a3cf3e8..98c1d10869 100644 --- a/vlib/v/gen/c/auto_str_methods.v +++ b/vlib/v/gen/c/auto_str_methods.v @@ -144,7 +144,7 @@ fn (mut g Gen) get_str_fn(typ ast.Type) string { styp := g.typ(unwrapped) mut sym := g.table.sym(unwrapped) mut str_fn_name := styp_to_str_fn_name(styp) - if mut sym.info is ast.Alias { + if mut sym.info is ast.Alias && !sym.has_method('str') { if sym.info.is_import { sym = g.table.sym(sym.info.parent_type) str_fn_name = styp_to_str_fn_name(sym.name) diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index d576ede838..a0c13cdf91 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -20,10 +20,11 @@ const ( // `small` should not be needed, but see: https://stackoverflow.com/questions/5874215/what-is-rpcndr-h c_reserved = ['array', 'auto', 'bool', 'break', 'calloc', 'case', 'char', 'class', 'complex', 'const', 'continue', 'default', 'delete', 'do', 'double', 'else', 'enum', 'error', 'exit', - 'export', 'extern', 'float', 'for', 'free', 'goto', 'if', 'inline', 'int', 'link', 'long', - 'malloc', 'namespace', 'new', 'panic', 'register', 'restrict', 'return', 'short', 'signed', - 'sizeof', 'static', 'string', 'struct', 'switch', 'typedef', 'typename', 'union', 'unix', - 'unsigned', 'void', 'volatile', 'while', 'template', 'small', 'stdout', 'stdin', 'stderr'] + 'export', 'extern', 'false', 'float', 'for', 'free', 'goto', 'if', 'inline', 'int', 'link', + 'long', 'malloc', 'namespace', 'new', 'panic', 'register', 'restrict', 'return', 'short', + 'signed', 'sizeof', 'static', 'string', 'struct', 'switch', 'typedef', 'typename', 'union', + 'unix', 'unsigned', 'void', 'volatile', 'while', 'template', 'true', 'small', 'stdout', + 'stdin', 'stderr'] c_reserved_map = string_array_to_map(c_reserved) // same order as in token.Kind cmp_str = ['eq', 'ne', 'gt', 'lt', 'ge', 'le'] @@ -955,7 +956,13 @@ fn (mut g Gen) optional_type_name(t ast.Type) (string, string) { fn (g Gen) optional_type_text(styp string, base string) string { // replace void with something else - size := if base == 'void' { 'byte' } else { base } + size := if base == 'void' { + 'byte' + } else if base.starts_with('anon_fn') { + 'void*' + } else { + base + } ret := 'struct $styp { byte state; IError err; @@ -1834,7 +1841,8 @@ fn (mut g Gen) stmt(node ast.Stmt) { } ast.Module { // g.is_builtin_mod = node.name == 'builtin' - g.is_builtin_mod = node.name in ['builtin', 'strconv', 'strings', 'dlmalloc'] + // g.is_builtin_mod = node.name in ['builtin', 'strconv', 'strings', 'dlmalloc'] + g.is_builtin_mod = util.module_is_builtin(node.name) // g.cur_mod = node.name g.cur_mod = node } @@ -2037,7 +2045,7 @@ fn (mut g Gen) expr_with_cast(expr ast.Expr, got_type_raw ast.Type, expected_typ && !expected_type.has_flag(.optional) { if expr is ast.StructInit && !got_type.is_ptr() { g.inside_cast_in_heap++ - got_styp := g.cc_type(got_type.ref(), true) + got_styp := g.cc_type(got_type_raw.ref(), true) // TODO: why does cc_type even add this in the first place? exp_styp := exp_sym.cname mut fname := 'I_${got_styp}_to_Interface_$exp_styp' @@ -2048,12 +2056,7 @@ fn (mut g Gen) expr_with_cast(expr ast.Expr, got_type_raw ast.Type, expected_typ got_styp) g.inside_cast_in_heap-- } else { - mut got_styp := g.cc_type(got_type, true) - got_styp = match got_styp { - 'int' { 'int_literal' } - 'f64' { 'float_literal' } - else { got_styp } - } + got_styp := g.cc_type(got_type_raw, true) got_is_shared := got_type.has_flag(.shared_f) exp_styp := if got_is_shared { '__shared__$exp_sym.cname' } else { exp_sym.cname } // If it's shared, we need to use the other caster: @@ -3645,11 +3648,23 @@ fn (mut g Gen) ident(node ast.Ident) { g.write(util.no_dots(node.name[2..])) return } - if node.kind == .constant { // && !node.name.starts_with('g_') { - // TODO globals hack - g.write('_const_') - } mut name := c_name(node.name) + if node.kind == .constant { // && !node.name.starts_with('g_') { + if g.pref.translated && !g.is_builtin_mod + && !util.module_is_builtin(node.name.all_before_last('.')) { + // Don't prepend "_const" to translated C consts, + // but only in user code, continue prepending "_const" to builtin consts. + mut x := util.no_dots(node.name) + if x.starts_with('main__') { + x = x['main__'.len..] + } + g.write(x) + return + } else { + // TODO globals hack + g.write('_const_') + } + } // TODO: temporary, remove this node_info := node.info mut is_auto_heap := false @@ -4114,6 +4129,12 @@ fn (mut g Gen) const_decl(node ast.ConstDecl) { } } name := c_name(field.name) + const_name := if node.attrs.contains('export') && !g.is_builtin_mod { + // TODO this only works for the first const in the group for now + node.attrs[0].arg + } else { + '_const_' + name + } field_expr := field.expr match field.expr { ast.ArrayInit { @@ -4121,19 +4142,19 @@ fn (mut g Gen) const_decl(node ast.ConstDecl) { styp := g.typ(field.expr.typ) if g.pref.build_mode != .build_module { val := g.expr_string(field.expr) - g.definitions.writeln('$styp _const_$name = $val; // fixed array const') + g.definitions.writeln('$styp $const_name = $val; // fixed array const') } else { - g.definitions.writeln('$styp _const_$name; // fixed array const') + g.definitions.writeln('$styp $const_name; // fixed array const') } } else { g.const_decl_init_later(field.mod, name, field.expr, field.typ, false) } } ast.StringLiteral { - g.definitions.writeln('string _const_$name; // a string literal, inited later') + g.definitions.writeln('string $const_name; // a string literal, inited later') if g.pref.build_mode != .build_module { val := g.expr_string(field.expr) - g.stringliterals.writeln('\t_const_$name = $val;') + g.stringliterals.writeln('\t$const_name = $val;') } } ast.CallExpr { @@ -4177,7 +4198,7 @@ fn (mut g Gen) const_decl(node ast.ConstDecl) { fn (mut g Gen) const_decl_precomputed(mod string, name string, ct_value ast.ComptTimeConstValue, typ ast.Type) bool { mut styp := g.typ(typ) - cname := '_const_$name' + cname := if g.pref.translated && !g.is_builtin_mod { name } else { '_const_$name' } $if trace_const_precomputed ? { eprintln('> styp: $styp | cname: $cname | ct_value: $ct_value | $ct_value.type_name()') } @@ -4246,7 +4267,7 @@ fn (mut g Gen) const_decl_precomputed(mod string, name string, ct_value ast.Comp // TODO: ^ the above for strings, cause: // `error C2099: initializer is not a constant` errors in MSVC, // so fall back to the delayed initialisation scheme: - g.definitions.writeln('$styp $cname; // inited later') + g.definitions.writeln('$styp $cname; // str inited later') g.init.writeln('\t$cname = _SLIT("$escaped_val");') if g.is_autofree { g.cleanups[mod].writeln('\tstring_free(&$cname);') @@ -4276,7 +4297,7 @@ fn (mut g Gen) const_decl_init_later(mod string, name string, expr ast.Expr, typ // Initialize more complex consts in `void _vinit/2{}` // (C doesn't allow init expressions that can't be resolved at compile time). mut styp := g.typ(typ) - cname := '_const_$name' + cname := if g.pref.translated && !g.is_builtin_mod { name } else { '_const_$name' } g.definitions.writeln('$styp $cname; // inited later') if cname == '_const_os__args' { if g.pref.os == .windows { @@ -4339,6 +4360,13 @@ fn (mut g Gen) global_decl(node ast.GlobalDecl) { } } styp := g.typ(field.typ) + mut anon_fn_expr := unsafe { field.expr } + if field.has_expr && mut anon_fn_expr is ast.AnonFn { + g.gen_anon_fn_decl(mut anon_fn_expr) + fn_type_name := g.get_anon_fn_type_name(mut anon_fn_expr, field.name) + g.definitions.writeln('$fn_type_name = ${g.table.sym(field.typ).name}; // global') + continue + } g.definitions.write_string('$visibility_kw$styp $attributes $field.name') if field.has_expr { if field.expr.is_literal() && should_init { @@ -4524,17 +4552,17 @@ fn (mut g Gen) write_builtin_types() { // Sort the types, make sure types that are referenced by other types // are added before them. fn (mut g Gen) write_sorted_types() { + g.type_definitions.writeln('// #start sorted_symbols') + defer { + g.type_definitions.writeln('// #end sorted_symbols') + } mut symbols := []&ast.TypeSymbol{cap: g.table.type_symbols.len} // structs that need to be sorted for sym in g.table.type_symbols { if sym.name !in c.builtins { symbols << sym } } - // sort structs sorted_symbols := g.sort_structs(symbols) - // Generate C code - g.type_definitions.writeln('// builtin types:') - g.type_definitions.writeln('//------------------ #endbuiltin') g.write_types(sorted_symbols) } @@ -4706,7 +4734,7 @@ fn (mut g Gen) write_types(symbols []&ast.TypeSymbol) { } // sort structs by dependant fields -fn (g &Gen) sort_structs(typesa []&ast.TypeSymbol) []&ast.TypeSymbol { +fn (mut g Gen) sort_structs(typesa []&ast.TypeSymbol) []&ast.TypeSymbol { util.timing_start(@METHOD) defer { util.timing_measure(@METHOD) @@ -4742,12 +4770,23 @@ fn (g &Gen) sort_structs(typesa []&ast.TypeSymbol) []&ast.TypeSymbol { field_deps << dep } for field in sym.info.fields { - dep := g.table.sym(field.typ).name + if field.typ.is_ptr() { + continue + } + fsym := g.table.sym(field.typ) + dep := fsym.name // skip if not in types list or already in deps - if dep !in type_names || dep in field_deps || field.typ.is_ptr() { + if dep !in type_names || dep in field_deps { continue } field_deps << dep + if fsym.info is ast.Alias { + xdep := g.table.sym(fsym.info.parent_type).name + if xdep !in type_names || xdep in field_deps { + continue + } + field_deps << xdep + } } } // ast.Interface {} @@ -4795,7 +4834,7 @@ fn (mut g Gen) go_before_ternary() string { } fn (mut g Gen) insert_before_stmt(s string) { - cur_line := g.go_before_stmt(0) + cur_line := g.go_before_stmt(g.inside_ternary) g.writeln(s) g.write(cur_line) } @@ -4815,12 +4854,15 @@ fn (mut g Gen) insert_at(pos int, s string) { // Returns the type of the last stmt fn (mut g Gen) or_block(var_name string, or_block ast.OrExpr, return_type ast.Type) { cvar_name := c_name(var_name) - mr_styp := g.base_type(return_type) + mut mr_styp := g.base_type(return_type) is_none_ok := return_type == ast.ovoid_type g.writeln(';') if is_none_ok { g.writeln('if (${cvar_name}.state != 0 && ${cvar_name}.err._typ != _IError_None___index) {') } else { + if return_type != 0 && g.table.sym(return_type).kind == .function { + mr_styp = 'voidptr' + } g.writeln('if (${cvar_name}.state != 0) { /*or block*/ ') } if or_block.kind == .block { @@ -5106,6 +5148,8 @@ fn (mut g Gen) go_expr(node ast.GoExpr) { } else if mut expr.left is ast.AnonFn { g.gen_anon_fn_decl(mut expr.left) name = expr.left.decl.name + } else if expr.is_fn_var { + name = g.table.sym(expr.fn_var_type).name } name = util.no_dots(name) if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') { @@ -5119,7 +5163,8 @@ fn (mut g Gen) go_expr(node ast.GoExpr) { panic('cgen: obf name "$key" not found, this should never happen') } } - g.writeln('// go') + g.empty_line = true + g.writeln('// start go') wrapper_struct_name := 'thread_arg_' + name wrapper_fn_name := name + '_thread_wrapper' arg_tmp_var := 'arg_' + tmp @@ -5158,7 +5203,7 @@ fn (mut g Gen) go_expr(node ast.GoExpr) { } else { 'thread_$tmp' } - g.writeln('HANDLE $simple_handle = CreateThread(0,0, (LPTHREAD_START_ROUTINE)$wrapper_fn_name, $arg_tmp_var, 0,0);') + g.writeln('HANDLE $simple_handle = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)$wrapper_fn_name, $arg_tmp_var, 0, 0);') g.writeln('if (!$simple_handle) panic_lasterr(tos3("`go ${name}()`: "));') if node.is_expr && node.call_expr.return_type != ast.void_type { g.writeln('$gohandle_name thread_$tmp = {') @@ -5177,7 +5222,7 @@ fn (mut g Gen) go_expr(node ast.GoExpr) { g.writeln('pthread_detach(thread_$tmp);') } } - g.writeln('// endgo\n') + g.writeln('// end go') if node.is_expr { handle = 'thread_$tmp' // create wait handler for this return type if none exists @@ -5643,7 +5688,8 @@ static inline __shared__$interface_name ${shared_fn_name}(__shared__$cctype* x) if fargs.len > 1 { methods_wrapper.write_string(', ') } - methods_wrapper.writeln('${fargs[1..].join(', ')});') + args := fargs[1..].join(', ') + methods_wrapper.writeln('$args);') } else { if parameter_name.starts_with('__shared__') { methods_wrapper.writeln('${method_call}(${fargs.join(', ')}->val);') diff --git a/vlib/v/gen/c/comptime.v b/vlib/v/gen/c/comptime.v index 9237cc16e8..29c07d8978 100644 --- a/vlib/v/gen/c/comptime.v +++ b/vlib/v/gen/c/comptime.v @@ -203,7 +203,7 @@ fn cgen_attrs(attrs []ast.Attr) []string { fn (mut g Gen) comptime_at(node ast.AtExpr) { if node.kind == .vmod_file { - val := cnewlines(node.val.replace('\r', '')) + val := cescape_nonascii(util.smart_quote(node.val, false)) g.write('_SLIT("$val")') } else { val := node.val.replace('\\', '\\\\') diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 445da57c66..15ddbc5b97 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -612,6 +612,25 @@ fn (mut g Gen) fn_decl_params(params []ast.Param, scope &ast.Scope, is_variadic return fargs, fargtypes, heap_promoted } +fn (mut g Gen) get_anon_fn_type_name(mut node ast.AnonFn, var_name string) string { + mut builder := strings.new_builder(64) + return_styp := g.typ(node.decl.return_type) + builder.write_string('$return_styp (*$var_name) (') + if node.decl.params.len == 0 { + builder.write_string('void)') + } else { + for i, param in node.decl.params { + param_styp := g.typ(param.typ) + builder.write_string('$param_styp $param.name') + if i != node.decl.params.len - 1 { + builder.write_string(', ') + } + } + builder.write_string(')') + } + return builder.str() +} + fn (mut g Gen) call_expr(node ast.CallExpr) { // g.write('/*call expr*/') // NOTE: everything could be done this way diff --git a/vlib/v/gen/c/if.v b/vlib/v/gen/c/if.v index 9ae3015754..bf1c31ca57 100644 --- a/vlib/v/gen/c/if.v +++ b/vlib/v/gen/c/if.v @@ -115,92 +115,91 @@ fn (mut g Gen) if_expr(node ast.IfExpr) { cvar_name := guard_vars[guard_idx] g.writeln('\tIError err = ${cvar_name}.err;') } - } else { - match branch.cond { - ast.IfGuardExpr { - mut var_name := guard_vars[i] - mut short_opt := false - if var_name == '' { - short_opt = true // we don't need a further tmp, so use the one we'll get later - var_name = g.new_tmp_var() - guard_vars[i] = var_name // for `else` - g.tmp_count-- - g.writeln('if (${var_name}.state == 0) {') + } else if branch.cond is ast.IfGuardExpr { + mut var_name := guard_vars[i] + mut short_opt := false + if var_name == '' { + short_opt = true // we don't need a further tmp, so use the one we'll get later + var_name = g.new_tmp_var() + guard_vars[i] = var_name // for `else` + g.tmp_count-- + g.writeln('if (${var_name}.state == 0) {') + } else { + g.write('if ($var_name = ') + g.expr(branch.cond.expr) + g.writeln(', ${var_name}.state == 0) {') + } + if short_opt || branch.cond.vars[0].name != '_' { + base_type := g.base_type(branch.cond.expr_type) + if short_opt { + cond_var_name := if branch.cond.vars[0].name == '_' { + '_dummy_${g.tmp_count + 1}' } else { - g.write('if ($var_name = ') - g.expr(branch.cond.expr) - g.writeln(', ${var_name}.state == 0) {') + branch.cond.vars[0].name } - if short_opt || branch.cond.vars[0].name != '_' { - base_type := g.base_type(branch.cond.expr_type) - if short_opt { - cond_var_name := if branch.cond.vars[0].name == '_' { - '_dummy_${g.tmp_count + 1}' - } else { - branch.cond.vars[0].name - } - g.write('\t$base_type $cond_var_name = ') - g.expr(branch.cond.expr) - g.writeln(';') + g.write('\t$base_type $cond_var_name = ') + g.expr(branch.cond.expr) + g.writeln(';') + } else { + mut is_auto_heap := false + if branch.stmts.len > 0 { + scope := g.file.scope.innermost(ast.Node(branch.stmts[branch.stmts.len - 1]).pos().pos) + if v := scope.find_var(branch.cond.vars[0].name) { + is_auto_heap = v.is_auto_heap + } + } + if branch.cond.vars.len == 1 { + left_var_name := c_name(branch.cond.vars[0].name) + if is_auto_heap { + g.writeln('\t$base_type* $left_var_name = HEAP($base_type, *($base_type*)${var_name}.data);') } else { - mut is_auto_heap := false - if branch.stmts.len > 0 { - scope := g.file.scope.innermost(ast.Node(branch.stmts[branch.stmts.len - 1]).pos().pos) - if v := scope.find_var(branch.cond.vars[0].name) { - is_auto_heap = v.is_auto_heap - } - } - if branch.cond.vars.len == 1 { - left_var_name := c_name(branch.cond.vars[0].name) - if is_auto_heap { - g.writeln('\t$base_type* $left_var_name = HEAP($base_type, *($base_type*)${var_name}.data);') - } else { - g.writeln('\t$base_type $left_var_name = *($base_type*)${var_name}.data;') - } - } else if branch.cond.vars.len > 1 { - for vi, var in branch.cond.vars { - left_var_name := c_name(var.name) - sym := g.table.sym(branch.cond.expr_type) - if sym.kind == .multi_return { - mr_info := sym.info as ast.MultiReturn - if mr_info.types.len == branch.cond.vars.len { - var_typ := g.typ(mr_info.types[vi]) - if is_auto_heap { - g.writeln('\t$var_typ* $left_var_name = (HEAP($base_type, *($base_type*)${var_name}.data).arg$vi);') - } else { - g.writeln('\t$var_typ $left_var_name = (*($base_type*)${var_name}.data).arg$vi;') - } - } + g.writeln('\t$base_type $left_var_name = *($base_type*)${var_name}.data;') + } + } else if branch.cond.vars.len > 1 { + for vi, var in branch.cond.vars { + left_var_name := c_name(var.name) + sym := g.table.sym(branch.cond.expr_type) + if sym.kind == .multi_return { + mr_info := sym.info as ast.MultiReturn + if mr_info.types.len == branch.cond.vars.len { + var_typ := g.typ(mr_info.types[vi]) + if is_auto_heap { + g.writeln('\t$var_typ* $left_var_name = (HEAP($base_type, *($base_type*)${var_name}.data).arg$vi);') + } else { + g.writeln('\t$var_typ $left_var_name = (*($base_type*)${var_name}.data).arg$vi;') } } } } } } - else { - mut no_needs_par := false - if branch.cond is ast.InfixExpr { - if branch.cond.op == .key_in && branch.cond.left !is ast.InfixExpr - && branch.cond.right is ast.ArrayInit { - no_needs_par = true - } - } - if no_needs_par { - g.write('if ') - } else { - g.write('if (') - } - g.expr(branch.cond) - if no_needs_par { - g.writeln(' {') - } else { - g.writeln(') {') - } + } + } else { + mut no_needs_par := false + if branch.cond is ast.InfixExpr { + if branch.cond.op == .key_in && branch.cond.left !is ast.InfixExpr + && branch.cond.right is ast.ArrayInit { + no_needs_par = true } } + if no_needs_par { + g.write('if ') + } else { + g.write('if (') + } + g.expr(branch.cond) + if no_needs_par { + g.writeln(' {') + } else { + g.writeln(') {') + } } if needs_tmp_var { + if node.is_expr && g.table.sym(node.typ).kind == .sum_type { + g.expected_cast_type = node.typ + } g.stmts_with_tmp_var(branch.stmts, tmp) + g.expected_cast_type = 0 } else { // restore if_expr stmt header pos stmt_pos := g.nth_stmt_pos(0) diff --git a/vlib/v/gen/c/index.v b/vlib/v/gen/c/index.v index 08a041d9d5..8434eda7cc 100644 --- a/vlib/v/gen/c/index.v +++ b/vlib/v/gen/c/index.v @@ -62,6 +62,7 @@ fn (mut g Gen) range_expr(node ast.IndexExpr, range ast.RangeExpr) { mut tmp_opt := '' mut cur_line := '' mut gen_or := node.or_expr.kind != .absent || node.is_option + mut tmp_left := '' if sym.kind == .string { if node.is_gated { @@ -82,6 +83,15 @@ fn (mut g Gen) range_expr(node ast.IndexExpr, range ast.RangeExpr) { } g.expr(node.left) } else if sym.kind == .array { + if !range.has_high { + tmp_left = g.new_tmp_var() + tmp_type := g.typ(node.left_type) + g.insert_before_stmt('${util.tabs(g.indent)}$tmp_type $tmp_left;') + // (tmp = expr, array_slice(...)) + g.write('($tmp_left = ') + g.expr(node.left) + g.write(', ') + } if node.is_gated { g.write('array_slice_ni(') } else { @@ -90,7 +100,11 @@ fn (mut g Gen) range_expr(node ast.IndexExpr, range ast.RangeExpr) { if node.left_type.is_ptr() { g.write('*') } - g.expr(node.left) + if range.has_high { + g.expr(node.left) + } else { + g.write(tmp_left) + } } else if sym.kind == .array_fixed { // Convert a fixed array to V array when doing `fixed_arr[start..end]` info := sym.info as ast.ArrayFixed @@ -101,17 +115,8 @@ fn (mut g Gen) range_expr(node ast.IndexExpr, range ast.RangeExpr) { g.write('array_slice(') } g.write('new_array_from_c_array${noscan}(') - g.write('$info.size') - g.write(', $info.size') - g.write(', sizeof(') - if node.left_type.is_ptr() { - g.write('(*') - } - g.expr(node.left) - if node.left_type.is_ptr() { - g.write(')') - } - g.write('[0]), ') + ctype := g.typ(info.elem_type) + g.write('$info.size, $info.size, sizeof($ctype), ') if node.left_type.is_ptr() { g.write('*') } @@ -132,15 +137,17 @@ fn (mut g Gen) range_expr(node ast.IndexExpr, range ast.RangeExpr) { } else if sym.kind == .array_fixed { info := sym.info as ast.ArrayFixed g.write('$info.size') - } else if node.left_type.is_ptr() { - g.write('(') - g.write('*') - g.expr(node.left) - g.write(')') - g.write('.len') + } else if sym.kind == .array { + if node.left_type.is_ptr() { + g.write('$tmp_left->') + } else { + g.write('${tmp_left}.') + } + g.write('len)') } else { + g.write('(') g.expr(node.left) - g.write('.len') + g.write(').len') } g.write(')') @@ -157,23 +164,22 @@ fn (mut g Gen) index_of_array(node ast.IndexExpr, sym ast.TypeSymbol) { gen_or := node.or_expr.kind != .absent || node.is_option left_is_ptr := node.left_type.is_ptr() info := sym.info as ast.Array - elem_type_str := g.typ(info.elem_type) + mut elem_type_str := g.typ(info.elem_type) elem_type := info.elem_type - elem_typ := g.table.sym(elem_type) + elem_sym := g.table.sym(elem_type) + if elem_sym.kind == .function { + elem_type_str = 'voidptr' + } // `vals[i].field = x` is an exception and requires `array_get`: // `(*(Val*)array_get(vals, i)).field = x;` is_selector := node.left is ast.SelectorExpr if g.is_assign_lhs && !is_selector && node.is_setter { is_direct_array_access := (g.fn_decl != 0 && g.fn_decl.is_direct_arr) || node.is_direct is_op_assign := g.assign_op != .assign && info.elem_type != ast.string_type - array_ptr_type_str := match elem_typ.kind { - .function { 'voidptr*' } - else { '$elem_type_str*' } - } if is_direct_array_access { - g.write('(($array_ptr_type_str)') + g.write('(($elem_type_str*)') } else if is_op_assign { - g.write('(*($array_ptr_type_str)array_get(') + g.write('(*($elem_type_str*)array_get(') if left_is_ptr && !node.left_type.has_flag(.shared_f) { g.write('*') } @@ -217,11 +223,7 @@ fn (mut g Gen) index_of_array(node ast.IndexExpr, sym ast.TypeSymbol) { } */ if need_wrapper { - if elem_typ.kind == .function { - g.write(', &(voidptr[]) { ') - } else { - g.write(', &($elem_type_str[]) { ') - } + g.write(', &($elem_type_str[]) { ') } else { g.write(', &') } @@ -232,10 +234,6 @@ fn (mut g Gen) index_of_array(node ast.IndexExpr, sym ast.TypeSymbol) { } } else { is_direct_array_access := (g.fn_decl != 0 && g.fn_decl.is_direct_arr) || node.is_direct - array_ptr_type_str := match elem_typ.kind { - .function { 'voidptr*' } - else { '$elem_type_str*' } - } // do not clone inside `opt_ok(opt_ok(&(string[]) {..})` before returns needs_clone := info.elem_type == ast.string_type_idx && g.is_autofree && !(g.inside_return && g.fn_decl.return_type.has_flag(.optional)) && !g.is_assign_lhs @@ -250,24 +248,24 @@ fn (mut g Gen) index_of_array(node ast.IndexExpr, sym ast.TypeSymbol) { tmp_opt := if gen_or { g.new_tmp_var() } else { '' } tmp_opt_ptr := if gen_or { g.new_tmp_var() } else { '' } if gen_or { - g.write('$array_ptr_type_str $tmp_opt_ptr = ($array_ptr_type_str)/*ee elem_ptr_typ */(array_get_with_check(') + g.write('$elem_type_str* $tmp_opt_ptr = ($elem_type_str*)/*ee elem_ptr_typ */(array_get_with_check(') } else { if needs_clone { g.write('/*2*/string_clone(') } if g.is_fn_index_call { - if elem_typ.info is ast.FnType { + if elem_sym.info is ast.FnType { g.write('((') - g.write_fn_ptr_decl(&elem_typ.info, '') - g.write(')(*($array_ptr_type_str)/*ee elem_typ */array_get(') + g.write_fn_ptr_decl(&elem_sym.info, '') + g.write(')(*($elem_type_str*)/*ee elem_sym */array_get(') } if left_is_ptr && !node.left_type.has_flag(.shared_f) { g.write('*') } } else if is_direct_array_access { - g.write('(($array_ptr_type_str)') + g.write('(($elem_type_str*)') } else { - g.write('(*($array_ptr_type_str)/*ee elem_typ */array_get(') + g.write('(*($elem_type_str*)/*ee elem_sym */array_get(') if left_is_ptr && !node.left_type.has_flag(.shared_f) { g.write('*') } @@ -358,9 +356,12 @@ fn (mut g Gen) index_of_map(node ast.IndexExpr, sym ast.TypeSymbol) { info := sym.info as ast.Map key_type_str := g.typ(info.key_type) elem_type := info.value_type - elem_type_str := g.typ(elem_type) - elem_typ := g.table.sym(elem_type) - get_and_set_types := elem_typ.kind in [.struct_, .map] + mut elem_type_str := g.typ(elem_type) + elem_sym := g.table.sym(elem_type) + if elem_sym.kind == .function { + elem_type_str = 'voidptr' + } + get_and_set_types := elem_sym.kind in [.struct_, .map] if g.is_assign_lhs && !g.is_arraymap_set && !get_and_set_types { if g.assign_op == .assign || info.value_type == ast.string_type { g.is_arraymap_set = true @@ -394,12 +395,8 @@ fn (mut g Gen) index_of_map(node ast.IndexExpr, sym ast.TypeSymbol) { g.is_arraymap_set = old_is_arraymap_set g.is_assign_lhs = old_is_assign_lhs g.write('}') - if elem_typ.kind == .function { - g.write(', &(voidptr[]) { ') - } else { - g.arraymap_set_pos = g.out.len - g.write(', &($elem_type_str[]) { ') - } + g.arraymap_set_pos = g.out.len + g.write(', &($elem_type_str[]) { ') if g.assign_op != .assign && info.value_type != ast.string_type { zero := g.type_default(info.value_type) g.write('$zero })))') @@ -438,13 +435,11 @@ fn (mut g Gen) index_of_map(node ast.IndexExpr, sym ast.TypeSymbol) { g.write('$elem_type_str* $tmp_opt_ptr = ($elem_type_str*)/*ee elem_ptr_typ */(map_get_check(') } else { if g.is_fn_index_call { - if elem_typ.info is ast.FnType { + if elem_sym.info is ast.FnType { g.write('((') - g.write_fn_ptr_decl(&elem_typ.info, '') + g.write_fn_ptr_decl(&elem_sym.info, '') g.write(')(*(voidptr*)map_get(') } - } else if elem_typ.kind == .function { - g.write('(*(voidptr*)map_get(') } else { g.write('(*($elem_type_str*)map_get(') } @@ -470,8 +465,6 @@ fn (mut g Gen) index_of_map(node ast.IndexExpr, sym ast.TypeSymbol) { g.write('))') } else if g.is_fn_index_call { g.write(', &(voidptr[]){ $zero })))') - } else if elem_typ.kind == .function { - g.write(', &(voidptr[]){ $zero }))') } else { g.write(', &($elem_type_str[]){ $zero }))') } diff --git a/vlib/v/gen/c/infix_expr.v b/vlib/v/gen/c/infix_expr.v index af061cf77d..61a47508da 100644 --- a/vlib/v/gen/c/infix_expr.v +++ b/vlib/v/gen/c/infix_expr.v @@ -507,7 +507,7 @@ fn (mut g Gen) infix_expr_is_op(node ast.InfixExpr) { else { ast.Type(0) } } sub_sym := g.table.sym(sub_type) - g.write('_${c_name(sym.name)}_${c_name(sub_sym.name)}_index') + g.write('_${sym.cname}_${sub_sym.cname}_index') return } else if sym.kind == .sum_type { g.write('_typ $cmp_op ') diff --git a/vlib/v/gen/c/match.v b/vlib/v/gen/c/match.v index 7dd059723d..bb07b2ce4e 100644 --- a/vlib/v/gen/c/match.v +++ b/vlib/v/gen/c/match.v @@ -233,7 +233,7 @@ fn (mut g Gen) match_expr_switch(node ast.MatchExpr, is_expr bool, cond_var stri } g.writeln(') {') g.stmts_with_tmp_var(range_branch.stmts, tmp_var) - g.writeln('break;') + g.writeln('\tbreak;') g.writeln('}') } g.indent-- @@ -259,7 +259,8 @@ fn (mut g Gen) match_expr_switch(node ast.MatchExpr, is_expr bool, cond_var stri } g.stmts_with_tmp_var(branch.stmts, tmp_var) g.expected_cast_type = 0 - g.writeln('} break;') + g.writeln('\tbreak;') + g.writeln('}') g.indent-- } if range_branches.len > 0 && !default_generated { @@ -297,7 +298,7 @@ fn (mut g Gen) match_expr_switch(node ast.MatchExpr, is_expr bool, cond_var stri } g.writeln(') {') g.stmts_with_tmp_var(range_branch.stmts, tmp_var) - g.writeln('break;') + g.writeln('\tbreak;') g.writeln('}') } g.indent-- diff --git a/vlib/v/gen/c/str.v b/vlib/v/gen/c/str.v index fc9f1090e5..4640078364 100644 --- a/vlib/v/gen/c/str.v +++ b/vlib/v/gen/c/str.v @@ -70,8 +70,8 @@ fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype ast.Type) { typ = typ.clear_flag(.shared_f).set_nr_muls(0) } mut sym := g.table.sym(typ) - // when type is alias, print the aliased value - if mut sym.info is ast.Alias { + // when type is alias and doesn't has `str()`, print the aliased value + if mut sym.info is ast.Alias && !sym.has_method('str') { parent_sym := g.table.sym(sym.info.parent_type) if parent_sym.has_method('str') { typ = sym.info.parent_type diff --git a/vlib/v/parser/expr.v b/vlib/v/parser/expr.v index 92cfa0074c..6b5d793c99 100644 --- a/vlib/v/parser/expr.v +++ b/vlib/v/parser/expr.v @@ -604,6 +604,13 @@ fn (mut p Parser) prefix_expr() ast.Expr { return right } } + if mut right is ast.ParExpr { + if right.expr is ast.StructInit { + p.note_with_pos('unnecessary `()`, use `&$right.expr` instead of `&($right.expr)`', + right.pos) + right = right.expr + } + } } mut or_stmts := []ast.Stmt{} mut or_kind := ast.OrKind.absent diff --git a/vlib/v/parser/if_match.v b/vlib/v/parser/if_match.v index 0bad3f9a63..0a993d033f 100644 --- a/vlib/v/parser/if_match.v +++ b/vlib/v/parser/if_match.v @@ -107,6 +107,10 @@ fn (mut p Parser) if_expr(is_comptime bool) ast.IfExpr { p.check(.decl_assign) comments << p.eat_comments() expr := p.expr(0) + if expr !in [ast.CallExpr, ast.IndexExpr, ast.PrefixExpr] { + p.error_with_pos('if guard condition expression is illegal, it should return optional', + expr.pos()) + } cond = ast.IfGuardExpr{ vars: vars diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index c4468d242b..5608996559 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -1943,6 +1943,7 @@ pub fn (mut p Parser) parse_ident(language ast.Language) ast.Ident { p.register_auto_import('sync') } mut_pos := p.tok.pos() + modifier_kind := p.tok.kind is_mut := p.tok.kind == .key_mut || is_shared || is_atomic if is_mut { p.next() @@ -1956,7 +1957,11 @@ pub fn (mut p Parser) parse_ident(language ast.Language) ast.Ident { p.next() } if p.tok.kind != .name { - p.error('unexpected token `$p.tok.lit`') + if is_mut || is_static || is_volatile { + p.error_with_pos('the `$modifier_kind` keyword is invalid here', mut_pos) + } else { + p.error('unexpected token `$p.tok.lit`') + } return ast.Ident{ scope: p.scope } @@ -2190,7 +2195,7 @@ pub fn (mut p Parser) name_expr() ast.Expr { } // Raw string (`s := r'hello \n ') if p.peek_tok.kind == .string && !p.inside_str_interp && p.peek_token(2).kind != .colon { - if p.tok.lit in ['r', 'c', 'js'] && p.tok.kind == .name { + if p.tok.kind == .name && p.tok.lit in ['r', 'c', 'js'] { return p.string_expr() } else { // don't allow any other string prefix except `r`, `js` and `c` @@ -2198,7 +2203,7 @@ pub fn (mut p Parser) name_expr() ast.Expr { } } // don't allow r`byte` and c`byte` - if p.tok.lit in ['r', 'c'] && p.peek_tok.kind == .chartoken { + if p.peek_tok.kind == .chartoken && p.tok.lit.len == 1 && p.tok.lit[0] in [`r`, `c`] { opt := if p.tok.lit == 'r' { '`r` (raw string)' } else { '`c` (c string)' } return p.error('cannot use $opt with `byte` and `rune`') } diff --git a/vlib/v/parser/tests/if_guard_cond_err.out b/vlib/v/parser/tests/if_guard_cond_err.out new file mode 100644 index 0000000000..70a11b41f5 --- /dev/null +++ b/vlib/v/parser/tests/if_guard_cond_err.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/if_guard_cond_err.vv:16:16: error: if guard condition expression is illegal, it should return optional + 14 | fp.usage_example('GOOG AAPL') + 15 | _ := fp.bool('version', `v`, false, 'version information.') + 16 | if args := fp.finalize() && args.len > 0 { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~ + 17 | return args + 18 | } else { diff --git a/vlib/v/parser/tests/if_guard_cond_err.vv b/vlib/v/parser/tests/if_guard_cond_err.vv new file mode 100644 index 0000000000..69de3f19ec --- /dev/null +++ b/vlib/v/parser/tests/if_guard_cond_err.vv @@ -0,0 +1,28 @@ +import os +import flag + +const version = "v0.1.0" + +// getting command line options and arguments +// returns the arguments +fn get_args() ?[]string { + mut fp := flag.new_flag_parser(os.args) + fp.application('ticker') + fp.version(version) + fp.description('A CLI yahoo ticker app') + fp.skip_executable() + fp.usage_example('GOOG AAPL') + _ := fp.bool('version', `v`, false, 'version information.') + if args := fp.finalize() && args.len > 0 { + return args + } else { + eprintln(err.msg()) + println(fp.usage()) + return none + } +} + +fn main() { + tickers := get_args() or { return } + println(tickers) +} diff --git a/vlib/v/parser/tests/invalid_using_atomic.out b/vlib/v/parser/tests/invalid_using_atomic.out new file mode 100644 index 0000000000..37b5c030f4 --- /dev/null +++ b/vlib/v/parser/tests/invalid_using_atomic.out @@ -0,0 +1,6 @@ +vlib/v/parser/tests/invalid_using_atomic.vv:2:5: error: the `atomic` keyword is invalid here + 1 | fn main() { + 2 | if atomic true { + | ~~~~~~ + 3 | println(true) + 4 | } diff --git a/vlib/v/parser/tests/invalid_using_atomic.vv b/vlib/v/parser/tests/invalid_using_atomic.vv new file mode 100644 index 0000000000..5d9b58aaad --- /dev/null +++ b/vlib/v/parser/tests/invalid_using_atomic.vv @@ -0,0 +1,5 @@ +fn main() { + if atomic true { + println(true) + } +} diff --git a/vlib/v/parser/tests/invalid_using_mut.out b/vlib/v/parser/tests/invalid_using_mut.out new file mode 100644 index 0000000000..fff45d7a9d --- /dev/null +++ b/vlib/v/parser/tests/invalid_using_mut.out @@ -0,0 +1,6 @@ +vlib/v/parser/tests/invalid_using_mut.vv:2:5: error: the `mut` keyword is invalid here + 1 | fn main() { + 2 | if mut true { + | ~~~ + 3 | println(true) + 4 | } diff --git a/vlib/v/parser/tests/unnecessary_mut_2.vv b/vlib/v/parser/tests/invalid_using_mut.vv similarity index 100% rename from vlib/v/parser/tests/unnecessary_mut_2.vv rename to vlib/v/parser/tests/invalid_using_mut.vv diff --git a/vlib/v/parser/tests/invalid_using_shared.out b/vlib/v/parser/tests/invalid_using_shared.out new file mode 100644 index 0000000000..318569239c --- /dev/null +++ b/vlib/v/parser/tests/invalid_using_shared.out @@ -0,0 +1,6 @@ +vlib/v/parser/tests/invalid_using_shared.vv:2:5: error: the `shared` keyword is invalid here + 1 | fn main() { + 2 | if shared true { + | ~~~~~~ + 3 | println(true) + 4 | } diff --git a/vlib/v/parser/tests/invalid_using_shared.vv b/vlib/v/parser/tests/invalid_using_shared.vv new file mode 100644 index 0000000000..3c30c4e471 --- /dev/null +++ b/vlib/v/parser/tests/invalid_using_shared.vv @@ -0,0 +1,5 @@ +fn main() { + if shared true { + println(true) + } +} diff --git a/vlib/v/parser/tests/invalid_using_static.out b/vlib/v/parser/tests/invalid_using_static.out new file mode 100644 index 0000000000..75bae06cfe --- /dev/null +++ b/vlib/v/parser/tests/invalid_using_static.out @@ -0,0 +1,6 @@ +vlib/v/parser/tests/invalid_using_static.vv:2:5: error: the `static` keyword is invalid here + 1 | fn main() { + 2 | if static true { + | ~~~~~~ + 3 | println(true) + 4 | } diff --git a/vlib/v/parser/tests/invalid_using_static.vv b/vlib/v/parser/tests/invalid_using_static.vv new file mode 100644 index 0000000000..c6a1ebc677 --- /dev/null +++ b/vlib/v/parser/tests/invalid_using_static.vv @@ -0,0 +1,5 @@ +fn main() { + if static true { + println(true) + } +} diff --git a/vlib/v/parser/tests/invalid_using_volatile.out b/vlib/v/parser/tests/invalid_using_volatile.out new file mode 100644 index 0000000000..6b5f37bd80 --- /dev/null +++ b/vlib/v/parser/tests/invalid_using_volatile.out @@ -0,0 +1,6 @@ +vlib/v/parser/tests/invalid_using_volatile.vv:2:5: error: the `volatile` keyword is invalid here + 1 | fn main() { + 2 | if volatile true { + | ~~~~~~~~ + 3 | println(true) + 4 | } diff --git a/vlib/v/parser/tests/invalid_using_volatile.vv b/vlib/v/parser/tests/invalid_using_volatile.vv new file mode 100644 index 0000000000..e5dfdece79 --- /dev/null +++ b/vlib/v/parser/tests/invalid_using_volatile.vv @@ -0,0 +1,5 @@ +fn main() { + if volatile true { + println(true) + } +} diff --git a/vlib/v/parser/tests/unnecessary_mut.out b/vlib/v/parser/tests/unnecessary_mut.out deleted file mode 100644 index 4399c9f164..0000000000 --- a/vlib/v/parser/tests/unnecessary_mut.out +++ /dev/null @@ -1,7 +0,0 @@ -vlib/v/parser/tests/unnecessary_mut.vv:3:5: error: remove unnecessary `mut` - 1 | fn main() { - 2 | mut x := 0 - 3 | if mut x == 0 { - | ~~~ - 4 | println(true) - 5 | } \ No newline at end of file diff --git a/vlib/v/parser/tests/unnecessary_mut_2.out b/vlib/v/parser/tests/unnecessary_mut_2.out deleted file mode 100644 index 5f7dfee46b..0000000000 --- a/vlib/v/parser/tests/unnecessary_mut_2.out +++ /dev/null @@ -1,6 +0,0 @@ -vlib/v/parser/tests/unnecessary_mut_2.vv:2:9: error: unexpected token `true` - 1 | fn main() { - 2 | if mut true { - | ~~~~ - 3 | println(true) - 4 | } \ No newline at end of file diff --git a/vlib/v/tests/array_get_anon_fn_value_test.v b/vlib/v/tests/array_get_anon_fn_value_test.v new file mode 100644 index 0000000000..0af940aabc --- /dev/null +++ b/vlib/v/tests/array_get_anon_fn_value_test.v @@ -0,0 +1,20 @@ +const numbers = [ + fn () int { + return 1 + }, + fn () int { + return 2 + }, +] + +fn test_array_get_anon_fn_value() { + num1 := numbers[0] + ret1 := num1() + println(ret1) + assert ret1 == 1 + + num2 := numbers[1] + ret2 := num2() + println(ret2) + assert ret2 == 2 +} diff --git a/vlib/v/tests/cast_int_to_interface_test.v b/vlib/v/tests/cast_int_to_interface_test.v new file mode 100644 index 0000000000..34b22a523f --- /dev/null +++ b/vlib/v/tests/cast_int_to_interface_test.v @@ -0,0 +1,19 @@ +interface Any {} + +fn return_any(val Any) ?Any { + return val +} + +fn test_cast_int_to_interface() { + code := 200 + if an := return_any(code) { + if an is int { + println('an is an int!') + } else { + println('an is not an int!') + } + assert '$an' == 'Any(200)' + } else { + assert false + } +} diff --git a/vlib/v/tests/empty_interface_test.v b/vlib/v/tests/empty_interface_test.v new file mode 100644 index 0000000000..ded28b335e --- /dev/null +++ b/vlib/v/tests/empty_interface_test.v @@ -0,0 +1,14 @@ +interface Any {} + +fn print_out(x Any) string { + if x is string { + println(x) + return '$x' + } + return '' +} + +fn test_empty_interface() { + ret := print_out('12345') + assert ret == '&12345' +} diff --git a/vlib/v/tests/go_anon_fn_variable_call_test.v b/vlib/v/tests/go_anon_fn_variable_call_test.v new file mode 100644 index 0000000000..e5f199c1fb --- /dev/null +++ b/vlib/v/tests/go_anon_fn_variable_call_test.v @@ -0,0 +1,35 @@ +fn sum1(a int, b int) int { + sum_func1 := fn (a int, b int) int { + return a + b + } + sum_func2 := sum_func1 + + g := go sum_func2(a, b) + + result := g.wait() + return result +} + +fn add(a int, b int) int { + return a + b +} + +fn sum2(a int, b int) int { + sum_func1 := add + sum_func2 := sum_func1 + + g := go sum_func2(a, b) + + result := g.wait() + return result +} + +fn test_go_anon_fn_variable_call() { + ret1 := sum1(22, 33) + println(ret1) + assert ret1 == 55 + + ret2 := sum2(2, 3) + println(ret2) + assert ret2 == 5 +} diff --git a/vlib/v/tests/go_array_wait_test.v b/vlib/v/tests/go_array_wait_test.v index d5513ca53c..cdf1686b3c 100644 --- a/vlib/v/tests/go_array_wait_test.v +++ b/vlib/v/tests/go_array_wait_test.v @@ -1,3 +1,5 @@ +// vtest retry: 3 + fn f(x f64) f64 { y := x * x return y diff --git a/vlib/v/tests/if_expr_with_sumtype_test.v b/vlib/v/tests/if_expr_with_sumtype_test.v new file mode 100644 index 0000000000..81bc07c7ea --- /dev/null +++ b/vlib/v/tests/if_expr_with_sumtype_test.v @@ -0,0 +1,23 @@ +struct A_str {} + +struct B_str {} + +type Token = A_str | B_str + +fn next(mut v []Token) Token { + return if v.len > 0 { v.pop() } else { A_str{} } +} + +fn test_if_expr_with_sumtype() { + mut arr := []Token{} + ret1 := next(mut arr) + println(ret1) + assert '$ret1' == 'Token(A_str{})' + + arr << A_str{} + arr << B_str{} + + ret2 := next(mut arr) + println(ret2) + assert '$ret2' == 'Token(B_str{})' +} diff --git a/vlib/v/tests/init_global_test.v b/vlib/v/tests/init_global_test.v index b39d4d468b..4dae83c8f1 100644 --- a/vlib/v/tests/init_global_test.v +++ b/vlib/v/tests/init_global_test.v @@ -53,6 +53,11 @@ fn test_no_type() { assert testmap['asd'] == -7.25 } +fn test_fn_type() { + assert func2(22) == 22 + assert func3(22) == '22' +} + __global ( intmap map[string]int numberfns map[string]fn () int @@ -75,6 +80,13 @@ __global ( 'asd': -7.25 'yxc': 3.125 } + func1 = fn () {} + func2 = fn (n int) int { + return n + } + func3 = fn (n int) string { + return '$n' + } ) fn init() { diff --git a/vlib/v/tests/inout/printing_alias_has_str_method.out b/vlib/v/tests/inout/printing_alias_has_str_method.out new file mode 100644 index 0000000000..640bb73cf1 --- /dev/null +++ b/vlib/v/tests/inout/printing_alias_has_str_method.out @@ -0,0 +1,3 @@ +hello +hello +hello diff --git a/vlib/v/tests/inout/printing_alias_has_str_method.vv b/vlib/v/tests/inout/printing_alias_has_str_method.vv new file mode 100644 index 0000000000..3b79901af9 --- /dev/null +++ b/vlib/v/tests/inout/printing_alias_has_str_method.vv @@ -0,0 +1,12 @@ +type Byte = byte + +fn (b Byte) str() string { + return 'hello' +} + +fn main() { + b := Byte(`a`) + println(b) + println(b.str()) + println('$b') +} diff --git a/vlib/v/tests/keyword_escaping_test.v b/vlib/v/tests/keyword_escaping_test.v new file mode 100644 index 0000000000..c96c5e47d4 --- /dev/null +++ b/vlib/v/tests/keyword_escaping_test.v @@ -0,0 +1,11 @@ +fn test_escapes() { + @if := 0 + @for := 0 + auto := 0 + calloc := 0 + stdout := 0 + signed := 0 + @true := 0 + @false := 0 + assert true +} diff --git a/vlib/v/tests/map_get_anon_fn_value_test.v b/vlib/v/tests/map_get_anon_fn_value_test.v new file mode 100644 index 0000000000..6b0c10249c --- /dev/null +++ b/vlib/v/tests/map_get_anon_fn_value_test.v @@ -0,0 +1,25 @@ +const numbers = { + 'one': fn () int { + return 1 + } +} + +fn test_map_get_anon_fn_value() { + num1 := numbers['one'] or { + fn () int { + return 2 + } + } + ret1 := num1() + println(ret1) + assert ret1 == 1 + + num2 := numbers['two'] or { + fn () int { + return 2 + } + } + ret2 := num2() + println(ret2) + assert ret2 == 2 +} diff --git a/vlib/v/tests/modules/alias_to_another_module/alias.v b/vlib/v/tests/modules/alias_to_another_module/alias.v new file mode 100644 index 0000000000..d92a17ea62 --- /dev/null +++ b/vlib/v/tests/modules/alias_to_another_module/alias.v @@ -0,0 +1,9 @@ +module alias_to_another_module + +import another_module + +pub type MyAlias = another_module.SomeStruct + +pub fn (m MyAlias) alias_method() int { + return 42 +} diff --git a/vlib/v/tests/modules/another_module/module.v b/vlib/v/tests/modules/another_module/module.v new file mode 100644 index 0000000000..a21270ea7c --- /dev/null +++ b/vlib/v/tests/modules/another_module/module.v @@ -0,0 +1,12 @@ +module another_module + +pub struct SomeStruct { +pub mut: + x int + y int + z int +} + +pub fn (s SomeStruct) some_method() int { + return 999 + s.x + s.y + s.z +} diff --git a/vlib/v/tests/slice_rval_test.v b/vlib/v/tests/slice_rval_test.v new file mode 100644 index 0000000000..2078f5b353 --- /dev/null +++ b/vlib/v/tests/slice_rval_test.v @@ -0,0 +1,8 @@ +fn test_arr_rval() { + a := [1, 2] + s := (*&a)[..] + assert s == a + + b := unsafe { *&[]int(&a) }[..] + assert b == a +} diff --git a/vlib/v/tests/string_interpolation_float_fmt_test.v b/vlib/v/tests/string_interpolation_float_fmt_test.v new file mode 100644 index 0000000000..37c4fe960a --- /dev/null +++ b/vlib/v/tests/string_interpolation_float_fmt_test.v @@ -0,0 +1,13 @@ +fn test_string_interpolation_float_fmt() { + mut a := 76.295 + eprintln('${a:8.2}') + assert '${a:8.2}' == ' 76.30' + eprintln('${a:8.2f}') + assert '${a:8.2f}' == ' 76.30' + + a = 76.296 + eprintln('${a:8.2}') + assert '${a:8.2}' == ' 76.30' + eprintln('${a:8.2f}') + assert '${a:8.2f}' == ' 76.30' +} diff --git a/vlib/v/tests/use_alias_from_another_module_in_struct_field_test.v b/vlib/v/tests/use_alias_from_another_module_in_struct_field_test.v new file mode 100644 index 0000000000..ed4d194279 --- /dev/null +++ b/vlib/v/tests/use_alias_from_another_module_in_struct_field_test.v @@ -0,0 +1,14 @@ +import alias_to_another_module + +struct MyStruct { + myfield alias_to_another_module.MyAlias +} + +fn test_using_struct_with_alias() { + m := MyStruct{ + myfield: alias_to_another_module.MyAlias{1, 2, 3} + } + dump(m) + assert m.myfield.alias_method() == 42 + assert m.myfield.some_method() == 1005 +} diff --git a/vlib/v/tests/valgrind/struct_of_array_of_same_struct.v b/vlib/v/tests/valgrind/struct_of_array_of_same_struct.v new file mode 100644 index 0000000000..7112117eb0 --- /dev/null +++ b/vlib/v/tests/valgrind/struct_of_array_of_same_struct.v @@ -0,0 +1,38 @@ +struct Abc { +mut: + name string + children []Abc +} + +[manualfree] +fn main() { + mut a := &Abc{} + a.name = 'aaa' + a.children = [ + Abc{ + name: 'xyz' + children: [ + Abc{ + name: 'xx' + }, + Abc{ + name: 'yy' + }, + ] + }, + Abc{ + name: 'def' + children: [ + Abc{ + name: 'dd' + }, + Abc{ + name: 'ee' + }, + ] + }, + ] + dump(a) + unsafe { a.free() } + unsafe { free(a) } +} diff --git a/vlib/v/util/util.v b/vlib/v/util/util.v index b3affaf60d..41f0da4fca 100644 --- a/vlib/v/util/util.v +++ b/vlib/v/util/util.v @@ -36,6 +36,12 @@ const ( ] ) +const builtin_module_names = ['builtin', 'strconv', 'strings', 'dlmalloc'] + +pub fn module_is_builtin(mod string) bool { + return mod in util.builtin_module_names +} + pub fn tabs(n int) string { return if n < util.const_tabs.len { util.const_tabs[n] } else { '\t'.repeat(n) } } diff --git a/vlib/v/vmod/parser.v b/vlib/v/vmod/parser.v index 0eeb580e95..b0bd2e9a9d 100644 --- a/vlib/v/vmod/parser.v +++ b/vlib/v/vmod/parser.v @@ -2,6 +2,8 @@ module vmod import os +const err_label = 'vmod:' + enum TokenKind { module_keyword field_key @@ -32,6 +34,7 @@ pub mut: struct Scanner { mut: pos int + line int = 1 text string inside_text bool tokens []Token @@ -44,8 +47,9 @@ mut: } struct Token { - typ TokenKind - val string + typ TokenKind + val string + line int } pub fn from_file(vmod_path string) ?Manifest { @@ -67,7 +71,7 @@ pub fn decode(contents string) ?Manifest { } fn (mut s Scanner) tokenize(t_type TokenKind, val string) { - s.tokens << Token{t_type, val} + s.tokens << Token{t_type, val, s.line} } fn (mut s Scanner) skip_whitespace() { @@ -82,7 +86,7 @@ fn is_name_alpha(chr byte) bool { fn (mut s Scanner) create_string(q byte) string { mut str := '' - for s.text[s.pos] != q { + for s.pos < s.text.len && s.text[s.pos] != q { if s.text[s.pos] == `\\` && s.text[s.pos + 1] == q { str += s.text[s.pos..s.pos + 1] s.pos += 2 @@ -96,7 +100,7 @@ fn (mut s Scanner) create_string(q byte) string { fn (mut s Scanner) create_ident() string { mut text := '' - for is_name_alpha(s.text[s.pos]) { + for s.pos < s.text.len && is_name_alpha(s.text[s.pos]) { text += s.text[s.pos].ascii_str() s.pos++ } @@ -112,6 +116,9 @@ fn (mut s Scanner) scan_all() { c := s.text[s.pos] if c.is_space() || c == `\\` { s.pos++ + if c == `\n` { + s.line++ + } continue } if is_name_alpha(c) { @@ -120,7 +127,7 @@ fn (mut s Scanner) scan_all() { s.tokenize(.module_keyword, name) s.pos++ continue - } else if s.text[s.pos] == `:` { + } else if s.pos < s.text.len && s.text[s.pos] == `:` { s.tokenize(.field_key, name + ':') s.pos += 2 continue @@ -155,7 +162,7 @@ fn get_array_content(tokens []Token, st_idx int) ?([]string, int) { mut vals := []string{} mut idx := st_idx if tokens[idx].typ != .labr { - return error('vmod: not a valid array') + return error('$vmod.err_label not a valid array, at line ${tokens[idx].line}') } idx++ for { @@ -164,7 +171,7 @@ fn get_array_content(tokens []Token, st_idx int) ?([]string, int) { .str { vals << tok.val if tokens[idx + 1].typ !in [.comma, .rabr] { - return error('vmod: invalid separator "${tokens[idx + 1].val}"') + return error('$vmod.err_label invalid separator "${tokens[idx + 1].val}", at line $tok.line') } idx += if tokens[idx + 1].typ == .comma { 2 } else { 1 } } @@ -173,7 +180,7 @@ fn get_array_content(tokens []Token, st_idx int) ?([]string, int) { break } else { - return error('vmod: invalid token "$tok.val"') + return error('$vmod.err_label invalid token "$tok.val", at line $tok.line') } } } @@ -181,15 +188,14 @@ fn get_array_content(tokens []Token, st_idx int) ?([]string, int) { } fn (mut p Parser) parse() ?Manifest { - err_label := 'vmod:' if p.scanner.text.len == 0 { - return error('$err_label no content.') + return error('$vmod.err_label no content.') } p.scanner.scan_all() tokens := p.scanner.tokens mut mn := Manifest{} if tokens[0].typ != .module_keyword { - return error('vmod: v.mod files should start with Module') + return error('$vmod.err_label v.mod files should start with Module, at line ${tokens[0].line}') } mut i := 1 for i < tokens.len { @@ -197,7 +203,7 @@ fn (mut p Parser) parse() ?Manifest { match tok.typ { .lcbr { if tokens[i + 1].typ !in [.field_key, .rcbr] { - return error('$err_label invalid content after opening brace') + return error('$vmod.err_label invalid content after opening brace, at line $tok.line') } i++ continue @@ -208,7 +214,7 @@ fn (mut p Parser) parse() ?Manifest { .field_key { field_name := tok.val.trim_right(':') if tokens[i + 1].typ !in [.str, .labr] { - return error('$err_label value of field "$field_name" must be either string or an array of strings') + return error('$vmod.err_label value of field "$field_name" must be either string or an array of strings, at line $tok.line') } field_value := tokens[i + 1].val match field_name { @@ -251,13 +257,13 @@ fn (mut p Parser) parse() ?Manifest { } .comma { if tokens[i - 1].typ !in [.str, .rabr] || tokens[i + 1].typ != .field_key { - return error('$err_label invalid comma placement') + return error('$vmod.err_label invalid comma placement, at line $tok.line') } i++ continue } else { - return error('$err_label invalid token "$tok.val"') + return error('$vmod.err_label invalid token "$tok.val", at line $tok.line') } } } diff --git a/vlib/v/vmod/parser_test.v b/vlib/v/vmod/parser_test.v new file mode 100644 index 0000000000..3bd8b6e188 --- /dev/null +++ b/vlib/v/vmod/parser_test.v @@ -0,0 +1,51 @@ +import v.vmod + +const quote = '\x22' + +const apos = '\x27' + +fn test_ok() ? { + ok_source := "Module { + name: 'V' + description: 'The V programming language.' + version: '0.2.4' + license: 'MIT' + repo_url: 'https://github.com/vlang/v' + dependencies: [] +}" + for s in [ok_source, ok_source.replace(apos, quote), ok_source.replace('\n', '\r\n'), + ok_source.replace('\n', '\r\n '), ok_source.replace('\n', '\n ')] { + content := vmod.decode(s) ? + assert content.name == 'V' + assert content.description == 'The V programming language.' + assert content.version == '0.2.4' + assert content.license == 'MIT' + assert content.repo_url == 'https://github.com/vlang/v' + assert content.dependencies == [] + assert content.unknown == {} + } + e := vmod.decode('Module{}') ? + assert e.name == '' + assert e.description == '' + assert e.version == '' + assert e.license == '' + assert e.repo_url == '' + assert e.dependencies == [] + assert e.unknown == {} +} + +fn test_invalid_start() ? { + vmod.decode('\n\nXYZ') or { + assert err.msg() == 'vmod: v.mod files should start with Module, at line 3' + return + } + assert false +} + +fn test_invalid_end() ? { + vmod.decode('\nModule{\n \nname: ${quote}zzzz}') or { + assert err.msg() == 'vmod: invalid token ${quote}eof$quote, at line 4' + return + } + assert false +} diff --git a/vlib/vweb/vweb.v b/vlib/vweb/vweb.v index ad11e36b55..03551e9dcb 100644 --- a/vlib/vweb/vweb.v +++ b/vlib/vweb/vweb.v @@ -459,7 +459,12 @@ fn handle_conn(mut conn net.TcpConn, mut app T, routes map[string]Route) { } return } - + $if trace_request ? { + dump(req) + } + $if trace_request_url ? { + dump(req.url) + } // URL Parse url := urllib.parse(req.url) or { eprintln('error parsing path: $err') @@ -628,8 +633,14 @@ fn (mut ctx Context) scan_static_directory(directory_path string, mount_path str } } -// Handles a directory static +// handle_static is used to mark a folder (relative to the current working folder) +// as one that contains only static resources (css files, images etc). // If `root` is set the mount path for the dir will be in '/' +// Usage: +// ```v +// os.chdir( os.executable() ) ? +// app.handle_static('assets', true) +// ``` pub fn (mut ctx Context) handle_static(directory_path string, root bool) bool { if ctx.done || !os.exists(directory_path) { return false @@ -696,6 +707,9 @@ pub fn not_found() Result { } fn send_string(mut conn net.TcpConn, s string) ? { + $if trace_response ? { + eprintln('> send_string:\n$s\n') + } conn.write(s.bytes()) ? } diff --git a/vlib/x/ttf/README.md b/vlib/x/ttf/README.md index 811f281a55..2a1062332c 100644 --- a/vlib/x/ttf/README.md +++ b/vlib/x/ttf/README.md @@ -298,7 +298,7 @@ fn main() { // TTF render 0 Frame counter app.ttf_render << &ttf.TTF_render_Sokol{ bmp: &ttf.BitMap{ - tf: &(app.tf[0]) + tf: &app.tf[0] buf: unsafe { malloc(32000000) } buf_size: (32000000) color: 0xFF0000FF diff --git a/vlib/x/x.v b/vlib/x/x.v new file mode 100644 index 0000000000..10a4b5ea01 --- /dev/null +++ b/vlib/x/x.v @@ -0,0 +1,3 @@ +module x + +pub const description = 'an empty module, used as a placeholder, for other modules'