Skip to content

Commit f882c81

Browse files
committed
builtin: show file and line number in backtraces; v2: implements, clone() auto generation
1 parent a5bd61a commit f882c81

21 files changed

Lines changed: 672 additions & 31 deletions

cmd/v/v.v

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,22 +207,22 @@ fn invoke_help_and_exit(remaining []string) {
207207
}
208208

209209
fn maybe_delegate_to_v2(command string, prefs &pref.Preferences) {
210-
if !prefs.use_v2 {
210+
is_ownership := '-ownership' in os.args
211+
if !prefs.use_v2 && !is_ownership {
211212
return
212213
}
213214
if !is_v2_relevant_command(command, prefs) {
214-
eprintln('v: `-v2` currently supports direct compilation only. Use `v -v2 hello.v` or `v -v2 -b arm64 hello.v`.')
215+
eprintln('v: `-v2`/`-ownership` currently support direct compilation only. Use `v -v2 hello.v` or `v -ownership module_dir`.')
215216
exit(1)
216217
}
217-
is_ownership := '-ownership' in os.args
218218
launch_v2_compiler(prefs.is_verbose, os.args[1..].filter(it != '-v2'), is_ownership)
219219
}
220220

221221
fn is_v2_relevant_command(command string, prefs &pref.Preferences) bool {
222222
if prefs.path == '' || prefs.is_run || prefs.is_crun {
223223
return false
224224
}
225-
return command.ends_with('.v') && prefs.path.ends_with('.v')
225+
return prefs.path == command && (command.ends_with('.v') || os.exists(command))
226226
}
227227

228228
@[noreturn]

vlib/builtin/backtraces.c.v

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,87 @@ pub fn print_backtrace() {
2323
}
2424
}
2525

26+
// demangle_v_symbol converts a C-mangled V symbol name to a human-readable V name.
27+
// For example: `veb__run_T_main__App_main__Context` => `veb.run[main.App, main.Context]`
28+
@[direct_array_access]
29+
fn demangle_v_symbol(cname string) string {
30+
mut name := cname
31+
// Strip builtin__ prefix:
32+
if name.starts_with('builtin__') {
33+
name = name[9..]
34+
}
35+
// Replace pointer type encoding:
36+
name = name.replace('__ptr__', '&')
37+
// Split base name from generic parameters on `_T_`:
38+
// C mangling: `base_T_type1_type2` where each type is prefixed with `_`
39+
// and types may contain `__` for module separators.
40+
t_pos := name.index('_T_') or { -1 }
41+
if t_pos >= 0 {
42+
base := name[..t_pos].replace('__', '.')
43+
generic_suffix := name[t_pos + 3..]
44+
// Split on single `_` that is not part of `__` (double underscore).
45+
params := split_generic_params(generic_suffix)
46+
mut demangled_params := []string{cap: params.len}
47+
for param in params {
48+
demangled_params << param.replace('__', '.')
49+
}
50+
return base + '[' + demangled_params.join(', ') + ']'
51+
}
52+
// No generics - just replace module separator:
53+
name = name.replace('__', '.')
54+
// `main.main` is V's internal C mangling, in V it's just `main`:
55+
if name == 'main.main' {
56+
return 'main'
57+
}
58+
return name
59+
}
60+
61+
// split_generic_params splits a generic suffix like `main__App_main__Context`
62+
// into individual type parameters [`main__App`, `main__Context`].
63+
// Splits on single `_` but not `__` (double underscore used for module separators).
64+
@[direct_array_access]
65+
fn split_generic_params(s string) []string {
66+
mut params := []string{}
67+
mut start := 0
68+
mut i := 0
69+
for i < s.len {
70+
if s[i] == `_` {
71+
if i + 1 < s.len && s[i + 1] == `_` {
72+
// Part of `__` (module separator), skip both.
73+
i += 2
74+
} else {
75+
// Single `_` is a generic param separator.
76+
if i > start {
77+
params << s[start..i]
78+
}
79+
i++
80+
start = i
81+
}
82+
} else {
83+
i++
84+
}
85+
}
86+
if start < s.len {
87+
params << s[start..]
88+
}
89+
return params
90+
}
91+
92+
// demangle_backtrace_sym demangles a V symbol within a Linux-style backtrace fragment.
93+
// Linux format: `./executable(symbol_name+0x1a4) `
94+
fn demangle_backtrace_sym(s string) string {
95+
paren_start := s.index('(') or { return s }
96+
plus_pos := s.index_after_('+', paren_start)
97+
if plus_pos < 0 {
98+
return s
99+
}
100+
symbol := s[paren_start + 1..plus_pos]
101+
if symbol.len == 0 {
102+
return s
103+
}
104+
return s[..paren_start + 1] + demangle_v_symbol(symbol) + s[plus_pos..]
105+
}
106+
26107
fn eprint_space_padding(output string, max_len int) {
27108
padding_len := max_len - output.len
28109
if padding_len > 0 {

vlib/builtin/backtraces_nix.c.v

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,110 @@ fn print_backtrace_skipping_top_frames_bsd(skipframes int) bool {
3131
eprintln('C.backtrace returned less than 2 frames')
3232
return false
3333
}
34-
C.backtrace_symbols_fd(&buffer[skipframes], nr_ptrs - skipframes, 2)
34+
nr_actual_frames := nr_ptrs - skipframes
35+
csymbols := C.backtrace_symbols(voidptr(&buffer[skipframes]), nr_actual_frames)
36+
atos_lines := bsd_backtrace_resolve_atos(&buffer[skipframes], nr_actual_frames)
37+
for i in 0 .. nr_actual_frames {
38+
sframe := unsafe { tos2(&u8(csymbols[i])) }
39+
mut file_line := ''
40+
if i < atos_lines.len {
41+
file_line = atos_lines[i]
42+
}
43+
// macOS format: `0 main 0x00000001047232f8 veb__run_T_main__App_main__Context + 356`
44+
symbol_start := sframe.index('0x') or { -1 }
45+
if symbol_start < 0 {
46+
continue
47+
}
48+
rest := sframe[symbol_start..]
49+
space_after_addr := rest.index(' ') or { -1 }
50+
if space_after_addr < 0 {
51+
continue
52+
}
53+
symbol_and_offset := rest[space_after_addr + 1..]
54+
plus_pos := symbol_and_offset.index(' + ') or { -1 }
55+
mut raw_symbol := symbol_and_offset
56+
if plus_pos >= 0 {
57+
raw_symbol = symbol_and_offset[..plus_pos]
58+
}
59+
// Skip C runtime frames that are not V functions:
60+
if raw_symbol in ['main', 'start', '_main'] {
61+
continue
62+
}
63+
mut demangled := ''
64+
if plus_pos >= 0 {
65+
demangled = demangle_v_symbol(raw_symbol) + symbol_and_offset[plus_pos..]
66+
} else {
67+
demangled = demangle_v_symbol(raw_symbol)
68+
}
69+
if file_line.len > 0 {
70+
eprint(file_line)
71+
eprint_space_padding(file_line, 45)
72+
eprint(' | ')
73+
eprintln(demangled)
74+
} else {
75+
eprintln(demangled)
76+
}
77+
}
78+
if nr_actual_frames > 0 {
79+
unsafe { C.free(csymbols) }
80+
}
3581
}
3682
return true
3783
}
3884
}
3985

