// Copyright (c) 2026 Alexander Medvednikov. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. module cleanc import v2.ast import v2.types struct InterfaceMethodInfo { name string // method name (e.g., "draw") cast_signature string // function pointer cast (e.g., "int (*)(void*)") ret_type string param_types []string } struct InterfaceWrapperSpec { fn_name string concrete_type string method InterfaceMethodInfo } struct EmbeddedMethodResolve { fn_name string owner_path string receiver_type string } struct InterfaceDataFieldInfo { name string c_type string } fn (mut g Gen) interface_method_info_from_ast(name string, fn_type ast.FnType) InterfaceMethodInfo { mut ret := 'void' if fn_type.return_type !is ast.EmptyExpr { ret = g.expr_type_to_c(fn_type.return_type) } mut param_types := []string{} mut cast_sig := '${ret} (*)(void*' for param in fn_type.params { mut t := g.expr_type_to_c(param.typ) if param.is_mut && !t.ends_with('*') { t += '*' } cast_sig += ', ${t}' param_types << t } cast_sig += ')' return InterfaceMethodInfo{ name: name cast_signature: cast_sig ret_type: ret param_types: param_types } } fn (mut g Gen) interface_method_info_from_raw(name string, raw types.Type) ?InterfaceMethodInfo { if !type_has_valid_data(raw) { return none } match raw { types.FnType { mut ret := 'void' if ret_type := raw.get_return_type() { ret = g.types_type_to_c(ret_type) } mut param_types := []string{} for param_type in raw.get_param_types() { param_types << g.types_type_to_c(param_type) } mut cast_sig := '${ret} (*)(void*' for param_type in param_types { cast_sig += ', ${param_type}' } cast_sig += ')' return InterfaceMethodInfo{ name: name cast_signature: cast_sig ret_type: ret param_types: param_types } } types.Alias { if raw.base_type is types.FnType { return g.interface_method_info_from_raw(name, raw.base_type) } return none } else { return none } } } fn (mut g Gen) interface_method_info(field ast.FieldDecl) ?InterfaceMethodInfo { if !field.is_interface_method { return none } if fn_type := g.get_fn_type_from_expr(field.typ) { return g.interface_method_info_from_ast(field.name, fn_type) } if raw := g.get_raw_type(field.typ) { // Only treat as method if the AST type was directly a FnType, // NOT if it's a named type alias for a function (e.g. `on_scroll_change ScrollViewChangedFn`). // Named fn-type aliases are interface data fields, not vtable methods. if raw is types.Alias && raw.base_type is types.FnType { // This is a fn-type alias field, not a method declaration return none } return g.interface_method_info_from_raw(field.name, raw) } return none } fn interface_type_id_for_name(name string) int { match name { 'None__' { return 1 } 'Error' { return 2 } 'MessageError' { return 3 } else { // Derive a deterministic non-zero id for all other concrete/interface types. // This keeps `if iface is ConcreteType` checks working beyond IError variants. if name.len == 0 { return 0 } mut hash := u32(2166136261) for b in name.bytes() { hash ^= u32(b) hash *= 16777619 } mut type_id := int(hash & 0x7fffffff) if type_id <= 3 { type_id += 4 } return type_id } } } fn interface_wrapper_name(iface_name string, concrete_type string, method_name string) string { return '__iface_wrap_' + mangle_alias_component(iface_name) + '_' + mangle_alias_component(concrete_type) + '_' + method_name } fn interface_clone_fn_name(iface_name string) string { return '__v_interface_clone__' + mangle_alias_component(iface_name) } fn (mut g Gen) collect_interface_wrapper_specs() { if g.interface_wrapper_specs.len > 0 { return } mut iface_names := g.interface_methods.keys() iface_names.sort() mut fn_names := g.fn_param_is_ptr.keys() fn_names.sort() for iface_name in iface_names { methods := g.interface_methods[iface_name] for method in methods { suffix := '__${method.name}' for fn_name in fn_names { if !fn_name.ends_with(suffix) { continue } ptr_params := g.fn_param_is_ptr[fn_name] or { continue } if ptr_params.len == 0 || ptr_params[0] { // Pointer receivers can be called directly through void*. continue } fn_params := g.fn_param_types[fn_name] or { continue } if fn_params.len != method.param_types.len + 1 { continue } if fn_ret := g.fn_return_types[fn_name] { if fn_ret != method.ret_type { continue } } concrete_type := fn_name[..fn_name.len - suffix.len] receiver_type := fn_params[0].trim_right('*') if receiver_type != concrete_type { continue } wrapper_name := interface_wrapper_name(iface_name, concrete_type, method.name) if wrapper_name in g.interface_wrapper_specs { continue } g.interface_wrapper_specs[wrapper_name] = InterfaceWrapperSpec{ fn_name: fn_name concrete_type: concrete_type method: method } } } } for iface_name in iface_names { methods := g.interface_methods[iface_name] if g.has_flat() { for i in 0 .. g.flat.files.len { fc := g.flat.file_cursor(i) g.set_file_cursor_module(fc) stmts := fc.stmts() for j in 0 .. stmts.len() { stmt := stmts.at(j) if stmt.kind() != .stmt_struct_decl { continue } decl := stmt.struct_decl() if decl.language != .v { continue } concrete_type := g.get_struct_name(decl) if concrete_type == '' || g.is_interface_type(concrete_type) { continue } for method in methods { direct_name := '${concrete_type}__${method.name}' if direct_name in g.fn_param_is_ptr || direct_name in g.fn_return_types { continue } resolved := g.resolve_embedded_method_info(concrete_type, method.name) or { continue } wrapper_name := interface_wrapper_name(iface_name, concrete_type, method.name) if wrapper_name in g.interface_wrapper_specs { continue } g.interface_wrapper_specs[wrapper_name] = InterfaceWrapperSpec{ fn_name: resolved.fn_name concrete_type: concrete_type method: method } } } } continue } for file in g.files { g.set_file_module(file) for stmt in file.stmts { if stmt is ast.StructDecl && stmt.language == .v { concrete_type := g.get_struct_name(stmt) if concrete_type == '' || g.is_interface_type(concrete_type) { continue } for method in methods { direct_name := '${concrete_type}__${method.name}' if direct_name in g.fn_param_is_ptr || direct_name in g.fn_return_types { continue } resolved := g.resolve_embedded_method_info(concrete_type, method.name) or { continue } wrapper_name := interface_wrapper_name(iface_name, concrete_type, method.name) if wrapper_name in g.interface_wrapper_specs { continue } g.interface_wrapper_specs[wrapper_name] = InterfaceWrapperSpec{ fn_name: resolved.fn_name concrete_type: concrete_type method: method } } } } } } } fn (mut g Gen) emit_interface_method_wrapper_decls() { mut names := g.interface_wrapper_specs.keys() names.sort() for name in names { spec := g.interface_wrapper_specs[name] g.sb.write_string('static ${spec.method.ret_type} ${name}(void* _obj') for i, param_type in spec.method.param_types { g.sb.write_string(', ${param_type} _arg${i}') } g.sb.writeln(');') } if names.len > 0 { g.sb.writeln('') } } fn (mut g Gen) emit_interface_method_wrapper_body(name string, spec InterfaceWrapperSpec) { g.sb.write_string('static ${spec.method.ret_type} ${name}(void* _obj') for i, param_type in spec.method.param_types { g.sb.write_string(', ${param_type} _arg${i}') } g.sb.writeln(') {') if spec.method.ret_type != 'void' { g.sb.write_string('\treturn ') } else { g.sb.write_string('\t') } mut receiver_type := spec.concrete_type if param_types := g.fn_param_types[spec.fn_name] { if param_types.len > 0 { receiver_type = param_types[0].trim_space().trim_right('*') } } ptr_params := g.fn_param_is_ptr[spec.fn_name] or { []bool{} } receiver_is_ptr := ptr_params.len > 0 && ptr_params[0] owner_path := if receiver_type != spec.concrete_type { g.embedded_owner_path_for_type(spec.concrete_type, receiver_type) } else { '' } g.sb.write_string('${spec.fn_name}(') if owner_path != '' { if receiver_is_ptr { g.sb.write_string('&(((${spec.concrete_type}*)_obj)->${owner_path})') } else { g.sb.write_string('(((${spec.concrete_type}*)_obj)->${owner_path})') } } else if receiver_is_ptr { g.sb.write_string('((${spec.concrete_type}*)_obj)') } else { g.sb.write_string('*(((${spec.concrete_type}*)_obj))') } for i in 0 .. spec.method.param_types.len { g.sb.write_string(', _arg${i}') } g.sb.writeln(');') g.sb.writeln('}') g.sb.writeln('') } fn (mut g Gen) emit_needed_interface_method_wrappers() { mut names := g.needed_interface_wrappers.keys() names.sort() if names.len == 0 { return } for name in names { spec := g.interface_wrapper_specs[name] or { continue } g.emit_interface_method_wrapper_body(name, spec) } } fn (mut g Gen) emit_interface_clone_decls() { mut names := g.emitted_interface_bodies.keys() names.sort() for name in names { g.sb.writeln('static inline ${name} ${interface_clone_fn_name(name)}(${name} x);') } if names.len > 0 { g.sb.writeln('') } } fn (mut g Gen) interface_clone_ptr_expr(ctype string, ptr_name string) string { if ctype == 'string' || ctype == 'builtin__string' { return '(${ctype}*)memdup(&(${ctype}){string__clone(*(${ctype}*)${ptr_name})}, sizeof(${ctype}))' } if ctype == 'map' || ctype.starts_with('Map_') { return '(${ctype}*)memdup(&(${ctype}){map__clone((${ctype}*)${ptr_name})}, sizeof(${ctype}))' } if ctype == 'array' || ctype.starts_with('Array_') { depth := g.array_clone_depth_for_c_type(ctype) return '(${ctype}*)memdup(&(${ctype}){array__clone_to_depth((${ctype}*)${ptr_name}, ${depth})}, sizeof(${ctype}))' } return '(${ctype}*)memdup(${ptr_name}, sizeof(${ctype}))' } fn (mut g Gen) emit_interface_clone_body(iface_name string) { clone_fn := interface_clone_fn_name(iface_name) g.sb.writeln('static inline ${iface_name} ${clone_fn}(${iface_name} x) {') g.sb.writeln('\tif (x._object == 0) {') g.sb.writeln('\t\treturn x;') g.sb.writeln('\t}') data_fields := g.interface_data_fields[iface_name] or { []InterfaceDataFieldInfo{} } for ctype in g.find_concrete_types_for_interface(iface_name) { type_short := if ctype.contains('__') { ctype.all_after_last('__') } else { ctype } type_id := interface_type_id_for_name(type_short) clone_ptr_expr := g.interface_clone_ptr_expr(ctype, 'x._object') g.sb.writeln('\tif (x._type_id == ${type_id}) {') g.sb.writeln('\t\t${ctype}* _clone = ${clone_ptr_expr};') g.sb.writeln('\t\t${iface_name} cloned = x;') g.sb.writeln('\t\tcloned._object = (void*)_clone;') for df in data_fields { owner := g.embedded_owner_for(ctype, df.name) if owner != '' { g.sb.writeln('\t\tcloned.${df.name} = &(_clone->${owner}.${df.name});') } else { g.sb.writeln('\t\tcloned.${df.name} = &(_clone->${df.name});') } } g.sb.writeln('\t\treturn cloned;') g.sb.writeln('\t}') } g.sb.writeln('\treturn x;') g.sb.writeln('}') g.sb.writeln('') } fn (mut g Gen) emit_interface_clone_helpers() { mut names := g.emitted_interface_bodies.keys() names.sort() for name in names { g.emit_interface_clone_body(name) } } fn (mut g Gen) emit_option_string_clone_helper() { if '_option_string' !in g.option_aliases { return } g.sb.writeln('static _option_string builtin__Option_string__clone(_option_string s) {') g.sb.writeln('\tif (s.state == 0) {') g.sb.writeln('\t\tstring _val = string__clone(*(string*)(((u8*)(&s.err)) + sizeof(IError)));') g.sb.writeln('\t\t_option_string _opt = (_option_string){ .state = 2 };') g.sb.writeln('\t\t_option_ok(&_val, (_option*)&_opt, sizeof(_val));') g.sb.writeln('\t\treturn _opt;') g.sb.writeln('\t}') g.sb.writeln('\treturn s;') g.sb.writeln('}') g.sb.writeln('') } fn (mut g Gen) struct_type_embeds_error(st types.Struct) bool { for embedded in st.embedded { embedded_c := g.types_type_to_c(embedded) if short_type_name(embedded_c) in ['Error', 'MessageError'] || ('${embedded_c}__msg' in g.fn_return_types && '${embedded_c}__code' in g.fn_return_types) { return true } live_emb := if embedded.fields.len == 0 { g.lookup_struct_type_by_c_name(embedded_c) } else { embedded } if (live_emb.fields.len > 0 || live_emb.embedded.len > 0) && g.struct_type_embeds_error(live_emb) { return true } } return false } fn (mut g Gen) struct_c_type_embeds_error(c_type string) bool { st := g.lookup_struct_type_by_c_name(c_type.trim_right('*')) if st.fields.len == 0 && st.embedded.len == 0 { return false } return g.struct_type_embeds_error(st) } fn (mut g Gen) concrete_ierror_base_for_c_type(c_type string) string { base := g.qualify_ierror_concrete_base(c_type.trim_right('*')) if base == '' || base == 'int' || base in ['IError', 'builtin__IError'] { return '' } if g.c_type_has_ierror_shape(base) || g.struct_c_type_embeds_error(base) { return base } return '' } fn (mut g Gen) c_type_has_ierror_shape(base string) bool { if '${base}__msg' in g.fn_return_types { return true } mut names := [base] if base.contains('__') { names << base.all_after_last('__') } if g.env != unsafe { nil } { for name in names { if _ := g.env.lookup_method(name, 'msg') { return true } } } return false } fn is_ierror_c_type(c_type string) bool { mut typ := c_type.trim_space().trim_right('*').trim_space() if typ.starts_with('struct ') { typ = typ['struct '.len..].trim_space() } return is_ierror_interface_name(typ) } fn ierror_type_label_for_base(base string) string { if base.contains('__') { short_name := base.all_after_last('__') if short_name != '' { return short_name } } return base } fn (mut g Gen) gen_ierror_from_base_expr(base string, expr ast.Expr, expr_type string) bool { if base == '' || base == 'int' || base in ['IError', 'builtin__IError'] { return false } g.ierror_wrapper_bases[base] = true g.needed_ierror_wrapper_bases[base] = true type_label := ierror_type_label_for_base(base) type_id := interface_type_id_for_name(type_label) if expr_type.ends_with('*') { g.sb.write_string('((IError){._object = (void*)(') g.expr(expr) g.sb.write_string('), ._type_id = ${type_id}, .type_name = IError_${base}_type_name_wrapper, .msg = IError_${base}_msg_wrapper, .code = IError_${base}_code_wrapper})') return true } tmp_name := '_ierr_obj${g.tmp_counter}' g.tmp_counter++ malloc_call := g.c_heap_malloc_call('sizeof(${base})') g.sb.write_string('({ ${base}* ${tmp_name} = (${base}*)${malloc_call}; *${tmp_name} = ') g.expr(expr) g.sb.write_string('; ((IError){._object = (void*)${tmp_name}, ._type_id = ${type_id}, .type_name = IError_${base}_type_name_wrapper, .msg = IError_${base}_msg_wrapper, .code = IError_${base}_code_wrapper}); })') return true } fn (mut g Gen) gen_ierror_from_concrete_expr(expr ast.Expr, expr_type string) bool { mut base := '' expr_base := g.ierror_concrete_base_for_expr(expr) if expr_base != '' { base = g.concrete_ierror_base_for_c_type(expr_base) } if base == '' { base = g.concrete_ierror_base_for_c_type(expr_type) } if base == '' { return false } return g.gen_ierror_from_base_expr(base, expr, expr_type) } fn (mut g Gen) collect_ierror_wrapper_bases() { for fn_name, ret_type in g.fn_return_types { if !fn_name.ends_with('__msg') || ret_type != 'string' { continue } base := fn_name[..fn_name.len - '__msg'.len] if base == '' || !is_c_identifier_like(base) { continue } g.ierror_wrapper_bases[base] = true } saved_module := g.cur_module if g.has_flat() { for i in 0 .. g.flat.files.len { fc := g.flat.file_cursor(i) g.set_file_cursor_module(fc) stmts := fc.stmts() for j in 0 .. stmts.len() { stmt := stmts.at(j) if stmt.kind() == .stmt_struct_decl { decl := stmt.struct_decl() if decl.language == .v && decl.embedded.len > 0 { base := g.get_struct_name(decl) if g.struct_c_type_embeds_error(base) { g.ierror_wrapper_bases[base] = true } } } else if stmt.kind() == .stmt_const_decl { const_decl := stmt.const_decl() for field in const_decl.fields { if base := g.ierror_const_wrapper_base(field.value) { g.ierror_wrapper_bases[base] = true } } } } } g.cur_module = saved_module return } for file in g.files { g.set_file_module(file) for stmt in file.stmts { if stmt is ast.StructDecl && stmt.language == .v && stmt.embedded.len > 0 { base := g.get_struct_name(stmt) if g.struct_c_type_embeds_error(base) { g.ierror_wrapper_bases[base] = true } } else if stmt is ast.ConstDecl { const_decl := stmt as ast.ConstDecl for field in const_decl.fields { if base := g.ierror_const_wrapper_base(field.value) { g.ierror_wrapper_bases[base] = true } } } } } g.cur_module = saved_module } fn (mut g Gen) ierror_const_wrapper_base(expr ast.Expr) ?string { match expr { ast.CastExpr { type_name := g.expr_type_to_c(expr.typ) if type_name in ['IError', 'builtin__IError'] { mut base := g.raw_concrete_type_for_interface_value(type_name, expr.expr).trim_right('*') if base == '' || base == 'int' || base in ['IError', 'builtin__IError'] { base = g.ierror_init_expr_base(expr.expr) or { '' } } base = g.qualify_ierror_concrete_base(base.trim_right('*')) if base != '' && base != 'int' && base !in ['IError', 'builtin__IError'] { return base } } return g.ierror_const_wrapper_base(expr.expr) } ast.ParenExpr { return g.ierror_const_wrapper_base(expr.expr) } else { return none } } } fn (mut g Gen) ierror_init_expr_base(expr ast.Expr) ?string { match expr { ast.PrefixExpr { return g.ierror_init_expr_base(expr.expr) } ast.ParenExpr { return g.ierror_init_expr_base(expr.expr) } ast.InitExpr { base := g.expr_type_to_c(expr.typ).trim_right('*') if base != '' && base != 'int' { return base } } else {} } return none } fn (mut g Gen) mark_needed_ierror_wrapper_from_ident(name string) { base := ierror_wrapper_base_from_ident(name) if base == '' { return } g.needed_ierror_wrapper_bases[base] = true } fn (g &Gen) should_emit_ierror_wrappers() bool { return g.has_emitted_ierror_body() } fn (g &Gen) has_emitted_ierror_body() bool { for name, _ in g.emitted_interface_bodies { if is_ierror_interface_name(name) { return true } } return false } fn (mut g Gen) emit_ierror_wrapper_decls() { if !g.should_emit_ierror_wrappers() { return } g.collect_ierror_wrapper_bases() mut bases := g.ierror_wrapper_bases.keys() bases.sort() for base in bases { g.sb.writeln('static string IError_${base}_type_name_wrapper(void* _obj);') g.sb.writeln('static string IError_${base}_msg_wrapper(void* _obj);') g.sb.writeln('static int IError_${base}_code_wrapper(void* _obj);') } if bases.len > 0 { g.sb.writeln('') } } fn (mut g Gen) emit_ierror_wrapper_body(base string) { type_label := ierror_type_label_for_base(base) g.sb.writeln('static string IError_${base}_type_name_wrapper(void* _obj) {') g.sb.writeln('\t(void)_obj;') g.sb.writeln('\treturn ${c_static_v_string_expr(type_label)};') g.sb.writeln('}') g.sb.writeln('static string IError_${base}_msg_wrapper(void* _obj) {') if '${base}__msg' in g.fn_return_types { g.sb.writeln('\treturn ${base}__msg(*(${base}*)_obj);') } else if resolved := g.resolve_embedded_method_info(base, 'msg') { if params := g.fn_param_is_ptr[resolved.fn_name] { if params.len > 0 && params[0] { g.sb.writeln('\treturn ${resolved.fn_name}(&(((${base}*)_obj)->${resolved.owner_path}));') } else { g.sb.writeln('\treturn ${resolved.fn_name}(((${base}*)_obj)->${resolved.owner_path});') } } else { g.sb.writeln('\treturn ${resolved.fn_name}(((${base}*)_obj)->${resolved.owner_path});') } } else { g.sb.writeln('\t(void)_obj;') g.sb.writeln('\treturn ${c_empty_v_string_expr()};') } g.sb.writeln('}') g.sb.writeln('static int IError_${base}_code_wrapper(void* _obj) {') code_fn := '${base}__code' if code_fn in g.fn_return_types { g.sb.writeln('\treturn ${base}__code(*(${base}*)_obj);') } else if resolved := g.resolve_embedded_method_info(base, 'code') { if params := g.fn_param_is_ptr[resolved.fn_name] { if params.len > 0 && params[0] { g.sb.writeln('\treturn ${resolved.fn_name}(&(((${base}*)_obj)->${resolved.owner_path}));') } else { g.sb.writeln('\treturn ${resolved.fn_name}(((${base}*)_obj)->${resolved.owner_path});') } } else { g.sb.writeln('\treturn ${resolved.fn_name}(((${base}*)_obj)->${resolved.owner_path});') } } else { g.sb.writeln('\t(void)_obj;') g.sb.writeln('\treturn 1;') } g.sb.writeln('}') } fn (mut g Gen) emit_fn_decl_by_c_name(fn_name string) { if fn_name == '' || 'fn_${fn_name}' in g.emitted_types { return } saved_file_name := g.cur_file_name saved_module := g.cur_module saved_import_modules := g.cur_import_modules.clone() defer { g.cur_file_name = saved_file_name g.cur_module = saved_module g.cur_import_modules = saved_import_modules.clone() g.is_module_ident_cache.clear() g.resolved_module_names.clear() } if g.has_flat() { for i in 0 .. g.flat.files.len { fc := g.flat.file_cursor(i) g.set_file_cursor_module(fc) if (g.emit_modules.len > 0 || g.emit_files.len > 0) && !g.should_emit_current_file() { continue } stmts := fc.stmts() for j in 0 .. stmts.len() { stmt := stmts.at(j) if stmt.kind() != .stmt_fn_decl { continue } decl_sig := stmt.fn_decl_signature() if g.get_fn_name(decl_sig) == fn_name { decl := stmt.fn_decl() g.gen_fn_decl_with_name(decl, fn_name) return } } } return } for file in g.files { g.set_file_module(file) if (g.emit_modules.len > 0 || g.emit_files.len > 0) && !g.should_emit_current_file() { continue } for stmt in file.stmts { if stmt is ast.FnDecl && g.get_fn_name(stmt) == fn_name { g.gen_fn_decl_with_name(stmt, fn_name) return } } } } fn (mut g Gen) emit_ierror_wrapper_dependencies(base string) { for method_name in ['msg', 'code'] { direct_name := '${base}__${method_name}' if direct_name in g.fn_return_types || direct_name in g.v_fn_return_types { g.emit_fn_decl_by_c_name(direct_name) continue } if resolved := g.resolve_embedded_method_info(base, method_name) { g.emit_fn_decl_by_c_name(resolved.fn_name) } } } fn (mut g Gen) emit_needed_ierror_wrappers() { if !g.should_emit_ierror_wrappers() { return } mut bases := g.needed_ierror_wrapper_bases.keys() bases.sort() if bases.len == 0 { return } mut emitted_any := false for base in bases { if base !in g.ierror_wrapper_bases { continue } g.emit_ierror_wrapper_dependencies(base) g.emit_ierror_wrapper_body(base) emitted_any = true } if emitted_any { g.sb.writeln('') } } fn (mut g Gen) get_interface_name(node ast.InterfaceDecl) string { if g.cur_module != '' && g.cur_module != 'main' && g.cur_module != 'builtin' { return '${g.cur_module}__${node.name}' } return node.name } fn (g &Gen) lookup_interface_data_field(iface_name string, field_name string) ?InterfaceDataFieldInfo { mut candidates := []string{cap: 3} candidates << iface_name if iface_name.contains('__') { candidates << iface_name.all_after_last('__') } for candidate in candidates { if fields := g.interface_data_fields[candidate] { for field in fields { if field.name == field_name { return field } } } } return none } fn (mut g Gen) selector_interface_data_field(sel ast.SelectorExpr) ?InterfaceDataFieldInfo { if raw_type := g.get_raw_type(sel.lhs) { match raw_type { types.Interface { return g.lookup_interface_data_field(g.types_type_to_c(raw_type), sel.rhs.name) } types.Pointer { if raw_type.base_type is types.Interface { return g.lookup_interface_data_field(g.types_type_to_c(raw_type.base_type), sel.rhs.name) } } else {} } } lhs_type := g.get_expr_type(sel.lhs) if lhs_type != '' { if field := g.lookup_interface_data_field(lhs_type, sel.rhs.name) { return field } } return none } fn (g &Gen) interface_type_ready(type_name string) bool { typ := type_name.trim_space() if typ == '' || typ == 'void' { return true } if typ in primitive_types { return true } if g.is_c_type_name(typ) { return true } if typ.ends_with('*') || typ.ends_with('ptr') { return true } if typ == 'string' || typ == 'builtin__string' { return 'body_string' in g.emitted_types || 'body_builtin__string' in g.emitted_types } if typ.starts_with('_option_') { return typ in g.emitted_option_structs } if typ.starts_with('_result_') { return typ in g.emitted_result_structs } if typ.starts_with('Array_fixed_') { return 'body_${typ}' in g.emitted_types || 'alias_${typ}' in g.emitted_types } if typ == 'array' || typ.starts_with('Array_') || typ in g.array_aliases { return 'body_array' in g.emitted_types } if typ == 'map' || typ.starts_with('Map_') || typ in g.map_aliases { return 'body_map' in g.emitted_types } return 'body_${typ}' in g.emitted_types || 'enum_${typ}' in g.emitted_types || 'alias_${typ}' in g.emitted_types } fn (mut g Gen) interface_fields_resolved(node ast.InterfaceDecl) bool { if !g.interface_type_ready('string') { return false } for field in node.fields { if method := g.interface_method_info(field) { if !g.interface_type_ready(method.ret_type) { return false } for param_type in method.param_types { if !g.interface_type_ready(param_type) { return false } } continue } field_type := g.field_type_name(field.typ) if !g.interface_type_ready(field_type) { return false } } return true } // resolve_embedded_method checks if a method exists on an embedded struct type. // For example, ssl__SSLConn embeds openssl__SSLConn, so ssl__SSLConn__addr // resolves to openssl__SSLConn__addr. fn (mut g Gen) resolve_embedded_method(struct_name string, method_name string) string { if resolved := g.resolve_embedded_method_info(struct_name, method_name) { return resolved.fn_name } return '' } fn (mut g Gen) resolve_embedded_method_info(struct_name string, method_name string) ?EmbeddedMethodResolve { st := g.lookup_struct_type_by_c_name(struct_name) for emb in st.embedded { emb_c_name := g.types_type_to_c(types.Type(emb)).trim_right('*') if emb_c_name == '' { continue } owner := embedded_owner_field_name(emb_c_name) candidate := '${emb_c_name}__${method_name}' if candidate in g.fn_param_is_ptr || candidate in g.fn_return_types { return EmbeddedMethodResolve{ fn_name: candidate owner_path: owner receiver_type: emb_c_name } } nested := g.resolve_embedded_method_info(emb_c_name, method_name) or { continue } return EmbeddedMethodResolve{ fn_name: nested.fn_name owner_path: '${owner}.${nested.owner_path}' receiver_type: nested.receiver_type } } return none } fn (mut g Gen) embedded_owner_path_for_type(struct_name string, target_type string) string { st := g.lookup_struct_type_by_c_name(struct_name) target_base := target_type.trim_right('*') for emb in st.embedded { emb_c_name := g.types_type_to_c(types.Type(emb)).trim_right('*') if emb_c_name == '' { continue } owner := embedded_owner_field_name(emb_c_name) if emb_c_name == target_base || short_type_name(emb_c_name) == short_type_name(target_base) { return owner } nested := g.embedded_owner_path_for_type(emb_c_name, target_base) if nested != '' { return '${owner}.${nested}' } } return '' } fn (mut g Gen) resolve_embedded_interface_fields(emb_name string) ?[]ast.FieldDecl { if decl := g.interface_decls[emb_name] { mut fields := []ast.FieldDecl{} // Recursively resolve further embeddings for emb_expr in decl.embedded { inner_name := g.expr_type_to_c(emb_expr) if inner_name != '' { if inner_fields := g.resolve_embedded_interface_fields(inner_name) { fields << inner_fields } } } fields << decl.fields return fields } return none } fn (mut g Gen) gen_interface_decl(node ast.InterfaceDecl) { name := g.get_interface_name(node) body_key := 'body_${name}' if body_key in g.emitted_types { return } g.emitted_types[body_key] = true g.sb.writeln('struct ${name} {') g.sb.writeln('\tvoid* _object;') g.sb.writeln('\tint _type_id;') mut has_type_name := false for field in node.fields { if field.name == 'type_name' { has_type_name = true break } } if !has_type_name { g.sb.writeln('\tstring (*type_name)(void*);') } // Collect all fields including those from embedded interfaces mut all_fields := []ast.FieldDecl{} for emb_expr in node.embedded { emb_name := g.expr_type_to_c(emb_expr) if emb_name != '' { // Look up the embedded interface declaration and include its fields if emb_fields := g.resolve_embedded_interface_fields(emb_name) { all_fields << emb_fields } } } all_fields << node.fields // Generate function pointers for each method mut methods := []InterfaceMethodInfo{} mut data_fields := []InterfaceDataFieldInfo{} for field in all_fields { if method := g.interface_method_info(field) { g.sb.write_string('\t${method.ret_type} (*${method.name})(void*') for param_type in method.param_types { g.sb.write_string(', ${param_type}') } g.sb.writeln(');') methods << method continue } // IError.msg() and IError.code() — the AST may not expose the fn // type, but the C layout requires function pointer fields. if name == 'IError' && field.name == 'msg' { info := InterfaceMethodInfo{ name: 'msg' cast_signature: 'string (*)(void*)' ret_type: 'string' param_types: []string{} } g.sb.writeln('\tstring (*msg)(void*);') methods << info continue } if name == 'IError' && field.name == 'code' { info := InterfaceMethodInfo{ name: 'code' cast_signature: 'int (*)(void*)' ret_type: 'int' param_types: []string{} } g.sb.writeln('\tint (*code)(void*);') methods << info continue } // Regular field t := g.expr_type_to_c(field.typ) // Match the v1 C layout for interface data fields: store pointers to the // concrete value's fields instead of embedding copies by value. This avoids // impossible recursive layouts like `?Interface` inside the interface itself. g.sb.writeln('\t${t}* ${field.name};') data_fields << InterfaceDataFieldInfo{ name: field.name c_type: t } } g.interface_methods[name] = methods g.interface_data_fields[name] = data_fields g.sb.writeln('};') g.sb.writeln('') g.emitted_interface_bodies[name] = true } // find_concrete_types_for_interface returns all concrete type names that implement // the given interface (i.e., have all required methods AND data fields). fn (mut g Gen) find_concrete_types_for_interface(iface_name string) []string { methods := g.interface_methods[iface_name] or { return [] } data_fields := g.interface_data_fields[iface_name] or { []InterfaceDataFieldInfo{} } if methods.len == 0 && data_fields.len == 0 { return [] } // Collect candidate concrete types from the first method's suffix mut candidates := map[string]bool{} if methods.len > 0 { first_suffix := '__${methods[0].name}' for fn_name, _ in g.fn_return_types { if fn_name.ends_with(first_suffix) { base := fn_name[..fn_name.len - first_suffix.len] if base.len == 0 || g.is_interface_type(base) { continue } param_types := g.fn_param_types[fn_name] or { []string{} } if param_types.len == 0 { continue } receiver_type := param_types[0].trim_space().trim_right('*') if receiver_type != base { continue } candidates[base] = true } } } // Filter to types that have ALL methods AND all data fields mut result := []string{} for cand, _ in candidates { mut has_all := true for method in methods { fn_name := '${cand}__${method.name}' if fn_name !in g.fn_return_types { has_all = false break } param_types := g.fn_param_types[fn_name] or { []string{} } if param_types.len == 0 { has_all = false break } receiver_type := param_types[0].trim_space().trim_right('*') if receiver_type != cand { has_all = false break } } if has_all { // Also check that the concrete type has all required data fields for df in data_fields { field_key := '${cand}.${df.name}' if field_key !in g.struct_field_types { // Try with embedded struct owner emb_key := '${cand}.${df.name}' short_cand := if cand.contains('__') { cand.all_after_last('__') } else { cand } emb_short_key := '${short_cand}.${df.name}' if emb_key !in g.embedded_field_owner && emb_short_key !in g.embedded_field_owner { has_all = false break } } } } if has_all { body_key := 'body_${cand}' if body_key in g.emitted_types || body_key in g.pending_late_body_keys { result << cand } } } result.sort() return result } // gen_iface_to_iface_cast generates an inline interface-to-interface conversion // using a GCC statement expression. Switches on _type_id to find the concrete type, // then constructs the target interface from (ConcreteType*)(_object). fn (mut g Gen) gen_iface_to_iface_cast(src_iface string, tgt_iface string, value_expr ast.Expr) bool { concrete_types := g.find_concrete_types_for_interface(tgt_iface) if concrete_types.len == 0 { // No known concrete types — fall back to raw expression g.expr(value_expr) return true } tmp := '_iconv${g.tmp_counter}' g.tmp_counter++ g.sb.write_string('({ ${src_iface} ${tmp} = ') g.expr(value_expr) g.sb.write_string('; ') for i, ctype in concrete_types { type_short := if ctype.contains('__') { ctype.all_after_last('__') } else { ctype } type_id := interface_type_id_for_name(type_short) if i == 0 { g.sb.write_string('(${tmp}._type_id == ${type_id}) ? ') } else { g.sb.write_string(' : (${tmp}._type_id == ${type_id}) ? ') } // Construct target interface from (ConcreteType*)(src._object) g.sb.write_string('(${tgt_iface}){._object = ${tmp}._object, ._type_id = ${tmp}._type_id') // Method pointers if methods := g.interface_methods[tgt_iface] { for method in methods { fn_name := '${ctype}__${method.name}' mut target_name := fn_name if ptr_params := g.fn_param_is_ptr[fn_name] { if ptr_params.len > 0 && !ptr_params[0] { target_name = interface_wrapper_name(tgt_iface, ctype, method.name) g.needed_interface_wrappers[target_name] = true if target_name !in g.interface_wrapper_specs { g.interface_wrapper_specs[target_name] = InterfaceWrapperSpec{ fn_name: fn_name concrete_type: ctype method: method } } } } g.sb.write_string(', .${method.name} = (${method.cast_signature})${target_name}') } } // Data field pointers if data_fields := g.interface_data_fields[tgt_iface] { for df in data_fields { embedded_owner := g.embedded_owner_for(ctype, df.name) if embedded_owner != '' { g.sb.write_string(', .${df.name} = &((${ctype}*)(${tmp}._object))->${embedded_owner}.${df.name}') } else { g.sb.write_string(', .${df.name} = &((${ctype}*)(${tmp}._object))->${df.name}') } } } g.sb.write_string('}') } // Default: zeroed struct g.sb.write_string(' : (${tgt_iface}){0}') g.sb.write_string('; })') return true }