Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions vlib/v/ast/table.v
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub mut:
used_consts map[string]bool // filled in by markused
used_globals map[string]bool // filled in by markused
used_syms map[int]bool // filled in by markused
used_str map[Type]bool // filled in by markused
used_veb_types []Type // veb context types, filled in by checker
used_maps int // how many times maps were used, filled in by markused
used_none int // how many times `none` was used, filled in by markused
Expand Down
15 changes: 15 additions & 0 deletions vlib/v/ast/types.v
Original file line number Diff line number Diff line change
Expand Up @@ -1883,6 +1883,21 @@ pub fn (t &TypeSymbol) find_method_with_generic_parent(name string) ?Fn {
return none
}

pub fn (t &TypeSymbol) has_method_with_sumtype_parent(name string) bool {
if t.has_method(name) {
return true
}
for s in global_table.type_symbols {
if s.kind == .sum_type {
info := s.info as SumType
if t.idx in info.variants {
return s.has_method(name)
}
}
}
return false
}

// is_js_compatible returns true if type can be converted to JS type and from JS type back to V type
pub fn (t &TypeSymbol) is_js_compatible() bool {
mut table := global_table
Expand Down
282 changes: 282 additions & 0 deletions vlib/v/builder/auto_fn.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
module builder

import strings
import v.ast
import v.parser
import os

pub fn (mut b Builder) gen_auto_fn() {
b.gen_auto_str_fn()
}

fn (mut b Builder) gen_auto_str_fn() {
mut generic_to_concrete_map := map[ast.Type][][]ast.Type{}
// construct a `type_map`, to generate only one file for a module
mut need_auto_str_fn_type_map := map[string][]ast.Type{}
mut already_gen_str_map := map[string]bool{}
for t, _ in b.table.used_features.used_str {
if t == ast.no_type || t == ast.void_type {
continue
}
s := b.table.sym(t)
$if trace_auto_fn ? {
dump(s.name)
}
has_str_method := s.has_method_with_sumtype_parent('str')
|| s.has_method_with_generic_parent('str')
if has_str_method || s.kind == .function || s.mod == '' {
continue
} else {
unsafe {
if t !in need_auto_str_fn_type_map[s.mod] {
need_auto_str_fn_type_map[s.mod] << t
}
}
}
}
$if trace_auto_fn ? {
dump(need_auto_str_fn_type_map)
// x := b.table.type_symbols.map(it.name).join('\n')
// dump(x)
// for k in b.table.type_symbols {
// if k.name.contains('main.Response') {
// if k.info is ast.Struct {
// dump(k.info)
// }
// }
//}
}

if need_auto_str_fn_type_map.len > 0 {
mut sb := strings.new_builder(128)
mut check_file_list := []&ast.File{}
for full_mod_name, types in need_auto_str_fn_type_map {
mod_name := full_mod_name.all_after_last('.')
sb.writeln('module ${mod_name}\nimport strings')
for t_ in types {
t := b.table.sym(t_)
mut type_name := b.auto_fn_get_type_name(t, false) // main.MyS[int] => MyS[T]
if t.name.starts_with('C.') {
type_name = 'C.' + type_name
} else if full_mod_name != 'builtin' {
type_name = full_mod_name + '.' + type_name
}
type_name2 := b.auto_fn_get_type_name(t, true) // main.MyS[int] => MyS[${T.name}]
match t.kind {
.struct {
info := t.info as ast.Struct
//$if trace_auto_fn ? {
// dump(info)
//}
if info.parent_type.has_flag(.generic) {
// for `post_process_generic_fns`
generic_to_concrete_map[info.parent_type] << [
info.concrete_types,
]
}
concrete_types := b.get_concrete_types(t_)
if concrete_types.len > 0 {
generic_to_concrete_map[t_] << concrete_types
}
hint := 'type=${t.idx}\nt.name=${t.name}'
if type_name !in already_gen_str_map {
b.gen_str_for_struct(mut sb, type_name, type_name2, info,
hint)
already_gen_str_map[type_name] = true
}
}
.alias {
info := t.info as ast.Alias
hint := 'type=${t.idx}\nt.name=${t.name}'
if type_name !in already_gen_str_map {
b.gen_str_for_alias(mut sb, type_name, type_name2, info, hint)
already_gen_str_map[type_name] = true
}
}
else {}
}
}
v_file := sb.str()
v_file_name := b.get_mod_full_path(full_mod_name) +
'/v_auto_generated_auto_fn(${full_mod_name}).v'
// os.write_file(v_file_name, v_file) or {}
$if trace_auto_fn ? {
dump(v_file_name)
dump(v_file)
}
mut the_file := parser.parse_text(v_file, v_file_name, mut b.table, .skip_comments,
b.pref)
b.parsed_files << the_file
b.table.filelist << v_file_name
check_file_list << the_file
}
// need to call `checker` for this generated v_file
for mut f in check_file_list {
b.checker.check(mut f)
b.auto_fn_fix_generics(mut f, generic_to_concrete_map)
}
}
}

fn (mut b Builder) gen_str_for_struct(mut sb strings.Builder, struct_name string, struct_name2 string, info ast.Struct, hint string) {
sb.writeln('/*${hint}*/')
clean_struct_v_type_name := if info.is_anon { 'struct ' } else { struct_name2 }
generic_types := if info.generic_types.len > 0 {
'[' + info.generic_types.map(b.table.sym(it).name).join(',') + ']'
} else {
''
}
if info.fields.len == 0 {
sb.writeln("@[markused]\npub fn (it ${struct_name}) str${generic_types}() string { return '${clean_struct_v_type_name}{}' }")
return
}
// -hide-auto-str hides potential sensitive struct data from resulting binary files
if b.pref.hide_auto_str {
sb.writeln("@[markused]\npub fn (it ${struct_name}) str${generic_types}() string { return 'str() used with -hide-auto-str' }")
return
}
sb.writeln('@[markused]\npub fn (it ${struct_name}) str${generic_types}() string {')
// is_c_struct := struct_name.starts_with('C.')
sb.writeln('\tmut res := strings.new_builder(222)')
sb.writeln('\tres.write_string(\'${clean_struct_v_type_name}{\')')
// find `[str: skip]` fields
mut field_skips := []int{}
for i, field in info.fields {
if attr := field.attrs.find_first('str') {
if attr.arg == 'skip' {
field_skips << i
}
}
}
mut is_first := true
for i, f in info.fields {
// Skip `str:skip` fields
if i in field_skips {
continue
}
if is_first {
is_first = false
sb.writeln("\tres.writeln('')")
}
if f.typ.has_flag(.shared_f) {
sb.writeln('\trlock it.${f.name} {')
}
if f.typ == ast.string_type {
sb.writeln('\tres.write_string(\'${f.name}: \\\'\${it.${f.name}}\\\'\\n\')')
} else {
sym := b.table.sym(f.typ)
if sym.kind == .struct {
if f.typ.has_flag(.option) {
sb.writeln('\tif option_f := it.${f.name} {')
sb.writeln("\t\tres.writeln('${f.name}: Option(\${option_f})')")
sb.writeln('\t}\n\telse {')
sb.writeln("\t\tres.writeln('${f.name}: Option(none)')")
sb.writeln('\t}')
} else {
sb.writeln("\tres.writeln('${f.name}: \${it.${f.name}}')")
}
} else if sym.kind == .chan {
elem_info := sym.info as ast.Chan
elem_type_str := b.table.type_to_str(elem_info.elem_type)
sb.writeln("\tres.writeln('${f.name}: chan ${elem_type_str}{cap: \${it.${f.name}.cap}, closed: \${it.${f.name}.closed}}')")
} else {
sb.writeln("\tres.writeln('${f.name}: \${it.${f.name}}')")
}
}
if f.typ.has_flag(.shared_f) {
sb.writeln('\t}')
}
}
sb.writeln("\tres.write_string('}')")
sb.writeln('\t// note: this can be removed after move dump beyond cgen')
sb.writeln('\tmut sb := strings.new_builder(222)')
sb.writeln('\tsb.indent(res.str())')
sb.writeln('\treturn sb.str()\n}')
}

fn (mut b Builder) gen_str_for_alias(mut sb strings.Builder, alias_name string, alias_name2 string, info ast.Alias, hint string) {
sb.writeln('/*${hint}*/')
// -hide-auto-str hides potential sensitive struct data from resulting binary files
if b.pref.hide_auto_str {
sb.writeln("@[markused]\npub fn (it ${alias_name}) str() string { return 'str() used with -hide-auto-str' }")
return
}
parent_type_name := b.table.type_to_str(info.parent_type)
sb.writeln('@[markused]\npub fn (it ${alias_name}) str() string {')
// sb.writeln('\tparent_it := *(&${parent_type_name}(&it))')
sb.writeln('\tmut parent_it := ${parent_type_name}{}')
sb.writeln('\tparent_it = unsafe {it}')
sb.writeln('\tmut res := strings.new_builder(222)')
sb.writeln("\tres.write_string('${alias_name2}(\${parent_it})')")
// sb.writeln('\t// note: this can be removed after move dump beyond cgen')
// sb.writeln('\tmut sb := strings.new_builder(222)')
// sb.writeln('\tsb.indent(res.str())')
// sb.writeln('\treturn sb.str()\n}')
sb.writeln('\treturn res.str()\n}')
}

fn (b Builder) get_mod_full_path(full_mod_name string) string {
for f in b.parsed_files {
if f.mod.name == full_mod_name {
return os.dir(f.path)
}
}
return ''
}

fn (mut b Builder) auto_fn_get_type_name(t &ast.TypeSymbol, is_get_type_name bool) string {
mut type_name := t.name.all_after_last('.')
if t.kind == .struct {
// main.MyS[int] => MyS[T]
info := t.info as ast.Struct
type_name = b.table.clean_generics_type_str(t.idx).all_after_last('.')
if info.generic_types.len > 0 {
type_name += '['
generic_names := info.generic_types.map(b.table.sym(it).name)
if is_get_type_name {
type_name += '\${'
type_name += generic_names.join('.name},\${')
type_name += '.name}'
} else {
type_name += generic_names.join(',')
}
type_name += ']'
}
}
return type_name
}

fn (mut b Builder) auto_fn_fix_generics(mut file ast.File, generic_to_concrete_map map[ast.Type][][]ast.Type) {
if file.generic_fns.len == 0 {
return
}
$if trace_auto_fn ? {
dump(generic_to_concrete_map)
}
for node in file.generic_fns {
$if trace_auto_fn ? {
dump(node.receiver.typ)
}
if concrete_types := generic_to_concrete_map[node.receiver.typ] {
$if trace_auto_fn ? {
dump(concrete_types)
}
fkey := node.fkey()
b.table.fn_generic_types[fkey] << concrete_types
}
}
b.checker.change_current_file(file)
b.checker.post_process_generic_fns() or {}
}

fn (mut b Builder) get_concrete_types(typ ast.Type) []ast.Type {
mut concrete_types := []ast.Type{}
for t in b.table.type_symbols {
if t.info is ast.Struct {
if t.info.parent_type == typ && t.info.concrete_types.len > 0 {
concrete_types << t.info.concrete_types
}
}
}
return concrete_types
}
18 changes: 12 additions & 6 deletions vlib/v/builder/builder.v
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,13 @@ pub mut:
table &ast.Table = unsafe { nil }
ccoptions CcompilerOptions
// Note: changes in mod `builtin` force invalidation of every other .v file
mod_invalidates_paths map[string][]string // changes in mod `os`, invalidate only .v files, that do `import os`
mod_invalidates_mods map[string][]string // changes in mod `os`, force invalidation of mods, that do `import os`
path_invalidates_mods map[string][]string // changes in a .v file from `os`, invalidates `os`
crun_cache_keys []string // target executable + top level source files; filled in by Builder.should_rebuild
executable_exists bool // if the executable already exists, don't remove new executable after `v run`
str_args string // for parallel_cc mode only, to know which cc args to use (like -I etc)
mod_invalidates_paths map[string][]string // changes in mod `os`, invalidate only .v files, that do `import os`
mod_invalidates_mods map[string][]string // changes in mod `os`, force invalidation of mods, that do `import os`
path_invalidates_mods map[string][]string // changes in a .v file from `os`, invalidates `os`
crun_cache_keys []string // target executable + top level source files; filled in by Builder.should_rebuild
executable_exists bool // if the executable already exists, don't remove new executable after `v run`
str_args string // for parallel_cc mode only, to know which cc args to use (like -I etc)
auto_str_has_import_strings bool
}

