#include "c_lsp.h" #include "lsp_node_iter.h" #include "../helpers.h" #include #include #include #include /* Safe kind accessor — returns CBM_TYPE_UNKNOWN for NULL types. * Prevents SEGV in c_eval_expr_type_inner on unusual C++ AST shapes. */ static inline CBMTypeKind safe_kind(const CBMType *t) { return t ? t->kind : CBM_TYPE_UNKNOWN; } // Forward declarations static void c_resolve_calls_in_node(CLSPContext *ctx, TSNode node); static void c_emit_resolved_call(CLSPContext *ctx, const char *callee_qn, const char *strategy, float confidence); static void c_emit_unresolved_call(CLSPContext *ctx, const char *expr_text, const char *reason); static const CBMType *c_lookup_field_type(CLSPContext *ctx, const char *type_qn, const char *field_name, int depth); static void c_process_function(CLSPContext *ctx, TSNode func_node); static void c_process_namespace(CLSPContext *ctx, TSNode ns_node); static void c_process_class(CLSPContext *ctx, TSNode class_node); static void c_process_body_child(CLSPContext *ctx, TSNode child); static const char *type_to_qn(const CBMType *t); const CBMType *c_simplify_type(CLSPContext *ctx, const CBMType *t, bool unwrap_pointer); const CBMType *c_eval_expr_type(CLSPContext *ctx, TSNode node); // External tree-sitter language functions (defined in grammar_*.c) extern const TSLanguage *tree_sitter_c(void); extern const TSLanguage *tree_sitter_cpp(void); // --- Smart pointer names for deref --- // Checks suffix: "std.shared_ptr", "test.main.std.shared_ptr" both match static bool is_smart_ptr(const char *name) { if (!name) return false; // Extract short name after last dot (or use full name) const char *short_name = strrchr(name, '.'); short_name = short_name ? short_name + 1 : name; // Check if the short name is a smart pointer type AND the QN contains "std" if (strcmp(short_name, "unique_ptr") == 0 || strcmp(short_name, "shared_ptr") == 0 || strcmp(short_name, "weak_ptr") == 0 || strcmp(short_name, "auto_ptr") == 0 || strcmp(short_name, "scoped_ptr") == 0) { return strstr(name, "std") != NULL || strstr(name, "boost") != NULL; } return false; } // --- Helper: get node text --- static char *c_node_text(CLSPContext *ctx, TSNode node) { return cbm_node_text(ctx->arena, node, ctx->source); } // --- Initialization --- void c_lsp_init(CLSPContext *ctx, CBMArena *arena, const char *source, int source_len, const CBMTypeRegistry *registry, const char *module_qn, bool cpp_mode, CBMResolvedCallArray *out) { memset(ctx, 0, sizeof(CLSPContext)); ctx->arena = arena; ctx->source = source; ctx->source_len = source_len; ctx->registry = registry; ctx->module_qn = module_qn; ctx->module_qn_len = module_qn ? strlen(module_qn) : 0; ctx->cpp_mode = cpp_mode; ctx->resolved_calls = out; ctx->current_scope = cbm_scope_push(arena, NULL); const char *debug_env = getenv("CBM_LSP_DEBUG"); ctx->debug = (debug_env && debug_env[0]); } void c_lsp_add_include(CLSPContext *ctx, const char *header_path, const char *ns_qn) { if (ctx->include_count % 32 == 0) { int new_cap = ctx->include_count + 32; const char **new_paths = (const char **)cbm_arena_alloc(ctx->arena, (new_cap + 1) * sizeof(const char *)); const char **new_qns = (const char **)cbm_arena_alloc(ctx->arena, (new_cap + 1) * sizeof(const char *)); if (!new_paths || !new_qns) return; if (ctx->include_paths && ctx->include_count > 0) { memcpy(new_paths, ctx->include_paths, ctx->include_count * sizeof(const char *)); memcpy(new_qns, ctx->include_ns_qns, ctx->include_count * sizeof(const char *)); } ctx->include_paths = new_paths; ctx->include_ns_qns = new_qns; } ctx->include_paths[ctx->include_count] = cbm_arena_strdup(ctx->arena, header_path); ctx->include_ns_qns[ctx->include_count] = cbm_arena_strdup(ctx->arena, ns_qn); ctx->include_count++; } // --- Helper: add using namespace --- static void c_add_using_namespace(CLSPContext *ctx, const char *ns_qn) { if (ctx->using_ns_count >= ctx->using_ns_cap) { int new_cap = ctx->using_ns_cap == 0 ? 16 : ctx->using_ns_cap * 2; const char **new_arr = (const char **)cbm_arena_alloc(ctx->arena, new_cap * sizeof(const char *)); if (!new_arr) return; if (ctx->using_namespaces && ctx->using_ns_count > 0) memcpy(new_arr, ctx->using_namespaces, ctx->using_ns_count * sizeof(const char *)); ctx->using_namespaces = new_arr; ctx->using_ns_cap = new_cap; } ctx->using_namespaces[ctx->using_ns_count++] = cbm_arena_strdup(ctx->arena, ns_qn); } // --- Helper: add using declaration --- static void c_add_using_decl(CLSPContext *ctx, const char *short_name, const char *full_qn) { if (ctx->using_decl_count >= ctx->using_decl_cap) { int new_cap = ctx->using_decl_cap == 0 ? 16 : ctx->using_decl_cap * 2; const char **new_names = (const char **)cbm_arena_alloc(ctx->arena, new_cap * sizeof(const char *)); const char **new_qns = (const char **)cbm_arena_alloc(ctx->arena, new_cap * sizeof(const char *)); if (!new_names || !new_qns) return; if (ctx->using_decl_names && ctx->using_decl_count > 0) { memcpy(new_names, ctx->using_decl_names, ctx->using_decl_count * sizeof(const char *)); memcpy(new_qns, ctx->using_decl_qns, ctx->using_decl_count * sizeof(const char *)); } ctx->using_decl_names = new_names; ctx->using_decl_qns = new_qns; ctx->using_decl_cap = new_cap; } ctx->using_decl_names[ctx->using_decl_count] = cbm_arena_strdup(ctx->arena, short_name); ctx->using_decl_qns[ctx->using_decl_count] = cbm_arena_strdup(ctx->arena, full_qn); ctx->using_decl_count++; } // --- Helper: add namespace alias --- static void c_add_ns_alias(CLSPContext *ctx, const char *alias, const char *full_qn) { if (ctx->ns_alias_count >= ctx->ns_alias_cap) { int new_cap = ctx->ns_alias_cap == 0 ? 8 : ctx->ns_alias_cap * 2; const char **new_names = (const char **)cbm_arena_alloc(ctx->arena, new_cap * sizeof(const char *)); const char **new_qns = (const char **)cbm_arena_alloc(ctx->arena, new_cap * sizeof(const char *)); if (!new_names || !new_qns) return; if (ctx->ns_alias_names && ctx->ns_alias_count > 0) { memcpy(new_names, ctx->ns_alias_names, ctx->ns_alias_count * sizeof(const char *)); memcpy(new_qns, ctx->ns_alias_qns, ctx->ns_alias_count * sizeof(const char *)); } ctx->ns_alias_names = new_names; ctx->ns_alias_qns = new_qns; ctx->ns_alias_cap = new_cap; } ctx->ns_alias_names[ctx->ns_alias_count] = cbm_arena_strdup(ctx->arena, alias); ctx->ns_alias_qns[ctx->ns_alias_count] = cbm_arena_strdup(ctx->arena, full_qn); ctx->ns_alias_count++; } // --- Helper: track function pointer target --- static void c_add_fp_target(CLSPContext *ctx, const char *var_name, const char *target_qn) { if (ctx->fp_count >= ctx->fp_cap) { int new_cap = ctx->fp_cap == 0 ? 16 : ctx->fp_cap * 2; const char **new_names = (const char **)cbm_arena_alloc(ctx->arena, new_cap * sizeof(const char *)); const char **new_qns = (const char **)cbm_arena_alloc(ctx->arena, new_cap * sizeof(const char *)); if (!new_names || !new_qns) return; if (ctx->fp_var_names && ctx->fp_count > 0) { memcpy(new_names, ctx->fp_var_names, ctx->fp_count * sizeof(const char *)); memcpy(new_qns, ctx->fp_target_qns, ctx->fp_count * sizeof(const char *)); } ctx->fp_var_names = new_names; ctx->fp_target_qns = new_qns; ctx->fp_cap = new_cap; } ctx->fp_var_names[ctx->fp_count] = cbm_arena_strdup(ctx->arena, var_name); ctx->fp_target_qns[ctx->fp_count] = cbm_arena_strdup(ctx->arena, target_qn); ctx->fp_count++; } // Look up function pointer target static const char *c_lookup_fp_target(CLSPContext *ctx, const char *var_name) { for (int i = ctx->fp_count - 1; i >= 0; i--) { if (strcmp(ctx->fp_var_names[i], var_name) == 0) return ctx->fp_target_qns[i]; } return NULL; } // --- Helper: extract function name from DLL/dynamic resolver call --- // Heuristic: if an expression is a call (possibly cast-wrapped) with a string // literal argument, the string is likely an external function name being resolved // dynamically (GetProcAddress, dlsym, or custom resolver). // Returns the function name string (without quotes), or NULL. // Sets *out_has_cast to true if the expression was wrapped in a cast. static const char *c_extract_dll_resolve_name(CLSPContext *ctx, TSNode expr, bool *out_has_cast) { if (ts_node_is_null(expr)) return NULL; *out_has_cast = false; // Unwrap cast expressions to find inner call TSNode inner = expr; const char *ik = ts_node_type(inner); for (int unwrap_depth = 0; unwrap_depth < 8; unwrap_depth++) { // Standard cast nodes: (Type)expr, static_cast(expr) etc. if (strcmp(ik, "cast_expression") == 0 || strcmp(ik, "static_cast_expression") == 0 || strcmp(ik, "reinterpret_cast_expression") == 0 || strcmp(ik, "dynamic_cast_expression") == 0 || strcmp(ik, "const_cast_expression") == 0) { *out_has_cast = true; uint32_t nc = ts_node_named_child_count(inner); if (nc == 0) return NULL; inner = ts_node_named_child(inner, nc - 1); if (ts_node_is_null(inner)) return NULL; ik = ts_node_type(inner); continue; } // C++ named casts may parse as call_expression with template_function: // static_cast(expr) → call_expression(template_function("static_cast",), (expr)) if (strcmp(ik, "call_expression") == 0) { TSNode fn = ts_node_child_by_field_name(inner, "function", 8); if (!ts_node_is_null(fn)) { char *fname = NULL; const char *fk = ts_node_type(fn); if (strcmp(fk, "template_function") == 0) { TSNode nn = ts_node_child_by_field_name(fn, "name", 4); if (!ts_node_is_null(nn)) fname = c_node_text(ctx, nn); } else if (strcmp(fk, "identifier") == 0) { fname = c_node_text(ctx, fn); } if (fname && (strcmp(fname, "static_cast") == 0 || strcmp(fname, "reinterpret_cast") == 0 || strcmp(fname, "dynamic_cast") == 0 || strcmp(fname, "const_cast") == 0)) { *out_has_cast = true; TSNode cargs = ts_node_child_by_field_name(inner, "arguments", 9); if (!ts_node_is_null(cargs) && ts_node_named_child_count(cargs) > 0) { inner = ts_node_named_child(cargs, 0); if (ts_node_is_null(inner)) return NULL; ik = ts_node_type(inner); continue; } return NULL; } } } break; } // Must be a call_expression if (strcmp(ik, "call_expression") != 0) return NULL; // Scan call arguments for a string literal TSNode args = ts_node_child_by_field_name(inner, "arguments", 9); if (ts_node_is_null(args)) return NULL; uint32_t anc = ts_node_named_child_count(args); for (uint32_t i = 0; i < anc; i++) { TSNode arg = ts_node_named_child(args, i); if (ts_node_is_null(arg)) continue; const char *ak = ts_node_type(arg); if (strcmp(ak, "string_literal") != 0 && strcmp(ak, "raw_string_literal") != 0) continue; char *text = c_node_text(ctx, arg); if (!text) continue; size_t len = strlen(text); if (len < 2 || text[0] != '"' || text[len - 1] != '"') continue; char *name = cbm_arena_strndup(ctx->arena, text + 1, len - 2); if (!name || !name[0]) continue; // Validate: function names are identifiers (no spaces, path separators, dots) bool valid = true; for (const char *p = name; *p; p++) { if (*p == ' ' || *p == '/' || *p == '\\' || *p == '.') { valid = false; break; } } if (valid) return name; } return NULL; } // --- Helper: pending template calls (member calls on TYPE_PARAM inside templates) --- static void c_add_pending_template_call(CLSPContext *ctx, const char *func_qn, const char *type_param, const char *method_name, int arg_count) { if (!func_qn || !type_param || !method_name) return; if (ctx->pending_tc_count >= ctx->pending_tc_cap) { int new_cap = ctx->pending_tc_cap == 0 ? 16 : ctx->pending_tc_cap * 2; size_t sz = new_cap * sizeof(ctx->pending_template_calls[0]); void *new_arr = cbm_arena_alloc(ctx->arena, sz); if (!new_arr) return; if (ctx->pending_template_calls && ctx->pending_tc_count > 0) memcpy(new_arr, ctx->pending_template_calls, ctx->pending_tc_count * sizeof(ctx->pending_template_calls[0])); ctx->pending_template_calls = new_arr; ctx->pending_tc_cap = new_cap; } const char *func_qn_copy = cbm_arena_strdup(ctx->arena, func_qn); const char *type_param_copy = cbm_arena_strdup(ctx->arena, type_param); const char *method_name_copy = cbm_arena_strdup(ctx->arena, method_name); if (!func_qn_copy || !type_param_copy || !method_name_copy) return; int i = ctx->pending_tc_count++; ctx->pending_template_calls[i].func_qn = func_qn_copy; ctx->pending_template_calls[i].type_param = type_param_copy; ctx->pending_template_calls[i].method_name = method_name_copy; ctx->pending_template_calls[i].arg_count = arg_count; } // Resolve pending template calls for a function being called with known arg types. // Deduces type params from call-site args and resolves stored method calls. static void c_resolve_pending_template_calls(CLSPContext *ctx, const CBMRegisteredFunc *callee, const CBMType **call_arg_types, int call_arg_count) { if (!callee || !callee->type_param_names || !call_arg_types) return; // Build type param → concrete type mapping from call-site arguments const char **tpn = callee->type_param_names; const CBMType *param_map[8] = {0}; int tpn_count = 0; while (tpn[tpn_count] && tpn_count < 8) tpn_count++; // Match call arg types against function param types to deduce type params. // The call site may contain more arguments than the parsed function signature // knows about (invalid code, macros, variadic calls, or parser recovery). The // signature arrays are NULL-terminated, so never index past the sentinel. if (callee->signature && callee->signature->kind == CBM_TYPE_FUNC && callee->signature->data.func.param_types) { int formal_count = 0; while (callee->signature->data.func.param_types[formal_count]) formal_count++; int limit = call_arg_count < formal_count ? call_arg_count : formal_count; for (int i = 0; i < limit; i++) { const CBMType *formal = callee->signature->data.func.param_types[i]; if (!formal || !call_arg_types[i]) continue; // Unwrap references/pointers while (formal && (formal->kind == CBM_TYPE_REFERENCE || formal->kind == CBM_TYPE_RVALUE_REF || formal->kind == CBM_TYPE_POINTER)) { formal = (formal->kind == CBM_TYPE_POINTER) ? formal->data.pointer.elem : formal->data.reference.elem; } if (formal && formal->kind == CBM_TYPE_TYPE_PARAM) { for (int j = 0; j < tpn_count; j++) { if (strcmp(tpn[j], formal->data.type_param.name) == 0) { const CBMType *arg = call_arg_types[i]; arg = c_simplify_type(ctx, arg, false); param_map[j] = arg; } } } } } // Resolve pending calls for this function — emit with template func as caller const char *saved_func_qn = ctx->enclosing_func_qn; ctx->enclosing_func_qn = callee->qualified_name; for (int i = 0; i < ctx->pending_tc_count; i++) { if (!ctx->pending_template_calls[i].func_qn || !ctx->pending_template_calls[i].type_param || !ctx->pending_template_calls[i].method_name) continue; if (strcmp(ctx->pending_template_calls[i].func_qn, callee->qualified_name) != 0) continue; const char *tp = ctx->pending_template_calls[i].type_param; const char *method = ctx->pending_template_calls[i].method_name; // Find which type param this is for (int j = 0; j < tpn_count; j++) { if (strcmp(tpn[j], tp) != 0 || !param_map[j]) continue; const char *concrete_qn = type_to_qn(param_map[j]); if (!concrete_qn) continue; // Look up the method on the concrete type const CBMRegisteredFunc *m = c_lookup_member(ctx, concrete_qn, method); if (m) { c_emit_resolved_call(ctx, m->qualified_name, "lsp_template_instantiation", 0.90f); } break; } } ctx->enclosing_func_qn = saved_func_qn; } // --- Helper: extract call argument types for overload scoring --- static const CBMType **c_extract_call_arg_types(CLSPContext *ctx, TSNode call_node, int *out_count) { TSNode args = ts_node_child_by_field_name(call_node, "arguments", 9); if (ts_node_is_null(args)) { *out_count = 0; return NULL; } uint32_t nc = ts_node_named_child_count(args); if (nc == 0) { *out_count = 0; return NULL; } const CBMType **types = (const CBMType **)cbm_arena_alloc(ctx->arena, (nc + 1) * sizeof(const CBMType *)); if (!types) { *out_count = 0; return NULL; } int count = 0; for (uint32_t i = 0; i < nc; i++) { TSNode child = ts_node_named_child(args, i); if (!ts_node_is_null(child)) { types[count++] = c_eval_expr_type(ctx, child); } } types[count] = NULL; *out_count = count; return types; } // --- Helper: parse template parameter defaults --- static void c_parse_template_params(CLSPContext *ctx, TSNode template_decl) { TSNode params = ts_node_child_by_field_name(template_decl, "parameters", 10); if (ts_node_is_null(params)) return; ctx->template_param_count = 0; uint32_t nc = ts_node_named_child_count(params); if (nc == 0) return; const char **names = (const char **)cbm_arena_alloc(ctx->arena, (nc + 1) * sizeof(const char *)); const CBMType **defaults = (const CBMType **)cbm_arena_alloc(ctx->arena, (nc + 1) * sizeof(const CBMType *)); if (!names || !defaults) return; int idx = 0; for (uint32_t i = 0; i < nc && idx < 16; i++) { TSNode param = ts_node_named_child(params, i); if (ts_node_is_null(param)) continue; const char *pk = ts_node_type(param); // type_parameter_declaration: template // optional_type_parameter_declaration: template if (strcmp(pk, "type_parameter_declaration") == 0 || strcmp(pk, "optional_type_parameter_declaration") == 0) { // Get parameter name TSNode name_node = ts_node_child_by_field_name(param, "name", 4); if (ts_node_is_null(name_node)) { // Some grammars put the identifier as a direct child for (uint32_t j = 0; j < ts_node_named_child_count(param); j++) { TSNode ch = ts_node_named_child(param, j); if (strcmp(ts_node_type(ch), "type_identifier") == 0 || strcmp(ts_node_type(ch), "identifier") == 0) { name_node = ch; break; } } } char *pname = NULL; if (!ts_node_is_null(name_node)) pname = c_node_text(ctx, name_node); if (!pname) pname = cbm_arena_sprintf(ctx->arena, "T%d", idx); names[idx] = pname; // Get default type TSNode default_node = ts_node_child_by_field_name(param, "default_type", 12); if (ts_node_is_null(default_node)) { // Try "default_value" field default_node = ts_node_child_by_field_name(param, "default", 7); } if (!ts_node_is_null(default_node)) { defaults[idx] = c_parse_type_node(ctx, default_node); } else { defaults[idx] = NULL; } idx++; } } names[idx] = NULL; defaults[idx] = NULL; ctx->template_param_names = names; ctx->template_param_defaults = defaults; ctx->template_param_count = idx; } // Resolve a template type parameter using defaults from current template scope static const CBMType *c_resolve_template_param(CLSPContext *ctx, const char *param_name) { if (!param_name || !ctx->template_param_names) return NULL; for (int i = 0; i < ctx->template_param_count; i++) { if (ctx->template_param_names[i] && strcmp(ctx->template_param_names[i], param_name) == 0) { return ctx->template_param_defaults[i]; // may be NULL if no default } } return NULL; } // --- Helper: resolve namespace alias --- static const char *c_resolve_ns_alias(CLSPContext *ctx, const char *name) { for (int i = 0; i < ctx->ns_alias_count; i++) { if (strcmp(ctx->ns_alias_names[i], name) == 0) return ctx->ns_alias_qns[i]; } return NULL; } // --- C/C++ builtin check --- static bool is_c_builtin_type(const char *name) { static const char *builtins[] = { "int", "char", "short", "long", "float", "double", "void", "bool", "size_t", "ssize_t", "ptrdiff_t", "intptr_t", "uintptr_t", "int8_t", "int16_t", "int32_t", "int64_t", "uint8_t", "uint16_t", "uint32_t", "uint64_t", "wchar_t", "char16_t", "char32_t", "char8_t", "unsigned", "signed", NULL}; for (const char **b = builtins; *b; b++) { if (strcmp(name, *b) == 0) return true; } return false; } static bool is_c_builtin_func(const char *name) { // C stdlib functions are registered in the registry, not hardcoded here. // But we skip certain compiler builtins that should not generate CALLS edges. static const char *skip[] = {"__builtin_expect", "__builtin_unreachable", "__builtin_offsetof", "__builtin_va_start", "__builtin_va_end", "__builtin_va_arg", "sizeof", "alignof", "_Alignof", "typeof", "decltype", "static_assert", "_Static_assert", NULL}; for (const char **b = skip; *b; b++) { if (strcmp(name, *b) == 0) return true; } return false; } // --- Qualified name construction --- // Build a qualified name from a namespace-qualified identifier. // Converts "::" to "." for our QN format. // Inline ABI namespaces to strip during QN construction. // Only ABI-internal segments, NOT __detail (contains internal types). static bool is_inline_abi_ns(const char *seg, size_t seg_len) { return (seg_len == 3 && memcmp(seg, "__1", 3) == 0) || (seg_len == 7 && memcmp(seg, "__cxx11", 7) == 0) || (seg_len == 9 && memcmp(seg, "__gnu_cxx", 9) == 0); } static const char *c_build_qn(CLSPContext *ctx, const char *text) { if (!text) return NULL; // Replace :: with . and strip inline ABI namespaces size_t len = strlen(text); char *buf = (char *)cbm_arena_alloc(ctx->arena, len + 1); if (!buf) return text; size_t j = 0; for (size_t i = 0; i < len; i++) { if (text[i] == ':' && i + 1 < len && text[i + 1] == ':') { // Peek at next segment to check for inline ABI namespace size_t seg_start = i + 2; size_t seg_end = seg_start; while (seg_end < len && text[seg_end] != ':') seg_end++; size_t seg_len = seg_end - seg_start; if (seg_len > 0 && is_inline_abi_ns(text + seg_start, seg_len) && seg_end + 1 < len && text[seg_end] == ':' && text[seg_end + 1] == ':') { // Skip ABI ns + its trailing :: i = seg_end + 1; // loop increments to seg_end+2 = after "::" // Don't emit a dot — we're removing this segment continue; } buf[j++] = '.'; i++; // skip second ':' } else { buf[j++] = text[i]; } } buf[j] = '\0'; return buf; } // --- Name lookup (C++ multi-scope resolution) --- // Look up a name following C++ name resolution order: // 1. Current scope chain // 2. Using declarations // 3. Enclosing class (implicit this) // 4. Using namespaces // 5. Current namespace // 6. Module scope static const char *c_resolve_name(CLSPContext *ctx, const char *name) { if (!name) return NULL; // 1. Scope lookup const CBMType *t = cbm_scope_lookup(ctx->current_scope, name); if (!cbm_type_is_unknown(t)) return NULL; // found in scope, not a function // 2. Using declarations for (int i = 0; i < ctx->using_decl_count; i++) { if (strcmp(ctx->using_decl_names[i], name) == 0) return ctx->using_decl_qns[i]; } // 3. Enclosing class (implicit this) if (ctx->enclosing_class_qn) { const CBMRegisteredFunc *f = cbm_registry_lookup_method(ctx->registry, ctx->enclosing_class_qn, name); if (f) return f->qualified_name; } // 4. Using namespaces for (int i = 0; i < ctx->using_ns_count; i++) { const CBMRegisteredFunc *f = cbm_registry_lookup_symbol(ctx->registry, ctx->using_namespaces[i], name); if (f) return f->qualified_name; } // 5. Current namespace + parent namespaces if (ctx->current_namespace) { const char *ns = ctx->current_namespace; while (ns && ns[0]) { const CBMRegisteredFunc *f = cbm_registry_lookup_symbol(ctx->registry, ns, name); if (f) return f->qualified_name; // Walk up: "a.b.c" -> "a.b" -> "a" const char *dot = strrchr(ns, '.'); if (!dot) break; char *parent = (char *)cbm_arena_alloc(ctx->arena, (size_t)(dot - ns) + 1); if (!parent) break; memcpy(parent, ns, (size_t)(dot - ns)); parent[dot - ns] = '\0'; ns = parent; } } // 6. Module scope if (ctx->module_qn) { const CBMRegisteredFunc *f = cbm_registry_lookup_symbol(ctx->registry, ctx->module_qn, name); if (f) return f->qualified_name; } // 7. Global scope (no prefix — C stdlib functions) const CBMRegisteredFunc *f = cbm_registry_lookup_func(ctx->registry, name); if (f) return f->qualified_name; return NULL; } // --- ADL (Argument-Dependent Lookup) --- // Extract the namespace from a qualified name: "std.vector" → "std", "boost.asio.ip" → "boost.asio" static const char *extract_namespace_from_qn(CBMArena *arena, const char *qn) { if (!qn) return NULL; const char *dot = strrchr(qn, '.'); if (!dot || dot == qn) return NULL; size_t len = (size_t)(dot - qn); char *ns = (char *)cbm_arena_alloc(arena, len + 1); if (!ns) return NULL; memcpy(ns, qn, len); ns[len] = '\0'; return ns; } // ADL: resolve unqualified function call by searching namespaces of argument types. // E.g., swap(a, b) where a is std::string → look up std::swap. static const char *c_adl_resolve(CLSPContext *ctx, const char *name, TSNode call_node) { if (!ctx->cpp_mode || !name) return NULL; TSNode args = ts_node_child_by_field_name(call_node, "arguments", 9); if (ts_node_is_null(args)) return NULL; uint32_t nc = ts_node_named_child_count(args); if (nc == 0) return NULL; // Collect unique namespace QNs from argument types (max 8 to bound work) const char *namespaces[8]; int ns_count = 0; for (uint32_t i = 0; i < nc && ns_count < 8; i++) { TSNode arg = ts_node_named_child(args, i); if (ts_node_is_null(arg)) continue; const CBMType *arg_type = c_eval_expr_type(ctx, arg); const char *qn = type_to_qn(c_simplify_type(ctx, arg_type, false)); if (!qn) continue; const char *ns = extract_namespace_from_qn(ctx->arena, qn); if (!ns) continue; // Deduplicate bool dup = false; for (int j = 0; j < ns_count; j++) { if (strcmp(namespaces[j], ns) == 0) { dup = true; break; } } if (!dup) namespaces[ns_count++] = ns; } // Try each namespace, then the module-prefixed form of it. An argument type // written as `ns::Data` evaluates to the namespace QN `ns`, but the function // is registered under the module-qualified `.ns.serialize`; without // the module-prefixed retry the namespace-scoped overload is never found. for (int i = 0; i < ns_count; i++) { const CBMRegisteredFunc *f = cbm_registry_lookup_symbol(ctx->registry, namespaces[i], name); if (!f && ctx->module_qn) { const char *prefixed = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, namespaces[i]); f = cbm_registry_lookup_symbol(ctx->registry, prefixed, name); } if (f) return f->qualified_name; } return NULL; } // Resolve a name to a function QN via registry only (skips scope check). // Used for function pointer target resolution where the name IS in scope // as a function definition but we need the registry QN, not NULL. static const char *c_resolve_name_to_func_qn(CLSPContext *ctx, const char *name) { if (!name) return NULL; // Using declarations for (int i = 0; i < ctx->using_decl_count; i++) { if (strcmp(ctx->using_decl_names[i], name) == 0) return ctx->using_decl_qns[i]; } // Enclosing class if (ctx->enclosing_class_qn) { const CBMRegisteredFunc *f = cbm_registry_lookup_method(ctx->registry, ctx->enclosing_class_qn, name); if (f) return f->qualified_name; } // Using namespaces for (int i = 0; i < ctx->using_ns_count; i++) { const CBMRegisteredFunc *f = cbm_registry_lookup_symbol(ctx->registry, ctx->using_namespaces[i], name); if (f) return f->qualified_name; } // Current namespace + parents if (ctx->current_namespace) { const char *ns = ctx->current_namespace; while (ns && ns[0]) { const CBMRegisteredFunc *f = cbm_registry_lookup_symbol(ctx->registry, ns, name); if (f) return f->qualified_name; const char *dot = strrchr(ns, '.'); if (!dot) break; char *parent = (char *)cbm_arena_alloc(ctx->arena, (size_t)(dot - ns) + 1); if (!parent) break; memcpy(parent, ns, (size_t)(dot - ns)); parent[dot - ns] = '\0'; ns = parent; } } // Module scope if (ctx->module_qn) { const CBMRegisteredFunc *f = cbm_registry_lookup_symbol(ctx->registry, ctx->module_qn, name); if (f) return f->qualified_name; } // Global scope const CBMRegisteredFunc *gf = cbm_registry_lookup_func(ctx->registry, name); if (gf) return gf->qualified_name; return NULL; } // Resolve a name to a type (for identifiers used as types) static const CBMType *c_resolve_name_to_type(CLSPContext *ctx, const char *name) { if (!name) return cbm_type_unknown(); // Builtin types if (is_c_builtin_type(name)) return cbm_type_builtin(ctx->arena, name); // Scope lookup: typedef/using aliases are bound here. // If the name resolves to an alias, follow it to the underlying type. { const CBMType *scoped = cbm_scope_lookup(ctx->current_scope, name); if (scoped && scoped->kind == CBM_TYPE_ALIAS) { const CBMType *resolved = cbm_type_resolve_alias(scoped); if (resolved && !cbm_type_is_unknown(resolved)) return resolved; } } // Template type parameters (T, U, etc.) if (ctx->in_template && ctx->template_param_names) { for (int i = 0; i < ctx->template_param_count; i++) { if (ctx->template_param_names[i] && strcmp(ctx->template_param_names[i], name) == 0) { // Return default if available, otherwise TYPE_PARAM if (ctx->template_param_defaults && ctx->template_param_defaults[i]) return ctx->template_param_defaults[i]; return cbm_type_type_param(ctx->arena, name); } } } // Using declarations for (int i = 0; i < ctx->using_decl_count; i++) { if (strcmp(ctx->using_decl_names[i], name) == 0) { const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, ctx->using_decl_qns[i]); if (rt) return cbm_type_named(ctx->arena, rt->qualified_name); } } // Enclosing class (nested types: Factory::Product) if (ctx->enclosing_class_qn) { const char *qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->enclosing_class_qn, name); const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, qn); if (rt) return cbm_type_named(ctx->arena, qn); } // Current namespace if (ctx->current_namespace) { const char *qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->current_namespace, name); const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, qn); if (rt) return cbm_type_named(ctx->arena, qn); } // Module scope if (ctx->module_qn) { const char *qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, name); const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, qn); if (rt) return cbm_type_named(ctx->arena, qn); } // Using namespaces for (int i = 0; i < ctx->using_ns_count; i++) { const char *qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->using_namespaces[i], name); const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, qn); if (rt) return cbm_type_named(ctx->arena, qn); } // Try std:: prefix (very common) if (ctx->cpp_mode) { const char *std_qn = cbm_arena_sprintf(ctx->arena, "std.%s", name); const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, std_qn); if (rt) return cbm_type_named(ctx->arena, std_qn); } // Unresolved — return named with module prefix if (ctx->module_qn) { return cbm_type_named(ctx->arena, cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, name)); } return cbm_type_named(ctx->arena, name); } // ============================================================================ // c_parse_type_node: AST type node -> CBMType // ============================================================================ const CBMType *c_parse_type_node(CLSPContext *ctx, TSNode node) { if (ts_node_is_null(node)) return cbm_type_unknown(); const char *kind = ts_node_type(node); // primitive_type: int, double, char, void, bool if (strcmp(kind, "primitive_type") == 0) { char *name = c_node_text(ctx, node); return name ? cbm_type_builtin(ctx->arena, name) : cbm_type_unknown(); } // sized_type_specifier: unsigned int, long long, etc. if (strcmp(kind, "sized_type_specifier") == 0) { char *name = c_node_text(ctx, node); return name ? cbm_type_builtin(ctx->arena, name) : cbm_type_unknown(); } // type_identifier: Foo, MyClass if (strcmp(kind, "type_identifier") == 0) { char *name = c_node_text(ctx, node); if (!name) return cbm_type_unknown(); return c_resolve_name_to_type(ctx, name); } // qualified_identifier: std::vector, ns::Foo if (strcmp(kind, "qualified_identifier") == 0 || strcmp(kind, "scoped_type_identifier") == 0) { // Check if name field is a template_type (e.g., std::vector) // If so, build scoped QN prefix and delegate to template_type handler TSNode name_child = ts_node_child_by_field_name(node, "name", 4); if (!ts_node_is_null(name_child) && strcmp(ts_node_type(name_child), "template_type") == 0) { // Build scope prefix from scope chain TSNode scope_node = ts_node_child_by_field_name(node, "scope", 5); char *scope_text = NULL; if (!ts_node_is_null(scope_node)) { scope_text = c_node_text(ctx, scope_node); if (scope_text) { const char *alias_qn = c_resolve_ns_alias(ctx, scope_text); if (alias_qn) scope_text = (char *)alias_qn; } } // Parse the template_type's own name TSNode tmpl_name = ts_node_child_by_field_name(name_child, "name", 4); TSNode tmpl_args = ts_node_child_by_field_name(name_child, "arguments", 9); char *tmpl_name_text = NULL; if (!ts_node_is_null(tmpl_name)) tmpl_name_text = c_node_text(ctx, tmpl_name); if (!tmpl_name_text) return cbm_type_unknown(); // Build full QN: scope.template_name const char *template_qn; if (scope_text) { const char *scoped = cbm_arena_sprintf(ctx->arena, "%s::%s", scope_text, tmpl_name_text); template_qn = c_build_qn(ctx, scoped); } else { template_qn = c_build_qn(ctx, tmpl_name_text); } // Resolve against registry const CBMRegisteredType *trt = cbm_registry_lookup_type(ctx->registry, template_qn); if (!trt && ctx->module_qn) { const char *mod_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, template_qn); trt = cbm_registry_lookup_type(ctx->registry, mod_qn); if (trt) template_qn = mod_qn; } // Parse template arguments const CBMType *targs[16] = { NULL}; /* zero-fill: cbm_type_substitute requires NULL-terminated args (uninitialized tail bound T to stack garbage -> corrupt type graph, bitcoin serialize.h) */ int targ_count = 0; if (!ts_node_is_null(tmpl_args)) { uint32_t nc = ts_node_named_child_count(tmpl_args); for (uint32_t i = 0; i < nc && targ_count < 15; i++) { TSNode arg = ts_node_named_child(tmpl_args, i); if (ts_node_is_null(arg)) continue; const char *ak = ts_node_type(arg); if (strcmp(ak, "type_descriptor") == 0) { if (ts_node_named_child_count(arg) > 0) targs[targ_count++] = c_parse_type_node(ctx, ts_node_named_child(arg, 0)); } else { targs[targ_count++] = c_parse_type_node(ctx, arg); } } } if (targ_count > 0) return cbm_type_template(ctx->arena, template_qn, targs, targ_count); return cbm_type_named(ctx->arena, template_qn); } char *text = c_node_text(ctx, node); if (!text) return cbm_type_unknown(); // Check for namespace alias at start TSNode scope_node = ts_node_child_by_field_name(node, "scope", 5); if (!ts_node_is_null(scope_node)) { char *scope_text = c_node_text(ctx, scope_node); if (scope_text) { const char *alias_qn = c_resolve_ns_alias(ctx, scope_text); if (alias_qn) { if (!ts_node_is_null(name_child)) { char *name = c_node_text(ctx, name_child); if (name) text = (char *)cbm_arena_sprintf(ctx->arena, "%s::%s", alias_qn, name); } } } } const char *qn = c_build_qn(ctx, text); // Check registry const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, qn); if (rt) return cbm_type_named(ctx->arena, qn); return cbm_type_named(ctx->arena, qn); } // template_type: vector, map if (strcmp(kind, "template_type") == 0) { TSNode name_node = ts_node_child_by_field_name(node, "name", 4); TSNode args_node = ts_node_child_by_field_name(node, "arguments", 9); char *name_text = NULL; if (!ts_node_is_null(name_node)) { name_text = c_node_text(ctx, name_node); } if (!name_text) return cbm_type_unknown(); const char *template_qn = c_build_qn(ctx, name_text); // Try resolving short name to full QN const CBMRegisteredType *trt = cbm_registry_lookup_type(ctx->registry, template_qn); if (!trt && ctx->module_qn) { // Try with module prefix const char *mod_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, template_qn); trt = cbm_registry_lookup_type(ctx->registry, mod_qn); if (trt) template_qn = mod_qn; } if (!trt && ctx->current_namespace) { // Try with current namespace prefix (e.g., inside namespace std, "shared_ptr" → // "std.shared_ptr") const char *ns_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->current_namespace, template_qn); trt = cbm_registry_lookup_type(ctx->registry, ns_qn); if (trt) template_qn = ns_qn; // Also try module + namespace if (!trt && ctx->module_qn) { const char *full_qn = cbm_arena_sprintf(ctx->arena, "%s.%s.%s", ctx->module_qn, ctx->current_namespace, template_qn); trt = cbm_registry_lookup_type(ctx->registry, full_qn); if (trt) template_qn = full_qn; } } if (!trt) { // Try with std:: prefix const char *std_qn = cbm_arena_sprintf(ctx->arena, "std.%s", template_qn); trt = cbm_registry_lookup_type(ctx->registry, std_qn); if (trt) template_qn = std_qn; } // Parse template arguments const CBMType *targs[16] = { NULL}; /* zero-fill: cbm_type_substitute requires NULL-terminated args (uninitialized tail bound T to stack garbage -> corrupt type graph, bitcoin serialize.h) */ int targ_count = 0; if (!ts_node_is_null(args_node)) { uint32_t nc = ts_node_named_child_count(args_node); for (uint32_t i = 0; i < nc && targ_count < 15; i++) { TSNode arg = ts_node_named_child(args_node, i); if (ts_node_is_null(arg)) continue; const char *ak = ts_node_type(arg); if (strcmp(ak, "type_descriptor") == 0) { // type_descriptor wraps a type node if (ts_node_named_child_count(arg) > 0) { targs[targ_count++] = c_parse_type_node(ctx, ts_node_named_child(arg, 0)); } } else { targs[targ_count++] = c_parse_type_node(ctx, arg); } } } if (targ_count > 0) { return cbm_type_template(ctx->arena, template_qn, targs, targ_count); } return cbm_type_named(ctx->arena, template_qn); } // pointer_declarator / pointer (*) in type context if (strcmp(kind, "pointer_declarator") == 0 || strcmp(kind, "pointer_type") == 0) { if (ts_node_named_child_count(node) > 0) { return cbm_type_pointer(ctx->arena, c_parse_type_node(ctx, ts_node_named_child(node, 0))); } return cbm_type_pointer(ctx->arena, cbm_type_unknown()); } // reference_declarator / reference_type (&) if (strcmp(kind, "reference_declarator") == 0 || strcmp(kind, "abstract_reference_declarator") == 0) { if (ts_node_named_child_count(node) > 0) { return cbm_type_reference(ctx->arena, c_parse_type_node(ctx, ts_node_named_child(node, 0))); } return cbm_type_reference(ctx->arena, cbm_type_unknown()); } // auto keyword if (strcmp(kind, "auto") == 0 || strcmp(kind, "placeholder_type_specifier") == 0) { return cbm_type_unknown(); // will be resolved from initializer } // decltype if (strcmp(kind, "decltype") == 0) { if (ts_node_named_child_count(node) > 0) { return c_eval_expr_type(ctx, ts_node_named_child(node, 0)); } return cbm_type_unknown(); } // const/volatile qualifier — strip and recurse if (strcmp(kind, "type_qualifier") == 0) { return cbm_type_unknown(); // qualifier itself has no type } // array_declarator: int[10] if (strcmp(kind, "array_declarator") == 0 || strcmp(kind, "array_type") == 0) { if (ts_node_named_child_count(node) > 0) { return cbm_type_slice(ctx->arena, c_parse_type_node(ctx, ts_node_named_child(node, 0))); } return cbm_type_unknown(); } // function_declarator: int (*)(int) if (strcmp(kind, "function_declarator") == 0 || strcmp(kind, "abstract_function_declarator") == 0) { return cbm_type_func(ctx->arena, NULL, NULL, NULL); } // struct_specifier / class_specifier (inline struct/class type) if (strcmp(kind, "struct_specifier") == 0 || strcmp(kind, "class_specifier") == 0) { TSNode name_node = ts_node_child_by_field_name(node, "name", 4); if (!ts_node_is_null(name_node)) { char *name = c_node_text(ctx, name_node); if (name) return c_resolve_name_to_type(ctx, name); } // Anonymous struct CBMType *t = (CBMType *)cbm_arena_alloc(ctx->arena, sizeof(CBMType)); memset(t, 0, sizeof(CBMType)); t->kind = CBM_TYPE_STRUCT; return t; } // enum_specifier if (strcmp(kind, "enum_specifier") == 0) { TSNode name_node = ts_node_child_by_field_name(node, "name", 4); if (!ts_node_is_null(name_node)) { char *name = c_node_text(ctx, name_node); if (name) return c_resolve_name_to_type(ctx, name); } return cbm_type_builtin(ctx->arena, "int"); } // type_descriptor: wraps a type with optional qualifiers if (strcmp(kind, "type_descriptor") == 0) { uint32_t nc = ts_node_named_child_count(node); // Find the actual type node (skip qualifiers) for (uint32_t i = 0; i < nc; i++) { TSNode child = ts_node_named_child(node, i); const char *ck = ts_node_type(child); if (strcmp(ck, "type_qualifier") != 0) { return c_parse_type_node(ctx, child); } } return cbm_type_unknown(); } // parenthesized_type: (T) if (strcmp(kind, "parenthesized_type") == 0 && ts_node_named_child_count(node) > 0) { return c_parse_type_node(ctx, ts_node_named_child(node, 0)); } // dependent_type: in template scope if (strcmp(kind, "dependent_type") == 0 || strcmp(kind, "dependent_name") == 0) { char *text = c_node_text(ctx, node); if (text) return cbm_type_named(ctx->arena, c_build_qn(ctx, text)); return cbm_type_unknown(); } return cbm_type_unknown(); } // ============================================================================ // c_simplify_type: multi-step type unwrapping (clangd simplifyType) // ============================================================================ const CBMType *c_simplify_type(CLSPContext *ctx, const CBMType *t, bool unwrap_pointer) { for (int i = 0; i < 64 && t; i++) { // Resolve aliases if (t->kind == CBM_TYPE_ALIAS) { if (t->data.alias.underlying) { t = t->data.alias.underlying; continue; } break; } // Resolve template type parameters using defaults if (t->kind == CBM_TYPE_TYPE_PARAM && ctx->in_template) { const CBMType *resolved = c_resolve_template_param(ctx, t->data.type_param.name); if (resolved) { t = resolved; continue; } break; // no default available } // Unwrap references if (t->kind == CBM_TYPE_REFERENCE || t->kind == CBM_TYPE_RVALUE_REF) { t = t->data.reference.elem; continue; } if (!unwrap_pointer) break; // Unwrap pointer if (t->kind == CBM_TYPE_POINTER) { t = t->data.pointer.elem; break; } // Smart pointer: extract first template arg if (t->kind == CBM_TYPE_TEMPLATE && is_smart_ptr(t->data.template_type.template_name)) { if (t->data.template_type.arg_count > 0 && t->data.template_type.template_args) { t = t->data.template_type.template_args[0]; break; } } // Named type with operator-> if (t->kind == CBM_TYPE_NAMED) { const CBMRegisteredFunc *op = cbm_registry_lookup_method( ctx->registry, t->data.named.qualified_name, "operator->"); if (op && op->signature && op->signature->kind == CBM_TYPE_FUNC && op->signature->data.func.return_types && op->signature->data.func.return_types[0]) { t = op->signature->data.func.return_types[0]; // deref the pointer return if (t->kind == CBM_TYPE_POINTER) t = t->data.pointer.elem; break; } } // Template type with operator-> (e.g., optional::operator->) if (t->kind == CBM_TYPE_TEMPLATE) { const char *tqn = t->data.template_type.template_name; const CBMRegisteredFunc *op = c_lookup_member(ctx, tqn, "operator->"); if (op && op->signature && op->signature->kind == CBM_TYPE_FUNC && op->signature->data.func.return_types && op->signature->data.func.return_types[0]) { const CBMType *ret = op->signature->data.func.return_types[0]; // Substitute template params using receiver's template args if (ret->kind == CBM_TYPE_TYPE_PARAM || ret->kind == CBM_TYPE_POINTER || ret->kind == CBM_TYPE_REFERENCE) { const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, tqn); if (!rt && ctx->module_qn) { rt = cbm_registry_lookup_type( ctx->registry, cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, tqn)); } if (rt && rt->type_param_names) { ret = cbm_type_substitute(ctx->arena, ret, rt->type_param_names, t->data.template_type.template_args); } else { const char *fb[] = {"T", "K", "V", NULL}; const CBMType *fa[3] = {NULL}; int na = t->data.template_type.arg_count; if (na > 0) fa[0] = t->data.template_type.template_args[0]; if (na > 1) fa[1] = t->data.template_type.template_args[1]; if (na > 2) fa[2] = t->data.template_type.template_args[2]; ret = cbm_type_substitute(ctx->arena, ret, fb, fa); } } t = ret; if (t->kind == CBM_TYPE_POINTER) t = t->data.pointer.elem; break; } // Fallback: known smart pointer types — use first template arg if (is_smart_ptr(tqn) && t->data.template_type.arg_count > 0 && t->data.template_type.template_args[0]) { t = t->data.template_type.template_args[0]; break; } } break; } return t ? t : cbm_type_unknown(); } // Get the QN string from a type (for member lookup) static const char *type_to_qn(const CBMType *t) { if (!t) return NULL; switch (t->kind) { case CBM_TYPE_NAMED: return t->data.named.qualified_name; case CBM_TYPE_TEMPLATE: return t->data.template_type.template_name; default: return NULL; } } // ============================================================================ // c_eval_expr_type: recursive expression type evaluator // ============================================================================ static const CBMType *c_eval_expr_type_inner(CLSPContext *ctx, TSNode node); #define C_EVAL_DEPTH_LIMIT 256 #define C_EVAL_MAX_STEPS_PER_FILE 10000 const CBMType *c_eval_expr_type(CLSPContext *ctx, TSNode node) { if (ts_node_is_null(node)) return cbm_type_unknown(); /* Expression type evaluation is best-effort. Some recovery-mode C++ ASTs * can repeatedly drive member/type lookup without increasing recursion * depth. Keep a generous per-file work budget so pathological expressions * degrade to unknown instead of hanging repository indexing. */ if (ctx->eval_depth > C_EVAL_DEPTH_LIMIT || ctx->eval_steps++ > C_EVAL_MAX_STEPS_PER_FILE) { if (ctx->debug && ctx->eval_steps == C_EVAL_MAX_STEPS_PER_FILE + 2) { fprintf(stderr, " [clsp] expression eval step budget exhausted; returning unknown\n"); } return cbm_type_unknown(); } ctx->eval_depth++; const CBMType *result = c_eval_expr_type_inner(ctx, node); ctx->eval_depth--; return result ? result : cbm_type_unknown(); } static const CBMType *c_eval_expr_type_inner(CLSPContext *ctx, TSNode node) { const char *kind = ts_node_type(node); // --- identifier: scope lookup --- if (strcmp(kind, "identifier") == 0) { char *name = c_node_text(ctx, node); if (!name) return cbm_type_unknown(); // Scope lookup const CBMType *t = cbm_scope_lookup(ctx->current_scope, name); if (!cbm_type_is_unknown(t)) return t; // Check if it's a registered function (before type check — functions // return FUNC type which lets call_expression extract return types) const char *fqn = c_resolve_name(ctx, name); if (fqn) { const CBMRegisteredFunc *f = cbm_registry_lookup_func(ctx->registry, fqn); if (f && f->signature) return f->signature; } // Check if it's a type name (for constructor calls / casts) const CBMType *type = c_resolve_name_to_type(ctx, name); if (type && type->kind == CBM_TYPE_NAMED) { // Could be a constructor call — return as type return type; } return cbm_type_unknown(); } // --- this --- if (strcmp(kind, "this") == 0) { if (ctx->enclosing_class_qn) { return cbm_type_pointer(ctx->arena, cbm_type_named(ctx->arena, ctx->enclosing_class_qn)); } return cbm_type_unknown(); } // --- field_expression: a.b or a->b --- if (strcmp(kind, "field_expression") == 0) { TSNode arg_node = ts_node_child_by_field_name(node, "argument", 8); TSNode field_node = ts_node_child_by_field_name(node, "field", 5); if (ts_node_is_null(arg_node) || ts_node_is_null(field_node)) return cbm_type_unknown(); char *field_name = c_node_text(ctx, field_node); if (!field_name) return cbm_type_unknown(); const CBMType *obj_type = c_eval_expr_type(ctx, arg_node); if (cbm_type_is_unknown(obj_type)) return cbm_type_unknown(); // Determine if this is . or -> access // Check for "->" operator in node text bool is_arrow = false; uint32_t nc = ts_node_child_count(node); for (uint32_t i = 0; i < nc; i++) { TSNode child = ts_node_child(node, i); if (!ts_node_is_named(child)) { char *op = c_node_text(ctx, child); if (op && strcmp(op, "->") == 0) { is_arrow = true; break; } } } // Simplify type for member lookup const CBMType *base; if (is_arrow) { base = c_simplify_type(ctx, obj_type, true); // unwrap pointer/smart ptr } else { base = c_simplify_type(ctx, obj_type, false); // just unwrap refs/aliases } const char *type_qn = type_to_qn(base); if (!type_qn) return cbm_type_unknown(); // Look up method const CBMRegisteredFunc *method = c_lookup_member(ctx, type_qn, field_name); if (method && method->signature) return method->signature; // Look up field const CBMType *field_type = c_lookup_field_type(ctx, type_qn, field_name, 0); if (field_type && !cbm_type_is_unknown(field_type)) { // Template field substitution: if field type is TYPE_PARAM (or NAMED matching // a template param name) and receiver is a TEMPLATE type, substitute using // receiver's template args. // e.g., pair.second where second has type V → substitute V=Foo bool is_tparam = (field_type->kind == CBM_TYPE_TYPE_PARAM); // Also check NAMED types that match template param names (extract_defs // may register template fields as NAMED("V") rather than TYPE_PARAM("V")) if (!is_tparam && field_type->kind == CBM_TYPE_NAMED && base->kind == CBM_TYPE_TEMPLATE) { const CBMRegisteredType *trt = cbm_registry_lookup_type(ctx->registry, type_qn); if (!trt && ctx->module_qn) { trt = cbm_registry_lookup_type( ctx->registry, cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, type_qn)); } if (trt && trt->type_param_names) { const char *fname_qn = field_type->data.named.qualified_name; // Extract short name from QN (after last dot) const char *short_fn = strrchr(fname_qn, '.'); short_fn = short_fn ? short_fn + 1 : fname_qn; for (int tpi = 0; trt->type_param_names[tpi]; tpi++) { if (strcmp(trt->type_param_names[tpi], short_fn) == 0) { is_tparam = true; // Convert to TYPE_PARAM for substitution field_type = cbm_type_type_param(ctx->arena, short_fn); break; } } } } if (is_tparam && base->kind == CBM_TYPE_TEMPLATE && base->data.template_type.template_args) { const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, type_qn); if (!rt && ctx->module_qn) { rt = cbm_registry_lookup_type( ctx->registry, cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, type_qn)); } if (rt && rt->type_param_names) { field_type = cbm_type_substitute(ctx->arena, field_type, rt->type_param_names, base->data.template_type.template_args); } else { // Fallback: positional T, K, V const char *params[] = {"T", "K", "V", "T1", "T2", NULL}; const CBMType *args[5] = {NULL}; int nargs = base->data.template_type.arg_count; if (nargs > 0) args[0] = base->data.template_type.template_args[0]; if (nargs > 0) args[1] = base->data.template_type.template_args[0]; if (nargs > 1) args[2] = base->data.template_type.template_args[1]; if (nargs > 0) args[3] = base->data.template_type.template_args[0]; if (nargs > 1) args[4] = base->data.template_type.template_args[1]; field_type = cbm_type_substitute(ctx->arena, field_type, params, args); } } return field_type; } return cbm_type_unknown(); } // --- qualified_identifier / scoped_identifier: ns::Class::method or Enum::Value --- if (strcmp(kind, "qualified_identifier") == 0 || strcmp(kind, "scoped_identifier") == 0) { char *text = c_node_text(ctx, node); if (!text) return cbm_type_unknown(); // Check if name child is template_function: ns::func → strip template args TSNode qi_name_child = ts_node_child_by_field_name(node, "name", 4); bool qi_has_tmpl_args = false; TSNode qi_tmpl_args = (TSNode){0}; if (!ts_node_is_null(qi_name_child) && strcmp(ts_node_type(qi_name_child), "template_function") == 0) { qi_has_tmpl_args = true; qi_tmpl_args = ts_node_child_by_field_name(qi_name_child, "arguments", 9); // Build scope::bare_name without template args TSNode scope_node = ts_node_child_by_field_name(node, "scope", 5); TSNode tmpl_bare = ts_node_child_by_field_name(qi_name_child, "name", 4); char *scope_text = !ts_node_is_null(scope_node) ? c_node_text(ctx, scope_node) : NULL; char *bare_name = !ts_node_is_null(tmpl_bare) ? c_node_text(ctx, tmpl_bare) : NULL; if (bare_name) { text = scope_text ? (char *)cbm_arena_sprintf(ctx->arena, "%s::%s", scope_text, bare_name) : bare_name; } } const char *qn = c_build_qn(ctx, text); // Try as function: module-prefixed first (shadows stdlib stubs), then bare QN const CBMRegisteredFunc *f = NULL; if (ctx->module_qn) { f = cbm_registry_lookup_func( ctx->registry, cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, qn)); } if (!f) f = cbm_registry_lookup_func(ctx->registry, qn); // Namespace fallback: extract_defs may omit namespace from QN. // "utils.create_logger" → try just "create_logger" with module prefix if (!f) { const char *ns_dot = strrchr(qn, '.'); if (ns_dot && ctx->module_qn) { const char *bare = ns_dot + 1; f = cbm_registry_lookup_func( ctx->registry, cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, bare)); if (!f) f = cbm_registry_lookup_func(ctx->registry, bare); } } if (f && f->signature) { // If template args present, substitute into return type if (qi_has_tmpl_args && !ts_node_is_null(qi_tmpl_args) && f->signature->kind == CBM_TYPE_FUNC && f->signature->data.func.return_types && f->signature->data.func.return_types[0]) { const CBMType *base_ret = f->signature->data.func.return_types[0]; const CBMType *targs[16] = { NULL}; /* zero-fill: cbm_type_substitute requires NULL-terminated args (uninitialized tail bound T to stack garbage -> corrupt type graph, bitcoin serialize.h) */ int targ_count = 0; uint32_t tnc = ts_node_named_child_count(qi_tmpl_args); for (uint32_t ti = 0; ti < tnc && targ_count < 15; ti++) { TSNode targ = ts_node_named_child(qi_tmpl_args, ti); if (ts_node_is_null(targ)) continue; const char *tak = ts_node_type(targ); if (strcmp(tak, "type_descriptor") == 0) { if (ts_node_named_child_count(targ) > 0) targs[targ_count++] = c_parse_type_node(ctx, ts_node_named_child(targ, 0)); } else { targs[targ_count++] = c_parse_type_node(ctx, targ); } } if (targ_count > 0) { const char **tpn = f->type_param_names; if (tpn) { base_ret = cbm_type_substitute(ctx->arena, base_ret, tpn, targs); } else { const char *fallback[] = {"T", "U", "V", "W", NULL}; base_ret = cbm_type_substitute(ctx->arena, base_ret, fallback, targs); } // If return type is still NAMED after substitution (no TYPE_PARAM was // present), wrap as TEMPLATE with explicit args so downstream code // (e.g., smart pointer dereference) can extract inner types. // e.g., make_shared returns NAMED("std.shared_ptr") → // TEMPLATE("std.shared_ptr", [Widget]) if (base_ret->kind == CBM_TYPE_NAMED) { const CBMType **final_targs = (const CBMType **)cbm_arena_alloc( ctx->arena, (targ_count + 1) * sizeof(const CBMType *)); for (int i = 0; i < targ_count; i++) final_targs[i] = targs[i]; final_targs[targ_count] = NULL; base_ret = cbm_type_template(ctx->arena, base_ret->data.named.qualified_name, final_targs, targ_count); } } const CBMType **rets = (const CBMType **)cbm_arena_alloc(ctx->arena, 2 * sizeof(const CBMType *)); rets[0] = base_ret; rets[1] = NULL; return cbm_type_func(ctx->arena, NULL, NULL, rets); } return f->signature; } // Try as type (bare QN then module-prefixed) const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, qn); if (rt) return cbm_type_named(ctx->arena, qn); if (!rt && ctx->module_qn) { const char *mod_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, qn); rt = cbm_registry_lookup_type(ctx->registry, mod_qn); if (rt) return cbm_type_named(ctx->arena, mod_qn); } // Try as enum member: Enum::Value → look up Enum type, return int/enum type const char *dot = strrchr(qn, '.'); if (dot) { size_t prefix_len = (size_t)(dot - qn); char *enum_qn = (char *)cbm_arena_alloc(ctx->arena, prefix_len + 1); if (enum_qn) { memcpy(enum_qn, qn, prefix_len); enum_qn[prefix_len] = '\0'; const CBMRegisteredType *enum_rt = cbm_registry_lookup_type(ctx->registry, enum_qn); if (enum_rt) { return cbm_type_named(ctx->arena, enum_qn); } // Try with module prefix if (ctx->module_qn) { const char *fqn_enum = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, enum_qn); enum_rt = cbm_registry_lookup_type(ctx->registry, fqn_enum); if (enum_rt) return cbm_type_named(ctx->arena, fqn_enum); } } // Template heuristic: T::member where T is a template param with default if (ctx->in_template && dot) { size_t prefix_len2 = (size_t)(dot - qn); char *scope_name = (char *)cbm_arena_alloc(ctx->arena, prefix_len2 + 1); if (scope_name) { memcpy(scope_name, qn, prefix_len2); scope_name[prefix_len2] = '\0'; const CBMType *default_type = c_resolve_template_param(ctx, scope_name); if (default_type) { default_type = c_simplify_type(ctx, default_type, false); const char *default_qn = type_to_qn(default_type); if (default_qn) { // Try as method: DefaultType::member const char *member = dot + 1; const CBMRegisteredFunc *mf = cbm_registry_lookup_method(ctx->registry, default_qn, member); if (mf && mf->signature) return mf->signature; // Try as static function const char *dep_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", default_qn, member); const CBMRegisteredFunc *sf = cbm_registry_lookup_func(ctx->registry, dep_qn); if (sf && sf->signature) return sf->signature; } } } } } return cbm_type_unknown(); } // --- template_function: make_shared, make_unique --- // Returns a FUNC type whose return type is TEMPLATE(base_return, [explicit_args]). if (strcmp(kind, "template_function") == 0) { TSNode name_node = ts_node_child_by_field_name(node, "name", 4); TSNode args_node = ts_node_child_by_field_name(node, "arguments", 9); if (ts_node_is_null(name_node)) return cbm_type_unknown(); char *name = c_node_text(ctx, name_node); if (!name) return cbm_type_unknown(); // Look up the function — handle both simple and qualified names const CBMRegisteredFunc *f = NULL; const char *nk = ts_node_type(name_node); if (strcmp(nk, "qualified_identifier") == 0 || strcmp(nk, "scoped_identifier") == 0) { // Qualified: std::make_shared → std.make_shared const char *qn = c_build_qn(ctx, name); f = cbm_registry_lookup_func(ctx->registry, qn); if (!f && ctx->module_qn) { const char *mod_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, qn); f = cbm_registry_lookup_func(ctx->registry, mod_qn); } } else { const char *fqn = c_resolve_name(ctx, name); if (fqn) f = cbm_registry_lookup_func(ctx->registry, fqn); } if (!f || !f->signature) return cbm_type_unknown(); // Get the base return type from the function signature const CBMType *base_ret = NULL; if (f->signature->kind == CBM_TYPE_FUNC && f->signature->data.func.return_types && f->signature->data.func.return_types[0]) { base_ret = f->signature->data.func.return_types[0]; } if (!base_ret) return f->signature; // Parse explicit template arguments from <...> const CBMType *targs[16] = { NULL}; /* zero-fill: cbm_type_substitute requires NULL-terminated args (uninitialized tail bound T to stack garbage -> corrupt type graph, bitcoin serialize.h) */ int targ_count = 0; if (!ts_node_is_null(args_node)) { uint32_t nc = ts_node_named_child_count(args_node); for (uint32_t i = 0; i < nc && targ_count < 15; i++) { TSNode arg = ts_node_named_child(args_node, i); if (ts_node_is_null(arg)) continue; const char *ak = ts_node_type(arg); if (strcmp(ak, "type_descriptor") == 0) { if (ts_node_named_child_count(arg) > 0) targs[targ_count++] = c_parse_type_node(ctx, ts_node_named_child(arg, 0)); } else { targs[targ_count++] = c_parse_type_node(ctx, arg); } } } // Substitute explicit template args into return type if (targ_count > 0) { const char **tpn = f->type_param_names; if (tpn) { // Use registered type param names (e.g., ["T", NULL]) base_ret = cbm_type_substitute(ctx->arena, base_ret, tpn, targs); } else { // Fallback: positional T, U, V const char *fallback_names[] = {"T", "U", "V", "W", NULL}; base_ret = cbm_type_substitute(ctx->arena, base_ret, fallback_names, targs); } } // Wrap return type in FUNC signature const CBMType **rets = cbm_arena_alloc(ctx->arena, 2 * sizeof(CBMType *)); rets[0] = base_ret; rets[1] = NULL; return cbm_type_func(ctx->arena, NULL, NULL, rets); } // --- call_expression: f(args) --- if (strcmp(kind, "call_expression") == 0) { TSNode func_node = ts_node_child_by_field_name(node, "function", 8); if (ts_node_is_null(func_node)) return cbm_type_unknown(); // Special-case std::move/std::forward: return operand's type (rvalue ref) { const char *fk = ts_node_type(func_node); char *fname = NULL; if (strcmp(fk, "qualified_identifier") == 0 || strcmp(fk, "scoped_identifier") == 0 || strcmp(fk, "identifier") == 0) { fname = c_node_text(ctx, func_node); } else if (strcmp(fk, "template_function") == 0) { TSNode name_node = ts_node_child_by_field_name(func_node, "name", 4); if (!ts_node_is_null(name_node)) fname = c_node_text(ctx, name_node); } if (fname && (strcmp(fname, "std::move") == 0 || strcmp(fname, "move") == 0 || strcmp(fname, "std::forward") == 0 || strcmp(fname, "forward") == 0)) { TSNode call_args = ts_node_child_by_field_name(node, "arguments", 9); if (!ts_node_is_null(call_args) && ts_node_named_child_count(call_args) > 0) { TSNode first_arg = ts_node_named_child(call_args, 0); if (!ts_node_is_null(first_arg)) { const CBMType *arg_type = c_eval_expr_type(ctx, first_arg); // Unwrap to base type — move/forward preserves the type arg_type = c_simplify_type(ctx, arg_type, false); if (!cbm_type_is_unknown(arg_type)) return arg_type; } } } } const CBMType *func_type = c_eval_expr_type(ctx, func_node); if (!func_type) return cbm_type_unknown(); // FUNC type -> return its return type if (func_type->kind == CBM_TYPE_FUNC && func_type->data.func.return_types && func_type->data.func.return_types[0]) { const CBMType *ret = func_type->data.func.return_types[0]; // Template param substitution: try receiver's template args (method calls) // then try TAD from call-site argument types (free function calls) bool needs_subst = (ret->kind == CBM_TYPE_TYPE_PARAM || ret->kind == CBM_TYPE_NAMED || ret->kind == CBM_TYPE_POINTER || ret->kind == CBM_TYPE_REFERENCE || ret->kind == CBM_TYPE_RVALUE_REF || ret->kind == CBM_TYPE_TEMPLATE || ret->kind == CBM_TYPE_SLICE); if (needs_subst) { bool substituted = false; // Strategy 1: method call on templated receiver — use receiver's template args if (strcmp(ts_node_type(func_node), "field_expression") == 0) { TSNode arg_node = ts_node_child_by_field_name(func_node, "argument", 8); if (!ts_node_is_null(arg_node)) { const CBMType *obj_type = c_eval_expr_type(ctx, arg_node); // Use false to preserve TEMPLATE type — we need template args // for substitution, not the pointed-to type from operator-> obj_type = c_simplify_type(ctx, obj_type, false); if (obj_type && obj_type->kind == CBM_TYPE_TEMPLATE && obj_type->data.template_type.template_args) { // Use registered type_param_names if available TSNode field_node = ts_node_child_by_field_name(func_node, "field", 5); char *method_name = !ts_node_is_null(field_node) ? c_node_text(ctx, field_node) : NULL; const char *recv_qn = type_to_qn(obj_type); const CBMRegisteredFunc *mf = (recv_qn && method_name) ? c_lookup_member(ctx, recv_qn, method_name) : NULL; const char **tpn = mf ? mf->type_param_names : NULL; // Try class template param names from registered type if (!tpn && recv_qn) { const CBMRegisteredType *rrt = cbm_registry_lookup_type(ctx->registry, recv_qn); if (!rrt && ctx->module_qn) { rrt = cbm_registry_lookup_type( ctx->registry, cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, recv_qn)); } if (rrt && rrt->type_param_names) tpn = rrt->type_param_names; } // Build substitution from receiver template args int nargs = obj_type->data.template_type.arg_count; if (tpn) { // Use actual type param names from class/function registration ret = cbm_type_substitute(ctx->arena, ret, tpn, obj_type->data.template_type.template_args); substituted = true; } else { // Fallback: positional params T, K, V, T1, T2 const char *params[] = {"T", "K", "V", "T1", "T2", NULL}; const CBMType *pargs[5] = {NULL}; if (nargs > 0) pargs[0] = obj_type->data.template_type.template_args[0]; if (nargs > 0) pargs[1] = obj_type->data.template_type.template_args[0]; if (nargs > 1) pargs[2] = obj_type->data.template_type.template_args[1]; if (nargs > 0) pargs[3] = obj_type->data.template_type.template_args[0]; if (nargs > 1) pargs[4] = obj_type->data.template_type.template_args[1]; ret = cbm_type_substitute(ctx->arena, ret, params, pargs); substituted = true; } } } } // Strategy 2: TAD — deduce template params from call-site argument types if (!substituted) { // Find the registered function to get its type_param_names and param_types const CBMRegisteredFunc *rf = NULL; const char *fn_type = ts_node_type(func_node); if (strcmp(fn_type, "identifier") == 0) { char *fname = c_node_text(ctx, func_node); if (fname) { const char *fqn = c_resolve_name(ctx, fname); if (fqn) rf = cbm_registry_lookup_func(ctx->registry, fqn); } } else if (strcmp(fn_type, "qualified_identifier") == 0 || strcmp(fn_type, "scoped_identifier") == 0) { char *fname = c_node_text(ctx, func_node); if (fname) { const char *qn = c_build_qn(ctx, fname); rf = cbm_registry_lookup_func(ctx->registry, qn); } } else if (strcmp(fn_type, "template_function") == 0) { TSNode name_node = ts_node_child_by_field_name(func_node, "name", 4); if (!ts_node_is_null(name_node)) { char *fname = c_node_text(ctx, name_node); if (fname) { const char *nk = ts_node_type(name_node); if (strcmp(nk, "qualified_identifier") == 0 || strcmp(nk, "scoped_identifier") == 0) { const char *qn = c_build_qn(ctx, fname); rf = cbm_registry_lookup_func(ctx->registry, qn); if (!rf && ctx->module_qn) { rf = cbm_registry_lookup_func( ctx->registry, cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, qn)); } } else { const char *fqn = c_resolve_name(ctx, fname); if (fqn) rf = cbm_registry_lookup_func(ctx->registry, fqn); } } } } if (rf && rf->type_param_names && rf->signature && rf->signature->kind == CBM_TYPE_FUNC && rf->signature->data.func.param_types) { // Deduce: match call-site arg types against param types // to bind type_param_names int tp_count = 0; while (rf->type_param_names[tp_count]) tp_count++; if (tp_count > 0 && tp_count <= 8) { const CBMType *deduced[8] = {NULL}; TSNode call_args = ts_node_child_by_field_name(node, "arguments", 9); int pi = 0; if (!ts_node_is_null(call_args)) { uint32_t anc = ts_node_named_child_count(call_args); for (uint32_t ai = 0; ai < anc && rf->signature->data.func.param_types[pi]; ai++) { TSNode carg = ts_node_named_child(call_args, ai); if (ts_node_is_null(carg)) continue; const CBMType *arg_t = c_eval_expr_type(ctx, carg); if (cbm_type_is_unknown(arg_t)) { pi++; continue; } // Unwrap ref/ptr from param type const CBMType *param_t = rf->signature->data.func.param_types[pi]; while (param_t && (param_t->kind == CBM_TYPE_REFERENCE || param_t->kind == CBM_TYPE_RVALUE_REF || param_t->kind == CBM_TYPE_POINTER)) { if (param_t->kind == CBM_TYPE_POINTER) param_t = param_t->data.pointer.elem; else param_t = param_t->data.reference.elem; } // If param type is TYPE_PARAM, bind it if (param_t && param_t->kind == CBM_TYPE_TYPE_PARAM) { for (int ti = 0; ti < tp_count; ti++) { if (strcmp(rf->type_param_names[ti], param_t->data.type_param.name) == 0) { // Unwrap ref/ptr from actual arg too const CBMType *unwrapped = arg_t; while (unwrapped && (unwrapped->kind == CBM_TYPE_REFERENCE || unwrapped->kind == CBM_TYPE_RVALUE_REF || unwrapped->kind == CBM_TYPE_POINTER)) { if (unwrapped->kind == CBM_TYPE_POINTER) unwrapped = unwrapped->data.pointer.elem; else unwrapped = unwrapped->data.reference.elem; } if (!deduced[ti]) deduced[ti] = unwrapped; break; } } } pi++; } } // Apply deduced substitutions bool any_deduced = false; for (int ti = 0; ti < tp_count; ti++) { if (deduced[ti]) { any_deduced = true; break; } } if (any_deduced) { const CBMType *substituted_ret = cbm_type_substitute( ctx->arena, ret, rf->type_param_names, deduced); if (substituted_ret) ret = substituted_ret; } } } } } // Unwrap references in return type if (!ret) return cbm_type_unknown(); if (ret->kind == CBM_TYPE_REFERENCE || ret->kind == CBM_TYPE_RVALUE_REF) ret = ret->data.reference.elem; return ret; } // Constructor call: Type(args) — if func_node resolves to a named type if (func_type->kind == CBM_TYPE_NAMED) { return func_type; } return cbm_type_unknown(); } // --- new_expression: new Foo(args) --- if (strcmp(kind, "new_expression") == 0) { TSNode type_node = ts_node_child_by_field_name(node, "type", 4); if (!ts_node_is_null(type_node)) { return cbm_type_pointer(ctx->arena, c_parse_type_node(ctx, type_node)); } // Try first named child uint32_t nc = ts_node_named_child_count(node); for (uint32_t i = 0; i < nc; i++) { TSNode child = ts_node_named_child(node, i); const char *ck = ts_node_type(child); if (strcmp(ck, "type_identifier") == 0 || strcmp(ck, "template_type") == 0 || strcmp(ck, "scoped_type_identifier") == 0 || strcmp(ck, "primitive_type") == 0) { return cbm_type_pointer(ctx->arena, c_parse_type_node(ctx, child)); } } return cbm_type_pointer(ctx->arena, cbm_type_unknown()); } // --- delete_expression --- if (strcmp(kind, "delete_expression") == 0) { return cbm_type_unknown(); // void } // --- subscript_expression: a[i] --- if (strcmp(kind, "subscript_expression") == 0) { TSNode arg_node = ts_node_child_by_field_name(node, "argument", 8); if (ts_node_is_null(arg_node)) return cbm_type_unknown(); const CBMType *arr_type = c_eval_expr_type(ctx, arg_node); if (!arr_type) return cbm_type_unknown(); // Array/slice: return element if (arr_type->kind == CBM_TYPE_SLICE) return arr_type->data.slice.elem; if (arr_type->kind == CBM_TYPE_POINTER) return arr_type->data.pointer.elem; // Template with operator[]: return via method lookup const char *qn = type_to_qn(c_simplify_type(ctx, arr_type, false)); if (qn) { const CBMRegisteredFunc *op = cbm_registry_lookup_method(ctx->registry, qn, "operator[]"); if (op && op->signature && op->signature->kind == CBM_TYPE_FUNC && op->signature->data.func.return_types && op->signature->data.func.return_types[0]) { const CBMType *ret = op->signature->data.func.return_types[0]; if (ret->kind == CBM_TYPE_REFERENCE) ret = ret->data.reference.elem; // Substitute template params if (ret->kind == CBM_TYPE_TYPE_PARAM && arr_type->kind == CBM_TYPE_TEMPLATE) { const char *params[] = {"T", "K", "V", NULL}; const CBMType *args[3] = {NULL}; int nargs = arr_type->data.template_type.arg_count; if (nargs > 0) args[0] = arr_type->data.template_type.template_args[0]; if (nargs > 0) args[1] = arr_type->data.template_type.template_args[0]; if (nargs > 1) args[2] = arr_type->data.template_type.template_args[1]; ret = cbm_type_substitute(ctx->arena, ret, params, args); } return ret; } } return cbm_type_unknown(); } // --- cast expressions --- if (strcmp(kind, "cast_expression") == 0) { TSNode type_node = ts_node_child_by_field_name(node, "type", 4); if (!ts_node_is_null(type_node)) return c_parse_type_node(ctx, type_node); // Fallback: first named child might be type_descriptor if (ts_node_named_child_count(node) > 0) { TSNode first = ts_node_named_child(node, 0); if (strcmp(ts_node_type(first), "type_descriptor") == 0) return c_parse_type_node(ctx, first); } return cbm_type_unknown(); } if (strcmp(kind, "static_cast_expression") == 0 || strcmp(kind, "dynamic_cast_expression") == 0 || strcmp(kind, "reinterpret_cast_expression") == 0 || strcmp(kind, "const_cast_expression") == 0) { TSNode type_node = ts_node_child_by_field_name(node, "type", 4); if (!ts_node_is_null(type_node)) return c_parse_type_node(ctx, type_node); return cbm_type_unknown(); } // --- unary_expression: *p, &x, !x, -x --- if (strcmp(kind, "unary_expression") == 0) { TSNode operand = ts_node_child_by_field_name(node, "argument", 8); if (ts_node_is_null(operand)) { // Try "operand" field name operand = ts_node_child_by_field_name(node, "operand", 7); } if (ts_node_is_null(operand)) return cbm_type_unknown(); // Get operator from first non-named child for (uint32_t i = 0; i < ts_node_child_count(node); i++) { TSNode child = ts_node_child(node, i); if (!ts_node_is_named(child)) { char *op = c_node_text(ctx, child); if (!op) continue; if (strcmp(op, "*") == 0) { const CBMType *inner = c_eval_expr_type(ctx, operand); inner = c_simplify_type(ctx, inner, false); // TEMPLATE with operator*(): look up and substitute return type if (inner && inner->kind == CBM_TYPE_TEMPLATE) { const char *tqn = inner->data.template_type.template_name; const CBMRegisteredFunc *opf = c_lookup_member(ctx, tqn, "operator*"); if (opf && opf->signature && opf->signature->kind == CBM_TYPE_FUNC && opf->signature->data.func.return_types && opf->signature->data.func.return_types[0]) { const CBMType *ret = opf->signature->data.func.return_types[0]; const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, tqn); if (!rt && ctx->module_qn) { rt = cbm_registry_lookup_type( ctx->registry, cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, tqn)); } if (rt && rt->type_param_names) { ret = cbm_type_substitute(ctx->arena, ret, rt->type_param_names, inner->data.template_type.template_args); } else { const char *fb[] = {"T", "K", "V", NULL}; const CBMType *fa[3] = {NULL}; int na = inner->data.template_type.arg_count; if (na > 0) fa[0] = inner->data.template_type.template_args[0]; if (na > 1) fa[1] = inner->data.template_type.template_args[1]; if (na > 2) fa[2] = inner->data.template_type.template_args[2]; ret = cbm_type_substitute(ctx->arena, ret, fb, fa); } if (ret->kind == CBM_TYPE_REFERENCE) ret = ret->data.reference.elem; return ret; } } return cbm_type_deref(inner); } if (strcmp(op, "&") == 0) { return cbm_type_pointer(ctx->arena, c_eval_expr_type(ctx, operand)); } if (strcmp(op, "!") == 0 || strcmp(op, "~") == 0) { return c_eval_expr_type(ctx, operand); } if (strcmp(op, "-") == 0 || strcmp(op, "+") == 0) { return c_eval_expr_type(ctx, operand); } break; } } return cbm_type_unknown(); } // --- pointer_expression: *p or &x (C tree-sitter uses this instead of unary_expression) --- if (strcmp(kind, "pointer_expression") == 0) { TSNode arg = ts_node_child_by_field_name(node, "argument", 8); if (ts_node_is_null(arg)) return cbm_type_unknown(); // Check operator for (uint32_t i = 0; i < ts_node_child_count(node); i++) { TSNode child = ts_node_child(node, i); if (!ts_node_is_named(child)) { char *op = c_node_text(ctx, child); if (op && strcmp(op, "*") == 0) { const CBMType *inner = c_eval_expr_type(ctx, arg); inner = c_simplify_type(ctx, inner, false); if (inner && inner->kind == CBM_TYPE_TEMPLATE) { const char *tqn = inner->data.template_type.template_name; const CBMRegisteredFunc *opf = c_lookup_member(ctx, tqn, "operator*"); if (opf && opf->signature && opf->signature->kind == CBM_TYPE_FUNC && opf->signature->data.func.return_types && opf->signature->data.func.return_types[0]) { const CBMType *ret = opf->signature->data.func.return_types[0]; const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, tqn); if (!rt && ctx->module_qn) { rt = cbm_registry_lookup_type( ctx->registry, cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, tqn)); } if (rt && rt->type_param_names) { ret = cbm_type_substitute(ctx->arena, ret, rt->type_param_names, inner->data.template_type.template_args); } else { const char *fb[] = {"T", "K", "V", NULL}; const CBMType *fa[3] = {NULL}; int na = inner->data.template_type.arg_count; if (na > 0) fa[0] = inner->data.template_type.template_args[0]; if (na > 1) fa[1] = inner->data.template_type.template_args[1]; if (na > 2) fa[2] = inner->data.template_type.template_args[2]; ret = cbm_type_substitute(ctx->arena, ret, fb, fa); } if (ret->kind == CBM_TYPE_REFERENCE) ret = ret->data.reference.elem; return ret; } } return cbm_type_deref(inner); } if (op && strcmp(op, "&") == 0) return cbm_type_pointer(ctx->arena, c_eval_expr_type(ctx, arg)); break; } } return cbm_type_unknown(); } // --- update_expression: ++i, i++ --- if (strcmp(kind, "update_expression") == 0) { TSNode operand = ts_node_child_by_field_name(node, "argument", 8); if (!ts_node_is_null(operand)) return c_eval_expr_type(ctx, operand); return cbm_type_unknown(); } // --- binary_expression --- if (strcmp(kind, "binary_expression") == 0) { TSNode left = ts_node_child_by_field_name(node, "left", 4); // Check operator for comparison (returns bool) for (uint32_t i = 0; i < ts_node_child_count(node); i++) { TSNode child = ts_node_child(node, i); if (!ts_node_is_named(child)) { char *op = c_node_text(ctx, child); if (op && (strcmp(op, "==") == 0 || strcmp(op, "!=") == 0 || strcmp(op, "<") == 0 || strcmp(op, ">") == 0 || strcmp(op, "<=") == 0 || strcmp(op, ">=") == 0 || strcmp(op, "&&") == 0 || strcmp(op, "||") == 0)) { return cbm_type_builtin(ctx->arena, "bool"); } break; } } if (!ts_node_is_null(left)) return c_eval_expr_type(ctx, left); return cbm_type_unknown(); } // --- conditional_expression: a ? b : c --- if (strcmp(kind, "conditional_expression") == 0) { TSNode consequence = ts_node_child_by_field_name(node, "consequence", 11); if (!ts_node_is_null(consequence)) return c_eval_expr_type(ctx, consequence); return cbm_type_unknown(); } // --- parenthesized_expression: (expr) --- if (strcmp(kind, "parenthesized_expression") == 0 && ts_node_named_child_count(node) > 0) { return c_eval_expr_type(ctx, ts_node_named_child(node, 0)); } // --- comma_expression --- if (strcmp(kind, "comma_expression") == 0) { TSNode right = ts_node_child_by_field_name(node, "right", 5); if (!ts_node_is_null(right)) return c_eval_expr_type(ctx, right); return cbm_type_unknown(); } // --- sizeof / alignof --- if (strcmp(kind, "sizeof_expression") == 0 || strcmp(kind, "alignof_expression") == 0) { return cbm_type_builtin(ctx->arena, "size_t"); } // --- lambda_expression --- if (strcmp(kind, "lambda_expression") == 0) { const CBMType *ret_type = NULL; // Phase A: trailing return type on declarator (e.g., [](int x) -> Widget { ... }) TSNode declarator = ts_node_child_by_field_name(node, "declarator", 10); if (!ts_node_is_null(declarator)) { uint32_t dnc = ts_node_named_child_count(declarator); for (uint32_t di = 0; di < dnc; di++) { TSNode ch = ts_node_named_child(declarator, di); if (strcmp(ts_node_type(ch), "trailing_return_type") == 0) { TSNode type_desc = ts_node_named_child_count(ch) > 0 ? ts_node_named_child(ch, 0) : ch; ret_type = c_parse_type_node(ctx, type_desc); break; } } } // Phase B: infer from body's immediate return statements (no descent into nested lambdas) if (!ret_type || cbm_type_is_unknown(ret_type)) { TSNode body = ts_node_child_by_field_name(node, "body", 4); if (!ts_node_is_null(body)) { uint32_t bnc = ts_node_named_child_count(body); for (uint32_t bi = 0; bi < bnc; bi++) { TSNode stmt = ts_node_named_child(body, bi); if (ts_node_is_null(stmt)) continue; const char *sk = ts_node_type(stmt); // Skip nested lambdas, loops, etc. — only immediate returns if (strcmp(sk, "return_statement") == 0) { if (ts_node_named_child_count(stmt) > 0) { TSNode ret_expr = ts_node_named_child(stmt, 0); ret_type = c_eval_expr_type(ctx, ret_expr); } break; // first return only } } } } if (ret_type && !cbm_type_is_unknown(ret_type)) { const CBMType **ret_types = (const CBMType **)cbm_arena_alloc(ctx->arena, 2 * sizeof(const CBMType *)); if (ret_types) { ret_types[0] = ret_type; ret_types[1] = NULL; return cbm_type_func(ctx->arena, NULL, NULL, ret_types); } } return cbm_type_func(ctx->arena, NULL, NULL, NULL); } // --- co_await_expression: co_await expr → evaluate operand, return await_resume type --- if (strcmp(kind, "co_await_expression") == 0) { if (ts_node_named_child_count(node) > 0) { TSNode operand = ts_node_named_child(node, 0); const CBMType *op_type = c_eval_expr_type(ctx, operand); // co_await returns the result of operator co_await or the awaitable's await_resume // Heuristic: if operand is a template type (e.g., Task), return first template arg op_type = c_simplify_type(ctx, op_type, false); if (op_type && op_type->kind == CBM_TYPE_TEMPLATE && op_type->data.template_type.arg_count > 0) { return op_type->data.template_type.template_args[0]; } // Fallback: check for await_resume method const char *op_qn = type_to_qn(op_type); if (op_qn) { const CBMRegisteredFunc *ar = c_lookup_member(ctx, op_qn, "await_resume"); if (ar && ar->signature && ar->signature->kind == CBM_TYPE_FUNC && ar->signature->data.func.return_types && ar->signature->data.func.return_types[0]) { return ar->signature->data.func.return_types[0]; } } } return cbm_type_unknown(); } // --- fold_expression: (args op ...) → return type of operand --- if (strcmp(kind, "fold_expression") == 0) { // Fold expressions apply a binary operator across a parameter pack. // The result type is the type of the operator applied to the pack elements. // Heuristic: return the type of the first named child that isn't an operator. uint32_t fnc = ts_node_named_child_count(node); for (uint32_t fi = 0; fi < fnc; fi++) { TSNode child = ts_node_named_child(node, fi); if (!ts_node_is_null(child)) { const CBMType *ct = c_eval_expr_type(ctx, child); if (!cbm_type_is_unknown(ct)) return ct; } } return cbm_type_unknown(); } // --- requires_expression: requires(T x) { x.method(); } → bool --- if (strcmp(kind, "requires_expression") == 0) { return cbm_type_builtin(ctx->arena, "bool"); } // --- generic_expression: _Generic(expr, type1: val1, type2: val2, ...) --- if (strcmp(kind, "generic_expression") == 0) { // C11 _Generic: evaluate the controlling expression's type, // then match against the association list. // Heuristic: return the type of the first non-default association value. uint32_t gnc = ts_node_named_child_count(node); for (uint32_t gi = 1; gi < gnc; gi++) { TSNode assoc = ts_node_named_child(node, gi); if (!ts_node_is_null(assoc)) { // Each association may have type + value children uint32_t anc = ts_node_named_child_count(assoc); if (anc > 0) { TSNode val = ts_node_named_child(assoc, anc - 1); const CBMType *vt = c_eval_expr_type(ctx, val); if (!cbm_type_is_unknown(vt)) return vt; } } } return cbm_type_unknown(); } // --- Literals --- if (strcmp(kind, "number_literal") == 0) { char *text = c_node_text(ctx, node); if (text) { // Check for float suffix or decimal point for (const char *p = text; *p; p++) { if (*p == '.' || *p == 'f' || *p == 'F' || *p == 'e' || *p == 'E') return cbm_type_builtin(ctx->arena, "double"); } } return cbm_type_builtin(ctx->arena, "int"); } if (strcmp(kind, "string_literal") == 0 || strcmp(kind, "concatenated_string") == 0 || strcmp(kind, "raw_string_literal") == 0) { // C and C++ string literals are const char*, not std::string // (std::string requires explicit construction or "hello"s suffix) return cbm_type_pointer(ctx->arena, cbm_type_builtin(ctx->arena, "char")); } if (strcmp(kind, "char_literal") == 0) { return cbm_type_builtin(ctx->arena, "char"); } if (strcmp(kind, "true") == 0 || strcmp(kind, "false") == 0) { return cbm_type_builtin(ctx->arena, "bool"); } if (strcmp(kind, "null") == 0 || strcmp(kind, "nullptr") == 0) { return cbm_type_pointer(ctx->arena, cbm_type_unknown()); } // --- compound_literal_expression: (Type){...} --- if (strcmp(kind, "compound_literal_expression") == 0) { TSNode type_node = ts_node_child_by_field_name(node, "type", 4); if (!ts_node_is_null(type_node)) return c_parse_type_node(ctx, type_node); return cbm_type_unknown(); } // --- assignment_expression: a = b (returns LHS type) --- if (strcmp(kind, "assignment_expression") == 0) { TSNode left = ts_node_child_by_field_name(node, "left", 4); if (!ts_node_is_null(left)) return c_eval_expr_type(ctx, left); return cbm_type_unknown(); } // --- initializer_list: {1, 2, 3} --- if (strcmp(kind, "initializer_list") == 0) { return cbm_type_unknown(); // context-dependent } return cbm_type_unknown(); } // ============================================================================ // c_lookup_member: method/field lookup with base class traversal // ============================================================================ // Build "." into a caller-provided stack buffer, replacing the // vsnprintf("%s.%s") path (whose %s does a strlen of both args) that otherwise // dominates the kernel cross-file resolve miss cascade. Uses the cached module_qn_len. // Returns buf on success, or NULL if the result would not fit (caller falls back to // cbm_arena_sprintf). The buffer is only read by the immediate registry lookup — a // stack lifetime is sufficient (the QN is never retained). static const char *c_build_module_prefixed(const CLSPContext *ctx, const char *type_qn, char *buf, size_t bufsz) { size_t mlen = ctx->module_qn_len; size_t tlen = strlen(type_qn); if (mlen + 1 + tlen + 1 > bufsz) return NULL; // would truncate → fall back to arena sprintf memcpy(buf, ctx->module_qn, mlen); buf[mlen] = '.'; memcpy(buf + mlen + 1, type_qn, tlen); buf[mlen + 1 + tlen] = '\0'; return buf; } // FNV-1a over (type_qn, 0xff separator, member_name) for the negative-lookup memo. // 0 is remapped to 1 so it can serve as the empty-slot sentinel. static uint64_t c_neg_memo_hash(const char *type_qn, const char *member_name) { uint64_t h = 1469598103934665603ULL; // FNV-1a offset basis for (const unsigned char *p = (const unsigned char *)type_qn; *p; p++) { h ^= (uint64_t)*p; h *= 1099511628211ULL; } h ^= (uint64_t)0xffu; h *= 1099511628211ULL; for (const unsigned char *p = (const unsigned char *)member_name; *p; p++) { h ^= (uint64_t)*p; h *= 1099511628211ULL; } return h ? h : 1ULL; } static bool c_neg_memo_contains(const CLSPContext *ctx, uint64_t h) { if (!ctx->neg_memo || ctx->neg_memo_cap == 0) return false; uint64_t mask = (uint64_t)ctx->neg_memo_cap - 1; for (uint64_t i = h & mask;; i = (i + 1) & mask) { uint64_t slot = ctx->neg_memo[i]; if (slot == 0) return false; // empty slot → not present if (slot == h) return true; } } static void c_neg_memo_insert(CLSPContext *ctx, uint64_t h) { // Lazy alloc / grow-by-rehash at 70% load. On OOM, silently disable the memo // (correctness is unaffected — the full cascade still runs). if (ctx->neg_memo == NULL) { uint64_t *nm = (uint64_t *)calloc(1024, sizeof(uint64_t)); if (!nm) return; ctx->neg_memo = nm; ctx->neg_memo_cap = 1024; ctx->neg_memo_count = 0; } else if ((ctx->neg_memo_count + 1) * 10 >= ctx->neg_memo_cap * 7) { int new_cap = ctx->neg_memo_cap * 2; uint64_t *nm = (uint64_t *)calloc((size_t)new_cap, sizeof(uint64_t)); if (nm) { uint64_t nmask = (uint64_t)new_cap - 1; for (int j = 0; j < ctx->neg_memo_cap; j++) { uint64_t v = ctx->neg_memo[j]; if (v == 0) continue; uint64_t k = v & nmask; while (nm[k] != 0) k = (k + 1) & nmask; nm[k] = v; } free(ctx->neg_memo); ctx->neg_memo = nm; ctx->neg_memo_cap = new_cap; } // if calloc failed: keep the existing table (load may exceed 70%, still correct) } uint64_t mask = (uint64_t)ctx->neg_memo_cap - 1; for (uint64_t i = h & mask;; i = (i + 1) & mask) { if (ctx->neg_memo[i] == 0) { ctx->neg_memo[i] = h; ctx->neg_memo_count++; return; } if (ctx->neg_memo[i] == h) return; // already recorded } } static void c_neg_memo_free(CLSPContext *ctx) { if (ctx->neg_memo) free(ctx->neg_memo); ctx->neg_memo = NULL; ctx->neg_memo_cap = 0; ctx->neg_memo_count = 0; } static const CBMRegisteredFunc *c_lookup_member_depth(CLSPContext *ctx, const char *type_qn, const char *member_name, int depth) { if (!type_qn || !member_name) return NULL; if (depth > CBM_LSP_MAX_LOOKUP_DEPTH) return NULL; // Direct method lookup. Runs FIRST, before consulting the memo, so a real // direct-resolvable member (incl. a hash-collision victim, or one registered // mid-file) can never be skipped: it is returned here regardless of the memo. const CBMRegisteredFunc *f = cbm_registry_lookup_method(ctx->registry, type_qn, member_name); if (f) return f; // Negative-lookup memo (depth==0, shared read-only registry only). A recorded // hit means this exact (type_qn, member) already failed the whole cascade. Under // the sealed Tier-2 registry the module-prefix (below), base-class and short-name // cascades are pure, immutable functions of (type_qn, registry), so they are // provably still NULL and can be skipped. Only the SCOPE-ALIAS path is // context-dependent, so it is still evaluated below before we trust the memo. // The direct lookup above already served as the collision/staleness verification. bool neg_memo_hit = false; uint64_t neg_h = 0; if (depth == 0 && ctx->registry_shared) { neg_h = c_neg_memo_hash(type_qn, member_name); neg_memo_hit = c_neg_memo_contains(ctx, neg_h); } // Try module-prefixed QN (e.g., "Container" -> "test.main.Container"). // Skipped on a memo hit: provably NULL under the read-only shared registry. if (!neg_memo_hit && ctx->module_qn) { char sbuf[1024]; const char *prefixed = c_build_module_prefixed(ctx, type_qn, sbuf, sizeof(sbuf)); if (!prefixed) prefixed = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, type_qn); f = cbm_registry_lookup_method(ctx->registry, prefixed, member_name); if (f) return f; } // Scope-based alias: using Vec = std::vector; // Vec is in scope as ALIAS → follow to underlying type's QN. // ALWAYS evaluated (scope is context-dependent — not covered by the memo). { const CBMType *scoped = cbm_scope_lookup(ctx->current_scope, type_qn); if (scoped && scoped->kind == CBM_TYPE_ALIAS) { const CBMType *underlying = cbm_type_resolve_alias(scoped); if (underlying && !cbm_type_is_unknown(underlying)) { const char *alias_target_qn = type_to_qn(underlying); if (alias_target_qn) { f = c_lookup_member_depth(ctx, alias_target_qn, member_name, depth + 1); if (f) return f; } } } } // Memo hit and the scope-alias path also missed: the remaining base-class and // short-name cascades are registry-only and provably NULL → skip them (this is // where the O(type_count) short-name scan is avoided). Idempotent re-insert is // unnecessary since neg_h is already present. if (neg_memo_hit) return NULL; // Check registered type for alias and base classes const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, type_qn); if (!rt && ctx->module_qn) { char sbuf[1024]; const char *prefixed = c_build_module_prefixed(ctx, type_qn, sbuf, sizeof(sbuf)); if (!prefixed) prefixed = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, type_qn); rt = cbm_registry_lookup_type(ctx->registry, prefixed); } if (rt) { // Alias chain if (rt->alias_of) { f = c_lookup_member_depth(ctx, rt->alias_of, member_name, depth + 1); if (f) return f; } // Base classes (embedded_types stores base class QNs) if (rt->embedded_types) { for (int i = 0; rt->embedded_types[i]; i++) { f = c_lookup_member_depth(ctx, rt->embedded_types[i], member_name, depth + 1); if (f) return f; } } } /* Namespaced-type short-name fallback: a type name that resolves nowhere may * be a type declared inside a namespace whose registered QN carries the * namespace ("..Logger"), while the use site only knew the * file-scoped ".Logger" or the bare "Logger" (e.g. the return type of * a namespace-scoped factory used outside that namespace). Resolve by the * SHORT name (last segment) against the registry and retry with the full QN. * Reached only after the direct/module/alias/base lookups all miss; prefers * an in-module match. Mirrors the C# short-name type fallback. */ if (depth == 0 && ctx->registry) { const char *dot = strrchr(type_qn, '.'); const char *shortn = dot ? dot + 1 : type_qn; size_t slen = strlen(shortn); const char *best_qn = NULL; for (int i = 0; i < ctx->registry->type_count; i++) { const char *q = ctx->registry->types[i].qualified_name; if (!q) { continue; } size_t qlen = strlen(q); if (qlen <= slen + 1 || q[qlen - slen - 1] != '.' || strcmp(q + qlen - slen, shortn) != 0) { continue; } if (strcmp(q, type_qn) == 0) { continue; // already tried as-is above } best_qn = q; if (ctx->module_qn && strncmp(q, ctx->module_qn, strlen(ctx->module_qn)) == 0) { break; // prefer a match in the current module } } if (best_qn) { f = c_lookup_member_depth(ctx, best_qn, member_name, depth + 1); if (f) { return f; } } } // Whole cascade missed: record the negative result so a repeat of this exact // (type_qn, member) can skip the module-prefix/base-class/short-name work. if (depth == 0 && ctx->registry_shared) c_neg_memo_insert(ctx, neg_h); return NULL; } const CBMRegisteredFunc *c_lookup_member(CLSPContext *ctx, const char *type_qn, const char *member_name) { return c_lookup_member_depth(ctx, type_qn, member_name, 0); } // True if any BASE class of type_qn (not type_qn itself) declares member_name — // i.e. a method found directly on type_qn is an OVERRIDE of an inherited method. // This mirrors the existing virtual-dispatch notion (a derived override of a base // method) for the case where the override is resolved directly on the derived // type rather than through the base. static bool c_base_declares_member(CLSPContext *ctx, const char *type_qn, const char *member_name) { const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, type_qn); if (!rt && ctx->module_qn) { rt = cbm_registry_lookup_type( ctx->registry, cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, type_qn)); } if (!rt || !rt->embedded_types) return false; for (int i = 0; rt->embedded_types[i]; i++) { if (c_lookup_member(ctx, rt->embedded_types[i], member_name)) return true; } return false; } // Field type lookup static const CBMType *c_lookup_field_type(CLSPContext *ctx, const char *type_qn, const char *field_name, int depth) { if (!type_qn || !field_name || depth > 5) return NULL; const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, type_qn); if (!rt && ctx->module_qn) { rt = cbm_registry_lookup_type( ctx->registry, cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, type_qn)); } if (!rt) return NULL; if (rt->alias_of) return c_lookup_field_type(ctx, rt->alias_of, field_name, depth + 1); if (rt->field_names) { for (int i = 0; rt->field_names[i]; i++) { if (strcmp(rt->field_names[i], field_name) == 0 && rt->field_types && rt->field_types[i]) return rt->field_types[i]; } } // Base classes if (rt->embedded_types) { for (int i = 0; rt->embedded_types[i]; i++) { const CBMType *f = c_lookup_field_type(ctx, rt->embedded_types[i], field_name, depth + 1); if (f) return f; } } return NULL; } // ============================================================================ // c_process_statement: bind variables from statements // ============================================================================ // Parse a declaration to extract type and declarators static const CBMType *c_parse_declaration_type(CLSPContext *ctx, TSNode decl_node) { // Look for type node in declaration children uint32_t nc = ts_node_named_child_count(decl_node); for (uint32_t i = 0; i < nc; i++) { TSNode child = ts_node_named_child(decl_node, i); const char *ck = ts_node_type(child); // Skip declarators and non-type specifiers if (strcmp(ck, "init_declarator") == 0 || strcmp(ck, "identifier") == 0 || strcmp(ck, "pointer_declarator") == 0 || strcmp(ck, "reference_declarator") == 0 || strcmp(ck, "array_declarator") == 0 || strcmp(ck, "function_declarator") == 0 || strcmp(ck, "storage_class_specifier") == 0 || strcmp(ck, "type_qualifier") == 0 || strcmp(ck, "virtual") == 0 || strcmp(ck, "explicit") == 0 || strcmp(ck, "virtual_function_specifier") == 0 || strcmp(ck, "access_specifier") == 0 || strcmp(ck, "friend") == 0 || strcmp(ck, "comment") == 0) continue; // Found a type node return c_parse_type_node(ctx, child); } return cbm_type_unknown(); } void c_process_statement(CLSPContext *ctx, TSNode node) { if (ts_node_is_null(node)) return; const char *kind = ts_node_type(node); // declaration: Type var = expr; or Type var1, var2; if (strcmp(kind, "declaration") == 0) { const CBMType *base_type = c_parse_declaration_type(ctx, node); bool has_auto = false; // Check if type is auto/decltype uint32_t nc = ts_node_named_child_count(node); for (uint32_t i = 0; i < nc; i++) { TSNode child = ts_node_named_child(node, i); const char *ck = ts_node_type(child); if (strcmp(ck, "placeholder_type_specifier") == 0 || strcmp(ck, "auto") == 0) { has_auto = true; break; } if (strcmp(ck, "decltype") == 0) { base_type = c_parse_type_node(ctx, child); break; } } // Process each declarator for (uint32_t i = 0; i < nc; i++) { TSNode child = ts_node_named_child(node, i); const char *ck = ts_node_type(child); if (strcmp(ck, "init_declarator") == 0) { TSNode decl = ts_node_child_by_field_name(child, "declarator", 10); TSNode value = ts_node_child_by_field_name(child, "value", 5); const CBMType *var_type = base_type; // For auto, infer from initializer if (has_auto && !ts_node_is_null(value)) { var_type = c_eval_expr_type(ctx, value); } // Get variable name from declarator if (!ts_node_is_null(decl)) { const char *dk = ts_node_type(decl); char *var_name = NULL; if (strcmp(dk, "identifier") == 0) { var_name = c_node_text(ctx, decl); } else if (strcmp(dk, "pointer_declarator") == 0) { // Count pointer depth and find identifier int ptr_depth = 0; TSNode inner = decl; while (!ts_node_is_null(inner) && strcmp(ts_node_type(inner), "pointer_declarator") == 0) { ptr_depth++; uint32_t dnc = ts_node_named_child_count(inner); inner = dnc > 0 ? ts_node_named_child(inner, dnc - 1) : (TSNode){0}; } if (!ts_node_is_null(inner) && strcmp(ts_node_type(inner), "identifier") == 0) var_name = c_node_text(ctx, inner); // For auto*, the deduced type already includes pointer depth // (e.g., auto* w = new Widget() → eval gives Widget*) if (!has_auto) { for (int d = 0; d < ptr_depth; d++) var_type = cbm_type_pointer(ctx->arena, var_type); } } else if (strcmp(dk, "reference_declarator") == 0) { if (ts_node_named_child_count(decl) > 0) { TSNode inner = ts_node_named_child(decl, 0); const char *ik = ts_node_type(inner); if (strcmp(ik, "identifier") == 0) { var_name = c_node_text(ctx, inner); } else if (strcmp(ik, "structured_binding_declarator") == 0) { // const auto& [a, b] = expr; — delegate to structured binding const CBMType *rhs_type = cbm_type_unknown(); if (!ts_node_is_null(value)) { rhs_type = c_eval_expr_type(ctx, value); rhs_type = c_simplify_type(ctx, rhs_type, false); } uint32_t bnc = ts_node_named_child_count(inner); int binding_idx = 0; for (uint32_t bi = 0; bi < bnc; bi++) { TSNode bn = ts_node_named_child(inner, bi); if (strcmp(ts_node_type(bn), "identifier") == 0) { char *bname = c_node_text(ctx, bn); if (!bname) continue; const CBMType *elem_type = cbm_type_unknown(); if (rhs_type && rhs_type->kind == CBM_TYPE_TEMPLATE && rhs_type->data.template_type.template_args) { int nta = rhs_type->data.template_type.arg_count; if (binding_idx < nta) elem_type = rhs_type->data.template_type .template_args[binding_idx]; } if (cbm_type_is_unknown(elem_type) && rhs_type && rhs_type->kind == CBM_TYPE_SLICE) elem_type = rhs_type->data.slice.elem; if (cbm_type_is_unknown(elem_type) && rhs_type) { const char *rhs_qn = type_to_qn(rhs_type); const CBMRegisteredType *rt = rhs_qn ? cbm_registry_lookup_type(ctx->registry, rhs_qn) : NULL; if (!rt && rhs_qn && ctx->module_qn) rt = cbm_registry_lookup_type( ctx->registry, cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, rhs_qn)); if (rt && rt->field_types && rt->field_types[binding_idx]) elem_type = rt->field_types[binding_idx]; } cbm_scope_bind(ctx->current_scope, bname, elem_type); binding_idx++; } } continue; } } // For auto&, the deduced type is already correct if (!has_auto) var_type = cbm_type_reference(ctx->arena, var_type); } else if (strcmp(dk, "function_declarator") == 0) { // Function pointer: int (*fp)(int) = &target_func; // Walk into function_declarator → parenthesized_declarator → // pointer_declarator → identifier TSNode inner = ts_node_child_by_field_name(decl, "declarator", 10); while (!ts_node_is_null(inner)) { const char *ik = ts_node_type(inner); if (strcmp(ik, "identifier") == 0) { var_name = c_node_text(ctx, inner); break; } else if (strcmp(ik, "parenthesized_declarator") == 0 || strcmp(ik, "pointer_declarator") == 0) { // Walk deeper: get last named child uint32_t nc = ts_node_named_child_count(inner); inner = nc > 0 ? ts_node_named_child(inner, nc - 1) : (TSNode){0}; } else { break; } } // Mark as pointer type (function pointer decays to pointer) var_type = cbm_type_pointer(ctx->arena, var_type); } else if (strcmp(dk, "array_declarator") == 0) { // Type var[N]; — extract name and wrap in slice TSNode inner = ts_node_child_by_field_name(decl, "declarator", 10); if (!ts_node_is_null(inner) && strcmp(ts_node_type(inner), "identifier") == 0) { var_name = c_node_text(ctx, inner); } var_type = cbm_type_slice(ctx->arena, var_type); } else if (strcmp(dk, "structured_binding_declarator") == 0) { // auto [a, b] = expr; // Try to decompose pair/tuple from initializer type const CBMType *rhs_type = cbm_type_unknown(); if (!ts_node_is_null(value)) { rhs_type = c_eval_expr_type(ctx, value); rhs_type = c_simplify_type(ctx, rhs_type, false); } uint32_t bnc = ts_node_named_child_count(decl); int binding_idx = 0; for (uint32_t bi = 0; bi < bnc; bi++) { TSNode bn = ts_node_named_child(decl, bi); if (strcmp(ts_node_type(bn), "identifier") == 0) { char *bname = c_node_text(ctx, bn); if (!bname) continue; const CBMType *elem_type = cbm_type_unknown(); // Decompose std::pair → first=T1, second=T2 if (rhs_type && rhs_type->kind == CBM_TYPE_TEMPLATE && rhs_type->data.template_type.template_args) { int nta = rhs_type->data.template_type.arg_count; if (binding_idx < nta) { elem_type = rhs_type->data.template_type.template_args[binding_idx]; } } // Decompose array (SLICE) → each binding gets element type if (cbm_type_is_unknown(elem_type) && rhs_type && rhs_type->kind == CBM_TYPE_SLICE) { elem_type = rhs_type->data.slice.elem; } // Decompose struct fields if (cbm_type_is_unknown(elem_type) && rhs_type) { const char *rhs_qn = type_to_qn(rhs_type); const CBMRegisteredType *rt = rhs_qn ? cbm_registry_lookup_type(ctx->registry, rhs_qn) : NULL; if (!rt && rhs_qn && ctx->module_qn) rt = cbm_registry_lookup_type( ctx->registry, cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, rhs_qn)); if (rt && rt->field_types && rt->field_types[binding_idx]) { elem_type = rt->field_types[binding_idx]; } } cbm_scope_bind(ctx->current_scope, bname, elem_type); binding_idx++; } } continue; } if (var_name && var_name[0] && strcmp(var_name, "_") != 0) { cbm_scope_bind(ctx->current_scope, var_name, var_type); // Track function pointer targets: fp = &foo or fp = foo // Accept pointer, func, or named types (typedef function pointers // like fn_t appear as CBM_TYPE_NAMED, not FUNC/POINTER). if (var_type && (var_type->kind == CBM_TYPE_FUNC || var_type->kind == CBM_TYPE_POINTER || var_type->kind == CBM_TYPE_NAMED)) { if (!ts_node_is_null(value)) { const char *vk = ts_node_type(value); // pointer_expression with & operator: &foo if (strcmp(vk, "pointer_expression") == 0 || strcmp(vk, "unary_expression") == 0) { uint32_t vnc = ts_node_named_child_count(value); for (uint32_t vi = 0; vi < vnc; vi++) { TSNode vch = ts_node_named_child(value, vi); if (strcmp(ts_node_type(vch), "identifier") == 0) { char *target_name = c_node_text(ctx, vch); if (target_name) { const char *target_qn = c_resolve_name_to_func_qn(ctx, target_name); if (target_qn) { c_add_fp_target(ctx, var_name, target_qn); } } break; } } } // Direct identifier: fp = foo (function decays to pointer) else if (strcmp(vk, "identifier") == 0) { char *target_name = c_node_text(ctx, value); if (target_name) { const char *target_qn = c_resolve_name_to_func_qn(ctx, target_name); if (target_qn) { c_add_fp_target(ctx, var_name, target_qn); } } } } } // Fallback: if RHS is an identifier resolving to a known function, // track as fp target even when var_type is unknown (typedef func ptrs). if (!c_lookup_fp_target(ctx, var_name) && !ts_node_is_null(value)) { const char *vk = ts_node_type(value); if (strcmp(vk, "identifier") == 0) { char *target_name = c_node_text(ctx, value); if (target_name) { const char *target_qn = c_resolve_name_to_func_qn(ctx, target_name); if (target_qn) { c_add_fp_target(ctx, var_name, target_qn); } } } } // DLL/dynamic resolver heuristic: fp = (FuncType)Resolve("FuncName") // If RHS is a call (possibly cast-wrapped) with a string literal arg, // and variable has function-pointer-like type or RHS has a cast, // treat the string as an external function name. if (!c_lookup_fp_target(ctx, var_name) && !ts_node_is_null(value)) { bool has_cast = false; const char *dll_func = c_extract_dll_resolve_name(ctx, value, &has_cast); if (dll_func) { bool is_fp_type = var_type && (var_type->kind == CBM_TYPE_FUNC || var_type->kind == CBM_TYPE_POINTER); if (is_fp_type || has_cast) { const char *target_qn = cbm_arena_sprintf(ctx->arena, "external.%s", dll_func); c_add_fp_target(ctx, var_name, target_qn); } } } } } } else if (strcmp(ck, "identifier") == 0) { // Bare declaration without initializer: Type var; char *var_name = c_node_text(ctx, child); if (var_name && var_name[0]) { cbm_scope_bind(ctx->current_scope, var_name, base_type); } } else if (strcmp(ck, "pointer_declarator") == 0) { // Type *var; without initializer int ptr_depth = 0; TSNode inner = child; while (!ts_node_is_null(inner) && strcmp(ts_node_type(inner), "pointer_declarator") == 0) { ptr_depth++; uint32_t dnc = ts_node_named_child_count(inner); inner = dnc > 0 ? ts_node_named_child(inner, dnc - 1) : (TSNode){0}; } if (!ts_node_is_null(inner) && strcmp(ts_node_type(inner), "identifier") == 0) { char *var_name = c_node_text(ctx, inner); if (var_name) { const CBMType *vt = base_type; for (int d = 0; d < ptr_depth; d++) vt = cbm_type_pointer(ctx->arena, vt); cbm_scope_bind(ctx->current_scope, var_name, vt); } } } else if (strcmp(ck, "array_declarator") == 0) { // Type var[N]; without initializer TSNode inner = ts_node_child_by_field_name(child, "declarator", 10); if (!ts_node_is_null(inner) && strcmp(ts_node_type(inner), "identifier") == 0) { char *var_name = c_node_text(ctx, inner); if (var_name) { cbm_scope_bind(ctx->current_scope, var_name, cbm_type_slice(ctx->arena, base_type)); } } } } return; } // using_declaration: using namespace std; or using std::cout; if (strcmp(kind, "using_declaration") == 0) { char *text = c_node_text(ctx, node); if (!text) return; // "using namespace XXX;" if (strstr(text, "namespace")) { // Extract namespace name after "namespace" uint32_t nc = ts_node_named_child_count(node); for (uint32_t i = 0; i < nc; i++) { TSNode child = ts_node_named_child(node, i); const char *ck = ts_node_type(child); if (strcmp(ck, "identifier") == 0 || strcmp(ck, "qualified_identifier") == 0 || strcmp(ck, "scoped_identifier") == 0) { char *ns_name = c_node_text(ctx, child); if (ns_name) c_add_using_namespace(ctx, c_build_qn(ctx, ns_name)); } } } else if (strstr(text, "enum")) { // "using enum MyEnum;" — import enum members into scope uint32_t nc = ts_node_named_child_count(node); for (uint32_t i = 0; i < nc; i++) { TSNode child = ts_node_named_child(node, i); const char *ck = ts_node_type(child); if (strcmp(ck, "identifier") == 0 || strcmp(ck, "qualified_identifier") == 0 || strcmp(ck, "scoped_identifier") == 0 || strcmp(ck, "type_identifier") == 0) { char *enum_text = c_node_text(ctx, child); if (enum_text) { const char *enum_qn = c_build_qn(ctx, enum_text); // Try to find the enum type in registry, import its members const CBMRegisteredType *et = cbm_registry_lookup_type(ctx->registry, enum_qn); if (!et && ctx->module_qn) { enum_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, enum_qn); et = cbm_registry_lookup_type(ctx->registry, enum_qn); } if (!et && ctx->current_namespace) { enum_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->current_namespace, enum_text); et = cbm_registry_lookup_type(ctx->registry, enum_qn); } // Even if enum type not found, add as using namespace for name lookup c_add_using_namespace(ctx, enum_qn); } } } } else { // "using std::cout;" — specific declaration uint32_t nc = ts_node_named_child_count(node); for (uint32_t i = 0; i < nc; i++) { TSNode child = ts_node_named_child(node, i); const char *ck = ts_node_type(child); if (strcmp(ck, "qualified_identifier") == 0 || strcmp(ck, "scoped_identifier") == 0) { char *full_text = c_node_text(ctx, child); if (full_text) { const char *qn = c_build_qn(ctx, full_text); // Extract short name (last component) const char *short_name = strrchr(qn, '.'); short_name = short_name ? short_name + 1 : qn; c_add_using_decl(ctx, short_name, qn); } } } } return; } // alias_declaration: using Foo = Bar; if (strcmp(kind, "alias_declaration") == 0) { TSNode name_node = ts_node_child_by_field_name(node, "name", 4); TSNode type_node = ts_node_child_by_field_name(node, "type", 4); if (!ts_node_is_null(name_node) && !ts_node_is_null(type_node)) { char *alias_name = c_node_text(ctx, name_node); const CBMType *target = c_parse_type_node(ctx, type_node); if (alias_name && target) { // Build alias QN const char *alias_qn = ctx->current_namespace ? cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->current_namespace, alias_name) : cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, alias_name); cbm_scope_bind(ctx->current_scope, alias_name, cbm_type_alias(ctx->arena, alias_qn, target)); } } return; } // type_definition: typedef Bar Foo; or typedef struct X Y; if (strcmp(kind, "type_definition") == 0) { // Get the type being aliased and the alias name(s). // Tree-sitter produces: type_definition { type_spec alias_name } // For "typedef RealWidget Widget": { type_identifier("RealWidget") // type_identifier("Widget") } For "typedef struct Foo Bar": { struct_specifier // type_identifier("Bar") } The first type_identifier may be the source type (when no // specifier precedes it). const CBMType *target = cbm_type_unknown(); uint32_t nc = ts_node_named_child_count(node); bool found_type = false; int first_type_id_idx = -1; for (uint32_t i = 0; i < nc; i++) { TSNode child = ts_node_named_child(node, i); const char *ck = ts_node_type(child); if (!found_type && strcmp(ck, "type_identifier") != 0 && strcmp(ck, "pointer_declarator") != 0 && strcmp(ck, "identifier") != 0) { target = c_parse_type_node(ctx, child); found_type = true; } else if (!found_type && strcmp(ck, "type_identifier") == 0 && first_type_id_idx < 0) { // First type_identifier without a preceding specifier — this IS the source type. // Mark it and parse; the NEXT type_identifier will be the alias name. first_type_id_idx = (int)i; target = c_parse_type_node(ctx, child); found_type = true; } else if (found_type && strcmp(ck, "type_identifier") == 0) { char *alias_name = c_node_text(ctx, child); if (alias_name) { const char *alias_qn = ctx->current_namespace ? cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->current_namespace, alias_name) : cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, alias_name); cbm_scope_bind(ctx->current_scope, alias_name, cbm_type_alias(ctx->arena, alias_qn, target)); } } } return; } // namespace_alias_definition: namespace fs = std::filesystem; if (strcmp(kind, "namespace_alias_definition") == 0) { TSNode name_node = ts_node_child_by_field_name(node, "name", 4); TSNode value_node = ts_node_child_by_field_name(node, "value", 5); if (ts_node_is_null(value_node)) { // Try last named child uint32_t nc = ts_node_named_child_count(node); if (nc >= 2) { name_node = ts_node_named_child(node, 0); value_node = ts_node_named_child(node, nc - 1); } } if (!ts_node_is_null(name_node) && !ts_node_is_null(value_node)) { char *alias = c_node_text(ctx, name_node); char *target = c_node_text(ctx, value_node); if (alias && target) { c_add_ns_alias(ctx, alias, c_build_qn(ctx, target)); } } return; } // for_range_loop: for (auto& x : container) { ... } if (strcmp(kind, "for_range_loop") == 0) { TSNode decl = ts_node_child_by_field_name(node, "declarator", 10); TSNode type_node = ts_node_child_by_field_name(node, "type", 4); TSNode right = ts_node_child_by_field_name(node, "right", 5); const CBMType *elem_type = cbm_type_unknown(); if (!ts_node_is_null(right)) { const CBMType *container_type = c_eval_expr_type(ctx, right); // Deduce element type: container's begin()->operator* or element type if (container_type) { if (container_type->kind == CBM_TYPE_SLICE) { elem_type = container_type->data.slice.elem; } else if (container_type->kind == CBM_TYPE_TEMPLATE && container_type->data.template_type.arg_count > 0) { // Map containers: element is pair not just K const char *tname = container_type->data.template_type.template_name; if (tname && container_type->data.template_type.arg_count >= 2 && (strstr(tname, "map") || strstr(tname, "Map"))) { const CBMType *pair_args[3]; pair_args[0] = container_type->data.template_type.template_args[0]; pair_args[1] = container_type->data.template_type.template_args[1]; pair_args[2] = NULL; elem_type = cbm_type_template(ctx->arena, "std.pair", pair_args, 2); } else { elem_type = container_type->data.template_type.template_args[0]; } } // Iterator protocol fallback: begin() -> iter -> operator*() -> elem if (cbm_type_is_unknown(elem_type) && container_type->kind == CBM_TYPE_NAMED) { const char *cqn = container_type->data.named.qualified_name; const CBMRegisteredFunc *begin_fn = c_lookup_member(ctx, cqn, "begin"); if (begin_fn && begin_fn->signature && begin_fn->signature->kind == CBM_TYPE_FUNC && begin_fn->signature->data.func.return_types && begin_fn->signature->data.func.return_types[0]) { const CBMType *iter_type = begin_fn->signature->data.func.return_types[0]; const char *iter_qn = type_to_qn(iter_type); if (iter_qn) { const CBMRegisteredFunc *deref = c_lookup_member(ctx, iter_qn, "operator*"); if (deref && deref->signature && deref->signature->kind == CBM_TYPE_FUNC && deref->signature->data.func.return_types && deref->signature->data.func.return_types[0]) { elem_type = deref->signature->data.func.return_types[0]; } } } } } } // If explicit type, use that if (!ts_node_is_null(type_node)) { const char *tk = ts_node_type(type_node); if (strcmp(tk, "placeholder_type_specifier") != 0 && strcmp(tk, "auto") != 0) { elem_type = c_parse_type_node(ctx, type_node); } } // Bind declarator if (!ts_node_is_null(decl)) { // Unwrap reference_declarator to get inner declarator TSNode bind_target = decl; const char *dk = ts_node_type(bind_target); if (strcmp(dk, "reference_declarator") == 0 && ts_node_named_child_count(bind_target) > 0) { bind_target = ts_node_named_child(bind_target, 0); dk = ts_node_type(bind_target); } if (strcmp(dk, "identifier") == 0) { char *var_name = c_node_text(ctx, bind_target); if (var_name) cbm_scope_bind(ctx->current_scope, var_name, elem_type); } else if (strcmp(dk, "structured_binding_declarator") == 0) { // for (auto& [k, v] : container) — decompose elem_type uint32_t bnc = ts_node_named_child_count(bind_target); int binding_idx = 0; for (uint32_t bi = 0; bi < bnc; bi++) { TSNode bn = ts_node_named_child(bind_target, bi); if (strcmp(ts_node_type(bn), "identifier") == 0) { char *bname = c_node_text(ctx, bn); if (!bname) continue; const CBMType *bt = cbm_type_unknown(); // TEMPLATE decomposition (e.g., pair) if (elem_type && elem_type->kind == CBM_TYPE_TEMPLATE && elem_type->data.template_type.template_args && binding_idx < elem_type->data.template_type.arg_count) { bt = elem_type->data.template_type.template_args[binding_idx]; } // SLICE decomposition if (cbm_type_is_unknown(bt) && elem_type && elem_type->kind == CBM_TYPE_SLICE) { bt = elem_type->data.slice.elem; } // Struct field decomposition if (cbm_type_is_unknown(bt) && elem_type) { const char *eq = type_to_qn(elem_type); const CBMRegisteredType *rt = eq ? cbm_registry_lookup_type(ctx->registry, eq) : NULL; if (!rt && eq && ctx->module_qn) rt = cbm_registry_lookup_type( ctx->registry, cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, eq)); if (rt && rt->field_types && rt->field_types[binding_idx]) bt = rt->field_types[binding_idx]; } cbm_scope_bind(ctx->current_scope, bname, bt); binding_idx++; } } } } return; } // parameter_declaration: bind param if (strcmp(kind, "parameter_declaration") == 0) { TSNode type_node = ts_node_child_by_field_name(node, "type", 4); TSNode decl = ts_node_child_by_field_name(node, "declarator", 10); if (ts_node_is_null(type_node)) return; const CBMType *param_type = c_parse_type_node(ctx, type_node); if (!ts_node_is_null(decl)) { const char *dk = ts_node_type(decl); if (strcmp(dk, "identifier") == 0) { char *name = c_node_text(ctx, decl); if (name) cbm_scope_bind(ctx->current_scope, name, param_type); } else if (strcmp(dk, "pointer_declarator") == 0) { int ptr_depth = 0; TSNode inner = decl; while (!ts_node_is_null(inner) && strcmp(ts_node_type(inner), "pointer_declarator") == 0) { ptr_depth++; uint32_t dnc = ts_node_named_child_count(inner); inner = dnc > 0 ? ts_node_named_child(inner, dnc - 1) : (TSNode){0}; } if (!ts_node_is_null(inner) && strcmp(ts_node_type(inner), "identifier") == 0) { char *name = c_node_text(ctx, inner); if (name) { const CBMType *vt = param_type; for (int d = 0; d < ptr_depth; d++) vt = cbm_type_pointer(ctx->arena, vt); cbm_scope_bind(ctx->current_scope, name, vt); } } } else if (strcmp(dk, "reference_declarator") == 0) { if (ts_node_named_child_count(decl) > 0) { TSNode inner = ts_node_named_child(decl, 0); if (strcmp(ts_node_type(inner), "identifier") == 0) { char *name = c_node_text(ctx, inner); if (name) cbm_scope_bind(ctx->current_scope, name, cbm_type_reference(ctx->arena, param_type)); } } } } return; } } // ============================================================================ // Emit helpers // ============================================================================ static void c_emit_resolved_call_orig(CLSPContext *ctx, const char *callee_qn, const char *orig, const char *strategy, float confidence) { if (!ctx->resolved_calls || !callee_qn || !ctx->enclosing_func_qn) return; CBMResolvedCall rc; rc.caller_qn = ctx->enclosing_func_qn; rc.callee_qn = callee_qn; rc.strategy = strategy; rc.confidence = confidence; // For a data-flow resolution (e.g. a function pointer `fp` resolved to its // target), `reason` carries the ORIGINAL textual callee name the LSP // resolved FROM, so the pipeline join can match the call site on that name // even though it differs from the resolved callee_qn's short name. `reason` // is otherwise NULL for resolved calls and is never read for them by the // pipeline consumers, so this overload is side-effect-free. rc.reason = orig; cbm_resolvedcall_push(ctx->resolved_calls, ctx->arena, rc); } static void c_emit_resolved_call(CLSPContext *ctx, const char *callee_qn, const char *strategy, float confidence) { c_emit_resolved_call_orig(ctx, callee_qn, NULL, strategy, confidence); } static void c_emit_unresolved_call(CLSPContext *ctx, const char *expr_text, const char *reason) { if (!ctx->resolved_calls || !ctx->enclosing_func_qn) return; CBMResolvedCall rc; rc.caller_qn = ctx->enclosing_func_qn; rc.callee_qn = expr_text ? expr_text : "?"; rc.strategy = "lsp_unresolved"; rc.confidence = 0.0f; rc.reason = reason; cbm_resolvedcall_push(ctx->resolved_calls, ctx->arena, rc); } // ============================================================================ // resolve_calls_in_node: walk AST and resolve calls // ============================================================================ static void c_resolve_calls_in_node_inner(CLSPContext *ctx, TSNode node); #define C_LSP_MAX_WALK_DEPTH 512 /* Depth-guarded entry: the AST walk recurses per nesting level and crashed * with a stack overflow on deeply nested real-world C++ (bitcoin, SIGSEGV in * cbm_type_substitute under hundreds of recursive c_resolve_calls_in_node * frames via c_adl_resolve). Past the cap the subtree is skipped — its calls * stay unresolved, which is graceful degradation, not a crash. */ static void c_resolve_calls_in_node(CLSPContext *ctx, TSNode node) { if (ctx->walk_depth >= C_LSP_MAX_WALK_DEPTH) return; ctx->walk_depth++; c_resolve_calls_in_node_inner(ctx, node); ctx->walk_depth--; } static void c_resolve_calls_in_node_inner(CLSPContext *ctx, TSNode node) { if (ts_node_is_null(node)) return; const char *kind = ts_node_type(node); // Process statements for scope building c_process_statement(ctx, node); // --- Resolve call expressions --- if (strcmp(kind, "call_expression") == 0) { TSNode func_node = ts_node_child_by_field_name(node, "function", 8); if (!ts_node_is_null(func_node)) { const char *fk = ts_node_type(func_node); // field_expression: obj.method() or ptr->method() if (strcmp(fk, "field_expression") == 0) { TSNode arg_node = ts_node_child_by_field_name(func_node, "argument", 8); TSNode field_node = ts_node_child_by_field_name(func_node, "field", 5); if (!ts_node_is_null(arg_node) && !ts_node_is_null(field_node)) { char *field_name = c_node_text(ctx, field_node); if (field_name) { const CBMType *obj_type = c_eval_expr_type(ctx, arg_node); // Determine if arrow or dot bool is_arrow = false; uint32_t fnc = ts_node_child_count(func_node); for (uint32_t fi = 0; fi < fnc; fi++) { TSNode ch = ts_node_child(func_node, fi); if (!ts_node_is_named(ch)) { char *op = c_node_text(ctx, ch); if (op && strcmp(op, "->") == 0) { is_arrow = true; break; } } } const CBMType *base = is_arrow ? c_simplify_type(ctx, obj_type, true) : c_simplify_type(ctx, obj_type, false); const char *type_qn = type_to_qn(base); if (type_qn) { int arg_count = 0; const CBMType **arg_types = c_extract_call_arg_types(ctx, node, &arg_count); if (ctx->debug) fprintf(stderr, " [clsp] member call: type_qn=%s field=%s args=%d\n", type_qn, field_name, arg_count); // Use type-aware overload scoring const CBMRegisteredFunc *method = cbm_registry_lookup_method_by_types( ctx->registry, type_qn, field_name, arg_types, arg_count); // Fall back to c_lookup_member for base class traversal if (!method) method = c_lookup_member(ctx, type_qn, field_name); if (ctx->debug) fprintf(stderr, " [clsp] member call result: %s\n", method ? method->qualified_name : "NULL"); if (method) { const char *strategy = "lsp_type_dispatch"; // Check if resolved through base class — prefer derived override if (method->receiver_type && strcmp(method->receiver_type, type_qn) != 0) { const CBMRegisteredFunc *override_m = cbm_registry_lookup_method(ctx->registry, type_qn, field_name); if (override_m) { method = override_m; strategy = "lsp_virtual_dispatch"; } else { strategy = "lsp_base_dispatch"; } } else if (c_base_declares_member(ctx, type_qn, field_name)) { // Method resolved directly on type_qn but also // declared in a base → a derived override of an // inherited (virtual) method → polymorphic dispatch. strategy = "lsp_virtual_dispatch"; } // Check if through smart pointer if (is_arrow && obj_type->kind == CBM_TYPE_TEMPLATE && is_smart_ptr(obj_type->data.template_type.template_name)) strategy = "lsp_smart_ptr_dispatch"; c_emit_resolved_call(ctx, method->qualified_name, strategy, 0.95f); goto recurse; } } // TYPE_PARAM receiver: store as pending template call if (base && base->kind == CBM_TYPE_TYPE_PARAM && ctx->enclosing_func_qn && ctx->in_template) { int ac = 0; c_extract_call_arg_types(ctx, node, &ac); c_add_pending_template_call(ctx, ctx->enclosing_func_qn, base->data.type_param.name, field_name, ac); } // Unresolved if (cbm_type_is_unknown(obj_type)) { char *arg_text = c_node_text(ctx, arg_node); c_emit_unresolved_call(ctx, cbm_arena_sprintf(ctx->arena, "%s.%s", arg_text ? arg_text : "?", field_name), "unknown_receiver_type"); } else if (type_qn) { c_emit_unresolved_call( ctx, cbm_arena_sprintf(ctx->arena, "%s.%s", type_qn, field_name), "method_not_found"); } } } goto recurse; } // qualified_identifier: ns::func() or Class::static_method() // Also handles ns::func() where name child is template_function if (strcmp(fk, "qualified_identifier") == 0 || strcmp(fk, "scoped_identifier") == 0) { char *text = c_node_text(ctx, func_node); if (text) { const char *qn = c_build_qn(ctx, text); // Strip template args from last component only: // std.get → std.get, but Registry.init stays unchanged const char *angle = strchr(qn, '<'); if (angle) { // Only strip if no '.' follows the '<' (i.e., template is in the last // segment) const char *dot_after = strchr(angle, '.'); if (!dot_after) { size_t strip_len = (size_t)(angle - qn); char *stripped = (char *)cbm_arena_alloc(ctx->arena, strip_len + 1); memcpy(stripped, qn, strip_len); stripped[strip_len] = '\0'; qn = stripped; } else { // Strip <...> segment but keep what follows: Registry.init → // Registry.init const char *close = strchr(angle, '>'); if (close && close < dot_after) { size_t prefix_len = (size_t)(angle - qn); size_t suffix_len = strlen(dot_after); char *stripped = (char *)cbm_arena_alloc( ctx->arena, prefix_len + suffix_len + 1); memcpy(stripped, qn, prefix_len); memcpy(stripped + prefix_len, dot_after, suffix_len); stripped[prefix_len + suffix_len] = '\0'; qn = stripped; } } } int arg_count = 0; const CBMType **arg_types = c_extract_call_arg_types(ctx, node, &arg_count); // Module-prefixed first (shadows stdlib stubs), then bare QN const CBMRegisteredFunc *f = NULL; if (ctx->module_qn) { f = cbm_registry_lookup_func( ctx->registry, cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, qn)); } if (!f) f = cbm_registry_lookup_func(ctx->registry, qn); if (f) { c_emit_resolved_call(ctx, f->qualified_name, "lsp_scoped", 0.95f); goto recurse; } // Try as method (Class::method) with type-aware overload resolution const char *dot = strrchr(qn, '.'); if (dot) { size_t prefix_len = dot - qn; char *class_qn = (char *)cbm_arena_alloc(ctx->arena, prefix_len + 1); memcpy(class_qn, qn, prefix_len); class_qn[prefix_len] = '\0'; const CBMRegisteredFunc *m = cbm_registry_lookup_method_by_types( ctx->registry, class_qn, dot + 1, arg_types, arg_count); // Try with module prefix if (!m && ctx->module_qn) { const char *mod_class = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, class_qn); m = cbm_registry_lookup_method_by_types(ctx->registry, mod_class, dot + 1, arg_types, arg_count); } if (m) { c_emit_resolved_call(ctx, m->qualified_name, "lsp_scoped", 0.95f); goto recurse; } } // Namespace fallback: extract_defs may omit namespace from QN. // "utils.create_logger" → try "create_logger" and "mod.create_logger" if (dot && ctx->module_qn) { const char *bare_name = dot + 1; const CBMRegisteredFunc *nf = cbm_registry_lookup_func(ctx->registry, bare_name); if (!nf) { nf = cbm_registry_lookup_func( ctx->registry, cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, bare_name)); } if (nf) { c_emit_resolved_call(ctx, nf->qualified_name, "lsp_scoped", 0.90f); goto recurse; } } c_emit_unresolved_call(ctx, qn, "scoped_not_in_registry"); } goto recurse; } // template_function: func(args) or ns::func(args) if (strcmp(fk, "template_function") == 0) { TSNode name_node = ts_node_child_by_field_name(func_node, "name", 4); if (!ts_node_is_null(name_node)) { char *name = c_node_text(ctx, name_node); if (name) { const char *nk = ts_node_type(name_node); const CBMRegisteredFunc *f = NULL; if (strcmp(nk, "qualified_identifier") == 0 || strcmp(nk, "scoped_identifier") == 0) { const char *qn = c_build_qn(ctx, name); f = cbm_registry_lookup_func(ctx->registry, qn); if (!f && ctx->module_qn) { const char *mod_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, qn); f = cbm_registry_lookup_func(ctx->registry, mod_qn); } // Try as Class::method if (!f) { const char *dot = strrchr(qn, '.'); if (dot) { size_t plen = dot - qn; char *cls = (char *)cbm_arena_alloc(ctx->arena, plen + 1); memcpy(cls, qn, plen); cls[plen] = '\0'; const CBMRegisteredFunc *m = cbm_registry_lookup_method(ctx->registry, cls, dot + 1); if (!m && ctx->module_qn) { const char *mc = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, cls); m = cbm_registry_lookup_method(ctx->registry, mc, dot + 1); } if (m) f = m; } } } else { const char *fqn = c_resolve_name(ctx, name); if (fqn) f = cbm_registry_lookup_func(ctx->registry, fqn); } if (f) { c_emit_resolved_call(ctx, f->qualified_name, "lsp_template", 0.95f); // Resolve pending template calls at this call site if (ctx->pending_tc_count > 0 && f->type_param_names) { int ac = 0; const CBMType **at = c_extract_call_arg_types(ctx, node, &ac); if (at) c_resolve_pending_template_calls(ctx, f, at, ac); } } else { c_emit_unresolved_call(ctx, c_build_qn(ctx, name), "template_not_in_registry"); } } } goto recurse; } // Direct identifier call: func() if (strcmp(fk, "identifier") == 0) { char *name = c_node_text(ctx, func_node); if (name && !is_c_builtin_func(name)) { // Check function pointer target map FIRST (before scope type check) // because C function pointer declarators like int (*fp)(int) // parse as pointer(int) in scope, not as FUNC type. const char *fp_target = c_lookup_fp_target(ctx, name); if (fp_target) { // Distinguish DLL/dynamic resolution from static fp targets bool is_dll = (strncmp(fp_target, "external.", 9) == 0); // The textual callee is the pointer variable `name` (e.g. // `fp`), resolved to a differently named target. Pass it // as orig so the join matches the call on the pointer name. c_emit_resolved_call_orig(ctx, fp_target, name, is_dll ? "lsp_dll_resolve" : "lsp_func_ptr", is_dll ? 0.80f : 0.85f); goto recurse; } // Check if it's a variable with callable type const CBMType *var_type = cbm_scope_lookup(ctx->current_scope, name); if (!cbm_type_is_unknown(var_type)) { if (var_type->kind == CBM_TYPE_FUNC) { // Function type variable without tracked target — can't resolve goto recurse; } // Functor call: variable with operator() const CBMType *base = c_simplify_type(ctx, var_type, false); const char *type_qn = type_to_qn(base); if (type_qn) { const CBMRegisteredFunc *op = c_lookup_member(ctx, type_qn, "operator()"); if (op) { c_emit_resolved_call(ctx, op->qualified_name, "lsp_operator", 0.90f); goto recurse; } } } // Check if it's a type name (constructor call) const CBMType *type = c_resolve_name_to_type(ctx, name); if (type && type->kind == CBM_TYPE_NAMED) { const char *type_qn_str = type->data.named.qualified_name; const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, type_qn_str); if (rt && !rt->is_interface) { // Constructor call const char *ctor_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", type_qn_str, name); c_emit_resolved_call(ctx, ctor_qn, "lsp_constructor", 0.95f); goto recurse; } } // Regular function call const char *fqn = c_resolve_name(ctx, name); if (fqn) { // Check if this is implicit 'this' call const char *strategy = "lsp_direct"; if (ctx->enclosing_class_qn) { const CBMRegisteredFunc *m = cbm_registry_lookup_method( ctx->registry, ctx->enclosing_class_qn, name); if (m && strcmp(fqn, m->qualified_name) == 0) { strategy = "lsp_implicit_this"; } } c_emit_resolved_call(ctx, fqn, strategy, 0.95f); // Resolve pending template calls at this call site if (ctx->pending_tc_count > 0) { const CBMRegisteredFunc *called = cbm_registry_lookup_func(ctx->registry, fqn); if (called && called->type_param_names) { int ac = 0; const CBMType **at = c_extract_call_arg_types(ctx, node, &ac); if (at) c_resolve_pending_template_calls(ctx, called, at, ac); } } } else { // ADL: search namespaces of argument types const char *adl_qn = c_adl_resolve(ctx, name, node); if (adl_qn) { c_emit_resolved_call(ctx, adl_qn, "lsp_adl", 0.90f); } else { c_emit_unresolved_call(ctx, name, "function_not_in_registry"); } } } goto recurse; } } } // --- Constructor calls from declarations --- if (strcmp(kind, "declaration") == 0 && ctx->cpp_mode) { // Check for Foo x(args) or Foo x{args} patterns uint32_t nc = ts_node_named_child_count(node); const CBMType *decl_type = cbm_type_unknown(); bool has_type = false; for (uint32_t i = 0; i < nc; i++) { TSNode child = ts_node_named_child(node, i); const char *ck = ts_node_type(child); if (!has_type && (strcmp(ck, "type_identifier") == 0 || strcmp(ck, "scoped_type_identifier") == 0 || strcmp(ck, "template_type") == 0)) { decl_type = c_parse_type_node(ctx, child); has_type = true; } if (has_type && strcmp(ck, "init_declarator") == 0) { // Check if value is argument_list (constructor with parens) TSNode value = ts_node_child_by_field_name(child, "value", 5); if (!ts_node_is_null(value)) { const char *vk = ts_node_type(value); if (strcmp(vk, "argument_list") == 0 || strcmp(vk, "initializer_list") == 0) { const char *type_qn = type_to_qn(decl_type); if (type_qn) { const char *short_name = strrchr(type_qn, '.'); short_name = short_name ? short_name + 1 : type_qn; const char *last_colon = strrchr(type_qn, ':'); if (last_colon) short_name = last_colon + 1; const char *ctor_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", type_qn, short_name); c_emit_resolved_call(ctx, ctor_qn, "lsp_constructor", 0.90f); } } } } } } // --- new_expression: emit constructor --- if (strcmp(kind, "new_expression") == 0) { TSNode type_node = ts_node_child_by_field_name(node, "type", 4); if (ts_node_is_null(type_node)) { uint32_t nc = ts_node_named_child_count(node); for (uint32_t i = 0; i < nc; i++) { TSNode child = ts_node_named_child(node, i); const char *ck = ts_node_type(child); if (strcmp(ck, "type_identifier") == 0 || strcmp(ck, "template_type") == 0 || strcmp(ck, "scoped_type_identifier") == 0) { type_node = child; break; } } } if (!ts_node_is_null(type_node)) { const CBMType *t = c_parse_type_node(ctx, type_node); const char *type_qn = type_to_qn(t); if (type_qn) { const char *short_name = strrchr(type_qn, '.'); short_name = short_name ? short_name + 1 : type_qn; const char *ctor_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", type_qn, short_name); c_emit_resolved_call(ctx, ctor_qn, "lsp_constructor", 0.95f); } } } // --- delete_expression: emit destructor --- if (strcmp(kind, "delete_expression") == 0 && ctx->cpp_mode) { TSNode operand = ts_node_child_by_field_name(node, "argument", 8); if (ts_node_is_null(operand)) { if (ts_node_named_child_count(node) > 0) operand = ts_node_named_child(node, 0); } if (!ts_node_is_null(operand)) { const CBMType *ptr_type = c_eval_expr_type(ctx, operand); const CBMType *base = c_simplify_type(ctx, ptr_type, true); const char *type_qn = type_to_qn(base); if (type_qn) { const char *short_name = strrchr(type_qn, '.'); short_name = short_name ? short_name + 1 : type_qn; const char *dtor_qn = cbm_arena_sprintf(ctx->arena, "%s.~%s", type_qn, short_name); // The destructor callee QN (`T.~T`) is not textually available // from `delete p` — the call walk can only synthesize a call to // the operand text. Stash that operand text in `reason` so the // pipeline join binds the synthesized call via the reason gate. c_emit_resolved_call_orig(ctx, dtor_qn, c_node_text(ctx, operand), "lsp_destructor", 0.90f); } } } // --- Operator calls (C++ only) --- if (ctx->cpp_mode && strcmp(kind, "binary_expression") == 0) { TSNode left = ts_node_child_by_field_name(node, "left", 4); if (!ts_node_is_null(left)) { const CBMType *lhs_type = c_eval_expr_type(ctx, left); const CBMType *base = c_simplify_type(ctx, lhs_type, false); if (base && base->kind != CBM_TYPE_BUILTIN && !cbm_type_is_unknown(base)) { const char *type_qn = type_to_qn(base); if (type_qn) { // Extract operator for (uint32_t i = 0; i < ts_node_child_count(node); i++) { TSNode child = ts_node_child(node, i); if (!ts_node_is_named(child)) { char *op = c_node_text(ctx, child); if (op) { const char *op_name = cbm_arena_sprintf(ctx->arena, "operator%s", op); const CBMRegisteredFunc *m = c_lookup_member(ctx, type_qn, op_name); if (m) { c_emit_resolved_call(ctx, m->qualified_name, "lsp_operator", 0.90f); } } break; } } } } } } // --- Compound assignment operator calls (C++ only): a += b, a -= b, etc. --- if (ctx->cpp_mode && strcmp(kind, "assignment_expression") == 0) { TSNode left = ts_node_child_by_field_name(node, "left", 4); if (!ts_node_is_null(left)) { const CBMType *lhs_type = c_eval_expr_type(ctx, left); const CBMType *base = c_simplify_type(ctx, lhs_type, false); if (base && base->kind != CBM_TYPE_BUILTIN && !cbm_type_is_unknown(base)) { const char *type_qn = type_to_qn(base); if (type_qn) { // Extract operator (+=, -=, *=, /=, etc.) for (uint32_t i = 0; i < ts_node_child_count(node); i++) { TSNode child = ts_node_child(node, i); if (!ts_node_is_named(child)) { char *op = c_node_text(ctx, child); if (op && strlen(op) >= 2 && op[strlen(op) - 1] == '=') { // Compound assignment: +=, -=, *=, /=, %=, <<=, >>=, &=, |=, ^= const char *op_name = cbm_arena_sprintf(ctx->arena, "operator%s", op); const CBMRegisteredFunc *m = c_lookup_member(ctx, type_qn, op_name); if (m) { c_emit_resolved_call(ctx, m->qualified_name, "lsp_operator", 0.90f); } } break; } } } } } } // --- subscript_expression operator[] --- if (ctx->cpp_mode && strcmp(kind, "subscript_expression") == 0) { TSNode arg_node = ts_node_child_by_field_name(node, "argument", 8); if (!ts_node_is_null(arg_node)) { const CBMType *arr_type = c_eval_expr_type(ctx, arg_node); const CBMType *base = c_simplify_type(ctx, arr_type, false); if (base && base->kind != CBM_TYPE_BUILTIN && !cbm_type_is_unknown(base) && base->kind != CBM_TYPE_POINTER && base->kind != CBM_TYPE_SLICE) { const char *type_qn = type_to_qn(base); if (type_qn) { const CBMRegisteredFunc *m = c_lookup_member(ctx, type_qn, "operator[]"); if (m) { c_emit_resolved_call(ctx, m->qualified_name, "lsp_operator", 0.90f); } } } } } // --- unary_expression/pointer_expression: operator* / operator++ / operator-- on custom types // --- if (ctx->cpp_mode && (strcmp(kind, "unary_expression") == 0 || strcmp(kind, "pointer_expression") == 0 || strcmp(kind, "update_expression") == 0)) { TSNode operand = ts_node_child_by_field_name(node, "argument", 8); if (ts_node_is_null(operand)) operand = ts_node_child_by_field_name(node, "operand", 7); if (!ts_node_is_null(operand)) { const CBMType *op_type = c_eval_expr_type(ctx, operand); const CBMType *base = c_simplify_type(ctx, op_type, false); if (base && base->kind != CBM_TYPE_BUILTIN && !cbm_type_is_unknown(base) && base->kind != CBM_TYPE_POINTER) { const char *type_qn = type_to_qn(base); if (type_qn) { // Determine which operator const char *op_name = NULL; for (uint32_t ui = 0; ui < ts_node_child_count(node); ui++) { TSNode ch = ts_node_child(node, ui); if (!ts_node_is_named(ch)) { char *op = c_node_text(ctx, ch); if (!op) continue; if (strcmp(op, "*") == 0) op_name = "operator*"; else if (strcmp(op, "++") == 0) op_name = "operator++"; else if (strcmp(op, "--") == 0) op_name = "operator--"; else if (strcmp(op, "!") == 0) op_name = "operator!"; break; } } if (op_name) { const CBMRegisteredFunc *m = c_lookup_member(ctx, type_qn, op_name); if (m) { c_emit_resolved_call(ctx, m->qualified_name, "lsp_operator", 0.90f); } } } } } } // --- Copy/move constructor: Foo a = expr; where expr has Foo type --- if (ctx->cpp_mode && strcmp(kind, "declaration") == 0) { const CBMType *decl_type = c_parse_declaration_type(ctx, node); if (decl_type && decl_type->kind == CBM_TYPE_NAMED) { uint32_t dnc = ts_node_named_child_count(node); for (uint32_t di = 0; di < dnc; di++) { TSNode dchild = ts_node_named_child(node, di); if (strcmp(ts_node_type(dchild), "init_declarator") == 0) { TSNode val = ts_node_child_by_field_name(dchild, "value", 5); if (!ts_node_is_null(val)) { const char *vk = ts_node_type(val); // Skip if it's argument_list (already handled as constructor above) // or initializer_list (also handled) if (strcmp(vk, "argument_list") != 0 && strcmp(vk, "initializer_list") != 0) { const CBMType *val_type = c_eval_expr_type(ctx, val); const CBMType *val_base = c_simplify_type(ctx, val_type, false); // If assigning an object of the same type -> copy/move constructor const char *type_qn = type_to_qn(decl_type); const char *val_qn = type_to_qn(val_base); if (type_qn && val_qn && strcmp(type_qn, val_qn) == 0) { const char *short_name = strrchr(type_qn, '.'); short_name = short_name ? short_name + 1 : type_qn; const char *ctor_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", type_qn, short_name); c_emit_resolved_call(ctx, ctor_qn, "lsp_copy_constructor", 0.85f); } } } } } } } // --- Conversion operator: implicit bool/int conversion in conditions --- if (ctx->cpp_mode && (strcmp(kind, "if_statement") == 0 || strcmp(kind, "while_statement") == 0 || strcmp(kind, "do_statement") == 0)) { TSNode cond = ts_node_child_by_field_name(node, "condition", 9); if (!ts_node_is_null(cond)) { // The `condition` field is a `condition_clause` wrapping the `( expr )`; // unwrap it to the inner expression so its type evaluates (a clause // node has no type, so `if (obj)` would never resolve obj's type). if (strcmp(ts_node_type(cond), "condition_clause") == 0 && ts_node_named_child_count(cond) == 1) { cond = ts_node_named_child(cond, 0); } // If condition is a single expression of a custom type with operator bool const CBMType *cond_type = c_eval_expr_type(ctx, cond); const CBMType *base = c_simplify_type(ctx, cond_type, false); if (base && base->kind != CBM_TYPE_BUILTIN && !cbm_type_is_unknown(base) && base->kind != CBM_TYPE_POINTER) { const char *type_qn = type_to_qn(base); if (type_qn) { const CBMRegisteredFunc *m = c_lookup_member(ctx, type_qn, "operator bool"); if (m) { c_emit_resolved_call(ctx, m->qualified_name, "lsp_conversion", 0.85f); } } } } } // --- throw_statement: throw MyError("msg") → emit constructor call --- if (strcmp(kind, "throw_statement") == 0) { uint32_t tnc = ts_node_named_child_count(node); if (tnc > 0) { TSNode thrown = ts_node_named_child(node, 0); if (!ts_node_is_null(thrown)) { // If the thrown expression is a call_expression (constructor), // it will be resolved by the normal call_expression handler during recursion. // Just let the recursion handle it — no special emission needed. } } // throw; (rethrow) — no calls, just skip } // --- co_yield_statement: co_yield expr → resolve inner expression calls --- if (strcmp(kind, "co_yield_statement") == 0) { // co_yield's operand is processed by normal recursion into children } // --- co_return_statement: co_return expr → resolve inner expression calls --- if (strcmp(kind, "co_return_statement") == 0) { // co_return's operand is processed by normal recursion into children } // --- Designated initializer: {.field = value} --- if (strcmp(kind, "initializer_pair") == 0 || strcmp(kind, "field_designator") == 0) { // These appear inside initializer_list for aggregate init // We don't emit CALLS for field access, but we could track which struct is being // initialized This is primarily for completeness — no CALLS edge needed for field // designators } recurse:; // Push scope for blocks and control structures bool push_scope = (strcmp(kind, "compound_statement") == 0 || strcmp(kind, "if_statement") == 0 || strcmp(kind, "for_statement") == 0 || strcmp(kind, "for_range_loop") == 0 || strcmp(kind, "while_statement") == 0 || strcmp(kind, "do_statement") == 0 || strcmp(kind, "switch_statement") == 0 || strcmp(kind, "catch_clause") == 0 || strcmp(kind, "lambda_expression") == 0); if (push_scope) { ctx->current_scope = cbm_scope_push(ctx->arena, ctx->current_scope); } // Process catch clause parameter if (strcmp(kind, "catch_clause") == 0) { TSNode params = ts_node_child_by_field_name(node, "parameters", 10); if (!ts_node_is_null(params)) { uint32_t pnc = ts_node_named_child_count(params); for (uint32_t pi = 0; pi < pnc; pi++) { c_process_statement(ctx, ts_node_named_child(params, pi)); } } } // Process lambda init-captures: [captured = expr] binds 'captured' in lambda scope if (strcmp(kind, "lambda_expression") == 0) { uint32_t lnc = ts_node_named_child_count(node); for (uint32_t li = 0; li < lnc; li++) { TSNode child = ts_node_named_child(node, li); if (ts_node_is_null(child)) continue; if (strcmp(ts_node_type(child), "lambda_capture_specifier") == 0) { uint32_t cnc = ts_node_named_child_count(child); for (uint32_t ci = 0; ci < cnc; ci++) { TSNode cap = ts_node_named_child(child, ci); if (ts_node_is_null(cap)) continue; const char *ck = ts_node_type(cap); if (strcmp(ck, "lambda_capture_initializer") == 0) { // Init-capture: name = value // Try field-based access first (name field = 4 chars) TSNode name_node = ts_node_child_by_field_name(cap, "name", 4); TSNode val_node = ts_node_child_by_field_name(cap, "value", 5); // Fallback: positional — first named child is name, second is value if (ts_node_is_null(name_node) && ts_node_named_child_count(cap) >= 2) { name_node = ts_node_named_child(cap, 0); val_node = ts_node_named_child(cap, 1); } if (!ts_node_is_null(name_node) && !ts_node_is_null(val_node)) { char *cap_name = c_node_text(ctx, name_node); const CBMType *cap_type = c_eval_expr_type(ctx, val_node); if (cap_name && !cbm_type_is_unknown(cap_type)) { cbm_scope_bind(ctx->current_scope, cap_name, cap_type); } } } } break; // only one capture specifier per lambda } } } // Recurse into children via a cursor (O(n)); ts_node_child(node,i) is O(i) // in tree-sitter → O(n²) on a wide node. { TSTreeCursor cursor = ts_tree_cursor_new(node); if (ts_tree_cursor_goto_first_child(&cursor)) { do { c_resolve_calls_in_node(ctx, ts_tree_cursor_current_node(&cursor)); } while (ts_tree_cursor_goto_next_sibling(&cursor)); } ts_tree_cursor_delete(&cursor); } if (push_scope) { ctx->current_scope = cbm_scope_pop(ctx->current_scope); } } // ============================================================================ // Process function: set enclosing QN, bind params, walk body // ============================================================================ static void c_process_function(CLSPContext *ctx, TSNode func_node) { TSNode decl = ts_node_child_by_field_name(func_node, "declarator", 10); if (ts_node_is_null(decl)) return; // Extract function name from declarator chain char *func_name = NULL; const char *saved_class_qn = ctx->enclosing_class_qn; TSNode params_node = (TSNode){0}; // Navigate declarator to find name and parameters TSNode cur = decl; for (int depth = 0; depth < 10 && !ts_node_is_null(cur); depth++) { const char *dk = ts_node_type(cur); if (strcmp(dk, "function_declarator") == 0) { TSNode fdecl = ts_node_child_by_field_name(cur, "declarator", 10); params_node = ts_node_child_by_field_name(cur, "parameters", 10); cur = fdecl; continue; } if (strcmp(dk, "pointer_declarator") == 0 || strcmp(dk, "reference_declarator") == 0) { if (ts_node_named_child_count(cur) > 0) cur = ts_node_named_child(cur, ts_node_named_child_count(cur) - 1); else break; continue; } if (strcmp(dk, "qualified_identifier") == 0 || strcmp(dk, "scoped_identifier") == 0) { func_name = c_node_text(ctx, cur); // Check if this is a method (has Class:: prefix) TSNode scope_node = ts_node_child_by_field_name(cur, "scope", 5); if (!ts_node_is_null(scope_node)) { char *scope_text = c_node_text(ctx, scope_node); if (scope_text) { const char *scope_qn = c_build_qn(ctx, scope_text); // Try as a type for enclosing class const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, scope_qn); if (rt) { ctx->enclosing_class_qn = scope_qn; } else if (ctx->module_qn) { const char *fqn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, scope_qn); rt = cbm_registry_lookup_type(ctx->registry, fqn); if (rt) ctx->enclosing_class_qn = fqn; } } } break; } if (strcmp(dk, "identifier") == 0) { func_name = c_node_text(ctx, cur); break; } if (strcmp(dk, "field_identifier") == 0) { func_name = c_node_text(ctx, cur); break; } // Destructor if (strcmp(dk, "destructor_name") == 0) { func_name = c_node_text(ctx, cur); break; } break; } if (!func_name || !func_name[0]) return; // Build enclosing function QN const char *func_qn = c_build_qn(ctx, func_name); // For a method defined INLINE inside its class body, func_name is a bare // identifier ("compute") and enclosing_class_qn was inherited from // c_process_class (saved_class_qn == enclosing_class_qn). The textual // extractor and the registry qualify the method as module.Class.method, so // building func_qn as module.method here (no class) made the LSP-resolved // call's caller_qn disagree with the textual call's enclosing_func_qn and // cbm_pipeline_find_lsp_resolution never joined them — every in-method call // (e.g. lsp_implicit_this) silently lost its type-aware strategy. Prepend // the enclosing class, mirroring the Go receiver-QN fix. Out-of-line // definitions (Widget::compute) already carry the class in func_name (a // qualified_identifier), so c_build_qn produces module.Class.method and the // enclosing_class_qn was set HERE (saved_class_qn != enclosing_class_qn); // skip those, and skip names that already contain the class scope. if (ctx->enclosing_class_qn && saved_class_qn == ctx->enclosing_class_qn && !strchr(func_qn, '.')) { func_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->enclosing_class_qn, func_qn); } else if (ctx->enclosing_class_qn && saved_class_qn != ctx->enclosing_class_qn && strchr(func_qn, '.')) { /* Out-of-line method `Class::method`: c_build_qn yields the bare * "Class.method" (no module) — the class scope was resolved HERE to the * full module-qualified class QN (saved_class_qn != enclosing_class_qn). * Rebuild as . so the caller_qn matches the * def walk and call-scope QN, which qualify out-of-line methods the same * way. Without this the caller_qn stays "Class.method", the exact-equality * lsp_resolve join misses, and the LSP rescue is discarded (gap #5a). */ const char *dot = strrchr(func_qn, '.'); func_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->enclosing_class_qn, dot + 1); } else if (!strchr(func_qn, '.')) { /* A free function in a namespace is qualified by the namespace scope * (current_namespace is module_qn.ns), matching the def QN the extractor * now produces; outside any namespace this falls back to the file module * so non-namespaced free functions are unchanged. */ const char *scope = ctx->current_namespace ? ctx->current_namespace : ctx->module_qn; if (scope) { func_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", scope, func_qn); } } ctx->enclosing_func_qn = func_qn; // If inside a template, attach type_param_names to the registered function // so pending template calls can be resolved at call sites. NEVER do this // against the shared Tier-2 cross registry: resolve workers walk files // concurrently, so the write races other readers AND stores a pointer to // THIS worker's per-file arena into shared state — once that arena is // recycled the registry holds dangling memory (intermittent SIGSEGV // indexing bitcoin). Cross-phase template deduction then relies on the // positional fallback, which is graceful degradation. if (ctx->in_template && ctx->template_param_count > 0 && !ctx->registry_shared) { // Find the registered function and set type_param_names for (int ri = 0; ri < ((CBMTypeRegistry *)ctx->registry)->func_count; ri++) { CBMRegisteredFunc *rf = &((CBMTypeRegistry *)ctx->registry)->funcs[ri]; if (strcmp(rf->qualified_name, func_qn) == 0 && !rf->type_param_names) { const char **tpn = (const char **)cbm_arena_alloc( ctx->arena, (ctx->template_param_count + 1) * sizeof(const char *)); for (int ti = 0; ti < ctx->template_param_count; ti++) tpn[ti] = ctx->template_param_names[ti]; tpn[ctx->template_param_count] = NULL; rf->type_param_names = tpn; break; } } } // Push function scope CBMScope *saved_scope = ctx->current_scope; ctx->current_scope = cbm_scope_push(ctx->arena, ctx->current_scope); // Bind 'this' if in a method if (ctx->enclosing_class_qn) { cbm_scope_bind( ctx->current_scope, "this", cbm_type_pointer(ctx->arena, cbm_type_named(ctx->arena, ctx->enclosing_class_qn))); } // Bind parameters and count defaults for min_params int total_params = 0, defaulted_params = 0; if (!ts_node_is_null(params_node)) { uint32_t pnc = ts_node_named_child_count(params_node); for (uint32_t i = 0; i < pnc; i++) { TSNode param = ts_node_named_child(params_node, i); if (!ts_node_is_null(param)) { const char *pk = ts_node_type(param); if (strcmp(pk, "parameter_declaration") == 0 || strcmp(pk, "optional_parameter_declaration") == 0) { total_params++; TSNode dv = ts_node_child_by_field_name(param, "default_value", 13); if (!ts_node_is_null(dv)) defaulted_params++; } c_process_statement(ctx, param); } } } // Set min_params on the registered function (for default-arg overload matching) if (total_params > 0 && defaulted_params > 0) { for (int ri = 0; ri < ((CBMTypeRegistry *)ctx->registry)->func_count; ri++) { CBMRegisteredFunc *rf = &((CBMTypeRegistry *)ctx->registry)->funcs[ri]; if (strcmp(rf->qualified_name, func_qn) == 0 && rf->min_params < 0) { rf->min_params = total_params - defaulted_params; break; } } } // Walk function body TSNode body = ts_node_child_by_field_name(func_node, "body", 4); if (!ts_node_is_null(body)) { c_resolve_calls_in_node(ctx, body); } // Restore ctx->current_scope = saved_scope; ctx->enclosing_class_qn = saved_class_qn; } // ============================================================================ // Process namespace // ============================================================================ // Process a top-level or nested declaration within a namespace/class body, // handling template_declaration wrapping. static void c_process_body_child(CLSPContext *ctx, TSNode child) { if (ts_node_is_null(child)) return; const char *ck = ts_node_type(child); if (strcmp(ck, "function_definition") == 0) { c_process_function(ctx, child); } else if (strcmp(ck, "namespace_definition") == 0) { c_process_namespace(ctx, child); } else if (strcmp(ck, "class_specifier") == 0 || strcmp(ck, "struct_specifier") == 0) { c_process_class(ctx, child); } else if (strcmp(ck, "declaration") == 0) { // declaration may contain class/struct specifier: class Foo { ... }; // Extract and process the class before resolving calls. bool has_class = false; uint32_t dnc = ts_node_named_child_count(child); for (uint32_t di = 0; di < dnc; di++) { TSNode dch = ts_node_named_child(child, di); const char *dk = ts_node_type(dch); if (strcmp(dk, "class_specifier") == 0 || strcmp(dk, "struct_specifier") == 0) { c_process_class(ctx, dch); has_class = true; } // Register bare function declarations: ReturnType func_name(params); // Unwrap reference_declarator/pointer_declarator to find function_declarator { TSNode func_decl_node = dch; bool is_ref_ret = false; bool is_ptr_ret = false; if (strcmp(dk, "reference_declarator") == 0 && ts_node_named_child_count(dch) > 0) { TSNode inner = ts_node_named_child(dch, 0); if (strcmp(ts_node_type(inner), "function_declarator") == 0) { func_decl_node = inner; is_ref_ret = true; } } else if (strcmp(dk, "pointer_declarator") == 0 && ts_node_named_child_count(dch) > 0) { TSNode inner = ts_node_named_child(dch, ts_node_named_child_count(dch) - 1); if (strcmp(ts_node_type(inner), "function_declarator") == 0) { func_decl_node = inner; is_ptr_ret = true; } } if (strcmp(ts_node_type(func_decl_node), "function_declarator") == 0) { TSNode fn_name = ts_node_child_by_field_name(func_decl_node, "declarator", 10); if (!ts_node_is_null(fn_name)) { char *fname = c_node_text(ctx, fn_name); if (fname && fname[0]) { // Parse return type from declaration's type specifier const CBMType *ret_type = NULL; for (uint32_t ri = 0; ri < dnc; ri++) { TSNode rch = ts_node_named_child(child, ri); const char *rk = ts_node_type(rch); if (strcmp(rk, "function_declarator") != 0 && strcmp(rk, "identifier") != 0 && strcmp(rk, "pointer_declarator") != 0 && strcmp(rk, "reference_declarator") != 0) { ret_type = c_parse_type_node(ctx, rch); if (!cbm_type_is_unknown(ret_type)) break; } } if (ret_type && !cbm_type_is_unknown(ret_type)) { if (is_ref_ret) ret_type = cbm_type_reference(ctx->arena, ret_type); if (is_ptr_ret) ret_type = cbm_type_pointer(ctx->arena, ret_type); const char *func_qn = c_build_qn(ctx, fname); if (ctx->module_qn && !strchr(func_qn, '.')) func_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, func_qn); // Use namespace if available if (ctx->current_namespace && !strstr(func_qn, ctx->current_namespace)) func_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->current_namespace, fname); // Only register if not already registered. On the // shared Tier-2 registry, skip entirely: it is // finalized + read-only, the def is already present // from the project-wide build, and the lookup + add // would otherwise hit/grow the post-finalize tail -> // O(files*defs) on large C codebases (the Linux-kernel // full-index hang) plus a cross-worker heap race. if (!ctx->registry_shared && !cbm_registry_lookup_func(ctx->registry, func_qn)) { const CBMType **rets = (const CBMType **)cbm_arena_alloc( ctx->arena, 2 * sizeof(const CBMType *)); rets[0] = ret_type; rets[1] = NULL; CBMRegisteredFunc rf = {0}; rf.qualified_name = func_qn; rf.short_name = fname; rf.signature = cbm_type_func(ctx->arena, NULL, NULL, rets); // Copy template params if in template context if (ctx->in_template && ctx->template_param_count > 0) { const char **tpn = (const char **)cbm_arena_alloc( ctx->arena, (ctx->template_param_count + 1) * sizeof(char *)); for (int tp = 0; tp < ctx->template_param_count; tp++) tpn[tp] = ctx->template_param_names[tp]; tpn[ctx->template_param_count] = NULL; rf.type_param_names = tpn; } cbm_registry_add_func((CBMTypeRegistry *)ctx->registry, rf); } } } } } } } if (!has_class) { c_resolve_calls_in_node(ctx, child); } } else if (strcmp(ck, "concept_definition") == 0) { // C++20 concept: concept Sortable = requires(T a) { a.sort(); }; // No CALLS edges from concept definitions — they are constraints, not runtime calls. // Skip processing to avoid false positive CALLS from requires-expressions. return; } else if (strcmp(ck, "template_declaration") == 0) { // Save template state, parse params for defaults, set flag bool saved_template = ctx->in_template; const char **saved_tpn = ctx->template_param_names; const CBMType **saved_tpd = ctx->template_param_defaults; int saved_tpc = ctx->template_param_count; ctx->in_template = true; c_parse_template_params(ctx, child); uint32_t tnc = ts_node_named_child_count(child); for (uint32_t ti = 0; ti < tnc; ti++) { TSNode inner = ts_node_named_child(child, ti); c_process_body_child(ctx, inner); } ctx->in_template = saved_template; ctx->template_param_names = saved_tpn; ctx->template_param_defaults = saved_tpd; ctx->template_param_count = saved_tpc; } else { c_resolve_calls_in_node(ctx, child); } } static void c_process_namespace(CLSPContext *ctx, TSNode ns_node) { const char *saved_ns = ctx->current_namespace; TSNode name_node = ts_node_child_by_field_name(ns_node, "name", 4); if (!ts_node_is_null(name_node)) { char *ns_name = c_node_text(ctx, name_node); if (ns_name) { if (saved_ns) { ctx->current_namespace = cbm_arena_sprintf(ctx->arena, "%s.%s", saved_ns, ns_name); } else if (ctx->module_qn) { ctx->current_namespace = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, ns_name); } else { ctx->current_namespace = ns_name; } } } TSNode body = ts_node_child_by_field_name(ns_node, "body", 4); if (!ts_node_is_null(body)) { // Cursor walk (O(n)); namespace bodies can be very wide. TSTreeCursor cursor = ts_tree_cursor_new(body); if (ts_tree_cursor_goto_first_child(&cursor)) { do { c_process_body_child(ctx, ts_tree_cursor_current_node(&cursor)); } while (ts_tree_cursor_goto_next_sibling(&cursor)); } ts_tree_cursor_delete(&cursor); } ctx->current_namespace = saved_ns; } // ============================================================================ // Process class/struct: process method bodies // ============================================================================ static void c_process_class(CLSPContext *ctx, TSNode class_node) { const char *saved_class = ctx->enclosing_class_qn; const char **saved_tpn = ctx->template_param_names; const CBMType **saved_tpd = ctx->template_param_defaults; int saved_tpc = ctx->template_param_count; char *class_name = NULL; TSNode name_node = ts_node_child_by_field_name(class_node, "name", 4); if (!ts_node_is_null(name_node)) { class_name = c_node_text(ctx, name_node); if (class_name) { const char *class_qn; if (ctx->current_namespace) { class_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->current_namespace, class_name); } else if (ctx->module_qn) { class_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, class_name); } else { class_qn = class_name; } ctx->enclosing_class_qn = class_qn; // Store template param names on the registered type (for substitution) if (ctx->in_template && ctx->template_param_names && ctx->template_param_count > 0) { CBMRegisteredType *rt = NULL; for (int ri = 0; ri < ((CBMTypeRegistry *)ctx->registry)->type_count; ri++) { if (strcmp(((CBMTypeRegistry *)ctx->registry)->types[ri].qualified_name, class_qn) == 0) { rt = &((CBMTypeRegistry *)ctx->registry)->types[ri]; break; } } if (rt && !rt->type_param_names) { const char **tpn = (const char **)cbm_arena_alloc( ctx->arena, (ctx->template_param_count + 1) * sizeof(const char *)); for (int pi = 0; pi < ctx->template_param_count; pi++) tpn[pi] = ctx->template_param_names[pi]; tpn[ctx->template_param_count] = NULL; rt->type_param_names = tpn; } } } } // CRTP detection: class Derived : Base // When a base class template argument matches the class name, // bind that template parameter to the derived class type. if (class_name && ctx->enclosing_class_qn) { // Walk base_class_clause children looking for template base classes uint32_t cnc = ts_node_named_child_count(class_node); for (uint32_t ci = 0; ci < cnc; ci++) { TSNode child = ts_node_named_child(class_node, ci); if (ts_node_is_null(child)) continue; const char *ck = ts_node_type(child); if (strcmp(ck, "base_class_clause") != 0) continue; // Iterate base specifiers uint32_t bnc = ts_node_named_child_count(child); for (uint32_t bi = 0; bi < bnc; bi++) { TSNode base_spec = ts_node_named_child(child, bi); if (ts_node_is_null(base_spec)) continue; // Look for template_type as base: Base TSNode type_node = base_spec; if (strcmp(ts_node_type(base_spec), "access_specifier") == 0) continue; // The base type might be directly a template_type or wrapped if (strcmp(ts_node_type(type_node), "template_type") != 0) { // Try walking children for template_type uint32_t snc = ts_node_named_child_count(type_node); bool found = false; for (uint32_t si = 0; si < snc; si++) { TSNode sc = ts_node_named_child(type_node, si); if (strcmp(ts_node_type(sc), "template_type") == 0) { type_node = sc; found = true; break; } } if (!found) continue; } // type_node is template_type: check arguments for CRTP TSNode targs = ts_node_child_by_field_name(type_node, "arguments", 9); if (ts_node_is_null(targs)) continue; // Check each template argument for CRTP pattern uint32_t tanc = ts_node_named_child_count(targs); for (uint32_t ti = 0; ti < tanc; ti++) { TSNode ta = ts_node_named_child(targs, ti); if (ts_node_is_null(ta)) continue; TSNode ta_inner = ta; // type_descriptor wraps the actual type if (strcmp(ts_node_type(ta), "type_descriptor") == 0 && ts_node_named_child_count(ta) > 0) { ta_inner = ts_node_named_child(ta, 0); } char *arg_text = c_node_text(ctx, ta_inner); if (arg_text && strcmp(arg_text, class_name) == 0) { // CRTP detected! Map template param at position ti to derived class // For now, bind using positional names (T0, T1, ...) // Also check existing template params if (ctx->template_param_names) { for (int pi = 0; pi < ctx->template_param_count; pi++) { if (ctx->template_param_names[pi] && ctx->template_param_defaults && !ctx->template_param_defaults[pi]) { // Bind unbound template param to derived class // Only bind the first unbound one at position ti if (pi == (int)ti) { ctx->template_param_defaults[pi] = cbm_type_named(ctx->arena, ctx->enclosing_class_qn); break; } } } } break; // found CRTP for this base } } } break; // only one base_class_clause } } TSNode body = ts_node_child_by_field_name(class_node, "body", 4); if (!ts_node_is_null(body)) { // Pre-pass: register method declarations (no body) as methods in registry. // This allows template return type substitution for methods like T& value(); if (ctx->enclosing_class_qn) { uint32_t bkn = 0; TSNode *bkids = cbm_lsp_collect_children(ctx->arena, body, &bkn); for (uint32_t i = 0; i < bkn; i++) { TSNode child = bkids[i]; if (!ts_node_is_named(child)) continue; const char *ck = ts_node_type(child); // field_declaration, declaration, or function_definition = method if (strcmp(ck, "field_declaration") != 0 && strcmp(ck, "declaration") != 0 && strcmp(ck, "function_definition") != 0) continue; // Parse return type from type specifier const CBMType *ret_type = c_parse_declaration_type(ctx, child); // Walk declarator chain to find function_declarator + method name uint32_t dnc = ts_node_named_child_count(child); for (uint32_t di = 0; di < dnc; di++) { TSNode decl = ts_node_named_child(child, di); // Navigate through reference_declarator, pointer_declarator to // function_declarator TSNode cur = decl; const CBMType *actual_ret = ret_type; for (int depth = 0; depth < 5 && !ts_node_is_null(cur); depth++) { const char *dk = ts_node_type(cur); if (strcmp(dk, "function_declarator") == 0) { // Found method declaration — extract name TSNode name_node = ts_node_child_by_field_name(cur, "declarator", 10); if (ts_node_is_null(name_node)) break; char *method_name = NULL; const char *nk = ts_node_type(name_node); if (strcmp(nk, "field_identifier") == 0 || strcmp(nk, "identifier") == 0) { method_name = c_node_text(ctx, name_node); } else if (strcmp(nk, "destructor_name") == 0 || strcmp(nk, "operator_name") == 0) { method_name = c_node_text(ctx, name_node); } if (!method_name || !method_name[0]) break; const char *method_qn = cbm_arena_sprintf( ctx->arena, "%s.%s", ctx->enclosing_class_qn, method_name); // Check if already registered — if so, upgrade return type // if pre-pass has a more specific type (e.g., TEMPLATE vs NAMED) // Check both full QN and short QN (extract_defs may use shorter) const CBMRegisteredFunc *existing = cbm_registry_lookup_method( ctx->registry, ctx->enclosing_class_qn, method_name); if (!existing && ctx->module_qn) { // Try without module prefix: "test.main.std.X" -> "std.X" const char *class_qn = ctx->enclosing_class_qn; size_t mod_len = strlen(ctx->module_qn); if (strncmp(class_qn, ctx->module_qn, mod_len) == 0 && class_qn[mod_len] == '.') { existing = cbm_registry_lookup_method( ctx->registry, class_qn + mod_len + 1, method_name); } } if (existing) { // Upgrade: if existing return type is NAMED but we have // TEMPLATE/TYPE_PARAM/POINTER(TYPE_PARAM), update in-place bool should_upgrade = false; if (existing->signature && existing->signature->kind == CBM_TYPE_FUNC && existing->signature->data.func.return_types && existing->signature->data.func.return_types[0]) { int ek = existing->signature->data.func.return_types[0]->kind; int nk2 = actual_ret ? actual_ret->kind : CBM_TYPE_UNKNOWN; // Upgrade if existing is NAMED and new is more specific if (ek == CBM_TYPE_NAMED && (nk2 == CBM_TYPE_TEMPLATE || nk2 == CBM_TYPE_TYPE_PARAM || nk2 == CBM_TYPE_POINTER || nk2 == CBM_TYPE_REFERENCE)) should_upgrade = true; } if (should_upgrade) { // Update existing entry's signature return type const CBMType **new_rets = (const CBMType **)cbm_arena_alloc( ctx->arena, 2 * sizeof(const CBMType *)); new_rets[0] = actual_ret; new_rets[1] = NULL; CBMRegisteredFunc *mut = (CBMRegisteredFunc *)existing; mut->signature = cbm_type_func(ctx->arena, NULL, NULL, new_rets); } break; } // Build return type with ref/ptr wrapping const CBMType **rets = (const CBMType **)cbm_arena_alloc( ctx->arena, 2 * sizeof(const CBMType *)); rets[0] = actual_ret; rets[1] = NULL; CBMRegisteredFunc rf; memset(&rf, 0, sizeof(rf)); rf.qualified_name = method_qn; rf.short_name = method_name; rf.receiver_type = ctx->enclosing_class_qn; rf.signature = cbm_type_func(ctx->arena, NULL, NULL, rets); rf.min_params = -1; cbm_registry_add_func((CBMTypeRegistry *)ctx->registry, rf); break; } else if (strcmp(dk, "reference_declarator") == 0) { actual_ret = cbm_type_reference(ctx->arena, actual_ret); cur = ts_node_named_child_count(cur) > 0 ? ts_node_named_child(cur, 0) : (TSNode){0}; } else if (strcmp(dk, "pointer_declarator") == 0) { actual_ret = cbm_type_pointer(ctx->arena, actual_ret); cur = ts_node_named_child_count(cur) > 0 ? ts_node_named_child(cur, 0) : (TSNode){0}; } else { break; } } } } } uint32_t bkn = 0; TSNode *bkids = cbm_lsp_collect_children(ctx->arena, body, &bkn); for (uint32_t i = 0; i < bkn; i++) { TSNode child = bkids[i]; if (!ts_node_is_named(child)) continue; c_process_body_child(ctx, child); } } ctx->enclosing_class_qn = saved_class; ctx->template_param_names = saved_tpn; ctx->template_param_defaults = saved_tpd; ctx->template_param_count = saved_tpc; } // Need forward declaration since c_process_namespace and c_process_class reference each other // Already defined above via forward declaration pattern — both reference c_resolve_calls_in_node // ============================================================================ // Process file: top-level walk // ============================================================================ __attribute__((no_sanitize("address"))) void c_lsp_process_file(CLSPContext *ctx, TSNode root) { if (ts_node_is_null(root)) return; // Collect top-level children once (O(n)); both passes reuse the array. // Indexing ts_node_child(root,i) per iteration is O(i) → O(n²) on a wide root. uint32_t kn = 0; TSNode *kids = cbm_lsp_collect_children(ctx->arena, root, &kn); TSNode child; // Hoisted: prevents ASan stack-use-after-scope between passes TSNode inner; // Pass 1: process using declarations and global variables for (uint32_t i = 0; i < kn; i++) { child = kids[i]; const char *ck = ts_node_type(child); if (strcmp(ck, "using_declaration") == 0 || strcmp(ck, "alias_declaration") == 0 || strcmp(ck, "type_definition") == 0 || strcmp(ck, "namespace_alias_definition") == 0) { c_process_statement(ctx, child); } else if (strcmp(ck, "declaration") == 0) { c_process_statement(ctx, child); } else if (strcmp(ck, "template_declaration") == 0) { // template using Vec = std::vector; // Unwrap template_declaration to process inner alias/typedef uint32_t tnc = ts_node_named_child_count(child); for (uint32_t ti = 0; ti < tnc; ti++) { inner = ts_node_named_child(child, ti); const char *ik = ts_node_type(inner); if (strcmp(ik, "alias_declaration") == 0 || strcmp(ik, "type_definition") == 0) { c_process_statement(ctx, inner); } } } } // Pass 2: process functions, namespaces, classes, templates for (uint32_t i = 0; i < kn; i++) { child = kids[i]; c_process_body_child(ctx, child); } // Release the per-file negative-lookup memo (malloc-owned; only allocated in // shared-registry mode). No-op when it was never populated. c_neg_memo_free(ctx); } // ============================================================================ // Parse C/C++ return type text into CBMType (for cross-file defs) // ============================================================================ static const CBMType *c_parse_return_type_text(CBMArena *a, const char *text, const char *module_qn) { if (!text || !text[0]) return cbm_type_unknown(); // Skip const/volatile qualifiers while (strncmp(text, "const ", 6) == 0) text += 6; while (strncmp(text, "volatile ", 9) == 0) text += 9; // Pointer size_t len = strlen(text); if (len > 0 && text[len - 1] == '*') { char *inner = cbm_arena_strndup(a, text, len - 1); // Trim trailing space size_t ilen = strlen(inner); while (ilen > 0 && inner[ilen - 1] == ' ') inner[--ilen] = '\0'; return cbm_type_pointer(a, c_parse_return_type_text(a, inner, module_qn)); } // Reference if (len > 0 && text[len - 1] == '&') { char *inner = cbm_arena_strndup(a, text, len - 1); size_t ilen = strlen(inner); while (ilen > 0 && inner[ilen - 1] == ' ') inner[--ilen] = '\0'; // Check for rvalue ref if (ilen > 0 && inner[ilen - 1] == '&') { inner[ilen - 1] = '\0'; while (ilen > 1 && inner[ilen - 2] == ' ') inner[--ilen - 1] = '\0'; return cbm_type_rvalue_ref(a, c_parse_return_type_text(a, inner, module_qn)); } return cbm_type_reference(a, c_parse_return_type_text(a, inner, module_qn)); } // decltype(expr) — extract the type from the expression if (strncmp(text, "decltype(", 9) == 0 && len > 10 && text[len - 1] == ')') { char *inner = cbm_arena_strndup(a, text + 9, len - 10); size_t ilen = strlen(inner); // decltype(auto) — auto deduction, can't resolve statically if (strcmp(inner, "auto") == 0) return cbm_type_unknown(); // decltype(TypeName()) — constructor call → type is TypeName if (ilen >= 2 && inner[ilen - 1] == ')' && inner[ilen - 2] == '(') { inner[ilen - 2] = '\0'; return c_parse_return_type_text(a, inner, module_qn); } // decltype(expr) — try to interpret as type name (covers decltype(x) where x is a type) return c_parse_return_type_text(a, inner, module_qn); } // Builtins if (is_c_builtin_type(text)) return cbm_type_builtin(a, text); // Template type parameters: only T, U, V, K, W (common conventions) or T1-T9. // Do NOT treat all single uppercase letters as type params — A, B, C, etc. // are legitimate class names (especially in C++ code with short names). if (len == 1 && (text[0] == 'T' || text[0] == 'U' || text[0] == 'V' || text[0] == 'K' || text[0] == 'W')) { return cbm_type_type_param(a, text); } if (len == 2 && text[0] == 'T' && text[1] >= '1' && text[1] <= '9') { return cbm_type_type_param(a, text); } // Template type: Name { const char *angle = strchr(text, '<'); if (angle && len > 0 && text[len - 1] == '>') { // Extract template name size_t name_len = (size_t)(angle - text); char *tmpl_name = cbm_arena_strndup(a, text, name_len); while (name_len > 0 && tmpl_name[name_len - 1] == ' ') tmpl_name[--name_len] = '\0'; // Replace :: with . for (char *p = tmpl_name; *p; p++) { if (p[0] == ':' && p[1] == ':') { p[0] = '.'; memmove(p + 1, p + 2, strlen(p + 2) + 1); } } // Extract args between < and > const char *args_start = angle + 1; size_t args_len = (size_t)((text + len - 1) - args_start); char *args_text = cbm_arena_strndup(a, args_start, args_len); // Split by comma at nesting depth 0 const CBMType *arg_types[16]; int arg_count = 0; int depth = 0; const char *arg_begin = args_text; for (const char *p = args_text;; p++) { if (*p == '<') depth++; else if (*p == '>') depth--; if ((*p == ',' && depth == 0) || *p == '\0') { if (arg_count < 16) { size_t alen = (size_t)(p - arg_begin); char *arg = cbm_arena_strndup(a, arg_begin, alen); // Trim whitespace while (*arg == ' ') arg++; size_t al = strlen(arg); while (al > 0 && arg[al - 1] == ' ') arg[--al] = '\0'; if (arg[0]) { arg_types[arg_count++] = c_parse_return_type_text(a, arg, module_qn); } } if (*p == '\0') break; arg_begin = p + 1; } } // Qualify with module if unqualified const char *qname = tmpl_name; if (!strchr(tmpl_name, '.') && module_qn && module_qn[0]) { qname = cbm_arena_sprintf(a, "%s.%s", module_qn, tmpl_name); } return cbm_type_template(a, qname, arg_types, arg_count); } } // Qualified name (has ::) if (strstr(text, "::")) { // Replace :: with . char *qn = cbm_arena_strdup(a, text); for (char *p = qn; *p; p++) { if (p[0] == ':' && p[1] == ':') { p[0] = '.'; memmove(p + 1, p + 2, strlen(p + 2) + 1); } } return cbm_type_named(a, qn); } // Named type: qualify with module if (module_qn && module_qn[0]) { return cbm_type_named(a, cbm_arena_sprintf(a, "%s.%s", module_qn, text)); } return cbm_type_named(a, text); } // ============================================================================ // Entry point: single-file LSP // ============================================================================ void cbm_run_c_lsp(CBMArena *arena, CBMFileResult *result, const char *source, int source_len, TSNode root, bool cpp_mode) { CBMTypeRegistry reg; cbm_registry_init(®, arena); // Register stdlib cbm_c_stdlib_register(®, arena); if (cpp_mode) cbm_cpp_stdlib_register(®, arena); const char *module_qn = result->module_qn; // Register file's own definitions for (int i = 0; i < result->defs.count; i++) { CBMDefinition *d = &result->defs.items[i]; if (!d->qualified_name || !d->name) continue; if (d->label && (strcmp(d->label, "Class") == 0 || strcmp(d->label, "Type") == 0)) { CBMRegisteredType rt; memset(&rt, 0, sizeof(rt)); rt.qualified_name = d->qualified_name; rt.short_name = d->name; // Base classes if (d->base_classes) { int bc_count = 0; while (d->base_classes[bc_count]) bc_count++; if (bc_count > 0) { const char **embedded = (const char **)cbm_arena_alloc( arena, (bc_count + 1) * sizeof(const char *)); for (int j = 0; j < bc_count; j++) { const char *bc = d->base_classes[j]; // Check if already qualified if (strchr(bc, '.') || strstr(bc, "::")) { embedded[j] = bc; } else { embedded[j] = cbm_arena_sprintf(arena, "%s.%s", module_qn, bc); } } embedded[bc_count] = NULL; rt.embedded_types = embedded; } } cbm_registry_add_type(®, rt); } // Field definitions → populate field_names/field_types on parent type if (d->label && strcmp(d->label, "Field") == 0 && d->parent_class && d->return_type) { // Find or add the parent type in registry CBMRegisteredType *parent_rt = NULL; for (int ri = 0; ri < reg.type_count; ri++) { if (strcmp(reg.types[ri].qualified_name, d->parent_class) == 0) { parent_rt = ®.types[ri]; break; } } if (parent_rt) { // Count existing fields int existing = 0; if (parent_rt->field_names) { while (parent_rt->field_names[existing]) existing++; } // Add this field const char **new_fnames = (const char **)cbm_arena_alloc(arena, (existing + 2) * sizeof(const char *)); const CBMType **new_ftypes = (const CBMType **)cbm_arena_alloc( arena, (existing + 2) * sizeof(const CBMType *)); if (new_fnames && new_ftypes) { for (int j = 0; j < existing; j++) { new_fnames[j] = parent_rt->field_names[j]; new_ftypes[j] = parent_rt->field_types ? parent_rt->field_types[j] : NULL; } new_fnames[existing] = d->name; new_ftypes[existing] = c_parse_return_type_text(arena, d->return_type, module_qn); new_fnames[existing + 1] = NULL; new_ftypes[existing + 1] = NULL; parent_rt->field_names = new_fnames; parent_rt->field_types = new_ftypes; } } } if (d->label && (strcmp(d->label, "Function") == 0 || strcmp(d->label, "Method") == 0)) { CBMRegisteredFunc rf; memset(&rf, 0, sizeof(rf)); rf.min_params = -1; rf.qualified_name = d->qualified_name; rf.short_name = d->name; // Build return type — prefer return_type (raw text) over return_types // (cleaned by clean_type_name which strips template args like ). const CBMType **ret_types = NULL; if (d->return_type && d->return_type[0]) { ret_types = (const CBMType **)cbm_arena_alloc(arena, 2 * sizeof(const CBMType *)); ret_types[0] = c_parse_return_type_text(arena, d->return_type, module_qn); ret_types[1] = NULL; } else if (d->return_types) { int count = 0; while (d->return_types[count]) count++; if (count > 0) { ret_types = (const CBMType **)cbm_arena_alloc( arena, (count + 1) * sizeof(const CBMType *)); for (int j = 0; j < count; j++) ret_types[j] = c_parse_return_type_text(arena, d->return_types[j], module_qn); ret_types[count] = NULL; } } // Build param types const CBMType **param_types_arr = NULL; if (d->param_types) { int count = 0; while (d->param_types[count]) count++; if (count > 0) { param_types_arr = (const CBMType **)cbm_arena_alloc( arena, (count + 1) * sizeof(const CBMType *)); for (int j = 0; j < count; j++) param_types_arr[j] = c_parse_return_type_text(arena, d->param_types[j], module_qn); param_types_arr[count] = NULL; } } rf.signature = cbm_type_func(arena, d->param_names, param_types_arr, ret_types); // Method receiver if (strcmp(d->label, "Method") == 0 && d->parent_class) { rf.receiver_type = d->parent_class; // Auto-create type if needed if (!cbm_registry_lookup_type(®, d->parent_class)) { CBMRegisteredType auto_type; memset(&auto_type, 0, sizeof(auto_type)); auto_type.qualified_name = d->parent_class; // Extract short name const char *dot = strrchr(d->parent_class, '.'); auto_type.short_name = dot ? dot + 1 : d->parent_class; cbm_registry_add_type(®, auto_type); } } cbm_registry_add_func(®, rf); } } // Initialize context and run CLSPContext ctx; c_lsp_init(&ctx, arena, source, source_len, ®, module_qn, cpp_mode, &result->resolved_calls); c_lsp_process_file(&ctx, root); } // ============================================================================ // Entry point: cross-file LSP // ============================================================================ /* Register one batch of CBMLSPDef[] into a registry. Shared by the * per-file cross-LSP path and the Tier 2 pre-built registry builder. * Reads field/return/embedded info from the def strings (def-driven — * no per-file AST mutation), so the same defs always yield the same * registry entries regardless of which file is being processed. */ static void c_register_lsp_defs(CBMArena *arena, CBMTypeRegistry *reg, const char *module_qn, CBMLSPDef *defs, int def_count) { for (int i = 0; i < def_count; i++) { CBMLSPDef *d = &defs[i]; if (!d->qualified_name || !d->short_name) continue; if (d->label && (strcmp(d->label, "Class") == 0 || strcmp(d->label, "Type") == 0 || strcmp(d->label, "Interface") == 0)) { CBMRegisteredType rt; memset(&rt, 0, sizeof(rt)); rt.qualified_name = d->qualified_name; /* borrowed — d outlives this call */ rt.short_name = d->short_name; rt.is_interface = d->is_interface; // Embedded/base types if (d->embedded_types) { // Parse "|"-separated list const char *src = d->embedded_types; const char *embeds[32]; int embed_count = 0; while (*src && embed_count < 31) { const char *sep = strchr(src, '|'); if (sep) { embeds[embed_count++] = cbm_arena_strndup(arena, src, sep - src); src = sep + 1; } else { embeds[embed_count++] = cbm_arena_strdup(arena, src); break; } } if (embed_count > 0) { const char **arr = (const char **)cbm_arena_alloc( arena, (embed_count + 1) * sizeof(const char *)); for (int j = 0; j < embed_count; j++) arr[j] = embeds[j]; arr[embed_count] = NULL; rt.embedded_types = arr; } } // Field defs if (d->field_defs) { const char *fsrc = d->field_defs; const char *fnames[64]; const CBMType *ftypes[64]; int fcount = 0; while (*fsrc && fcount < 63) { const char *sep = strchr(fsrc, '|'); const char *end = sep ? sep : fsrc + strlen(fsrc); char *pair = cbm_arena_strndup(arena, fsrc, end - fsrc); char *colon = strchr(pair, ':'); if (colon) { *colon = '\0'; fnames[fcount] = pair; ftypes[fcount] = c_parse_return_type_text( arena, colon + 1, d->def_module_qn ? d->def_module_qn : module_qn); fcount++; } if (!sep) break; fsrc = sep + 1; } if (fcount > 0) { const char **fnarr = (const char **)cbm_arena_alloc(arena, (fcount + 1) * sizeof(const char *)); const CBMType **ftarr = (const CBMType **)cbm_arena_alloc( arena, (fcount + 1) * sizeof(const CBMType *)); for (int j = 0; j < fcount; j++) { fnarr[j] = fnames[j]; ftarr[j] = ftypes[j]; } fnarr[fcount] = NULL; ftarr[fcount] = NULL; rt.field_names = fnarr; rt.field_types = ftarr; } } cbm_registry_add_type(reg, rt); } if (d->label && (strcmp(d->label, "Function") == 0 || strcmp(d->label, "Method") == 0)) { CBMRegisteredFunc rf; memset(&rf, 0, sizeof(rf)); rf.min_params = -1; rf.qualified_name = d->qualified_name; /* borrowed */ rf.short_name = d->short_name; const char *def_module = d->def_module_qn ? d->def_module_qn : module_qn; // Return types if (d->return_types) { // Parse "|"-separated const char *rsrc = d->return_types; const CBMType *rets[16]; int rcount = 0; while (*rsrc && rcount < 15) { const char *sep = strchr(rsrc, '|'); const char *end = sep ? sep : rsrc + strlen(rsrc); char *rt_text = cbm_arena_strndup(arena, rsrc, end - rsrc); rets[rcount++] = c_parse_return_type_text(arena, rt_text, def_module); if (!sep) break; rsrc = sep + 1; } if (rcount > 0) { const CBMType **rarr = (const CBMType **)cbm_arena_alloc( arena, (rcount + 1) * sizeof(const CBMType *)); for (int j = 0; j < rcount; j++) rarr[j] = rets[j]; rarr[rcount] = NULL; rf.signature = cbm_type_func(arena, NULL, NULL, rarr); } } if (!rf.signature) rf.signature = cbm_type_func(arena, NULL, NULL, NULL); if (d->receiver_type) { rf.receiver_type = d->receiver_type; /* borrowed */ } cbm_registry_add_func(reg, rf); } } } /* Tier 2: build a project-wide C/C++/CUDA registry ONCE from all defs. * Registers both C and C++ stdlibs (C is a subset; harmless overlap) * and all C-family defs. Shared READ-ONLY across resolve workers. * Def-driven (no AST field collection) so produces identical entries * to the per-file build — zero quality loss. */ CBMTypeRegistry *cbm_c_build_cross_registry(CBMArena *arena, CBMLSPDef *defs, int def_count) { if (!arena) return NULL; CBMTypeRegistry *reg = (CBMTypeRegistry *)cbm_arena_alloc(arena, sizeof(*reg)); if (!reg) return NULL; cbm_registry_init(reg, arena); cbm_c_stdlib_register(reg, arena); cbm_cpp_stdlib_register(reg, arena); for (int i = 0; i < def_count; i++) { CBMLSPDef *d = &defs[i]; if (d->lang != CBM_LANG_C && d->lang != CBM_LANG_CPP && d->lang != CBM_LANG_CUDA) { continue; } c_register_lsp_defs(arena, reg, "", d, 1); } cbm_registry_finalize(reg); reg->read_only = true; /* seal: shared Tier-2 registry is read-only during resolve */ return reg; } void cbm_run_c_lsp_cross_with_registry(CBMArena *arena, const char *source, int source_len, const char *module_qn, bool cpp_mode, CBMTypeRegistry *reg, const char **include_paths, const char **include_ns_qns, int include_count, TSTree *cached_tree, CBMResolvedCallArray *out) { if (!source || source_len == 0 || !out || !reg) return; TSParser *parser = NULL; TSTree *tree = cached_tree; bool owns_tree = false; if (!tree) { parser = ts_parser_new(); if (!parser) return; const TSLanguage *ts_lang = cpp_mode ? tree_sitter_cpp() : tree_sitter_c(); ts_parser_set_language(parser, ts_lang); tree = ts_parser_parse_string(parser, NULL, source, source_len); ts_parser_delete(parser); owns_tree = true; if (!tree) return; } TSNode root = ts_tree_root_node(tree); CLSPContext ctx; c_lsp_init(&ctx, arena, source, source_len, reg, module_qn, cpp_mode, out); ctx.registry_shared = true; /* Tier-2 shared registry: read-only, see flag doc */ for (int i = 0; i < include_count; i++) { c_lsp_add_include(&ctx, include_paths[i], include_ns_qns[i]); } c_lsp_process_file(&ctx, root); if (owns_tree) { ts_tree_delete(tree); } } void cbm_run_c_lsp_cross(CBMArena *arena, const char *source, int source_len, const char *module_qn, bool cpp_mode, CBMLSPDef *defs, int def_count, const char **include_paths, const char **include_ns_qns, int include_count, TSTree *cached_tree, CBMResolvedCallArray *out) { if (!source || source_len == 0 || !out) return; CBMTypeRegistry reg; cbm_registry_init(®, arena); // Register stdlib cbm_c_stdlib_register(®, arena); if (cpp_mode) cbm_cpp_stdlib_register(®, arena); // Register all defs (shared helper — def-driven) c_register_lsp_defs(arena, ®, module_qn, defs, def_count); // Use cached tree if available, otherwise parse fresh TSParser *parser = NULL; TSTree *tree = cached_tree; bool owns_tree = false; if (!tree) { parser = ts_parser_new(); if (!parser) return; const TSLanguage *ts_lang = cpp_mode ? tree_sitter_cpp() : tree_sitter_c(); ts_parser_set_language(parser, ts_lang); tree = ts_parser_parse_string(parser, NULL, source, source_len); ts_parser_delete(parser); owns_tree = true; if (!tree) return; } TSNode root = ts_tree_root_node(tree); // Finalize registry — O(1) lookups. See go_lsp.c "3c. Finalize" // comment for the rationale (linear-scan fallback otherwise). cbm_registry_finalize(®); // Initialize context and run CLSPContext ctx; c_lsp_init(&ctx, arena, source, source_len, ®, module_qn, cpp_mode, out); // Add include mappings for (int i = 0; i < include_count; i++) { c_lsp_add_include(&ctx, include_paths[i], include_ns_qns[i]); } c_lsp_process_file(&ctx, root); if (owns_tree) { ts_tree_delete(tree); } } // --- Batch cross-file LSP --- void cbm_batch_c_lsp_cross(CBMArena *arena, CBMBatchCLSPFile *files, int file_count, CBMResolvedCallArray *out) { if (!files || file_count <= 0 || !out) return; for (int f = 0; f < file_count; f++) { CBMBatchCLSPFile *file = &files[f]; memset(&out[f], 0, sizeof(CBMResolvedCallArray)); if (!file->source || file->source_len <= 0 || file->def_count <= 0) continue; // Per-file arena: registry + temp data freed after each file CBMArena file_arena; cbm_arena_init(&file_arena); CBMResolvedCallArray file_out; memset(&file_out, 0, sizeof(file_out)); // Delegate to existing per-file function cbm_run_c_lsp_cross(&file_arena, file->source, file->source_len, file->module_qn, file->cpp_mode, file->defs, file->def_count, file->include_paths, file->include_ns_qns, file->include_count, file->cached_tree, &file_out); // Copy results to output arena (must outlive per-file arena) if (file_out.count > 0) { out[f].count = file_out.count; out[f].items = (CBMResolvedCall *)cbm_arena_alloc(arena, file_out.count * sizeof(CBMResolvedCall)); for (int j = 0; j < file_out.count; j++) { CBMResolvedCall *src = &file_out.items[j]; CBMResolvedCall *dst = &out[f].items[j]; dst->caller_qn = src->caller_qn ? cbm_arena_strdup(arena, src->caller_qn) : NULL; dst->callee_qn = src->callee_qn ? cbm_arena_strdup(arena, src->callee_qn) : NULL; dst->strategy = src->strategy ? cbm_arena_strdup(arena, src->strategy) : NULL; dst->confidence = src->confidence; dst->reason = src->reason ? cbm_arena_strdup(arena, src->reason) : NULL; } } cbm_arena_destroy(&file_arena); } }