86+
// bsd_backtrace_resolve_atos uses macOS `atos` to resolve addresses to file:line info.
87+
// Returns an array of file:line strings (one per frame). Empty string if unresolved.
88+
@[direct_array_access]
89+
fn bsd_backtrace_resolve_atos(buffer &voidptr, nr_frames int) []string {
90+
$if macos {
91+
exe_name := backtrace_current_executable_name()
92+
if exe_name.len == 0 {
93+
return []string{}
94+
}
95+
base_addr := C._dyld_get_image_header(0)
96+
if base_addr == unsafe { nil } {
97+
return []string{}
98+
}
99+
// Build single atos command with all addresses for efficiency:
100+
mut cmd := 'atos --fullPath -o "' + exe_name + '" -l ' + ptr_str(base_addr)
101+
for i in 0 .. nr_frames {
102+
cmd += ' ' + ptr_str(unsafe { buffer[i] })
103+
}
104+
f := C.popen(&char(cmd.str), c'r')
105+
if f == unsafe { nil } {
106+
return []string{}
107+
}
108+
buf := [4096]u8{}
109+
mut lines := []string{cap: nr_frames}
110+
unsafe {
111+
bp := &buf[0]
112+
for C.fgets(&char(bp), 4096, f) != 0 {
113+
line := tos(bp, vstrlen(bp)).trim_chars(' \t\n\r', .trim_both)
114+
// atos output format: `func_name (in binary) (file.v:42)`
115+
// Extract the last parenthesized (file:line) part:
116+
paren_pos := line.index_last_('(')
117+
if paren_pos >= 0 {
118+
file_part := line[paren_pos + 1..]
119+
end_paren := file_part.index_last_(')')
120+
if end_paren >= 0 {
121+
file_line := file_part[..end_paren]
122+
if file_line.contains(':') && !file_line.starts_with('in ')
123+
&& !file_line.contains('.tmp.c:') {
124+
lines << file_line
125+
continue
126+
}
127+
}
128+
}
129+
lines << ''
130+
}
131+
}
132+
C.pclose(f)
133+
return lines
134+
}
135+
return []string{}
136+
}
137+
40138
fn C.tcc_backtrace(fmt &char) i32
41139

