From 365ea7b16d02b653f406dd2521e6a1150c9aa72e Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 11 Mar 2026 15:54:37 +0300 Subject: [PATCH] checker: immutable instance of a struct can be modified via interface (fixes #23509) --- vlib/v/checker/checker.v | 15 ++++++++++++++- ...face_cast_from_immutable_to_mutable_err.out | 13 +++++++++++++ ...rface_cast_from_immutable_to_mutable_err.vv | 18 ++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 vlib/v/checker/tests/interface_cast_from_immutable_to_mutable_err.out create mode 100644 vlib/v/checker/tests/interface_cast_from_immutable_to_mutable_err.vv diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 050c1c585..4e3ea17b0 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -1183,6 +1183,12 @@ fn (mut c Checker) fail_if_immutable_to_mutable(left_type ast.Type, right_type a return true } match right { + ast.CastExpr { + if c.cast_expr_aliases_value(right) { + mut expr := right.expr + c.fail_if_immutable(mut expr) + } + } ast.Ident { if right.obj is ast.Var { if left_type.is_ptr() && !right.is_mut() && right_type.is_ptr() { @@ -1243,6 +1249,10 @@ fn (mut c Checker) fail_if_immutable_to_mutable(left_type ast.Type, right_type a return true } +fn (mut c Checker) cast_expr_aliases_value(node ast.CastExpr) bool { + return c.table.final_sym(c.unwrap_generic(node.typ)).kind == .interface +} + // 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(mut expr ast.Expr) (string, token.Pos) { @@ -1251,7 +1261,10 @@ fn (mut c Checker) fail_if_immutable(mut expr ast.Expr) (string, token.Pos) { mut explicit_lock_needed := false match mut expr { ast.CastExpr { - // TODO + if c.cast_expr_aliases_value(expr) { + mut inner_expr := expr.expr + return c.fail_if_immutable(mut inner_expr) + } return '', expr.pos } ast.ComptimeSelector { diff --git a/vlib/v/checker/tests/interface_cast_from_immutable_to_mutable_err.out b/vlib/v/checker/tests/interface_cast_from_immutable_to_mutable_err.out new file mode 100644 index 000000000..88e06e42a --- /dev/null +++ b/vlib/v/checker/tests/interface_cast_from_immutable_to_mutable_err.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/interface_cast_from_immutable_to_mutable_err.vv:15:26: error: `bar` is immutable, declare it with `mut` to make it mutable + 13 | foo: 'hello' + 14 | } + 15 | mut foobar := Interface(bar) + | ~~~ + 16 | foobar.foo = 'bye' + 17 | Interface(bar).foo = 'changed' +vlib/v/checker/tests/interface_cast_from_immutable_to_mutable_err.vv:17:12: error: `bar` is immutable, declare it with `mut` to make it mutable + 15 | mut foobar := Interface(bar) + 16 | foobar.foo = 'bye' + 17 | Interface(bar).foo = 'changed' + | ~~~ + 18 | } diff --git a/vlib/v/checker/tests/interface_cast_from_immutable_to_mutable_err.vv b/vlib/v/checker/tests/interface_cast_from_immutable_to_mutable_err.vv new file mode 100644 index 000000000..21329989d --- /dev/null +++ b/vlib/v/checker/tests/interface_cast_from_immutable_to_mutable_err.vv @@ -0,0 +1,18 @@ +interface Interface { +mut: + foo string +} + +struct Struct { +mut: + foo string +} + +fn main() { + bar := Struct{ + foo: 'hello' + } + mut foobar := Interface(bar) + foobar.foo = 'bye' + Interface(bar).foo = 'changed' +} -- 2.39.5