v4 / vlib / v / checker / used_features.v
568 lines · 538 sloc · 18.13 KB · 5e934d589db08febfd9c80d6351e3b0f5cb626c2
Raw
1// Copyright (c) 2019-2024 Felipe Pena. 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 v.ast
6
7@[inline]
8fn (mut c Checker) markused_comptime_call(check bool, key string) {
9 if check {
10 c.table.used_features.comptime_calls[key] = true
11 }
12}
13
14fn comptime_call_last_arg_type(arg ast.CallArg) ast.Type {
15 return if arg.expr is ast.ArrayDecompose {
16 arg.expr.expr_type
17 } else {
18 arg.typ
19 }
20}
21
22fn (mut c Checker) markused_assertstmt_auto_str(mut node ast.AssertStmt) {
23 if !c.is_builtin_mod && mut node.expr is ast.InfixExpr {
24 left_type := c.unwrap_generic(node.expr.left_type)
25 right_type := c.unwrap_generic(node.expr.right_type)
26 c.markused_auto_str_dependencies(left_type)
27 c.markused_auto_str_dependencies(right_type)
28 if !c.table.used_features.auto_str && !c.table.sym(left_type).has_method('str') {
29 c.table.used_features.auto_str = true
30 return
31 }
32 if !c.table.used_features.auto_str && !c.table.sym(right_type).has_method('str') {
33 c.table.used_features.auto_str = true
34 }
35 }
36}
37
38fn (mut c Checker) markused_dumpexpr(mut node ast.DumpExpr) {
39 if c.is_builtin_mod {
40 return
41 }
42 unwrapped_type := c.unwrap_generic(node.expr_type)
43 if node.expr_type.has_flag(.generic) {
44 c.table.used_features.comptime_syms[unwrapped_type] = true
45 }
46 if !c.table.sym(unwrapped_type).has_method('str') {
47 c.table.used_features.auto_str = true
48 c.markused_auto_str_dependencies(unwrapped_type)
49 if node.expr_type.is_ptr() {
50 c.table.used_features.auto_str_ptr = true
51 }
52 if !c.table.used_features.auto_str_arr {
53 c.table.used_features.auto_str_arr = c.table.final_sym(unwrapped_type).kind == .array
54 }
55 } else {
56 c.markused_generic_str_method(unwrapped_type, c.table.sym(unwrapped_type))
57 c.table.used_features.print_types[node.expr_type.idx()] = true
58 }
59 c.table.used_features.print_types[ast.int_type_idx] = true
60}
61
62@[inline]
63fn (mut c Checker) markused_used_maps(check bool) {
64 if check {
65 c.table.used_features.used_maps++
66 }
67}
68
69fn (mut c Checker) markused_castexpr(mut _ ast.CastExpr, _ ast.Type, mut final_to_sym ast.TypeSymbol) {
70 if c.is_builtin_mod {
71 return
72 }
73 if c.table.used_features.used_maps == 0 && mut final_to_sym.info is ast.SumType {
74 if final_to_sym.info.variants.any(c.table.final_sym(it).kind == .map) {
75 c.table.used_features.used_maps++
76 }
77 }
78}
79
80fn (mut c Checker) markused_comptimecall(mut node ast.ComptimeCall) {
81 c.markused_comptime_call(true,
82 '${int(c.unwrap_generic(c.comptime.comptime_for_method.receiver_type))}.${c.comptime.comptime_for_method.name}')
83 if c.inside_anon_fn {
84 // $method passed to anon fn, mark all methods as used
85 sym := c.table.sym(c.unwrap_generic(node.left_type))
86 for m in sym.get_methods() {
87 c.table.used_features.comptime_calls['${int(c.unwrap_generic(m.receiver_type))}.${m.name}'] = true
88 if node.args.len > 0 && m.params.len > 0 {
89 last_param := m.params.last().typ
90 last_arg_type := comptime_call_last_arg_type(node.args.last())
91 if (last_param.is_int() || last_param.is_float()
92 || last_param.is_bool()) && c.table.final_sym(last_arg_type).kind == .array {
93 c.table.used_features.comptime_calls['${ast.string_type_idx}.${c.table.type_to_str(m.params.last().typ)}'] = true
94 }
95 }
96 }
97 } else {
98 m := c.comptime.comptime_for_method
99 if node.args.len > 0 && m.params.len > 0 {
100 last_param := m.params.last().typ
101 last_arg_type := comptime_call_last_arg_type(node.args.last())
102 if (last_param.is_int() || last_param.is_float() || last_param.is_bool())
103 && c.table.final_sym(last_arg_type).kind == .array {
104 c.table.used_features.comptime_calls['${ast.string_type_idx}.${c.table.type_to_str(m.params.last().typ)}'] = true
105 }
106 }
107 }
108}
109
110fn (mut c Checker) markused_comptimefor(mut _ ast.ComptimeFor, unwrapped_expr_type ast.Type) {
111 if c.table.used_features.used_maps == 0 {
112 final_sym := c.table.final_sym(unwrapped_expr_type)
113 if final_sym.info is ast.Map {
114 c.table.used_features.used_maps++
115 } else if final_sym.info is ast.SumType {
116 if final_sym.info.variants.any(c.table.final_sym(it).kind == .map) {
117 c.table.used_features.used_maps++
118 }
119 }
120 }
121}
122
123fn (mut c Checker) markused_call_expr(left_type ast.Type, mut node ast.CallExpr) {
124 if left_type != 0 && left_type.is_ptr() && !c.table.used_features.auto_str_ptr
125 && node.name == 'str' {
126 c.table.used_features.auto_str_ptr = true
127 if !c.table.used_features.auto_str_arr {
128 c.table.used_features.auto_str_arr = c.table.final_sym(left_type).kind == .array
129 }
130 }
131}
132
133fn (mut c Checker) markused_print_call(mut node ast.CallExpr) {
134 if !c.is_builtin_mod && c.mod != 'math.bits' && node.args[0].expr !is ast.StringLiteral {
135 arg_typ := c.unwrap_generic(node.args[0].typ)
136 if arg_typ == 0 {
137 return
138 }
139 c.markused_auto_str_dependencies(arg_typ)
140 if (node.args[0].expr is ast.CallExpr && node.args[0].expr.is_method
141 && node.args[0].expr.name == 'str')
142 || !c.table.sym(arg_typ).has_method('str') {
143 c.table.used_features.auto_str = true
144 } else {
145 c.mark_type_str_method_as_referenced(arg_typ)
146 if arg_typ.has_option_or_result() {
147 c.table.used_features.print_options = true
148 }
149 c.table.used_features.print_types[arg_typ.idx()] = true
150 if !c.table.used_features.auto_str_ptr && node.args[0].expr is ast.Ident {
151 var_obj := node.args[0].expr.obj
152 if var_obj is ast.Var {
153 if var_obj.orig_type != 0 {
154 fsym := c.table.final_sym(var_obj.orig_type)
155 if fsym.kind == .interface {
156 c.table.used_features.auto_str_ptr = true
157 } else if fsym.kind == .array {
158 c.table.used_features.auto_str_arr = true
159 }
160 return
161 }
162 }
163 }
164 }
165 if arg_typ.is_ptr() {
166 c.table.used_features.auto_str_ptr = true
167 }
168 if !c.table.used_features.auto_str_arr || !c.table.used_features.auto_str_ptr {
169 sym := c.table.final_sym(arg_typ)
170 if sym.kind == .array {
171 c.table.used_features.auto_str_arr = true
172 } else if sym.info is ast.Struct {
173 if !c.table.used_features.auto_str_ptr {
174 c.table.used_features.auto_str_ptr = sym.info.fields.any(it.typ.is_ptr()
175 || it.typ.is_pointer())
176 }
177 c.table.used_features.auto_str_arr =
178 sym.info.fields.any(c.table.final_sym(it.typ).kind == .array)
179 }
180 }
181 }
182}
183
184fn (mut c Checker) markused_method_call(mut node ast.CallExpr, mut left_expr ast.Expr, left_type ast.Type) {
185 if !left_type.has_flag(.generic) && mut left_expr is ast.Ident {
186 if left_expr.obj is ast.Var && left_expr.obj.ct_type_var == .smartcast {
187 c.table.used_features.comptime_calls['${int(left_type)}.${node.name}'] = true
188 }
189 if c.table.sym(left_type).kind == .alias {
190 c.table.used_features.comptime_calls['${int(left_type)}.${node.name}'] = true
191 // Alias method calls can also auto-resolve to pointer receivers in cgen.
192 if !left_type.is_ptr() {
193 c.table.used_features.comptime_calls['${int(left_type.ref())}.${node.name}'] = true
194 }
195 }
196 } else if !left_type.has_flag(.generic) && left_expr is ast.ComptimeSelector {
197 // `x.$(field.name).method()` — the concrete receiver type varies per
198 // outer `$for f in T.fields` iteration, and skip-unused has no way to
199 // see the runtime dispatch. Mark the per-iteration receiver type so
200 // the method survives -skip-unused.
201 c.table.used_features.comptime_calls['${int(left_type)}.${node.name}'] = true
202 if !left_type.is_ptr() {
203 c.table.used_features.comptime_calls['${int(left_type.ref())}.${node.name}'] = true
204 }
205 } else if left_type.has_flag(.generic) {
206 unwrapped_left := c.unwrap_generic(left_type)
207 c.table.used_features.comptime_calls['${int(unwrapped_left)}.${node.name}'] = true
208 // Generic method calls can resolve to pointer receivers during cgen (`x.name()` -> `(&x).name()`).
209 // Mark both forms so skip-unused does not drop pointer receiver methods.
210 if !unwrapped_left.is_ptr() {
211 c.table.used_features.comptime_calls['${int(unwrapped_left.ref())}.${node.name}'] = true
212 }
213 }
214}
215
216fn string_inter_lit_format_uses_str(fmt u8) bool {
217 return fmt == `s`
218}
219
220fn (mut c Checker) markused_string_inter_lit(mut _ ast.StringInterLiteral, ftyp ast.Type, fmt u8) {
221 if c.is_builtin_mod {
222 return
223 }
224 uses_str_format := string_inter_lit_format_uses_str(fmt)
225 if !c.table.sym(ftyp).has_method('str') {
226 if uses_str_format {
227 c.table.used_features.auto_str = true
228 c.markused_auto_str_dependencies(ftyp)
229 }
230 } else {
231 if uses_str_format {
232 c.markused_generic_str_method(ftyp, c.table.sym(ftyp))
233 c.mark_type_str_method_as_referenced(ftyp)
234 c.table.used_features.print_types[ftyp.idx()] = true
235 }
236 }
237 if ftyp.is_ptr() {
238 c.table.used_features.auto_str_ptr = true
239 }
240 if !c.table.used_features.auto_str_arr {
241 c.table.used_features.auto_str_arr = c.table.final_sym(ftyp).kind == .array
242 }
243 if ftyp.has_option_or_result() {
244 c.table.used_features.print_options = true
245 }
246}
247
248fn (mut c Checker) markused_auto_str_dependencies(typ ast.Type) {
249 mut visited := map[ast.Type]bool{}
250 c.markused_auto_str_dependencies_for_type(typ, mut visited)
251}
252
253fn (mut c Checker) markused_auto_str_dependencies_for_type(typ ast.Type, mut visited map[ast.Type]bool) {
254 base_typ := c.unwrap_generic(typ.clear_option_and_result().clear_flags(.variadic, .shared_f,
255 .atomic_f).clear_ref())
256 if base_typ == 0 || visited[base_typ] {
257 return
258 }
259 visited[base_typ] = true
260 mut sym := c.table.sym(base_typ)
261 has_parent_str := sym.has_method_with_generic_parent('str')
262 has_exact_str := sym.has_method('str')
263 if has_parent_str || has_exact_str {
264 c.markused_generic_str_method(base_typ, sym)
265 return
266 }
267 if sym.info is ast.Alias {
268 c.markused_auto_str_dependencies_for_type(sym.info.parent_type.derive(base_typ), mut
269 visited)
270 return
271 }
272 sym = c.table.final_sym(base_typ)
273 if sym.has_method_with_generic_parent('str') || sym.has_method('str') {
274 c.markused_generic_str_method(ast.idx_to_type(sym.idx), sym)
275 return
276 }
277 match sym.info {
278 ast.Array {
279 c.markused_auto_str_dependencies_for_type(sym.info.elem_type, mut visited)
280 }
281 ast.ArrayFixed {
282 c.markused_auto_str_dependencies_for_type(sym.info.elem_type, mut visited)
283 }
284 ast.Chan {
285 c.markused_auto_str_dependencies_for_type(sym.info.elem_type, mut visited)
286 }
287 ast.Interface {
288 for concrete_type in sym.info.types {
289 c.markused_auto_str_dependencies_for_type(concrete_type, mut visited)
290 }
291 }
292 ast.Map {
293 c.markused_auto_str_dependencies_for_type(sym.info.key_type, mut visited)
294 c.markused_auto_str_dependencies_for_type(sym.info.value_type, mut visited)
295 }
296 ast.MultiReturn {
297 for ret_type in sym.info.types {
298 c.markused_auto_str_dependencies_for_type(ret_type, mut visited)
299 }
300 }
301 ast.Struct {
302 for field in sym.info.fields {
303 if attr := field.attrs.find_first('str') {
304 if attr.arg == 'skip' {
305 continue
306 }
307 }
308 c.markused_auto_str_dependencies_for_type(field.typ, mut visited)
309 }
310 }
311 ast.SumType {
312 for variant in sym.info.variants {
313 c.markused_auto_str_dependencies_for_type(variant, mut visited)
314 }
315 }
316 ast.Thread {
317 c.markused_auto_str_dependencies_for_type(sym.info.return_type, mut visited)
318 }
319 else {}
320 }
321}
322
323fn (mut c Checker) markused_generic_str_method(typ ast.Type, sym &ast.TypeSymbol) {
324 mut method := ast.Fn{}
325 mut concrete_types := []ast.Type{}
326 if structured_method := c.table.find_structured_receiver_method_with_types(typ, 'str') {
327 if exact_method := sym.find_method('str') {
328 method = exact_method
329 } else if exact_method := c.table.find_alias_parent_exact_method(typ, 'str') {
330 method = exact_method
331 } else {
332 method = structured_method.method
333 concrete_types = structured_method.concrete_types.map(c.unwrap_generic(it))
334 }
335 } else {
336 method = sym.find_method_with_generic_parent('str') or {
337 sym.find_method('str') or { return }
338 }
339 }
340 if concrete_types.len == 0 {
341 concrete_types = c.concrete_types_for_generic_str_receiver(typ, sym, method)
342 }
343 if method.generic_names.len != concrete_types.len || concrete_types.len == 0
344 || concrete_types.any(it.has_flag(.generic) || c.type_has_unresolved_generic_parts(it)) {
345 return
346 }
347 mut registered := c.register_auto_str_fn_concrete_types(method.fkey(), concrete_types)
348 decl_fkey := method_decl_fkey(method)
349 if decl_fkey != method.fkey() {
350 registered = c.register_auto_str_fn_concrete_types(decl_fkey, concrete_types) || registered
351 }
352 for resolved_decl_fkey in c.str_method_decl_fkeys_for_type(typ, method) {
353 if resolved_decl_fkey != method.fkey() && resolved_decl_fkey != decl_fkey {
354 registered = c.register_auto_str_fn_concrete_types(resolved_decl_fkey, concrete_types)
355 || registered
356 }
357 }
358 if registered {
359 c.need_recheck_generic_fns = true
360 }
361}
362
363fn (mut c Checker) register_auto_str_fn_concrete_types(fkey string, concrete_types []ast.Type) bool {
364 if fkey == '' {
365 return false
366 }
367 if _ := c.table.fn_generic_types[fkey] {
368 // The generic function key is already registered.
369 } else {
370 c.table.register_fn_generic_types(fkey)
371 }
372 return c.table.register_fn_concrete_types(fkey, concrete_types)
373}
374
375fn method_decl_fkey(method ast.Fn) string {
376 if method.source_fn != unsafe { nil } {
377 fndecl := unsafe { &ast.FnDecl(method.source_fn) }
378 return fndecl.fkey()
379 }
380 return method.fkey()
381}
382
383fn (c &Checker) generic_parent_str_method_fkey(sym &ast.TypeSymbol) string {
384 match sym.info {
385 ast.Struct, ast.Interface, ast.SumType {
386 if sym.info.parent_type.has_flag(.generic) {
387 parent_sym := c.table.sym(sym.info.parent_type)
388 if method := parent_sym.find_method('str') {
389 return method_decl_fkey(method)
390 }
391 }
392 }
393 ast.GenericInst {
394 if sym.info.parent_idx > 0 {
395 parent_sym := c.table.sym(ast.idx_to_type(sym.info.parent_idx))
396 if method := parent_sym.find_method('str') {
397 return method_decl_fkey(method)
398 }
399 }
400 }
401 else {}
402 }
403
404 return ''
405}
406
407fn add_str_method_decl_fkey(mut fkeys []string, fkey string) {
408 if fkey != '' && fkey !in fkeys {
409 fkeys << fkey
410 }
411}
412
413fn add_matching_str_method_decl_fkeys(mut fkeys []string, candidate_fkeys []string, resolved_decl_fkey string) {
414 if resolved_decl_fkey in candidate_fkeys {
415 for fkey in candidate_fkeys {
416 add_str_method_decl_fkey(mut fkeys, fkey)
417 }
418 }
419}
420
421fn (c &Checker) str_method_decl_fkeys_for_type(typ ast.Type, method ast.Fn) []string {
422 mut fkeys := []string{}
423 if typ == 0 {
424 return fkeys
425 }
426 resolved_decl_fkey := method_decl_fkey(method)
427 for fkey in c.str_method_decl_fkeys_for_candidate(typ) {
428 add_str_method_decl_fkey(mut fkeys, fkey)
429 }
430 unaliased := c.table.unaliased_type(typ)
431 if unaliased != 0 && unaliased != typ {
432 add_matching_str_method_decl_fkeys(mut fkeys,
433 c.str_method_decl_fkeys_for_candidate(unaliased), resolved_decl_fkey)
434 }
435 if !typ.is_ptr() {
436 ref_typ := typ.ref()
437 if ref_typ != 0 {
438 add_matching_str_method_decl_fkeys(mut fkeys,
439 c.str_method_decl_fkeys_for_candidate(ref_typ), resolved_decl_fkey)
440 }
441 } else {
442 deref_typ := typ.deref()
443 if deref_typ != 0 {
444 add_matching_str_method_decl_fkeys(mut fkeys,
445 c.str_method_decl_fkeys_for_candidate(deref_typ), resolved_decl_fkey)
446 }
447 }
448 return fkeys
449}
450
451fn (c &Checker) str_method_decl_fkeys_for_candidate(candidate ast.Type) []string {
452 mut fkeys := []string{}
453 sym := c.table.sym(candidate)
454 parent_fkey := c.generic_parent_str_method_fkey(sym)
455 if parent_fkey != '' {
456 add_str_method_decl_fkey(mut fkeys, parent_fkey)
457 }
458 if method := sym.find_method_with_generic_parent('str') {
459 add_str_method_decl_fkey(mut fkeys, method_decl_fkey(method))
460 }
461 if method := c.table.find_method(sym, 'str') {
462 add_str_method_decl_fkey(mut fkeys, method_decl_fkey(method))
463 }
464 method, embed_types := c.table.find_method_from_embeds(c.table.final_sym(candidate), 'str') or {
465 ast.Fn{}, []ast.Type{}
466 }
467 if embed_types.len != 0 {
468 add_str_method_decl_fkey(mut fkeys, method_decl_fkey(method))
469 }
470 return fkeys
471}
472
473fn (mut c Checker) concrete_types_for_generic_str_receiver(typ ast.Type, sym &ast.TypeSymbol, method ast.Fn) []ast.Type {
474 mut concrete_types := c.concrete_types_for_type_symbol(sym)
475 if concrete_types.len == 0 && sym.info is ast.Alias {
476 concrete_types = c.alias_parent_concrete_types(sym.info)
477 }
478 if method.generic_names.len == 0
479 || (concrete_types.len > 0 && concrete_types.len == method.generic_names.len) {
480 return concrete_types
481 }
482 return c.infer_method_receiver_concrete_types(method, typ)
483}
484
485fn (c &Checker) alias_parent_concrete_types(info ast.Alias) []ast.Type {
486 parent_sym := c.table.sym(info.parent_type)
487 match parent_sym.info {
488 ast.Struct, ast.SumType, ast.Interface {
489 mut concrete_types := parent_sym.info.concrete_types.clone()
490 if concrete_types.len == 0
491 && parent_sym.generic_types.len == parent_sym.info.generic_types.len
492 && parent_sym.generic_types != parent_sym.info.generic_types {
493 concrete_types = parent_sym.generic_types.clone()
494 }
495 return concrete_types
496 }
497 ast.GenericInst {
498 return parent_sym.info.concrete_types.clone()
499 }
500 ast.Array {
501 return [parent_sym.info.elem_type]
502 }
503 ast.ArrayFixed {
504 return [parent_sym.info.elem_type]
505 }
506 ast.Chan {
507 return [parent_sym.info.elem_type]
508 }
509 ast.Map {
510 return [parent_sym.info.key_type, parent_sym.info.value_type]
511 }
512 ast.Alias {
513 return c.alias_parent_concrete_types(parent_sym.info)
514 }
515 else {}
516 }
517
518 return []ast.Type{}
519}
520
521fn (mut c Checker) infer_method_receiver_concrete_types(method ast.Fn, typ ast.Type) []ast.Type {
522 mut concrete_types := []ast.Type{cap: method.generic_names.len}
523 for generic_name in method.generic_names {
524 mut concrete_type := c.infer_composite_generic_type(generic_name, method.receiver_type, typ)
525 if concrete_type == ast.void_type {
526 unaliased_receiver_type := c.table.fully_unaliased_type(method.receiver_type)
527 unaliased_typ := c.table.fully_unaliased_type(typ)
528 if unaliased_receiver_type != method.receiver_type || unaliased_typ != typ {
529 concrete_type = c.infer_composite_generic_type(generic_name,
530 unaliased_receiver_type, unaliased_typ)
531 }
532 }
533 if concrete_type == ast.void_type {
534 return []ast.Type{}
535 }
536 concrete_types << c.unwrap_generic(concrete_type)
537 }
538 return concrete_types
539}
540
541fn (mut c Checker) markused_array_method(check bool, method_name string) {
542 if !check {
543 return
544 }
545 match method_name {
546 '' { // array init
547 }
548 'first' {
549 c.table.used_features.arr_first = true
550 }
551 'last' {
552 c.table.used_features.arr_last = true
553 }
554 'pop_left' {
555 c.table.used_features.arr_pop_left = true
556 }
557 'pop' {
558 c.table.used_features.arr_pop = true
559 }
560 'delete' {
561 c.table.used_features.arr_delete = true
562 }
563 'map' {
564 c.table.used_features.arr_map = true
565 }
566 else {}
567 }
568}
569