42140
fn backtrace_current_executable_name() string {
@@ -132,7 +230,7 @@ fn print_backtrace_skipping_top_frames_linux(skipframes int) bool {
132230
eprint(' | ')
133231
eprint(addr)
134232
eprint(' | ')
135-
eprintln(beforeaddr)
233+
eprintln(demangle_backtrace_sym(beforeaddr))
136234
}
137235
if nr_actual_frames > 0 {
138236
unsafe { C.free(csymbols) }

vlib/builtin/backtraces_windows.c.v

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ fn print_backtrace_skipping_top_frames_msvc(skipframes int) bool {
122122
// addr:
123123
lineinfo = '?? : address = 0x' + ptr_str(frame_addr)
124124
}
125-
sfunc := unsafe { tos3(fname) }
125+
sfunc := demangle_v_symbol(unsafe { tos3(fname) })
126126
snframe := i64(nframe).str()
127127
eprint_space_padding(snframe, 2)
128128
eprint(': ')

vlib/builtin/builtin_backtraces_nix.c.v

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,9 @@ module builtin
44
fn C.backtrace(a &voidptr, size i32) i32
55
fn C.backtrace_symbols(a &voidptr, size i32) &&char
66
fn C.backtrace_symbols_fd(a &voidptr, size i32, fd i32)
7+
8+
$if macos {
9+
#include <mach-o/dyld.h>
10+
11+
fn C._dyld_get_image_header(image_index u32) voidptr
12+
}

vlib/builtin/builtin_d_use_libbacktrace.c.v

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ fn bt_print_callback(data &BacktraceOptions, pc voidptr, filename_ptr &char, lin
4747
fn_name := if fn_name_ptr == unsafe { nil } {
4848
'???'
4949
} else {
50-
(unsafe { fn_name_ptr.vstring() }).replace('__', '.')
50+
demangle_v_symbol(unsafe { fn_name_ptr.vstring() })
5151
}
5252
// keep it for later
5353
// pc_64 := u64(pc)

vlib/builtin/string_d_ownership.v

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
// Provides .to_owned() which creates an owned string with move semantics.
44
module builtin
55

6+
// IClone marks values that provide explicit clone semantics in ownership mode.
7+
//
8+
// This is a marker interface. Concrete types still define their own `clone()`
9+
// methods with concrete return types.
10+
pub interface IClone {}
11+
612
// to_owned creates an owned copy of the string.
713
// When ownership checking is enabled, owned strings have move semantics:
814
// assigning an owned string to another variable moves ownership,

vlib/v/gen/c/fn.v

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3384,8 +3384,8 @@ fn (mut g Gen) unwrap_receiver_type(node ast.CallExpr) (ast.Type, &ast.TypeSymbo
33843384
mut typ_sym := g.table.sym(unwrapped_rec_type)
33853385
mut left_sym := g.table.sym(left_type)
33863386
if left_type != 0 && left_type != g.unwrap_generic(node.receiver_type)
3387-
&& left_sym.kind != .aggregate
3388-
&& left_sym.has_method(node.name) && node.from_embed_types.len == 0 {
3387+
&& left_sym.kind != .aggregate && left_sym.has_method(node.name)
3388+
&& node.from_embed_types.len == 0 {
33893389
unwrapped_rec_type = left_type
33903390
typ_sym = left_sym
33913391
}
@@ -3522,8 +3522,7 @@ fn (mut g Gen) method_call(node ast.CallExpr) {
35223522
if left_type != 0 && (left_type != g.unwrap_generic(node.receiver_type)
35233523
|| left_type != unwrapped_rec_type) {
35243524
resolved_left_sym := g.table.sym(left_type)
3525-
if resolved_left_sym.kind != .aggregate
3526-
&& (resolved_left_sym.has_method(method_name)
3525+
if resolved_left_sym.kind != .aggregate && (resolved_left_sym.has_method(method_name)
35273526
|| resolved_left_sym.has_method_with_generic_parent(method_name)) {
35283527
unwrapped_rec_type = left_type
35293528
receiver_type = left_type.derive(node.receiver_type).clear_flag(.generic)

vlib/v2/ast/ast.v

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -925,6 +925,7 @@ pub:
925925
attributes []Attribute
926926
is_public bool
927927
is_union bool
928+
implements []Expr
928929
embedded []Expr
929930
language Language = .v
930931
name string

vlib/v2/ast_dump/ast_dump.v

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,11 @@ fn (mut jb JsonBuilder) write_interface_decl(stmt ast.InterfaceDecl) {
655655
jb.write_exprs(stmt.generic_params)
656656
jb.sb.write_string(',\n')
657657

658+
jb.write_indent()
659+
jb.sb.write_string('"implements": ')
660+
jb.write_exprs(stmt.implements)
661+
jb.sb.write_string(',\n')
662+
658663
jb.write_indent()
659664
jb.sb.write_string('"embedded": ')
660665
jb.write_exprs(stmt.embedded)

0 commit comments

Comments
 (0)