@@ -1034,7 +1034,15 @@ fn (mut g Gen) gen_str_for_struct(info ast.Struct, lang ast.Language, styp strin
10341034 if base_typ.has_flag (.shared_f) {
10351035 base_typ = base_typ.clear_flag (.shared_f).deref ()
10361036 }
1037- base_fmt := g.type_to_fmt (base_typ)
1037+ // For C structs, voidptr/byteptr fields in V may have different actual types in C headers,
1038+ // so we use string format instead of pointer format to avoid invalid casts.
1039+ // charptr fields are excluded as they are properly handled by builtin__tos4.
1040+ mut base_fmt := g.type_to_fmt (base_typ)
1041+ is_c_voidptr_field := is_c_struct && field.typ in ast.cptr_types
1042+ && field.typ ! in ast.charptr_types
1043+ if is_c_voidptr_field {
1044+ base_fmt = .si_s
1045+ }
10381046 is_opt_field := field.typ.has_flag (.option)
10391047
10401048 // manage prefix and quote symbol for the filed
@@ -1096,6 +1104,11 @@ fn (mut g Gen) gen_str_for_struct(info ast.Struct, lang ast.Language, styp strin
10961104 it_field_name := 'it${op }${field_name }'
10971105 if ftyp_nr_muls > 1 || field.typ in ast.cptr_types {
10981106 if is_opt_field {
1107+ } else if is_c_voidptr_field {
1108+ // For C structs, the actual type may differ from V's declaration,
1109+ // so we just output a placeholder instead of trying to cast
1110+ func = '_S("<cptr>")'
1111+ caller_should_free = false
10991112 } else {
11001113 func = '(voidptr) ${it_field_name }'
11011114 caller_should_free = false
@@ -1136,8 +1149,8 @@ fn (mut g Gen) gen_str_for_struct(info ast.Struct, lang ast.Language, styp strin
11361149 fn_body.write_string ('${funcprefix }_S("<circular>")' )
11371150 }
11381151 } else {
1139- // manage C charptr
1140- if field.typ in ast.charptr_types {
1152+ // manage C charptr (but not our placeholder for C struct voidptr fields)
1153+ if field.typ in ast.charptr_types && ! is_c_voidptr_field {
11411154 fn_body.write_string ('builtin__tos4((byteptr)${func })' )
11421155 } else {
11431156 if field.typ.is_ptr () && sym.kind in [.struct , .interface ] {
0 commit comments