pub fn new_builder(pref_ &pref.Preferences) Builder {
Expand Down Expand Up @@ -164,6 +165,11 @@ pub fn (mut b Builder) middle_stages() ! {
if b.pref.show_callgraph {
callgraph.show(mut b.table, b.pref, b.parsed_files)
}
// generate auto fns, such as `.str()`
b.gen_auto_fn()
$if trace_auto_fn ? {
b.print_warnings_and_errors()
}
}

pub fn (mut b Builder) front_and_middle_stages(v_files []string) ! {
Expand Down
2 changes: 1 addition & 1 deletion vlib/v/checker/fn.v
Original file line number Diff line number Diff line change
Expand Up @@ -2912,7 +2912,7 @@ fn (mut c Checker) set_node_expected_arg_types(mut node ast.CallExpr, func &ast.
}
}

fn (mut c Checker) post_process_generic_fns() ! {
pub fn (mut c Checker) post_process_generic_fns() ! {
mut all_generic_fns := map[string]int{}
// Loop thru each generic function concrete type.
// Check each specific fn instantiation.
Expand Down
14 changes: 11 additions & 3 deletions vlib/v/gen/c/comptime.v
Original file line number Diff line number Diff line change
Expand Up @@ -431,9 +431,17 @@ fn (mut g Gen) comptime_if(node ast.IfExpr) {
// `g.table.comptime_is_true` are the branch condition results set by `checker`
is_true = comptime_is_true
} else {
g.error('checker error: condition result idx string not found => [${idx_str}]',
node.branches[i].cond.pos())
return
if comptime_branch_context_str.contains('method.name=str') {
// workaround for auto_str(). Because `builder` call `checker` before `gen_auto_fn()`
// Always set to false to bypass this
is_true = ast.ComptTimeCondResult{
c_str: 'false'
}
} else {
g.error('checker error: condition result idx string not found => [${idx_str}]',
node.branches[i].cond.pos())
return
}
}
if !node.has_else || i < node.branches.len - 1 {
if i == 0 {
Expand Down
1 change: 1 addition & 0 deletions vlib/v/markused/markused.v
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ pub fn mark_used(mut table ast.Table, mut pref_ pref.Preferences, ast_files []&a
table.used_features.used_globals = walker.used_globals.move()
table.used_features.used_syms = walker.used_syms.move()
table.used_features.used_closures = walker.used_closures
table.used_features.used_str = walker.uses_str.move()

if trace_skip_unused {
eprintln('>> t.used_fns: ${table.used_features.used_fns.keys()}')
Expand Down
Loading
Loading