Skip to content

Commit 43e1d97

Browse files
committed
v2: move most of string interpolation to transformer
1 parent 8f69b50 commit 43e1d97

4 files changed

Lines changed: 225 additions & 11 deletions

File tree

‎vlib/v2/ast/ast.v‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -681,7 +681,8 @@ pub:
681681
expr Expr
682682
// TEMP: prob removed once individual
683683
// fields are set, precision etc
684-
format_expr Expr = empty_expr
684+
format_expr Expr = empty_expr
685+
resolved_fmt string // resolved sprintf format specifier (e.g. '%d', '%s', '%lld'), set by transformer
685686
}
686687

687688
pub enum StringInterFormat {

‎vlib/v2/gen/cleanc/str_intp.v‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,25 @@ fn (mut g Gen) gen_string_inter_literal(node ast.StringInterLiteral) {
2828
fmt_str.write_string(escaped)
2929
if i < node.inters.len {
3030
inter := node.inters[i]
31-
fmt_str.write_string(g.get_sprintf_format(inter))
31+
if inter.resolved_fmt != '' {
32+
// Transformer already resolved the format specifier
33+
fmt_str.write_string(inter.resolved_fmt)
34+
} else {
35+
fmt_str.write_string(g.get_sprintf_format(inter))
36+
}
3237
}
3338
}
3439
fmt_lit := c_string_literal_content_to_c(fmt_str.str())
3540
g.sb.write_string('({ char* _sip; int _sil = asprintf(&_sip, ${fmt_lit}')
3641
// Write arguments
3742
for inter in node.inters {
3843
g.sb.write_string(', ')
39-
g.write_sprintf_arg(inter)
44+
if inter.resolved_fmt != '' {
45+
// Transformer already transformed the arg, emit directly
46+
g.expr(inter.expr)
47+
} else {
48+
g.write_sprintf_arg(inter)
49+
}
4050
}
4151
g.sb.write_string('); ${c_v_string_expr_from_ptr_len('_sip', '_sil', false)}; })')
4252
}

‎vlib/v2/transformer/transformer.v‎

Lines changed: 208 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -926,8 +926,7 @@ fn (mut t Transformer) transform_assign_stmt(stmt ast.AssignStmt) ast.AssignStmt
926926
mut rhs := []ast.Expr{cap: stmt.rhs.len}
927927
// For decl_assign with IfExpr RHS, skip value-lowering since cleanc
928928
// already handles this case efficiently (Type name; if (...) { name = a; } else { ... }).
929-
is_decl_with_if_rhs := stmt.op == .decl_assign && rhs_src.len == 1
930-
&& rhs_src[0] is ast.IfExpr
929+
is_decl_with_if_rhs := stmt.op == .decl_assign && rhs_src.len == 1 && rhs_src[0] is ast.IfExpr
931930
saved_skip := t.skip_if_value_lowering
932931
if is_decl_with_if_rhs {
933932
t.skip_if_value_lowering = true
@@ -3224,15 +3223,217 @@ fn (mut t Transformer) lower_wrapper_payload_access(wrapper_expr ast.Expr, base_
32243223
}
32253224
}
32263225

