Compare commits

...

25 Commits

Author SHA1 Message Date
Delyan Angelov f9079b6700
vfmt: keep selective imported names used for generic calls
ci/woodpecker/push/vc Pipeline was successful Details
ci/woodpecker/push/docker Pipeline was successful Details
ci/woodpecker/push/arch Pipeline was successful Details
2022-05-19 12:16:19 +02:00
yuyi 7c7d59acee
parser: improve error messages of 'for val in array' (#14459) 2022-05-19 12:16:19 +02:00
Delyan Angelov c5933aa3c5
ci: use V_CI_CSTRICT=1 consistently for every `v -cstrict test-self` 2022-05-19 12:16:19 +02:00
Delyan Angelov 990a540851
tests: fix the push_work_on_channel.vv output 2022-05-19 12:16:19 +02:00
Delyan Angelov 366ff17bff
tests: re-add the disambiguated `for (val in [TokenValue(`+`), TokenValue(`-`)]) {` test 2022-05-19 12:16:19 +02:00
Delyan Angelov bdc1375d00
Revert "parser: fix 'val in array' as condition in for stmt (fix #14440) (#14451)"
This reverts commit b482c0512b.
2022-05-19 12:16:19 +02:00
yuyi 5acc130c60
parser: fix 'val in array' as condition in for stmt (fix #14440) (#14451) 2022-05-19 12:16:19 +02:00
Delyan Angelov 6a93b3df2f
ci: skip more .vv files on specific jobs 2022-05-19 12:16:19 +02:00
yuyi 4549afd642
checker: split up infix.v from checker.v (#14449) 2022-05-19 12:16:19 +02:00
yuyi 09886f78d3
cgen: fix another error for 'in array of sumtype' (#14448) 2022-05-19 12:16:19 +02:00
Delyan Angelov a1bd9acf82
tests: do not use -prod for compiling .vv files in vlib/v/checker/tests/ and vlib/v/parser/tests/ 2022-05-19 12:16:18 +02:00
Delyan Angelov 888c85dedc
tests: cleanup compiler_errors_test.v using language features (chan), instead of raw `sync.new_channel` and `unsafe { ch.push }` calls 2022-05-19 12:16:18 +02:00
Larpon 21308289ad
checker: add test for empty #flag node, (fix #14291) (#14447) 2022-05-19 12:16:18 +02:00
Adam Oates 88c4e8db29
os: add `fn user_names()` (#14424) 2022-05-19 12:16:18 +02:00
yuyi b37130e664
cgen: fix error for 'in array of sumtype' (#14444) 2022-05-19 12:16:18 +02:00
Larpon c5d93afdc1
vcomplete: improve flag completion, add missdoc (#14415) 2022-05-19 12:16:18 +02:00
Delyan Angelov 3bae3a23df
math.big: fix Integer.bit_len() when there are no digits in the number 2022-05-19 12:16:18 +02:00
Delyan Angelov 055c33e2ca
pref: pass -v after a command, to the command only, do not set verbose mode on 2022-05-19 12:16:18 +02:00
playX 84c1f642f5
checker: c2v fixed array fix (#14436) 2022-05-19 12:16:18 +02:00
spaceface a2985d652a
cgen: reduce the closure memory usage (#14437) 2022-05-19 12:16:18 +02:00
Delyan Angelov c78022c583
tools: add a vet_known_failing_windows skip list to `v test-cleancode` 2022-05-19 12:16:18 +02:00
Delyan Angelov 68569ae598
ci: use VJOBS=1 for the macos v test-cleancode task too; cleanup periodic.yml 2022-05-19 12:16:18 +02:00
Delyan Angelov 4858a113a7
ci: vfmt builtin_d_use_libbacktrace.c.v 2022-05-19 12:16:17 +02:00
Alexander Medvednikov f66e514430
cgen: fix eq generation for translated code 2022-05-19 12:16:17 +02:00
Ned 5ada79c629
builtin: print libbacktrace output to stderr, on panics/segfault crash (#14434) 2022-05-19 12:16:17 +02:00
66 changed files with 1385 additions and 956 deletions

View File

@ -60,7 +60,7 @@ jobs:
- name: Self tests
run: ./v test-self
# - name: Self tests (-cstrict)
# run: ./v -cstrict test-self
# run: V_CI_CSTRICT=1 ./v -cstrict test-self
- name: Test time functions in a timezone UTC-12
run: TZ=Etc/GMT+12 ./v test vlib/time/
- name: Test time functions in a timezone UTC-3
@ -194,7 +194,7 @@ jobs:
./v cmd/tools/test_if_v_test_system_works.v
./cmd/tools/test_if_v_test_system_works
- name: All code is formatted
run: ./v test-cleancode
run: VJOBS=1 ./v test-cleancode
- name: Self tests
run: VJOBS=1 ./v test-self
- name: Build examples
@ -294,7 +294,7 @@ jobs:
- name: Self tests (-prod)
run: ./v -o vprod -prod cmd/v && ./vprod test-self
- name: Self tests (-cstrict)
run: ./v -cc gcc -cstrict test-self
run: V_CI_CSTRICT=1 ./v -cc gcc -cstrict test-self
- name: Build examples
run: ./v build-examples
- name: Build tetris.v with -autofree
@ -383,12 +383,16 @@ jobs:
./cmd/tools/test_if_v_test_system_works
- name: All code is formatted
run: ./v test-cleancode
- name: Self tests
run: ./v test-self
- name: Self tests (-prod)
run: ./v -o vprod -prod cmd/v && ./vprod test-self
- name: Self tests (vprod)
run: |
./v -o vprod -prod cmd/v
./vprod test-self
- name: Self tests (-cstrict)
run: ./v -cstrict test-self
run: V_CI_CSTRICT=1 ./vprod -cstrict test-self
- name: Build examples
run: ./v build-examples
- name: Build examples with -autofree

View File

@ -2,57 +2,56 @@ name: Periodic
on:
schedule:
- cron: '0 */2 * * *'
- cron: '0 */6 * * *'
jobs:
network-tests-ubuntu:
runs-on: ubuntu-20.04
timeout-minutes: 30
env:
V_CI_PERIODIC: 1
V_CI_PERIODIC: 1
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: sudo apt-get install --quiet -y libssl-dev sqlite3 libsqlite3-dev valgrind
- name: Build v
run: make -j4
- name: Symlink V
run: sudo ./v symlink
## - name: Run network tests
## run: ./v -d network test vlib/net
- uses: actions/checkout@v2
- name: Install dependencies 1
run: sudo apt-get install --quiet -y libssl-dev sqlite3 libsqlite3-dev
- name: Build v
run: make
- name: Symlink V
run: sudo ./v symlink
## - name: Run network tests
## run: ./v -d network test vlib/net
network-tests-macos:
runs-on: macOS-latest
timeout-minutes: 30
env:
V_CI_PERIODIC: 1
V_CI_PERIODIC: 1
steps:
- uses: actions/checkout@v2
- name: Setup openssl library path
run: export LIBRARY_PATH="$LIBRARY_PATH:/usr/local/opt/openssl/lib/"
- name: Build V
run: make -j4
- name: Symlink V
run: sudo ./v symlink
- name: Ensure thirdparty/cJSON/cJSON.o is compiled, before running tests.
run: ./v examples/json.v
## - name: Run network tests
## run: ./v -d network test vlib/net
- uses: actions/checkout@v2
- name: Setup openssl library path
run: export LIBRARY_PATH="$LIBRARY_PATH:/usr/local/opt/openssl/lib/"
- name: Build V
run: make
- name: Symlink V
run: sudo ./v symlink
- name: Ensure thirdparty/cJSON/cJSON.o is compiled, before running tests.
run: ./v examples/json.v
## - name: Run network tests
## run: ./v -d network test vlib/net
network-windows-msvc:
runs-on: windows-2019
timeout-minutes: 30
env:
V_CI_PERIODIC: 1
VFLAGS: -cc msvc
V_CI_PERIODIC: 1
VFLAGS: -cc msvc
steps:
- uses: actions/checkout@v2
- name: Build
run: |
echo %VFLAGS%
echo $VFLAGS
.\make.bat -msvc
## - name: Run network tests
## run: .\v.exe -d network test vlib/net
- uses: actions/checkout@v2
- name: Build
run: |
echo %VFLAGS%
echo $VFLAGS
.\make.bat -msvc
## - name: Run network tests
## run: .\v.exe -d network test vlib/net

View File

@ -76,7 +76,7 @@ SUBCMD:
// Snooped from cmd/v/v.v, vlib/v/pref/pref.v
const (
auto_complete_commands = [
auto_complete_commands = [
// simple_cmd
'ast',
'doc',
@ -114,7 +114,6 @@ const (
'help',
'new',
'init',
'complete',
'translate',
'self',
'search',
@ -130,8 +129,13 @@ const (
'run',
'build',
'build-module',
'missdoc',
]
auto_complete_flags = [
// Entries in the flag arrays below should be entered as is:
// * Short flags, e.g.: "-v", should be entered: '-v'
// * Long flags, e.g.: "--version", should be entered: '--version'
// * Single-dash flags, e.g.: "-version", should be entered: '-version'
auto_complete_flags = [
'-apk',
'-show-timings',
'-check-syntax',
@ -190,7 +194,7 @@ const (
'-version',
'--version',
]
auto_complete_flags_doc = [
auto_complete_flags_doc = [
'-all',
'-f',
'-h',
@ -209,7 +213,7 @@ const (
'-s',
'-l',
]
auto_complete_flags_fmt = [
auto_complete_flags_fmt = [
'-c',
'-diff',
'-l',
@ -217,7 +221,7 @@ const (
'-debug',
'-verify',
]
auto_complete_flags_bin2v = [
auto_complete_flags_bin2v = [
'-h',
'--help',
'-m',
@ -227,22 +231,39 @@ const (
'-w',
'--write',
]
auto_complete_flags_shader = [
'help',
'h',
'force-update',
'u',
'verbose',
'v',
'slang',
'l',
'output',
'o',
auto_complete_flags_shader = [
'--help',
'-h',
'--force-update',
'-u',
'--verbose',
'-v',
'--slang',
'-l',
'--output',
'-o',
]
auto_complete_flags_self = [
auto_complete_flags_missdoc = [
'--help',
'-h',
'--tags',
'-t',
'--deprecated',
'-d',
'--private',
'-p',
'--no-line-numbers',
'-n',
'--exclude',
'-e',
'--relative-paths',
'-r',
'--js',
]
auto_complete_flags_self = [
'-prod',
]
auto_complete_compilers = [
auto_complete_compilers = [
'cc',
'gcc',
'tcc',
@ -372,12 +393,17 @@ fn auto_complete_request(args []string) []string {
parent_command = parts[i]
break
}
get_flags := fn (base []string, flag string) []string {
if flag.len == 1 { return base
} else { return base.filter(it.starts_with(flag))
}
}
if part.starts_with('-') { // 'v -<tab>' -> flags.
if part.starts_with('-') { // 'v [subcmd] -<tab>' or 'v [subcmd] --<tab>'-> flags.
get_flags := fn (base []string, flag string) []string {
mut results := []string{}
for entry in base {
if entry.starts_with(flag) {
results << entry
}
}
return results
}
match parent_command {
'bin2v' { // 'v bin2v -<tab>'
list = get_flags(auto_complete_flags_bin2v, part)
@ -397,6 +423,9 @@ fn auto_complete_request(args []string) []string {
'shader' { // 'v shader -<tab>' -> flags.
list = get_flags(auto_complete_flags_shader, part)
}
'missdoc' { // 'v missdoc -<tab>' -> flags.
list = get_flags(auto_complete_flags_missdoc, part)
}
else {
for flag in auto_complete_flags {
if flag == part {
@ -414,6 +443,11 @@ fn auto_complete_request(args []string) []string {
}
}
}
// Clear the list if the result is identical to the part examined
// (the flag must have already been completed)
if list.len == 1 && part == list[0] {
list.clear()
}
} else {
match part {
'help' { // 'v help <tab>' -> top level commands except "help".

View File

@ -5,52 +5,67 @@ import testing
import v.util
import arrays
const (
vet_known_failing_exceptions = []string{}
vet_folders = [
'vlib/sqlite',
'vlib/v',
'vlib/x/json2',
'vlib/x/ttf',
'cmd/v',
'cmd/tools',
'examples/2048',
'examples/tetris',
'examples/term.ui',
]
verify_known_failing_exceptions = [
// Handcrafted meaningful formatting of code parts (mostly arrays)
'examples/sokol/02_cubes_glsl/cube_glsl.v',
'examples/sokol/03_march_tracing_glsl/rt_glsl.v',
'examples/sokol/04_multi_shader_glsl/rt_glsl.v',
'examples/sokol/05_instancing_glsl/rt_glsl.v',
'examples/sokol/06_obj_viewer/show_obj.v',
'vlib/v/checker/tests/modules/deprecated_module/main.v' /* adds deprecated_module. module prefix to imports, even though the folder has v.mod */,
'vlib/gg/m4/graphic.v',
'vlib/gg/m4/m4_test.v',
'vlib/gg/m4/matrix.v',
'vlib/builtin/int_test.v' /* special number formatting that should be tested */,
// TODOs and unfixed vfmt bugs
'vlib/v/gen/js/tests/js.v', /* local `hello` fn, gets replaced with module `hello` aliased as `hl` */
]
vfmt_verify_list = [
'cmd/',
'examples/',
'tutorials/',
'vlib/',
]
vfmt_known_failing_exceptions = arrays.merge(verify_known_failing_exceptions, [
'vlib/regex/regex_test.v' /* contains meaningfull formatting of the test case data */,
'vlib/crypto/sha512/sha512block_generic.v' /* formatting of large constant arrays wraps to too many lines */,
'vlib/crypto/aes/const.v' /* formatting of large constant arrays wraps to too many lines */,
])
)
const vet_known_failing = [
'do_not_delete_this',
]
const (
vexe = os.getenv('VEXE')
vroot = os.dir(vexe)
is_fix = '-fix' in os.args
)
const vet_known_failing_windows = [
'do_not_delete_this',
'vlib/v/gen/js/tests/testdata/byte_is_space.v',
'vlib/v/gen/js/tests/testdata/compare_ints.v',
'vlib/v/gen/js/tests/testdata/hw.v',
'vlib/v/gen/js/tests/testdata/string_methods.v',
'vlib/v/tests/project_with_modules_having_submodules/bin/main.vsh',
'vlib/v/tests/valgrind/simple_interpolation_script_mode.v',
'vlib/v/tests/valgrind/simple_interpolation_script_mode_more_scopes.v',
]
const vet_folders = [
'vlib/sqlite',
'vlib/v',
'vlib/x/json2',
'vlib/x/ttf',
'cmd/v',
'cmd/tools',
'examples/2048',
'examples/tetris',
'examples/term.ui',
]
const verify_known_failing_exceptions = [
// Handcrafted meaningful formatting of code parts (mostly arrays)
'examples/sokol/02_cubes_glsl/cube_glsl.v',
'examples/sokol/03_march_tracing_glsl/rt_glsl.v',
'examples/sokol/04_multi_shader_glsl/rt_glsl.v',
'examples/sokol/05_instancing_glsl/rt_glsl.v',
'examples/sokol/06_obj_viewer/show_obj.v',
'vlib/v/checker/tests/modules/deprecated_module/main.v' /* adds deprecated_module. module prefix to imports, even though the folder has v.mod */,
'vlib/gg/m4/graphic.v',
'vlib/gg/m4/m4_test.v',
'vlib/gg/m4/matrix.v',
'vlib/builtin/int_test.v' /* special number formatting that should be tested */,
// TODOs and unfixed vfmt bugs
'vlib/v/gen/js/tests/js.v', /* local `hello` fn, gets replaced with module `hello` aliased as `hl` */
]
const vfmt_verify_list = [
'cmd/',
'examples/',
'tutorials/',
'vlib/',
]
const vfmt_known_failing_exceptions = arrays.merge(verify_known_failing_exceptions, [
'vlib/regex/regex_test.v' /* contains meaningfull formatting of the test case data */,
'vlib/crypto/sha512/sha512block_generic.v' /* formatting of large constant arrays wraps to too many lines */,
'vlib/crypto/aes/const.v' /* formatting of large constant arrays wraps to too many lines */,
])
const vexe = os.getenv('VEXE')
const vroot = os.dir(vexe)
const is_fix = '-fix' in os.args
fn main() {
args_string := os.args[1..].join(' ')
@ -76,8 +91,12 @@ fn tsession(vargs string, tool_source string, tool_cmd string, tool_args string,
fn v_test_vetting(vargs string) {
expanded_vet_list := util.find_all_v_files(vet_folders) or { return }
mut vet_known_exceptions := vet_known_failing.clone()
if os.user_os() == 'windows' {
vet_known_exceptions << vet_known_failing_windows
}
vet_session := tsession(vargs, 'vvet', '${os.quoted_path(vexe)} vet', 'vet', expanded_vet_list,
vet_known_failing_exceptions)
vet_known_exceptions)
//
fmt_cmd, fmt_args := if is_fix {
'${os.quoted_path(vexe)} fmt -w', 'fmt -w'

View File

@ -55,7 +55,7 @@ fn panic_debug(line_no int, file string, mod string, fn_name string, s string) {
C.exit(1)
}
$if use_libbacktrace ? {
print_libbacktrace(1)
eprint_libbacktrace(1)
} $else {
print_backtrace_skipping_top_frames(1)
}
@ -106,7 +106,7 @@ pub fn panic(s string) {
C.exit(1)
}
$if use_libbacktrace ? {
print_libbacktrace(1)
eprint_libbacktrace(1)
} $else {
print_backtrace_skipping_top_frames(1)
}

View File

@ -130,6 +130,10 @@ pub:
[markused]
fn v_segmentation_fault_handler(signal int) {
eprintln('signal 11: segmentation fault')
print_backtrace()
$if use_libbacktrace ? {
eprint_libbacktrace(1)
} $else {
print_backtrace()
}
exit(128 + 11)
}

View File

@ -33,11 +33,11 @@ fn init_bt_state() &C.backtrace_state {
}
// for bt_error_callback
// struct BacktraceData {
// state &C.backtrace_state
// }
struct BacktraceOptions {
stdin bool = true
}
fn bt_print_callback(data voidptr, pc voidptr, filename_ptr &char, line int, fn_name_ptr &char) int {
fn bt_print_callback(data &BacktraceOptions, pc voidptr, filename_ptr &char, line int, fn_name_ptr &char) int {
filename := if isnil(filename_ptr) { '???' } else { unsafe { filename_ptr.vstring() } }
fn_name := if isnil(fn_name_ptr) {
'???'
@ -46,7 +46,12 @@ fn bt_print_callback(data voidptr, pc voidptr, filename_ptr &char, line int, fn_
}
// keep it for later
// pc_64 := u64(pc)
println('$filename:$line: by $fn_name')
bt_str := '$filename:$line: by $fn_name'
if data.stdin {
println(bt_str)
} else {
eprintln(bt_str)
}
return 0
}
@ -81,6 +86,17 @@ fn print_libbacktrace(frames_to_skip int) {
$if no_backtrace ? {
return
}
// data := &BacktraceData{bt_state}
C.backtrace_full(bt_state, frames_to_skip, bt_print_callback, bt_error_callback, 0)
data := &BacktraceOptions{}
C.backtrace_full(bt_state, frames_to_skip, bt_print_callback, bt_error_callback, data)
}
[noinline]
fn eprint_libbacktrace(frames_to_skip int) {
$if no_backtrace ? {
return
}
data := &BacktraceOptions{
stdin: false
}
C.backtrace_full(bt_state, frames_to_skip, bt_print_callback, bt_error_callback, data)
}

View File

@ -2,3 +2,7 @@ module builtin
fn print_libbacktrace(frames_to_skip int) {
}
[noinline]
fn eprint_libbacktrace(frames_to_skip int) {
}

View File

@ -433,7 +433,7 @@ fn test_set_bit() {
assert a == b
}
fn test_bit_len() {
fn test_bit_len() ? {
assert big.zero_int.bit_len() == 0
assert big.one_int.bit_len() == 1
@ -441,7 +441,10 @@ fn test_bit_len() {
assert big.one_int.lshift(1239).bit_len() == 1240
assert big.integer_from_string('4338476092346017364013796407961305761039463198075691378460917856') or {
panic('Could not read from decimal')
}.bit_len() == 212
mut num := big.integer_from_string('4338476092346017364013796407961305761039463198075691378460917856')?
assert num.bit_len() == 212
for num.bit_len() > 0 {
num = num.rshift(8)
assert true
}
}

View File

@ -960,6 +960,8 @@ pub fn (x Integer) bit_len() int {
if x.signum == 0 {
return 0
}
if x.digits.len == 0 {
return 0
}
return x.digits.len * 32 - bits.leading_zeros_32(x.digits.last())
}

View File

@ -343,6 +343,28 @@ pub fn user_os() string {
return 'unknown'
}
// user_names returns an array of the name of every user on the system.
pub fn user_names() ?[]string {
$if windows {
result := execute('wmic useraccount get name')
if result.exit_code != 0 {
return error('Failed to get user names. Exited with code $result.exit_code: $result.output')
}
mut users := result.output.split_into_lines()
// windows command prints an empty line at the end of output
users.delete(users.len - 1)
return users
} $else {
lines := read_lines('/etc/passwd')?
mut users := []string{cap: lines.len}
for line in lines {
end_name := line.index(':') or { line.len }
users << line[0..end_name]
}
return users
}
}
// home_dir returns path to the user's home directory.
pub fn home_dir() string {
$if windows {

View File

@ -56,6 +56,9 @@ pub fn (mut c Checker) check_types(got ast.Type, expected ast.Type) bool {
&& (expected_sym.info as ast.ArrayFixed).elem_type.is_any_kind_of_pointer() {
return true
}
if c.check_types((got_sym.info as ast.ArrayFixed).elem_type, (expected_sym.info as ast.ArrayFixed).elem_type) {
return true
}
}
if got_sym.kind == .enum_ {

View File

@ -555,626 +555,6 @@ pub fn (mut c Checker) expand_iface_embeds(idecl &ast.InterfaceDecl, level int,
return ares
}
fn (mut c Checker) check_div_mod_by_zero(expr ast.Expr, op_kind token.Kind) {
match expr {
ast.FloatLiteral {
if expr.val.f64() == 0.0 {
oper := if op_kind == .div { 'division' } else { 'modulo' }
c.error('$oper by zero', expr.pos)
}
}
ast.IntegerLiteral {
if expr.val.int() == 0 {
oper := if op_kind == .div { 'division' } else { 'modulo' }
c.error('$oper by zero', expr.pos)
}
}
ast.CastExpr {
c.check_div_mod_by_zero(expr.expr, op_kind)
}
else {}
}
}
pub fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type {
former_expected_type := c.expected_type
defer {
c.expected_type = former_expected_type
}
mut left_type := c.expr(node.left)
node.left_type = left_type
c.expected_type = left_type
mut right_type := c.expr(node.right)
node.right_type = right_type
if left_type.is_number() && !left_type.is_ptr()
&& right_type in [ast.int_literal_type, ast.float_literal_type] {
node.right_type = left_type
}
if right_type.is_number() && !right_type.is_ptr()
&& left_type in [ast.int_literal_type, ast.float_literal_type] {
node.left_type = right_type
}
mut right_sym := c.table.sym(right_type)
right_final := c.table.final_sym(right_type)
mut left_sym := c.table.sym(left_type)
left_final := c.table.final_sym(left_type)
left_pos := node.left.pos()
right_pos := node.right.pos()
left_right_pos := left_pos.extend(right_pos)
if left_type.is_any_kind_of_pointer()
&& node.op in [.plus, .minus, .mul, .div, .mod, .xor, .amp, .pipe] {
if !c.pref.translated && ((right_type.is_any_kind_of_pointer() && node.op != .minus)
|| (!right_type.is_any_kind_of_pointer() && node.op !in [.plus, .minus])) {
left_name := c.table.type_to_str(left_type)
right_name := c.table.type_to_str(right_type)
c.error('invalid operator `$node.op` to `$left_name` and `$right_name`', left_right_pos)
} else if node.op in [.plus, .minus] {
if !c.inside_unsafe && !node.left.is_auto_deref_var() && !node.right.is_auto_deref_var() {
c.warn('pointer arithmetic is only allowed in `unsafe` blocks', left_right_pos)
}
if left_type == ast.voidptr_type && !c.pref.translated {
c.error('`$node.op` cannot be used with `voidptr`', left_pos)
}
}
}
mut return_type := left_type
if node.op != .key_is {
match mut node.left {
ast.Ident, ast.SelectorExpr {
if node.left.is_mut {
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.
// TODO: First branch includes ops where single side check is not needed, or needed but hasn't been implemented.
// TODO: Some of the checks are not single side. Should find a better way to organize them.
match node.op {
// .eq, .ne, .gt, .lt, .ge, .le, .and, .logical_or, .dot, .key_as, .right_shift {}
.eq, .ne {
is_mismatch :=
(left_sym.kind == .alias && right_sym.kind in [.struct_, .array, .sum_type])
|| (right_sym.kind == .alias && left_sym.kind in [.struct_, .array, .sum_type])
if is_mismatch {
c.error('possible type mismatch of compared values of `$node.op` operation',
left_right_pos)
} else if left_type in ast.integer_type_idxs && right_type in ast.integer_type_idxs {
is_left_type_signed := left_type in ast.signed_integer_type_idxs
is_right_type_signed := right_type in ast.signed_integer_type_idxs
if !is_left_type_signed && mut node.right is ast.IntegerLiteral {
if node.right.val.int() < 0 && left_type in ast.int_promoted_type_idxs {
lt := c.table.sym(left_type).name
c.error('`$lt` cannot be compared with negative value', node.right.pos)
}
} else if !is_right_type_signed && mut node.left is ast.IntegerLiteral {
if node.left.val.int() < 0 && right_type in ast.int_promoted_type_idxs {
rt := c.table.sym(right_type).name
c.error('negative value cannot be compared with `$rt`', node.left.pos)
}
} else if is_left_type_signed != is_right_type_signed
&& left_type != ast.int_literal_type_idx
&& right_type != ast.int_literal_type_idx {
ls, _ := c.table.type_size(left_type)
rs, _ := c.table.type_size(right_type)
// prevent e.g. `u32 == i16` but not `u16 == i32` as max_u16 fits in i32
// TODO u32 == i32, change < to <=
if !c.pref.translated && ((is_left_type_signed && ls < rs)
|| (is_right_type_signed && rs < ls)) {
lt := c.table.sym(left_type).name
rt := c.table.sym(right_type).name
c.error('`$lt` cannot be compared with `$rt`', node.pos)
}
}
}
}
.key_in, .not_in {
match right_final.kind {
.array {
if left_sym.kind !in [.sum_type, .interface_] {
elem_type := right_final.array_info().elem_type
c.check_expected(left_type, elem_type) or {
c.error('left operand to `$node.op` does not match the array element type: $err.msg()',
left_right_pos)
}
}
}
.map {
map_info := right_final.map_info()
c.check_expected(left_type, map_info.key_type) or {
c.error('left operand to `$node.op` does not match the map key type: $err.msg()',
left_right_pos)
}
node.left_type = map_info.key_type
}
.array_fixed {
if left_sym.kind !in [.sum_type, .interface_] {
elem_type := right_final.array_fixed_info().elem_type
c.check_expected(left_type, elem_type) or {
c.error('left operand to `$node.op` does not match the fixed array element type: $err.msg()',
left_right_pos)
}
}
}
else {
c.error('`$node.op.str()` can only be used with arrays and maps',
node.pos)
}
}
return ast.bool_type
}
.plus, .minus, .mul, .div, .mod, .xor, .amp, .pipe {
// binary operators that expect matching types
if right_sym.info is ast.Alias && (right_sym.info as ast.Alias).language != .c
&& c.mod == c.table.type_to_str(right_type).split('.')[0]
&& c.table.sym((right_sym.info as ast.Alias).parent_type).is_primitive() {
right_sym = c.table.sym((right_sym.info as ast.Alias).parent_type)
}
if left_sym.info is ast.Alias && (left_sym.info as ast.Alias).language != .c
&& c.mod == c.table.type_to_str(left_type).split('.')[0]
&& c.table.sym((left_sym.info as ast.Alias).parent_type).is_primitive() {
left_sym = c.table.sym((left_sym.info as ast.Alias).parent_type)
}
if c.pref.translated && node.op in [.plus, .minus, .mul]
&& left_type.is_any_kind_of_pointer() && right_type.is_any_kind_of_pointer() {
return_type = left_type
} else if !c.pref.translated && left_sym.kind == .alias && left_sym.info is ast.Alias
&& !(c.table.sym((left_sym.info as ast.Alias).parent_type).is_primitive()) {
if left_sym.has_method(node.op.str()) {
if method := left_sym.find_method(node.op.str()) {
return_type = method.return_type
} else {
return_type = left_type
}
} else {
left_name := c.table.type_to_str(left_type)
right_name := c.table.type_to_str(right_type)
if left_name == right_name {
c.error('undefined operation `$left_name` $node.op.str() `$right_name`',
left_right_pos)
} else {
c.error('mismatched types `$left_name` and `$right_name`', left_right_pos)
}
}
} else if !c.pref.translated && right_sym.kind == .alias && right_sym.info is ast.Alias
&& !(c.table.sym((right_sym.info as ast.Alias).parent_type).is_primitive()) {
if right_sym.has_method(node.op.str()) {
if method := right_sym.find_method(node.op.str()) {
return_type = method.return_type
} else {
return_type = right_type
}
} else {
left_name := c.table.type_to_str(left_type)
right_name := c.table.type_to_str(right_type)
if left_name == right_name {
c.error('undefined operation `$left_name` $node.op.str() `$right_name`',
left_right_pos)
} else {
c.error('mismatched types `$left_name` and `$right_name`', left_right_pos)
}
}
}
if !c.pref.translated && left_sym.kind in [.array, .array_fixed, .map, .struct_] {
if left_sym.has_method_with_generic_parent(node.op.str()) {
if method := left_sym.find_method_with_generic_parent(node.op.str()) {
return_type = method.return_type
} else {
return_type = left_type
}
} else {
left_name := c.table.type_to_str(left_type)
right_name := c.table.type_to_str(right_type)
if left_name == right_name {
c.error('undefined operation `$left_name` $node.op.str() `$right_name`',
left_right_pos)
} else {
c.error('mismatched types `$left_name` and `$right_name`', left_right_pos)
}
}
} else if !c.pref.translated && right_sym.kind in [.array, .array_fixed, .map, .struct_] {
if right_sym.has_method_with_generic_parent(node.op.str()) {
if method := right_sym.find_method_with_generic_parent(node.op.str()) {
return_type = method.return_type
} else {
return_type = right_type
}
} else {
left_name := c.table.type_to_str(left_type)
right_name := c.table.type_to_str(right_type)
if left_name == right_name {
c.error('undefined operation `$left_name` $node.op.str() `$right_name`',
left_right_pos)
} else {
c.error('mismatched types `$left_name` and `$right_name`', left_right_pos)
}
}
} else if node.left.is_auto_deref_var() || node.right.is_auto_deref_var() {
deref_left_type := if node.left.is_auto_deref_var() {
left_type.deref()
} else {
left_type
}
deref_right_type := if node.right.is_auto_deref_var() {
right_type.deref()
} else {
right_type
}
left_name := c.table.type_to_str(ast.mktyp(deref_left_type))
right_name := c.table.type_to_str(ast.mktyp(deref_right_type))
if left_name != right_name {
c.error('mismatched types `$left_name` and `$right_name`', left_right_pos)
}
} else {
unaliased_left_type := c.table.unalias_num_type(left_type)
unalias_right_type := c.table.unalias_num_type(right_type)
mut promoted_type := c.promote(unaliased_left_type, unalias_right_type)
// substract pointers is allowed in unsafe block
is_allowed_pointer_arithmetic := left_type.is_any_kind_of_pointer()
&& right_type.is_any_kind_of_pointer() && node.op == .minus
if is_allowed_pointer_arithmetic {
promoted_type = ast.int_type
}
if promoted_type.idx() == ast.void_type_idx {
left_name := c.table.type_to_str(left_type)
right_name := c.table.type_to_str(right_type)
c.error('mismatched types `$left_name` and `$right_name`', left_right_pos)
} else if promoted_type.has_flag(.optional) {
s := c.table.type_to_str(promoted_type)
c.error('`$node.op` cannot be used with `$s`', node.pos)
} else if promoted_type.is_float() {
if node.op in [.mod, .xor, .amp, .pipe] {
side := if left_type == promoted_type { 'left' } else { 'right' }
pos := if left_type == promoted_type { left_pos } else { right_pos }
name := if left_type == promoted_type {
left_sym.name
} else {
right_sym.name
}
if node.op == .mod {
c.error('float modulo not allowed, use math.fmod() instead',
pos)
} else {
c.error('$side type of `$node.op.str()` cannot be non-integer type `$name`',
pos)
}
}
}
if node.op in [.div, .mod] {
c.check_div_mod_by_zero(node.right, node.op)
}
return_type = promoted_type
}
}
.gt, .lt, .ge, .le {
if left_sym.kind in [.array, .array_fixed] && right_sym.kind in [.array, .array_fixed] {
c.error('only `==` and `!=` are defined on arrays', node.pos)
} else if left_sym.kind == .struct_
&& (left_sym.info as ast.Struct).generic_types.len > 0 {
return ast.bool_type
} else if left_sym.kind == .struct_ && right_sym.kind == .struct_
&& node.op in [.eq, .lt] {
if !(left_sym.has_method(node.op.str()) && right_sym.has_method(node.op.str())) {
left_name := c.table.type_to_str(left_type)
right_name := c.table.type_to_str(right_type)
if left_name == right_name {
if !(node.op == .lt && c.pref.translated) {
// Allow `&Foo < &Foo` in translated code.
// TODO maybe in unsafe as well?
c.error('undefined operation `$left_name` $node.op.str() `$right_name`',
left_right_pos)
}
} else {
c.error('mismatched types `$left_name` and `$right_name`', left_right_pos)
}
}
}
if left_sym.kind == .struct_ && right_sym.kind == .struct_ {
if !left_sym.has_method('<') && node.op in [.ge, .le] {
c.error('cannot use `$node.op` as `<` operator method is not defined',
left_right_pos)
} else if !left_sym.has_method('<') && node.op == .gt {
c.error('cannot use `>` as `<=` operator method is not defined', left_right_pos)
}
} else if left_type.has_flag(.generic) && right_type.has_flag(.generic) {
// Try to unwrap the generic type to make sure that
// the below check works as expected
left_gen_type := c.unwrap_generic(left_type)
gen_sym := c.table.sym(left_gen_type)
need_overload := gen_sym.kind in [.struct_, .interface_]
if need_overload && !gen_sym.has_method_with_generic_parent('<')
&& node.op in [.ge, .le] {
c.error('cannot use `$node.op` as `<` operator method is not defined',
left_right_pos)
} else if need_overload && !gen_sym.has_method_with_generic_parent('<')
&& node.op == .gt {
c.error('cannot use `>` as `<=` operator method is not defined', left_right_pos)
}
} else if left_type in ast.integer_type_idxs && right_type in ast.integer_type_idxs {
is_left_type_signed := left_type in ast.signed_integer_type_idxs
|| left_type == ast.int_literal_type_idx
is_right_type_signed := right_type in ast.signed_integer_type_idxs
|| right_type == ast.int_literal_type_idx
if is_left_type_signed != is_right_type_signed {
if is_right_type_signed {
if mut node.right is ast.IntegerLiteral {
if node.right.val.int() < 0 {
c.error('unsigned integer cannot be compared with negative value',
node.right.pos)
}
}
} else if is_left_type_signed {
if mut node.left is ast.IntegerLiteral {
if node.left.val.int() < 0 {
c.error('unsigned integer cannot be compared with negative value',
node.left.pos)
}
}
}
}
} else if left_type.has_flag(.optional) || right_type.has_flag(.optional) {
opt_comp_pos := if left_type.has_flag(.optional) { left_pos } else { right_pos }
c.error('unwrapped optional cannot be compared in an infix expression',
opt_comp_pos)
}
}
.left_shift {
if left_final.kind == .array {
if !node.is_stmt {
c.error('array append cannot be used in an expression', node.pos)
}
// `array << elm`
c.check_expr_opt_call(node.right, right_type)
node.auto_locked, _ = c.fail_if_immutable(node.left)
left_value_type := c.table.value_type(c.unwrap_generic(left_type))
left_value_sym := c.table.sym(c.unwrap_generic(left_value_type))
if left_value_sym.kind == .interface_ {
if right_final.kind != .array {
// []Animal << Cat
if c.type_implements(right_type, left_value_type, right_pos) {
if !right_type.is_ptr() && !right_type.is_pointer() && !c.inside_unsafe
&& right_sym.kind != .interface_ {
c.mark_as_referenced(mut &node.right, true)
}
}
} else {
// []Animal << []Cat
c.type_implements(c.table.value_type(right_type), left_value_type,
right_pos)
}
return ast.void_type
} else if left_value_sym.kind == .sum_type {
if right_final.kind != .array {
if !c.table.is_sumtype_or_in_variant(left_value_type, ast.mktyp(right_type)) {
c.error('cannot append `$right_sym.name` to `$left_sym.name`',
right_pos)
}
} else {
right_value_type := c.table.value_type(right_type)
if !c.table.is_sumtype_or_in_variant(left_value_type, ast.mktyp(right_value_type)) {
c.error('cannot append `$right_sym.name` to `$left_sym.name`',
right_pos)
}
}
return ast.void_type
}
// []T << T or []T << []T
unwrapped_right_type := c.unwrap_generic(right_type)
if c.check_types(unwrapped_right_type, left_value_type) {
// []&T << T is wrong: we check for that, !(T.is_ptr()) && ?(&T).is_ptr()
if !(!unwrapped_right_type.is_ptr() && left_value_type.is_ptr()
&& left_value_type.share() == .mut_t) {
return ast.void_type
}
} else if c.check_types(unwrapped_right_type, c.unwrap_generic(left_type)) {
return ast.void_type
}
c.error('cannot append `$right_sym.name` to `$left_sym.name`', right_pos)
return ast.void_type
} else {
return c.check_shift(mut node, left_type, right_type)
}
}
.right_shift {
return c.check_shift(mut node, left_type, right_type)
}
.unsigned_right_shift {
modified_left_type := if !left_type.is_int() {
c.error('invalid operation: shift on type `${c.table.sym(left_type).name}`',
left_pos)
ast.void_type_idx
} else if left_type.is_int_literal() {
// int literal => i64
ast.u32_type_idx
} else if left_type.is_unsigned() {
left_type
} else {
// signed types' idx adds with 5 will get correct relative unsigned type
// i8 => byte
// i16 => u16
// int => u32
// i64 => u64
// isize => usize
// i128 => u128 NOT IMPLEMENTED YET
left_type.idx() + ast.u32_type_idx - ast.int_type_idx
}
if modified_left_type == 0 {
return ast.void_type
}
node = ast.InfixExpr{
left: ast.CastExpr{
expr: node.left
typ: modified_left_type
typname: c.table.type_str(modified_left_type)
pos: node.pos
}
left_type: left_type
op: .right_shift
right: node.right
right_type: right_type
is_stmt: false
pos: node.pos
auto_locked: node.auto_locked
or_block: node.or_block
}
return c.check_shift(mut node, left_type, right_type)
}
.key_is, .not_is {
right_expr := node.right
mut typ := match right_expr {
ast.TypeNode {
right_expr.typ
}
ast.None {
ast.none_type_idx
}
else {
c.error('invalid type `$right_expr`', right_expr.pos())
ast.Type(0)
}
}
if typ != ast.Type(0) {
typ_sym := c.table.sym(typ)
op := node.op.str()
if typ_sym.kind == .placeholder {
c.error('$op: type `$typ_sym.name` does not exist', right_expr.pos())
}
if left_sym.kind == .aggregate {
parent_left_type := (left_sym.info as ast.Aggregate).sum_type
left_sym = c.table.sym(parent_left_type)
}
if left_sym.kind !in [.interface_, .sum_type] {
c.error('`$op` can only be used with interfaces and sum types', node.pos)
} else if mut left_sym.info is ast.SumType {
if typ !in left_sym.info.variants {
c.error('`$left_sym.name` has no variant `$right_sym.name`', node.pos)
}
}
}
return ast.bool_type
}
.arrow { // `chan <- elem`
if left_sym.kind == .chan {
chan_info := left_sym.chan_info()
elem_type := chan_info.elem_type
if !c.check_types(right_type, elem_type) {
c.error('cannot push `$right_sym.name` on `$left_sym.name`', right_pos)
}
if chan_info.is_mut {
// TODO: The error message of the following could be more specific...
c.fail_if_immutable(node.right)
}
if elem_type.is_ptr() && !right_type.is_ptr() {
c.error('cannot push non-reference `$right_sym.name` on `$left_sym.name`',
right_pos)
}
c.stmts_ending_with_expression(node.or_block.stmts)
} else {
c.error('cannot push on non-channel `$left_sym.name`', left_pos)
}
return ast.void_type
}
.and, .logical_or {
if !c.pref.translated && !c.file.is_translated {
if node.left_type != ast.bool_type_idx {
c.error('left operand for `$node.op` is not a boolean', node.left.pos())
}
if node.right_type != ast.bool_type_idx {
c.error('right operand for `$node.op` is not a boolean', node.right.pos())
}
}
if mut node.left is ast.InfixExpr {
if node.left.op != node.op && node.left.op in [.logical_or, .and] {
// for example: `(a && b) || c` instead of `a && b || c`
c.error('ambiguous boolean expression. use `()` to ensure correct order of operations',
node.pos)
}
}
}
else {}
}
// TODO: Absorb this block into the above single side check block to accelerate.
if left_type == ast.bool_type && node.op !in [.eq, .ne, .logical_or, .and] {
c.error('bool types only have the following operators defined: `==`, `!=`, `||`, and `&&`',
node.pos)
} else if left_type == ast.string_type && node.op !in [.plus, .eq, .ne, .lt, .gt, .le, .ge] {
// TODO broken !in
c.error('string types only have the following operators defined: `==`, `!=`, `<`, `>`, `<=`, `>=`, and `+`',
node.pos)
} else if left_sym.kind == .enum_ && right_sym.kind == .enum_ && !eq_ne {
left_enum := left_sym.info as ast.Enum
right_enum := right_sym.info as ast.Enum
if left_enum.is_flag && right_enum.is_flag {
// `[flag]` tagged enums are a special case that allow also `|` and `&` binary operators
if node.op !in [.pipe, .amp] {
c.error('only `==`, `!=`, `|` and `&` are defined on `[flag]` tagged `enum`, use an explicit cast to `int` if needed',
node.pos)
}
} else if !c.pref.translated && !c.file.is_translated {
// Regular enums
c.error('only `==` and `!=` are defined on `enum`, use an explicit cast to `int` if needed',
node.pos)
}
}
// sum types can't have any infix operation except of `is`, `eq`, `ne`.
// `is` is checked before and doesn't reach this.
if c.table.type_kind(left_type) == .sum_type && !eq_ne {
c.error('cannot use operator `$node.op` with `$left_sym.name`', node.pos)
} else if c.table.type_kind(right_type) == .sum_type && !eq_ne {
c.error('cannot use operator `$node.op` with `$right_sym.name`', node.pos)
}
// TODO move this to symmetric_check? Right now it would break `return 0` for `fn()?int `
left_is_optional := left_type.has_flag(.optional)
right_is_optional := right_type.has_flag(.optional)
if left_is_optional && right_is_optional {
c.error('unwrapped optionals cannot be used in an infix expression', left_right_pos)
} else if left_is_optional || right_is_optional {
opt_infix_pos := if left_is_optional { left_pos } else { right_pos }
c.error('unwrapped optional cannot be used in an infix expression', opt_infix_pos)
}
// Dual sides check (compatibility check)
if !(c.symmetric_check(left_type, right_type) && c.symmetric_check(right_type, left_type))
&& !c.pref.translated && !c.file.is_translated && !node.left.is_auto_deref_var()
&& !node.right.is_auto_deref_var() {
// for type-unresolved consts
if left_type == ast.void_type || right_type == ast.void_type {
return ast.void_type
}
if left_type.nr_muls() > 0 && right_type.is_int() {
// pointer arithmetic is fine, it is checked in other places
return return_type
}
c.error('infix expr: cannot use `$right_sym.name` (right expression) as `$left_sym.name`',
left_right_pos)
}
/*
if (node.left is ast.InfixExpr &&
(node.left as ast.InfixExpr).op == .inc) ||
(node.right is ast.InfixExpr && (node.right as ast.InfixExpr).op == .inc) {
c.warn('`++` and `--` are statements, not expressions', node.pos)
}
*/
return if node.op.is_relational() { ast.bool_type } else { return_type }
}
// returns name and position of variable that needs write lock
// also sets `is_changed` to true (TODO update the name to reflect this?)
fn (mut c Checker) fail_if_immutable(expr_ ast.Expr) (string, token.Pos) {
@ -2460,6 +1840,9 @@ fn (mut c Checker) hash_stmt(mut node ast.HashStmt) {
'flag' {
// #flag linux -lm
mut flag := node.main
if flag == 'flag' { // Checks for empty flag
c.error('no argument(s) provided for #flag', node.pos)
}
if flag.contains('@VROOT') {
// c.note(checker.vroot_is_deprecated_message, node.pos)
flag = util.resolve_vmodroot(flag.replace('@VROOT', '@VMODROOT'), c.file.path) or {

View File

@ -0,0 +1,625 @@
module checker
import v.ast
import v.pref
import v.token
pub fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type {
former_expected_type := c.expected_type
defer {
c.expected_type = former_expected_type
}
mut left_type := c.expr(node.left)
node.left_type = left_type
c.expected_type = left_type
mut right_type := c.expr(node.right)
node.right_type = right_type
if left_type.is_number() && !left_type.is_ptr()
&& right_type in [ast.int_literal_type, ast.float_literal_type] {
node.right_type = left_type
}
if right_type.is_number() && !right_type.is_ptr()
&& left_type in [ast.int_literal_type, ast.float_literal_type] {
node.left_type = right_type
}
mut right_sym := c.table.sym(right_type)
right_final := c.table.final_sym(right_type)
mut left_sym := c.table.sym(left_type)
left_final := c.table.final_sym(left_type)
left_pos := node.left.pos()
right_pos := node.right.pos()
left_right_pos := left_pos.extend(right_pos)
if left_type.is_any_kind_of_pointer()
&& node.op in [.plus, .minus, .mul, .div, .mod, .xor, .amp, .pipe] {
if !c.pref.translated && ((right_type.is_any_kind_of_pointer() && node.op != .minus)
|| (!right_type.is_any_kind_of_pointer() && node.op !in [.plus, .minus])) {
left_name := c.table.type_to_str(left_type)
right_name := c.table.type_to_str(right_type)
c.error('invalid operator `$node.op` to `$left_name` and `$right_name`', left_right_pos)
} else if node.op in [.plus, .minus] {
if !c.inside_unsafe && !node.left.is_auto_deref_var() && !node.right.is_auto_deref_var() {
c.warn('pointer arithmetic is only allowed in `unsafe` blocks', left_right_pos)
}
if left_type == ast.voidptr_type && !c.pref.translated {
c.error('`$node.op` cannot be used with `voidptr`', left_pos)
}
}
}
mut return_type := left_type
if node.op != .key_is {
match mut node.left {
ast.Ident, ast.SelectorExpr {
if node.left.is_mut {
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.
// TODO: First branch includes ops where single side check is not needed, or needed but hasn't been implemented.
// TODO: Some of the checks are not single side. Should find a better way to organize them.
match node.op {
// .eq, .ne, .gt, .lt, .ge, .le, .and, .logical_or, .dot, .key_as, .right_shift {}
.eq, .ne {
is_mismatch :=
(left_sym.kind == .alias && right_sym.kind in [.struct_, .array, .sum_type])
|| (right_sym.kind == .alias && left_sym.kind in [.struct_, .array, .sum_type])
if is_mismatch {
c.error('possible type mismatch of compared values of `$node.op` operation',
left_right_pos)
} else if left_type in ast.integer_type_idxs && right_type in ast.integer_type_idxs {
is_left_type_signed := left_type in ast.signed_integer_type_idxs
is_right_type_signed := right_type in ast.signed_integer_type_idxs
if !is_left_type_signed && mut node.right is ast.IntegerLiteral {
if node.right.val.int() < 0 && left_type in ast.int_promoted_type_idxs {
lt := c.table.sym(left_type).name
c.error('`$lt` cannot be compared with negative value', node.right.pos)
}
} else if !is_right_type_signed && mut node.left is ast.IntegerLiteral {
if node.left.val.int() < 0 && right_type in ast.int_promoted_type_idxs {
rt := c.table.sym(right_type).name
c.error('negative value cannot be compared with `$rt`', node.left.pos)
}
} else if is_left_type_signed != is_right_type_signed
&& left_type != ast.int_literal_type_idx
&& right_type != ast.int_literal_type_idx {
ls, _ := c.table.type_size(left_type)
rs, _ := c.table.type_size(right_type)
// prevent e.g. `u32 == i16` but not `u16 == i32` as max_u16 fits in i32
// TODO u32 == i32, change < to <=
if !c.pref.translated && ((is_left_type_signed && ls < rs)
|| (is_right_type_signed && rs < ls)) {
lt := c.table.sym(left_type).name
rt := c.table.sym(right_type).name
c.error('`$lt` cannot be compared with `$rt`', node.pos)
}
}
}
}
.key_in, .not_in {
match right_final.kind {
.array {
if left_sym.kind !in [.sum_type, .interface_] {
elem_type := right_final.array_info().elem_type
c.check_expected(left_type, elem_type) or {
c.error('left operand to `$node.op` does not match the array element type: $err.msg()',
left_right_pos)
}
}
}
.map {
map_info := right_final.map_info()
c.check_expected(left_type, map_info.key_type) or {
c.error('left operand to `$node.op` does not match the map key type: $err.msg()',
left_right_pos)
}
node.left_type = map_info.key_type
}
.array_fixed {
if left_sym.kind !in [.sum_type, .interface_] {
elem_type := right_final.array_fixed_info().elem_type
c.check_expected(left_type, elem_type) or {
c.error('left operand to `$node.op` does not match the fixed array element type: $err.msg()',
left_right_pos)
}
}
}
else {
c.error('`$node.op.str()` can only be used with arrays and maps',
node.pos)
}
}
return ast.bool_type
}
.plus, .minus, .mul, .div, .mod, .xor, .amp, .pipe {
// binary operators that expect matching types
if right_sym.info is ast.Alias && (right_sym.info as ast.Alias).language != .c
&& c.mod == c.table.type_to_str(right_type).split('.')[0]
&& c.table.sym((right_sym.info as ast.Alias).parent_type).is_primitive() {
right_sym = c.table.sym((right_sym.info as ast.Alias).parent_type)
}
if left_sym.info is ast.Alias && (left_sym.info as ast.Alias).language != .c
&& c.mod == c.table.type_to_str(left_type).split('.')[0]
&& c.table.sym((left_sym.info as ast.Alias).parent_type).is_primitive() {
left_sym = c.table.sym((left_sym.info as ast.Alias).parent_type)
}
if c.pref.translated && node.op in [.plus, .minus, .mul]
&& left_type.is_any_kind_of_pointer() && right_type.is_any_kind_of_pointer() {
return_type = left_type
} else if !c.pref.translated && left_sym.kind == .alias && left_sym.info is ast.Alias
&& !(c.table.sym((left_sym.info as ast.Alias).parent_type).is_primitive()) {
if left_sym.has_method(node.op.str()) {
if method := left_sym.find_method(node.op.str()) {
return_type = method.return_type
} else {
return_type = left_type
}
} else {
left_name := c.table.type_to_str(left_type)
right_name := c.table.type_to_str(right_type)
if left_name == right_name {
c.error('undefined operation `$left_name` $node.op.str() `$right_name`',
left_right_pos)
} else {
c.error('mismatched types `$left_name` and `$right_name`', left_right_pos)
}
}
} else if !c.pref.translated && right_sym.kind == .alias && right_sym.info is ast.Alias
&& !(c.table.sym((right_sym.info as ast.Alias).parent_type).is_primitive()) {
if right_sym.has_method(node.op.str()) {
if method := right_sym.find_method(node.op.str()) {
return_type = method.return_type
} else {
return_type = right_type
}
} else {
left_name := c.table.type_to_str(left_type)
right_name := c.table.type_to_str(right_type)
if left_name == right_name {
c.error('undefined operation `$left_name` $node.op.str() `$right_name`',
left_right_pos)
} else {
c.error('mismatched types `$left_name` and `$right_name`', left_right_pos)
}
}
}
if !c.pref.translated && left_sym.kind in [.array, .array_fixed, .map, .struct_] {
if left_sym.has_method_with_generic_parent(node.op.str()) {
if method := left_sym.find_method_with_generic_parent(node.op.str()) {
return_type = method.return_type
} else {
return_type = left_type
}
} else {
left_name := c.table.type_to_str(left_type)
right_name := c.table.type_to_str(right_type)
if left_name == right_name {
c.error('undefined operation `$left_name` $node.op.str() `$right_name`',
left_right_pos)
} else {
c.error('mismatched types `$left_name` and `$right_name`', left_right_pos)
}
}
} else if !c.pref.translated && right_sym.kind in [.array, .array_fixed, .map, .struct_] {
if right_sym.has_method_with_generic_parent(node.op.str()) {
if method := right_sym.find_method_with_generic_parent(node.op.str()) {
return_type = method.return_type
} else {
return_type = right_type
}
} else {
left_name := c.table.type_to_str(left_type)
right_name := c.table.type_to_str(right_type)
if left_name == right_name {
c.error('undefined operation `$left_name` $node.op.str() `$right_name`',
left_right_pos)
} else {
c.error('mismatched types `$left_name` and `$right_name`', left_right_pos)
}
}
} else if node.left.is_auto_deref_var() || node.right.is_auto_deref_var() {
deref_left_type := if node.left.is_auto_deref_var() {
left_type.deref()
} else {
left_type
}
deref_right_type := if node.right.is_auto_deref_var() {
right_type.deref()
} else {
right_type
}
left_name := c.table.type_to_str(ast.mktyp(deref_left_type))
right_name := c.table.type_to_str(ast.mktyp(deref_right_type))
if left_name != right_name {
c.error('mismatched types `$left_name` and `$right_name`', left_right_pos)
}
} else {
unaliased_left_type := c.table.unalias_num_type(left_type)
unalias_right_type := c.table.unalias_num_type(right_type)
mut promoted_type := c.promote(unaliased_left_type, unalias_right_type)
// substract pointers is allowed in unsafe block
is_allowed_pointer_arithmetic := left_type.is_any_kind_of_pointer()
&& right_type.is_any_kind_of_pointer() && node.op == .minus
if is_allowed_pointer_arithmetic {
promoted_type = ast.int_type
}
if promoted_type.idx() == ast.void_type_idx {
left_name := c.table.type_to_str(left_type)
right_name := c.table.type_to_str(right_type)
c.error('mismatched types `$left_name` and `$right_name`', left_right_pos)
} else if promoted_type.has_flag(.optional) {
s := c.table.type_to_str(promoted_type)
c.error('`$node.op` cannot be used with `$s`', node.pos)
} else if promoted_type.is_float() {
if node.op in [.mod, .xor, .amp, .pipe] {
side := if left_type == promoted_type { 'left' } else { 'right' }
pos := if left_type == promoted_type { left_pos } else { right_pos }
name := if left_type == promoted_type {
left_sym.name
} else {
right_sym.name
}
if node.op == .mod {
c.error('float modulo not allowed, use math.fmod() instead',
pos)
} else {
c.error('$side type of `$node.op.str()` cannot be non-integer type `$name`',
pos)
}
}
}
if node.op in [.div, .mod] {
c.check_div_mod_by_zero(node.right, node.op)
}
return_type = promoted_type
}
}
.gt, .lt, .ge, .le {
if left_sym.kind in [.array, .array_fixed] && right_sym.kind in [.array, .array_fixed] {
c.error('only `==` and `!=` are defined on arrays', node.pos)
} else if left_sym.kind == .struct_
&& (left_sym.info as ast.Struct).generic_types.len > 0 {
return ast.bool_type
} else if left_sym.kind == .struct_ && right_sym.kind == .struct_
&& node.op in [.eq, .lt] {
if !(left_sym.has_method(node.op.str()) && right_sym.has_method(node.op.str())) {
left_name := c.table.type_to_str(left_type)
right_name := c.table.type_to_str(right_type)
if left_name == right_name {
if !(node.op == .lt && c.pref.translated) {
// Allow `&Foo < &Foo` in translated code.
// TODO maybe in unsafe as well?
c.error('undefined operation `$left_name` $node.op.str() `$right_name`',
left_right_pos)
}
} else {
c.error('mismatched types `$left_name` and `$right_name`', left_right_pos)
}
}
}
if left_sym.kind == .struct_ && right_sym.kind == .struct_ {
if !left_sym.has_method('<') && node.op in [.ge, .le] {
c.error('cannot use `$node.op` as `<` operator method is not defined',
left_right_pos)
} else if !left_sym.has_method('<') && node.op == .gt {
c.error('cannot use `>` as `<=` operator method is not defined', left_right_pos)
}
} else if left_type.has_flag(.generic) && right_type.has_flag(.generic) {
// Try to unwrap the generic type to make sure that
// the below check works as expected
left_gen_type := c.unwrap_generic(left_type)
gen_sym := c.table.sym(left_gen_type)
need_overload := gen_sym.kind in [.struct_, .interface_]
if need_overload && !gen_sym.has_method_with_generic_parent('<')
&& node.op in [.ge, .le] {
c.error('cannot use `$node.op` as `<` operator method is not defined',
left_right_pos)
} else if need_overload && !gen_sym.has_method_with_generic_parent('<')
&& node.op == .gt {
c.error('cannot use `>` as `<=` operator method is not defined', left_right_pos)
}
} else if left_type in ast.integer_type_idxs && right_type in ast.integer_type_idxs {
is_left_type_signed := left_type in ast.signed_integer_type_idxs
|| left_type == ast.int_literal_type_idx
is_right_type_signed := right_type in ast.signed_integer_type_idxs
|| right_type == ast.int_literal_type_idx
if is_left_type_signed != is_right_type_signed {
if is_right_type_signed {
if mut node.right is ast.IntegerLiteral {
if node.right.val.int() < 0 {
c.error('unsigned integer cannot be compared with negative value',
node.right.pos)
}
}
} else if is_left_type_signed {
if mut node.left is ast.IntegerLiteral {
if node.left.val.int() < 0 {
c.error('unsigned integer cannot be compared with negative value',
node.left.pos)
}
}
}
}
} else if left_type.has_flag(.optional) || right_type.has_flag(.optional) {
opt_comp_pos := if left_type.has_flag(.optional) { left_pos } else { right_pos }
c.error('unwrapped optional cannot be compared in an infix expression',
opt_comp_pos)
}
}
.left_shift {
if left_final.kind == .array {
if !node.is_stmt {
c.error('array append cannot be used in an expression', node.pos)
}
// `array << elm`
c.check_expr_opt_call(node.right, right_type)
node.auto_locked, _ = c.fail_if_immutable(node.left)
left_value_type := c.table.value_type(c.unwrap_generic(left_type))
left_value_sym := c.table.sym(c.unwrap_generic(left_value_type))
if left_value_sym.kind == .interface_ {
if right_final.kind != .array {
// []Animal << Cat
if c.type_implements(right_type, left_value_type, right_pos) {
if !right_type.is_ptr() && !right_type.is_pointer() && !c.inside_unsafe
&& right_sym.kind != .interface_ {
c.mark_as_referenced(mut &node.right, true)
}
}
} else {
// []Animal << []Cat
c.type_implements(c.table.value_type(right_type), left_value_type,
right_pos)
}
return ast.void_type
} else if left_value_sym.kind == .sum_type {
if right_final.kind != .array {
if !c.table.is_sumtype_or_in_variant(left_value_type, ast.mktyp(right_type)) {
c.error('cannot append `$right_sym.name` to `$left_sym.name`',
right_pos)
}
} else {
right_value_type := c.table.value_type(right_type)
if !c.table.is_sumtype_or_in_variant(left_value_type, ast.mktyp(right_value_type)) {
c.error('cannot append `$right_sym.name` to `$left_sym.name`',
right_pos)
}
}
return ast.void_type
}
// []T << T or []T << []T
unwrapped_right_type := c.unwrap_generic(right_type)
if c.check_types(unwrapped_right_type, left_value_type) {
// []&T << T is wrong: we check for that, !(T.is_ptr()) && ?(&T).is_ptr()
if !(!unwrapped_right_type.is_ptr() && left_value_type.is_ptr()
&& left_value_type.share() == .mut_t) {
return ast.void_type
}
} else if c.check_types(unwrapped_right_type, c.unwrap_generic(left_type)) {
return ast.void_type
}
c.error('cannot append `$right_sym.name` to `$left_sym.name`', right_pos)
return ast.void_type
} else {
return c.check_shift(mut node, left_type, right_type)
}
}
.right_shift {
return c.check_shift(mut node, left_type, right_type)
}
.unsigned_right_shift {
modified_left_type := if !left_type.is_int() {
c.error('invalid operation: shift on type `${c.table.sym(left_type).name}`',
left_pos)
ast.void_type_idx
} else if left_type.is_int_literal() {
// int literal => i64
ast.u32_type_idx
} else if left_type.is_unsigned() {
left_type
} else {
// signed types' idx adds with 5 will get correct relative unsigned type
// i8 => byte
// i16 => u16
// int => u32
// i64 => u64
// isize => usize
// i128 => u128 NOT IMPLEMENTED YET
left_type.idx() + ast.u32_type_idx - ast.int_type_idx
}
if modified_left_type == 0 {
return ast.void_type
}
node = ast.InfixExpr{
left: ast.CastExpr{
expr: node.left
typ: modified_left_type
typname: c.table.type_str(modified_left_type)
pos: node.pos
}
left_type: left_type
op: .right_shift
right: node.right
right_type: right_type
is_stmt: false
pos: node.pos
auto_locked: node.auto_locked
or_block: node.or_block
}
return c.check_shift(mut node, left_type, right_type)
}
.key_is, .not_is {
right_expr := node.right
mut typ := match right_expr {
ast.TypeNode {
right_expr.typ
}
ast.None {
ast.none_type_idx
}
else {
c.error('invalid type `$right_expr`', right_expr.pos())
ast.Type(0)
}
}
if typ != ast.Type(0) {
typ_sym := c.table.sym(typ)
op := node.op.str()
if typ_sym.kind == .placeholder {
c.error('$op: type `$typ_sym.name` does not exist', right_expr.pos())
}
if left_sym.kind == .aggregate {
parent_left_type := (left_sym.info as ast.Aggregate).sum_type
left_sym = c.table.sym(parent_left_type)
}
if left_sym.kind !in [.interface_, .sum_type] {
c.error('`$op` can only be used with interfaces and sum types', node.pos)
} else if mut left_sym.info is ast.SumType {
if typ !in left_sym.info.variants {
c.error('`$left_sym.name` has no variant `$right_sym.name`', node.pos)
}
}
}
return ast.bool_type
}
.arrow { // `chan <- elem`
if left_sym.kind == .chan {
chan_info := left_sym.chan_info()
elem_type := chan_info.elem_type
if !c.check_types(right_type, elem_type) {
c.error('cannot push `$right_sym.name` on `$left_sym.name`', right_pos)
}
if chan_info.is_mut {
// TODO: The error message of the following could be more specific...
c.fail_if_immutable(node.right)
}
if elem_type.is_ptr() && !right_type.is_ptr() {
c.error('cannot push non-reference `$right_sym.name` on `$left_sym.name`',
right_pos)
}
c.stmts_ending_with_expression(node.or_block.stmts)
} else {
c.error('cannot push on non-channel `$left_sym.name`', left_pos)
}
return ast.void_type
}
.and, .logical_or {
if !c.pref.translated && !c.file.is_translated {
if node.left_type != ast.bool_type_idx {
c.error('left operand for `$node.op` is not a boolean', node.left.pos())
}
if node.right_type != ast.bool_type_idx {
c.error('right operand for `$node.op` is not a boolean', node.right.pos())
}
}
if mut node.left is ast.InfixExpr {
if node.left.op != node.op && node.left.op in [.logical_or, .and] {
// for example: `(a && b) || c` instead of `a && b || c`
c.error('ambiguous boolean expression. use `()` to ensure correct order of operations',
node.pos)
}
}
}
else {}
}
// TODO: Absorb this block into the above single side check block to accelerate.
if left_type == ast.bool_type && node.op !in [.eq, .ne, .logical_or, .and] {
c.error('bool types only have the following operators defined: `==`, `!=`, `||`, and `&&`',
node.pos)
} else if left_type == ast.string_type && node.op !in [.plus, .eq, .ne, .lt, .gt, .le, .ge] {
// TODO broken !in
c.error('string types only have the following operators defined: `==`, `!=`, `<`, `>`, `<=`, `>=`, and `+`',
node.pos)
} else if left_sym.kind == .enum_ && right_sym.kind == .enum_ && !eq_ne {
left_enum := left_sym.info as ast.Enum
right_enum := right_sym.info as ast.Enum
if left_enum.is_flag && right_enum.is_flag {
// `[flag]` tagged enums are a special case that allow also `|` and `&` binary operators
if node.op !in [.pipe, .amp] {
c.error('only `==`, `!=`, `|` and `&` are defined on `[flag]` tagged `enum`, use an explicit cast to `int` if needed',
node.pos)
}
} else if !c.pref.translated && !c.file.is_translated {
// Regular enums
c.error('only `==` and `!=` are defined on `enum`, use an explicit cast to `int` if needed',
node.pos)
}
}
// sum types can't have any infix operation except of `is`, `eq`, `ne`.
// `is` is checked before and doesn't reach this.
if c.table.type_kind(left_type) == .sum_type && !eq_ne {
c.error('cannot use operator `$node.op` with `$left_sym.name`', node.pos)
} else if c.table.type_kind(right_type) == .sum_type && !eq_ne {
c.error('cannot use operator `$node.op` with `$right_sym.name`', node.pos)
}
// TODO move this to symmetric_check? Right now it would break `return 0` for `fn()?int `
left_is_optional := left_type.has_flag(.optional)
right_is_optional := right_type.has_flag(.optional)
if left_is_optional && right_is_optional {
c.error('unwrapped optionals cannot be used in an infix expression', left_right_pos)
} else if left_is_optional || right_is_optional {
opt_infix_pos := if left_is_optional { left_pos } else { right_pos }
c.error('unwrapped optional cannot be used in an infix expression', opt_infix_pos)
}
// Dual sides check (compatibility check)
if !(c.symmetric_check(left_type, right_type) && c.symmetric_check(right_type, left_type))
&& !c.pref.translated && !c.file.is_translated && !node.left.is_auto_deref_var()
&& !node.right.is_auto_deref_var() {
// for type-unresolved consts
if left_type == ast.void_type || right_type == ast.void_type {
return ast.void_type
}
if left_type.nr_muls() > 0 && right_type.is_int() {
// pointer arithmetic is fine, it is checked in other places
return return_type
}
c.error('infix expr: cannot use `$right_sym.name` (right expression) as `$left_sym.name`',
left_right_pos)
}
/*
if (node.left is ast.InfixExpr &&
(node.left as ast.InfixExpr).op == .inc) ||
(node.right is ast.InfixExpr && (node.right as ast.InfixExpr).op == .inc) {
c.warn('`++` and `--` are statements, not expressions', node.pos)
}
*/
return if node.op.is_relational() { ast.bool_type } else { return_type }
}
fn (mut c Checker) check_div_mod_by_zero(expr ast.Expr, op_kind token.Kind) {
match expr {
ast.FloatLiteral {
if expr.val.f64() == 0.0 {
oper := if op_kind == .div { 'division' } else { 'modulo' }
c.error('$oper by zero', expr.pos)
}
}
ast.IntegerLiteral {
if expr.val.int() == 0 {
oper := if op_kind == .div { 'division' } else { 'modulo' }
c.error('$oper by zero', expr.pos)
}
}
ast.CastExpr {
c.check_div_mod_by_zero(expr.expr, op_kind)
}
else {}
}
}

View File

@ -1,4 +1,5 @@
*.v
*.c
require_or_block_sumtype_map.err
!*_test.v
!modules/**/*.v
!modules/**/*.v

View File

@ -1,3 +1,9 @@
vlib/v/checker/tests/ambiguous_field_method_err.vv:23:2: warning: unused variable: `n`
21 | b := Bar{}
22 | b.test()
23 | n := b.name
| ^
24 | }
vlib/v/checker/tests/ambiguous_field_method_err.vv:22:4: error: ambiguous method `test`
20 | fn main() {
21 | b := Bar{}
@ -10,4 +16,4 @@ vlib/v/checker/tests/ambiguous_field_method_err.vv:23:9: error: ambiguous field
22 | b.test()
23 | n := b.name
| ~~~~
24 | }
24 | }

View File

@ -1,5 +1,11 @@
vlib/v/checker/tests/array_of_generic_struct_init_err.vv:6:6: warning: unused variable: `arr`
4 |
5 | fn main() {
6 | mut arr := []Item{}
| ~~~
7 | }
vlib/v/checker/tests/array_of_generic_struct_init_err.vv:6:15: error: generic struct must specify type parameter, e.g. Foo<int>
4 |
4 |
5 | fn main() {
6 | mut arr := []Item{}
| ~~~~

View File

@ -1,3 +1,9 @@
vlib/v/checker/tests/assign_to_typeless_variable_err.vv:2:3: warning: unused variable: `val`
1 | fn main() {
2 | val := {}
| ~~~
3 | val = 1
4 | }
vlib/v/checker/tests/assign_to_typeless_variable_err.vv:2:10: error: invalid empty map initialisation syntax, use e.g. map[string]int{} instead
1 | fn main() {
2 | val := {}

View File

@ -1,4 +1,11 @@
vlib/v/checker/tests/cannot_cast_to_struct.vv:10:7: error: casting to struct is deprecated, use e.g. `Struct{...expr}` instead
vlib/v/checker/tests/cannot_cast_to_struct.vv:10:7: warning: casting to struct is deprecated, use e.g. `Struct{...expr}` instead
8 |
9 | fn main() {
10 | _ := Test(Abc{})
| ~~~~~~~~~~~
11 | sum := Alphabet(Xyz{})
12 | _ = Xyz(sum)
vlib/v/checker/tests/cannot_cast_to_struct.vv:10:7: error: cannot convert struct `Abc` to struct `Test`
8 |
9 | fn main() {
10 | _ := Test(Abc{})

View File

@ -1,3 +1,10 @@
vlib/v/checker/tests/compile_error.vv:8:5: warning: On non Vinix this warning should be shown
6 |
7 | $if !vinix {
8 | $compile_warn('On non Vinix this warning should be shown')
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9 | }
10 |
vlib/v/checker/tests/compile_error.vv:4:5: error: Only Serenity is supported
2 |
3 | $if !serenity {
@ -5,10 +12,3 @@ vlib/v/checker/tests/compile_error.vv:4:5: error: Only Serenity is supported
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 | }
6 |
vlib/v/checker/tests/compile_error.vv:8:5: error: On non Vinix this warning should be shown
6 |
7 | $if !vinix {
8 | $compile_warn('On non Vinix this warning should be shown')
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9 | }
10 |

View File

@ -1,3 +1,16 @@
vlib/v/checker/tests/comptime_for.vv:2:7: warning: unused variable: `m`
1 | fn unknown() {
2 | $for m in Huh.methods {}
| ^
3 | $for f in Huh.fields {}
4 | $for f in T.fields {
vlib/v/checker/tests/comptime_for.vv:3:7: warning: unused variable: `f`
1 | fn unknown() {
2 | $for m in Huh.methods {}
3 | $for f in Huh.fields {}
| ^
4 | $for f in T.fields {
5 | $if f.typ is Huh {}
vlib/v/checker/tests/comptime_for.vv:2:12: error: unknown type `Huh`
1 | fn unknown() {
2 | $for m in Huh.methods {}

View File

@ -12,6 +12,26 @@ vlib/v/checker/tests/deprecations.vv:67:4: notice: method `Abc.future` will be d
| ~~~~~~~~
68 | a.past()
69 | a.simply_deprecated()
vlib/v/checker/tests/deprecations.vv:62:2: warning: function `simply_deprecated` has been deprecated; custom message 7
60 | future()
61 | past()
62 | simply_deprecated()
| ~~~~~~~~~~~~~~~~~~~
63 | just_deprecated()
64 | ancient()
vlib/v/checker/tests/deprecations.vv:63:2: warning: function `just_deprecated` has been deprecated
61 | past()
62 | simply_deprecated()
63 | just_deprecated()
| ~~~~~~~~~~~~~~~~~
64 | ancient()
65 | //
vlib/v/checker/tests/deprecations.vv:69:4: warning: method `Abc.simply_deprecated` has been deprecated; custom message 3
67 | a.future()
68 | a.past()
69 | a.simply_deprecated()
| ~~~~~~~~~~~~~~~~~~~
70 | }
vlib/v/checker/tests/deprecations.vv:61:2: error: function `past` has been deprecated since 2021-03-01; custom message 5
59 | fn main() {
60 | future()
@ -19,20 +39,6 @@ vlib/v/checker/tests/deprecations.vv:61:2: error: function `past` has been depre
| ~~~~~~
62 | simply_deprecated()
63 | just_deprecated()
vlib/v/checker/tests/deprecations.vv:62:2: error: function `simply_deprecated` has been deprecated; custom message 7
60 | future()
61 | past()
62 | simply_deprecated()
| ~~~~~~~~~~~~~~~~~~~
63 | just_deprecated()
64 | ancient()
vlib/v/checker/tests/deprecations.vv:63:2: error: function `just_deprecated` has been deprecated
61 | past()
62 | simply_deprecated()
63 | just_deprecated()
| ~~~~~~~~~~~~~~~~~
64 | ancient()
65 | //
vlib/v/checker/tests/deprecations.vv:64:2: error: function `ancient` has been deprecated since 1990-03-01; custom message 6
62 | simply_deprecated()
63 | just_deprecated()
@ -47,9 +53,3 @@ vlib/v/checker/tests/deprecations.vv:68:4: error: method `Abc.past` has been dep
| ~~~~~~
69 | a.simply_deprecated()
70 | }
vlib/v/checker/tests/deprecations.vv:69:4: error: method `Abc.simply_deprecated` has been deprecated; custom message 3
67 | a.future()
68 | a.past()
69 | a.simply_deprecated()
| ~~~~~~~~~~~~~~~~~~~
70 | }

View File

@ -1,3 +1,16 @@
vlib/v/checker/tests/disallow_pointer_arithmetic_err.vv:7:7: warning: pointer arithmetic is only allowed in `unsafe` blocks
5 | _ := p * p //should be error
6 | _ := p * 2 //should be error
7 | _ := p + 5 //OK but only in unsafe block, r is *int
| ~~~~~
8 | _ := p - p //OK even in safe code, but n should be isize
9 | }
vlib/v/checker/tests/disallow_pointer_arithmetic_err.vv:8:7: warning: pointer arithmetic is only allowed in `unsafe` blocks
6 | _ := p * 2 //should be error
7 | _ := p + 5 //OK but only in unsafe block, r is *int
8 | _ := p - p //OK even in safe code, but n should be isize
| ~~~~~
9 | }
vlib/v/checker/tests/disallow_pointer_arithmetic_err.vv:4:7: error: invalid operator `+` to `&int` and `&int`
2 | x := 5
3 | p := &x
@ -19,16 +32,3 @@ vlib/v/checker/tests/disallow_pointer_arithmetic_err.vv:6:7: error: invalid oper
| ~~~~~
7 | _ := p + 5 //OK but only in unsafe block, r is *int
8 | _ := p - p //OK even in safe code, but n should be isize
vlib/v/checker/tests/disallow_pointer_arithmetic_err.vv:7:7: error: pointer arithmetic is only allowed in `unsafe` blocks
5 | _ := p * p //should be error
6 | _ := p * 2 //should be error
7 | _ := p + 5 //OK but only in unsafe block, r is *int
| ~~~~~
8 | _ := p - p //OK even in safe code, but n should be isize
9 | }
vlib/v/checker/tests/disallow_pointer_arithmetic_err.vv:8:7: error: pointer arithmetic is only allowed in `unsafe` blocks
6 | _ := p * 2 //should be error
7 | _ := p + 5 //OK but only in unsafe block, r is *int
8 | _ := p - p //OK even in safe code, but n should be isize
| ~~~~~
9 | }

View File

@ -1,25 +1,25 @@
vlib/v/checker/tests/enum_cast.vv:9:13: error: 12 does not represent a value of enum Color
vlib/v/checker/tests/enum_cast.vv:9:13: warning: 12 does not represent a value of enum Color
7 | println(Color(0))
8 | println(Color(10))
9 | println(Color(12))
| ~~~~~~~~~
10 | println(Color(-10))
11 |
vlib/v/checker/tests/enum_cast.vv:10:13: error: -10 does not represent a value of enum Color
vlib/v/checker/tests/enum_cast.vv:10:13: warning: -10 does not represent a value of enum Color
8 | println(Color(10))
9 | println(Color(12))
10 | println(Color(-10))
| ~~~~~~~~~~
11 |
12 | println(Permissions(0b101))
vlib/v/checker/tests/enum_cast.vv:13:13: error: 10 does not represent a value of enum Permissions
vlib/v/checker/tests/enum_cast.vv:13:13: warning: 10 does not represent a value of enum Permissions
11 |
12 | println(Permissions(0b101))
13 | println(Permissions(0b1010))
| ~~~~~~~~~~~~~~~~~~~
14 | println(Permissions(-1))
15 | }
vlib/v/checker/tests/enum_cast.vv:14:13: error: -1 does not represent a value of enum Permissions
vlib/v/checker/tests/enum_cast.vv:14:13: warning: -1 does not represent a value of enum Permissions
12 | println(Permissions(0b101))
13 | println(Permissions(0b1010))
14 | println(Permissions(-1))

View File

@ -1,3 +1,9 @@
vlib/v/checker/tests/filter_func_return_nonbool_err.vv:2:2: warning: unused variable: `list`
1 | fn main() {
2 | list := [1,2,3].filter(stringsss(it))
| ~~~~
3 | }
4 |
vlib/v/checker/tests/filter_func_return_nonbool_err.vv:2:25: error: type mismatch, `stringsss` must return a bool
1 | fn main() {
2 | list := [1,2,3].filter(stringsss(it))

View File

@ -1,3 +1,10 @@
vlib/v/checker/tests/fixed_array_conv.vv:6:5: warning: cannot cast a fixed array (use e.g. `&arr[0]` instead)
4 | mut ip := &int(0)
5 | ip = arr
6 | _ = &int(arr)
| ~~~~~~~~~
7 | _ = p
8 | _ = ip
vlib/v/checker/tests/fixed_array_conv.vv:3:3: error: mismatched types `voidptr` and `[2]int`
1 | arr := [2,3]!
2 | mut p := voidptr(0)
@ -12,13 +19,6 @@ vlib/v/checker/tests/fixed_array_conv.vv:5:4: error: mismatched types `&int` and
| ^
6 | _ = &int(arr)
7 | _ = p
vlib/v/checker/tests/fixed_array_conv.vv:6:5: error: cannot cast a fixed array (use e.g. `&arr[0]` instead)
4 | mut ip := &int(0)
5 | ip = arr
6 | _ = &int(arr)
| ~~~~~~~~~
7 | _ = p
8 | _ = ip
vlib/v/checker/tests/fixed_array_conv.vv:11:13: error: cannot use `[2]int` as `voidptr` in argument 1 to `memdup`
9 |
10 | unsafe {

View File

@ -1,3 +1,8 @@
vlib/v/checker/tests/generic_interface_err.vv:10:1: warning: unused variable: `i`
8 |
9 | s := Struct{7}
10 | i := Interface(s)
| ^
vlib/v/checker/tests/generic_interface_err.vv:9:6: error: generic struct init must specify type parameter, e.g. Foo<int>
7 | }
8 |

View File

@ -1,3 +1,31 @@
vlib/v/checker/tests/goto_label.vv:7:8: warning: `goto` requires `unsafe` (consider using labelled break/continue)
5 | goto a1
6 | _ = fn(){
7 | goto f1
| ~~
8 | goto f2
9 | goto a1
vlib/v/checker/tests/goto_label.vv:8:8: warning: `goto` requires `unsafe` (consider using labelled break/continue)
6 | _ = fn(){
7 | goto f1
8 | goto f2
| ~~
9 | goto a1
10 | a1:
vlib/v/checker/tests/goto_label.vv:9:8: warning: `goto` requires `unsafe` (consider using labelled break/continue)
7 | goto f1
8 | goto f2
9 | goto a1
| ~~
10 | a1:
11 | goto a1
vlib/v/checker/tests/goto_label.vv:11:8: warning: `goto` requires `unsafe` (consider using labelled break/continue)
9 | goto a1
10 | a1:
11 | goto a1
| ~~
12 | }
13 | f2:
vlib/v/checker/tests/goto_label.vv:5:7: error: unknown label `a1`
3 | goto f2
4 | f1:
@ -19,20 +47,6 @@ vlib/v/checker/tests/goto_label.vv:8:8: error: unknown label `f2`
| ~~
9 | goto a1
10 | a1:
vlib/v/checker/tests/goto_label.vv:9:8: error: `goto` requires `unsafe` (consider using labelled break/continue)
7 | goto f1
8 | goto f2
9 | goto a1
| ~~
10 | a1:
11 | goto a1
vlib/v/checker/tests/goto_label.vv:11:8: error: `goto` requires `unsafe` (consider using labelled break/continue)
9 | goto a1
10 | a1:
11 | goto a1
| ~~
12 | }
13 | f2:
vlib/v/checker/tests/goto_label.vv:14:7: error: unknown label `a1`
12 | }
13 | f2:

View File

@ -1,3 +1,8 @@
vlib/v/checker/tests/interface_generic_err.vv:8:1: warning: unused variable: `why`
6 | // no segfault without generic
7 | what := What{}
8 | why := Why(what)
| ~~~
vlib/v/checker/tests/interface_generic_err.vv:7:9: error: generic struct init must specify type parameter, e.g. Foo<int>
5 |
6 | // no segfault without generic

View File

@ -1,3 +1,9 @@
vlib/v/checker/tests/map_func_void_return_err.vv:2:2: warning: unused variable: `list`
1 | fn main() {
2 | list := [1,2,3].map(voids(it))
| ~~~~
3 | }
4 |
vlib/v/checker/tests/map_func_void_return_err.vv:2:22: error: type mismatch, `voids` does not return anything
1 | fn main() {
2 | list := [1,2,3].map(voids(it))

View File

@ -1,5 +1,11 @@
vlib/v/checker/tests/map_of_generic_struct_init_err.vv:6:6: warning: unused variable: `m`
4 |
5 | fn main() {
6 | mut m := map[string]Item{}
| ^
7 | }
vlib/v/checker/tests/map_of_generic_struct_init_err.vv:6:11: error: generic struct `Item` must specify type parameter, e.g. Foo<int>
4 |
4 |
5 | fn main() {
6 | mut m := map[string]Item{}
| ~~~~~~~~~~~~~~~~~

View File

@ -1,3 +1,17 @@
vlib/v/checker/tests/mut_arg.vv:6:3: warning: automatic referencing/dereferencing is deprecated and will be removed soon (got: 0 references, expected: 1 references)
4 | }
5 |
6 | f([3,4])
| ~~~~~
7 | mut a := [1,2]
8 | f(a)
vlib/v/checker/tests/mut_arg.vv:8:3: warning: automatic referencing/dereferencing is deprecated and will be removed soon (got: 0 references, expected: 1 references)
6 | f([3,4])
7 | mut a := [1,2]
8 | f(a)
| ^
9 |
10 | g(mut [3,4])
vlib/v/checker/tests/mut_arg.vv:6:3: error: function `f` parameter `par` is `mut`, so use `mut [3, 4]` instead
4 | }
5 |

View File

@ -1,4 +1,4 @@
vlib/v/checker/tests/redundant_parentheses_warning.vv:3:7: error: redundant parentheses are used
vlib/v/checker/tests/redundant_parentheses_warning.vv:3:7: warning: redundant parentheses are used
1 | fn main() {
2 | a := 2
3 | b := ((a + 2))

View File

@ -1,4 +1,4 @@
vlib/v/checker/tests/require_or_block_sumtype_map.err.vv:8:8: error: `or {}` block required when indexing a map with sum type value
vlib/v/checker/tests/require_or_block_sumtype_map.err.vv:8:8: warning: `or {}` block required when indexing a map with sum type value
6 | println(y)
7 | }
8 | _ := x['nonexisting']

View File

@ -1,4 +1,10 @@
vlib/v/checker/tests/struct_cast_to_struct_generic_err.vv:11:7: error: casting to struct is deprecated, use e.g. `Struct{...expr}` instead
vlib/v/checker/tests/struct_cast_to_struct_generic_err.vv:11:7: warning: casting to struct is deprecated, use e.g. `Struct{...expr}` instead
9 | fn main() {
10 | abc := Abc<int>{}
11 | _ := Xyz(abc)
| ~~~~~~~~
12 | }
vlib/v/checker/tests/struct_cast_to_struct_generic_err.vv:11:7: error: cannot convert struct `Abc<int>` to struct `Xyz`
9 | fn main() {
10 | abc := Abc<int>{}
11 | _ := Xyz(abc)

View File

@ -1,4 +1,10 @@
vlib/v/checker/tests/struct_cast_to_struct_mut_err_a.vv:12:7: error: casting to struct is deprecated, use e.g. `Struct{...expr}` instead
vlib/v/checker/tests/struct_cast_to_struct_mut_err_a.vv:12:7: warning: casting to struct is deprecated, use e.g. `Struct{...expr}` instead
10 | fn main() {
11 | abc := Abc{}
12 | _ := Xyz(abc)
| ~~~~~~~~
13 | }
vlib/v/checker/tests/struct_cast_to_struct_mut_err_a.vv:12:7: error: cannot convert struct `Abc` to struct `Xyz`
10 | fn main() {
11 | abc := Abc{}
12 | _ := Xyz(abc)

View File

@ -1,4 +1,10 @@
vlib/v/checker/tests/struct_cast_to_struct_mut_err_b.vv:12:7: error: casting to struct is deprecated, use e.g. `Struct{...expr}` instead
vlib/v/checker/tests/struct_cast_to_struct_mut_err_b.vv:12:7: warning: casting to struct is deprecated, use e.g. `Struct{...expr}` instead
10 | fn main() {
11 | abc := Abc{}
12 | _ := Xyz(abc)
| ~~~~~~~~
13 | }
vlib/v/checker/tests/struct_cast_to_struct_mut_err_b.vv:12:7: error: cannot convert struct `Abc` to struct `Xyz`
10 | fn main() {
11 | abc := Abc{}
12 | _ := Xyz(abc)

View File

@ -1,4 +1,10 @@
vlib/v/checker/tests/struct_cast_to_struct_pub_err_a.vv:12:7: error: casting to struct is deprecated, use e.g. `Struct{...expr}` instead
vlib/v/checker/tests/struct_cast_to_struct_pub_err_a.vv:12:7: warning: casting to struct is deprecated, use e.g. `Struct{...expr}` instead
10 | fn main() {
11 | abc := Abc{}
12 | _ := Xyz(abc)
| ~~~~~~~~
13 | }
vlib/v/checker/tests/struct_cast_to_struct_pub_err_a.vv:12:7: error: cannot convert struct `Abc` to struct `Xyz`
10 | fn main() {
11 | abc := Abc{}
12 | _ := Xyz(abc)

View File

@ -1,4 +1,10 @@
vlib/v/checker/tests/struct_cast_to_struct_pub_err_b.vv:12:7: error: casting to struct is deprecated, use e.g. `Struct{...expr}` instead
vlib/v/checker/tests/struct_cast_to_struct_pub_err_b.vv:12:7: warning: casting to struct is deprecated, use e.g. `Struct{...expr}` instead
10 | fn main() {
11 | abc := Abc{}
12 | _ := Xyz(abc)
| ~~~~~~~~
13 | }
vlib/v/checker/tests/struct_cast_to_struct_pub_err_b.vv:12:7: error: cannot convert struct `Abc` to struct `Xyz`
10 | fn main() {
11 | abc := Abc{}
12 | _ := Xyz(abc)

View File

@ -1,20 +1,20 @@
vlib/v/checker/tests/struct_unneeded_default.vv:2:10: error: unnecessary default value of `0`: struct fields are zeroed by default
vlib/v/checker/tests/struct_unneeded_default.vv:2:10: warning: unnecessary default value of `0`: struct fields are zeroed by default
1 | struct Test {
2 | n int = 0
| ^
3 | s string = ''
4 | b bool = false
vlib/v/checker/tests/struct_unneeded_default.vv:3:13: error: unnecessary default value of '': struct fields are zeroed by default
vlib/v/checker/tests/struct_unneeded_default.vv:3:13: warning: unnecessary default value of '': struct fields are zeroed by default
1 | struct Test {
2 | n int = 0
3 | s string = ''
| ~~
4 | b bool = false
5 | }
vlib/v/checker/tests/struct_unneeded_default.vv:4:11: error: unnecessary default value `false`: struct fields are zeroed by default
vlib/v/checker/tests/struct_unneeded_default.vv:4:11: warning: unnecessary default value `false`: struct fields are zeroed by default
2 | n int = 0
3 | s string = ''
4 | b bool = false
| ~~~~~
5 | }
6 |
6 |

View File

@ -1,13 +1,13 @@
vlib/v/checker/tests/undefined_type_on_sumtype.vv:1:17: error: unknown type `Token`.
Did you mean `Ok<[]Token>`?
1 | type ParseRes = Result<[]Token, ParseErr>
| ~~~~~~~~~~~~~~~~~~~~~~~~~
2 |
3 | // Token type is unknown
vlib/v/checker/tests/undefined_type_on_sumtype.vv:30:4: error: unused variable: `rx`
vlib/v/checker/tests/undefined_type_on_sumtype.vv:30:4: warning: unused variable: `rx`
28 | match r {
29 | Some<ParseRes> {
30 | rx := r.value
| ~~
31 | }
32 | None<ParseRes> {}
vlib/v/checker/tests/undefined_type_on_sumtype.vv:1:17: error: unknown type `Token`.
Did you mean `Ok<[]Token>`?
1 | type ParseRes = Result<[]Token, ParseErr>
| ~~~~~~~~~~~~~~~~~~~~~~~~~
2 |
3 | // Token type is unknown

View File

@ -1,13 +1,19 @@
vlib/v/checker/tests/union_unsafe_fields.vv:10:9: error: reading a union field (or its address) requires `unsafe`
vlib/v/checker/tests/union_unsafe_fields.vv:10:9: warning: reading a union field (or its address) requires `unsafe`
8 | mut u := Uf32{u: 3}
9 | u.f = 3.3 // ok
10 | _ := u.u
| ^
11 | return &u.f
12 | }
vlib/v/checker/tests/union_unsafe_fields.vv:11:12: error: reading a union field (or its address) requires `unsafe`
vlib/v/checker/tests/union_unsafe_fields.vv:11:12: warning: reading a union field (or its address) requires `unsafe`
9 | u.f = 3.3 // ok
10 | _ := u.u
11 | return &u.f
| ^
12 | }
vlib/v/checker/tests/union_unsafe_fields.vv:11:9: error: fn `f` expects you to return a non reference type `f32`, but you are returning `&f32` instead
9 | u.f = 3.3 // ok
10 | _ := u.u
11 | return &u.f
| ^
12 | }

View File

@ -1,11 +1,11 @@
vlib/v/checker/tests/unsafe_c_calls_should_be_checked.vv:3:16: error: function `C.malloc` must be called from an `unsafe` block
1 |
vlib/v/checker/tests/unsafe_c_calls_should_be_checked.vv:3:16: warning: function `C.malloc` must be called from an `unsafe` block
1 |
2 | fn test_c() {
3 | mut p := C.malloc(4)
| ~~~~~~~~~
4 | s := 'hope'
5 | C.memcpy(p, s.str, 4)
vlib/v/checker/tests/unsafe_c_calls_should_be_checked.vv:5:7: error: function `C.memcpy` must be called from an `unsafe` block
vlib/v/checker/tests/unsafe_c_calls_should_be_checked.vv:5:7: warning: function `C.memcpy` must be called from an `unsafe` block
3 | mut p := C.malloc(4)
4 | s := 'hope'
5 | C.memcpy(p, s.str, 4)

View File

@ -1,25 +1,25 @@
vlib/v/checker/tests/unsafe_pointer_arithmetic_should_be_checked.vv:4:6: error: pointer arithmetic is only allowed in `unsafe` blocks
vlib/v/checker/tests/unsafe_pointer_arithmetic_should_be_checked.vv:4:6: warning: pointer arithmetic is only allowed in `unsafe` blocks
2 | mut v := 5
3 | mut p := &v
4 | p++
| ~~
5 | p += 2
6 | _ := v
vlib/v/checker/tests/unsafe_pointer_arithmetic_should_be_checked.vv:5:7: error: pointer arithmetic is only allowed in `unsafe` blocks
vlib/v/checker/tests/unsafe_pointer_arithmetic_should_be_checked.vv:5:7: warning: pointer arithmetic is only allowed in `unsafe` blocks
3 | mut p := &v
4 | p++
5 | p += 2
| ~~
6 | _ := v
7 | }
vlib/v/checker/tests/unsafe_pointer_arithmetic_should_be_checked.vv:11:14: error: pointer arithmetic is only allowed in `unsafe` blocks
vlib/v/checker/tests/unsafe_pointer_arithmetic_should_be_checked.vv:11:14: warning: pointer arithmetic is only allowed in `unsafe` blocks
9 | fn test_ptr_infix() {
10 | v := 4
11 | mut q := &v - 1
| ~~~~~~
12 | q = q + 3
13 | _ := q
vlib/v/checker/tests/unsafe_pointer_arithmetic_should_be_checked.vv:12:9: error: pointer arithmetic is only allowed in `unsafe` blocks
vlib/v/checker/tests/unsafe_pointer_arithmetic_should_be_checked.vv:12:9: warning: pointer arithmetic is only allowed in `unsafe` blocks
10 | v := 4
11 | mut q := &v - 1
12 | q = q + 3

View File

@ -1,18 +1,18 @@
vlib/v/checker/tests/unsafe_required.vv:8:7: error: method `S1.f` must be called from an `unsafe` block
vlib/v/checker/tests/unsafe_required.vv:8:7: warning: method `S1.f` must be called from an `unsafe` block
6 | fn test_funcs() {
7 | s := S1{}
8 | s.f()
| ~~~
9 | }
10 |
vlib/v/checker/tests/unsafe_required.vv:16:7: error: pointer indexing is only allowed in `unsafe` blocks
vlib/v/checker/tests/unsafe_required.vv:16:7: warning: pointer indexing is only allowed in `unsafe` blocks
14 | _ = b[0] // OK
15 | c := &b
16 | _ = c[0]
| ~~~
17 |
18 | v := 4
vlib/v/checker/tests/unsafe_required.vv:20:10: error: pointer indexing is only allowed in `unsafe` blocks
vlib/v/checker/tests/unsafe_required.vv:20:10: warning: pointer indexing is only allowed in `unsafe` blocks
18 | v := 4
19 | p := &v
20 | _ = p[0]

View File

@ -1,18 +1,18 @@
vlib/v/checker/tests/use_deprecated_function_warning.vv:12:2: error: function `xyz` has been deprecated
vlib/v/checker/tests/use_deprecated_function_warning.vv:12:2: warning: function `xyz` has been deprecated
10 |
11 | fn main() {
12 | xyz()
| ~~~~~
13 | abc()
14 | }
vlib/v/checker/tests/use_deprecated_function_warning.vv:13:2: error: function `abc` has been deprecated; use foo2 instead
vlib/v/checker/tests/use_deprecated_function_warning.vv:13:2: warning: function `abc` has been deprecated; use foo2 instead
11 | fn main() {
12 | xyz()
13 | abc()
| ~~~~~
14 | }
15 |
vlib/v/checker/tests/use_deprecated_function_warning.vv:23:4: error: method `S1.m` has been deprecated; use bar instead
vlib/v/checker/tests/use_deprecated_function_warning.vv:23:4: warning: method `S1.m` has been deprecated; use bar instead
21 | fn method() {
22 | s := S1{}
23 | s.m()

View File

@ -1,5 +1,12 @@
vlib/v/checker/tests/var_duplicate_const.vv:4:5: error: duplicate of a const name `size`
2 |
vlib/v/checker/tests/var_duplicate_const.vv:4:5: warning: duplicate of a const name `size`
2 |
3 | fn main() {
4 | size := 11
| ~~~~
5 | println(main.size)
6 | }
vlib/v/checker/tests/var_duplicate_const.vv:4:5: warning: unused variable: `size`
2 |
3 | fn main() {
4 | size := 11
| ~~~~

View File

@ -1,11 +1,11 @@
vlib/v/checker/tests/vweb_routing_checks.vv:20:1: error: mismatched parameters count between vweb method `App.bar` (1) and route attribute ['/bar'] (0)
vlib/v/checker/tests/vweb_routing_checks.vv:20:1: warning: mismatched parameters count between vweb method `App.bar` (1) and route attribute ['/bar'] (0)
18 | // segfault because path taks 0 vars and fcn takes 1 arg
19 | ['/bar']
20 | pub fn (mut app App) bar(a string) vweb.Result {
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
21 | return app.html('works')
22 | }
vlib/v/checker/tests/vweb_routing_checks.vv:26:1: error: mismatched parameters count between vweb method `App.cow` (0) and route attribute ['/cow/:low'] (1)
vlib/v/checker/tests/vweb_routing_checks.vv:26:1: warning: mismatched parameters count between vweb method `App.cow` (0) and route attribute ['/cow/:low'] (1)
24 | // no segfault, but it shouldnt compile
25 | ['/cow/:low']
26 | pub fn (mut app App) cow() vweb.Result {

View File

@ -4,7 +4,6 @@ import term
import v.util.diff
import v.util.vtest
import time
import sync
import runtime
import benchmark
@ -12,12 +11,22 @@ const skip_files = [
'non_existing.vv', // minimize commit diff churn, do not remove
]
const skip_on_ubuntu_musl = [
'vlib/v/checker/tests/vweb_tmpl_used_var.vv',
const skip_on_cstrict = [
'vlib/v/checker/tests/missing_c_lib_header_1.vv',
'vlib/v/checker/tests/missing_c_lib_header_with_explanation_2.vv',
]
const skip_on_ubuntu_musl = [
'vlib/v/checker/tests/vweb_tmpl_used_var.vv',
'vlib/v/checker/tests/vweb_routing_checks.vv',
]
const vexe = os.getenv('VEXE')
const turn_off_vcolors = os.setenv('VCOLORS', 'never', true)
const show_cmd = os.getenv('VTEST_SHOW_CMD') != ''
// This is needed, because some of the .vv files are tests, and we do need stable
// output from them, that can be compared against their .out files:
const turn_on_normal_test_runner = os.setenv('VTEST_RUNNER', 'normal', true)
@ -26,6 +35,10 @@ const should_autofix = os.getenv('VAUTOFIX') != ''
const github_job = os.getenv('GITHUB_JOB')
const v_ci_ubuntu_musl = os.getenv('V_CI_UBUNTU_MUSL').len > 0
const v_ci_cstrict = os.getenv('V_CI_CSTRICT').len > 0
struct TaskDescription {
vexe string
evars string
@ -54,7 +67,6 @@ mut:
}
fn test_all() {
vexe := os.getenv('VEXE')
vroot := os.dir(vexe)
os.chdir(vroot) or {}
checker_dir := 'vlib/v/checker/tests'
@ -74,14 +86,13 @@ fn test_all() {
module_tests := get_tests_in_dir(module_dir, true)
run_tests := get_tests_in_dir(run_dir, false)
skip_unused_dir_tests := get_tests_in_dir(skip_unused_dir, false)
// -prod is used for the parser and checker tests, so that warns are errors
mut tasks := Tasks{
vexe: vexe
label: 'all tests'
}
tasks.add('', parser_dir, '-prod', '.out', parser_tests, false)
tasks.add('', checker_dir, '-prod', '.out', checker_tests, false)
tasks.add('', scanner_dir, '-prod', '.out', scanner_tests, false)
tasks.add('', parser_dir, '', '.out', parser_tests, false)
tasks.add('', checker_dir, '', '.out', checker_tests, false)
tasks.add('', scanner_dir, '', '.out', scanner_tests, false)
tasks.add('', checker_dir, '-enable-globals run', '.run.out', ['globals_error.vv'],
false)
tasks.add('', global_run_dir, '-enable-globals run', '.run.out', global_run_tests,
@ -162,15 +173,11 @@ fn (mut tasks Tasks) add(custom_vexe string, dir string, voptions string, result
}
fn (mut tasks Tasks) add_evars(evars string, custom_vexe string, dir string, voptions string, result_extension string, tests []string, is_module bool) {
mut vexe := tasks.vexe
if custom_vexe != '' {
vexe = custom_vexe
}
paths := vtest.filter_vtest_only(tests, basepath: dir)
for path in paths {
tasks.all << TaskDescription{
evars: evars
vexe: vexe
vexe: if custom_vexe != '' { custom_vexe } else { tasks.vexe }
dir: dir
voptions: voptions
result_extension: result_extension
@ -186,16 +193,22 @@ fn bstep_message(mut bench benchmark.Benchmark, label string, msg string, sdurat
// process an array of tasks in parallel, using no more than vjobs worker threads
fn (mut tasks Tasks) run() {
tasks.show_cmd = os.getenv('VTEST_SHOW_CMD') != ''
if tasks.all.len == 0 {
return
}
tasks.show_cmd = show_cmd
vjobs := if tasks.parallel_jobs > 0 { tasks.parallel_jobs } else { runtime.nr_jobs() }
mut bench := benchmark.new_benchmark()
bench.set_total_expected_steps(tasks.all.len)
mut work := sync.new_channel<TaskDescription>(u32(tasks.all.len))
mut results := sync.new_channel<TaskDescription>(u32(tasks.all.len))
mut work := chan TaskDescription{cap: tasks.all.len}
mut results := chan TaskDescription{cap: tasks.all.len}
mut m_skip_files := skip_files.clone()
if os.getenv('V_CI_UBUNTU_MUSL').len > 0 {
if v_ci_ubuntu_musl {
m_skip_files << skip_on_ubuntu_musl
}
if v_ci_cstrict {
m_skip_files << skip_on_cstrict
}
$if noskip ? {
m_skip_files = []
}
@ -220,11 +233,11 @@ fn (mut tasks Tasks) run() {
if tasks.all[i].path in m_skip_files {
tasks.all[i].is_skipped = true
}
unsafe { work.push(&tasks.all[i]) }
work <- tasks.all[i]
}
work.close()
for _ in 0 .. vjobs {
go work_processor(mut work, mut results)
go work_processor(work, results)
}
if github_job == '' {
println('')
@ -233,7 +246,7 @@ fn (mut tasks Tasks) run() {
mut total_errors := 0
for _ in 0 .. tasks.all.len {
mut task := TaskDescription{}
results.pop(&task)
task = <-results
bench.step()
if task.is_skipped {
bench.skip()
@ -261,8 +274,7 @@ fn (mut tasks Tasks) run() {
bench.ok()
assert true
if tasks.show_cmd {
eprintln(bstep_message(mut bench, benchmark.b_ok, '$task.cli_cmd $task.path',
task.took))
eprintln(bstep_message(mut bench, benchmark.b_ok, '$task.cli_cmd', task.took))
} else {
if github_job == '' {
// local mode:
@ -285,16 +297,13 @@ fn (mut tasks Tasks) run() {
// a single worker thread spends its time getting work from the `work` channel,
// processing the task, and then putting the task in the `results` channel
fn work_processor(mut work sync.Channel, mut results sync.Channel) {
fn work_processor(work chan TaskDescription, results chan TaskDescription) {
for {
mut task := TaskDescription{}
if !work.pop(&task) {
break
}
mut task := <-work or { break }
sw := time.new_stopwatch()
task.execute()
task.took = sw.elapsed()
results.push(&task)
results <- task
}
}

View File

@ -1694,6 +1694,7 @@ fn (mut f Fmt) write_generic_call_if_require(node ast.CallExpr) {
name = 'JS.' + name
}
f.write(name)
f.mark_import_as_used(name)
if i != node.concrete_types.len - 1 {
f.write(', ')
}

View File

@ -1,12 +1,16 @@
import math.complex { Complex }
import gg { MouseButton }
import time { Duration }
import modtest { Test }
fn keep_imported_enum_map_key() {
bm := map[MouseButton]string{}
}
fn generic<T>() {}
fn main() {
_ := Duration(10) // keep cast type
assert *(&f64(&byte(&num) + __offsetof(Complex, re))) == 1.0
generic<Test>()
}

View File

@ -4756,6 +4756,9 @@ fn (mut g Gen) write_init_function() {
g.writeln('\tbuiltin_init();')
g.writeln('\tvinit_string_literals();')
//
if g.nr_closures > 0 {
g.writeln('\t_closure_mtx_init();')
}
for mod_name in g.table.modules {
g.writeln('\t{ // Initializations for module $mod_name :')
g.write(g.inits[mod_name].str())

View File

@ -76,86 +76,135 @@ fn c_closure_helpers(pref &pref.Preferences) string {
#define __RETURN_ADDRESS() ((char*)__builtin_extract_return_addr(__builtin_return_address(0)))
#endif
#define ASSUMED_PAGE_SIZE 0x4000 // 16K
#define _CLOSURE_SIZE (((3*sizeof(void*) > sizeof(__closure_thunk) ? 3*sizeof(void*) : sizeof(__closure_thunk)) + sizeof(void*) - 1) & ~(sizeof(void*) - 1))
// equal to `max(3*sizeof(void*), sizeof(__closure_thunk))`, rounded up to the next multiple of `sizeof(void*)`
// refer to https://godbolt.org/z/r7P3EYv6c for a complete assembly
#ifdef __V_amd64
static const char __closure_thunk[] = {
0x8f, 0x05, 0xda, 0xff, 0xff, 0xff, // pop QWORD PTR [rip - 0x26] # <_orig_rbp>
0xff, 0x15, 0xe4, 0xff, 0xff, 0xff, // call QWORD PTR [rip - 0x1C] # <fn>
0xff, 0x25, 0xce, 0xff, 0xff, 0xff, // jmp QWORD PTR [rip - 0x32] # <orig_rbp>
0x8f, 0x05, 0x0a, 0xc0, 0xff, 0xff, // pop QWORD PTR [rip - return_addr]
0xff, 0x15, 0xfc, 0xbf, 0xff, 0xff, // call QWORD PTR [rip - fn]
0xff, 0x25, 0xfe, 0xbf, 0xff, 0xff // jmp QWORD PTR [rip - return_addr]
};
#define __CLOSURE_DATA_OFFSET 20
#define __CLOSURE_DATA_OFFSET 0x400C
#elif defined(__V_x86)
static char __closure_thunk[] = {
0xe8, 0x00, 0x00, 0x00, 0x00, // call 4
0x59, // pop ecx
0x8f, 0x41, 0xeb, // pop DWORD PTR [ecx - 21] # <_orig_rbp>
0xff, 0x51, 0xf3, // call DWORD PTR [ecx - 13] # <fn>
0xe8, 0x00, 0x00, 0x00, 0x00, // call 4
0x59, // pop ecx
0xff, 0x61, 0xdf, // jmp DWORD PTR [ecx - 33] # <_orig_rbp>
0xe8, 0x00, 0x00, 0x00, 0x00, // call 4
0x59, // pop ecx
0x8f, 0x81, 0x03, 0xc0, 0xff, 0xff, // pop DWORD PTR [ecx - 0x3ffd] # <return_addr>
0xff, 0x91, 0xff, 0xbf, 0xff, 0xff, // call DWORD PTR [ecx - 0x4001] # <fn>
0xe8, 0x00, 0x00, 0x00, 0x00, // call 4
0x59, // pop ecx
0xff, 0xa1, 0xf1, 0xbf, 0xff, 0xff // jmp DWORD PTR [ecx - 0x400f] # <return_addr>
};
#define __CLOSURE_DATA_OFFSET 16
#define __CLOSURE_DATA_OFFSET 0x4012
#elif defined(__V_arm64)
static char __closure_thunk[] = {
0x10, 0x00, 0x00, 0x10, // adr x16, start
0x1e, 0x02, 0x1e, 0xf8, // str x30, _orig_x30
0x50, 0xff, 0xff, 0x58, // ldr x16, fn
0x90, 0x00, 0xfe, 0x10, // adr x16, return_addr
0x1e, 0x02, 0x00, 0xf9, // str x30, [x16]
0x10, 0x00, 0xfe, 0x58, // ldr x16, fn
0x00, 0x02, 0x3f, 0xd6, // blr x16
0x9e, 0xfe, 0xff, 0x58, // ldr x30, _orig_x30
0x1e, 0x00, 0xfe, 0x58, // ldr x30, return_addr
0xc0, 0x03, 0x5f, 0xd6 // ret
};
#define __CLOSURE_DATA_OFFSET 24
#define __CLOSURE_DATA_OFFSET 0x4010
#elif defined(__V_arm32)
// arm32 needs a small page size because its pc-relative addressing range is just ±4095 bytes
#undef ASSUMED_PAGE_SIZE
#define ASSUMED_PAGE_SIZE 4080
#undef _CLOSURE_SIZE
#define _CLOSURE_SIZE 28
static char __closure_thunk[] = {
0x18, 0xe0, 0x0f, 0xe5, // str lr, orig_lr
0x14, 0xc0, 0x1f, 0xe5, // ldr ip, fn
0xf0, 0xef, 0x0f, 0xe5, // str lr, return_addr
0xf8, 0xcf, 0x1f, 0xe5, // ldr ip, fn
0x3c, 0xff, 0x2f, 0xe1, // blx ip
0x24, 0xe0, 0x1f, 0xe5, // ldr lr, orig_lr
0xfc, 0xef, 0x1f, 0xe5, // ldr lr, return_addr
0x1e, 0xff, 0x2f, 0xe1 // bx lr
};
#define __CLOSURE_DATA_OFFSET 16
#define __CLOSURE_DATA_OFFSET 0xFFC
#endif
static int _V_PAGE_SIZE = 4096; // pre-initialized to the most common value, in case _vinit is not called (in a DLL, for example)
static inline void __closure_set_data(void* closure, void* data) {
void** p = closure;
p[-1] = data;
static inline void __closure_set_data(char* closure, void* data) {
void** p = (void**)(closure - ASSUMED_PAGE_SIZE);
p[0] = data;
}
static inline void __closure_set_function(void* closure, void* f) {
void** p = closure;
p[-2] = f;
static inline void __closure_set_function(char* closure, void* f) {
void** p = (void**)(closure - ASSUMED_PAGE_SIZE);
p[1] = f;
}
#ifdef _WIN32
#include <synchapi.h>
static SRWLOCK _closure_mtx;
#define _closure_mtx_init() InitializeSRWLock(&_closure_mtx)
#define _closure_mtx_lock() AcquireSRWLockExclusive(&_closure_mtx)
#define _closure_mtx_unlock() ReleaseSRWLockExclusive(&_closure_mtx)
#else
static pthread_mutex_t _closure_mtx;
#define _closure_mtx_init() pthread_mutex_init(&_closure_mtx, 0)
#define _closure_mtx_lock() pthread_mutex_lock(&_closure_mtx)
#define _closure_mtx_unlock() pthread_mutex_unlock(&_closure_mtx)
#endif
static char* _closure_ptr = 0;
static int _closure_cap = 0;
static void* __closure_create(void* fn, void* data) {
_closure_mtx_lock();
if (_closure_cap == 0) {
#ifdef _WIN32
SYSTEM_INFO si;
GetNativeSystemInfo(&si);
uint32_t page_size = si.dwPageSize;
page_size = page_size * (((ASSUMED_PAGE_SIZE - 1) / page_size) + 1);
char* p = VirtualAlloc(NULL, page_size * 2, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (p == NULL) return 0;
#else
uint32_t page_size = sysconf(_SC_PAGESIZE);
page_size = page_size * (((ASSUMED_PAGE_SIZE - 1) / page_size) + 1);
int prot = PROT_READ | PROT_WRITE;
int flags = MAP_ANONYMOUS | MAP_PRIVATE;
char* p = mmap(0, page_size * 2, prot, flags, -1, 0);
if (p == MAP_FAILED) return 0;
#endif
char* x = p + page_size;
int remaining = page_size / _CLOSURE_SIZE;
_closure_ptr = x;
_closure_cap = remaining;
while (remaining > 0) {
memcpy(x, __closure_thunk, sizeof(__closure_thunk));
remaining--;
x += _CLOSURE_SIZE;
}
#ifdef _WIN32
DWORD _tmp;
VirtualProtect(_closure_ptr, page_size, PAGE_EXECUTE_READ, &_tmp);
#else
mprotect(_closure_ptr, page_size, PROT_READ | PROT_EXEC);
#endif
}
_closure_cap--;
void* closure = _closure_ptr;
_closure_ptr += _CLOSURE_SIZE;
__closure_set_data(closure, data);
__closure_set_function(closure, fn);
_closure_mtx_unlock();
return closure;
}
static void __closure_destroy(void *closure) {
#ifdef _WIN32
SYSTEM_INFO si;
GetNativeSystemInfo(&si);
uint32_t page_size = si.dwPageSize;
char* p = VirtualAlloc(NULL, page_size * 2, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (p == NULL) return 0;
page_size = page_size * (((ASSUMED_PAGE_SIZE - 1) / page_size) + 1);
VirtualFree(closure, page_size * 2, MEM_RELEASE);
#else
uint32_t page_size = sysconf(_SC_PAGESIZE);
int prot = PROT_READ | PROT_WRITE;
int flags = MAP_ANONYMOUS | MAP_PRIVATE;
char* p = mmap(0, page_size * 2, prot, flags, -1, 0);
if (p == MAP_FAILED) return 0;
long page_size = sysconf(_SC_PAGESIZE);
page_size = page_size * (((ASSUMED_PAGE_SIZE - 1) / page_size) + 1);
munmap((char*)closure - page_size, page_size * 2);
#endif
void* closure = p + page_size;
memcpy(closure, __closure_thunk, sizeof(__closure_thunk));
#ifdef _WIN32
DWORD _tmp;
VirtualProtect(closure, page_size, PAGE_EXECUTE_READ, &_tmp);
#else
mprotect(closure, page_size, PROT_READ | PROT_EXEC);
#endif
__closure_set_data(closure, data);
__closure_set_function(closure, fn);
return closure;
}
')
return builder.str()

View File

@ -116,6 +116,10 @@ fn (mut g Gen) infix_expr_eq_op(node ast.InfixExpr) {
g.write(')')
} else if left.typ.idx() == right.typ.idx()
&& left.sym.kind in [.array, .array_fixed, .alias, .map, .struct_, .sum_type, .interface_] {
if g.pref.translated && !g.is_builtin_mod {
g.gen_plain_infix_expr(node)
return
}
match left.sym.kind {
.alias {
ptr_typ := g.equality_fn(left.typ)
@ -201,25 +205,25 @@ fn (mut g Gen) infix_expr_eq_op(node ast.InfixExpr) {
g.write(')')
}
.struct_ {
if g.pref.translated {
g.gen_plain_infix_expr(node)
} else {
ptr_typ := g.equality_fn(left.unaliased)
if node.op == .ne {
g.write('!')
}
g.write('${ptr_typ}_struct_eq(')
if left.typ.is_ptr() {
g.write('*')
}
g.expr(node.left)
g.write(', ')
if right.typ.is_ptr() {
g.write('*')
}
g.expr(node.right)
g.write(')')
// if g.pref.translated {
// g.gen_plain_infix_expr(node)
//} else {
ptr_typ := g.equality_fn(left.unaliased)
if node.op == .ne {
g.write('!')
}
g.write('${ptr_typ}_struct_eq(')
if left.typ.is_ptr() {
g.write('*')
}
g.expr(node.left)
g.write(', ')
if right.typ.is_ptr() {
g.write('*')
}
g.expr(node.right)
g.write(')')
//}
}
.sum_type {
ptr_typ := g.equality_fn(left.unaliased)
@ -378,7 +382,8 @@ fn (mut g Gen) infix_expr_in_op(node ast.InfixExpr) {
if right.unaliased_sym.kind == .array {
if left.sym.kind in [.sum_type, .interface_] {
if node.right is ast.ArrayInit {
if node.right.exprs.len > 0 {
if node.right.exprs.len > 0
&& g.table.sym(node.right.expr_types[0]).kind !in [.sum_type, .interface_] {
mut infix_exprs := []ast.InfixExpr{}
for i in 0 .. node.right.exprs.len {
infix_exprs << ast.InfixExpr{
@ -397,11 +402,25 @@ fn (mut g Gen) infix_expr_in_op(node ast.InfixExpr) {
}
}
if node.right is ast.ArrayInit {
elem_type := node.right.elem_type
elem_sym := g.table.sym(elem_type)
if node.right.exprs.len > 0 {
// `a in [1,2,3]` optimization => `a == 1 || a == 2 || a == 3`
// avoids an allocation
g.write('(')
g.infix_expr_in_optimization(node.left, node.right)
if elem_sym.kind == .sum_type && left.sym.kind != .sum_type {
if node.left_type in elem_sym.sumtype_info().variants {
new_node_left := ast.CastExpr{
arg: ast.EmptyExpr{}
typ: elem_type
expr: node.left
expr_type: node.left_type
}
g.infix_expr_in_optimization(new_node_left, node.right)
}
} else {
g.infix_expr_in_optimization(node.left, node.right)
}
g.write(')')
return
}

View File

@ -125,7 +125,8 @@ fn (mut p Parser) for_stmt() ast.Stmt {
is_stack_obj: true
})
} else if p.scope.known_var(val_var_name) {
return p.error('redefinition of value iteration variable `$val_var_name`')
return p.error_with_pos('redefinition of value iteration variable `$val_var_name`, use `for ($val_var_name in array) {` if you want to check for a condition instead',
val_var_pos)
}
p.check(.key_in)
if p.tok.kind == .name && p.tok.lit in [key_var_name, val_var_name] {

View File

@ -1,4 +1,4 @@
vlib/v/parser/tests/closure_not_used.vv:3:11: error: unused variable: `a`
vlib/v/parser/tests/closure_not_used.vv:3:11: warning: unused variable: `a`
1 | fn my_fn() {
2 | a := 1
3 | f := fn [a] () {

View File

@ -0,0 +1,7 @@
vlib/v/parser/tests/for_val_in_array_err.vv:5:6: error: redefinition of value iteration variable `val`, use `for (val in array) {` if you want to check for a condition instead
3 | fn main() {
4 | val := `+`
5 | for val in [TokenValue(`+`), TokenValue(`-`)] {
| ~~~
6 | println("ok")
7 | break

View File

@ -0,0 +1,9 @@
type TokenValue = rune | u64
fn main() {
val := `+`
for val in [TokenValue(`+`), TokenValue(`-`)] {
println("ok")
break
}
}

View File

@ -0,0 +1,3 @@
vlib/v/parser/tests/hash_empty_flag_value.vv:1:1: error: no argument(s) provided for #flag
1 | #flag
| ~~~~~

View File

@ -0,0 +1 @@
#flag

View File

@ -237,7 +237,7 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin
}
res.run_only = os.getenv('VTEST_ONLY_FN').split_any(',')
mut command := ''
mut command_pos := 0
mut command_pos := -1
// for i, arg in args {
for i := 0; i < args.len; i++ {
arg := args[i]
@ -289,6 +289,10 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin
res.is_help = true
}
'-v' {
if command_pos != -1 {
// a -v flag after the command, is intended for the command, not for V itself
continue
}
// `-v` flag is for setting verbosity, but without any args it prints the version, like Clang
if args.len > 1 {
res.is_verbose = true

View File

@ -1,4 +1,4 @@
vlib/v/scanner/tests/position_0_err.vv:1:1: error: unused variable: `i`
vlib/v/scanner/tests/position_0_err.vv:1:1: warning: unused variable: `i`
1 | i := 'hello'
| ^
2 | x := 3

View File

@ -0,0 +1,10 @@
type TokenValue = rune | u64
fn test_for_cond() {
val := `+`
for (val in [TokenValue(`+`), TokenValue(`-`)]) {
println('ok')
break
}
assert true
}

View File

@ -309,3 +309,13 @@ fn test_in_alias_array() {
assert Str('') in [Str(''), Str('a')]
assert Struct{} == Struct{}
}
type TokenValue = rune | u64
fn test_in_array_literal_of_sumtype() {
val1 := TokenValue(`+`)
assert val1 in [TokenValue(`+`), TokenValue(`-`)]
val2 := `+`
assert val2 in [TokenValue(`+`), TokenValue(`-`)]
}

View File

@ -0,0 +1,14 @@
> main start
> work started
> work x: 0
> work x: 1
> work x: 2
> work x: 3
> work x: 4
> work x: 5
> work x: 6
> work x: 7
> work x: 8
> work x: 9
> work ended
> main task was finished

View File

@ -0,0 +1,29 @@
import time
fn work(input chan u32, started chan bool) {
println('> work started')
started <- true
for {
x := <-input or { break }
println('> work x: $x')
time.sleep(50 * time.millisecond)
}
println('> work ended')
}
fn main() {
println('> main start')
ch := chan u32{cap: 100}
work_started := chan bool{}
for x in 0 .. 10 {
ch <- x
}
task := go work(ch, work_started)
_ := <-work_started
ch.close()
// println('> main ch.close called') // the position of this is not deterministic
task.wait()
println('> main task was finished')
}