Skip to content

Commit

Permalink
cgen: force C struct types which does not implement str() to be passe…
Browse files Browse the repository at this point in the history
…d as ptr (vlang#21054)
  • Loading branch information
felipensp authored Mar 20, 2024
1 parent 0b573c5 commit 24d1572
Show file tree
Hide file tree
Showing 9 changed files with 155 additions and 31 deletions.
6 changes: 5 additions & 1 deletion vlib/builtin/wchar/wchar.c.v
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ pub fn (a Character) == (b Character) bool {
// to_rune creates a V rune, given a Character
@[inline]
pub fn (c Character) to_rune() rune {
return unsafe { *(&rune(&c)) }
$if windows {
return unsafe { *(&rune(&c)) } & 0xFFFF
} $else {
return unsafe { *(&rune(&c)) }
}
}

// from_rune creates a Character, given a V rune
Expand Down
12 changes: 12 additions & 0 deletions vlib/v/ast/types.v
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,15 @@ pub fn (t &TypeSymbol) is_array_fixed() bool {
}
}

pub fn (t &TypeSymbol) is_c_struct() bool {
if t.info is Struct {
return t.language == .c
} else if t.info is Alias {
return global_table.final_sym(t.info.parent_type).is_c_struct()
}
return false
}

pub fn (t &TypeSymbol) is_array_fixed_ret() bool {
if t.info is ArrayFixed {
return t.info.is_fn_ret
Expand Down Expand Up @@ -1707,6 +1716,9 @@ pub fn (t &TypeSymbol) str_method_info() (bool, bool, int) {
if nr_args > 0 {
expects_ptr = sym_str_method.params[0].typ.is_ptr()
}
} else {
// C Struct which does not implement str() are passed as pointer to handle incomplete type
expects_ptr = t.is_c_struct()
}
return has_str_method, expects_ptr, nr_args
}
Expand Down
80 changes: 56 additions & 24 deletions vlib/v/gen/c/auto_str_methods.v
Original file line number Diff line number Diff line change
Expand Up @@ -248,19 +248,25 @@ fn (mut g Gen) gen_str_for_result(typ ast.Type, styp string, str_fn_name string)

fn (mut g Gen) gen_str_for_alias(info ast.Alias, styp string, str_fn_name string) {
parent_str_fn_name := g.get_str_fn(info.parent_type)
_, str_method_expects_ptr, _ := g.table.sym(info.parent_type).str_method_info()
parent_sym := g.table.sym(info.parent_type)
_, str_method_expects_ptr, _ := parent_sym.str_method_info()

$if trace_autostr ? {
eprintln('> gen_str_for_alias: ${parent_str_fn_name} | ${styp} | ${str_fn_name}')
}
mut clean_type_v_type_name := util.strip_main_name(styp.replace('__', '.'))
g.definitions.writeln('static string ${str_fn_name}(${styp} it); // auto')
g.auto_str_funcs.writeln('static string ${str_fn_name}(${styp} it) { return indent_${str_fn_name}(it, 0); }')
g.definitions.writeln('static string indent_${str_fn_name}(${styp} it, int indent_count); // auto')
g.auto_str_funcs.writeln('static string indent_${str_fn_name}(${styp} it, int indent_count) {')

is_c_struct := parent_sym.is_c_struct() && str_method_expects_ptr
arg_def := if is_c_struct { '${styp}* it' } else { '${styp} it' }

g.definitions.writeln('static string ${str_fn_name}(${arg_def}); // auto')
g.auto_str_funcs.writeln('static string ${str_fn_name}(${arg_def}) { return indent_${str_fn_name}(it, 0); }')
g.definitions.writeln('static string indent_${str_fn_name}(${arg_def}, int indent_count); // auto')
g.auto_str_funcs.writeln('static string indent_${str_fn_name}(${arg_def}, int indent_count) {')
g.auto_str_funcs.writeln('\tstring indents = string_repeat(_SLIT(" "), indent_count);')
if str_method_expects_ptr {
g.auto_str_funcs.writeln('\tstring tmp_ds = ${parent_str_fn_name}(&it);')
it_arg := if is_c_struct { 'it' } else { '&it' }
g.auto_str_funcs.writeln('\tstring tmp_ds = ${parent_str_fn_name}(${it_arg});')
} else {
deref, _ := deref_kind(str_method_expects_ptr, info.parent_type.is_ptr(), info.parent_type)
g.auto_str_funcs.writeln('\tstring tmp_ds = ${parent_str_fn_name}(${deref}it);')
Expand Down Expand Up @@ -882,14 +888,17 @@ fn (mut g Gen) gen_str_for_struct(info ast.Struct, lang ast.Language, styp strin
eprintln('> gen_str_for_struct: ${info.parent_type.debug()} | ${styp} | ${str_fn_name}')
}
// _str() functions should have a single argument, the indenting ones take 2:
g.definitions.writeln('static string ${str_fn_name}(${styp} it); // auto')
g.auto_str_funcs.writeln('static string ${str_fn_name}(${styp} it) { return indent_${str_fn_name}(it, 0);}')
g.definitions.writeln('static string indent_${str_fn_name}(${styp} it, int indent_count); // auto')
is_c_struct := lang == .c
arg_def := if is_c_struct { '${styp}* it' } else { '${styp} it' }
g.definitions.writeln('static string ${str_fn_name}(${arg_def}); // auto')
g.auto_str_funcs.writeln('static string ${str_fn_name}(${arg_def}) { return indent_${str_fn_name}(it, 0);}')
g.definitions.writeln('static string indent_${str_fn_name}(${arg_def}, int indent_count); // auto')
mut fn_builder := strings.new_builder(512)
defer {
g.auto_fn_definitions << fn_builder.str()
}
fn_builder.writeln('static string indent_${str_fn_name}(${styp} it, int indent_count) {')
fn_builder.writeln('static string indent_${str_fn_name}(${arg_def}, int indent_count) {')

clean_struct_v_type_name := if info.is_anon { 'struct ' } else { util.strip_main_name(typ_str) }
// generate ident / indent length = 4 spaces
if info.fields.len == 0 {
Expand Down Expand Up @@ -997,18 +1006,20 @@ fn (mut g Gen) gen_str_for_struct(info ast.Struct, lang ast.Language, styp strin
field_styp_fn_name, field.name, sym_has_str_method, str_method_expects_ptr)
ftyp_nr_muls := field.typ.nr_muls()
field_name := if lang == .c { field.name } else { c_name(field.name) }
op := if is_c_struct { '->' } else { '.' }
it_field_name := 'it${op}${field_name}'
if ftyp_nr_muls > 1 || field.typ in ast.cptr_types {
if is_opt_field {
} else {
func = '(voidptr) it.${field.name}'
func = '(voidptr) ${it_field_name}'
caller_should_free = false
}
} else if ftyp_noshared.is_ptr() {
// reference types can be "nil"
if ftyp_noshared.has_flag(.option) {
funcprefix += 'isnil(&it.${field_name}) || isnil(&it.${field_name}.data)'
funcprefix += 'isnil(&${it_field_name}) || isnil(&${it_field_name}.data)'
} else {
funcprefix += 'isnil(it.${field_name})'
funcprefix += 'isnil(${it_field_name})'
}
funcprefix += ' ? _SLIT("nil") : '
// struct, floats and ints have a special case through the _str function
Expand All @@ -1031,9 +1042,9 @@ fn (mut g Gen) gen_str_for_struct(info ast.Struct, lang ast.Language, styp strin
if is_field_array {
if is_opt_field {
arr_styp := g.base_type(field.typ)
fn_body.write_string('it.${field_name}.state != 2 && (*(${arr_styp}*)it.${field_name}.data).len > 0 ? ${funcprefix}_SLIT("[<circular>]") : ${funcprefix}_SLIT("[]")')
fn_body.write_string('${it_field_name}.state != 2 && (*(${arr_styp}*)${it_field_name}.data).len > 0 ? ${funcprefix}_SLIT("[<circular>]") : ${funcprefix}_SLIT("[]")')
} else {
fn_body.write_string('it.${field_name}.len > 0 ? ${funcprefix}_SLIT("[<circular>]") : ${funcprefix}_SLIT("[]")')
fn_body.write_string('${it_field_name}.len > 0 ? ${funcprefix}_SLIT("[<circular>]") : ${funcprefix}_SLIT("[]")')
}
} else {
fn_body.write_string('${funcprefix}_SLIT("<circular>")')
Expand Down Expand Up @@ -1065,6 +1076,25 @@ fn (mut g Gen) gen_str_for_struct(info ast.Struct, lang ast.Language, styp strin
fn_body.writeln('\t}));')
}

// c_struct_ptr handles the C struct argument for .str() method
@[inline]
pub fn c_struct_ptr(sym &ast.TypeSymbol, typ ast.Type, expects_ptr bool) string {
if sym.is_c_struct() {
if typ.has_flag(.option) {
return ''
}
if typ.nr_muls() >= 1 {
if expects_ptr {
return '*'.repeat(typ.nr_muls() - 1)
} else {
return '*'.repeat(typ.nr_muls())
}
}
return if expects_ptr { '&' } else { '' }
}
return ''
}

fn struct_auto_str_func(sym &ast.TypeSymbol, lang ast.Language, _field_type ast.Type, fn_name string, field_name string, has_custom_str bool, expects_ptr bool) (string, bool) {
$if trace_autostr ? {
eprintln('> struct_auto_str_func: ${sym.name} | field_type.debug() | ${fn_name} | ${field_name} | ${has_custom_str} | ${expects_ptr}')
Expand All @@ -1073,38 +1103,40 @@ fn struct_auto_str_func(sym &ast.TypeSymbol, lang ast.Language, _field_type ast.
sufix := if field_type.has_flag(.shared_f) { '->val' } else { '' }
deref, _ := deref_kind(expects_ptr, field_type.is_ptr(), field_type)
final_field_name := if lang == .c { field_name } else { c_name(field_name) }
op := if lang == .c { '->' } else { '.' }
prefix := if sym.is_c_struct() { c_struct_ptr(sym, _field_type, expects_ptr) } else { deref }
if sym.kind == .enum_ {
return '${fn_name}(${deref}(it.${final_field_name}))', true
return '${fn_name}(${deref}(it${op}${final_field_name}))', true
} else if _field_type.has_flag(.option) || should_use_indent_func(sym.kind) {
obj := '${deref}it.${final_field_name}${sufix}'
obj := '${prefix}it${op}${final_field_name}${sufix}'
if has_custom_str {
if sym.kind == .interface_ && (sym.info as ast.Interface).defines_method('str') {
iface_obj := 'it.${final_field_name}${sufix}'
iface_obj := '${prefix}it${op}${final_field_name}${sufix}'
dot := if field_type.is_ptr() { '->' } else { '.' }
return '${fn_name.trim_string_right('_str')}_name_table[${iface_obj}${dot}_typ]._method_str(${iface_obj}${dot}_object)', true
}
return '${fn_name}(${obj})', true
}
return 'indent_${fn_name}(${obj}, indent_count + 1)', true
} else if sym.kind in [.array, .array_fixed, .map, .sum_type] {
obj := '${deref}it.${final_field_name}${sufix}'
obj := '${prefix}it${op}${final_field_name}${sufix}'
if has_custom_str {
return '${fn_name}(${obj})', true
}
return 'indent_${fn_name}(${obj}, indent_count + 1)', true
} else if sym.kind == .function {
obj := '${deref}it.${final_field_name}${sufix}'
obj := '${deref}it${op}${final_field_name}${sufix}'
return '${fn_name}(${obj})', true
} else if sym.kind == .chan {
return '${fn_name}(${deref}it.${final_field_name}${sufix})', true
return '${fn_name}(${deref}it${op}${final_field_name}${sufix})', true
} else if sym.kind == .thread {
return '${fn_name}(${deref}it.${final_field_name}${sufix})', false
return '${fn_name}(${deref}it${op}${final_field_name}${sufix})', false
} else {
mut method_str := ''
if !field_type.is_ptr() && field_type.has_option_or_result() {
method_str = '(*(${sym.name}*)it.${final_field_name}.data)'
method_str = '(*(${sym.name}*)it${op}${final_field_name}.data)'
} else {
method_str = 'it.${final_field_name}'
method_str = 'it${op}${final_field_name}'
}
if sym.kind == .bool {
return '${method_str} ? _SLIT("true") : _SLIT("false")', false
Expand Down
1 change: 0 additions & 1 deletion vlib/v/gen/c/cgen.v
Original file line number Diff line number Diff line change
Expand Up @@ -2458,7 +2458,6 @@ fn (mut g Gen) expr_with_cast(expr ast.Expr, got_type_raw ast.Type, expected_typ
lock g.referenced_fns {
g.referenced_fns[fname] = true
}
fname = '/*${exp_sym}*/${fname}'
if exp_sym.info.is_generic {
fname = g.generic_fn_name(exp_sym.info.concrete_types, fname)
}
Expand Down
14 changes: 12 additions & 2 deletions vlib/v/gen/c/dumpexpr.v
Original file line number Diff line number Diff line change
Expand Up @@ -181,11 +181,21 @@ fn (mut g Gen) dump_expr_definitions() {
surrounder.add('\tstring value = isnil(&dump_arg.data) ? _SLIT("nil") : ${to_string_fn_name}(${deref}dump_arg);',
'\tstring_free(&value);')
} else {
surrounder.add('\tstring value = (dump_arg == NULL) ? _SLIT("nil") : ${to_string_fn_name}(${deref}dump_arg);',
prefix := if dump_sym.is_c_struct() {
c_struct_ptr(dump_sym, dump_type, str_method_expects_ptr)
} else {
deref
}
surrounder.add('\tstring value = (dump_arg == NULL) ? _SLIT("nil") : ${to_string_fn_name}(${prefix}dump_arg);',
'\tstring_free(&value);')
}
} else {
surrounder.add('\tstring value = ${to_string_fn_name}(${deref}dump_arg);',
prefix := if dump_sym.is_c_struct() {
c_struct_ptr(dump_sym, dump_type, str_method_expects_ptr)
} else {
deref
}
surrounder.add('\tstring value = ${to_string_fn_name}(${prefix}dump_arg);',
'\tstring_free(&value);')
}
surrounder.add('
Expand Down
14 changes: 12 additions & 2 deletions vlib/v/gen/c/str.v
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype ast.Type) {
}
g.write('${str_fn_name}(')
if str_method_expects_ptr && !is_ptr {
if is_dump_expr {
if is_dump_expr || (g.pref.ccompiler_type != .tinyc && expr is ast.CallExpr) {
g.write('ADDR(${g.typ(typ)}, ')
defer {
g.write(')')
Expand All @@ -162,7 +162,13 @@ fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype ast.Type) {
} else if is_ptr && typ.has_flag(.option) {
g.write('*(${g.typ(typ)}*)&')
} else if !str_method_expects_ptr && !is_shared && (is_ptr || is_var_mut) {
g.write('*'.repeat(typ.nr_muls()))
if sym.is_c_struct() {
g.write(c_struct_ptr(sym, typ, str_method_expects_ptr))
} else {
g.write('*'.repeat(typ.nr_muls()))
}
} else if sym.is_c_struct() {
g.write(c_struct_ptr(sym, typ, str_method_expects_ptr))
}
if expr is ast.ArrayInit {
if expr.is_fixed {
Expand Down Expand Up @@ -199,6 +205,10 @@ fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype ast.Type) {
g.write('&')
} else if (!str_method_expects_ptr && is_ptr && !is_shared) || is_var_mut {
g.write('*')
} else {
if sym.is_c_struct() {
g.write(c_struct_ptr(sym, typ, str_method_expects_ptr))
}
}
g.expr_with_cast(expr, typ, typ)
} else if typ.has_flag(.option) {
Expand Down
1 change: 0 additions & 1 deletion vlib/v/gen/c/str_intp.v
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ fn (mut g Gen) str_format(node ast.StringInterLiteral, i int, fmts []u8) (u64, s
mut remove_tail_zeros := false
fspec := fmts[i]
mut fmt_type := StrIntpType.si_no_str
g.write('/*${fspec} ${sym}*/')
// upper cases
if (fspec - `A`) <= (`Z` - `A`) {
upper_case = true
Expand Down
8 changes: 8 additions & 0 deletions vlib/v/tests/c_structs/cstruct.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,11 @@
struct Abc {
int field;
};

typedef struct Test2 {
int a;
} Test2;

typedef struct Test1 {
Test2 a;
} Test1;
50 changes: 50 additions & 0 deletions vlib/v/tests/c_structs/cstruct_str_test.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#include "@VMODROOT/cstruct.h"

@[typedef]
struct C.Test2 {}

@[typedef]
struct C.Test1 {
a C.Test2
}

fn (s C.Test2) str() string {
return 'test2'
}

fn (s C.Test1) get() C.Test1 {
return s
}

fn (s C.Test1) s() string {
return '.${s.get()}.'
}

type TestAlias = C.Test2

fn (s TestAlias) str() string {
return 'test_alias'
}

fn (s TestAlias) get() TestAlias {
return s
}

fn test_main() {
x := unsafe { &C.Test1(malloc(1024)) }
println(x)
assert dump('${x}') == '&C.Test1{
a: test2
}'
println('.${x.get()}.${x.s()}')

y := unsafe { &TestAlias(malloc(1024)) }
println(y)
assert dump('${y}') == '&test_alias'
println('.${y.get()}.')

w := TestAlias(*y)
assert dump(w.str()) == 'test_alias'

assert true
}

0 comments on commit 24d1572

Please sign in to comment.