3226+
fn (t &Transformer) get_sprintf_format_for_type(typ types.Type) string {
3227+
match typ {
3228+
types.String {
3229+
return '%s'
3230+
}
3231+
types.Primitive {
3232+
if typ.props.has(types.Properties.boolean) {
3233+
return '%s'
3234+
}
3235+
if typ.props.has(types.Properties.float) {
3236+
return '%f'
3237+
}
3238+
if typ.props.has(types.Properties.unsigned) {
3239+
if typ.size == 64 {
3240+
return '%llu'
3241+
}
3242+
return '%u'
3243+
}
3244+
// signed integers
3245+
if typ.size == 64 {
3246+
return '%lld'
3247+
}
3248+
return '%d'
3249+
}
3250+
types.Rune {
3251+
return '%c'
3252+
}
3253+
types.Char {
3254+
return '%c'
3255+
}
3256+
types.Enum {
3257+
// Enums use their .str() method, so format is %s
3258+
if _ := t.get_str_fn_name_for_type(typ) {
3259+
return '%s'
3260+
}
3261+
return '%d'
3262+
}
3263+
types.Pointer {
3264+
return '%p'
3265+
}
3266+
types.Alias {
3267+
// For aliases to primitive types (e.g., ValueID = int),
3268+
// use the base type's format directly.
3269+
return t.get_sprintf_format_for_type(typ.base_type)
3270+
}
3271+
else {
3272+
// Custom types (struct, array, map, sumtype) that have .str() methods
3273+
if _ := t.get_str_fn_name_for_type(typ) {
3274+
return '%s'
3275+
}
3276+
return '%d'
3277+
}
3278+
}
3279+
}
3280+
3281+
fn (mut t Transformer) resolve_sprintf_format(inter ast.StringInter) string {
3282+
mut fmt := '%'
3283+
if inter.width > 0 {
3284+
fmt += '${inter.width}'
3285+
}
3286+
if inter.precision > 0 {
3287+
fmt += '.${inter.precision}'
3288+
}
3289+
if inter.format != .unformatted {
3290+
match inter.format {
3291+
.decimal { fmt += 'd' }
3292+
.float { fmt += 'f' }
3293+
.hex { fmt += 'x' }
3294+
.octal { fmt += 'o' }
3295+
.character { fmt += 'c' }
3296+
.exponent { fmt += 'e' }
3297+
.exponent_short { fmt += 'g' }
3298+
.binary { fmt += 'd' } // binary not supported in printf, fallback to decimal
3299+
.pointer_address { fmt += 'p' }
3300+
.string { fmt += 's' }
3301+
.unformatted { fmt += 'd' }
3302+
}
3303+
return fmt
3304+
}
3305+
// Infer from expression type
3306+
if typ := t.get_expr_type(inter.expr) {
3307+
return t.get_sprintf_format_for_type(typ)
3308+
}
3309+
return '%d' // fallback
3310+
}
3311+
3312+
fn (mut t Transformer) transform_sprintf_arg(inter ast.StringInter) ast.Expr {
3313+
transformed := t.transform_expr(inter.expr)
3314+
typ := t.get_expr_type(inter.expr) or {
3315+
return transformed // can't resolve type, pass as-is
3316+
}
3317+
// When an explicit format is specified, pass the expression as-is.
3318+
// The user has explicitly chosen the format, so no wrapping is needed
3319+
// (e.g., ${ptr:p} should pass the pointer directly, not call .str()).
3320+
if inter.format != .unformatted {
3321+
if inter.format == .string {
3322+
// Explicit :s still needs .str access for V strings
3323+
if typ is types.String {
3324+
return t.synth_selector(transformed, 'str', types.Type(types.voidptr_))
3325+
}
3326+
}
3327+
return transformed
3328+
}
3329+
match typ {
3330+
types.String {
3331+
// string -> expr.str (access C char* pointer for sprintf %s)
3332+
return t.synth_selector(transformed, 'str', types.Type(types.voidptr_))
3333+
}
3334+
types.Primitive {
3335+
if typ.props.has(types.Properties.boolean) {
3336+
// bool -> if expr { "true" } else { "false" } (ternary for %s)
3337+
return ast.Expr(ast.IfExpr{
3338+
cond: transformed
3339+
stmts: [
3340+
ast.Stmt(ast.ExprStmt{
3341+
expr: ast.Expr(ast.StringLiteral{
3342+
kind: .c
3343+
value: '"true"'
3344+
})
3345+
}),
3346+
]
3347+
else_expr: ast.Expr(ast.StringLiteral{
3348+
kind: .c
3349+
value: '"false"'
3350+
})
3351+
pos: inter.expr.pos()
3352+
})
3353+
}
3354+
// numeric primitives: pass as-is
3355+
return transformed
3356+
}
3357+
types.Rune, types.Char {
3358+
return transformed
3359+
}
3360+
types.Enum {
3361+
// Enums should call their .str() method for string representation
3362+
if str_fn_name := t.get_str_fn_name_for_type(typ) {
3363+
str_call := ast.Expr(ast.CallExpr{
3364+
lhs: ast.Ident{
3365+
name: str_fn_name
3366+
}
3367+
args: [transformed]
3368+
pos: inter.expr.pos()
3369+
})
3370+
return t.synth_selector(str_call, 'str', types.Type(types.voidptr_))
3371+
}
3372+
return transformed
3373+
}
3374+
types.Alias {
3375+
// For aliases to primitives (e.g., ValueID = int),
3376+
// handle based on the base type to avoid calling non-existent str() functions.
3377+
base := typ.base_type
3378+
match base {
3379+
types.String {
3380+
return t.synth_selector(transformed, 'str', types.Type(types.voidptr_))
3381+
}
3382+
types.Primitive {
3383+
if base.props.has(types.Properties.boolean) {
3384+
return ast.Expr(ast.IfExpr{
3385+
cond: transformed
3386+
stmts: [
3387+
ast.Stmt(ast.ExprStmt{
3388+
expr: ast.Expr(ast.StringLiteral{
3389+
kind: .c
3390+
value: '"true"'
3391+
})
3392+
}),
3393+
]
3394+
else_expr: ast.Expr(ast.StringLiteral{
3395+
kind: .c
3396+
value: '"false"'
3397+
})
3398+
pos: inter.expr.pos()
3399+
})
3400+
}
3401+
return transformed
3402+
}
3403+
else {
3404+
return transformed
3405+
}
3406+
}
3407+
}
3408+
else {
3409+
// For custom types with str() method: Type__str(expr).str
3410+
str_fn_info := t.get_str_fn_info_for_expr(inter.expr)
3411+
if str_fn_info.str_fn_name != '' {
3412+
t.needed_str_fns[str_fn_info.str_fn_name] = str_fn_info.elem_type
3413+
str_call := ast.Expr(ast.CallExpr{
3414+
lhs: ast.Ident{
3415+
name: str_fn_info.str_fn_name
3416+
}
3417+
args: [transformed]
3418+
pos: inter.expr.pos()
3419+
})
3420+
return t.synth_selector(str_call, 'str', types.Type(types.voidptr_))
3421+
}
3422+
return transformed
3423+
}
3424+
}
3425+
}
3426+
32273427
fn (mut t Transformer) transform_string_inter_literal(expr ast.StringInterLiteral) ast.Expr {
32283428
mut new_inters := []ast.StringInter{cap: expr.inters.len}
32293429
for inter in expr.inters {
32303430
new_inters << ast.StringInter{
3231-
format: inter.format
3232-
width: inter.width
3233-
precision: inter.precision
3234-
expr: t.transform_expr(inter.expr)
3235-
format_expr: inter.format_expr
3431+
format: inter.format
3432+
width: inter.width
3433+
precision: inter.precision
3434+
expr: t.transform_sprintf_arg(inter)
3435+
format_expr: inter.format_expr
3436+
resolved_fmt: t.resolve_sprintf_format(inter)
32363437
}
32373438
}
32383439
return ast.StringInterLiteral{

‎vlib/v2/transformer/types.v‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1912,7 +1912,9 @@ fn (t &Transformer) get_str_fn_name_for_type(typ types.Type) ?string {
19121912
return t.get_str_fn_name_for_type(typ.base_type)
19131913
}
19141914
types.Alias {
1915-
return '${typ.name}__str'
1915+
// Recurse to base type — aliases to primitives (e.g., ValueID = int)
1916+
// don't have their own str() functions.
1917+
return t.get_str_fn_name_for_type(typ.base_type)
19161918
}
19171919
else {
19181920
return none

0 commit comments

Comments
 (0)