v2 / vlib / v / checker / errors.v
248 lines · 231 sloc · 6.93 KB · 497bfade251b0a4d0e75e1a1d1d6bba23e6aa60b
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license that can be found in the LICENSE file.
3module checker
4
5import time
6import v.ast
7import v.token
8import v.errors
9import v.util
10
11// call this *before* calling error or warn
12fn (mut c Checker) add_error_detail(s string) {
13 c.error_details << s
14}
15
16fn (mut c Checker) add_error_detail_with_pos(msg string, pos token.Pos) {
17 file_path := if pos.file_idx < 0 { c.file.path } else { c.table.filelist[pos.file_idx] }
18 c.add_error_detail(util.formatted_error('details:', msg, file_path, pos))
19}
20
21fn (mut c Checker) add_instruction_for_option_type() {
22 c.add_error_detail_with_pos('prepend ? before the declaration of the return type of `${c.table.cur_fn.name}`',
23 c.table.cur_fn.return_type_pos)
24}
25
26fn (mut c Checker) add_instruction_for_result_type() {
27 c.add_error_detail_with_pos('prepend ! before the declaration of the return type of `${c.table.cur_fn.name}`',
28 c.table.cur_fn.return_type_pos)
29}
30
31@[params]
32pub struct MessageOptions {
33pub:
34 call_stack []errors.CallStackItem
35}
36
37fn (mut c Checker) warn(s string, pos token.Pos, options MessageOptions) {
38 allow_warnings := !(c.pref.is_prod || c.pref.warns_are_errors) // allow warnings only in dev builds
39 c.warn_or_error(s, pos, allow_warnings, options)
40}
41
42fn (mut c Checker) warn_alloc(s string, pos token.Pos) {
43 if c.assign_stmt_attr == 'freed' {
44 return
45 }
46
47 if !c.is_builtin_mod && c.mod !in ['strings', 'math', 'math.bits', 'builtin', 'strconv'] {
48 c.warn('allocation (${s})', pos)
49 }
50}
51
52fn (mut c Checker) error(message string, pos token.Pos, options MessageOptions) {
53 if (c.pref.translated || c.file.is_translated) && message.starts_with('mismatched types') {
54 // TODO: move this
55 return
56 }
57 mut msg := message.replace('`Array_', '`[]')
58 if c.pref.is_template { // set during veb template checking
59 // Show in which veb action the error occurred (for easier debugging)
60 veb_action := c.table.cur_fn.name.replace('veb_tmpl_', '')
61 mut j := 0
62 for _, ch in veb_action {
63 if ch.is_digit() {
64 break
65 }
66 j++
67 }
68 msg += ' (veb action: ${veb_action[..j]})'
69 }
70 c.warn_or_error(msg, pos, false, options)
71}
72
73fn (mut c Checker) fatal(message string, pos token.Pos, options MessageOptions) {
74 if (c.pref.translated || c.file.is_translated) && message.starts_with('mismatched types') {
75 // TODO: move this
76 return
77 }
78 msg := message.replace('`Array_', '`[]')
79 c.pref.fatal_errors = true
80 c.warn_or_error(msg, pos, false, options)
81}
82
83fn (mut c Checker) note(message string, pos token.Pos) {
84 if c.is_generated {
85 return
86 }
87 if c.pref.notes_are_errors {
88 c.error(message, pos)
89 }
90 mut details := ''
91 if c.error_details.len > 0 {
92 details = c.error_details.join('\n')
93 c.error_details = []
94 }
95 // deduplicate notices for the same line
96 file_path := if pos.file_idx < 0 { c.file.path } else { c.table.filelist[pos.file_idx] }
97 kpos := '${file_path}:${pos.line_nr}:${message}'
98 if kpos !in c.notice_lines {
99 c.notice_lines[kpos] = true
100 c.nr_notices++
101 if c.pref.message_limit >= 0 && c.notices.len >= c.pref.message_limit {
102 return
103 }
104 note := errors.Notice{
105 reporter: errors.Reporter.checker
106 pos: pos
107 file_path: file_path
108 message: message
109 details: details
110 }
111 c.file.notices << note
112 c.notices << note
113 }
114}
115
116fn (mut c Checker) warn_or_error(message string, pos token.Pos, warn bool, options MessageOptions) {
117 if !warn {
118 $if checker_exit_on_first_error ? {
119 eprintln('\n\n>> checker error: ${message}, pos: ${pos}')
120 print_backtrace()
121 exit(1)
122 }
123 if c.pref.is_verbose {
124 print_backtrace()
125 }
126 }
127 mut details := ''
128 if c.error_details.len > 0 {
129 details = c.error_details.join('\n')
130 c.error_details = []
131 }
132 file_path := if pos.file_idx < 0 { c.file.path } else { c.table.filelist[pos.file_idx] }
133 if warn && !c.pref.skip_warnings {
134 c.nr_warnings++
135 if c.pref.message_limit >= 0 && c.warnings.len >= c.pref.message_limit {
136 return
137 }
138 // deduplicate warnings for the same line
139 kpos := '${file_path}:${pos.line_nr}:${message}'
140 if kpos !in c.warning_lines {
141 c.warning_lines[kpos] = true
142 wrn := errors.Warning{
143 reporter: errors.Reporter.checker
144 pos: pos
145 file_path: file_path
146 message: message
147 details: details
148 }
149 c.file.warnings << wrn
150 c.warnings << wrn
151 }
152 return
153 }
154 if !warn {
155 // Use provided call_stack or fall back to file.call_stack
156 actual_call_stack := if options.call_stack.len > 0 {
157 options.call_stack
158 } else {
159 c.file.call_stack
160 }
161 if c.pref.fatal_errors {
162 util.show_compiler_message('error:', errors.CompilerMessage{
163 pos: pos
164 file_path: file_path
165 message: message
166 details: details
167 call_stack: actual_call_stack
168 })
169 exit(1)
170 }
171 c.nr_errors++
172 if c.pref.message_limit >= 0 && c.errors.len >= c.pref.message_limit {
173 c.should_abort = true
174 return
175 }
176 // deduplicate errors for the same line
177 kpos := '${file_path}:${pos.line_nr}:${message}'
178 if kpos !in c.error_lines {
179 c.error_lines[kpos] = true
180 err := errors.Error{
181 reporter: errors.Reporter.checker
182 pos: pos
183 file_path: file_path
184 message: message
185 details: details
186 call_stack: actual_call_stack
187 }
188 c.file.errors << err
189 c.errors << err
190 }
191 }
192}
193
194// for debugging only
195fn (c &Checker) fileis(s string) bool {
196 return c.file.path.contains(s)
197}
198
199fn (mut c Checker) trace[T](fbase string, x &T) {
200 if c.file.path_base == fbase {
201 println('> c.trace | ${fbase:-10s} | ${x}')
202 }
203}
204
205fn (mut c Checker) deprecate(kind string, name string, attrs []ast.Attr, pos token.Pos) {
206 // println('deprecate kind=${kind} name=${name} attrs=${attrs}')
207 // print_backtrace()
208 mut deprecation_message := ''
209 now := time.now()
210 mut after_time := now
211 for attr in attrs {
212 if attr.arg == '' {
213 continue
214 }
215 if attr.name == 'deprecated' {
216 deprecation_message = attr.arg
217 } else if attr.name == 'deprecated_after' {
218 after_time = time.parse_iso8601(attr.arg) or {
219 c.error('invalid time format', attr.pos)
220 now
221 }
222 }
223 }
224 start_message := '${kind} `${name}`'
225 error_time := after_time.add_days(180)
226 if error_time < now {
227 c.error(semicolonize('${start_message} has been deprecated since ${after_time.ymmdd()}',
228 deprecation_message), pos)
229 } else if after_time < now {
230 c.warn(semicolonize('${start_message} has been deprecated since ${after_time.ymmdd()}, it will be an error after ${error_time.ymmdd()}',
231 deprecation_message), pos)
232 } else if after_time == now {
233 // print_backtrace()
234 c.warn(semicolonize('${start_message} has been deprecated', deprecation_message), pos)
235 // c.warn(semicolonize('${start_message} has been deprecated!11 m=${deprecation_message}',
236 // deprecation_message), pos)
237 } else {
238 c.note(semicolonize('${start_message} will be deprecated after ${after_time.ymmdd()}, and will become an error after ${error_time.ymmdd()}',
239 deprecation_message), pos)
240 }
241}
242
243fn semicolonize(main string, details string) string {
244 if details == '' {
245 return main
246 }
247 return '${main}; ${details}'
248}
249