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