@@ -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+
32273427fn (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{
0 commit comments