vxx2 / vlib / v / gen / c / auto_str_methods.v
1602 lines · 1539 sloc · 64.16 KB · 5e934d589db08febfd9c80d6351e3b0f5cb626c2
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license that can be found in the LICENSE file.
3module c
4
5import v.ast
6import v.util
7import strings
8
9// BUG: this const is not released from the memory! use a const for now
10// si_s_code = "0x" + int(StrIntpType.si_s).hex() // code for a simple string
11const si_s_code = '0xfe10'
12
13fn (mut g Gen) gen_str_default(sym ast.TypeSymbol, styp string, str_fn_name string) {
14 $if trace_autostr ? {
15 eprintln('> gen_str_default: ${sym.name} | ${styp} | str_fn_name')
16 }
17 mut convertor := ''
18 mut typename_ := ''
19 if sym.parent_idx in ast.integer_type_idxs {
20 convertor = 'int'
21 typename_ = 'int'
22 } else if sym.parent_idx == ast.f32_type_idx {
23 convertor = 'float'
24 typename_ = 'f32'
25 } else if sym.parent_idx == ast.f64_type_idx {
26 convertor = 'double'
27 typename_ = 'f64'
28 } else if sym.parent_idx == ast.bool_type_idx {
29 convertor = 'bool'
30 typename_ = 'bool'
31 } else {
32 verror('could not generate string method for type `${styp}`')
33 }
34 g.definitions.writeln('string ${str_fn_name}(${styp} it);')
35 g.auto_str_funcs.writeln('string ${str_fn_name}(${styp} it) {')
36 if convertor == 'bool' {
37 g.auto_str_funcs.writeln('\tstring tmp1 = builtin__string__plus(_S("${styp}("), ((${convertor})it ? _S("true") : _S("false")));')
38 } else {
39 g.auto_str_funcs.writeln('\tstring tmp1 = builtin__string__plus(_S("${styp}("), builtin__tos3(_string__plus(_S("${typename_}("), _string__plus(_S(")"), _S(")"))).str));')
40 }
41 g.auto_str_funcs.writeln('\tstring tmp2 = builtin__string__plus(tmp1, _S(")"));')
42 g.auto_str_funcs.writeln('\tbuiltin__string_free(&tmp1);')
43 g.auto_str_funcs.writeln('\treturn tmp2;')
44 g.auto_str_funcs.writeln('}')
45}
46
47struct StrType {
48 styp string
49mut:
50 typ ast.Type
51}
52
53fn (mut g Gen) get_str_fn(typ ast.Type) string {
54 $if trace_autostr ? {
55 eprintln('> get_str_fn: ${typ.debug()}')
56 }
57 mut unwrapped := if typ.has_flag(.option) {
58 g.unwrap_generic(typ).clear_flag(.variadic)
59 } else {
60 g.unwrap_generic(typ).set_nr_muls(0).clear_flag(.variadic)
61 }
62 if g.pref.nofloat {
63 if typ == ast.f32_type {
64 unwrapped = ast.u32_type
65 } else if typ == ast.f64_type {
66 unwrapped = ast.u64_type
67 }
68 }
69 // Promote literal types (int_literal -> int, float_literal -> f64) in array/map element types.
70 // These are never emitted as C typedefs (e.g., `Array_int_literal` doesn't exist).
71 unwrapped_sym := g.table.sym(unwrapped)
72 if unwrapped_sym.info is ast.Array {
73 promoted_elem := ast.mktyp(unwrapped_sym.info.elem_type)
74 if promoted_elem != unwrapped_sym.info.elem_type {
75 idx := g.table.find_or_register_array(promoted_elem)
76 if idx > 0 {
77 unwrapped = ast.new_type(idx).derive(unwrapped)
78 }
79 }
80 }
81 styp := g.styp(unwrapped)
82 mut sym := g.table.sym(unwrapped)
83 mut str_fn_name := styp_to_str_fn_name(styp)
84 if mut sym.info is ast.Alias && !sym.has_method('str') {
85 if sym.info.is_import {
86 sym = g.table.sym(sym.info.parent_type)
87 str_fn_name = styp_to_str_fn_name(sym.name)
88 }
89 }
90 if sym.is_builtin() && !str_fn_name.starts_with('builtin__') {
91 str_fn_name = 'builtin__${str_fn_name}'
92 }
93 if !g.pref.new_generic_solver {
94 if str_method := sym.find_method_with_generic_parent('str') {
95 if method_has_generic_source(str_method) {
96 match mut sym.info {
97 ast.Struct, ast.SumType, ast.Interface, ast.Alias, ast.GenericInst, ast.FnType {
98 str_fn_name = g.generic_fn_name(g.str_method_concrete_types(unwrapped, sym),
99 str_fn_name)
100 }
101 else {}
102 }
103 }
104 }
105 }
106 if sym.language == .c && !typ.has_flag(.option) && sym.has_method('str') {
107 str_fn_name = util.no_dots(g.cc_type(unwrapped, false)) + '_str'
108 }
109 g.str_types << StrType{
110 typ: unwrapped
111 styp: styp
112 }
113 return str_fn_name
114}
115
116fn (mut g Gen) final_gen_str(typ StrType) {
117 if typ in g.generated_str_fns {
118 return
119 }
120 $if trace_autostr ? {
121 eprintln('> final_gen_str: ${typ}')
122 }
123 g.generated_str_fns << typ
124 sym := g.table.sym(typ.typ)
125 if sym.has_method_with_generic_parent('str') && !(typ.typ.has_flag(.option)
126 || typ.typ.has_flag(.result)) {
127 return
128 }
129 // Skip str generation for unresolved generic parameter types (e.g. T)
130 if sym.kind == .any && !sym.is_builtin() {
131 return
132 }
133 styp := typ.styp
134 str_fn_name := g.get_str_fn(typ.typ)
135 lock g.str_fn_names {
136 if str_fn_name in g.str_fn_names {
137 return
138 }
139 g.str_fn_names << str_fn_name
140 }
141 if typ.typ.has_flag(.option) {
142 opt_typ := if typ.typ.has_flag(.option_mut_param_t) { styp.replace('*', '') } else { styp }
143 // Check if this is a type alias to an option type
144 mut type_name := 'Option'
145 mut unwrapped_typ := g.table.unaliased_type(typ.typ)
146 if unwrapped_typ != typ.typ {
147 // This is a type alias, check if it's an alias to an option type
148 alias_sym := g.table.sym(typ.typ)
149 if alias_sym.kind == .alias && alias_sym.info is ast.Alias {
150 // Check if the parent type has the option flag
151 if alias_sym.info.parent_type.has_flag(.option) {
152 // This is an alias to an option type (e.g., type MyOpt = ?MyStruct)
153 // Use the alias name instead of "Option"
154 mut alias_name := alias_sym.name
155 if alias_name.contains('.') {
156 alias_name = alias_name.all_after_last('.')
157 }
158 type_name = '?${alias_name}'
159 }
160 }
161 }
162 g.gen_str_for_option(typ.typ, opt_typ, str_fn_name, type_name)
163 return
164 }
165 if typ.typ.has_flag(.result) {
166 g.gen_str_for_result(typ.typ, styp, str_fn_name)
167 return
168 }
169 match sym.info {
170 ast.Alias {
171 if sym.info.is_import {
172 g.gen_str_default(sym, styp, str_fn_name)
173 } else {
174 g.gen_str_for_alias(sym.info, styp, str_fn_name)
175 }
176 }
177 ast.Array {
178 g.gen_str_for_array(sym.info, styp, str_fn_name)
179 }
180 ast.ArrayFixed {
181 g.gen_str_for_array_fixed(sym.info, styp, str_fn_name)
182 }
183 ast.Enum {
184 g.gen_str_for_enum(sym.info, styp, str_fn_name)
185 }
186 ast.FnType {
187 g.gen_str_for_fn_type(sym.info, styp, str_fn_name)
188 }
189 ast.Struct {
190 g.gen_str_for_struct(sym.info, sym.language, styp, g.table.type_to_str(typ.typ),
191 str_fn_name)
192 }
193 ast.Map {
194 g.gen_str_for_map(sym.info, styp, str_fn_name)
195 }
196 ast.MultiReturn {
197 g.gen_str_for_multi_return(sym.info, styp, str_fn_name)
198 }
199 ast.SumType {
200 g.gen_str_for_union_sum_type(sym.info, styp, g.table.type_to_str(typ.typ), str_fn_name)
201 }
202 ast.Interface {
203 g.gen_str_for_interface(sym.info, styp, g.table.type_to_str(typ.typ), str_fn_name)
204 }
205 ast.Chan {
206 g.gen_str_for_chan(sym.info, styp, str_fn_name)
207 }
208 ast.Thread {
209 g.gen_str_for_thread(sym.info, styp, str_fn_name)
210 }
211 else {
212 if sym.name != 'nil' {
213 verror('could not generate string method `${str_fn_name}` for type `${styp}`')
214 }
215 }
216 }
217}
218
219fn (mut g Gen) gen_str_for_option(typ ast.Type, styp string, str_fn_name string, type_name string) {
220 $if trace_autostr ? {
221 eprintln('> gen_str_for_option: ${typ.debug()} | ${styp} | ${str_fn_name}')
222 }
223 parent_type := typ.clear_flag(.option)
224 sym := g.table.sym(parent_type)
225 sym_has_str_method, expects_ptr, _ := sym.str_method_info()
226 parent_str_fn_name := g.get_str_fn(parent_type)
227 parent_cast_type := g.auto_str_storage_cast_type(parent_type)
228
229 g.definitions.writeln('string ${str_fn_name}(${styp} it);')
230 g.auto_str_funcs.writeln('string ${str_fn_name}(${styp} it) { return indent_${str_fn_name}(it, 0); }')
231 g.definitions.writeln('string indent_${str_fn_name}(${styp} it, ${ast.int_type_name} indent_count);')
232 g.auto_str_funcs.writeln('string indent_${str_fn_name}(${styp} it, ${ast.int_type_name} indent_count) {')
233 g.auto_str_funcs.writeln('\tstring res;')
234 g.auto_str_funcs.writeln('\tif (it.state == 0) {')
235 deref := if typ.is_ptr() && !typ.has_flag(.option_mut_param_t) {
236 dot := if expects_ptr { '*'.repeat(typ.nr_muls()) } else { '*'.repeat(typ.nr_muls() + 1) }
237 '${dot}(${parent_cast_type}**)&'
238 } else if typ.has_flag(.option_mut_param_t) {
239 '*(${parent_cast_type}*)'
240 } else if expects_ptr {
241 '(${parent_cast_type}*)'
242 } else {
243 '*(${parent_cast_type}*)'
244 }
245 if sym.kind == .string {
246 if typ.nr_muls() > 1 {
247 g.auto_str_funcs.writeln('\t\tres = builtin__ptr_str(*(${parent_cast_type}**)&it.data);')
248 } else {
249 tmp_res := '${parent_str_fn_name}(${deref}it.data)'
250 g.auto_str_funcs.writeln('\t\tres = ${str_intp_sq(tmp_res)};')
251 }
252 } else if should_use_indent_func(sym.kind) && !sym_has_str_method {
253 g.auto_str_funcs.writeln('\t\tres = indent_${parent_str_fn_name}(${deref}it.data, indent_count);')
254 } else if sym.kind == .function {
255 g.auto_str_funcs.writeln('\t\tres = ${parent_str_fn_name}();')
256 } else {
257 if typ.nr_muls() > 1 {
258 g.auto_str_funcs.writeln('\t\tres = builtin__ptr_str(*(${parent_cast_type}**)&it.data);')
259 } else {
260 g.auto_str_funcs.writeln('\t\tres = ${parent_str_fn_name}(${deref}it.data);')
261 }
262 }
263 g.auto_str_funcs.writeln('\t\treturn ${str_intp_sub('${type_name}(%%)', 'res')};')
264 g.auto_str_funcs.writeln('\t}')
265 g.auto_str_funcs.writeln('\treturn _S("${type_name}(none)");')
266 g.auto_str_funcs.writeln('}')
267}
268
269fn (mut g Gen) gen_str_for_result(typ ast.Type, styp string, str_fn_name string) {
270 $if trace_autostr ? {
271 eprintln('> gen_str_for_result: ${typ.debug()} | ${styp} | ${str_fn_name}')
272 }
273 parent_type := typ.clear_flag(.result)
274 sym := g.table.sym(parent_type)
275 sym_has_str_method, _, _ := sym.str_method_info()
276 parent_str_fn_name := g.get_str_fn(parent_type)
277 parent_cast_type := g.auto_str_storage_cast_type(parent_type)
278
279 g.definitions.writeln('string ${str_fn_name}(${styp} it);')
280 g.auto_str_funcs.writeln('string ${str_fn_name}(${styp} it) { return indent_${str_fn_name}(it, 0); }')
281 g.definitions.writeln('string indent_${str_fn_name}(${styp} it, ${ast.int_type_name} indent_count);')
282 g.auto_str_funcs.writeln('string indent_${str_fn_name}(${styp} it, ${ast.int_type_name} indent_count) {')
283 g.auto_str_funcs.writeln('\tstring res;')
284 g.auto_str_funcs.writeln('\tif (!it.is_error) {')
285 if sym.kind == .string {
286 tmp_res := '${parent_str_fn_name}(*(${parent_cast_type}*)it.data)'
287 g.auto_str_funcs.writeln('\t\tres = ${str_intp_sq(tmp_res)};')
288 } else if should_use_indent_func(sym.kind) && !sym_has_str_method {
289 g.auto_str_funcs.writeln('\t\tres = indent_${parent_str_fn_name}(*(${parent_cast_type}*)it.data, indent_count);')
290 } else {
291 g.auto_str_funcs.writeln('\t\tres = ${parent_str_fn_name}(*(${parent_cast_type}*)it.data);')
292 }
293 g.auto_str_funcs.writeln('\t} else {')
294
295 tmp_str := str_intp_sub('error: %%', 'builtin__IError_str(it.err)')
296 g.auto_str_funcs.writeln('\t\tres = ${tmp_str};')
297 g.auto_str_funcs.writeln('\t}')
298
299 g.auto_str_funcs.writeln('\treturn ${str_intp_sub('Result(%%)', 'res')};')
300 g.auto_str_funcs.writeln('}')
301}
302
303@[inline]
304fn (mut g Gen) auto_str_storage_cast_type(typ ast.Type) string {
305 return g.base_type(typ).trim('*')
306}
307
308fn (mut g Gen) gen_str_for_alias(info ast.Alias, styp string, str_fn_name string) {
309 parent_str_fn_name := g.get_str_fn(info.parent_type)
310 parent_sym := g.table.sym(info.parent_type)
311 _, str_method_expects_ptr, _ := parent_sym.str_method_info()
312
313 $if trace_autostr ? {
314 eprintln('> gen_str_for_alias: ${parent_str_fn_name} | ${styp} | ${str_fn_name}')
315 }
316 mut clean_type_v_type_name := util.strip_main_name(styp.replace('__', '.'))
317
318 is_c_struct := parent_sym.is_c_struct() && str_method_expects_ptr
319 arg_def := if is_c_struct { '${styp}* it' } else { '${styp} it' }
320
321 g.definitions.writeln('${g.static_non_parallel}string ${str_fn_name}(${arg_def});')
322 g.auto_str_funcs.writeln('${g.static_non_parallel}string ${str_fn_name}(${arg_def}) { return indent_${str_fn_name}(it, 0); }')
323 g.definitions.writeln('${g.static_non_parallel}string indent_${str_fn_name}(${arg_def}, ${ast.int_type_name} indent_count);')
324 g.auto_str_funcs.writeln('${g.static_non_parallel}string indent_${str_fn_name}(${arg_def}, ${ast.int_type_name} indent_count) {')
325 old := g.reset_tmp_count()
326 defer { g.tmp_count = old }
327 if str_method_expects_ptr {
328 it_arg := if is_c_struct { 'it' } else { '&it' }
329 g.auto_str_funcs.writeln('\tstring tmp_ds = ${parent_str_fn_name}(${it_arg});')
330 } else {
331 deref, _ := deref_kind(str_method_expects_ptr, info.parent_type.is_ptr(), info.parent_type)
332 g.auto_str_funcs.writeln('\tstring tmp_ds = ${parent_str_fn_name}(${deref}it);')
333 }
334 g.auto_str_funcs.writeln('\tstring res = builtin__str_intp(2, _MOV((StrIntpData[]){
335 {_S("${clean_type_v_type_name}("), ${si_s_code}, {.d_s = tmp_ds }, 0, 0, 0},
336 {_S(")"), 0, {0}, 0, 0, 0}
337 }));')
338 g.auto_str_funcs.writeln('\tbuiltin__string_free(&tmp_ds);')
339 g.auto_str_funcs.writeln('\treturn res;')
340 g.auto_str_funcs.writeln('}')
341}
342
343fn (mut g Gen) gen_str_for_multi_return(info ast.MultiReturn, styp string, str_fn_name string) {
344 $if trace_autostr ? {
345 eprintln('> gen_str_for_multi_return: ${info.types} | ${styp} | ${str_fn_name}')
346 }
347 g.definitions.writeln('${g.static_non_parallel}string ${str_fn_name}(${styp} a);')
348 mut fn_builder := strings.new_builder(512)
349 fn_builder.writeln('${g.static_non_parallel}string ${str_fn_name}(${styp} a) {')
350 fn_builder.writeln('\tstrings__Builder sb = strings__new_builder(2 + ${info.types.len} * 10);')
351 fn_builder.writeln('\tstrings__Builder_write_string(&sb, _S("("));')
352 for i, typ in info.types {
353 sym := g.table.sym(typ)
354 is_arg_ptr := typ.is_ptr()
355 sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info()
356 arg_str_fn_name := g.get_str_fn(typ)
357
358 if should_use_indent_func(sym.kind) && !sym_has_str_method {
359 fn_builder.writeln('\tstrings__Builder_write_string(&sb, ${arg_str_fn_name}(a.arg${i}));')
360 } else if sym.kind in [.f32, .f64] {
361 if sym.kind == .f32 {
362 tmp_val := str_intp_g32('a.arg${i}')
363 fn_builder.writeln('\tstrings__Builder_write_string(&sb, ${tmp_val});')
364 } else {
365 tmp_val := str_intp_g64('a.arg${i}')
366 fn_builder.writeln('\tstrings__Builder_write_string(&sb, ${tmp_val});')
367 }
368 } else if sym.kind == .string {
369 tmp_str := str_intp_sq('a.arg${i}')
370 fn_builder.writeln('\tstrings__Builder_write_string(&sb, ${tmp_str});')
371 } else if sym.kind == .function {
372 fn_builder.writeln('\tstrings__Builder_write_string(&sb, ${arg_str_fn_name}());')
373 } else {
374 deref, deref_label := deref_kind(str_method_expects_ptr, is_arg_ptr, typ)
375 fn_builder.writeln('\t\tstrings__Builder_write_string(&sb, _S("${deref_label}"));')
376 fn_builder.writeln('\tstrings__Builder_write_string(&sb, ${arg_str_fn_name}( ${deref} a.arg${i}));')
377 }
378 if i != info.types.len - 1 {
379 fn_builder.writeln('\tstrings__Builder_write_string(&sb, _S(", "));')
380 }
381 }
382 fn_builder.writeln('\tstrings__Builder_write_string(&sb, _S(")"));')
383 fn_builder.writeln('\tstring res = strings__Builder_str(&sb);')
384 fn_builder.writeln('\tstrings__Builder_free(&sb);')
385 fn_builder.writeln('\treturn res;')
386 fn_builder.writeln('}')
387 g.auto_fn_definitions << fn_builder.str()
388}
389
390fn (mut g Gen) gen_str_for_enum(info ast.Enum, styp string, str_fn_name string) {
391 $if trace_autostr ? {
392 eprintln('> gen_str_for_enum: ${info} | ${styp} | ${str_fn_name}')
393 }
394 s := util.no_dots(styp)
395 g.definitions.writeln('${g.static_non_parallel}string ${str_fn_name}(${styp} it);')
396 g.auto_str_funcs.writeln('${g.static_non_parallel}string ${str_fn_name}(${styp} it) { /* gen_str_for_enum */')
397 // Enums tagged with `@[flag]` are special in that they can be a combination of enum values
398 if info.is_flag {
399 clean_name := util.strip_main_name(styp.replace('__', '.'))
400 g.auto_str_funcs.writeln('\tstring ret = _S("${clean_name}{");')
401 g.auto_str_funcs.writeln('\t${ast.int_type_name} first = 1;')
402 g.auto_str_funcs.writeln('\tu64 zit = (u64)it;')
403 for i, val in info.vals {
404 mask := u64(1) << i
405 g.auto_str_funcs.writeln('\tif (zit & 0x${mask:016x}U) {if (!first) {ret = builtin__string__plus(ret, _S(" | "));} ret = builtin__string__plus(ret, _S(".${val}")); first = 0;}')
406 }
407 g.auto_str_funcs.writeln('\tret = builtin__string__plus(ret, _S("}"));')
408 g.auto_str_funcs.writeln('\treturn ret;')
409 } else if info.uses_exprs {
410 // The enum values could be C macros, expanded later to duplicate values, and we do not know if that is the case, so we can not use a switch here.
411 // Instead we generate multiple if statements, which is slower, but is guaranteed to work in the presence of duplicates.
412 for val in info.vals {
413 g.auto_str_funcs.writeln('\t\tif(it == ${s}__${val}){ return _S("${val}"); }')
414 }
415 g.auto_str_funcs.writeln('\t\treturn _S("unknown enum value");')
416 } else {
417 g.auto_str_funcs.writeln('\tswitch(it) {')
418 // Only use the first multi value on the lookup
419 mut seen := []string{len: info.vals.len}
420 for val in info.vals {
421 if info.is_multi_allowed && val in seen {
422 continue
423 } else if info.is_multi_allowed {
424 seen << val
425 }
426 g.auto_str_funcs.writeln('\t\tcase ${s}__${val}: return _S("${val}");')
427 }
428 g.auto_str_funcs.writeln('\t\tdefault: return _S("unknown enum value");')
429 g.auto_str_funcs.writeln('\t}')
430 }
431 g.auto_str_funcs.writeln('}')
432}
433
434fn (mut g Gen) gen_str_for_interface(info ast.Interface, styp string, typ_str string, str_fn_name string) {
435 $if trace_autostr ? {
436 eprintln('> gen_str_for_interface: ${info.types} | ${styp} | ${str_fn_name}')
437 }
438 // _str() functions should have a single argument, the indenting ones take 2:
439 g.definitions.writeln('${g.static_non_parallel}string ${str_fn_name}(${styp} x);')
440 g.auto_str_funcs.writeln('${g.static_non_parallel}string ${str_fn_name}(${styp} x) { return indent_${str_fn_name}(x, 0); }')
441 g.definitions.writeln('${g.static_non_parallel}string indent_${str_fn_name}(${styp} x, ${ast.int_type_name} indent_count);')
442 mut fn_builder := strings.new_builder(512)
443 clean_interface_v_type_name := util.strip_main_name(typ_str)
444 fn_builder.writeln('${g.static_non_parallel}string indent_${str_fn_name}(${styp} x, ${ast.int_type_name} indent_count) { /* gen_str_for_interface */')
445 fn_builder.writeln('\tif (x._typ == 0 && x._object == NULL) return _S("nil");')
446 for typ in info.types {
447 // Skip unresolved generic struct variants (e.g. a leftover `Text[T]`
448 // registered when a generic struct is used as the default value of an
449 // interface-typed field in a generic wrapper). Only their concrete
450 // instantiations (`Text[int]`) are real runtime variants. This mirrors
451 // the same skip in interface_table().
452 type_sym := g.table.sym(typ)
453 if type_sym.info is ast.Struct && type_sym.info.is_unresolved_generic() {
454 continue
455 }
456 sub_sym := g.table.sym(ast.mktyp(typ))
457 if g.pref.skip_unused && sub_sym.idx !in g.table.used_features.used_syms {
458 continue
459 }
460 mut func_name := g.get_str_fn(typ)
461 sym_has_str_method, str_method_expects_ptr, _ := sub_sym.str_method_info()
462 if should_use_indent_func(sub_sym.kind) && !sym_has_str_method {
463 func_name = 'indent_${func_name}'
464 }
465
466 // str_intp
467 if sub_sym.kind == .function {
468 res := 'builtin__str_intp(2, _MOV((StrIntpData[]){
469 {_S("${clean_interface_v_type_name}("), ${si_s_code}, {.d_s = ${func_name}()}, 0, 0, 0},
470 {_S(")"), 0, {0}, 0, 0, 0}
471 }))'
472 fn_builder.write_string2('\tif (x._typ == _${styp}_${sub_sym.cname}_index)',
473 ' return ${res};\n')
474 continue
475 }
476 deref := if sym_has_str_method && str_method_expects_ptr { ' ' } else { '*' }
477 if typ == ast.string_type {
478 mut val := '${func_name}(${deref}(${sub_sym.cname}*)x._${sub_sym.cname}'
479 if should_use_indent_func(sub_sym.kind) && !sym_has_str_method {
480 val += ', indent_count'
481 }
482 val += ')'
483 res := 'builtin__str_intp(2, _MOV((StrIntpData[]){
484 {_S("${clean_interface_v_type_name}(\'"), ${si_s_code}, {.d_s = ${val}}, 0, 0, 0},
485 {_S("\')"), 0, {0}, 0, 0, 0}
486 }))'
487 fn_builder.write_string2('\tif (x._typ == _${styp}_${sub_sym.cname}_index)',
488 ' return ${res};')
489 } else {
490 if !(sub_sym.kind == .array && g.table.sym(g.table.value_type(typ)).cname == styp) {
491 mut val := '${func_name}(${deref}(${sub_sym.cname}*)x._${sub_sym.cname}'
492 if should_use_indent_func(sub_sym.kind) && !sym_has_str_method {
493 val += ', indent_count'
494 }
495 val += ')'
496 res := 'builtin__str_intp(2, _MOV((StrIntpData[]){
497 {_S("${clean_interface_v_type_name}("), ${si_s_code}, {.d_s = ${val}}, 0, 0, 0},
498 {_S(")"), 0, {0}, 0, 0, 0}
499 }))'
500 if should_use_indent_func(sub_sym.kind) {
501 tmpvar := g.new_tmp_var()
502 fn_builder.writeln('\tif (x._typ == _${styp}_${sub_sym.cname}_index) {')
503 fn_builder.writeln('\t\tif (builtin__isnil(x._object) || builtin__autostr_addr_in_stack(x._object)) {')
504 fn_builder.writeln('\t\t\treturn builtin__isnil(x._object) ? _S("nil") : _S("<circular>");')
505 fn_builder.writeln('\t\t}')
506 fn_builder.writeln('\t\tbuiltin__autostr_addr_push(x._object);')
507 fn_builder.writeln('\t\tstring ${tmpvar} = ${res};')
508 fn_builder.writeln('\t\tbuiltin__autostr_addr_pop();')
509 fn_builder.writeln('\t\treturn ${tmpvar};')
510 fn_builder.writeln('\t}')
511 } else {
512 fn_builder.write_string2('\tif (x._typ == _${styp}_${sub_sym.cname}_index)',
513 ' return ${res};\n')
514 }
515 } else {
516 fn_builder.write_string2('\tif (x._typ == _${styp}_${sub_sym.cname}_index)',
517 ' return _S("<circular>");\n')
518 }
519 }
520 }
521 fn_builder.writeln('\treturn _S("unknown interface value");')
522 fn_builder.writeln('}')
523 g.auto_fn_definitions << fn_builder.str()
524}
525
526fn (mut g Gen) gen_str_for_union_sum_type(info ast.SumType, styp string, typ_str string, str_fn_name string) {
527 $if trace_autostr ? {
528 eprintln('> gen_str_for_union_sum_type: ${info.variants} | ${styp} | ${str_fn_name}')
529 }
530 // _str() functions should have a single argument, the indenting ones take 2:
531 g.definitions.writeln('${g.static_non_parallel}string ${str_fn_name}(${styp} x);')
532 g.auto_str_funcs.writeln('${g.static_non_parallel}string ${str_fn_name}(${styp} x) { return indent_${str_fn_name}(x, 0); }')
533 g.definitions.writeln('${g.static_non_parallel}string indent_${str_fn_name}(${styp} x, ${ast.int_type_name} indent_count);')
534 mut fn_builder := strings.new_builder(512)
535 fn_builder.writeln('${g.static_non_parallel}string indent_${str_fn_name}(${styp} x, ${ast.int_type_name} indent_count) {')
536 mut clean_sum_type_v_type_name := ''
537 if info.is_anon {
538 variant_names := info.variants.map(util.strip_main_name(g.table.sym(it).name))
539 clean_sum_type_v_type_name = '${variant_names.join('|')}'
540 } else {
541 clean_sum_type_v_type_name = util.strip_main_name(typ_str)
542 }
543 fn_builder.writeln('\tswitch(x._typ) {')
544 mut idxs := []int{}
545 for typ in info.variants {
546 if typ in idxs {
547 continue
548 }
549 idxs << typ
550 typ_name := g.styp(typ)
551 mut func_name := g.get_str_fn(typ)
552 sym := g.table.sym(typ)
553 is_c_struct := sym.is_c_struct()
554 sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info()
555 deref := if is_c_struct || (sym_has_str_method && str_method_expects_ptr) {
556 ' '
557 } else {
558 '*'
559 }
560 if should_use_indent_func(sym.kind) && !sym_has_str_method {
561 func_name = 'indent_${func_name}'
562 }
563
564 // str_intp
565 if typ == ast.string_type {
566 mut val := '${func_name}(${deref}(${typ_name}*)x._${sym.cname}'
567 if should_use_indent_func(sym.kind) && !sym_has_str_method {
568 val += ', indent_count'
569 }
570 val += ')'
571 res := 'builtin__str_intp(2, _MOV((StrIntpData[]){
572 {_S("${clean_sum_type_v_type_name}(\'"), ${si_s_code}, {.d_s = ${val}}, 0, 0, 0},
573 {_S("\')"), 0, {0}, 0, 0, 0}
574 }))'
575 fn_builder.write_string('\t\tcase ${int(typ)}: return ${res};\n')
576 } else {
577 mut val := '${func_name}(${deref}(${typ_name}*)x._${g.get_sumtype_variant_name(typ, sym)}'
578 if should_use_indent_func(sym.kind) && !sym_has_str_method {
579 val += ', indent_count'
580 }
581 val += ')'
582 res := 'builtin__str_intp(2, _MOV((StrIntpData[]){
583 {_S("${clean_sum_type_v_type_name}("), ${si_s_code}, {.d_s = ${val}}, 0, 0, 0},
584 {_S(")"), 0, {0}, 0, 0, 0}
585 }))'
586 fn_builder.write_string('\t\tcase ${int(typ)}: return ${res};\n')
587 }
588 }
589 fn_builder.writeln('\t\tdefault: return _S("unknown sum type value");')
590 fn_builder.writeln('\t}')
591 fn_builder.writeln('}')
592 g.auto_fn_definitions << fn_builder.str()
593}
594
595fn (mut g Gen) fn_decl_str(info ast.FnType) string {
596 mut fn_str := 'fn ('
597 for i, arg in info.func.params {
598 if arg.is_mut {
599 fn_str += 'mut '
600 }
601 if i > 0 {
602 fn_str += ', '
603 }
604 if arg.typ.has_flag(.option) {
605 fn_str += '?'
606 }
607 fn_str += util.strip_main_name(g.table.get_type_name(g.unwrap_generic(arg.typ)))
608 }
609 fn_str += ')'
610 if info.func.return_type == ast.ovoid_type {
611 fn_str += ' ?'
612 } else if info.func.return_type == ast.rvoid_type {
613 fn_str += ' !'
614 } else if info.func.return_type != ast.void_type {
615 x := util.strip_main_name(g.table.get_type_name(g.unwrap_generic(info.func.return_type)))
616 if info.func.return_type.has_flag(.option) {
617 fn_str += ' ?${x}'
618 } else if info.func.return_type.has_flag(.result) {
619 fn_str += ' !${x}'
620 } else {
621 fn_str += ' ${x}'
622 }
623 }
624 return fn_str
625}
626
627fn (mut g Gen) gen_str_for_fn_type(info ast.FnType, styp string, str_fn_name string) {
628 $if trace_autostr ? {
629 eprintln('> gen_str_for_fn_type: ${info.func.name} | ${styp} | ${str_fn_name}')
630 }
631 g.definitions.writeln('${g.static_non_parallel}string ${str_fn_name}();')
632 g.auto_str_funcs.writeln('${g.static_non_parallel}string ${str_fn_name}() { return _S("${g.fn_decl_str(info)}");}')
633}
634
635fn (mut g Gen) gen_str_for_chan(info ast.Chan, styp string, str_fn_name string) {
636 $if trace_autostr ? {
637 eprintln('> gen_str_for_chan: ${info.elem_type.debug()} | ${styp} | ${str_fn_name}')
638 }
639 elem_type_name := util.strip_main_name(g.table.get_type_name(g.unwrap_generic(info.elem_type)))
640 g.definitions.writeln('${g.static_non_parallel}string ${str_fn_name}(${styp} x);')
641 g.auto_str_funcs.writeln('${g.static_non_parallel}string ${str_fn_name}(${styp} x) { return indent_${str_fn_name}(x, 0);}')
642 g.definitions.writeln('${g.static_non_parallel}string indent_${str_fn_name}(${styp} x, ${ast.int_type_name} indent_count);')
643 g.auto_str_funcs.writeln('${g.static_non_parallel}string indent_${str_fn_name}(${styp} x, ${ast.int_type_name} indent_count) {')
644 g.auto_str_funcs.writeln('\tstring indents = builtin__string_repeat(_S(" "), indent_count);')
645 g.auto_str_funcs.writeln('\tstrings__Builder sb = strings__new_builder(64);')
646 g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _S("chan "));')
647 g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _S("${elem_type_name}"));')
648 g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _S("{\\n"));')
649 g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, indents);')
650 g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _S(" cap: "));')
651 g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, builtin__int_str(x->cap));')
652 g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _S(", closed: "));')
653 g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, (x->closed != 0 ? _S("true") : _S("false")));')
654 g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _S("\\n"));')
655 g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, indents);')
656 g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _S("}"));')
657 g.auto_str_funcs.writeln('\tstring res = strings__Builder_str(&sb);')
658 g.auto_str_funcs.writeln('\tstrings__Builder_free(&sb);')
659 g.auto_str_funcs.writeln('\tbuiltin__string_free(&indents);')
660 g.auto_str_funcs.writeln('\treturn res;')
661 g.auto_str_funcs.writeln('}')
662}
663
664fn (mut g Gen) gen_str_for_thread(info ast.Thread, styp string, str_fn_name string) {
665 $if trace_autostr ? {
666 eprintln('> gen_str_for_thread: ${info.return_type.debug()} | ${styp} | ${str_fn_name}')
667 }
668 ret_type_name := util.strip_main_name(g.table.get_type_name(info.return_type))
669 g.definitions.writeln('${g.static_non_parallel}string ${str_fn_name}(${styp} _); // auto}')
670 g.auto_str_funcs.writeln('${g.static_non_parallel}string ${str_fn_name}(${styp} _) { return _S("thread(${ret_type_name})");}')
671}
672
673@[inline]
674fn styp_to_str_fn_name(styp string) string {
675 return styp.replace_each(['*', '', '.', '__', ' ', '__']) + '_str'
676}
677
678fn method_has_generic_source(method ast.Fn) bool {
679 if method.generic_names.len > 0 {
680 return true
681 }
682 if method.source_fn != unsafe { nil } {
683 fndecl := unsafe { &ast.FnDecl(method.source_fn) }
684 return fndecl.generic_names.len > 0
685 }
686 return false
687}
688
689fn (mut g Gen) str_method_concrete_types(typ ast.Type, sym &ast.TypeSymbol) []ast.Type {
690 if _ := g.receiver_exact_method_for_type(typ, 'str') {
691 match sym.info {
692 ast.Struct, ast.SumType, ast.Interface {
693 return sym.info.concrete_types.clone()
694 }
695 ast.GenericInst {
696 return sym.info.concrete_types.clone()
697 }
698 ast.FnType {
699 return g.concrete_types_for_fn_type_symbol(sym)
700 }
701 ast.Alias {
702 return g.alias_parent_concrete_types(sym.info)
703 }
704 else {}
705 }
706 }
707 if structured_method := g.table.find_structured_receiver_method_with_types(typ, 'str') {
708 return structured_method.concrete_types.map(g.unwrap_generic(it))
709 }
710 match sym.info {
711 ast.Struct, ast.SumType, ast.Interface {
712 return sym.info.concrete_types.clone()
713 }
714 ast.GenericInst {
715 return sym.info.concrete_types.clone()
716 }
717 ast.FnType {
718 return g.concrete_types_for_fn_type_symbol(sym)
719 }
720 ast.Alias {
721 return g.alias_parent_concrete_types(sym.info)
722 }
723 else {}
724 }
725
726 return []ast.Type{}
727}
728
729fn (mut g Gen) concrete_types_for_fn_type_symbol(sym &ast.TypeSymbol) []ast.Type {
730 if sym.info is ast.FnType && sym.generic_types.len > 0
731 && !sym.generic_types.any(it.has_flag(.generic)
732 || g.table.generic_type_names(it).len > 0) {
733 return sym.generic_types.clone()
734 }
735 return []ast.Type{}
736}
737
738fn (mut g Gen) alias_parent_concrete_types(info ast.Alias) []ast.Type {
739 parent_sym := g.table.sym(info.parent_type)
740 match parent_sym.info {
741 ast.Struct, ast.SumType, ast.Interface {
742 mut concrete_types := parent_sym.info.concrete_types.clone()
743 if concrete_types.len == 0
744 && parent_sym.generic_types.len == parent_sym.info.generic_types.len
745 && parent_sym.generic_types != parent_sym.info.generic_types {
746 concrete_types = parent_sym.generic_types.clone()
747 }
748 return concrete_types
749 }
750 ast.GenericInst {
751 return parent_sym.info.concrete_types.clone()
752 }
753 ast.Array {
754 return [parent_sym.info.elem_type]
755 }
756 ast.ArrayFixed {
757 return [parent_sym.info.elem_type]
758 }
759 ast.Chan {
760 return [parent_sym.info.elem_type]
761 }
762 ast.Map {
763 return [parent_sym.info.key_type, parent_sym.info.value_type]
764 }
765 ast.Alias {
766 return g.alias_parent_concrete_types(parent_sym.info)
767 }
768 else {}
769 }
770
771 return []ast.Type{}
772}
773
774// deref_kind returns deref, deref_label
775fn deref_kind(str_method_expects_ptr bool, is_elem_ptr bool, typ ast.Type) (string, string) {
776 if typ.has_flag(.option) {
777 return '', ''
778 }
779 if str_method_expects_ptr != is_elem_ptr {
780 if is_elem_ptr {
781 return '*'.repeat(typ.nr_muls()), '&'.repeat(typ.nr_muls())
782 } else {
783 return '&', ''
784 }
785 }
786 return '', ''
787}
788
789fn (mut g Gen) gen_str_for_array(info ast.Array, styp string, str_fn_name string) {
790 $if trace_autostr ? {
791 eprintln('> gen_str_for_array: ${info.elem_type.debug()} | ${styp} | ${str_fn_name}')
792 }
793 mut typ := info.elem_type
794 mut sym := g.table.sym(info.elem_type)
795 is_option := typ.has_flag(.option)
796 if !is_option && mut sym.info is ast.Alias {
797 // Preserve pointer depth when arrays contain alias pointer types.
798 typ = g.unalias_type_keep_muls(typ)
799 sym = g.table.sym(typ)
800 }
801 field_styp := g.styp(typ)
802 is_elem_ptr := typ.is_ptr()
803 sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info()
804 elem_str_fn_name := g.get_str_fn(typ)
805
806 g.definitions.writeln('${g.static_non_parallel}string ${str_fn_name}(${styp} a);')
807 g.auto_str_funcs.writeln('${g.static_non_parallel}string ${str_fn_name}(${styp} a) { return indent_${str_fn_name}(a, 0);}')
808 g.definitions.writeln('${g.static_non_parallel}string indent_${str_fn_name}(${styp} a, ${ast.int_type_name} indent_count);')
809 g.auto_str_funcs.writeln('${g.static_non_parallel}string indent_${str_fn_name}(${styp} a, ${ast.int_type_name} indent_count) {')
810 g.auto_str_funcs.writeln('\tstrings__Builder sb = strings__new_builder(2 + a.len * 10);')
811 g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _S("["));')
812 g.auto_str_funcs.writeln('\tfor (${ast.int_type_name} i = 0; i < a.len; ++i) {')
813 if sym.kind == .function {
814 g.auto_str_funcs.writeln('\t\tstring x = ${elem_str_fn_name}();')
815 } else {
816 if !typ.has_flag(.option) && sym.kind == .array_fixed {
817 g.auto_str_funcs.writeln('\t\t${field_styp} it;')
818 g.auto_str_funcs.writeln('\t\tmemcpy(*(${field_styp}*)it, (byte*)builtin__array_get(a, i), sizeof(${field_styp}));')
819 } else {
820 g.auto_str_funcs.writeln('\t\t${field_styp} it = *(${field_styp}*)builtin__array_get(a, i);')
821 }
822 if should_use_indent_func(sym.kind) && !sym_has_str_method {
823 if is_elem_ptr {
824 deref, deref_label := deref_kind(str_method_expects_ptr, is_elem_ptr, typ)
825 g.auto_str_funcs.writeln('\t\tstring x = _S("nil");')
826 if !typ.has_flag(.option) {
827 g.auto_str_funcs.writeln('\t\tif (it != 0) {')
828 }
829 g.auto_str_funcs.writeln('\t\t\tstrings__Builder_write_string(&sb, _S("${deref_label}"));')
830 g.auto_str_funcs.writeln('\t\t\tx = ${elem_str_fn_name}(${deref}it);')
831 if !typ.has_flag(.option) {
832 g.auto_str_funcs.writeln('\t\t}')
833 }
834 } else {
835 prefix := if !is_option && sym.is_c_struct() && str_method_expects_ptr {
836 '&'
837 } else {
838 ''
839 }
840 g.auto_str_funcs.writeln('\t\tstring x = indent_${elem_str_fn_name}(${prefix}it, indent_count);')
841 }
842 } else if sym.kind in [.f32, .f64] {
843 if sym.kind == .f32 {
844 g.auto_str_funcs.writeln('\t\tstring x = ${str_intp_g32('it')};')
845 } else {
846 g.auto_str_funcs.writeln('\t\tstring x = ${str_intp_g64('it')};')
847 }
848 } else if sym.kind == .rune {
849 // Rune are managed at this level as strings
850 g.auto_str_funcs.writeln('\t\tstring x = builtin__str_intp(2, _MOV((StrIntpData[]){{_S("\`"), ${si_s_code}, {.d_s = ${elem_str_fn_name}(it) }, 0, 0, 0}, {_S("\`"), 0, {0}, 0, 0, 0}}));\n')
851 } else if sym.kind == .string {
852 if typ.has_flag(.option) {
853 func := g.get_str_fn(typ)
854 g.auto_str_funcs.writeln('\t\tstring x = ${func}(it);\n')
855 } else if is_elem_ptr {
856 g.auto_str_funcs.writeln('\t\tstring x = builtin__str_intp(2, _MOV((StrIntpData[]){{_S("&\'"), ${si_s_code}, {.d_s = *it }, 0, 0, 0}, {_S("\'"), 0, {0}, 0, 0, 0}}));\n')
857 } else {
858 g.auto_str_funcs.writeln('\t\tstring x = builtin__str_intp(2, _MOV((StrIntpData[]){{_S("\'"), ${si_s_code}, {.d_s = it }, 0, 0, 0}, {_S("\'"), 0, {0}, 0, 0, 0}}));\n')
859 }
860 } else {
861 // There is a custom .str() method, so use it.
862 // Note: we need to take account of whether the user has defined
863 // `fn (x T) str() {` or `fn (x &T) str() {`, and convert accordingly
864 deref, deref_label := deref_kind(str_method_expects_ptr, is_elem_ptr, typ)
865 if is_elem_ptr {
866 g.auto_str_funcs.writeln('\t\tstring x = _S("nil");')
867 if !typ.has_flag(.option) {
868 g.auto_str_funcs.writeln('\t\tif (it != 0) {')
869 }
870 g.auto_str_funcs.writeln('\t\t\tstrings__Builder_write_string(&sb, _S("${deref_label}"));')
871 g.auto_str_funcs.writeln('\t\t\tx = ${elem_str_fn_name}(${deref}it);')
872 if !typ.has_flag(.option) {
873 g.auto_str_funcs.writeln('\t\t}')
874 }
875 } else {
876 g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, _S("${deref_label}"));')
877 g.auto_str_funcs.writeln('\t\tstring x = ${elem_str_fn_name}(${deref}it);')
878 }
879 }
880 }
881 g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, x);')
882 if g.is_autofree && typ != ast.bool_type {
883 // no need to free "true"/"false" literals
884 g.auto_str_funcs.writeln('\t\tbuiltin__string_free(&x);')
885 }
886 g.auto_str_funcs.writeln('\t\tif (i < a.len-1) {')
887 g.auto_str_funcs.writeln('\t\t\tstrings__Builder_write_string(&sb, _S(", "));')
888 g.auto_str_funcs.writeln('\t\t}')
889 g.auto_str_funcs.writeln('\t}')
890 g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _S("]"));')
891 g.auto_str_funcs.writeln('\tstring res = strings__Builder_str(&sb);')
892 g.auto_str_funcs.writeln('\tstrings__Builder_free(&sb);')
893 g.auto_str_funcs.writeln('\treturn res;')
894 g.auto_str_funcs.writeln('}')
895}
896
897fn (mut g Gen) gen_str_for_array_fixed(info ast.ArrayFixed, styp string, str_fn_name string) {
898 $if trace_autostr ? {
899 eprintln('> gen_str_for_array_fixed: ${info.elem_type.debug()} | ${styp} | ${str_fn_name}')
900 }
901 mut typ := info.elem_type
902 mut sym := g.table.sym(info.elem_type)
903 if mut sym.info is ast.Alias {
904 typ = g.unalias_type_keep_muls(typ)
905 sym = g.table.sym(typ)
906 }
907 is_elem_ptr := typ.is_ptr()
908 sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info()
909 elem_str_fn_name := g.get_str_fn(typ)
910 def_arg := if info.is_fn_ret { '${g.styp(typ)} a[${info.size}]' } else { '${styp} a' }
911
912 g.definitions.writeln('${g.static_non_parallel}string ${str_fn_name}(${def_arg});')
913 g.auto_str_funcs.writeln('${g.static_non_parallel}string ${str_fn_name}(${def_arg}) { return indent_${str_fn_name}(a, 0);}')
914 g.definitions.writeln('${g.static_non_parallel}string indent_${str_fn_name}(${def_arg}, ${ast.int_type_name} indent_count);')
915 g.auto_str_funcs.writeln('${g.static_non_parallel}string indent_${str_fn_name}(${def_arg}, ${ast.int_type_name} indent_count) {')
916 g.auto_str_funcs.writeln('\tstrings__Builder sb = strings__new_builder(2 + ${info.size} * 10);')
917 g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _S("["));')
918 g.auto_str_funcs.writeln('\tfor (${ast.int_type_name} i = 0; i < ${info.size}; ++i) {')
919 if sym.kind == .function {
920 g.auto_str_funcs.writeln('\t\tstring x = ${elem_str_fn_name}();')
921 g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, x);')
922 } else {
923 deref, deref_label := deref_kind(str_method_expects_ptr, is_elem_ptr, typ)
924 if should_use_indent_func(sym.kind) && !sym_has_str_method {
925 if is_elem_ptr {
926 g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, _S("${deref_label}"));')
927 g.auto_str_funcs.writeln('\t\tif ( 0 == a[i] ) {')
928 g.auto_str_funcs.writeln('\t\t\tstrings__Builder_write_string(&sb, _S("0"));')
929 g.auto_str_funcs.writeln('\t\t}else{')
930 g.auto_str_funcs.writeln('\t\t\tstrings__Builder_write_string(&sb, ${elem_str_fn_name}(${deref}a[i]));')
931 g.auto_str_funcs.writeln('\t\t}')
932 } else {
933 g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${elem_str_fn_name}(${deref}a[i]));')
934 }
935 } else if sym.kind in [.f32, .f64] && !typ.has_flag(.option) {
936 if sym.kind == .f32 {
937 field_str := str_intp_g32('a[i]')
938 g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${field_str});')
939 } else {
940 field_str := str_intp_g64('a[i]')
941 g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${field_str});')
942 }
943 } else if sym.kind == .string && !typ.has_flag(.option) {
944 field_str := str_intp_sq('a[i]')
945 g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${field_str});')
946 } else if sym.kind == .rune && !typ.has_flag(.option) {
947 tmp_str := str_intp_rune('${elem_str_fn_name}(${deref}a[i])')
948 g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${tmp_str});')
949 } else {
950 g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${elem_str_fn_name}(${deref}a[i]));')
951 }
952 }
953 g.auto_str_funcs.writeln('\t\tif (i < ${info.size - 1}) {')
954 g.auto_str_funcs.writeln('\t\t\tstrings__Builder_write_string(&sb, _S(", "));')
955 g.auto_str_funcs.writeln('\t\t}')
956 g.auto_str_funcs.writeln('\t}')
957 g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _S("]"));')
958 g.auto_str_funcs.writeln('\tstring res = strings__Builder_str(&sb);')
959 g.auto_str_funcs.writeln('\tstrings__Builder_free(&sb);')
960 g.auto_str_funcs.writeln('\treturn res;')
961 g.auto_str_funcs.writeln('}')
962}
963
964fn (mut g Gen) gen_str_for_map(info ast.Map, styp string, str_fn_name string) {
965 $if trace_autostr ? {
966 eprintln('> gen_str_for_map: ${info.key_type.debug()} -> ${info.value_type.debug()} | ${styp} | ${str_fn_name}')
967 }
968 mut key_typ := info.key_type
969 mut key_sym := g.table.sym(key_typ)
970 if mut key_sym.info is ast.Alias {
971 key_typ = key_sym.info.parent_type
972 key_sym = g.table.sym(key_typ)
973 }
974 key_styp := g.styp(key_typ)
975 key_str_fn_name := g.get_str_fn(key_typ)
976 if !key_sym.has_method('str') {
977 g.get_str_fn(key_typ)
978 }
979
980 mut val_typ := info.value_type
981 mut val_sym := g.table.sym(val_typ)
982 is_option := val_typ.has_flag(.option)
983 if !is_option && mut val_sym.info is ast.Alias {
984 val_typ = val_sym.info.parent_type
985 val_sym = g.table.sym(val_typ)
986 }
987 val_styp := g.styp(val_typ)
988 mut elem_str_fn_name := g.get_str_fn(val_typ)
989
990 mut receiver_is_ptr := false
991 fn_str := val_sym.find_method_with_generic_parent('str') or { ast.Fn{} }
992
993 if fn_str.name == 'str' {
994 receiver_is_ptr = fn_str.receiver_type.is_ptr()
995 } else {
996 g.get_str_fn(val_typ)
997 }
998
999 g.definitions.writeln('${g.static_non_parallel}string ${str_fn_name}(${styp} m);')
1000 g.auto_str_funcs.writeln('${g.static_non_parallel}string ${str_fn_name}(${styp} m) { return indent_${str_fn_name}(m, 0);}')
1001 g.definitions.writeln('${g.static_non_parallel}string indent_${str_fn_name}(${styp} m, ${ast.int_type_name} indent_count);')
1002 g.auto_str_funcs.writeln('${g.static_non_parallel}string indent_${str_fn_name}(${styp} m, ${ast.int_type_name} indent_count) { /* gen_str_for_map */')
1003 g.auto_str_funcs.writeln('\tstrings__Builder sb = strings__new_builder(2 + m.key_values.len * 10);')
1004 g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _S("{"));')
1005 g.auto_str_funcs.writeln('\tbool is_first = true;')
1006 g.auto_str_funcs.writeln('\tfor (${ast.int_type_name} i = 0; i < m.key_values.len; ++i) {')
1007 g.auto_str_funcs.writeln('\t\tif (!builtin__DenseArray_has_index(&m.key_values, i)) { continue; }')
1008 g.auto_str_funcs.writeln('\t\telse if (!is_first) { strings__Builder_write_string(&sb, _S(", ")); }')
1009
1010 if key_sym.kind == .string {
1011 g.auto_str_funcs.writeln('\t\tstring key = *(string*)builtin__DenseArray_key(&m.key_values, i);')
1012 } else if key_sym.kind == .array_fixed {
1013 g.auto_str_funcs.writeln('\t\t${key_styp} key;')
1014 g.auto_str_funcs.writeln('\t\tmemcpy(key, builtin__DenseArray_key(&m.key_values, i), sizeof(${key_styp}));')
1015 } else {
1016 g.auto_str_funcs.writeln('\t\t${key_styp} key = *(${key_styp}*)builtin__DenseArray_key(&m.key_values, i);')
1017 }
1018 if key_sym.kind == .string {
1019 g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${str_intp_sq('key')});')
1020 } else if key_sym.kind == .rune {
1021 tmp_str := str_intp_rune('${key_str_fn_name}(key)')
1022 g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${tmp_str});')
1023 } else {
1024 g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${key_str_fn_name}(key));')
1025 }
1026 g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, _S(": "));')
1027 _, str_method_expects_ptr, _ := val_sym.str_method_info()
1028 _, deref_label := deref_kind(str_method_expects_ptr, val_typ.is_ptr(), val_typ)
1029 if deref_label != '' {
1030 g.auto_str_funcs.writeln('\t\t\tstrings__Builder_write_string(&sb, _S("${deref_label}"));')
1031 }
1032 if val_sym.kind == .function {
1033 g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${elem_str_fn_name}());')
1034 } else if val_sym.kind == .string {
1035 if val_typ.has_flag(.option) {
1036 func := g.get_str_fn(val_typ)
1037 g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${func}(*(${val_styp}*)builtin__DenseArray_value(&m.key_values, i)));')
1038 } else {
1039 tmp_str := str_intp_sq('*(${val_styp}*)builtin__DenseArray_value(&m.key_values, i)')
1040 g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${tmp_str});')
1041 }
1042 } else if should_use_indent_func(val_sym.kind) && fn_str.name != 'str' {
1043 deref := if !is_option && val_sym.is_c_struct() && str_method_expects_ptr {
1044 ''
1045 } else {
1046 '*'.repeat(val_typ.nr_muls() + 1)
1047 }
1048 g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, indent_${elem_str_fn_name}(${deref}(${val_styp}*)builtin__DenseArray_value(&m.key_values, i), indent_count));')
1049 } else if val_sym.kind in [.f32, .f64] {
1050 tmp_val := '*(${val_styp}*)builtin__DenseArray_value(&m.key_values, i)'
1051 if val_typ.has_flag(.option) {
1052 g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${g.get_str_fn(val_typ)}(*(${val_styp}*)builtin__DenseArray_value(&m.key_values, i)));')
1053 } else {
1054 if val_sym.kind == .f32 {
1055 g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${str_intp_g32(tmp_val)});')
1056 } else {
1057 g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${str_intp_g64(tmp_val)});')
1058 }
1059 }
1060 } else if val_sym.kind == .rune {
1061 tmp_str :=
1062 str_intp_rune('${elem_str_fn_name}(*(${val_styp}*)builtin__DenseArray_value(&m.key_values, i))')
1063 g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${tmp_str});')
1064 } else {
1065 deref := '*'.repeat(val_typ.nr_muls())
1066 if val_typ.has_flag(.option) {
1067 g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${g.get_str_fn(val_typ)}(*${deref}(${val_styp}*)builtin__DenseArray_value(&m.key_values, i)));')
1068 } else if receiver_is_ptr {
1069 g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${elem_str_fn_name}(${deref}(${val_styp}*)builtin__DenseArray_value(&m.key_values, i)));')
1070 } else {
1071 g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${elem_str_fn_name}(*${deref}(${val_styp}*)builtin__DenseArray_value(&m.key_values, i)));')
1072 }
1073 }
1074 g.auto_str_funcs.writeln('\t\tis_first = false;')
1075 g.auto_str_funcs.writeln('\t}')
1076 g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _S("}"));')
1077 g.auto_str_funcs.writeln('\tstring res = strings__Builder_str(&sb);')
1078 g.auto_str_funcs.writeln('\tstrings__Builder_free(&sb);')
1079 g.auto_str_funcs.writeln('\treturn res;')
1080 g.auto_str_funcs.writeln('}')
1081}
1082
1083fn (g &Gen) type_to_fmt(typ ast.Type) StrIntpType {
1084 if typ == ast.u8_type_idx {
1085 return .si_u8
1086 }
1087 if typ == ast.char_type_idx {
1088 return .si_c
1089 }
1090 if typ in ast.voidptr_types || typ == ast.nil_type || typ in ast.byteptr_types {
1091 return .si_p
1092 }
1093 if typ in ast.charptr_types {
1094 // return '%C\\000' // a C string
1095 return .si_s
1096 }
1097 typ_nr_muls := typ.nr_muls()
1098 if typ_nr_muls > 1 {
1099 return .si_p
1100 }
1101 sym := g.table.sym(typ)
1102 if typ.is_int_valptr() || typ.is_float_valptr() {
1103 return .si_s
1104 } else if sym.kind in [.struct, .array, .array_fixed, .map, .bool, .enum, .interface, .sum_type,
1105 .function, .alias, .chan, .thread] {
1106 return .si_s
1107 } else if sym.kind == .string {
1108 return .si_s
1109 // return "'%.*s\\000'"
1110 } else if sym.kind in [.f32, .f64] {
1111 if sym.kind == .f32 {
1112 return .si_g32
1113 }
1114 return .si_g64
1115 } else if sym.kind == .int {
1116 $if new_int ? && x64 {
1117 return .si_i64
1118 } $else {
1119 return .si_i32
1120 }
1121 } else if sym.kind == .u32 {
1122 return .si_u32
1123 } else if sym.kind == .u64 {
1124 return .si_u64
1125 } else if sym.kind == .i64 {
1126 return .si_i64
1127 } else if sym.kind == .usize {
1128 return .si_u64
1129 } else if sym.kind == .isize {
1130 return .si_i64
1131 }
1132 return .si_i32
1133}
1134
1135fn (mut g Gen) gen_str_for_struct(info ast.Struct, lang ast.Language, styp string, typ_str string, str_fn_name string) {
1136 $if trace_autostr ? {
1137 eprintln('> gen_str_for_struct: ${info.parent_type.debug()} | ${styp} | ${str_fn_name}')
1138 }
1139 // _str() functions should have a single argument, the indenting ones take 2:
1140 is_c_struct := lang == .c
1141 arg_def := if is_c_struct { '${styp}* it' } else { '${styp} it' }
1142 g.definitions.writeln('${g.static_non_parallel}string ${str_fn_name}(${arg_def});')
1143 g.auto_str_funcs.writeln('${g.static_non_parallel}string ${str_fn_name}(${arg_def}) { return indent_${str_fn_name}(it, 0);}')
1144 g.definitions.writeln('${g.static_non_parallel}string indent_${str_fn_name}(${arg_def}, ${ast.int_type_name} indent_count);')
1145 mut fn_builder := strings.new_builder(512)
1146 defer {
1147 g.auto_fn_definitions << fn_builder.str()
1148 }
1149 fn_builder.writeln('string indent_${str_fn_name}(${arg_def}, ${ast.int_type_name} indent_count) {')
1150 old := g.reset_tmp_count()
1151 defer { g.tmp_count = old }
1152 clean_struct_v_type_name := if info.is_anon { 'struct ' } else { util.strip_main_name(typ_str) }
1153 // generate ident / indent length = 4 spaces
1154 if info.fields.len == 0 {
1155 fn_builder.writeln('\treturn _S("${clean_struct_v_type_name}{}");')
1156 fn_builder.writeln('}')
1157 return
1158 }
1159 allow_circular := info.attrs.any(it.name == 'autostr' && it.arg == 'allowrecurse')
1160 if g.pref.hide_auto_str {
1161 fn_builder.writeln('\tstring res = { .str ="str() used with -hide-auto-str", .len=30 };')
1162 fn_builder.writeln('\treturn res;')
1163 fn_builder.writeln('}')
1164 return
1165 }
1166 if !allow_circular {
1167 fn_builder.writeln('\tif (indent_count > 20) {')
1168 fn_builder.writeln('\t\treturn _S("<circular>");')
1169 fn_builder.writeln('\t}')
1170 }
1171 fn_builder.writeln('\tstring indents = builtin__string_repeat(_S(" "), indent_count);')
1172
1173 mut fn_body_surrounder := util.new_surrounder(info.fields.len)
1174 mut fn_body := strings.new_builder(info.fields.len * 256)
1175 defer {
1176 fn_body_surrounder.builder_write_befores(mut fn_builder)
1177 fn_builder << fn_body
1178 fn_body_surrounder.builder_write_afters(mut fn_builder)
1179 fn_builder.writeln('\tbuiltin__string_free(&indents);')
1180 fn_builder.writeln('\treturn res;')
1181 fn_builder.writeln('}')
1182 }
1183 // find `[str: skip]` fields
1184 mut field_skips := []int{}
1185 for i, field in info.fields {
1186 if attr := field.attrs.find_first('str') {
1187 if attr.arg == 'skip' {
1188 field_skips << i
1189 }
1190 }
1191 }
1192 // For @[typedef] C structs, V's declared field type may differ from the
1193 // actual C struct field type (e.g. V declares a field as `voidptr` while
1194 // the C header has it as a value struct like `FT_BBox`). Casting a struct
1195 // to voidptr in the auto-generated str() would be a C compile error, so
1196 // skip voidptr fields entirely for @[typedef] structs — users can write
1197 // their own str() method if they need to print these opaque fields.
1198 if is_c_struct && info.is_typedef {
1199 for i, field in info.fields {
1200 if i in field_skips {
1201 continue
1202 }
1203 if field.typ in ast.cptr_types {
1204 field_skips << i
1205 }
1206 }
1207 }
1208 fn_body.writeln('\tstring res = builtin__str_intp( ${(info.fields.len - field_skips.len) * 4 + 3}, _MOV((StrIntpData[]){')
1209 fn_body.writeln('\t\t{_S("${clean_struct_v_type_name}{\\n"), 0, {0}, 0, 0, 0},')
1210
1211 mut is_first := true
1212 for i, field in info.fields {
1213 // Skip `str:skip` fields
1214 if i in field_skips {
1215 continue
1216 }
1217 ftyp_noshared := if field.typ.has_flag(.shared_f) {
1218 field.typ.deref().clear_flag(.shared_f)
1219 } else {
1220 field.typ
1221 }
1222 mut ptr_amp := if ftyp_noshared.is_ptr() { '&' } else { '' }
1223 mut base_typ := g.unwrap_generic(field.typ)
1224 if base_typ.has_flag(.shared_f) {
1225 base_typ = base_typ.clear_flag(.shared_f).deref()
1226 }
1227 base_fmt := g.type_to_fmt(base_typ)
1228 is_opt_field := field.typ.has_flag(.option)
1229
1230 // manage prefix and quote symbol for the filed
1231 mut quote_str := ''
1232 mut prefix := ''
1233 sym := g.table.sym(g.unwrap_generic(field.typ))
1234 if !is_opt_field {
1235 if sym.kind == .string {
1236 quote_str = "'"
1237 } else if field.typ in ast.charptr_types {
1238 quote_str = '\\"'
1239 prefix = 'C'
1240 }
1241 }
1242
1243 if is_first {
1244 // first field doesn't need \n
1245 fn_body.write_string('\t\t{_SLIT0, ${si_s_code}, {.d_s=indents}, 0, 0, 0}, {_S(" ${field.name}: ${ptr_amp}${prefix}"), 0, {0}, 0, 0, 0}, ')
1246 is_first = false
1247 } else {
1248 fn_body.write_string('\t\t{_S("\\n"), ${si_s_code}, {.d_s=indents}, 0, 0, 0}, {_S(" ${field.name}: ${ptr_amp}${prefix}"), 0, {0}, 0, 0, 0}, ')
1249 }
1250
1251 // custom methods management
1252 sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info()
1253 sftyp := g.styp(ftyp_noshared)
1254 mut field_styp := sftyp.replace('*', '')
1255 field_styp_fn_name := if sym_has_str_method {
1256 mut field_fn_name := if ftyp_noshared.has_flag(.option) {
1257 g.get_str_fn(ftyp_noshared)
1258 } else {
1259 left_cc_type := g.cc_type(ftyp_noshared, false)
1260 left_fn_name := util.no_dots(left_cc_type)
1261 '${left_fn_name}_str'
1262 }
1263 if !g.pref.new_generic_solver {
1264 if str_method := sym.find_method_with_generic_parent('str') {
1265 if method_has_generic_source(str_method) && !ftyp_noshared.has_flag(.option) {
1266 match sym.info {
1267 ast.Struct, ast.SumType, ast.Interface, ast.Alias, ast.GenericInst,
1268 ast.FnType {
1269 field_fn_name = g.generic_fn_name(g.str_method_concrete_types(ftyp_noshared, sym),
1270 field_fn_name)
1271 }
1272 else {}
1273 }
1274 }
1275 }
1276 }
1277 if sym.is_builtin() && !field_fn_name.starts_with('builtin__') {
1278 field_fn_name = 'builtin__${field_fn_name}'
1279 }
1280 field_fn_name
1281 } else {
1282 g.get_str_fn(ftyp_noshared)
1283 }
1284 // with floats we use always the g representation:
1285 if is_opt_field {
1286 fn_body.write_string('{_S("${quote_str}"), ${si_s_code}, {.d_s=')
1287 } else if sym.kind !in [.f32, .f64] {
1288 fn_body.write_string('{_S("${quote_str}"), ${int(base_fmt)}, {.${data_str(base_fmt)}=')
1289 } else {
1290 g_fmt := '0x' + (u32(base_fmt) | u32(0x7F) << 9).hex()
1291 fn_body.write_string('{_S("${quote_str}"), ${g_fmt}, {.${data_str(base_fmt)}=')
1292 }
1293
1294 mut funcprefix := ''
1295 mut func, mut caller_should_free := struct_auto_str_func(sym, lang, field.typ,
1296 field_styp_fn_name, field.name, sym_has_str_method, str_method_expects_ptr)
1297 ftyp_nr_muls := field.typ.nr_muls()
1298 field_name := if lang == .c { field.name } else { c_name(field.name) }
1299 op := if is_c_struct { '->' } else { '.' }
1300 it_field_name := 'it${op}${field_name}'
1301 if ftyp_nr_muls > 1 || field.typ in ast.cptr_types {
1302 if is_opt_field {
1303 } else {
1304 func = '(voidptr) ${it_field_name}'
1305 caller_should_free = false
1306 }
1307 } else if ftyp_noshared.is_ptr() {
1308 if ftyp_noshared.has_flag(.option) {
1309 // optional pointer types: let the option auto-str function handle the none case
1310 } else {
1311 // reference types can be "nil"
1312 funcprefix += 'builtin__isnil(${it_field_name})'
1313 funcprefix += ' ? _S("nil") : '
1314 // struct, floats and ints have a special case through the _str function
1315 if sym.kind !in [.struct, .alias, .enum, .sum_type, .map, .interface, .bool]
1316 && !field.typ.is_int_valptr() && !field.typ.is_float_valptr() {
1317 funcprefix += '*'
1318 }
1319 }
1320 }
1321 mut is_field_array := false
1322 if sym.info is ast.Array {
1323 field_styp = g.styp(sym.info.elem_type).trim('*')
1324 is_field_array = true
1325 } else if sym.info is ast.ArrayFixed {
1326 field_styp = g.styp(sym.info.elem_type).trim('*')
1327 is_field_array = true
1328 }
1329 // handle circular ref type of struct to the struct itself
1330 if styp == field_styp && !allow_circular {
1331 if is_field_array {
1332 tmpvar := g.new_tmp_var()
1333 if is_opt_field {
1334 arr_styp := g.base_type(field.typ)
1335 fn_body_surrounder.add('\tstring ${tmpvar} = ${funcprefix}builtin__autostr_array_circular(${it_field_name}.state != 2 ? (*(${arr_styp}*)${it_field_name}.data).len : 0);',
1336 '\tbuiltin__string_free(&${tmpvar});')
1337 } else {
1338 fn_body_surrounder.add('\tstring ${tmpvar} = ${funcprefix}builtin__autostr_array_circular(${it_field_name}.len);',
1339 '\tbuiltin__string_free(&${tmpvar});')
1340 }
1341 fn_body.write_string(tmpvar)
1342 } else {
1343 fn_body.write_string('${funcprefix}_S("<circular>")')
1344 }
1345 } else {
1346 // manage C charptr
1347 if field.typ in ast.charptr_types {
1348 fn_body.write_string('builtin__tos4((byteptr)${func})')
1349 } else {
1350 is_ptr_field := field.typ.is_ptr() && sym.kind in [.struct, .interface]
1351 is_opt_ptr_field := field.typ.has_flag(.option) && field.typ.is_ptr()
1352 && sym.kind in [.struct, .interface]
1353 if is_ptr_field && !field.typ.has_flag(.option) {
1354 // Use address-based circular reference detection for pointer fields.
1355 // This correctly detects actual circular references (same instance)
1356 // without false positives from different instances of the same type.
1357 tmpvar := g.new_tmp_var()
1358 mut before := '\tstring ${tmpvar};\n'
1359 before += '\tif (builtin__isnil((voidptr)${it_field_name}) || builtin__autostr_addr_in_stack((voidptr)${it_field_name})) {\n'
1360 before += '\t\t${tmpvar} = ${funcprefix}builtin__isnil((voidptr)${it_field_name}) ? _S("nil") : _S("<circular>");\n'
1361 before += '\t} else {\n'
1362 before += '\t\tbuiltin__autostr_addr_push((voidptr)${it_field_name});\n'
1363 before += '\t\t${tmpvar} = ${funcprefix}${func};\n'
1364 before += '\t\tbuiltin__autostr_addr_pop();\n'
1365 before += '\t}'
1366 mut after := ''
1367 if caller_should_free {
1368 after = '\tbuiltin__string_free(&${tmpvar});'
1369 }
1370 fn_body_surrounder.add(before, after)
1371 fn_body.write_string(tmpvar)
1372 } else if is_opt_ptr_field {
1373 // Use address-based circular reference detection for option pointer fields (?&Struct).
1374 // Extract the pointer from the option struct's data field.
1375 tmpvar := g.new_tmp_var()
1376 mut before := '\tstring ${tmpvar};\n'
1377 before += '\tif (${it_field_name}.state != 0) {\n'
1378 before += '\t\t${tmpvar} = ${funcprefix}_S("Option(none)");\n'
1379 before += '\t} else {\n'
1380 before += '\t\tvoidptr ${tmpvar}_addr = *(voidptr*)${it_field_name}.data;\n'
1381 before += '\t\tif (builtin__isnil(${tmpvar}_addr) || builtin__autostr_addr_in_stack(${tmpvar}_addr)) {\n'
1382 before += '\t\t\t${tmpvar} = ${funcprefix}builtin__isnil(${tmpvar}_addr) ? _S("Option(nil)") : _S("<circular>");\n'
1383 before += '\t\t} else {\n'
1384 before += '\t\t\tbuiltin__autostr_addr_push(${tmpvar}_addr);\n'
1385 before += '\t\t\t${tmpvar} = ${funcprefix}${func};\n'
1386 before += '\t\t\tbuiltin__autostr_addr_pop();\n'
1387 before += '\t\t}\n'
1388 before += '\t}'
1389 mut after := ''
1390 if caller_should_free {
1391 after = '\tbuiltin__string_free(&${tmpvar});'
1392 }
1393 fn_body_surrounder.add(before, after)
1394 fn_body.write_string(tmpvar)
1395 } else if caller_should_free {
1396 tmpvar := g.new_tmp_var()
1397 fn_body_surrounder.add('\tstring ${tmpvar} = ${funcprefix}${func};',
1398 '\tbuiltin__string_free(&${tmpvar});')
1399 fn_body.write_string(tmpvar)
1400 } else {
1401 fn_body.write_string2(funcprefix, func)
1402 }
1403 }
1404 }
1405
1406 fn_body.writeln('}, 0, 0, 0}, {_S("${quote_str}"), 0, {0}, 0, 0, 0},')
1407 }
1408 fn_body.writeln('\t\t{_S("\\n"), ${si_s_code}, {.d_s=indents}, 0, 0, 0}, {_S("}"), 0, {0}, 0, 0, 0},')
1409 fn_body.writeln('\t}));')
1410}
1411
1412// c_struct_ptr handles the C struct argument for .str() method
1413fn c_struct_ptr(sym &ast.TypeSymbol, typ ast.Type, expects_ptr bool) string {
1414 if sym.is_c_struct() {
1415 if typ.has_flag(.option) {
1416 return ''
1417 }
1418 if typ.nr_muls() >= 1 {
1419 if expects_ptr {
1420 return '*'.repeat(typ.nr_muls() - 1)
1421 } else {
1422 return '*'.repeat(typ.nr_muls())
1423 }
1424 }
1425 return if expects_ptr { '&' } else { '' }
1426 }
1427 return ''
1428}
1429
1430fn struct_auto_str_func(sym &ast.TypeSymbol, lang ast.Language, _field_type ast.Type, fn_name string, field_name string,
1431 has_custom_str bool, expects_ptr bool) (string, bool) {
1432 $if trace_autostr ? {
1433 eprintln('> struct_auto_str_func: ${sym.name} | field_type.debug() | ${fn_name} | ${field_name} | ${has_custom_str} | ${expects_ptr}')
1434 }
1435 field_type := if _field_type.has_flag(.shared_f) { _field_type.deref() } else { _field_type }
1436 sufix := if field_type.has_flag(.shared_f) { '->val' } else { '' }
1437 deref, _ := deref_kind(expects_ptr, field_type.is_ptr(), field_type)
1438 final_field_name := if lang == .c { field_name } else { c_name(field_name) }
1439 op := if lang == .c { '->' } else { '.' }
1440 prefix := if sym.is_c_struct() { c_struct_ptr(sym, _field_type, expects_ptr) } else { deref }
1441 if sym.kind == .enum {
1442 return '${fn_name}(${deref}(it${op}${final_field_name}))', true
1443 } else if _field_type.has_flag(.option) || should_use_indent_func(sym.kind) {
1444 obj := '${prefix}it${op}${final_field_name}${sufix}'
1445 if has_custom_str {
1446 if sym.kind == .interface && (sym.info as ast.Interface).defines_method('str') {
1447 iface_obj := '${prefix}it${op}${final_field_name}${sufix}'
1448 dot := if field_type.is_ptr() { '->' } else { '.' }
1449 return '${fn_name.trim_string_right('_str')}_name_table[${iface_obj}${dot}_typ]._method_str(${iface_obj}${dot}_object)', true
1450 }
1451 return '${fn_name}(${obj})', true
1452 }
1453 if sym.kind == .struct {
1454 if sym.info is ast.Struct && sym.info.is_anon && !_field_type.has_flag(.option)
1455 && !_field_type.has_flag(.shared_f) {
1456 typed_obj := if lang == .c {
1457 '(${sym.cname}*)&(${obj})'
1458 } else {
1459 '*(${sym.cname}*)&(${obj})'
1460 }
1461 return '${fn_name}(${typed_obj})', true
1462 }
1463 }
1464 return 'indent_${fn_name}(${obj}, indent_count + 1)', true
1465 } else if sym.kind in [.array, .array_fixed, .map, .sum_type] {
1466 obj := '${prefix}it${op}${final_field_name}${sufix}'
1467 if has_custom_str {
1468 return '${fn_name}(${obj})', true
1469 }
1470 return 'indent_${fn_name}(${obj}, indent_count + 1)', true
1471 } else if sym.kind == .function {
1472 if has_custom_str {
1473 return '${fn_name}(${prefix}it${op}${final_field_name}${sufix})', true
1474 }
1475 return '${fn_name}()', true
1476 } else if sym.kind == .chan {
1477 return '${fn_name}(${deref}it${op}${final_field_name}${sufix})', true
1478 } else if sym.kind == .thread {
1479 return '${fn_name}(${deref}it${op}${final_field_name}${sufix})', false
1480 } else {
1481 mut method_str := ''
1482 if !field_type.is_ptr() && field_type.has_option_or_result() {
1483 method_str = '(*(${sym.name}*)it${op}${final_field_name}.data)'
1484 } else {
1485 method_str = 'it${op}${final_field_name}${sufix}'
1486 }
1487 if sym.kind == .bool {
1488 deref_str := if field_type.is_ptr() { '*${method_str}' } else { method_str }
1489 return '(${deref_str} ? _S("true") : _S("false"))', false
1490 } else if (field_type.is_int_valptr() || field_type.is_float_valptr()) && !expects_ptr {
1491 // ptr int can be "nil", so this needs to be casted to a string
1492 if sym.kind == .f32 {
1493 return 'builtin__str_intp(1, _MOV((StrIntpData[]){
1494 {_SLIT0, ${si_g32_code}, {.d_f32 = *${method_str} }, 0, 0, 0}
1495 }))', true
1496 } else if sym.kind == .f64 {
1497 return 'builtin__str_intp(1, _MOV((StrIntpData[]){
1498 {_SLIT0, ${si_g64_code}, {.d_f64 = *${method_str} }, 0, 0, 0}
1499 }))', true
1500 } else if sym.kind in [.u64, .usize] {
1501 fmt_type := StrIntpType.si_u64
1502 return 'builtin__str_intp(1, _MOV((StrIntpData[]){{_SLIT0, ${u32(fmt_type) | 0xfe00}, {.d_u64 = *${method_str} }, 0, 0, 0}}))', true
1503 } else if sym.kind in [.i64, .isize] {
1504 fmt_type := StrIntpType.si_i64
1505 return 'builtin__str_intp(1, _MOV((StrIntpData[]){{_SLIT0, ${u32(fmt_type) | 0xfe00}, {.d_i64 = *${method_str} }, 0, 0, 0}}))', true
1506 }
1507 fmt_type := StrIntpType.si_i32
1508 return 'builtin__str_intp(1, _MOV((StrIntpData[]){{_SLIT0, ${u32(fmt_type) | 0xfe00}, {.d_i32 = *${method_str} }, 0, 0, 0}}))', true
1509 }
1510 return method_str, false
1511 }
1512}
1513
1514fn data_str(x StrIntpType) string {
1515 return match x {
1516 .si_no_str { 'no_str' }
1517 .si_c { 'd_c' }
1518 .si_u8 { 'd_u8' }
1519 .si_i8 { 'd_i8' }
1520 .si_u16 { 'd_u16' }
1521 .si_i16 { 'd_i16' }
1522 .si_u32 { 'd_u32' }
1523 .si_i32 { 'd_i32' }
1524 .si_u64 { 'd_u64' }
1525 .si_i64 { 'd_i64' }
1526 .si_f32 { 'd_f32' }
1527 .si_f64 { 'd_f64' }
1528 .si_g32 { 'd_f32' } // g32 format use f32 data
1529 .si_g64 { 'd_f64' } // g64 format use f64 data
1530 .si_e32 { 'd_f32' } // e32 format use f32 data
1531 .si_e64 { 'd_f64' } // e64 format use f64 data
1532 .si_s { 'd_s' }
1533 .si_r { 'd_r' } // repeat string
1534 .si_p { 'd_p' }
1535 .si_vp { 'd_vp' }
1536 }
1537}
1538
1539fn should_use_indent_func(kind ast.Kind) bool {
1540 return kind in [.struct, .alias, .array, .array_fixed, .map, .sum_type, .interface, .chan]
1541}
1542
1543fn (mut g Gen) get_enum_type_idx_from_fn_name(fn_name string) (string, int) {
1544 enum_name := fn_name.all_before('__static__')
1545 mut mod_enum_name := if !enum_name.contains('.') {
1546 g.cur_mod.name + '.' + enum_name
1547 } else {
1548 enum_name
1549 }
1550 mut idx := g.table.type_idxs[mod_enum_name]
1551 if idx == 0 && (enum_name.contains('.') || enum_name[0].is_capital()) {
1552 // no cur mod, find from another mods.
1553 for import_sym in g.file.imports {
1554 mod_enum_name = '${import_sym.mod}.${enum_name}'
1555 idx = g.table.type_idxs[mod_enum_name]
1556 if idx > 0 {
1557 break
1558 }
1559 }
1560 }
1561 return mod_enum_name, idx
1562}
1563
1564fn (mut g Gen) gen_enum_static_from_string(fn_name string, mod_enum_name string, idx int) {
1565 enum_typ := ast.idx_to_type(idx)
1566 enum_styp := g.styp(enum_typ)
1567 option_enum_typ := enum_typ.set_flag(.option)
1568 option_enum_styp := g.styp(option_enum_typ)
1569 enum_field_names := g.table.get_enum_field_names(mod_enum_name)
1570 enum_field_vals := g.table.get_enum_field_vals(mod_enum_name)
1571
1572 mut fn_builder := strings.new_builder(512)
1573 g.definitions.writeln('${g.static_non_parallel}${option_enum_styp} ${fn_name}(string name);')
1574
1575 fn_builder.writeln('${g.static_non_parallel}${option_enum_styp} ${fn_name}(string name) {')
1576 fn_builder.writeln('\t${option_enum_styp} t1;')
1577 fn_builder.writeln('\tbool exists = false;')
1578 fn_builder.writeln('\tint inx = 0;')
1579 fn_builder.writeln('\tarray field_names = ((array){.data = 0, .offset = 0, .len = 0, .cap = 0, .flags = 0, .element_size = sizeof(string)});')
1580 for field_name in enum_field_names {
1581 fn_builder.writeln('\tbuiltin__array_push((array*)&field_names, _MOV((string[]){ _S("${field_name}") }));')
1582 }
1583 fn_builder.writeln('\tarray field_vals = ((array){.data = 0, .offset = 0, .len = 0, .cap = 0, .flags = 0, .element_size = sizeof(i64)});')
1584 for field_val in enum_field_vals {
1585 fn_builder.writeln('\tbuiltin__array_push((array*)&field_vals, _MOV((i64[]){ ${field_val} }));')
1586 }
1587 fn_builder.writeln('\tfor (${ast.int_type_name} i = 0; i < ${enum_field_names.len}; ++i) {')
1588 fn_builder.writeln('\t\tif (builtin__fast_string_eq(name, (*(string*)builtin__array_get(field_names, i)))) {')
1589 fn_builder.writeln('\t\t\texists = true;')
1590 fn_builder.writeln('\t\t\tinx = i;')
1591 fn_builder.writeln('\t\t\tbreak;')
1592 fn_builder.writeln('\t\t}')
1593 fn_builder.writeln('\t}')
1594 fn_builder.writeln('\tif (exists) {')
1595 fn_builder.writeln('\t\tbuiltin___option_ok(&(${enum_styp}[]){ (*(i64*)builtin__array_get(field_vals, inx)) }, (_option*)&t1, sizeof(${enum_styp}));')
1596 fn_builder.writeln('\t\treturn t1;')
1597 fn_builder.writeln('\t} else {')
1598 fn_builder.writeln('\t\treturn (${option_enum_styp}){ .state=2, .err=_const_none__, .data={E_STRUCT} };')
1599 fn_builder.writeln('\t}')
1600 fn_builder.writeln('}')
1601 g.auto_fn_definitions << fn_builder.str()
1602}
1603