1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

all: improve -skip-unused, track consts, walk all AST nodes, support tests

This commit is contained in:
Delyan Angelov 2021-02-07 01:01:37 +02:00
parent 26121d5ae7
commit d77bb2f606
No known key found for this signature in database
GPG Key ID: 66886C0F12D595ED
12 changed files with 410 additions and 95 deletions

View File

@ -26,7 +26,7 @@ pub fn (d float_literal) str() string {
return f64(d).str()
}
// strsci returns the `f64` as a `string` in scientific notation with `digit_num` deciamals displayed, max 17 digits.
// strsci returns the `f64` as a `string` in scientific notation with `digit_num` decimals displayed, max 17 digits.
// Example: assert f64(1.234).strsci(3) == '1.234e+00'
[inline]
pub fn (x f64) strsci(digit_num int) string {

View File

@ -8,46 +8,47 @@ import v.checker.mark_used_walker
// mark_used walks the AST, starting at main() and marks all used fns transitively
fn (mut c Checker) mark_used(ast_files []ast.File) {
util.timing_start(@METHOD)
mut walker := mark_used_walker.Walker{
files: ast_files
}
mut allfns := map[string]ast.FnDecl{}
util.timing_start('all_fn_and_const')
mut all_fns := map[string]ast.FnDecl{}
mut all_consts := map[string]ast.ConstField{}
for i in 0 .. ast_files.len {
file := unsafe { &ast_files[i] }
for node in file.stmts {
if node is ast.FnDecl {
fkey := if node.is_method {
'${int(node.receiver.typ)}.$node.name'
} else {
node.name
match node {
ast.FnDecl {
fkey := if node.is_method {
'${int(node.receiver.typ)}.$node.name'
} else {
node.name
}
all_fns[fkey] = node
}
allfns[fkey] = node
ast.ConstDecl {
for cfield in node.fields {
ckey := cfield.name
all_consts[ckey] = cfield
}
}
else {}
}
}
}
all_fn_root_names := [
util.timing_measure('all_fn_and_const')
mut all_fn_root_names := [
'main.main',
'__new_array',
'__new_array_with_default',
'new_array_from_c_array',
'panic',
'memdup',
'vstrlen',
'tos2',
'tos',
'tos2',
'tos3',
'isnil',
'utf8_char_len',
/* utf8_str_visible_length is used by c/str.v */
'utf8_str_visible_length',
'builtin_init',
'print_backtrace_skipping_top_frames',
'print_backtrace_skipping_top_frames_mac',
'print_backtrace_skipping_top_frames_linux',
'print_backtrace_skipping_top_frames_freebsd',
'print_backtrace_skipping_top_frames_windows',
'print_backtrace_skipping_top_frames_mingw',
'print_backtrace_skipping_top_frames_msvc',
'print_backtrace_skipping_top_frames_tcc',
/* byteptr and charptr */
'3.vstring',
'3.vstring_with_len',
@ -55,8 +56,6 @@ fn (mut c Checker) mark_used(ast_files []ast.File) {
'4.vstring_with_len',
/* string. methods */
'18.add',
'18.all_after',
'18.all_before',
'18.trim_space',
'18.replace',
'18.clone',
@ -81,9 +80,13 @@ fn (mut c Checker) mark_used(ast_files []ast.File) {
'19.add',
/* other array methods */
'21.get',
'21.set',
'21.get_unsafe',
'21.set_unsafe',
'21.slice',
'21.slice2',
'59.get',
'65557.free',
'59.set',
'65557.push',
'65557.set',
'65557.set_unsafe',
@ -91,28 +94,75 @@ fn (mut c Checker) mark_used(ast_files []ast.File) {
'os.getwd',
'os.init_os_args',
]
// println( allfns.keys() )
for fn_name in all_fn_root_names {
walker.fn_decl(mut allfns[fn_name])
// implicit string builders are generated in auto_eq_methods.v
mut sb_mut_type := ''
if sbfn := c.table.find_fn('strings.new_builder') {
sb_mut_type = sbfn.return_type.set_nr_muls(1).str() + '.'
}
$if trace_skip_unused ? {
for k, _ in all_fns {
if k.ends_with('.init') {
all_fn_root_names << k
}
if k.ends_with('.free') {
all_fn_root_names << k
}
if c.pref.is_test && (k.starts_with('test_') || k.contains('.test_')) {
all_fn_root_names << k
}
if sb_mut_type != '' && k.starts_with(sb_mut_type) {
all_fn_root_names << k
}
}
if c.pref.is_debug {
all_fn_root_names << 'panic_debug'
}
if c.pref.is_test {
all_fn_root_names << 'main.cb_assertion_ok'
all_fn_root_names << 'main.cb_assertion_failed'
if benched_tests_sym := c.table.find_type('main.BenchedTests') {
bts_type := benched_tests_sym.methods[0].params[0].typ
all_fn_root_names << '${bts_type}.testing_step_start'
all_fn_root_names << '${bts_type}.testing_step_end'
all_fn_root_names << '${bts_type}.end_testing'
all_fn_root_names << 'main.start_testing'
}
}
mut walker := mark_used_walker.Walker{
files: ast_files
all_fns: all_fns
all_consts: all_consts
}
// println( all_fns.keys() )
walker.mark_root_fns(all_fn_root_names)
if walker.n_asserts > 0 {
walker.fn_decl(mut all_fns['__print_assert_failure'])
}
if walker.n_maps > 0 {
for k, mut mfn in all_fns {
if k == 'new_map_2' || k.starts_with('map_') || k.ends_with('set_1')
|| k.ends_with('exists_1')|| k.ends_with('get_1') {
walker.fn_decl(mut mfn)
}
}
}
$if trace_skip_unused_fn_names ? {
for key, _ in walker.used_fns {
println('> used fn key: $key')
}
}
c.table.used_fns = walker.used_fns
//
c.table.used_fns['term.can_show_color_on_stdin'] = true
c.table.used_fns['term.can_show_color_on_stdout'] = true
c.table.used_fns['term.can_show_color_on_stderr'] = true
//
c.table.used_fns['main.can_use_relative_paths'] = true
//
// eprintln('>>> c.table.used_fns: $c.table.used_fns')
util.timing_measure(@METHOD)
c.table.used_consts = walker.used_consts
// println(walker.used_fns)
// c.walk(ast_files)
$if trace_skip_unused ? {
eprintln('>> c.table.used_fns: $c.table.used_fns.keys()')
eprintln('>> c.table.used_consts: $c.table.used_consts.keys()')
eprintln('>> walker.n_maps: $walker.n_maps')
}
util.timing_measure(@METHOD)
}

View File

@ -8,25 +8,61 @@ import v.ast
pub struct Walker {
pub mut:
used_fns map[string]bool // used_fns['println'] == true
used_fns map[string]bool // used_fns['println'] == true
used_consts map[string]bool // used_consts['os.args'] == true
n_maps int
n_asserts int
mut:
files []ast.File
files []ast.File
all_fns map[string]ast.FnDecl
all_consts map[string]ast.ConstField
}
/*
fn (mut w Walker) walk_files(ast_files []ast.File) {
t := time.ticks()
*/
pub fn (mut w Walker) mark_fn_as_used(fkey string) {
$if trace_skip_unused_marked ? {
eprintln(' fn > |$fkey|')
}
w.used_fns[fkey] = true
}
pub fn (mut w Walker) mark_const_as_used(ckey string) {
$if trace_skip_unused_marked ? {
eprintln(' const > |$ckey|')
}
w.used_consts[ckey] = true
cfield := w.all_consts[ckey] or { return }
w.expr(cfield.expr)
}
pub fn (mut w Walker) mark_root_fns(all_fn_root_names []string) {
for fn_name in all_fn_root_names {
if fn_name !in w.used_fns {
$if trace_skip_unused_roots ? {
println('>>>> $fn_name uses: ')
}
w.fn_decl(mut w.all_fns[fn_name])
}
}
}
pub fn (mut w Walker) stmt(node ast.Stmt) {
match mut node {
ast.AssertStmt {
w.expr(node.expr)
w.n_asserts++
}
ast.AssignStmt {
for l in node.left {
w.expr(l)
}
for r in node.right {
w.expr(r)
}
w.exprs(node.left)
w.exprs(node.right)
}
ast.Block {
w.stmts(node.stmts)
}
ast.CompFor {
w.stmts(node.stmts)
}
ast.ConstDecl {
w.const_fields(node.fields)
}
ast.ExprStmt {
w.expr(node.expr)
@ -34,92 +70,295 @@ pub fn (mut w Walker) stmt(node ast.Stmt) {
ast.FnDecl {
w.fn_decl(mut node)
}
ast.ForCStmt {
w.expr(node.cond)
w.stmt(node.inc)
w.stmts(node.stmts)
}
ast.ForInStmt {
w.expr(node.cond)
w.expr(node.high)
w.stmts(node.stmts)
}
ast.ForStmt {
w.expr(node.cond)
for stmt in node.stmts {
w.stmt(stmt)
w.stmts(node.stmts)
}
ast.GoStmt {
w.expr(node.call_expr)
}
ast.Return {
w.exprs(node.exprs)
}
ast.SqlStmt {
w.expr(node.db_expr)
w.expr(node.where_expr)
w.exprs(node.update_exprs)
}
ast.StructDecl {
w.struct_fields(node.fields)
}
ast.DeferStmt {
w.stmts(node.stmts)
}
ast.GlobalDecl {
for gf in node.fields {
if gf.has_expr {
w.expr(gf.expr)
}
}
}
else {}
ast.BranchStmt {}
ast.EnumDecl {}
ast.GotoLabel {}
ast.GotoStmt {}
ast.HashStmt {}
ast.Import {}
ast.InterfaceDecl {}
ast.Module {}
ast.TypeDecl {}
}
}
fn (mut w Walker) defer_stmts(stmts []ast.DeferStmt) {
for stmt in stmts {
w.stmts(stmt.stmts)
}
}
fn (mut w Walker) stmts(stmts []ast.Stmt) {
for stmt in stmts {
w.stmt(stmt)
}
}
fn (mut w Walker) exprs(exprs []ast.Expr) {
for expr in exprs {
w.expr(expr)
}
}
fn (mut w Walker) expr(node ast.Expr) {
match mut node {
ast.AnonFn {
w.fn_decl(mut node.decl)
}
ast.Assoc {
w.exprs(node.exprs)
}
ast.ArrayInit {
w.expr(node.len_expr)
w.expr(node.cap_expr)
w.expr(node.default_expr)
w.exprs(node.exprs)
}
ast.ArrayDecompose {
w.expr(node.expr)
}
ast.CallExpr {
w.call_expr(mut node)
}
ast.CastExpr {
w.expr(node.expr)
w.expr(node.arg)
}
ast.ChanInit {
w.expr(node.cap_expr)
}
ast.ConcatExpr {
w.exprs(node.vals)
}
ast.ComptimeSelector {
w.expr(node.left)
w.expr(node.field_expr)
}
ast.ComptimeCall {
w.expr(node.left)
if node.is_vweb {
w.stmts(node.vweb_tmpl.stmts)
}
}
ast.GoExpr {
w.expr(node.go_stmt.call_expr)
}
ast.IndexExpr {
w.expr(node.left)
w.expr(node.index)
w.or_block(node.or_expr)
}
ast.InfixExpr {
w.expr(node.left)
w.expr(node.right)
w.or_block(node.or_block)
}
ast.IfGuardExpr {
w.expr(node.expr)
}
ast.IfExpr {
w.expr(node.left)
for b in node.branches {
w.expr(b.cond)
for stmt in b.stmts {
w.stmt(stmt)
}
w.stmts(b.stmts)
}
}
ast.Ident {
if node.kind == .constant {
w.mark_const_as_used(node.name)
}
}
ast.Likely {
w.expr(node.expr)
}
ast.MapInit {
w.exprs(node.keys)
w.exprs(node.vals)
w.n_maps++
}
ast.MatchExpr {
w.expr(node.cond)
for b in node.branches {
for expr in b.exprs {
w.expr(expr)
}
for stmt in b.stmts {
w.stmt(stmt)
}
w.exprs(b.exprs)
w.stmts(b.stmts)
}
}
else {}
ast.None {
w.mark_fn_as_used('opt_none')
}
ast.ParExpr {
w.expr(node.expr)
}
ast.PrefixExpr {
w.expr(node.right)
}
ast.PostfixExpr {
w.expr(node.expr)
}
ast.RangeExpr {
if node.has_low {
w.expr(node.low)
}
if node.has_high {
w.expr(node.high)
}
}
ast.SizeOf {
w.expr(node.expr)
}
ast.StringInterLiteral {
w.exprs(node.exprs)
}
ast.SelectorExpr {
w.expr(node.expr)
}
ast.SqlExpr {
w.expr(node.db_expr)
w.expr(node.offset_expr)
w.expr(node.order_expr)
w.expr(node.limit_expr)
w.expr(node.where_expr)
}
ast.StructInit {
// eprintln('>>>> ast.StructInit: $node')
w.expr(node.update_expr)
for sif in node.fields {
w.expr(sif.expr)
}
for sie in node.embeds {
w.expr(sie.expr)
}
}
ast.TypeOf {
w.expr(node.expr)
}
///
ast.AsCast {
w.expr(node.expr)
}
ast.AtExpr {}
ast.BoolLiteral {}
ast.FloatLiteral {}
ast.CharLiteral {}
ast.IntegerLiteral {}
ast.StringLiteral {}
ast.CTempVar {
w.expr(node.orig)
}
ast.Comment {}
ast.EnumVal {}
ast.LockExpr {
w.stmts(node.stmts)
}
ast.OffsetOf {}
ast.OrExpr {
w.or_block(node)
}
ast.SelectExpr {
for branch in node.branches {
w.stmt(branch.stmt)
w.stmts(branch.stmts)
}
}
ast.Type {}
ast.UnsafeExpr {
w.expr(node.expr)
}
}
}
pub fn (mut w Walker) fn_decl(mut node ast.FnDecl) {
if node.language == .c {
return
}
fkey := if node.is_method { '${int(node.receiver.typ)}.$node.name' } else { node.name }
if w.used_fns[fkey] {
// This function is already known to be called, meaning it has been processed already.
// Save CPU time and do nothing.
return
}
if node.language == .c {
return
}
w.used_fns[fkey] = true
for stmt in node.stmts {
w.stmt(stmt)
}
w.mark_fn_as_used(fkey)
w.stmts(node.stmts)
w.defer_stmts(node.defer_stmts)
}
pub fn (mut w Walker) call_expr(mut node ast.CallExpr) {
if node.language == .c {
return
}
w.expr(node.left)
for arg in node.args {
w.expr(arg.expr)
}
w.or_block(node.or_block)
//
fn_name := if node.is_method { node.receiver_type.str() + '.' + node.name } else { node.name }
// fn_name := node.name
// println('call_expr $fn_name')
// if node.is_method {
// println('M $node.name $node.receiver_type')
//}
if w.used_fns[fn_name] {
return
}
// w.used_fns[fn_name] = true
// Find the FnDecl for this CallExpr, mark the function as used, and process
// all its statements.
loop: for file in w.files {
for stmt in file.stmts {
if stmt is ast.FnDecl {
if stmt.name == node.name
&& (!node.is_method || (node.receiver_type == stmt.receiver.typ)) {
w.used_fns[fn_name] = true
for fn_stmt in stmt.stmts {
w.stmt(fn_stmt)
}
break loop
}
}
stmt := w.all_fns[fn_name] or { return }
if stmt.name == node.name {
if !node.is_method || (node.receiver_type == stmt.receiver.typ) {
w.mark_fn_as_used(fn_name)
w.stmts(stmt.stmts)
}
}
}
pub fn (mut w Walker) struct_fields(sfields []ast.StructField) {
for sf in sfields {
if sf.has_default_expr {
w.expr(sf.default_expr)
}
}
}
pub fn (mut w Walker) const_fields(cfields []ast.ConstField) {
for cf in cfields {
w.expr(cf.expr)
}
}
pub fn (mut w Walker) or_block(node ast.OrExpr) {
if node.kind == .block {
w.stmts(node.stmts)
}
}

View File

@ -4725,6 +4725,15 @@ fn (mut g Gen) const_decl(node ast.ConstDecl) {
g.inside_const = false
}
for field in node.fields {
if g.pref.skip_unused {
if field.name !in g.table.used_consts {
$if trace_skip_unused_consts ? {
eprintln('>> skipping unused const name: $field.name')
}
continue
}
}
name := c_name(field.name)
// TODO hack. Cut the generated value and paste it into definitions.
pos := g.out.len

View File

@ -30,7 +30,7 @@ fn (mut g Gen) gen_fn_decl(node ast.FnDecl, skip bool) {
if g.pref.skip_unused {
fkey := if node.is_method { '${int(node.receiver.typ)}.$node.name' } else { node.name }
is_used_by_main := g.table.used_fns[fkey]
$if trace_skip_unused ? {
$if trace_skip_unused_fns ? {
println('> is_used_by_main: $is_used_by_main | node.name: $node.name | fkey: $fkey | node.is_method: $node.is_method')
}
if !is_used_by_main {

View File

@ -20,6 +20,7 @@ pub mut:
cmod_prefix string // needed for table.type_to_str(Type) while vfmt; contains `os.`
is_fmt bool
used_fns map[string]bool // filled in by the checker, when pref.skip_unused = true;
used_consts map[string]bool // filled in by the checker, when pref.skip_unused = true;
}
pub struct Fn {

View File

@ -0,0 +1,3 @@
fn test_abc() {
assert 'abc' == 'abc'
}

View File

@ -0,0 +1,5 @@
vlib/v/tests/skip_unused/assert_works_test.vv:2: fn test_abc
> assert 'abc' == 'xyz'
Left value: abc
Right value: xyz

View File

@ -0,0 +1,5 @@
vlib/v/tests/skip_unused/assert_works_test.vv:2: fn test_abc
> assert 'abc' == 'xyz'
Left value: abc
Right value: xyz

View File

@ -0,0 +1,3 @@
fn test_abc() {
assert 'abc' == 'xyz'
}