Skip to content

Commit adcd421

Browse files
authored
checker: add error message call stack support (requested by #16127, #24575, etc) (#26356)
1 parent f5d9495 commit adcd421

19 files changed

Lines changed: 196 additions & 34 deletions

‎vlib/v/ast/ast.v‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,9 +1108,10 @@ pub mut:
11081108
imported_symbols map[string]string // used for `import {symbol}`, it maps symbol => module.symbol
11091109
imported_symbols_trie token.KeywordsMatcherTrie // constructed from imported_symbols, to accelerate presense checks
11101110
imported_symbols_used map[string]bool
1111-
errors []errors.Error // all the checker errors in the file
1112-
warnings []errors.Warning // all the checker warnings in the file
1113-
notices []errors.Notice // all the checker notices in the file
1111+
errors []errors.Error // all the checker errors in the file
1112+
warnings []errors.Warning // all the checker warnings in the file
1113+
notices []errors.Notice // all the checker notices in the file
1114+
call_stack []errors.CallStackItem // call stack for this file (used for template errors)
11141115
generic_fns []&FnDecl
11151116
global_labels []string // from `asm { .globl labelname }`
11161117
template_paths []string // all the .html/.md files that were processed with $tmpl

‎vlib/v/checker/check_types.v‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1282,6 +1282,7 @@ fn (mut c Checker) infer_fn_generic_types(func &ast.Fn, mut node ast.CallExpr) {
12821282
if c.table.register_fn_concrete_types(func.fkey(), inferred_types) {
12831283
c.need_recheck_generic_fns = true
12841284
}
1285+
c.generic_call_positions[c.build_generic_call_key(func.fkey(), inferred_types)] = node.pos
12851286
}
12861287

12871288
// is_contains_any_kind_of_pointer check that the type and submember types(arrays, fixed arrays, maps, struct fields, and so on)

‎vlib/v/checker/checker.v‎

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -140,15 +140,16 @@ mut:
140140
inside_assign bool
141141
// doing_line_info int // a quick single file run when called with v -line-info (contains line nr to inspect)
142142
// doing_line_path string // same, but stores the path being parsed
143-
is_index_assign bool
144-
comptime_call_pos int // needed for correctly checking use before decl for templates
145-
goto_labels map[string]ast.GotoLabel // to check for unused goto labels
146-
enum_data_type ast.Type
147-
field_data_type ast.Type
148-
variant_data_type ast.Type
149-
fn_return_type ast.Type
150-
orm_table_fields map[string][]ast.StructField // known table structs
151-
short_module_names []string // to check for function names colliding with module functions
143+
is_index_assign bool
144+
comptime_call_pos int // needed for correctly checking use before decl for templates
145+
generic_call_positions map[string]token.Pos // map from generic function key to call position
146+
goto_labels map[string]ast.GotoLabel // to check for unused goto labels
147+
enum_data_type ast.Type
148+
field_data_type ast.Type
149+
variant_data_type ast.Type
150+
fn_return_type ast.Type
151+
orm_table_fields map[string][]ast.StructField // known table structs
152+
short_module_names []string // to check for function names colliding with module functions
152153

153154
v_current_commit_hash string // same as old C.V_CURRENT_COMMIT_HASH
154155
assign_stmt_attr string // for `x := [1,2,3] @[freed]`
@@ -187,6 +188,15 @@ pub fn new_checker(table &ast.Table, pref_ &pref.Preferences) &Checker {
187188
return checker
188189
}
189190

191+
// build_generic_call_key builds a key for tracking generic function call positions
192+
fn (c &Checker) build_generic_call_key(fkey string, concrete_types []ast.Type) string {
193+
mut types_str := ''
194+
for typ in concrete_types {
195+
types_str += c.table.type_to_str(typ) + ','
196+
}
197+
return '${fkey}[${types_str}]'
198+
}
199+
190200
fn (mut c Checker) reset_checker_state_at_start_of_new_file() {
191201
c.expected_type = ast.void_type
192202
c.expected_or_type = ast.void_type

‎vlib/v/checker/comptime.v‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,28 @@ import v.pref
88
import v.util
99
import v.pkgconfig
1010
import v.type_resolver
11+
import v.errors
1112
import strings
1213

1314
fn (mut c Checker) comptime_call(mut node ast.ComptimeCall) ast.Type {
1415
if node.left !is ast.EmptyExpr {
1516
node.left_type = c.expr(mut node.left)
1617
}
1718
if node.kind == .compile_error {
18-
c.error(c.comptime_call_msg(node), node.pos)
19+
// Add call stack information for `$compile_error()`
20+
mut call_stack := []errors.CallStackItem{}
21+
// Only add call stack if we're inside a function (not at module level)
22+
if c.fn_level > 0 && c.table.cur_fn != unsafe { nil } {
23+
call_key := c.build_generic_call_key(c.table.cur_fn.fkey(), c.table.cur_concrete_types)
24+
pos := c.generic_call_positions[call_key] or { c.table.cur_fn.name_pos }
25+
// Use the file path from the position, not the current file
26+
file_path := if pos.file_idx >= 0 { c.table.filelist[pos.file_idx] } else { c.file.path }
27+
call_stack << errors.CallStackItem{
28+
file_path: file_path
29+
pos: pos
30+
}
31+
}
32+
c.error(c.comptime_call_msg(node), node.pos, call_stack: call_stack)
1933
return ast.void_type
2034
} else if node.kind == .compile_warn {
2135
c.warn(c.comptime_call_msg(node), node.pos)

‎vlib/v/checker/errors.v‎

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,15 @@ fn (mut c Checker) add_instruction_for_result_type() {
2828
c.table.cur_fn.return_type_pos)
2929
}
3030

31-
fn (mut c Checker) warn(s string, pos token.Pos) {
31+
@[params]
32+
pub struct MessageOptions {
33+
pub:
34+
call_stack []errors.CallStackItem
35+
}
36+
37+
fn (mut c Checker) warn(s string, pos token.Pos, options MessageOptions) {
3238
allow_warnings := !(c.pref.is_prod || c.pref.warns_are_errors) // allow warnings only in dev builds
33-
c.warn_or_error(s, pos, allow_warnings)
39+
c.warn_or_error(s, pos, allow_warnings, options)
3440
}
3541

3642
fn (mut c Checker) warn_alloc(s string, pos token.Pos) {
@@ -43,7 +49,7 @@ fn (mut c Checker) warn_alloc(s string, pos token.Pos) {
4349
}
4450
}
4551

46-
fn (mut c Checker) error(message string, pos token.Pos) {
52+
fn (mut c Checker) error(message string, pos token.Pos, options MessageOptions) {
4753
if (c.pref.translated || c.file.is_translated) && message.starts_with('mismatched types') {
4854
// TODO: move this
4955
return
@@ -61,17 +67,17 @@ fn (mut c Checker) error(message string, pos token.Pos) {
6167
}
6268
msg += ' (veb action: ${veb_action[..j]})'
6369
}
64-
c.warn_or_error(msg, pos, false)
70+
c.warn_or_error(msg, pos, false, options)
6571
}
6672

67-
fn (mut c Checker) fatal(message string, pos token.Pos) {
73+
fn (mut c Checker) fatal(message string, pos token.Pos, options MessageOptions) {
6874
if (c.pref.translated || c.file.is_translated) && message.starts_with('mismatched types') {
6975
// TODO: move this
7076
return
7177
}
7278
msg := message.replace('`Array_', '`[]')
7379
c.pref.fatal_errors = true
74-
c.warn_or_error(msg, pos, false)
80+
c.warn_or_error(msg, pos, false, options)
7581
}
7682

7783
fn (mut c Checker) note(message string, pos token.Pos) {
@@ -108,7 +114,7 @@ fn (mut c Checker) note(message string, pos token.Pos) {
108114
}
109115
}
110116

111-
fn (mut c Checker) warn_or_error(message string, pos token.Pos, warn bool) {
117+
fn (mut c Checker) warn_or_error(message string, pos token.Pos, warn bool, options MessageOptions) {
112118
if !warn {
113119
$if checker_exit_on_first_error ? {
114120
eprintln('\n\n>> checker error: ${message}, pos: ${pos}')
@@ -148,12 +154,19 @@ fn (mut c Checker) warn_or_error(message string, pos token.Pos, warn bool) {
148154
return
149155
}
150156
if !warn {
157+
// Use provided call_stack or fall back to file.call_stack
158+
actual_call_stack := if options.call_stack.len > 0 {
159+
options.call_stack
160+
} else {
161+
c.file.call_stack
162+
}
151163
if c.pref.fatal_errors {
152164
util.show_compiler_message('error:', errors.CompilerMessage{
153-
pos: pos
154-
file_path: file_path
155-
message: message
156-
details: details
165+
pos: pos
166+
file_path: file_path
167+
message: message
168+
details: details
169+
call_stack: actual_call_stack
157170
})
158171
exit(1)
159172
}
@@ -167,11 +180,12 @@ fn (mut c Checker) warn_or_error(message string, pos token.Pos, warn bool) {
167180
if kpos !in c.error_lines {
168181
c.error_lines[kpos] = true
169182
err := errors.Error{
170-
reporter: errors.Reporter.checker
171-
pos: pos
172-
file_path: file_path
173-
message: message
174-
details: details
183+
reporter: errors.Reporter.checker
184+
pos: pos
185+
file_path: file_path
186+
message: message
187+
details: details
188+
call_stack: actual_call_stack
175189
}
176190
c.file.errors << err
177191
c.errors << err

‎vlib/v/checker/fn.v‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,6 +1000,8 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast.
10001000
if no_exists {
10011001
c.need_recheck_generic_fns = true
10021002
}
1003+
full_fkey := if fn_name_has_dot { fkey } else { c.mod + '.' + fkey }
1004+
c.generic_call_positions[c.build_generic_call_key(full_fkey, concrete_types)] = node.pos
10031005
}
10041006
args_len := node.args.len
10051007
if node.kind == .jsawait {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
vlib/v/checker/tests/compile_error_call_position.vv:5:3: error: son only taken int as input
2+
3 | fn son[T](val T) {
3+
4 | $if T !is int {
4+
5 | $compile_error('son only taken int as input')
5+
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6+
6 | }
7+
7 | }
8+
called from vlib/v/checker/tests/compile_error_call_position.vv:10:2
9+
8 |
10+
9 | fn main() {
11+
10 | son(false)
12+
| ~~~~~~~~~~
13+
11 | }
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module main
2+
3+
fn son[T](val T) {
4+
$if T !is int {
5+
$compile_error('son only taken int as input')
6+
}
7+
}
8+
9+
fn main() {
10+
son(false)
11+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
vlib/v/checker/tests/compile_error_explicit_type.vv:5:3: error: son only take `int` as input
2+
3 | fn son[T](val T) {
3+
4 | $if T !is int {
4+
5 | $compile_error('son only take `int` as input')
5+
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6+
6 | }
7+
7 | }
8+
called from vlib/v/checker/tests/compile_error_explicit_type.vv:11:2
9+
9 | fn main() {
10+
10 | son[int](123)
11+
11 | son[bool](false)
12+
| ~~~~~~~~~~~~~~~~
13+
12 | son[int](456)
14+
13 | son[string]('hello')
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module main
2+
3+
fn son[T](val T) {
4+
$if T !is int {
5+
$compile_error('son only take `int` as input')
6+
}
7+
}
8+
9+
fn main() {
10+
son[int](123)
11+
son[bool](false)
12+
son[int](456)
13+
son[string]('hello')
14+
}

0 commit comments

Comments
 (0)