vxx2 / vlib / v / gen / c / str_intp.v
977 lines · 947 sloc · 27.47 KB · b8f98b7ac706f9b2aaf0dea306feb81297759cf4
Raw
1/*
2str_intp.v
3
4Copyright (c) 2019-2024 Dario Deledda. All rights reserved.
5Use of this source code is governed by an MIT license
6that can be found in the LICENSE file.
7
8This file contains string interpolation V functions
9*/
10module c
11
12import v.ast
13import v.util
14
15fn (mut g Gen) is_type_name_string_expr(expr ast.Expr) bool {
16 return match expr {
17 ast.SelectorExpr {
18 expr.field_name == 'name' && (expr.expr is ast.TypeOf || expr.name_type != 0)
19 }
20 ast.CallExpr {
21 if expr.is_method && expr.name == 'type_name' && expr.args.len == 0 {
22 if func := g.table.find_method(g.table.sym(g.unwrap_generic(expr.left_type)),
23 expr.name)
24 {
25 func.return_type == ast.string_type
26 } else {
27 g.table.final_sym(g.unwrap_generic(expr.left_type)).kind == .sum_type
28 }
29 } else {
30 false
31 }
32 }
33 ast.ParExpr {
34 g.is_type_name_string_expr(expr.expr)
35 }
36 else {
37 false
38 }
39 }
40}
41
42fn (g Gen) is_or_block_var_unwrapped(obj ast.Var) bool {
43 init_expr := obj.expr
44 return match init_expr {
45 ast.CallExpr { init_expr.or_block.kind != .absent }
46 ast.Ident { init_expr.or_expr.kind != .absent }
47 ast.IndexExpr { init_expr.or_expr.kind != .absent }
48 ast.SelectorExpr { init_expr.or_block.kind != .absent }
49 ast.PrefixExpr { init_expr.or_block.kind != .absent }
50 else { false }
51 }
52}
53
54fn (g Gen) should_clear_option_flag(expr ast.Expr) bool {
55 ident := match expr {
56 ast.Ident { expr }
57 else { return false }
58 }
59
60 match ident.obj {
61 ast.Var {
62 if ident.obj.is_unwrapped {
63 return true
64 }
65 if g.is_or_block_var_unwrapped(ident.obj) {
66 return true
67 }
68 if !ident.obj.typ.has_flag(.option) && ident.obj.ct_type_var == .no_comptime {
69 return true
70 }
71 }
72 else {}
73 }
74
75 return false
76}
77
78fn (g &Gen) int_ref_interpolates_as_value(expr ast.Expr, typ ast.Type, fmt u8) bool {
79 if fmt == `p` || !(typ.is_int_valptr() || typ.is_float_valptr()) {
80 return false
81 }
82 if g.expr_is_auto_deref_var(expr) {
83 return true
84 }
85 match expr {
86 ast.Ident {
87 obj := expr.obj
88 match obj {
89 ast.Var {
90 if obj.is_arg || obj.expr is ast.AsCast {
91 return true
92 }
93 if obj.expr is ast.PrefixExpr {
94 return obj.expr.op == .amp
95 }
96 }
97 else {}
98 }
99 }
100 ast.PrefixExpr {
101 return expr.op == .amp
102 }
103 else {}
104 }
105
106 return false
107}
108
109fn (mut g Gen) should_resolve_str_intp_expr_type(expr ast.Expr, typ ast.Type) bool {
110 if typ == 0 || typ.has_flag(.generic) || g.type_has_unresolved_generic_parts(typ) {
111 return true
112 }
113 // In generic contexts, always resolve expression types since AST types
114 // may be stale from a previous checker instantiation
115 if g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 {
116 return true
117 }
118 return match expr {
119 ast.CallExpr, ast.ComptimeSelector, ast.Ident, ast.IndexExpr, ast.InfixExpr {
120 true
121 }
122 ast.SelectorExpr {
123 expr.expr is ast.TypeOf
124 }
125 else {
126 false
127 }
128 }
129}
130
131fn (mut g Gen) resolved_if_guard_ident_str_intp_type(expr ast.Ident) ast.Type {
132 if g.cur_fn == unsafe { nil } || g.cur_concrete_types.len == 0 {
133 return 0
134 }
135 mut if_guard := ast.IfGuardExpr{}
136 mut found_guard := false
137 if expr.obj is ast.Var {
138 if expr.obj.smartcasts.len > 0 || expr.obj.ct_type_var == .smartcast {
139 return 0
140 }
141 if expr.obj.expr is ast.IfGuardExpr {
142 if_guard = expr.obj.expr as ast.IfGuardExpr
143 found_guard = true
144 }
145 }
146 if !found_guard {
147 mut scope := if expr.scope != unsafe { nil } {
148 expr.scope.innermost(expr.pos.pos)
149 } else {
150 expr.scope
151 }
152 if scope == unsafe { nil } || scope.find_var(expr.name) == none {
153 scope = if g.file.scope != unsafe { nil } {
154 g.file.scope.innermost(expr.pos.pos)
155 } else {
156 expr.scope
157 }
158 }
159 if scope != unsafe { nil } {
160 if v := scope.find_var(expr.name) {
161 if v.smartcasts.len > 0 || v.ct_type_var == .smartcast {
162 return 0
163 }
164 if v.expr is ast.IfGuardExpr {
165 if_guard = v.expr as ast.IfGuardExpr
166 found_guard = true
167 }
168 }
169 }
170 }
171 if !found_guard {
172 return 0
173 }
174 if if_guard.vars.len > 1 {
175 return 0
176 }
177 mut guard_var_idx := -1
178 for i, guard_var in if_guard.vars {
179 if guard_var.name == expr.name {
180 guard_var_idx = i
181 break
182 }
183 }
184 if guard_var_idx < 0 {
185 return 0
186 }
187 mut guard_expr_type := g.resolved_expr_type(if_guard.expr, if_guard.expr_type)
188 if guard_expr_type == 0 || guard_expr_type == ast.void_type {
189 guard_expr_type = if_guard.expr_type
190 }
191 if guard_expr_type == 0 || guard_expr_type == ast.void_type {
192 return 0
193 }
194 guard_value_type :=
195 g.unwrap_generic(g.recheck_concrete_type(guard_expr_type)).clear_option_and_result()
196 return guard_value_type
197}
198
199fn (mut g Gen) get_default_fmt(ftyp ast.Type, typ ast.Type) u8 {
200 if ftyp.has_option_or_result() {
201 return `s`
202 } else if typ.is_float() {
203 return `g`
204 } else if typ.is_signed() || typ.is_int_literal() {
205 return `d`
206 } else if typ.is_unsigned() {
207 return `u`
208 } else if typ.is_pointer() {
209 return `p`
210 } else {
211 mut sym := g.table.sym(g.unwrap_generic(ftyp))
212 if sym.kind == .alias {
213 // string aliases should be printable
214 info := sym.info as ast.Alias
215 sym = g.table.sym(info.parent_type)
216 if info.parent_type == ast.string_type {
217 return `s`
218 }
219 }
220 if sym.kind == .function {
221 return `s`
222 }
223 if ftyp in [ast.string_type, ast.bool_type]
224 || sym.kind in [.enum, .array, .array_fixed, .struct, .generic_inst, .map, .multi_return, .sum_type, .interface, .none]
225 || ftyp.has_option_or_result() || sym.has_method('str') {
226 return `s`
227 } else {
228 return `_`
229 }
230 }
231}
232
233fn (mut g Gen) str_format(node ast.StringInterLiteral, i int, fmts []u8) (u64, string) {
234 mut base := 0 // numeric base
235 mut upper_case := false // set uppercase for the result string
236 expr := node.exprs[i]
237 mut typ := if i < node.expr_types.len {
238 g.unwrap_generic(node.expr_types[i])
239 } else {
240 ast.string_type
241 }
242 if g.is_type_name_string_expr(expr) {
243 typ = ast.string_type
244 } else if expr is ast.Ident {
245 if g.resolved_ident_is_by_value_auto_deref_capture(expr) {
246 resolved_scope_type := g.resolved_scope_var_type(expr)
247 if resolved_scope_type != 0 {
248 typ = g.unwrap_generic(resolved_scope_type)
249 }
250 }
251 if expr.obj is ast.Var {
252 if expr.obj.smartcasts.len > 0 {
253 if expr.obj.orig_type != 0 && g.table.sym(expr.obj.orig_type).kind == .interface
254 && i < node.expr_types.len && node.expr_types[i] != ast.void_type {
255 typ = g.unwrap_generic(node.expr_types[i])
256 } else {
257 typ = g.unwrap_generic(expr.obj.smartcasts.last())
258 cast_sym := *g.table.sym(typ)
259 smartcast_variant_typ := cast_sym.aggregate_variant_type(g.aggregate_type_idx)
260 if smartcast_variant_typ != 0 {
261 typ = smartcast_variant_typ
262 } else if expr.obj.ct_type_var == .smartcast {
263 typ = g.unwrap_generic(g.type_resolver.get_type(expr))
264 }
265 }
266 } else if expr.obj.ct_type_var == .smartcast {
267 resolved_typ := g.unwrap_generic(g.type_resolver.get_type(expr))
268 if resolved_typ != ast.void_type {
269 typ = resolved_typ
270 }
271 }
272 }
273 }
274 if g.expr_is_auto_deref_var(node.exprs[i]) && typ.nr_muls() > 0 {
275 typ = typ.deref()
276 }
277 if g.int_ref_interpolates_as_value(expr, typ, fmts[i]) && typ.is_ptr() {
278 typ = typ.deref()
279 }
280 typ = g.table.final_type(typ)
281 if typ.has_flag(.shared_f) && typ.is_ptr() {
282 typ = typ.clear_flag(.shared_f).deref()
283 }
284 mut remove_tail_zeros := false
285 fspec := fmts[i]
286 mut fmt_type := StrIntpType.si_no_str
287 // upper cases
288 if (fspec - `A`) <= (`Z` - `A`) {
289 upper_case = true
290 }
291
292 if fspec in [`s`, `S`] {
293 /*
294 if node.fwidths[i] == 0 {
295 fmt_type = .si_s
296 } else {
297 fmt_type = .si_s
298 }
299 */
300 fmt_type = .si_s
301 } else if fspec in [`r`, `R`] {
302 fmt_type = .si_r
303 } else if typ.is_float() {
304 if fspec in [`g`, `G`] {
305 match typ {
306 ast.f32_type { fmt_type = .si_g32 }
307 // ast.f64_type { fmt_type = .si_g64 }
308 else { fmt_type = .si_g64 }
309 }
310
311 remove_tail_zeros = true
312 } else if fspec in [`e`, `E`] {
313 match typ {
314 ast.f32_type { fmt_type = .si_e32 }
315 // ast.f64_type { fmt_type = .si_e64 }
316 else { fmt_type = .si_e64 }
317 }
318 } else if fspec in [`f`, `F`] {
319 match typ {
320 ast.f32_type { fmt_type = .si_f32 }
321 // ast.f64_type { fmt_type = .si_f64 }
322 else { fmt_type = .si_f64 }
323 }
324 }
325 } else if typ.is_pointer() {
326 if fspec in [`x`, `X`] {
327 base = 16 - 2 // our base start from 2
328 }
329 if fspec in [`p`, `x`, `X`] {
330 fmt_type = .si_p
331 } else {
332 fmt_type = .si_vp
333 }
334 } else if typ.is_int() {
335 if fspec in [`x`, `X`] {
336 base = 16 - 2 // our base start from 2
337 }
338 // if fspec in [`o`] {
339 if fspec == `o` {
340 base = 8 - 2 // our base start from 2
341 }
342 // binary format
343 if fspec == `b` {
344 base = 1 // our base start from 2 we use 1 for binary
345 }
346 if fspec == `c` {
347 fmt_type = .si_c
348 } else {
349 match typ {
350 ast.i8_type {
351 fmt_type = .si_i8
352 }
353 ast.u8_type {
354 fmt_type = .si_u8
355 }
356 ast.i16_type {
357 fmt_type = .si_i16
358 }
359 ast.u16_type {
360 fmt_type = .si_u16
361 }
362 ast.i64_type {
363 fmt_type = .si_i64
364 }
365 ast.u64_type {
366 fmt_type = .si_u64
367 }
368 ast.i32_type {
369 fmt_type = .si_i32
370 }
371 ast.u32_type {
372 fmt_type = .si_u32
373 }
374 ast.int_type {
375 $if new_int ? && x64 {
376 fmt_type = .si_i64
377 } $else {
378 fmt_type = .si_i32
379 }
380 }
381 ast.usize_type {
382 fmt_type = .si_u64
383 }
384 ast.isize_type {
385 fmt_type = .si_i64
386 }
387 else {
388 fmt_type = .si_i32
389 }
390 }
391 }
392 } else {
393 // TODO: better check this case
394 fmt_type = .si_p
395 }
396
397 /*
398 // pad filling 64bit format
399 mut pad_ch := u8(0)
400 if node.fills[i] {
401 pad_ch = u8(`0`)
402 }
403 res := get_str_intp_u64_format(fmt_type, node.fwidths[i], node.precisions[i], remove_tail_zeros, node.pluss[i], pad_ch, base, upper_case)
404 */
405
406 // pad filling 32bit format
407 mut pad_ch := 0
408 if node.fills[i] {
409 pad_ch = 1
410 }
411 static_width := if i < node.fwidth_exprs.len && node.fwidth_exprs[i] !is ast.EmptyExpr {
412 0
413 } else {
414 node.fwidths[i]
415 }
416 static_precision := if i < node.precision_exprs.len && node.precision_exprs[i] !is ast.EmptyExpr {
417 987698
418 } else {
419 node.precisions[i]
420 }
421 res := get_str_intp_u32_format(fmt_type, static_width, static_precision, remove_tail_zeros,
422 node.pluss[i], u8(pad_ch), base, upper_case)
423
424 return res, fmt_type.str()
425}
426
427fn (mut g Gen) str_val(node ast.StringInterLiteral, i int, fmts []u8) {
428 expr := node.exprs[i]
429 fmt := fmts[i]
430 mut orig_typ := if i < node.expr_types.len {
431 g.unwrap_generic(node.expr_types[i])
432 } else {
433 ast.string_type
434 }
435 resolved_if_guard_typ := if expr is ast.Ident {
436 g.resolved_if_guard_ident_str_intp_type(expr)
437 } else {
438 ast.Type(0)
439 }
440 if resolved_if_guard_typ != 0 {
441 orig_typ = resolved_if_guard_typ
442 } else if g.should_resolve_str_intp_expr_type(expr, orig_typ) {
443 resolved_expr_typ := g.resolved_expr_type(expr, orig_typ)
444 if resolved_expr_typ != 0 {
445 orig_typ = g.unwrap_generic(g.recheck_concrete_type(resolved_expr_typ))
446 }
447 }
448 // Resolve aggregate types (from multi-branch match arms) to the
449 // concrete variant type for the current iteration.
450 orig_typ_sym := *g.table.sym(orig_typ)
451 orig_variant_typ := orig_typ_sym.aggregate_variant_type(g.aggregate_type_idx)
452 if orig_variant_typ != 0 {
453 orig_typ = orig_variant_typ
454 }
455 is_int_valptr := g.int_ref_interpolates_as_value(expr, orig_typ, fmt)
456 typ := if is_int_valptr { orig_typ.deref() } else { orig_typ }
457 typ_sym := g.table.sym(typ)
458 if g.is_type_name_string_expr(expr) {
459 g.expr(expr)
460 return
461 }
462 if typ == ast.string_type && g.comptime.comptime_for_method == unsafe { nil } {
463 if g.inside_veb_tmpl {
464 g.write('${g.veb_filter_fn_name}(')
465 if g.expr_is_auto_deref_var(expr) && fmt != `p` {
466 g.write('*')
467 }
468 g.expr(expr)
469 g.write(')')
470 } else {
471 if g.is_autofree_tmp && g.is_autofree
472 && expr !in [ast.Ident, ast.StringLiteral, ast.SelectorExpr, ast.ComptimeSelector] {
473 if expr is ast.CallExpr {
474 old_is_autofree_tmp := g.is_autofree_tmp
475 g.autofree_call_pregen(expr)
476 g.is_autofree_tmp = old_is_autofree_tmp
477 }
478 tmp := g.new_tmp_var()
479 tmp_pos := expr.pos()
480 mut scope := g.file.scope.innermost(tmp_pos.pos)
481 scope.register(ast.Var{
482 name: tmp
483 typ: ast.string_type
484 is_autofree_tmp: true
485 pos: tmp_pos
486 })
487 pos_before := g.out.len
488 if g.expr_is_auto_deref_var(expr) && fmt != `p` {
489 g.write('*')
490 }
491 g.expr(expr)
492 expr_code := g.out.cut_to(pos_before).trim_space()
493 g.strs_to_free0 << 'string ${tmp} = ${expr_code};'
494 g.write(tmp)
495 return
496 }
497 if g.expr_is_auto_deref_var(expr) && fmt != `p` {
498 g.write('*')
499 }
500 g.expr(expr)
501 }
502 } else if !typ.has_option_or_result() && typ_sym.kind == .interface
503 && (typ_sym.info as ast.Interface).defines_method('str') {
504 rec_type_name := util.no_dots(g.cc_type(typ, false))
505 g.write('${c_name(rec_type_name)}_name_table[')
506 g.expr(expr)
507 dot := if typ.is_ptr() { '->' } else { '.' }
508 g.write('${dot}_typ]._method_str(')
509 g.expr(expr)
510 g.write2('${dot}_object', ')')
511 } else if fmt == `s` || typ.has_flag(.variadic) {
512 mut exp_typ := orig_typ
513 is_comptime_for_var := expr is ast.Ident && g.is_comptime_for_var(expr)
514 if !is_comptime_for_var && expr is ast.Ident {
515 if g.comptime.get_ct_type_var(expr) == .smartcast {
516 exp_typ = g.type_resolver.get_type(expr)
517 } else if expr.obj is ast.Var {
518 if expr.obj.smartcasts.len > 0 {
519 exp_typ = g.unwrap_generic(expr.obj.smartcasts.last())
520 cast_sym := *g.table.sym(exp_typ)
521 exp_variant_typ := cast_sym.aggregate_variant_type(g.aggregate_type_idx)
522 if exp_variant_typ != 0 {
523 exp_typ = exp_variant_typ
524 }
525 if exp_typ.has_flag(.option) && expr.obj.is_unwrapped {
526 exp_typ = exp_typ.clear_flag(.option)
527 }
528 } else if expr.obj.is_unwrapped && exp_typ.has_flag(.option) {
529 exp_typ = exp_typ.clear_flag(.option)
530 }
531 }
532 }
533 if exp_typ.has_flag(.option) && expr is ast.Ident && g.is_comptime_for_var(expr) {
534 str_fn_name := g.get_str_fn(exp_typ.clear_flag(.option))
535 g.write('${str_fn_name}(*(${g.base_type(exp_typ)}*)(')
536 old_inside_opt_or_res := g.inside_opt_or_res
537 g.inside_opt_or_res = true
538 g.expr(expr)
539 g.inside_opt_or_res = old_inside_opt_or_res
540 g.write('.data))')
541 } else {
542 if g.gen_windows_liveshared_string_tmp(expr, exp_typ) {
543 return
544 }
545 g.gen_expr_to_string(expr, exp_typ)
546 }
547 } else if typ.is_number() || typ.is_pointer() || fmt == `d` {
548 if typ.is_signed() && fmt in [`x`, `X`, `o`] {
549 // convert to unsigned first befors C's integer propagation strikes
550 if typ == ast.i8_type {
551 g.write('(byte)(')
552 } else if typ == ast.i16_type {
553 g.write('(u16)(')
554 } else if typ == ast.i32_type {
555 g.write('(u32)(')
556 } else if typ == ast.int_type {
557 $if new_int ? && x64 {
558 g.write('(u64)(')
559 } $else {
560 g.write('(u32)(')
561 }
562 } else {
563 g.write('(u64)(')
564 }
565 if g.expr_is_auto_deref_var(expr) || is_int_valptr {
566 g.write('*')
567 }
568 g.expr(expr)
569 if typ.has_flag(.shared_f) {
570 g.write('->val')
571 }
572 g.write(')')
573 } else {
574 if (g.expr_is_auto_deref_var(expr) || is_int_valptr) && fmt != `p` {
575 g.write('*')
576 }
577 g.expr(expr)
578 if typ.has_flag(.shared_f) {
579 g.write('->val')
580 }
581 }
582 } else {
583 if g.expr_is_auto_deref_var(expr) && fmt != `p` {
584 g.write('*')
585 }
586 g.expr(expr)
587 if typ.has_flag(.shared_f) {
588 g.write('->val')
589 }
590 }
591}
592
593fn (mut g Gen) string_inter_literal(node ast.StringInterLiteral) {
594 inside_interface_deref_old := g.inside_interface_deref
595 g.inside_interface_deref = true
596 defer {
597 g.inside_interface_deref = inside_interface_deref_old
598 }
599 mut node_ := unsafe { node }
600 mut fmts := node_.fmts.clone()
601 for i, mut expr in node_.exprs {
602 has_explicit_fmt := i < node_.has_fmts.len && node_.has_fmts[i]
603 if g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 && !has_explicit_fmt
604 && !node_.need_fmts[i] && fmts[i] != `_` {
605 fmts[i] = `_`
606 }
607 mut resolved_if_guard_typ := ast.Type(0)
608 mut field_typ := if g.is_type_name_string_expr(expr) {
609 ast.string_type
610 } else if mut expr is ast.AsCast {
611 expr.typ
612 } else if mut expr is ast.CastExpr {
613 expr.typ
614 } else if mut expr is ast.PrefixExpr && expr.op == .mul && expr.right_type != 0 {
615 expr.right_type.deref()
616 } else if mut expr is ast.Ident && g.is_comptime_for_var(expr) {
617 g.comptime.comptime_for_field_type
618 } else if mut expr is ast.Ident && expr.obj is ast.Var {
619 resolved_if_guard_typ = g.resolved_if_guard_ident_str_intp_type(expr)
620 if resolved_if_guard_typ != 0 {
621 resolved_if_guard_typ
622 } else if expr.obj.smartcasts.len > 0 {
623 if expr.obj.orig_type != 0 && g.table.sym(expr.obj.orig_type).kind == .interface
624 && i < node_.expr_types.len && node_.expr_types[i] != ast.void_type {
625 node_.expr_types[i]
626 } else {
627 mut typ := g.unwrap_generic(expr.obj.smartcasts.last())
628 cast_sym := *g.table.sym(typ)
629 field_variant_typ := cast_sym.aggregate_variant_type(g.aggregate_type_idx)
630 if field_variant_typ != 0 {
631 typ = field_variant_typ
632 } else if expr.obj.ct_type_var == .smartcast {
633 typ = g.unwrap_generic(g.type_resolver.get_type(expr))
634 }
635 typ
636 }
637 } else if expr.obj.ct_type_var == .smartcast {
638 g.unwrap_generic(g.type_resolver.get_type(expr))
639 } else if i < node_.expr_types.len
640 && g.table.final_sym(g.unwrap_generic(expr.obj.typ)).kind in [.interface, .sum_type] {
641 node_.expr_types[i]
642 } else if i < node_.expr_types.len {
643 node_.expr_types[i]
644 } else {
645 g.type_resolver.get_type_or_default(expr, expr.obj.typ)
646 }
647 } else if i < node_.expr_types.len {
648 node_.expr_types[i]
649 } else {
650 ast.void_type
651 }
652 if g.comptime.inside_comptime_for && mut expr is ast.SelectorExpr {
653 if expr.expr is ast.TypeOf && expr.field_name == 'name' {
654 field_typ = ast.string_type
655 }
656 }
657 if g.comptime.is_comptime(expr) || (g.comptime.inside_comptime_for && expr is ast.Ident) {
658 mut ctyp := g.type_resolver.get_type_or_default(expr, field_typ)
659 // In generic contexts, comptime type may be stale from a previous
660 // checker instantiation. Prefer resolved_expr_type when available.
661 if g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 {
662 resolved_ct := g.resolved_expr_type(expr, ctyp)
663 if resolved_ct != ast.void_type && resolved_ct != 0 {
664 ctyp = g.unwrap_generic(g.recheck_concrete_type(resolved_ct))
665 }
666 }
667 if ctyp != ast.void_type {
668 // Clear option flag for variables unwrapped via `or {}` blocks
669 if ctyp.has_flag(.option) && g.should_clear_option_flag(expr) {
670 ctyp = ctyp.clear_flag(.option)
671 }
672 node_.expr_types[i] = ctyp
673 if !has_explicit_fmt && fmts[i] == `_` {
674 ftyp_sym := g.table.sym(ctyp)
675 typ := if ftyp_sym.kind == .alias && !ftyp_sym.has_method('str') {
676 g.table.unalias_num_type(ctyp)
677 } else {
678 ctyp
679 }
680 fmts[i] = g.get_default_fmt(ctyp, typ)
681 }
682 }
683 } else {
684 if resolved_if_guard_typ == 0 && g.should_resolve_str_intp_expr_type(expr, field_typ) {
685 resolved_field_typ := g.resolved_expr_type(expr, field_typ)
686 if resolved_field_typ != ast.void_type {
687 field_typ = g.unwrap_generic(g.recheck_concrete_type(resolved_field_typ))
688 }
689 }
690 // Resolve aggregate types (from multi-branch match arms) to the
691 // concrete variant type for the current iteration.
692 field_sym := *g.table.sym(field_typ)
693 interp_variant_typ := field_sym.aggregate_variant_type(g.aggregate_type_idx)
694 if interp_variant_typ != 0 {
695 field_typ = interp_variant_typ
696 }
697 // Clear option flag for variables unwrapped via `or {}` blocks
698 if field_typ.has_flag(.option) && g.should_clear_option_flag(expr) {
699 field_typ = field_typ.clear_flag(.option)
700 }
701 if i >= node_.expr_types.len {
702 node_.expr_types << field_typ
703 } else {
704 node_.expr_types[i] = field_typ
705 }
706 // Update format specifier if it was auto-determined and the type changed
707 if !has_explicit_fmt && !node_.need_fmts[i] && fmts[i] == `_` {
708 ftyp_sym := g.table.sym(field_typ)
709 new_typ := if ftyp_sym.kind == .alias && !ftyp_sym.has_method('str') {
710 g.table.unalias_num_type(field_typ)
711 } else {
712 field_typ
713 }
714 fmts[i] = g.get_default_fmt(field_typ, new_typ)
715 }
716 }
717 expr_ := expr
718 match expr_ {
719 ast.Ident {
720 if expr_.obj is ast.Var && g.table.is_interface_smartcast(expr_.obj) {
721 expr_var := expr_.obj
722 if field_typ.is_ptr() && !expr_var.orig_type.is_ptr()
723 && g.table.final_sym(expr_var.orig_type).kind == .interface
724 && g.table.final_sym(field_typ).kind != .interface {
725 field_typ = field_typ.deref()
726 node_.expr_types[i] = field_typ
727 if !has_explicit_fmt && !node_.need_fmts[i] {
728 fmts[i] = g.get_default_fmt(field_typ, field_typ)
729 }
730 }
731 } else if g.expr_is_auto_deref_var(expr_) && field_typ.is_ptr()
732 && !field_typ.has_flag(.option) && !field_typ.has_flag(.shared_f) {
733 // `for mut x in arr` loop variables surface in C as pointers
734 // (e.g. `string*`), but `${x}` should interpolate the
735 // underlying value. Pick the format specifier from the
736 // dereferenced element type so it lands in the `%s` path
737 // instead of falling through to `%p`. The pointer type is
738 // preserved in expr_types so `str_val` still emits the
739 // required `*` when reading the value.
740 if !has_explicit_fmt && !node_.need_fmts[i] {
741 deref_typ := field_typ.deref()
742 ftyp_sym := g.table.sym(deref_typ)
743 new_typ := if ftyp_sym.kind == .alias && !ftyp_sym.has_method('str') {
744 g.table.unalias_num_type(deref_typ)
745 } else {
746 deref_typ
747 }
748 fmts[i] = g.get_default_fmt(deref_typ, new_typ)
749 }
750 }
751 }
752 else {}
753 }
754 }
755 if g.gen_simple_string_inter_literal(node_, fmts) {
756 return
757 }
758 g.write2('builtin__str_intp(', node_.vals.len.str())
759 g.write(', _MOV((StrIntpData[]){')
760 for i, val in node_.vals {
761 mut escaped_val := cescape_nonascii(util.smart_quote(val, false))
762 escaped_val = escaped_val.replace('\0', '\\0')
763
764 if escaped_val.len > 0 {
765 g.write2('{_S("', escaped_val)
766 g.write('"), ')
767 } else {
768 g.write('{_SLIT0, ')
769 }
770
771 if i >= node_.exprs.len {
772 // last part of the string
773 g.write('0, { .d_c = 0 }, 0, 0, 0}')
774 break
775 }
776
777 ft_u64, ft_str := g.str_format(node_, i, fmts)
778 $if trace_ci_fixes ? {
779 if g.file.path.contains('comptime_for_in_options_struct_test.v')
780 || g.file.path.contains('comptime_map_fields_decode_test.v') {
781 g.write('/*trace_str_intp expr=')
782 g.write(node_.exprs[i].str().replace('*/', '* /'))
783 g.write(' typ=')
784 g.write(g.table.type_to_str(node_.expr_types[i]).replace('*/', '* /'))
785 g.write(' fmt=')
786 g.write(ft_str)
787 g.write('*/')
788 }
789 }
790 g.write2('0x', ft_u64.hex())
791 g.write2(', {.d_', ft_str)
792 g.write(' = ')
793
794 // for pointers we need a void* cast
795 if unsafe { ft_str.str[0] } == `p` {
796 g.write('(void*)(')
797 g.str_val(node_, i, fmts)
798 g.write(')')
799 } else {
800 g.str_val(node_, i, fmts)
801 }
802
803 g.write('}')
804 has_dynamic_width := i < node_.fwidth_exprs.len && node_.fwidth_exprs[i] !is ast.EmptyExpr
805 has_dynamic_precision := i < node_.precision_exprs.len
806 && node_.precision_exprs[i] !is ast.EmptyExpr
807 if has_dynamic_width || has_dynamic_precision {
808 g.write(', ')
809 if has_dynamic_width {
810 g.expr(node_.fwidth_exprs[i])
811 } else {
812 g.write('0')
813 }
814 g.write(', ')
815 if has_dynamic_precision {
816 g.expr(node_.precision_exprs[i])
817 } else {
818 g.write('0')
819 }
820 g.write(', ')
821 g.write(if has_dynamic_width && has_dynamic_precision {
822 '3'
823 } else if has_dynamic_width {
824 '1'
825 } else {
826 '2'
827 })
828 } else {
829 g.write(', 0, 0, 0')
830 }
831 g.write('}')
832 if i < (node_.vals.len - 1) {
833 g.write(', ')
834 }
835 }
836 g.write('}))')
837}
838
839const simple_string_interpolation_default_precision = 987698
840
841fn (mut g Gen) gen_simple_string_inter_literal(node ast.StringInterLiteral, fmts []u8) bool {
842 if g.is_autofree || g.pref.gc_mode == .boehm_leak {
843 // The fast `string_plus_many` lowering can leave nested temporary
844 // strings without scope cleanup in autofree/leak-detection modes.
845 // Use the regular `str_intp` path there so temporaries remain explicit.
846 return false
847 }
848 if node.exprs.len == 0 || node.expr_types.len < node.exprs.len {
849 return false
850 }
851 for i in 0 .. node.exprs.len {
852 if i >= node.need_fmts.len || node.need_fmts[i] || i >= fmts.len || fmts[i] == `_` {
853 return false
854 }
855 normalized_expr_type := g.table.fully_unaliased_type(g.unwrap_generic(node.expr_types[i]))
856 // Pointer aliases need the full `str_intp` path so nil formatting stays
857 // consistent with plain pointer interpolation.
858 if normalized_expr_type.is_any_kind_of_pointer() || normalized_expr_type.is_int_valptr()
859 || normalized_expr_type.is_float_valptr() {
860 return false
861 }
862 // Interface types need the full str_intp path for vtable dispatch and
863 // interface smartcasts need it for correct pointer prefix handling.
864 expr_i := node.exprs[i]
865 if expr_i is ast.Ident && expr_i.obj is ast.Var {
866 expr_var := expr_i.obj as ast.Var
867 if expr_var.orig_type != 0
868 && g.table.final_sym(g.unwrap_generic(expr_var.orig_type)).kind == .interface {
869 return false
870 }
871 }
872 etyp_sym := g.table.final_sym(node.expr_types[i])
873 if etyp_sym.kind == .interface {
874 return false
875 }
876 if i < node.fwidths.len && node.fwidths[i] != 0 {
877 return false
878 }
879 if i < node.fwidth_exprs.len && node.fwidth_exprs[i] !is ast.EmptyExpr {
880 return false
881 }
882 if i < node.precisions.len
883 && node.precisions[i] != simple_string_interpolation_default_precision {
884 return false
885 }
886 if i < node.precision_exprs.len && node.precision_exprs[i] !is ast.EmptyExpr {
887 return false
888 }
889 if i < node.pluss.len && node.pluss[i] {
890 return false
891 }
892 if i < node.fills.len && node.fills[i] {
893 return false
894 }
895 }
896 if g.inside_ternary > 0 {
897 for i, expr in node.exprs {
898 if i >= node.expr_types.len {
899 break
900 }
901 if g.should_materialize_windows_liveshared_string(expr, node.expr_types[i]) {
902 // The Windows live-shared temp lowering inserts standalone C statements.
903 // That is valid for normal statement contexts, but not inside ternary branches.
904 // Fall back to the regular `builtin__str_intp` path there instead.
905 return false
906 }
907 }
908 }
909 if node.exprs.len == 1 && node.vals.len == 2 && node.vals[0].len == 0 && node.vals[1].len == 0 {
910 if !g.gen_windows_liveshared_string_tmp(node.exprs[0], node.expr_types[0]) {
911 g.gen_expr_to_string(node.exprs[0], node.expr_types[0])
912 }
913 return true
914 }
915 mut part_count := 0
916 for i, val in node.vals {
917 if val.len > 0 {
918 part_count++
919 }
920 if i < node.exprs.len {
921 part_count++
922 }
923 }
924 if part_count <= 0 {
925 return false
926 }
927 g.write('builtin__string_plus_many(${part_count}, _MOV((string[${part_count}]){')
928 mut written_parts := 0
929 for i, val in node.vals {
930 if val.len > 0 {
931 if written_parts > 0 {
932 g.write(', ')
933 }
934 mut escaped_val := cescape_nonascii(util.smart_quote(val, false))
935 escaped_val = escaped_val.replace('\0', '\\0')
936 g.write2('_S("', escaped_val)
937 g.write('")')
938 written_parts++
939 }
940 if i < node.exprs.len {
941 if written_parts > 0 {
942 g.write(', ')
943 }
944 if !g.gen_windows_liveshared_string_tmp(node.exprs[i], node.expr_types[i]) {
945 g.gen_expr_to_string(node.exprs[i], node.expr_types[i])
946 }
947 written_parts++
948 }
949 }
950 g.write('}))')
951 return true
952}
953
954fn (g &Gen) should_materialize_windows_liveshared_string(expr ast.Expr, typ ast.Type) bool {
955 if g.pref.os != .windows || !g.pref.is_liveshared || g.inside_const {
956 return false
957 }
958 // The live shared DLL path on Windows is sensitive to nested string-returning
959 // expressions inside interpolation compound literals, so lower them via temps.
960 if typ == ast.string_type {
961 return expr !is ast.Ident && expr !is ast.StringLiteral && expr !is ast.SelectorExpr
962 && expr !is ast.ComptimeSelector
963 }
964 return true
965}
966
967fn (mut g Gen) gen_windows_liveshared_string_tmp(expr ast.Expr, typ ast.Type) bool {
968 if !g.should_materialize_windows_liveshared_string(expr, typ) {
969 return false
970 }
971 past := g.past_tmp_var_new()
972 g.write('string ${past.tmp_var} = ')
973 g.gen_expr_to_string(expr, typ)
974 g.writeln(';')
975 g.past_tmp_var_done(past)
976 return true
977}
978