From 506b64303e97ecc36fd4634cd2bb05a20ecd98b5 Mon Sep 17 00:00:00 2001
From: Felipe Pena <felipensp@gmail.com>
Date: Tue, 4 Mar 2025 09:06:47 -0300
Subject: [PATCH 1/4] implement

---
 vlib/v/checker/struct.v | 30 +++++++++++++++++++++++++++++-
 1 file changed, 29 insertions(+), 1 deletion(-)

diff --git a/vlib/v/checker/struct.v b/vlib/v/checker/struct.v
index 4ca2edb277beff..bc433d2d1fd8d9 100644
--- a/vlib/v/checker/struct.v
+++ b/vlib/v/checker/struct.v
@@ -534,7 +534,7 @@ fn (mut c Checker) struct_init(mut node ast.StructInit, is_field_zero_struct_ini
 		&& c.table.cur_concrete_types.len == 0 {
 		pos := type_sym.name.last_index_u8(`.`)
 		first_letter := type_sym.name[pos + 1]
-		if !first_letter.is_capital()
+		if !first_letter.is_capital() && type_sym.kind != .none
 			&& (type_sym.kind != .struct || !(type_sym.info is ast.Struct && type_sym.info.is_anon))
 			&& type_sym.kind != .placeholder {
 			c.error('cannot initialize builtin type `${type_sym.name}`', node.pos)
@@ -850,6 +850,34 @@ or use an explicit `unsafe{ a[..] }`, if you do not want a copy of the slice.',
 					inited_fields)
 			}
 		}
+		.none {
+			// var := struct { name: "" }
+			mut init_fields := []ast.StructField{}
+			for init_field in node.init_fields {
+				mut expr := unsafe { init_field }
+				init_fields << ast.StructField{
+					name: init_field.name
+					typ:  c.expr(mut expr.expr)
+				}
+			}
+			c.table.anon_struct_counter++
+			name := '_VAnonStruct${c.table.anon_struct_counter}'
+			sym_struct := ast.TypeSymbol{
+				kind:     .struct
+				language: .v
+				name:     name
+				cname:    util.no_dots(name)
+				mod:      c.mod
+				info:     ast.Struct{
+					is_anon: true
+					fields:  init_fields
+				}
+				is_pub:   true
+			}
+			ret := c.table.register_sym(sym_struct)
+			c.table.register_anon_struct(name, ret)
+			node.typ = c.table.find_type_idx(name)
+		}
 		else {}
 	}
 	if node.has_update_expr {

From 9c7d6639be9f5f5696f4333c7bd59bee3b54a29e Mon Sep 17 00:00:00 2001
From: Felipe Pena <felipensp@gmail.com>
Date: Tue, 4 Mar 2025 17:33:35 -0300
Subject: [PATCH 2/4] implement

---
 vlib/v/checker/assign.v  |  6 ++++++
 vlib/v/checker/checker.v |  1 +
 vlib/v/checker/fn.v      |  5 +++++
 vlib/v/checker/struct.v  | 19 +++++++++++++++++--
 vlib/v/gen/c/fn.v        |  2 ++
 5 files changed, 31 insertions(+), 2 deletions(-)

diff --git a/vlib/v/checker/assign.v b/vlib/v/checker/assign.v
index 89c1c165f9d535..6bbbc20cb0d876 100644
--- a/vlib/v/checker/assign.v
+++ b/vlib/v/checker/assign.v
@@ -199,6 +199,12 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) {
 			}
 			c.inside_decl_rhs = is_decl
 			mut expr := node.right[i]
+			if left is ast.Ident && left.is_mut() && expr is ast.StructInit && expr.is_anon {
+				c.anon_struct_should_be_mut = true
+				defer {
+					c.anon_struct_should_be_mut = false
+				}
+			}
 			right_type := c.expr(mut expr)
 			c.inside_decl_rhs = false
 			c.inside_ref_lit = old_inside_ref_lit
diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v
index dfdea9de31a550..4a8f7fa212de8f 100644
--- a/vlib/v/checker/checker.v
+++ b/vlib/v/checker/checker.v
@@ -89,6 +89,7 @@ pub mut:
 	inside_fn_arg               bool        // `a`, `b` in `a.f(b)`
 	inside_ct_attr              bool        // true inside `[if expr]`
 	inside_x_is_type            bool        // true inside the Type expression of `if x is Type {`
+	anon_struct_should_be_mut   bool        // true when `mut var := struct { ... }` is used
 	inside_generic_struct_init  bool
 	inside_integer_literal_cast bool // true inside `int(123)`
 	cur_struct_generic_types    []ast.Type
diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v
index 1205fc20803fe3..30f1f933393157 100644
--- a/vlib/v/checker/fn.v
+++ b/vlib/v/checker/fn.v
@@ -1654,6 +1654,11 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast.
 					}
 				}
 				continue
