From ace2e785c55b9f65d813b26e8dd73a97024aebcb Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 25 Mar 2026 22:19:19 +0300 Subject: [PATCH] checker: fix using sum type or interface for os.file.write_struct (fixes #19117) --- vlib/v/checker/fn.v | 47 +++++++++++++++++++ .../os_file_read_struct_non_struct_err.out | 13 +++++ .../os_file_read_struct_non_struct_err.vv | 23 +++++++++ .../os_file_write_struct_non_struct_err.out | 13 +++++ .../os_file_write_struct_non_struct_err.vv | 24 ++++++++++ 5 files changed, 120 insertions(+) create mode 100644 vlib/v/checker/tests/os_file_read_struct_non_struct_err.out create mode 100644 vlib/v/checker/tests/os_file_read_struct_non_struct_err.vv create mode 100644 vlib/v/checker/tests/os_file_write_struct_non_struct_err.out create mode 100644 vlib/v/checker/tests/os_file_write_struct_non_struct_err.vv diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 0e2b1c843..17e56a11a 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -2224,6 +2224,52 @@ fn (mut c Checker) check_type_and_visibility(name string, type_idx int, expected return true } +fn (c &Checker) is_valid_os_file_struct_io_type(typ ast.Type) bool { + if typ.nr_muls() > 0 || typ.has_option_or_result() { + return false + } + mut current_typ := typ + mut sym := c.table.sym(current_typ) + for { + if sym.info !is ast.Alias { + break + } + alias_info := sym.info as ast.Alias + current_typ = alias_info.parent_type + if current_typ.nr_muls() > 0 || current_typ.has_option_or_result() { + return false + } + sym = c.table.sym(current_typ) + } + return sym.kind == .struct +} + +fn (mut c Checker) check_os_file_struct_io_method_call(node &ast.CallExpr, method ast.Fn, concrete_types []ast.Type) { + if method.name !in ['read_struct', 'read_struct_at', 'write_struct', 'write_struct_at'] { + return + } + if method.params.len == 0 || concrete_types.len != 1 { + return + } + receiver_sym := c.table.final_sym(method.params[0].typ) + if receiver_sym.name != 'os.File' { + return + } + concrete_type := concrete_types[0] + if concrete_type.has_flag(.generic) || c.is_valid_os_file_struct_io_type(concrete_type) { + return + } + err_pos := if node.raw_concrete_types.len > 0 { + node.concrete_list_pos + } else if node.args.len > 0 { + node.args[0].pos + } else { + node.pos + } + c.error('`${receiver_sym.name}.${method.name}` expects a struct type, but got `${c.table.type_to_str(concrete_type)}`', + err_pos) +} + fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) ast.Type { // `(if true { 'foo.bar' } else { 'foo.bar.baz' }).all_after('foo.')` mut left_expr := node.left @@ -2926,6 +2972,7 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) c.need_recheck_generic_fns = true } } + c.check_os_file_struct_io_method_call(node, method, concrete_types) if node.concrete_types.len > 0 && method_generic_names_len == 0 { c.error('a non generic function called like a generic one', node.concrete_list_pos) diff --git a/vlib/v/checker/tests/os_file_read_struct_non_struct_err.out b/vlib/v/checker/tests/os_file_read_struct_non_struct_err.out new file mode 100644 index 000000000..71397d1d9 --- /dev/null +++ b/vlib/v/checker/tests/os_file_read_struct_non_struct_err.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/os_file_read_struct_non_struct_err.vv:21:23: error: `os.File.read_struct` expects a struct type, but got `MySumType` + 19 | mut sum_value := MySumType(MyStruct{val: 1}) + 20 | mut iface_value := MyInterface(MyStruct{val: 1}) + 21 | file.read_struct(mut sum_value) or { panic(err) } + | ~~~~~~~~~ + 22 | file.read_struct_at[MyInterface](mut iface_value, 0) or { panic(err) } + 23 | } +vlib/v/checker/tests/os_file_read_struct_non_struct_err.vv:22:39: error: `os.File.read_struct_at` expects a struct type, but got `MyInterface` + 20 | mut iface_value := MyInterface(MyStruct{val: 1}) + 21 | file.read_struct(mut sum_value) or { panic(err) } + 22 | file.read_struct_at[MyInterface](mut iface_value, 0) or { panic(err) } + | ~~~~~~~~~~~ + 23 | } diff --git a/vlib/v/checker/tests/os_file_read_struct_non_struct_err.vv b/vlib/v/checker/tests/os_file_read_struct_non_struct_err.vv new file mode 100644 index 000000000..670c4aa8d --- /dev/null +++ b/vlib/v/checker/tests/os_file_read_struct_non_struct_err.vv @@ -0,0 +1,23 @@ +import os + +struct MyStruct { + val i32 +} + +struct SomeOtherStruct { + val i32 +} + +type MySumType = MyStruct | SomeOtherStruct + +interface MyInterface { + val i32 +} + +fn main() { + mut file := os.create('/tmp/os_file_read_struct_non_struct_err.bin') or { panic(err) } + mut sum_value := MySumType(MyStruct{val: 1}) + mut iface_value := MyInterface(MyStruct{val: 1}) + file.read_struct(mut sum_value) or { panic(err) } + file.read_struct_at[MyInterface](mut iface_value, 0) or { panic(err) } +} diff --git a/vlib/v/checker/tests/os_file_write_struct_non_struct_err.out b/vlib/v/checker/tests/os_file_write_struct_non_struct_err.out new file mode 100644 index 000000000..c45dde9f4 --- /dev/null +++ b/vlib/v/checker/tests/os_file_write_struct_non_struct_err.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/os_file_write_struct_non_struct_err.vv:22:20: error: `os.File.write_struct` expects a struct type, but got `MySumType` + 20 | sum_value := MySumType(p) + 21 | iface_value := MyInterface(p) + 22 | file.write_struct(sum_value) or { panic(err) } + | ~~~~~~~~~ + 23 | file.write_struct_at(iface_value, 0) or { panic(err) } + 24 | } +vlib/v/checker/tests/os_file_write_struct_non_struct_err.vv:23:23: error: `os.File.write_struct_at` expects a struct type, but got `MyInterface` + 21 | iface_value := MyInterface(p) + 22 | file.write_struct(sum_value) or { panic(err) } + 23 | file.write_struct_at(iface_value, 0) or { panic(err) } + | ~~~~~~~~~~~ + 24 | } diff --git a/vlib/v/checker/tests/os_file_write_struct_non_struct_err.vv b/vlib/v/checker/tests/os_file_write_struct_non_struct_err.vv new file mode 100644 index 000000000..1a374d6d4 --- /dev/null +++ b/vlib/v/checker/tests/os_file_write_struct_non_struct_err.vv @@ -0,0 +1,24 @@ +import os + +struct MyStruct { + val i32 +} + +struct SomeOtherStruct { + val i32 +} + +type MySumType = MyStruct | SomeOtherStruct + +interface MyInterface { + val i32 +} + +fn main() { + mut file := os.create('/tmp/os_file_write_struct_non_struct_err.bin') or { panic(err) } + p := MyStruct{val: 1} + sum_value := MySumType(p) + iface_value := MyInterface(p) + file.write_struct(sum_value) or { panic(err) } + file.write_struct_at(iface_value, 0) or { panic(err) } +} -- 2.39.5