Skip to content

Commit e8d0714

Browse files
checker,cgen: allow embedded interface upcasts (#26772)
Add support for implicit upcasting when an interface embeds another interface (including transitive chains like Dense -> Solid -> Base). - checker: `interface_embeds_interface_recursive` walks the embed tree to accept the upcast instead of erroring with "cannot implement interface with a different interface". - cgen: `expr_with_cast` emits `I_<Got>_as_I_<Exp>(...)` conversion calls for interface-to-interface casts, skipping the branch when either side carries `shared_f` so the existing shared-container path handles those. - cgen/fn: `v_typeof_interface_` helpers now return `string` (via `_S(...)`) instead of `char *`, fixing charptr/string mismatches. - vfmt/parser: preserve attribute call syntax (`@[attr(...)]`) through format round-trips. - closure: keep ppc64 bootstrap-compatible by guarding arch-specific trampoline code. Fixes #26759. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 1f24f30 commit e8d0714

9 files changed

Lines changed: 154 additions & 29 deletions

File tree

vlib/builtin/closure/closure.c.v

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ module closure
55
// https://nullprogram.com/blog/2017/01/08/
66

77
const assumed_page_size = int(0x4000)
8+
const ppc64_architecture = int(11)
9+
10+
type ClosureGetDataFn = fn () voidptr
811

912
@[heap]
1013
struct Closure {
1114
ClosureMutex
1215
mut:
1316
closure_ptr voidptr
14-
closure_get_data fn () voidptr = unsafe { nil }
17+
closure_get_data ClosureGetDataFn = unsafe { nil }
1518
closure_cap int
1619
v_page_size int = int(0x4000)
1720
}
@@ -23,10 +26,22 @@ enum MemoryProtectAtrr {
2326
read_write
2427
}
2528

29+
// Keep this runtime check bootstrap-compatible. Older compilers can not parse `$if ppc64` yet.
30+
@[inline]
31+
fn is_ppc64() bool {
32+
$if big_endian {
33+
return C.__V_architecture == ppc64_architecture
34+
} $else {
35+
return false
36+
}
37+
}
38+
2639
// refer to https://godbolt.org/z/r7P3EYv6c for a complete assembly
2740
//
2841
// NOTE: Keep the first branch as the longest byte sequence. In translated/bootstrap C mode
2942
// (`vc/v.c`), V emits a fixed C array whose size is inferred from the first branch.
43+
// The final `big_endian` branch maps to ppc64 here, since the supported big-endian
44+
// closure targets handled above are s390x and sparc64.
3045
// vfmt off
3146
pub const closure_thunk = $if ppc64le {
3247
[
@@ -108,7 +123,7 @@ pub const closure_thunk = $if ppc64le {
108123
0x81, 0xc0, 0x40, 0x00, // jmp %g1
109124
0x01, 0x00, 0x00, 0x00 // nop
110125
]!
111-
} $else $if ppc64 {
126+
} $else $if big_endian {
112127
[
113128
u8(0x7C), 0x08, 0x02, 0xA6, // mflr %r0
114129
0x48, 0x00, 0x00, 0x05, // bl here
@@ -181,7 +196,7 @@ const closure_get_data_bytes = $if arm32 {
181196
0x81, 0xc3, 0xe0, 0x08, // retl
182197
0x01, 0x00, 0x00, 0x00 // nop
183198
]!
184-
} $else $if ppc64 {
199+
} $else $if big_endian {
185200
[
186201
u8(0x7d), 0xc3, 0x00, 0x66, // mfvsrd %r3, %f14
187202
0x4e, 0x80, 0x00, 0x20 // blr
@@ -246,14 +261,14 @@ fn closure_init() {
246261
closure_memory_protect_platform(g_closure.closure_ptr, page_size, .read_exec)
247262
}
248263
// Setup global closure handler pointer
249-
$if ppc64 {
264+
if is_ppc64() {
250265
mut desc := unsafe { &voidptr(&u8(g_closure.closure_ptr) - assumed_page_size) }
251266
unsafe {
252267
desc[0] = g_closure.closure_ptr
253268
desc[1] = nil
254269
}
255-
g_closure.closure_get_data = desc
256-
} $else {
270+
g_closure.closure_get_data = unsafe { ClosureGetDataFn(desc) }
271+
} else {
257272
g_closure.closure_get_data = g_closure.closure_ptr
258273
}
259274

@@ -283,7 +298,7 @@ fn closure_create(func voidptr, data voidptr) voidptr {
283298

284299
// Write closure metadata (data + function pointer)
285300
mut p := &voidptr(&u8(curr_closure) - assumed_page_size)
286-
$if ppc64 {
301+
if is_ppc64() {
287302
// ELFv1: guard page layout per slot:
288303
// [0] desc[0] = thunk code address <- returned as ELFv1 function pointer
289304
// [1] desc[1] = nil (TOC unused; thunk loads real TOC from func descriptor)
@@ -293,18 +308,17 @@ fn closure_create(func voidptr, data voidptr) voidptr {
293308
p[1] = nil
294309
p[2] = data
295310
p[3] = func
296-
} $else {
311+
} else {
297312
p[0] = data // Stored closure context
298313
p[1] = func // Target function to execute
299314
}
300315
}
301316
closure_mtx_unlock_platform()
302317

303318
// Return executable closure object
304-
$if ppc64 {
319+
if is_ppc64() {
305320
// ELFv1: return descriptor address (guard page), not raw code address
306321
return unsafe { &u8(curr_closure) - assumed_page_size }
307-
} $else {
308-
return curr_closure
309322
}
323+
return curr_closure
310324
}

vlib/v/ast/attr.v

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,15 @@ pub:
2828
// original call-style metadata for `@[foo(...)]`, used by vfmt
2929
call_name string
3030
call_arg_name string
31+
call_arg_idx int = -1
3132
pub mut:
3233
ct_expr Expr // .kind == comptime_define, for [if !name]
3334
ct_evaled bool // whether ct_skip has been evaluated already
3435
ct_skip bool // is the comptime expr *false*, filled by checker
3536
}
3637

3738
pub fn (a &Attr) debug() string {
38-
return 'Attr{ name: "${a.name}", has_arg: ${a.has_arg}, arg: "${a.arg}", kind: ${a.kind}, ct_expr: ${a.ct_expr}, ct_opt: ${a.ct_opt}, ct_skip: ${a.ct_skip}, call_name: "${a.call_name}", call_arg_name: "${a.call_arg_name}" }'
39+
return 'Attr{ name: "${a.name}", has_arg: ${a.has_arg}, arg: "${a.arg}", kind: ${a.kind}, ct_expr: ${a.ct_expr}, ct_opt: ${a.ct_opt}, ct_skip: ${a.ct_skip}, call_name: "${a.call_name}", call_arg_name: "${a.call_arg_name}", call_arg_idx: ${a.call_arg_idx} }'
3940
}
4041

4142
// str returns the string representation without square brackets

vlib/v/checker/checker.v

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1404,6 +1404,9 @@ fn (mut c Checker) type_implements(typ ast.Type, interface_type ast.Type, pos to
14041404
}
14051405
if typ_sym.kind == .interface && inter_sym.kind == .interface && !styp.starts_with('JS.')
14061406
&& !inter_sym.name.starts_with('JS.') {
1407+
if c.interface_embeds_interface(utyp, interface_type) {
1408+
return true
1409+
}
14071410
c.error('cannot implement interface `${inter_sym.name}` with a different interface `${styp}`',
14081411
pos)
14091412
}
@@ -6197,6 +6200,37 @@ fn (mut c Checker) fail_if_unreadable(expr ast.Expr, typ ast.Type, what string)
61976200
return false
61986201
}
61996202

6203+
fn (mut c Checker) interface_embeds_interface(interface_type ast.Type, embedded_interface_type ast.Type) bool {
6204+
mut visited := map[int]bool{}
6205+
return c.interface_embeds_interface_recursive(interface_type, embedded_interface_type, mut
6206+
visited)
6207+
}
6208+
6209+
fn (mut c Checker) interface_embeds_interface_recursive(interface_type ast.Type, embedded_interface_type ast.Type, mut visited map[int]bool) bool {
6210+
u_interface_type := c.unwrap_generic(interface_type)
6211+
if visited[u_interface_type.idx()] {
6212+
return false
6213+
}
6214+
visited[u_interface_type.idx()] = true
6215+
embedded_idx := c.unwrap_generic(embedded_interface_type).idx()
6216+
final_embedded_idx := c.table.final_sym(embedded_interface_type).idx
6217+
if iface_decl := c.table.interfaces[u_interface_type] {
6218+
for embed in iface_decl.embeds {
6219+
embed_typ := c.unwrap_generic(embed.typ)
6220+
if embed_typ.idx() == embedded_idx
6221+
|| c.table.final_sym(embed_typ).idx == final_embedded_idx {
6222+
return true
6223+
}
6224+
if c.interface_embeds_interface_recursive(embed_typ, embedded_interface_type, mut
6225+
visited)
6226+
{
6227+
return true
6228+
}
6229+
}
6230+
}
6231+
return false
6232+
}
6233+
62006234
fn (mut c Checker) fail_if_stack_struct_action_outside_unsafe(mut ident ast.Ident, failed_action string) {
62016235
if mut ident.obj is ast.Var {
62026236
mut obj := unsafe { &ident.obj }

vlib/v/fmt/attrs.v

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,10 @@ fn attr_call_group_str(attrs []ast.Attr) string {
141141
if attrs.len == 0 {
142142
return ''
143143
}
144+
mut ordered_attrs := attrs.clone()
145+
ordered_attrs.sort(a.call_arg_idx < b.call_arg_idx)
144146
mut args := []string{}
145-
for attr in attrs {
147+
for attr in ordered_attrs {
146148
if !attr.has_arg {
147149
continue
148150
}

vlib/v/fmt/tests/attribute_call_syntax_keep.vv

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ fn old_positional() {}
77
@[deprecated(msg: 'use new_fn instead', after: '2999-10-10')]
88
fn old_named() {}
99

10+
@[foo(bar: 'x', 123)]
11+
fn mixed_named_positional() {}
12+
13+
@[foo(123, bar: 'x')]
14+
fn mixed_positional_named() {}
15+
1016
struct Config {
1117
flag bool @[custom(flag: true, count: 2)]
1218
value string @[xml(name: 'cfg'); raw]

vlib/v/gen/c/cgen.v

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,11 +1229,11 @@ pub fn (mut g Gen) write_typeof_functions() {
12291229
continue
12301230
}
12311231
already_generated_ifaces[sym.cname] = true
1232-
g.definitions.writeln('${g.static_non_parallel}char * v_typeof_interface_${sym.cname}(u32 sidx);')
1232+
g.definitions.writeln('${g.static_non_parallel}string v_typeof_interface_${sym.cname}(u32 sidx);')
12331233
if g.pref.parallel_cc {
1234-
g.extern_out.writeln('extern char * v_typeof_interface_${sym.cname}(u32 sidx);')
1234+
g.extern_out.writeln('extern string v_typeof_interface_${sym.cname}(u32 sidx);')
12351235
}
1236-
g.writeln('${g.static_non_parallel}char * v_typeof_interface_${sym.cname}(u32 sidx) {')
1236+
g.writeln('${g.static_non_parallel}string v_typeof_interface_${sym.cname}(u32 sidx) {')
12371237
for t in inter_info.types {
12381238
sub_sym := g.table.sym(ast.mktyp(t))
12391239
if sub_sym.info is ast.Struct && sub_sym.info.is_unresolved_generic() {
@@ -1243,9 +1243,9 @@ pub fn (mut g Gen) write_typeof_functions() {
12431243
&& sub_sym.idx !in g.table.used_features.used_syms {
12441244
continue
12451245
}
1246-
g.writeln('\tif (sidx == _${sym.cname}_${sub_sym.cname}_index) return "${util.strip_main_name(sub_sym.name)}";')
1246+
g.writeln('\tif (sidx == _${sym.cname}_${sub_sym.cname}_index) return _S("${util.strip_main_name(sub_sym.name)}");')
12471247
}
1248-
g.writeln2('\treturn "unknown ${util.strip_main_name(sym.name)}";', '}')
1248+
g.writeln2('\treturn _S("unknown ${util.strip_main_name(sym.name)}");', '}')
12491249
// Avoid duplicate symbol '_v_typeof_interface_idx_IError' when using -usecache
12501250
if g.pref.build_mode != .build_module {
12511251
g.definitions.writeln('u32 v_typeof_interface_idx_${sym.cname}(u32 sidx);')
@@ -3314,6 +3314,28 @@ fn (mut g Gen) expr_with_cast(expr ast.Expr, got_type_raw ast.Type, expected_typ
33143314
}
33153315
return
33163316
}
3317+
if got_sym.info is ast.Interface && exp_sym.info is ast.Interface
3318+
&& got_type.idx() != expected_type.idx() && !got_type.has_flag(.shared_f)
3319+
&& !expected_type.has_flag(.shared_f) {
3320+
g.write('I_${got_sym.cname}_as_I_${exp_sym.cname}(')
3321+
if got_type.is_ptr() {
3322+
g.write('*')
3323+
}
3324+
g.expr(expr)
3325+
g.write(')')
3326+
3327+
mut got_interface_sym := g.table.sym(got_type)
3328+
mut info := got_interface_sym.info as ast.Interface
3329+
lock info.conversions {
3330+
if expected_type !in info.conversions {
3331+
left_variants := g.table.iface_types[got_sym.name]
3332+
right_variants := g.table.iface_types[exp_sym.name]
3333+
info.conversions[expected_type] = left_variants.filter(it in right_variants)
3334+
}
3335+
}
3336+
got_interface_sym.info = info
3337+
return
3338+
}
33173339
// cast to sum type
33183340
exp_styp := g.styp(expected_type)
33193341
mut got_styp := g.styp(got_type)
@@ -8717,7 +8739,7 @@ return ${cast_shared_struct_str};
87178739
variant_sym := g.table.sym(variant)
87188740
conversion_functions.writeln('\tif (x._typ == _${interface_name}_${variant_sym.cname}_index) return I_${variant_sym.cname}_to_Interface_${vsym.cname}(x._${variant_sym.cname});')
87198741
}
8720-
pmessage := 'builtin__string__plus(builtin__string__plus(builtin__tos3("`as_cast`: cannot convert "), builtin__tos3(v_typeof_interface_${interface_name}(x._typ))), builtin__tos3(" to ${util.strip_main_name(vsym.name)}"))'
8742+
pmessage := 'builtin__string__plus(builtin__string__plus(_S("`as_cast`: cannot convert "), v_typeof_interface_${interface_name}(x._typ)), _S(" to ${util.strip_main_name(vsym.name)}"))'
87218743
if g.pref.is_debug {
87228744
// TODO: actually return a valid position here
87238745
conversion_functions.write_string2('\tbuiltin__panic_debug(1, builtin__tos3("builtin.v"), builtin__tos3("builtin"), builtin__tos3("__as_cast"), ',

vlib/v/gen/c/fn.v

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1769,10 +1769,14 @@ fn (mut g Gen) method_call(node ast.CallExpr) {
17691769
prefix_name := if left_sym.kind == .sum_type { 'sumtype' } else { 'interface' }
17701770
match node.kind {
17711771
.type_name {
1772-
if left_sym.kind in [.sum_type, .interface] {
1773-
g.conversion_function_call('builtin__charptr_vstring_literal(v_typeof_${prefix_name}_${typ_sym.cname}',
1772+
if left_sym.kind == .sum_type {
1773+
g.conversion_function_call('builtin__charptr_vstring_literal(v_typeof_sumtype_${typ_sym.cname}',
17741774
')', node)
17751775
return
1776+
} else if left_sym.kind == .interface {
1777+
g.conversion_function_call('v_typeof_interface_${typ_sym.cname}',
1778+
'', node)
1779+
return
17761780
}
17771781
}
17781782
.type_idx {

vlib/v/parser/attribute.v

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,12 @@ fn (mut p Parser) parse_attr_call(name string, is_at bool, apos token.Pos) []ast
4343
mut base_arg := ''
4444
mut base_quote := u8(`'`)
4545
mut base_arg_name := ''
46+
mut base_call_arg_idx := -1
4647
mut base_has_arg := false
4748
mut attrs := []ast.Attr{}
4849
mut has_base_arg := false
4950
mut positional_arg_idx := 1
51+
mut call_arg_idx := 0
5052
for p.tok.kind !in [.rpar, .eof] {
5153
mut is_named := false
5254
mut arg_name := ''
@@ -68,6 +70,7 @@ fn (mut p Parser) parse_attr_call(name string, is_at bool, apos token.Pos) []ast
6870
base_kind = kind
6971
base_quote = quote
7072
base_arg_name = arg_name
73+
base_call_arg_idx = call_arg_idx
7174
has_base_arg = true
7275
} else {
7376
attrs << ast.Attr{
@@ -80,27 +83,32 @@ fn (mut p Parser) parse_attr_call(name string, is_at bool, apos token.Pos) []ast
8083
has_at: is_at
8184
call_name: name
8285
call_arg_name: arg_name
86+
call_arg_idx: call_arg_idx
8387
}
8488
}
8589
} else if !has_base_arg {
8690
base_has_arg = true
8791
base_arg = arg
8892
base_kind = kind
8993
base_quote = quote
94+
base_arg_name = arg_name
95+
base_call_arg_idx = call_arg_idx
9096
has_base_arg = true
9197
} else {
9298
attrs << ast.Attr{
93-
name: '${name}_${positional_arg_idx}'
94-
has_arg: true
95-
arg: arg
96-
kind: kind
97-
quote: quote
98-
pos: apos.extend(p.prev_tok.pos())
99-
has_at: is_at
100-
call_name: name
99+
name: '${name}_${positional_arg_idx}'
100+
has_arg: true
101+
arg: arg
102+
kind: kind
103+
quote: quote
104+
pos: apos.extend(p.prev_tok.pos())
105+
has_at: is_at
106+
call_name: name
107+
call_arg_idx: call_arg_idx
101108
}
102109
positional_arg_idx++
103110
}
111+
call_arg_idx++
104112
if p.tok.kind == .comma {
105113
p.next()
106114
continue
@@ -118,6 +126,7 @@ fn (mut p Parser) parse_attr_call(name string, is_at bool, apos token.Pos) []ast
118126
has_at: is_at
119127
call_name: name
120128
call_arg_name: base_arg_name
129+
call_arg_idx: base_call_arg_idx
121130
}
122131
attrs.insert(0, base_attr)
123132
return attrs
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
interface Base {
2+
}
3+
4+
interface Solid {
5+
Base
6+
}
7+
8+
interface Dense {
9+
Solid
10+
}
11+
12+
struct Empty {
13+
}
14+
15+
fn greet(x Base) bool {
16+
return x is Empty
17+
}
18+
19+
fn test_interface_embedding_implicit_upcast() {
20+
solid := Solid(Empty{})
21+
assert greet(solid)
22+
23+
mut base := Base(Empty{})
24+
base = solid
25+
assert base is Empty
26+
assert (solid as Base) is Empty
27+
28+
dense := Dense(Empty{})
29+
assert greet(dense)
30+
base = dense
31+
assert base is Empty
32+
assert (dense as Base) is Empty
33+
}

0 commit comments

Comments
 (0)