+			} else if param_typ_sym.info is ast.Struct && arg_typ_sym.info is ast.Struct
+				&& param_typ_sym.info.is_anon {
+				if c.is_anon_struct_compatible(param_typ_sym.info, arg_typ_sym.info) {
+					continue
+				}
 			}
 			if c.pref.translated || c.file.is_translated {
 				// in case of variadic make sure to use array elem type for checks
diff --git a/vlib/v/checker/struct.v b/vlib/v/checker/struct.v
index bc433d2d1fd8d9..645103c566ef1c 100644
--- a/vlib/v/checker/struct.v
+++ b/vlib/v/checker/struct.v
@@ -856,8 +856,9 @@ or use an explicit `unsafe{ a[..] }`, if you do not want a copy of the slice.',
 			for init_field in node.init_fields {
 				mut expr := unsafe { init_field }
 				init_fields << ast.StructField{
-					name: init_field.name
-					typ:  c.expr(mut expr.expr)
+					name:   init_field.name
+					typ:    c.expr(mut expr.expr)
+					is_mut: c.anon_struct_should_be_mut
 				}
 			}
 			c.table.anon_struct_counter++
@@ -1168,3 +1169,17 @@ fn (mut c Checker) check_ref_fields_initialized_note(struct_sym &ast.TypeSymbol,
 		}
 	}
 }
+
+fn (mut c Checker) is_anon_struct_compatible(s1 ast.Struct, s2 ast.Struct) bool {
+	if !(s1.is_anon && s2.is_anon && s1.fields.len == s2.fields.len) {
+		return false
+	}
+	mut is_compatible := true
+	for k, field in s1.fields {
+		if !c.check_basic(field.typ, s2.fields[k].typ) {
+			is_compatible = false
+			break
+		}
+	}
+	return is_compatible
+}
diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v
index 00c87e7acf1a43..c2a2a8cc94ad79 100644
--- a/vlib/v/gen/c/fn.v
+++ b/vlib/v/gen/c/fn.v
@@ -2760,6 +2760,8 @@ fn (mut g Gen) ref_or_deref_arg(arg ast.CallArg, expected_type ast.Type, lang as
 		g.expr_with_cast(arg.expr, arg_typ, expected_type)
 		g.write('.data')
 		return
+	} else if arg_sym.info is ast.Struct && arg_sym.info.is_anon {
+		g.write('*(${g.cc_type(expected_type, false)}*)&')
 	}
 	// check if the argument must be dereferenced or not
 	g.arg_no_auto_deref = is_smartcast && !arg_is_ptr && !exp_is_ptr && arg.should_be_ptr

From 217ee0b253b8e12da6ff49ebe0041ea090976cf9 Mon Sep 17 00:00:00 2001
From: Felipe Pena <felipensp@gmail.com>
Date: Tue, 4 Mar 2025 18:41:18 -0300
Subject: [PATCH 3/4] fix

---
 vlib/v/gen/c/fn.v | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v
index c2a2a8cc94ad79..029a2a192cd520 100644
--- a/vlib/v/gen/c/fn.v
+++ b/vlib/v/gen/c/fn.v
@@ -2760,7 +2760,8 @@ fn (mut g Gen) ref_or_deref_arg(arg ast.CallArg, expected_type ast.Type, lang as
 		g.expr_with_cast(arg.expr, arg_typ, expected_type)
 		g.write('.data')
 		return
-	} else if arg_sym.info is ast.Struct && arg_sym.info.is_anon {
+	} else if arg.expr is ast.Ident && arg_sym.info is ast.Struct && arg_sym.info.is_anon {
+		// make anon struct struct compatible with another anon struct declaration
 		g.write('*(${g.cc_type(expected_type, false)}*)&')
 	}
 	// check if the argument must be dereferenced or not

From 53833238e0514817fd7ee3b13083a4d5340c5060 Mon Sep 17 00:00:00 2001
From: Felipe Pena <felipensp@gmail.com>
Date: Tue, 4 Mar 2025 19:13:58 -0300
Subject: [PATCH 4/4] add test

---
 .../structs/anon_struct_assign_expr_test.v    | 25 +++++++++++++++++++
 1 file changed, 25 insertions(+)
 create mode 100644 vlib/v/tests/structs/anon_struct_assign_expr_test.v

diff --git a/vlib/v/tests/structs/anon_struct_assign_expr_test.v b/vlib/v/tests/structs/anon_struct_assign_expr_test.v
new file mode 100644
index 00000000000000..b861f178998b16
--- /dev/null
+++ b/vlib/v/tests/structs/anon_struct_assign_expr_test.v
@@ -0,0 +1,25 @@
+fn t() int {
+	return 123
+}
+
+fn r(a struct { name string age int }) {
+	assert '${a}' == "struct {
+    name: 'Foo'
+    age: 123
+}"
+}
+
+fn test_main() {
+	mut a := struct {
+		name: 'Foo'
+		age:  t()
+	}
+	dump(a)
+	r(a)
+	r(struct { name: 'Foo', age: t() })
+	a.age = 2
+	assert '${a}' == "struct {
+    name: 'Foo'
+    age: 2
+}"
+}