Skip to content

Commit 45b79df

Browse files
authored
vet: add an -I option to notice fns, with the potential to be inlined (#23534)
1 parent 4e68a86 commit 45b79df

7 files changed

Lines changed: 74 additions & 21 deletions

File tree

‎cmd/tools/vast/vast.v‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,7 @@ fn (t Tree) fn_decl(node ast.FnDecl) &Node {
582582
obj.add('is_direct_arr', t.bool_node(node.is_direct_arr))
583583
obj.add('ctdefine_idx', t.number_node(node.ctdefine_idx))
584584
obj.add('pos', t.pos(node.pos))
585+
obj.add('end_pos', t.pos(node.end_pos))
585586
obj.add('body_pos', t.pos(node.body_pos))
586587
obj.add('return_type_pos', t.pos(node.return_type_pos))
587588
obj.add('file', t.string_node(node.file))

‎cmd/tools/vvet/analyze.v‎

Lines changed: 61 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,36 @@ module main
44

55
import v.ast
66
import v.token
7+
import os
78
import arrays
89

910
// cutoffs
10-
const indexexpr_cutoff = 10
11-
const infixexpr_cutoff = 10
12-
const selectorexpr_cutoff = 10
13-
const callexpr_cutoff = 10
14-
const stringinterliteral_cutoff = 10
15-
const stringliteral_cutoff = 10
16-
const ascast_cutoff = 10
17-
const stringconcat_cutoff = 10
11+
const indexexpr_cutoff = os.getenv_opt('VET_INDEXEXPR_CUTOFF') or { '10' }.int()
12+
const infixexpr_cutoff = os.getenv_opt('VET_INFIXEXPR_CUTOFF') or { '10' }.int()
13+
const selectorexpr_cutoff = os.getenv_opt('VET_SELECTOREXPR_CUTOFF') or { '10' }.int()
14+
const callexpr_cutoff = os.getenv_opt('VET_CALLEXPR_CUTOFF') or { '10' }.int()
15+
const stringinterliteral_cutoff = os.getenv_opt('STRINGINTERLITERAL_CUTOFF') or { '10' }.int()
16+
const stringliteral_cutoff = os.getenv_opt('STRINGLITERAL_CUTOFF') or { '10' }.int()
17+
const ascast_cutoff = os.getenv_opt('ASCAST_CUTOFF') or { '10' }.int()
18+
const stringconcat_cutoff = os.getenv_opt('STRINGCONCAT_CUTOFF') or { '10' }.int()
19+
20+
// possibly inline fn cutoff
21+
const fns_call_cutoff = os.getenv_opt('VET_FNS_CALL_CUTOFF') or { '10' }.int() // at least N calls
22+
const short_fns_cutoff = os.getenv_opt('VET_SHORT_FNS_CUTOFF') or { '3' }.int() // lines
1823

1924
// minimum size for string literals
20-
const stringliteral_min_size = 20
25+
const stringliteral_min_size = os.getenv_opt('VET_STRINGLITERAL_MIN_SIZE') or { '20' }.int()
2126

2227
// long functions cutoff
23-
const long_fns_cutoff = 300
28+
const long_fns_cutoff = os.getenv_opt('VET_LONG_FNS_CUTOFF') or { '300' }.int()
2429

2530
struct VetAnalyze {
2631
mut:
27-
repeated_expr_cutoff shared map[string]int // repeated code cutoff
28-
repeated_expr shared map[string]map[string]map[string][]token.Pos // repeated exprs in fn scope
29-
cur_fn ast.FnDecl // current fn declaration
32+
repeated_expr_cutoff shared map[string]int // repeated code cutoff
33+
repeated_expr shared map[string]map[string]map[string][]token.Pos // repeated exprs in fn scope
34+
potential_non_inlined shared map[string]map[string]token.Pos // fns might be inlined
35+
call_counter shared map[string]int // fn call counter
36+
cur_fn ast.FnDecl // current fn declaration
3037
}
3138

3239
// stmt checks for repeated code in statements
@@ -80,9 +87,18 @@ fn (mut vt VetAnalyze) expr(vet &Vet, expr ast.Expr) {
8087
}
8188
ast.CallExpr {
8289
if expr.is_static_method || expr.is_method {
83-
vt.save_expr(callexpr_cutoff, '${expr.left}.${expr.name}(${expr.args.map(it.str()).join(', ')})',
90+
left_str := expr.left.str()
91+
lock vt.call_counter {
92+
if vt.cur_fn.receiver.name == left_str {
93+
vt.call_counter['${int(vt.cur_fn.receiver.typ)}.${expr.name}']++
94+
}
95+
}
96+
vt.save_expr(callexpr_cutoff, '${left_str}.${expr.name}(${expr.args.map(it.str()).join(', ')})',
8497
vet.file, expr.pos)
8598
} else {
99+
lock vt.call_counter {
100+
vt.call_counter[expr.name]++
101+
}
86102
vt.save_expr(callexpr_cutoff, '${expr.name}(${expr.args.map(it.str()).join(', ')})',
87103
vet.file, expr.pos)
88104
}
@@ -104,18 +120,27 @@ fn (mut vt VetAnalyze) expr(vet &Vet, expr ast.Expr) {
104120

105121
// long_or_empty_fns checks for long or empty functions
106122
fn (mut vt VetAnalyze) long_or_empty_fns(mut vet Vet, fn_decl ast.FnDecl) {
107-
nr_lines := if fn_decl.stmts.len == 0 {
108-
0
109-
} else {
110-
fn_decl.stmts.last().pos.line_nr - fn_decl.pos.line_nr
111-
}
123+
nr_lines := fn_decl.end_pos.line_nr - fn_decl.pos.line_nr - 2
112124
if nr_lines > long_fns_cutoff {
113125
vet.notice('Long function - ${nr_lines} lines long.', fn_decl.pos.line_nr, .long_fns)
114126
} else if nr_lines == 0 {
115127
vet.notice('Empty function.', fn_decl.pos.line_nr, .empty_fn)
116128
}
117129
}
118130

131+
// potential_non_inlined checks for potential fns to be inlined
132+
fn (mut vt VetAnalyze) potential_non_inlined(mut vet Vet, fn_decl ast.FnDecl) {
133+
nr_lines := fn_decl.end_pos.line_nr - fn_decl.pos.line_nr - 2
134+
if nr_lines < short_fns_cutoff {
135+
attr := fn_decl.attrs.find_first('inline')
136+
if attr == none {
137+
lock vt.potential_non_inlined {
138+
vt.potential_non_inlined[fn_decl.fkey()][vet.file] = fn_decl.pos
139+
}
140+
}
141+
}
142+
}
143+
119144
// vet_fn_analysis reports repeated code by scope
120145
fn (mut vt VetAnalyze) vet_repeated_code(mut vet Vet) {
121146
rlock vt.repeated_expr {
@@ -137,9 +162,26 @@ fn (mut vt VetAnalyze) vet_repeated_code(mut vet Vet) {
137162
}
138163
}
139164

165+
// vet_inlining_fn reports possible fn to be inlined
166+
fn (mut vt VetAnalyze) vet_inlining_fn(mut vet Vet) {
167+
for fn_name, info in vt.potential_non_inlined {
168+
for file, pos in info {
169+
calls := vt.call_counter[fn_name] or { 0 }
170+
if calls < fns_call_cutoff {
171+
continue
172+
}
173+
vet.notice_with_file(file, '${fn_name.all_after('.')} fn might be inlined (possibly called at least ${calls} times)',
174+
pos.line_nr, .inline_fn)
175+
}
176+
}
177+
}
178+
140179
// vet_code_analyze performs code analysis
141180
fn (mut vt Vet) vet_code_analyze() {
142181
if vt.opt.repeated_code {
143182
vt.analyze.vet_repeated_code(mut vt)
144183
}
184+
if vt.opt.fn_inlining {
185+
vt.analyze.vet_inlining_fn(mut vt)
186+
}
145187
}

‎cmd/tools/vvet/errors.v‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ pub enum FixKind {
1616
repeated_code
1717
long_fns
1818
empty_fn
19+
inline_fn
1920
}
2021

2122
// ErrorType is used to filter out false positive errors under specific conditions
2223
pub enum ErrorType {
2324
default
2425
space_indent
2526
trailing_space
26-
long_fns
2727
}
2828

2929
@[minify]

‎cmd/tools/vvet/vvet.v‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ struct Options {
3232
doc_private_fns_too bool
3333
fn_sizing bool
3434
repeated_code bool
35+
fn_inlining bool
3536
mut:
3637
is_vfmt_off bool
3738
}
@@ -52,6 +53,7 @@ fn main() {
5253
|| (term_colors && '-nocolor' !in vet_options)
5354
repeated_code: '-r' in vet_options
5455
fn_sizing: '-F' in vet_options
56+
fn_inlining: '-I' in vet_options
5557
}
5658
}
5759
mut paths := cmdline.only_non_options(vet_options)
@@ -296,6 +298,9 @@ fn (mut vt Vet) stmt(stmt ast.Stmt) {
296298
if vt.opt.fn_sizing {
297299
vt.analyze.long_or_empty_fns(mut vt, stmt)
298300
}
301+
if vt.opt.fn_inlining {
302+
vt.analyze.potential_non_inlined(mut vt, stmt)
303+
}
299304
vt.analyze.cur_fn = old_fn_decl
300305
}
301306
ast.StructDecl {

‎vlib/v/ast/ast.v‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,7 @@ pub mut:
630630
scope &Scope = unsafe { nil }
631631
label_names []string
632632
pos token.Pos // function declaration position
633+
end_pos token.Pos // end position
633634
//
634635
is_expand_simple_interpolation bool // true, when @[expand_simple_interpolation] is used on a fn. It should have a single string argument.
635636
}

‎vlib/v/help/common/vet.txt‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ Options:
1212

1313
-v, -verbose Enable verbose logging.
1414

15-
-F Report empty and long function declaration (>300 lines).
15+
-F Report empty and long function declaration
16+
(default: >300 lines).
17+
18+
-I Report potential function to be inlined.
1619

1720
-p Report private functions with missing documentation too
1821
(by default, only the `pub fn` functions will be reported).

‎vlib/v/parser/fn.v‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,7 @@ run them via `v file.v` instead',
707707
language: language
708708
no_body: no_body
709709
pos: start_pos.extend_with_last_line(end_pos, p.prev_tok.line_nr)
710+
end_pos: p.tok.pos()
710711
name_pos: name_pos
711712
body_pos: body_start_pos
712713
file: p.file_path

0 commit comments

Comments
 (0)