markused,gen: fix direct str()call on a smart-casted interface variable#27715
markused,gen: fix direct str()call on a smart-casted interface variable#27715rilaaax wants to merge 2 commits into
str()call on a smart-casted interface variable#27715Conversation
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 58be88136c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| is_interface_smartcast_boxed_primitive := is_ptr && expr is ast.Ident && expr.obj is ast.Var | ||
| && (expr.obj as ast.Var).orig_type != 0 && g.table.final_sym(g.unwrap_generic((expr.obj as ast.Var).orig_type)).kind == .interface && g.table.final_sym(g.unwrap_generic(typ.deref())).kind !in [.struct, .interface, .sum_type, .array, .array_fixed, .map, .multi_return] |
There was a problem hiding this comment.
Preserve real pointer prefixes in interface .str()
When an interface is smart-cast to an actual pointer primitive, for example an empty-interface value containing &int and a match v { &int { v.str() } } arm, this predicate is also true because typ.deref() resolves to int. The code then suppresses ref_str, so direct .str() loses the pointer-style prefix (42 instead of the real pointer receiver formatting such as &42) and no longer matches rebinding before .str(). Please distinguish boxed value smartcasts from genuine pointer variants before dropping the & prefix.
Useful? React with 👍 / 👎.
| is_interface_smartcast_boxed_primitive := is_ptr && expr is ast.Ident && expr.obj is ast.Var | ||
| && (expr.obj as ast.Var).orig_type != 0 && g.table.final_sym(g.unwrap_generic((expr.obj as ast.Var).orig_type)).kind == .interface && g.table.final_sym(g.unwrap_generic(typ.deref())).kind !in [.struct, .interface, .sum_type, .array, .array_fixed, .map, .multi_return] |
There was a problem hiding this comment.
Keep aggregate smartcasts out of primitive handling
For grouped interface match arms such as A, B { v.str() }, the smartcast target is represented as an .aggregate, but .aggregate is not excluded here even though the surrounding smartcast code treats it like structs/arrays rather than scalars. That makes is_interface_smartcast_boxed_primitive true for aggregate arms containing struct variants and drops the leading &, so direct .str() in those arms prints A{}/B{} instead of the existing interface-smartcast struct formatting (&A{}/&B{}) and differs from single-variant arms. Please include .aggregate in the non-primitive set.
Useful? React with 👍 / 👎.
| if v.smartcasts.len > 0 { | ||
| raw_smartcast_typ := v.smartcasts.last() | ||
| if raw_smartcast_typ.is_ptr() { | ||
| w.uses_str[raw_smartcast_typ] = true |
There was a problem hiding this comment.
Avoid retaining str_intp for enum smartcasts
For an interface match arm whose variant is an enum, e.g. match v { MyEnum { v.str() } }, cgen handles the pointer-flagged receiver in the enum branch and emits the enum str call directly rather than builtin__str_intp. This unconditional raw-pointer mark therefore keeps str_intp/StrIntpData in the generated C even though the call does not need them, regressing skip-unused output size for enum smartcasts. Please restrict the extra mark to the raw smartcast kinds whose .str() code path actually emits string interpolation support.
Useful? React with 👍 / 👎.
Fixes #27713
Problem
Calling
.str()directly on an interface variable that's been smart-cast to a primitive type inside amatchbranch (without rebinding it to a new variable first) had two separate bugs:&42instead of42Minimal repro:
Note: rebinding first (
x := v; x.str()) avoids both issues, which was thefirst clue pointing at cgen/dead-code-elimination rather than the checker.
Cause
Interfaces box non-pointer-kind variants (like
int) behind an allocated pointer in C (_intis stored asint*). For a direct.str()call on a smart-cast identifier, cgen resolves the receiver using the raw internal smartcast type, which still carries that pointer flag — unlike the "exposed" V-level type (plainint) that the checker andnode.left_typeuse.This raw, pointer-flagged type causes two independent problems:
markused/walker.vdecides whether to keepstr_intp(the string interpolation runtime) based onnode.left_type(plainint, no pointer flag), so it never learns that cgen's pointer-flagged code path will actually needstr_intp, and strips it from the final C output.gen_expr_to_string()instr.vtreats any pointer-flagged type as "this is a real reference the user wants printed with&" — correct for genuine pointers/structs stored by reference, but not for a primitive value that only looks like a pointer because of interface boxing.Fix
markused/walker.v: when registering.str()usage, also check whether the receiver is a smart-cast identifier whose raw smartcast type is a pointer, and register that type too, sostr_intp/StrIntpDataaren't stripped when cgen will actually need them.str.v: detect the same case (an interface variable smart-cast to a primitive/non struct-like type) and use an empty ref prefix instead of&, leaving all other pointer/struct/reference printing behavior unchanged.Test
Regression test added.