3048 lines
138 KiB
C
3048 lines
138 KiB
C
#include "go_lsp.h"
|
||
#include "lsp_node_iter.h"
|
||
#include "../helpers.h"
|
||
#include <string.h>
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
|
||
// Forward declarations
|
||
static void resolve_calls_in_node_inner(GoLSPContext* ctx, TSNode node);
|
||
|
||
/* Depth-guarded entry for the AST call-resolution walk. The walk recurses once
|
||
* per nesting level; a deeply-nested or cyclic file can overflow the native
|
||
* stack (SIGSEGV) and take down the whole index. Past the cap the subtree is
|
||
* skipped — its calls stay unresolved, which is graceful degradation, not a
|
||
* crash. The cap is CBM_LSP_MAX_WALK_DEPTH, env-overridable via the same name.
|
||
* The walk_depth-- runs after the inner returns, so early returns in the body
|
||
* never leak the counter. */
|
||
static void resolve_calls_in_node(GoLSPContext* ctx, TSNode node) {
|
||
if (ctx->walk_depth >= cbm_lsp_max_walk_depth())
|
||
return;
|
||
ctx->walk_depth++;
|
||
resolve_calls_in_node_inner(ctx, node);
|
||
ctx->walk_depth--;
|
||
}
|
||
static void emit_resolved_call(GoLSPContext* ctx, const char* callee_qn, const char* strategy, float confidence);
|
||
static const CBMType* go_lookup_field(GoLSPContext* ctx, const char* type_qn, const char* field_name, int depth);
|
||
static void extract_type_params_from_ast(CBMArena* arena, CBMTypeRegistry* reg,
|
||
TSNode root, const char* source, const char* module_qn);
|
||
|
||
// --- Initialization ---
|
||
|
||
void go_lsp_init(GoLSPContext* ctx, CBMArena* arena, const char* source, int source_len,
|
||
const CBMTypeRegistry* registry, const char* package_qn, CBMResolvedCallArray* out) {
|
||
memset(ctx, 0, sizeof(GoLSPContext));
|
||
ctx->arena = arena;
|
||
ctx->source = source;
|
||
ctx->source_len = source_len;
|
||
ctx->registry = registry;
|
||
ctx->package_qn = package_qn;
|
||
ctx->resolved_calls = out;
|
||
ctx->current_scope = cbm_scope_push(arena, NULL); // root scope
|
||
|
||
{
|
||
const char* debug_env = getenv("CBM_LSP_DEBUG");
|
||
ctx->debug = (debug_env && debug_env[0]);
|
||
}
|
||
}
|
||
|
||
void go_lsp_add_import(GoLSPContext* ctx, const char* local_name, const char* pkg_qn) {
|
||
// Store in parallel arrays (arena-allocated, grow by doubling)
|
||
if (ctx->import_count % 32 == 0) {
|
||
int new_cap = ctx->import_count + 32;
|
||
const char** new_names = (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_names || !new_qns) return;
|
||
if (ctx->import_local_names && ctx->import_count > 0) {
|
||
memcpy(new_names, ctx->import_local_names, ctx->import_count * sizeof(const char*));
|
||
memcpy(new_qns, ctx->import_package_qns, ctx->import_count * sizeof(const char*));
|
||
}
|
||
ctx->import_local_names = new_names;
|
||
ctx->import_package_qns = new_qns;
|
||
}
|
||
ctx->import_local_names[ctx->import_count] = cbm_arena_strdup(ctx->arena, local_name);
|
||
ctx->import_package_qns[ctx->import_count] = cbm_arena_strdup(ctx->arena, pkg_qn);
|
||
ctx->import_count++;
|
||
}
|
||
|
||
// --- Helper: get node text ---
|
||
|
||
static char* lsp_node_text(GoLSPContext* ctx, TSNode node) {
|
||
return cbm_node_text(ctx->arena, node, ctx->source);
|
||
}
|
||
|
||
// --- Helper: resolve import alias to package QN ---
|
||
|
||
static const char* resolve_import(GoLSPContext* ctx, const char* local_name) {
|
||
for (int i = 0; i < ctx->import_count; i++) {
|
||
if (strcmp(ctx->import_local_names[i], local_name) == 0) {
|
||
return ctx->import_package_qns[i];
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
// --- Helper: check if name is a Go builtin ---
|
||
|
||
static bool is_go_builtin_func(const char* name) {
|
||
static const char* builtins[] = {
|
||
"make", "new", "append", "len", "cap", "delete",
|
||
"close", "copy", "panic", "recover", "print", "println",
|
||
"complex", "real", "imag", "min", "max", "clear",
|
||
NULL
|
||
};
|
||
for (const char** b = builtins; *b; b++) {
|
||
if (strcmp(name, *b) == 0) return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// --- Helper: check if name is a Go builtin type ---
|
||
|
||
static const CBMType* resolve_builtin_type(GoLSPContext* ctx, const char* name) {
|
||
static const char* builtin_types[] = {
|
||
"int", "int8", "int16", "int32", "int64",
|
||
"uint", "uint8", "uint16", "uint32", "uint64",
|
||
"float32", "float64", "complex64", "complex128",
|
||
"string", "bool", "byte", "rune", "error",
|
||
"uintptr", "any",
|
||
NULL
|
||
};
|
||
for (const char** b = builtin_types; *b; b++) {
|
||
if (strcmp(name, *b) == 0) {
|
||
return cbm_type_builtin(ctx->arena, name);
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
// --- go_parse_type_node: AST type node -> CBMType ---
|
||
|
||
const CBMType* go_parse_type_node(GoLSPContext* ctx, TSNode node) {
|
||
if (ts_node_is_null(node)) return cbm_type_unknown();
|
||
|
||
const char* kind = ts_node_type(node);
|
||
|
||
// type_identifier: simple named type
|
||
if (strcmp(kind, "type_identifier") == 0) {
|
||
char* name = lsp_node_text(ctx, node);
|
||
if (!name) return cbm_type_unknown();
|
||
const CBMType* builtin = resolve_builtin_type(ctx, name);
|
||
if (builtin) return builtin;
|
||
// Resolve as local type: package_qn.TypeName
|
||
return cbm_type_named(ctx->arena,
|
||
cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->package_qn, name));
|
||
}
|
||
|
||
// qualified_type: pkg.Type
|
||
if (strcmp(kind, "qualified_type") == 0) {
|
||
TSNode pkg_node = ts_node_child_by_field_name(node, "package", 7);
|
||
TSNode name_node = ts_node_child_by_field_name(node, "name", 4);
|
||
if (!ts_node_is_null(pkg_node) && !ts_node_is_null(name_node)) {
|
||
char* pkg = lsp_node_text(ctx, pkg_node);
|
||
char* name = lsp_node_text(ctx, name_node);
|
||
const char* pkg_qn = resolve_import(ctx, pkg);
|
||
if (pkg_qn) {
|
||
return cbm_type_named(ctx->arena,
|
||
cbm_arena_sprintf(ctx->arena, "%s.%s", pkg_qn, name));
|
||
}
|
||
}
|
||
return cbm_type_unknown();
|
||
}
|
||
|
||
// pointer_type: *T
|
||
if (strcmp(kind, "pointer_type") == 0) {
|
||
uint32_t nc = ts_node_named_child_count(node);
|
||
if (nc > 0) {
|
||
return cbm_type_pointer(ctx->arena,
|
||
go_parse_type_node(ctx, ts_node_named_child(node, nc - 1)));
|
||
}
|
||
return cbm_type_unknown();
|
||
}
|
||
|
||
// slice_type: []T
|
||
if (strcmp(kind, "slice_type") == 0) {
|
||
TSNode elem = ts_node_child_by_field_name(node, "element", 7);
|
||
if (ts_node_is_null(elem) && ts_node_named_child_count(node) > 0) {
|
||
elem = ts_node_named_child(node, ts_node_named_child_count(node) - 1);
|
||
}
|
||
return cbm_type_slice(ctx->arena, go_parse_type_node(ctx, elem));
|
||
}
|
||
|
||
// array_type: [N]T — treat as slice for our purposes
|
||
if (strcmp(kind, "array_type") == 0) {
|
||
TSNode elem = ts_node_child_by_field_name(node, "element", 7);
|
||
if (ts_node_is_null(elem) && ts_node_named_child_count(node) > 0) {
|
||
elem = ts_node_named_child(node, ts_node_named_child_count(node) - 1);
|
||
}
|
||
return cbm_type_slice(ctx->arena, go_parse_type_node(ctx, elem));
|
||
}
|
||
|
||
// map_type: map[K]V
|
||
if (strcmp(kind, "map_type") == 0) {
|
||
TSNode key = ts_node_child_by_field_name(node, "key", 3);
|
||
TSNode value = ts_node_child_by_field_name(node, "value", 5);
|
||
return cbm_type_map(ctx->arena,
|
||
go_parse_type_node(ctx, key),
|
||
go_parse_type_node(ctx, value));
|
||
}
|
||
|
||
// channel_type: chan T
|
||
if (strcmp(kind, "channel_type") == 0) {
|
||
TSNode value = ts_node_child_by_field_name(node, "value", 5);
|
||
if (ts_node_is_null(value) && ts_node_named_child_count(node) > 0) {
|
||
value = ts_node_named_child(node, ts_node_named_child_count(node) - 1);
|
||
}
|
||
// Determine direction from text
|
||
char* text = lsp_node_text(ctx, node);
|
||
int dir = 0;
|
||
if (text) {
|
||
if (strncmp(text, "chan<-", 6) == 0 || strncmp(text, "chan <-", 7) == 0) dir = 1;
|
||
else if (strncmp(text, "<-chan", 6) == 0 || strncmp(text, "<- chan", 7) == 0) dir = 2;
|
||
}
|
||
return cbm_type_channel(ctx->arena, go_parse_type_node(ctx, value), dir);
|
||
}
|
||
|
||
// function_type: func(...)...
|
||
if (strcmp(kind, "function_type") == 0) {
|
||
return cbm_type_func(ctx->arena, NULL, NULL, NULL); // simplified
|
||
}
|
||
|
||
// interface_type
|
||
if (strcmp(kind, "interface_type") == 0) {
|
||
CBMType* t = (CBMType*)cbm_arena_alloc(ctx->arena, sizeof(CBMType));
|
||
memset(t, 0, sizeof(CBMType));
|
||
t->kind = CBM_TYPE_INTERFACE;
|
||
return t;
|
||
}
|
||
|
||
// struct_type
|
||
if (strcmp(kind, "struct_type") == 0) {
|
||
CBMType* t = (CBMType*)cbm_arena_alloc(ctx->arena, sizeof(CBMType));
|
||
memset(t, 0, sizeof(CBMType));
|
||
t->kind = CBM_TYPE_STRUCT;
|
||
return t;
|
||
}
|
||
|
||
// parenthesized_type: (T)
|
||
if (strcmp(kind, "parenthesized_type") == 0 && ts_node_named_child_count(node) > 0) {
|
||
return go_parse_type_node(ctx, ts_node_named_child(node, 0));
|
||
}
|
||
|
||
// type_elem: wrapper in type_arguments, unwrap to inner type
|
||
if (strcmp(kind, "type_elem") == 0 && ts_node_named_child_count(node) > 0) {
|
||
return go_parse_type_node(ctx, ts_node_named_child(node, 0));
|
||
}
|
||
|
||
// generic_type: Type[T1, T2] — return as named without generic args for now
|
||
if (strcmp(kind, "generic_type") == 0) {
|
||
TSNode type_node = ts_node_child_by_field_name(node, "type", 4);
|
||
if (!ts_node_is_null(type_node)) {
|
||
return go_parse_type_node(ctx, type_node);
|
||
}
|
||
}
|
||
|
||
// parameter_list used as result type (multi-return)
|
||
if (strcmp(kind, "parameter_list") == 0) {
|
||
int count = 0;
|
||
const CBMType* elems[16];
|
||
uint32_t nc = ts_node_child_count(node);
|
||
for (uint32_t i = 0; i < nc && count < 16; i++) {
|
||
TSNode child = ts_node_child(node, i);
|
||
if (ts_node_is_null(child) || !ts_node_is_named(child)) continue;
|
||
const char* ck = ts_node_type(child);
|
||
if (strcmp(ck, "parameter_declaration") == 0) {
|
||
TSNode tn = ts_node_child_by_field_name(child, "type", 4);
|
||
if (!ts_node_is_null(tn)) {
|
||
elems[count++] = go_parse_type_node(ctx, tn);
|
||
}
|
||
} else {
|
||
elems[count++] = go_parse_type_node(ctx, child);
|
||
}
|
||
}
|
||
if (count == 1) return elems[0];
|
||
if (count > 1) return cbm_type_tuple(ctx->arena, elems, count);
|
||
}
|
||
|
||
return cbm_type_unknown();
|
||
}
|
||
|
||
// --- Implicit generics: type unification ---
|
||
|
||
// Unify a parameter type pattern (containing TYPE_PARAM) against a concrete argument type.
|
||
// Fills inferred[i] with the concrete type bound to type_param_names[i].
|
||
static void go_unify_type(const CBMType* param_type, const CBMType* arg_type,
|
||
const char** type_param_names, const CBMType** inferred, int param_count) {
|
||
if (!param_type || !arg_type || cbm_type_is_unknown(arg_type)) return;
|
||
|
||
if (param_type->kind == CBM_TYPE_TYPE_PARAM) {
|
||
for (int i = 0; i < param_count; i++) {
|
||
if (strcmp(param_type->data.type_param.name, type_param_names[i]) == 0) {
|
||
if (!inferred[i]) inferred[i] = arg_type;
|
||
break;
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
|
||
// Structural matching — recurse into composite types
|
||
if (param_type->kind == CBM_TYPE_SLICE && arg_type->kind == CBM_TYPE_SLICE) {
|
||
go_unify_type(param_type->data.slice.elem, arg_type->data.slice.elem,
|
||
type_param_names, inferred, param_count);
|
||
}
|
||
if (param_type->kind == CBM_TYPE_POINTER && arg_type->kind == CBM_TYPE_POINTER) {
|
||
go_unify_type(param_type->data.pointer.elem, arg_type->data.pointer.elem,
|
||
type_param_names, inferred, param_count);
|
||
}
|
||
if (param_type->kind == CBM_TYPE_MAP && arg_type->kind == CBM_TYPE_MAP) {
|
||
go_unify_type(param_type->data.map.key, arg_type->data.map.key,
|
||
type_param_names, inferred, param_count);
|
||
go_unify_type(param_type->data.map.value, arg_type->data.map.value,
|
||
type_param_names, inferred, param_count);
|
||
}
|
||
if (param_type->kind == CBM_TYPE_CHANNEL && arg_type->kind == CBM_TYPE_CHANNEL) {
|
||
go_unify_type(param_type->data.channel.elem, arg_type->data.channel.elem,
|
||
type_param_names, inferred, param_count);
|
||
}
|
||
if (param_type->kind == CBM_TYPE_FUNC && arg_type->kind == CBM_TYPE_FUNC) {
|
||
// Match param types
|
||
if (param_type->data.func.param_types && arg_type->data.func.param_types) {
|
||
for (int i = 0; param_type->data.func.param_types[i] && arg_type->data.func.param_types[i]; i++) {
|
||
go_unify_type(param_type->data.func.param_types[i], arg_type->data.func.param_types[i],
|
||
type_param_names, inferred, param_count);
|
||
}
|
||
}
|
||
// Match return types
|
||
if (param_type->data.func.return_types && arg_type->data.func.return_types) {
|
||
for (int i = 0; param_type->data.func.return_types[i] && arg_type->data.func.return_types[i]; i++) {
|
||
go_unify_type(param_type->data.func.return_types[i], arg_type->data.func.return_types[i],
|
||
type_param_names, inferred, param_count);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// --- go_eval_expr_type: recursive expression type evaluator ---
|
||
|
||
const CBMType* go_eval_expr_type(GoLSPContext* ctx, TSNode node) {
|
||
if (ts_node_is_null(node)) return cbm_type_unknown();
|
||
|
||
const char* kind = ts_node_type(node);
|
||
|
||
// --- Identifier: scope lookup ---
|
||
if (strcmp(kind, "identifier") == 0) {
|
||
char* name = lsp_node_text(ctx, node);
|
||
if (!name) return cbm_type_unknown();
|
||
|
||
// Check scope first
|
||
const CBMType* t = cbm_scope_lookup(ctx->current_scope, name);
|
||
if (!cbm_type_is_unknown(t)) return t;
|
||
|
||
// Check if it's a package-level function
|
||
const CBMRegisteredFunc* f = cbm_registry_lookup_symbol(ctx->registry, ctx->package_qn, name);
|
||
if (f && f->signature) return f->signature;
|
||
|
||
// Check if it's a builtin type (for type conversions like string(x), int(x))
|
||
const CBMType* bt = resolve_builtin_type(ctx, name);
|
||
if (bt) return cbm_type_named(ctx->arena, name);
|
||
|
||
// Check if it's a registered type (for type conversions like MyType(x))
|
||
const char* type_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->package_qn, name);
|
||
const CBMRegisteredType* rt = cbm_registry_lookup_type(ctx->registry, type_qn);
|
||
if (rt) return cbm_type_named(ctx->arena, type_qn);
|
||
|
||
return cbm_type_unknown();
|
||
}
|
||
|
||
// --- Selector expression: a.B ---
|
||
if (strcmp(kind, "selector_expression") == 0) {
|
||
TSNode operand = ts_node_child_by_field_name(node, "operand", 7);
|
||
TSNode field = ts_node_child_by_field_name(node, "field", 5);
|
||
if (ts_node_is_null(operand) || ts_node_is_null(field)) return cbm_type_unknown();
|
||
|
||
char* field_name = lsp_node_text(ctx, field);
|
||
if (!field_name) return cbm_type_unknown();
|
||
|
||
// Check if operand is an import alias (pkg.Symbol)
|
||
if (strcmp(ts_node_type(operand), "identifier") == 0) {
|
||
char* pkg_name = lsp_node_text(ctx, operand);
|
||
if (pkg_name) {
|
||
const char* pkg_qn = resolve_import(ctx, pkg_name);
|
||
if (pkg_qn) {
|
||
// Look up pkg.Symbol as a function or type
|
||
const CBMRegisteredFunc* f = cbm_registry_lookup_symbol(ctx->registry, pkg_qn, field_name);
|
||
if (f && f->signature) return f->signature;
|
||
// Check if it's a type
|
||
char* type_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", pkg_qn, field_name);
|
||
const CBMRegisteredType* rt = cbm_registry_lookup_type(ctx->registry, type_qn);
|
||
if (rt) return cbm_type_named(ctx->arena, type_qn);
|
||
return cbm_type_unknown();
|
||
}
|
||
}
|
||
}
|
||
|
||
// Evaluate operand type
|
||
const CBMType* recv_type = go_eval_expr_type(ctx, operand);
|
||
if (cbm_type_is_unknown(recv_type)) return cbm_type_unknown();
|
||
|
||
// Auto-deref pointers for method calls
|
||
const CBMType* base_type = recv_type;
|
||
if (base_type->kind == CBM_TYPE_POINTER) {
|
||
base_type = cbm_type_deref(base_type);
|
||
}
|
||
|
||
if (base_type->kind == CBM_TYPE_NAMED) {
|
||
const char* type_qn = base_type->data.named.qualified_name;
|
||
|
||
// Look up method/field (methods recurse through embeddings)
|
||
const CBMRegisteredFunc* method = go_lookup_field_or_method(ctx, type_qn, field_name);
|
||
if (method && method->signature) return method->signature;
|
||
|
||
// Check struct fields (with embedded field promotion)
|
||
const CBMType* field_type = go_lookup_field(ctx, type_qn, field_name, 0);
|
||
if (field_type && !cbm_type_is_unknown(field_type)) return field_type;
|
||
}
|
||
|
||
return cbm_type_unknown();
|
||
}
|
||
|
||
// --- Call expression: f(...) ---
|
||
if (strcmp(kind, "call_expression") == 0) {
|
||
TSNode func_node = ts_node_child_by_field_name(node, "function", 8);
|
||
TSNode args_node = ts_node_child_by_field_name(node, "arguments", 9);
|
||
if (ts_node_is_null(func_node)) return cbm_type_unknown();
|
||
|
||
// Check for builtin calls
|
||
if (strcmp(ts_node_type(func_node), "identifier") == 0) {
|
||
char* name = lsp_node_text(ctx, func_node);
|
||
if (name && is_go_builtin_func(name)) {
|
||
return go_eval_builtin_call(ctx, name, args_node);
|
||
}
|
||
}
|
||
|
||
// Evaluate function type
|
||
const CBMType* func_type = go_eval_expr_type(ctx, func_node);
|
||
|
||
// If it's a FUNC type, return its return type
|
||
if (func_type && func_type->kind == CBM_TYPE_FUNC &&
|
||
func_type->data.func.return_types && func_type->data.func.return_types[0]) {
|
||
|
||
// Check for explicit type arguments: call_expression has type_arguments field
|
||
// Go tree-sitter: Func[T1, T2](args) → call_expression { function, type_arguments, arguments }
|
||
TSNode targs_node = ts_node_child_by_field_name(node, "type_arguments", 14);
|
||
if (!ts_node_is_null(targs_node)) {
|
||
// Look up the registered function to get type_param_names
|
||
const CBMRegisteredFunc* rfunc = NULL;
|
||
char* func_name = lsp_node_text(ctx, func_node);
|
||
if (func_name) {
|
||
const char* func_qn = cbm_arena_sprintf(ctx->arena, "%s.%s",
|
||
ctx->package_qn, func_name);
|
||
rfunc = cbm_registry_lookup_func(ctx->registry, func_qn);
|
||
// If not found as local, try via import (selector_expression: pkg.Func)
|
||
if (!rfunc && strcmp(ts_node_type(func_node), "selector_expression") == 0) {
|
||
TSNode operand = ts_node_child_by_field_name(func_node, "operand", 7);
|
||
TSNode field = ts_node_child_by_field_name(func_node, "field", 5);
|
||
if (!ts_node_is_null(operand) && !ts_node_is_null(field)) {
|
||
char* pkg = lsp_node_text(ctx, operand);
|
||
char* fn = lsp_node_text(ctx, field);
|
||
if (pkg && fn) {
|
||
const char* pkg_qn = resolve_import(ctx, pkg);
|
||
if (pkg_qn) {
|
||
rfunc = cbm_registry_lookup_symbol(ctx->registry, pkg_qn, fn);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (rfunc && rfunc->type_param_names) {
|
||
// Parse type arguments from AST
|
||
const CBMType* type_args[16];
|
||
int targ_count = 0;
|
||
uint32_t ta_nc = ts_node_child_count(targs_node);
|
||
for (uint32_t ti = 0; ti < ta_nc && targ_count < 15; ti++) {
|
||
TSNode targ = ts_node_child(targs_node, ti);
|
||
if (ts_node_is_null(targ) || !ts_node_is_named(targ)) continue;
|
||
type_args[targ_count++] = go_parse_type_node(ctx, targ);
|
||
}
|
||
|
||
// Count type params
|
||
int param_count = 0;
|
||
while (rfunc->type_param_names[param_count]) param_count++;
|
||
|
||
if (targ_count > 0 && targ_count == param_count) {
|
||
const CBMType** targ_arr = (const CBMType**)cbm_arena_alloc(
|
||
ctx->arena, (targ_count + 1) * sizeof(const CBMType*));
|
||
for (int ti = 0; ti < targ_count; ti++) targ_arr[ti] = type_args[ti];
|
||
targ_arr[targ_count] = NULL;
|
||
|
||
// Substitute type params in return type(s)
|
||
int ret_count = 0;
|
||
while (func_type->data.func.return_types[ret_count]) ret_count++;
|
||
|
||
if (ret_count == 1) {
|
||
return cbm_type_substitute(ctx->arena,
|
||
func_type->data.func.return_types[0],
|
||
rfunc->type_param_names, targ_arr);
|
||
}
|
||
// Multi-return: substitute all
|
||
const CBMType** new_rets = (const CBMType**)cbm_arena_alloc(
|
||
ctx->arena, (ret_count + 1) * sizeof(const CBMType*));
|
||
for (int ri = 0; ri < ret_count; ri++) {
|
||
new_rets[ri] = cbm_type_substitute(ctx->arena,
|
||
func_type->data.func.return_types[ri],
|
||
rfunc->type_param_names, targ_arr);
|
||
}
|
||
new_rets[ret_count] = NULL;
|
||
return cbm_type_tuple(ctx->arena, new_rets, ret_count);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Implicit generics: infer type args from argument types
|
||
if (ts_node_is_null(targs_node)) {
|
||
// Look up registered function to check for type_param_names
|
||
const CBMRegisteredFunc* rfunc = NULL;
|
||
char* func_name = lsp_node_text(ctx, func_node);
|
||
if (func_name) {
|
||
const char* func_qn = cbm_arena_sprintf(ctx->arena, "%s.%s",
|
||
ctx->package_qn, func_name);
|
||
rfunc = cbm_registry_lookup_func(ctx->registry, func_qn);
|
||
if (!rfunc && strcmp(ts_node_type(func_node), "selector_expression") == 0) {
|
||
TSNode operand = ts_node_child_by_field_name(func_node, "operand", 7);
|
||
TSNode field = ts_node_child_by_field_name(func_node, "field", 5);
|
||
if (!ts_node_is_null(operand) && !ts_node_is_null(field)) {
|
||
char* pkg = lsp_node_text(ctx, operand);
|
||
char* fn = lsp_node_text(ctx, field);
|
||
if (pkg && fn) {
|
||
const char* pkg_qn = resolve_import(ctx, pkg);
|
||
if (pkg_qn)
|
||
rfunc = cbm_registry_lookup_symbol(ctx->registry, pkg_qn, fn);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (rfunc && rfunc->type_param_names && func_type->data.func.param_types) {
|
||
int tpc = 0;
|
||
while (rfunc->type_param_names[tpc]) tpc++;
|
||
|
||
// Check if any return type uses a type param
|
||
bool has_type_param = false;
|
||
int ret_count = 0;
|
||
while (func_type->data.func.return_types[ret_count]) ret_count++;
|
||
for (int ri = 0; ri < ret_count && !has_type_param; ri++) {
|
||
// Quick check: walk return type tree for TYPE_PARAM nodes
|
||
const CBMType* rt = func_type->data.func.return_types[ri];
|
||
if (rt->kind == CBM_TYPE_TYPE_PARAM) has_type_param = true;
|
||
else if (rt->kind == CBM_TYPE_SLICE && rt->data.slice.elem &&
|
||
rt->data.slice.elem->kind == CBM_TYPE_TYPE_PARAM) has_type_param = true;
|
||
else if (rt->kind == CBM_TYPE_POINTER && rt->data.pointer.elem &&
|
||
rt->data.pointer.elem->kind == CBM_TYPE_TYPE_PARAM) has_type_param = true;
|
||
else if (rt->kind == CBM_TYPE_MAP) {
|
||
if (rt->data.map.key && rt->data.map.key->kind == CBM_TYPE_TYPE_PARAM) has_type_param = true;
|
||
if (rt->data.map.value && rt->data.map.value->kind == CBM_TYPE_TYPE_PARAM) has_type_param = true;
|
||
}
|
||
}
|
||
|
||
if (has_type_param && tpc > 0 && tpc <= 16) {
|
||
// Evaluate argument types and unify
|
||
const CBMType* inferred[16] = {0};
|
||
|
||
if (!ts_node_is_null(args_node)) {
|
||
uint32_t argc = ts_node_named_child_count(args_node);
|
||
int pi = 0;
|
||
for (uint32_t ai = 0; ai < argc && func_type->data.func.param_types[pi]; ai++) {
|
||
TSNode arg = ts_node_named_child(args_node, ai);
|
||
if (ts_node_is_null(arg)) continue;
|
||
const CBMType* arg_type = go_eval_expr_type(ctx, arg);
|
||
go_unify_type(func_type->data.func.param_types[pi],
|
||
arg_type, rfunc->type_param_names, inferred, tpc);
|
||
pi++;
|
||
}
|
||
}
|
||
|
||
// Check if all type params were inferred
|
||
bool all_inferred = true;
|
||
for (int i = 0; i < tpc; i++) {
|
||
if (!inferred[i]) { all_inferred = false; break; }
|
||
}
|
||
|
||
if (all_inferred) {
|
||
const CBMType** targ_arr = (const CBMType**)cbm_arena_alloc(
|
||
ctx->arena, (tpc + 1) * sizeof(const CBMType*));
|
||
for (int i = 0; i < tpc; i++) targ_arr[i] = inferred[i];
|
||
targ_arr[tpc] = NULL;
|
||
|
||
if (ret_count == 1) {
|
||
return cbm_type_substitute(ctx->arena,
|
||
func_type->data.func.return_types[0],
|
||
rfunc->type_param_names, targ_arr);
|
||
}
|
||
const CBMType** new_rets = (const CBMType**)cbm_arena_alloc(
|
||
ctx->arena, (ret_count + 1) * sizeof(const CBMType*));
|
||
for (int ri = 0; ri < ret_count; ri++) {
|
||
new_rets[ri] = cbm_type_substitute(ctx->arena,
|
||
func_type->data.func.return_types[ri],
|
||
rfunc->type_param_names, targ_arr);
|
||
}
|
||
new_rets[ret_count] = NULL;
|
||
return cbm_type_tuple(ctx->arena, new_rets, ret_count);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// No type arguments or no substitution needed — return as-is
|
||
if (!func_type->data.func.return_types[1]) {
|
||
return func_type->data.func.return_types[0];
|
||
}
|
||
int count = 0;
|
||
while (func_type->data.func.return_types[count]) count++;
|
||
if (count > 1) {
|
||
return cbm_type_tuple(ctx->arena, func_type->data.func.return_types, count);
|
||
}
|
||
}
|
||
|
||
// Type conversion: Type(expr) — if func_node resolves to a named type
|
||
if (func_type && func_type->kind == CBM_TYPE_NAMED) {
|
||
return func_type;
|
||
}
|
||
|
||
// Type conversion with composite type syntax: []byte(s), map[K]V(x), etc.
|
||
// The function node is a type node (slice_type, map_type, etc.)
|
||
{
|
||
const char* fk = ts_node_type(func_node);
|
||
if (strcmp(fk, "slice_type") == 0 || strcmp(fk, "array_type") == 0 ||
|
||
strcmp(fk, "map_type") == 0 || strcmp(fk, "pointer_type") == 0 ||
|
||
strcmp(fk, "channel_type") == 0) {
|
||
return go_parse_type_node(ctx, func_node);
|
||
}
|
||
}
|
||
|
||
return cbm_type_unknown();
|
||
}
|
||
|
||
// --- Composite literal: Type{...} ---
|
||
if (strcmp(kind, "composite_literal") == 0) {
|
||
TSNode type_node = ts_node_child_by_field_name(node, "type", 4);
|
||
if (!ts_node_is_null(type_node)) {
|
||
return go_parse_type_node(ctx, type_node);
|
||
}
|
||
return cbm_type_unknown();
|
||
}
|
||
|
||
// --- Unary expression: &x, *x, <-ch, !x ---
|
||
if (strcmp(kind, "unary_expression") == 0) {
|
||
TSNode operand = ts_node_child_by_field_name(node, "operand", 7);
|
||
if (ts_node_is_null(operand)) return cbm_type_unknown();
|
||
|
||
// Get 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 = lsp_node_text(ctx, child);
|
||
if (!op) continue;
|
||
if (strcmp(op, "&") == 0) {
|
||
return cbm_type_pointer(ctx->arena, go_eval_expr_type(ctx, operand));
|
||
}
|
||
if (strcmp(op, "*") == 0) {
|
||
return cbm_type_deref(go_eval_expr_type(ctx, operand));
|
||
}
|
||
if (strcmp(op, "<-") == 0) {
|
||
const CBMType* ch_type = go_eval_expr_type(ctx, operand);
|
||
if (ch_type && ch_type->kind == CBM_TYPE_CHANNEL) {
|
||
return ch_type->data.channel.elem;
|
||
}
|
||
return cbm_type_unknown();
|
||
}
|
||
if (strcmp(op, "!") == 0) {
|
||
return cbm_type_builtin(ctx->arena, "bool");
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
return cbm_type_unknown();
|
||
}
|
||
|
||
// --- Index expression: a[i] ---
|
||
if (strcmp(kind, "index_expression") == 0) {
|
||
TSNode operand = ts_node_child_by_field_name(node, "operand", 7);
|
||
if (ts_node_is_null(operand)) return cbm_type_unknown();
|
||
const CBMType* op_type = go_eval_expr_type(ctx, operand);
|
||
if (!op_type) return cbm_type_unknown();
|
||
if (op_type->kind == CBM_TYPE_MAP) return op_type->data.map.value;
|
||
if (op_type->kind == CBM_TYPE_SLICE) return op_type->data.slice.elem;
|
||
return cbm_type_unknown();
|
||
}
|
||
|
||
// --- Type assertion: x.(Type) ---
|
||
if (strcmp(kind, "type_assertion_expression") == 0) {
|
||
TSNode type_node = ts_node_child_by_field_name(node, "type", 4);
|
||
if (!ts_node_is_null(type_node)) {
|
||
return go_parse_type_node(ctx, type_node);
|
||
}
|
||
return cbm_type_unknown();
|
||
}
|
||
|
||
// --- Parenthesized expression: (x) ---
|
||
if (strcmp(kind, "parenthesized_expression") == 0 && ts_node_named_child_count(node) > 0) {
|
||
return go_eval_expr_type(ctx, ts_node_named_child(node, 0));
|
||
}
|
||
|
||
// --- Binary expression ---
|
||
if (strcmp(kind, "binary_expression") == 0) {
|
||
// For comparisons, return bool
|
||
// For arithmetic, return left operand type
|
||
TSNode left = ts_node_child_by_field_name(node, "left", 4);
|
||
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 = lsp_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 go_eval_expr_type(ctx, left);
|
||
return cbm_type_unknown();
|
||
}
|
||
|
||
// --- Slice expression: a[low:high] ---
|
||
if (strcmp(kind, "slice_expression") == 0) {
|
||
TSNode operand = ts_node_child_by_field_name(node, "operand", 7);
|
||
if (!ts_node_is_null(operand)) return go_eval_expr_type(ctx, operand);
|
||
return cbm_type_unknown();
|
||
}
|
||
|
||
// --- Literals ---
|
||
if (strcmp(kind, "interpreted_string_literal") == 0 ||
|
||
strcmp(kind, "raw_string_literal") == 0) {
|
||
return cbm_type_builtin(ctx->arena, "string");
|
||
}
|
||
if (strcmp(kind, "int_literal") == 0) {
|
||
return cbm_type_builtin(ctx->arena, "int");
|
||
}
|
||
if (strcmp(kind, "float_literal") == 0) {
|
||
return cbm_type_builtin(ctx->arena, "float64");
|
||
}
|
||
if (strcmp(kind, "true") == 0 || strcmp(kind, "false") == 0) {
|
||
return cbm_type_builtin(ctx->arena, "bool");
|
||
}
|
||
if (strcmp(kind, "nil") == 0) {
|
||
return cbm_type_unknown(); // nil has no concrete type
|
||
}
|
||
|
||
// --- Func literal (closure) ---
|
||
if (strcmp(kind, "func_literal") == 0) {
|
||
// Process the closure body to resolve calls with captured scope
|
||
TSNode body = ts_node_child_by_field_name(node, "body", 4);
|
||
if (!ts_node_is_null(body)) {
|
||
// Push child scope (inherits all outer bindings via parent chain)
|
||
CBMScope* saved = ctx->current_scope;
|
||
ctx->current_scope = cbm_scope_push(ctx->arena, ctx->current_scope);
|
||
|
||
// Bind closure parameters
|
||
TSNode params = ts_node_child_by_field_name(node, "parameters", 10);
|
||
if (!ts_node_is_null(params)) {
|
||
uint32_t nc = ts_node_child_count(params);
|
||
for (uint32_t i = 0; i < nc; i++) {
|
||
TSNode param = ts_node_child(params, i);
|
||
if (ts_node_is_null(param) || !ts_node_is_named(param)) continue;
|
||
if (strcmp(ts_node_type(param), "parameter_declaration") != 0) continue;
|
||
TSNode type_node = ts_node_child_by_field_name(param, "type", 4);
|
||
const CBMType* pt = go_parse_type_node(ctx, type_node);
|
||
uint32_t pnc = ts_node_child_count(param);
|
||
for (uint32_t j = 0; j < pnc; j++) {
|
||
TSNode ch = ts_node_child(param, j);
|
||
if (!ts_node_is_null(ch) && ts_node_is_named(ch) &&
|
||
strcmp(ts_node_type(ch), "identifier") == 0) {
|
||
char* pname = lsp_node_text(ctx, ch);
|
||
if (pname && strcmp(pname, "_") != 0)
|
||
cbm_scope_bind(ctx->current_scope, pname, pt);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Walk closure body to resolve calls
|
||
resolve_calls_in_node(ctx, body);
|
||
|
||
ctx->current_scope = saved;
|
||
}
|
||
|
||
// Build full FUNC type with param/return types from AST
|
||
const CBMType* pt_arr[16];
|
||
int pt_count = 0;
|
||
TSNode params2 = ts_node_child_by_field_name(node, "parameters", 10);
|
||
if (!ts_node_is_null(params2)) {
|
||
uint32_t nc2 = ts_node_child_count(params2);
|
||
for (uint32_t i = 0; i < nc2 && pt_count < 15; i++) {
|
||
TSNode p = ts_node_child(params2, i);
|
||
if (ts_node_is_null(p) || !ts_node_is_named(p)) continue;
|
||
if (strcmp(ts_node_type(p), "parameter_declaration") != 0) continue;
|
||
TSNode pt = ts_node_child_by_field_name(p, "type", 4);
|
||
if (!ts_node_is_null(pt))
|
||
pt_arr[pt_count++] = go_parse_type_node(ctx, pt);
|
||
}
|
||
}
|
||
pt_arr[pt_count] = NULL;
|
||
|
||
const CBMType* rt_arr[16];
|
||
int rt_count = 0;
|
||
TSNode result = ts_node_child_by_field_name(node, "result", 6);
|
||
if (!ts_node_is_null(result)) {
|
||
if (strcmp(ts_node_type(result), "parameter_list") == 0) {
|
||
uint32_t rnc = ts_node_child_count(result);
|
||
for (uint32_t i = 0; i < rnc && rt_count < 15; i++) {
|
||
TSNode rc = ts_node_child(result, i);
|
||
if (ts_node_is_null(rc) || !ts_node_is_named(rc)) continue;
|
||
TSNode rt = ts_node_child_by_field_name(rc, "type", 4);
|
||
if (ts_node_is_null(rt)) rt = rc;
|
||
rt_arr[rt_count++] = go_parse_type_node(ctx, rt);
|
||
}
|
||
} else {
|
||
rt_arr[rt_count++] = go_parse_type_node(ctx, result);
|
||
}
|
||
}
|
||
rt_arr[rt_count] = NULL;
|
||
|
||
return cbm_type_func(ctx->arena, NULL,
|
||
pt_count > 0 ? (const CBMType**)pt_arr : NULL,
|
||
rt_count > 0 ? (const CBMType**)rt_arr : NULL);
|
||
}
|
||
|
||
return cbm_type_unknown();
|
||
}
|
||
|
||
// --- go_eval_builtin_call ---
|
||
|
||
const CBMType* go_eval_builtin_call(GoLSPContext* ctx, const char* name, TSNode args) {
|
||
// make(Type, ...) -> Type
|
||
if (strcmp(name, "make") == 0 && !ts_node_is_null(args)) {
|
||
uint32_t nc = ts_node_named_child_count(args);
|
||
if (nc > 0) {
|
||
TSNode first_arg = ts_node_named_child(args, 0);
|
||
return go_parse_type_node(ctx, first_arg);
|
||
}
|
||
}
|
||
|
||
// new(Type) -> *Type
|
||
if (strcmp(name, "new") == 0 && !ts_node_is_null(args)) {
|
||
uint32_t nc = ts_node_named_child_count(args);
|
||
if (nc > 0) {
|
||
TSNode first_arg = ts_node_named_child(args, 0);
|
||
return cbm_type_pointer(ctx->arena, go_parse_type_node(ctx, first_arg));
|
||
}
|
||
}
|
||
|
||
// append(slice, ...) -> same slice type
|
||
if (strcmp(name, "append") == 0 && !ts_node_is_null(args)) {
|
||
uint32_t nc = ts_node_named_child_count(args);
|
||
if (nc > 0) {
|
||
return go_eval_expr_type(ctx, ts_node_named_child(args, 0));
|
||
}
|
||
}
|
||
|
||
// len, cap -> int
|
||
if (strcmp(name, "len") == 0 || strcmp(name, "cap") == 0) {
|
||
return cbm_type_builtin(ctx->arena, "int");
|
||
}
|
||
|
||
// delete -> void (no return)
|
||
if (strcmp(name, "delete") == 0) {
|
||
return cbm_type_unknown();
|
||
}
|
||
|
||
return cbm_type_unknown();
|
||
}
|
||
|
||
// --- go_lookup_field: struct field lookup with embedding recursion ---
|
||
|
||
static const CBMType* go_lookup_field(GoLSPContext* 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) return NULL;
|
||
|
||
// Follow alias chain
|
||
if (rt->alias_of) return go_lookup_field(ctx, rt->alias_of, field_name, depth + 1);
|
||
|
||
// Direct field lookup
|
||
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];
|
||
}
|
||
}
|
||
}
|
||
|
||
// Promoted fields from embedded types
|
||
if (rt->embedded_types) {
|
||
for (int i = 0; rt->embedded_types[i]; i++) {
|
||
const CBMType* f = go_lookup_field(ctx, rt->embedded_types[i], field_name, depth + 1);
|
||
if (f) return f;
|
||
}
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
// --- go_lookup_field_or_method: method sets + embedding ---
|
||
|
||
static const CBMRegisteredFunc* go_lookup_field_or_method_depth(GoLSPContext* 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
|
||
const CBMRegisteredFunc* f = cbm_registry_lookup_method(ctx->registry, type_qn, member_name);
|
||
if (f) return f;
|
||
|
||
const CBMRegisteredType* rt = cbm_registry_lookup_type(ctx->registry, type_qn);
|
||
if (rt) {
|
||
// Follow type alias chain
|
||
if (rt->alias_of) {
|
||
f = go_lookup_field_or_method_depth(ctx, rt->alias_of, member_name, depth + 1);
|
||
if (f) return f;
|
||
}
|
||
|
||
// Check embedded types (promoted methods)
|
||
if (rt->embedded_types) {
|
||
for (int i = 0; rt->embedded_types[i]; i++) {
|
||
f = go_lookup_field_or_method_depth(ctx, rt->embedded_types[i], member_name, depth + 1);
|
||
if (f) return f;
|
||
}
|
||
}
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
const CBMRegisteredFunc* go_lookup_field_or_method(GoLSPContext* ctx,
|
||
const char* type_qn, const char* member_name) {
|
||
return go_lookup_field_or_method_depth(ctx, type_qn, member_name, 0);
|
||
}
|
||
|
||
// --- go_process_statement: bind variables from statements ---
|
||
|
||
void go_process_statement(GoLSPContext* ctx, TSNode node) {
|
||
if (ts_node_is_null(node)) return;
|
||
const char* kind = ts_node_type(node);
|
||
|
||
// short_var_declaration: a, b := expr
|
||
if (strcmp(kind, "short_var_declaration") == 0) {
|
||
TSNode left = ts_node_child_by_field_name(node, "left", 4);
|
||
TSNode right = ts_node_child_by_field_name(node, "right", 5);
|
||
if (ts_node_is_null(left) || ts_node_is_null(right)) return;
|
||
|
||
const CBMType* rhs_type = NULL;
|
||
|
||
// Check if RHS is an expression_list (multiple values)
|
||
if (strcmp(ts_node_type(right), "expression_list") == 0) {
|
||
uint32_t rhs_count = ts_node_named_child_count(right);
|
||
if (rhs_count == 1) {
|
||
// Single expression that might return a tuple (multi-return)
|
||
rhs_type = go_eval_expr_type(ctx, ts_node_named_child(right, 0));
|
||
}
|
||
} else {
|
||
rhs_type = go_eval_expr_type(ctx, right);
|
||
}
|
||
|
||
// Bind left-hand side variables
|
||
if (strcmp(ts_node_type(left), "expression_list") == 0) {
|
||
uint32_t lhs_count = ts_node_named_child_count(left);
|
||
for (uint32_t i = 0; i < lhs_count; i++) {
|
||
TSNode lhs_var = ts_node_named_child(left, i);
|
||
if (strcmp(ts_node_type(lhs_var), "identifier") != 0) continue;
|
||
char* var_name = lsp_node_text(ctx, lhs_var);
|
||
if (!var_name || strcmp(var_name, "_") == 0) continue;
|
||
|
||
const CBMType* var_type = cbm_type_unknown();
|
||
if (rhs_type) {
|
||
if (rhs_type->kind == CBM_TYPE_TUPLE && (int)i < rhs_type->data.tuple.count) {
|
||
var_type = rhs_type->data.tuple.elems[i];
|
||
} else if (i == 0) {
|
||
var_type = rhs_type;
|
||
}
|
||
}
|
||
cbm_scope_bind(ctx->current_scope, var_name, var_type);
|
||
}
|
||
} else if (strcmp(ts_node_type(left), "identifier") == 0) {
|
||
char* var_name = lsp_node_text(ctx, left);
|
||
if (var_name && strcmp(var_name, "_") != 0 && rhs_type) {
|
||
cbm_scope_bind(ctx->current_scope, var_name, rhs_type);
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
|
||
// var_spec: var x Type = expr OR var a, b, c Type = expr
|
||
if (strcmp(kind, "var_spec") == 0) {
|
||
TSNode type_node = ts_node_child_by_field_name(node, "type", 4);
|
||
TSNode value_node = ts_node_child_by_field_name(node, "value", 5);
|
||
|
||
const CBMType* var_type = cbm_type_unknown();
|
||
if (!ts_node_is_null(type_node)) {
|
||
var_type = go_parse_type_node(ctx, type_node);
|
||
} else if (!ts_node_is_null(value_node)) {
|
||
if (strcmp(ts_node_type(value_node), "expression_list") == 0 &&
|
||
ts_node_named_child_count(value_node) > 0) {
|
||
var_type = go_eval_expr_type(ctx, ts_node_named_child(value_node, 0));
|
||
} else {
|
||
var_type = go_eval_expr_type(ctx, value_node);
|
||
}
|
||
}
|
||
|
||
// Bind all name identifiers (handles: var a, b, c int)
|
||
uint32_t vnc = ts_node_child_count(node);
|
||
for (uint32_t vi = 0; vi < vnc; vi++) {
|
||
TSNode ch = ts_node_child(node, vi);
|
||
if (ts_node_is_null(ch) || !ts_node_is_named(ch)) continue;
|
||
if (strcmp(ts_node_type(ch), "identifier") == 0) {
|
||
char* var_name = lsp_node_text(ctx, ch);
|
||
if (var_name && strcmp(var_name, "_") != 0) {
|
||
cbm_scope_bind(ctx->current_scope, var_name, var_type);
|
||
}
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
|
||
// const_spec: const x Type = expr OR const x = expr
|
||
if (strcmp(kind, "const_spec") == 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);
|
||
TSNode value_node = ts_node_child_by_field_name(node, "value", 5);
|
||
if (ts_node_is_null(name_node)) return;
|
||
|
||
const CBMType* const_type = cbm_type_unknown();
|
||
if (!ts_node_is_null(type_node)) {
|
||
const_type = go_parse_type_node(ctx, type_node);
|
||
} else if (!ts_node_is_null(value_node)) {
|
||
// For expression_list values, evaluate first element
|
||
if (strcmp(ts_node_type(value_node), "expression_list") == 0 &&
|
||
ts_node_named_child_count(value_node) > 0) {
|
||
const_type = go_eval_expr_type(ctx, ts_node_named_child(value_node, 0));
|
||
} else {
|
||
const_type = go_eval_expr_type(ctx, value_node);
|
||
}
|
||
}
|
||
|
||
if (strcmp(ts_node_type(name_node), "identifier") == 0) {
|
||
char* name = lsp_node_text(ctx, name_node);
|
||
if (name && strcmp(name, "_") != 0)
|
||
cbm_scope_bind(ctx->current_scope, name, const_type);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// range_clause: for k, v := range container
|
||
if (strcmp(kind, "range_clause") == 0) {
|
||
TSNode left = ts_node_child_by_field_name(node, "left", 4);
|
||
TSNode right = ts_node_child_by_field_name(node, "right", 5);
|
||
if (ts_node_is_null(right)) return;
|
||
|
||
const CBMType* container_type = go_eval_expr_type(ctx, right);
|
||
|
||
// Determine key and value types based on container type
|
||
const CBMType* key_type = cbm_type_unknown();
|
||
const CBMType* val_type = cbm_type_unknown();
|
||
|
||
if (container_type) {
|
||
switch (container_type->kind) {
|
||
case CBM_TYPE_SLICE:
|
||
key_type = cbm_type_builtin(ctx->arena, "int");
|
||
val_type = container_type->data.slice.elem;
|
||
break;
|
||
case CBM_TYPE_MAP:
|
||
key_type = container_type->data.map.key;
|
||
val_type = container_type->data.map.value;
|
||
break;
|
||
case CBM_TYPE_CHANNEL:
|
||
val_type = container_type->data.channel.elem;
|
||
break;
|
||
default:
|
||
if (container_type->kind == CBM_TYPE_BUILTIN &&
|
||
strcmp(container_type->data.builtin.name, "string") == 0) {
|
||
key_type = cbm_type_builtin(ctx->arena, "int");
|
||
val_type = cbm_type_builtin(ctx->arena, "rune");
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!ts_node_is_null(left) && strcmp(ts_node_type(left), "expression_list") == 0) {
|
||
uint32_t lhs_count = ts_node_named_child_count(left);
|
||
for (uint32_t i = 0; i < lhs_count; i++) {
|
||
TSNode var_node = ts_node_named_child(left, i);
|
||
if (strcmp(ts_node_type(var_node), "identifier") != 0) continue;
|
||
char* var_name = lsp_node_text(ctx, var_node);
|
||
if (!var_name || strcmp(var_name, "_") == 0) continue;
|
||
cbm_scope_bind(ctx->current_scope, var_name, i == 0 ? key_type : val_type);
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
|
||
// type_switch_statement is handled in resolve_calls_in_node, not here
|
||
// (needs per-case scope narrowing, not just variable binding)
|
||
}
|
||
|
||
// --- Emit a resolved call ---
|
||
|
||
static void emit_resolved_call(GoLSPContext* ctx, const char* callee_qn, 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;
|
||
rc.reason = NULL;
|
||
cbm_resolvedcall_push(ctx->resolved_calls, ctx->arena, rc);
|
||
}
|
||
|
||
// Emit a diagnostic for an unresolved call (confidence 0.0).
|
||
static void emit_unresolved_call(GoLSPContext* 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);
|
||
}
|
||
|
||
// --- Walk call expressions and resolve them ---
|
||
|
||
static void resolve_calls_in_node_inner(GoLSPContext* ctx, TSNode node) {
|
||
if (ts_node_is_null(node)) return;
|
||
const char* kind = ts_node_type(node);
|
||
|
||
// Process statements to build scope
|
||
go_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);
|
||
|
||
// selector_expression: obj.Method() or pkg.Func()
|
||
if (strcmp(fk, "selector_expression") == 0) {
|
||
TSNode operand = ts_node_child_by_field_name(func_node, "operand", 7);
|
||
TSNode field = ts_node_child_by_field_name(func_node, "field", 5);
|
||
if (!ts_node_is_null(operand) && !ts_node_is_null(field)) {
|
||
char* field_name = lsp_node_text(ctx, field);
|
||
|
||
// Check if operand is a package import
|
||
if (strcmp(ts_node_type(operand), "identifier") == 0) {
|
||
char* pkg_name = lsp_node_text(ctx, operand);
|
||
if (pkg_name) {
|
||
const char* pkg_qn = resolve_import(ctx, pkg_name);
|
||
if (pkg_qn && field_name) {
|
||
const CBMRegisteredFunc* f = cbm_registry_lookup_symbol(ctx->registry, pkg_qn, field_name);
|
||
if (f) {
|
||
emit_resolved_call(ctx, f->qualified_name, "lsp_direct", 0.95f);
|
||
goto recurse;
|
||
}
|
||
// Package found but symbol not in registry
|
||
emit_unresolved_call(ctx,
|
||
cbm_arena_sprintf(ctx->arena, "%s.%s", pkg_name, field_name),
|
||
"symbol_not_in_registry");
|
||
goto recurse;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Type-based method dispatch
|
||
if (field_name) {
|
||
const CBMType* recv_type = go_eval_expr_type(ctx, operand);
|
||
const CBMType* base = recv_type;
|
||
if (base && base->kind == CBM_TYPE_POINTER) base = cbm_type_deref(base);
|
||
|
||
if (base && base->kind == CBM_TYPE_NAMED) {
|
||
const CBMRegisteredFunc* method = go_lookup_field_or_method(ctx,
|
||
base->data.named.qualified_name, field_name);
|
||
if (method) {
|
||
const char* strategy = "lsp_type_dispatch";
|
||
if (method->receiver_type &&
|
||
strcmp(method->receiver_type, base->data.named.qualified_name) != 0) {
|
||
strategy = "lsp_embed_dispatch";
|
||
}
|
||
emit_resolved_call(ctx, method->qualified_name, strategy, 0.95f);
|
||
goto recurse;
|
||
}
|
||
}
|
||
|
||
// Interface dispatch: NAMED type that is an interface, or bare INTERFACE type
|
||
if (base && field_name) {
|
||
bool is_iface = (base->kind == CBM_TYPE_INTERFACE);
|
||
const char* iface_qn = NULL;
|
||
if (!is_iface && base->kind == CBM_TYPE_NAMED) {
|
||
const CBMRegisteredType* rt = cbm_registry_lookup_type(ctx->registry,
|
||
base->data.named.qualified_name);
|
||
if (rt && rt->is_interface) {
|
||
is_iface = true;
|
||
iface_qn = base->data.named.qualified_name;
|
||
}
|
||
}
|
||
if (is_iface) {
|
||
// Try interface satisfaction: find concrete types implementing this interface
|
||
const CBMRegisteredType* iface_rt = iface_qn ?
|
||
cbm_registry_lookup_type(ctx->registry, iface_qn) : NULL;
|
||
if (iface_rt && iface_rt->method_names && iface_rt->method_names[0]) {
|
||
// Count interface methods
|
||
int iface_mcount = 0;
|
||
while (iface_rt->method_names[iface_mcount]) iface_mcount++;
|
||
|
||
// Scan all registered types for satisfaction
|
||
const char* sole_impl_qn = NULL;
|
||
int impl_count = 0;
|
||
// Skip stdlib types when interface is from a project package
|
||
bool iface_is_project = iface_qn && strchr(iface_qn, '/') != NULL;
|
||
for (int ti = 0; ti < ctx->registry->type_count && impl_count < 2; ti++) {
|
||
const CBMRegisteredType* cand = &ctx->registry->types[ti];
|
||
if (cand->is_interface) continue;
|
||
if (!cand->qualified_name) continue;
|
||
if (cand->alias_of) continue;
|
||
// For project interfaces, skip stdlib candidates (no '/' in QN)
|
||
if (iface_is_project && !strchr(cand->qualified_name, '/')) continue;
|
||
|
||
// Check if candidate has all interface methods
|
||
bool satisfies = true;
|
||
for (int mi = 0; mi < iface_mcount; mi++) {
|
||
if (!cbm_registry_lookup_method(ctx->registry,
|
||
cand->qualified_name, iface_rt->method_names[mi])) {
|
||
satisfies = false;
|
||
break;
|
||
}
|
||
}
|
||
if (satisfies) {
|
||
sole_impl_qn = cand->qualified_name;
|
||
impl_count++;
|
||
}
|
||
}
|
||
|
||
if (impl_count == 1 && sole_impl_qn) {
|
||
// Single implementer: resolve to concrete method
|
||
const CBMRegisteredFunc* concrete_method =
|
||
cbm_registry_lookup_method(ctx->registry, sole_impl_qn, field_name);
|
||
if (concrete_method) {
|
||
// Sole-implementer interface dispatch is an unambiguous
|
||
// resolution (exactly one concrete method); rank it at least
|
||
// as high as a direct type dispatch (0.95) so the concrete
|
||
// `Type.method` wins over the interface-method type_dispatch
|
||
// for the same call site.
|
||
emit_resolved_call(ctx, concrete_method->qualified_name,
|
||
"lsp_interface_resolve", 0.95f);
|
||
goto recurse;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Fallback: generic interface dispatch
|
||
emit_resolved_call(ctx,
|
||
cbm_arena_sprintf(ctx->arena, "%s.%s",
|
||
iface_qn ? iface_qn : "interface", field_name),
|
||
"lsp_interface_dispatch", 0.85f);
|
||
goto recurse;
|
||
}
|
||
}
|
||
|
||
// Type resolved to NAMED but neither method nor interface matched
|
||
if (base && base->kind == CBM_TYPE_NAMED) {
|
||
emit_unresolved_call(ctx,
|
||
cbm_arena_sprintf(ctx->arena, "%s.%s",
|
||
base->data.named.qualified_name, field_name),
|
||
"method_not_found");
|
||
} else if (cbm_type_is_unknown(recv_type)) {
|
||
char* operand_text = lsp_node_text(ctx, operand);
|
||
emit_unresolved_call(ctx,
|
||
cbm_arena_sprintf(ctx->arena, "%s.%s",
|
||
operand_text ? operand_text : "?", field_name),
|
||
"unknown_receiver_type");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Direct function call: FuncName()
|
||
if (strcmp(fk, "identifier") == 0) {
|
||
char* name = lsp_node_text(ctx, func_node);
|
||
if (name && !is_go_builtin_func(name)) {
|
||
// Package-local function
|
||
const CBMRegisteredFunc* f = cbm_registry_lookup_symbol(ctx->registry, ctx->package_qn, name);
|
||
if (f) {
|
||
emit_resolved_call(ctx, f->qualified_name, "lsp_direct", 0.95f);
|
||
} else {
|
||
emit_unresolved_call(ctx, name, "function_not_in_registry");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
recurse:;
|
||
// Push scope for blocks and statements that introduce variables
|
||
bool push_scope = (strcmp(kind, "block") == 0 ||
|
||
strcmp(kind, "if_statement") == 0 ||
|
||
strcmp(kind, "for_statement") == 0 ||
|
||
strcmp(kind, "expression_switch_statement") == 0);
|
||
if (push_scope) {
|
||
ctx->current_scope = cbm_scope_push(ctx->arena, ctx->current_scope);
|
||
}
|
||
|
||
// Process initializer field for if/for/switch statements.
|
||
// Go patterns: if err := f(); ..., for i := 0; ..., switch v := x; v { ... }
|
||
if (strcmp(kind, "if_statement") == 0 ||
|
||
strcmp(kind, "for_statement") == 0 ||
|
||
strcmp(kind, "expression_switch_statement") == 0) {
|
||
TSNode init = ts_node_child_by_field_name(node, "initializer", 11);
|
||
if (!ts_node_is_null(init)) {
|
||
go_process_statement(ctx, init);
|
||
}
|
||
}
|
||
|
||
// Process for_statement range_clause before recursing into body
|
||
if (strcmp(kind, "for_statement") == 0) {
|
||
for (uint32_t i = 0; i < ts_node_child_count(node); i++) {
|
||
TSNode child = ts_node_child(node, i);
|
||
if (!ts_node_is_null(child) && ts_node_is_named(child) &&
|
||
strcmp(ts_node_type(child), "range_clause") == 0) {
|
||
go_process_statement(ctx, child);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Type switch: switch a := expr.(type) { case *T: a.Method() }
|
||
// Go tree-sitter structure: type_switch_statement has flat children:
|
||
// expression_list (contains var name "a")
|
||
// identifier (operand "animal")
|
||
// type_case (case clause, may repeat)
|
||
if (strcmp(kind, "type_switch_statement") == 0) {
|
||
const char* switch_var = NULL;
|
||
// Pass 1: find switch variable and operand type
|
||
uint32_t nc2 = ts_node_child_count(node);
|
||
bool found_assign = false;
|
||
for (uint32_t i = 0; i < nc2; i++) {
|
||
TSNode child = ts_node_child(node, i);
|
||
if (ts_node_is_null(child)) continue;
|
||
const char* ck = ts_node_type(child);
|
||
|
||
// expression_list before := is the LHS (variable name)
|
||
if (!found_assign && strcmp(ck, "expression_list") == 0) {
|
||
TSNode var_node = ts_node_named_child(child, 0);
|
||
if (!ts_node_is_null(var_node) && strcmp(ts_node_type(var_node), "identifier") == 0)
|
||
switch_var = lsp_node_text(ctx, var_node);
|
||
}
|
||
// := operator marks the assignment
|
||
if (!ts_node_is_named(child)) {
|
||
char* tok = lsp_node_text(ctx, child);
|
||
if (tok && strcmp(tok, ":=") == 0) found_assign = true;
|
||
}
|
||
// identifier after := is the operand being type-switched
|
||
if (found_assign && strcmp(ck, "identifier") == 0) {
|
||
(void)go_eval_expr_type(ctx, child);
|
||
}
|
||
}
|
||
|
||
// Pass 2: process each type_case with narrowed scope
|
||
for (uint32_t i = 0; i < nc2; i++) {
|
||
TSNode child = ts_node_child(node, i);
|
||
if (ts_node_is_null(child) || !ts_node_is_named(child)) continue;
|
||
if (strcmp(ts_node_type(child), "type_case") != 0) continue;
|
||
|
||
CBMScope* saved = ctx->current_scope;
|
||
ctx->current_scope = cbm_scope_push(ctx->arena, ctx->current_scope);
|
||
|
||
// Find case type and bind switch variable
|
||
uint32_t cc_count = ts_node_child_count(child);
|
||
for (uint32_t j = 0; j < cc_count; j++) {
|
||
TSNode cc_child = ts_node_child(child, j);
|
||
if (ts_node_is_null(cc_child) || !ts_node_is_named(cc_child)) continue;
|
||
const char* cc_kind = ts_node_type(cc_child);
|
||
if (strcmp(cc_kind, "type_identifier") == 0 ||
|
||
strcmp(cc_kind, "qualified_type") == 0 ||
|
||
strcmp(cc_kind, "pointer_type") == 0 ||
|
||
strcmp(cc_kind, "slice_type") == 0) {
|
||
if (switch_var) {
|
||
cbm_scope_bind(ctx->current_scope, switch_var,
|
||
go_parse_type_node(ctx, cc_child));
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
// Recurse into case body statements
|
||
for (uint32_t j = 0; j < cc_count; j++) {
|
||
TSNode cc_child = ts_node_child(child, j);
|
||
if (ts_node_is_null(cc_child) || !ts_node_is_named(cc_child)) continue;
|
||
const char* cc_kind = ts_node_type(cc_child);
|
||
// Skip type nodes, process everything else (expression_statement, etc.)
|
||
if (strcmp(cc_kind, "type_identifier") == 0 ||
|
||
strcmp(cc_kind, "qualified_type") == 0 ||
|
||
strcmp(cc_kind, "pointer_type") == 0 ||
|
||
strcmp(cc_kind, "slice_type") == 0) continue;
|
||
resolve_calls_in_node(ctx, cc_child);
|
||
}
|
||
|
||
ctx->current_scope = saved;
|
||
}
|
||
|
||
if (push_scope) ctx->current_scope = cbm_scope_pop(ctx->current_scope);
|
||
return;
|
||
}
|
||
|
||
// Select statement: each communication_case gets its own scope
|
||
// case msg := <-ch: → receive_statement has left (vars) + unary_expression (<-ch)
|
||
if (strcmp(kind, "select_statement") == 0) {
|
||
uint32_t nc3 = ts_node_child_count(node);
|
||
for (uint32_t i = 0; i < nc3; i++) {
|
||
TSNode child = ts_node_child(node, i);
|
||
if (ts_node_is_null(child) || !ts_node_is_named(child)) continue;
|
||
const char* ck3 = ts_node_type(child);
|
||
|
||
if (strcmp(ck3, "communication_case") == 0 || strcmp(ck3, "default_case") == 0) {
|
||
CBMScope* saved = ctx->current_scope;
|
||
ctx->current_scope = cbm_scope_push(ctx->arena, ctx->current_scope);
|
||
|
||
// Process children: receive_statement binds vars, then recurse body
|
||
uint32_t cc_count = ts_node_child_count(child);
|
||
for (uint32_t j = 0; j < cc_count; j++) {
|
||
TSNode cc_child = ts_node_child(child, j);
|
||
if (ts_node_is_null(cc_child) || !ts_node_is_named(cc_child)) continue;
|
||
const char* cc_kind = ts_node_type(cc_child);
|
||
|
||
if (strcmp(cc_kind, "receive_statement") == 0) {
|
||
// receive_statement: left := <-right
|
||
// tree-sitter Go: right field is the channel expression (after <-)
|
||
// The receive value type is the channel's element type
|
||
TSNode left = ts_node_child_by_field_name(cc_child, "left", 4);
|
||
TSNode right = ts_node_child_by_field_name(cc_child, "right", 5);
|
||
if (!ts_node_is_null(right)) {
|
||
const CBMType* right_type = go_eval_expr_type(ctx, right);
|
||
// right_type might be a channel (if right is the channel)
|
||
// or already the elem type (if right is a <-ch unary expr)
|
||
const CBMType* recv_type = right_type;
|
||
if (right_type && right_type->kind == CBM_TYPE_CHANNEL) {
|
||
recv_type = right_type->data.channel.elem;
|
||
}
|
||
if (!ts_node_is_null(left)) {
|
||
if (strcmp(ts_node_type(left), "expression_list") == 0) {
|
||
uint32_t lhs_count = ts_node_named_child_count(left);
|
||
for (uint32_t k = 0; k < lhs_count; k++) {
|
||
TSNode var_node = ts_node_named_child(left, k);
|
||
if (strcmp(ts_node_type(var_node), "identifier") != 0) continue;
|
||
char* var_name = lsp_node_text(ctx, var_node);
|
||
if (!var_name || strcmp(var_name, "_") == 0) continue;
|
||
if (k == 0) {
|
||
cbm_scope_bind(ctx->current_scope, var_name, recv_type);
|
||
} else {
|
||
// second var is the ok bool
|
||
cbm_scope_bind(ctx->current_scope, var_name,
|
||
cbm_type_builtin(ctx->arena, "bool"));
|
||
}
|
||
}
|
||
} else if (strcmp(ts_node_type(left), "identifier") == 0) {
|
||
char* var_name = lsp_node_text(ctx, left);
|
||
if (var_name && strcmp(var_name, "_") != 0) {
|
||
cbm_scope_bind(ctx->current_scope, var_name, recv_type);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
// Body statements
|
||
resolve_calls_in_node(ctx, cc_child);
|
||
}
|
||
}
|
||
|
||
ctx->current_scope = saved;
|
||
}
|
||
}
|
||
if (push_scope) ctx->current_scope = cbm_scope_pop(ctx->current_scope);
|
||
return;
|
||
}
|
||
|
||
// Recurse into children via a cursor (O(n)); ts_node_child(node,i) is O(i)
|
||
// → O(n²) on a wide node.
|
||
{
|
||
TSTreeCursor cursor = ts_tree_cursor_new(node);
|
||
if (ts_tree_cursor_goto_first_child(&cursor)) {
|
||
do {
|
||
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 a single function body ---
|
||
|
||
static void process_function(GoLSPContext* ctx, TSNode func_node) {
|
||
// Set enclosing function QN
|
||
TSNode name_node = ts_node_child_by_field_name(func_node, "name", 4);
|
||
if (ts_node_is_null(name_node)) return;
|
||
|
||
char* func_name = lsp_node_text(ctx, name_node);
|
||
if (!func_name || !func_name[0]) return;
|
||
|
||
// For methods, the enclosing-function QN must include the receiver type
|
||
// (package.Type.Method), matching how the textual extractor and the
|
||
// registry qualify the method. Building it as package.Method (no receiver)
|
||
// here made the LSP-resolved call's caller_qn disagree with the textual
|
||
// call's enclosing_func_qn, so cbm_pipeline_find_lsp_resolution never
|
||
// joined them — every call inside a method body silently lost its
|
||
// type-aware LSP strategy. Derive the bare receiver type name the same way
|
||
// the receiver binding below does.
|
||
char* recv_type_name = NULL;
|
||
{
|
||
TSNode recv0 = ts_node_child_by_field_name(func_node, "receiver", 8);
|
||
if (!ts_node_is_null(recv0)) {
|
||
uint32_t rnc0 = ts_node_child_count(recv0);
|
||
for (uint32_t i = 0; i < rnc0 && !recv_type_name; i++) {
|
||
TSNode rp = ts_node_child(recv0, i);
|
||
if (ts_node_is_null(rp) || !ts_node_is_named(rp)) continue;
|
||
if (strcmp(ts_node_type(rp), "parameter_declaration") != 0) continue;
|
||
TSNode rtype = ts_node_child_by_field_name(rp, "type", 4);
|
||
if (ts_node_is_null(rtype)) continue;
|
||
// Unwrap a pointer receiver (*Type) to the bare type identifier.
|
||
const char* rtk = ts_node_type(rtype);
|
||
if (strcmp(rtk, "pointer_type") == 0 && ts_node_named_child_count(rtype) > 0) {
|
||
rtype = ts_node_named_child(rtype, 0);
|
||
}
|
||
char* tn = lsp_node_text(ctx, rtype);
|
||
if (tn && tn[0]) recv_type_name = tn;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (recv_type_name) {
|
||
ctx->enclosing_func_qn =
|
||
cbm_arena_sprintf(ctx->arena, "%s.%s.%s", ctx->package_qn, recv_type_name, func_name);
|
||
} else {
|
||
ctx->enclosing_func_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->package_qn, func_name);
|
||
}
|
||
|
||
// Push function scope
|
||
CBMScope* saved_scope = ctx->current_scope;
|
||
ctx->current_scope = cbm_scope_push(ctx->arena, ctx->current_scope);
|
||
|
||
// Bind parameters into scope (including variadic)
|
||
TSNode params = ts_node_child_by_field_name(func_node, "parameters", 10);
|
||
if (!ts_node_is_null(params)) {
|
||
uint32_t nc = ts_node_child_count(params);
|
||
for (uint32_t i = 0; i < nc; i++) {
|
||
TSNode param = ts_node_child(params, i);
|
||
if (ts_node_is_null(param) || !ts_node_is_named(param)) continue;
|
||
const char* pk = ts_node_type(param);
|
||
|
||
bool is_variadic = (strcmp(pk, "variadic_parameter_declaration") == 0);
|
||
if (!is_variadic && strcmp(pk, "parameter_declaration") != 0) continue;
|
||
|
||
// Get type
|
||
TSNode type_node = ts_node_child_by_field_name(param, "type", 4);
|
||
const CBMType* param_type = go_parse_type_node(ctx, type_node);
|
||
|
||
// Variadic: ...T is []T in the function body
|
||
if (is_variadic && param_type) {
|
||
param_type = cbm_type_slice(ctx->arena, param_type);
|
||
}
|
||
|
||
// Get name(s) — Go allows multiple names per declaration: a, b int
|
||
uint32_t pnc = ts_node_child_count(param);
|
||
for (uint32_t j = 0; j < pnc; j++) {
|
||
TSNode child = ts_node_child(param, j);
|
||
if (ts_node_is_null(child) || !ts_node_is_named(child)) continue;
|
||
if (strcmp(ts_node_type(child), "identifier") == 0) {
|
||
char* pname = lsp_node_text(ctx, child);
|
||
if (pname && strcmp(pname, "_") != 0) {
|
||
cbm_scope_bind(ctx->current_scope, pname, param_type);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Bind named return values into scope
|
||
TSNode result_node = ts_node_child_by_field_name(func_node, "result", 6);
|
||
if (!ts_node_is_null(result_node)) {
|
||
// result can be a parameter_list (named returns) or a simple type
|
||
const char* rk = ts_node_type(result_node);
|
||
if (strcmp(rk, "parameter_list") == 0) {
|
||
uint32_t rnc = ts_node_child_count(result_node);
|
||
for (uint32_t i = 0; i < rnc; i++) {
|
||
TSNode rparam = ts_node_child(result_node, i);
|
||
if (ts_node_is_null(rparam) || !ts_node_is_named(rparam)) continue;
|
||
if (strcmp(ts_node_type(rparam), "parameter_declaration") != 0) continue;
|
||
|
||
TSNode rtype = ts_node_child_by_field_name(rparam, "type", 4);
|
||
const CBMType* ret_type = go_parse_type_node(ctx, rtype);
|
||
|
||
uint32_t rpnc = ts_node_child_count(rparam);
|
||
for (uint32_t j = 0; j < rpnc; j++) {
|
||
TSNode child = ts_node_child(rparam, j);
|
||
if (ts_node_is_null(child) || !ts_node_is_named(child)) continue;
|
||
if (strcmp(ts_node_type(child), "identifier") == 0) {
|
||
char* rname = lsp_node_text(ctx, child);
|
||
if (rname && strcmp(rname, "_") != 0) {
|
||
cbm_scope_bind(ctx->current_scope, rname, ret_type);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Bind receiver for methods
|
||
TSNode recv = ts_node_child_by_field_name(func_node, "receiver", 8);
|
||
if (!ts_node_is_null(recv)) {
|
||
// receiver is a parameter_list with one parameter_declaration
|
||
uint32_t rnc = ts_node_child_count(recv);
|
||
for (uint32_t i = 0; i < rnc; i++) {
|
||
TSNode rp = ts_node_child(recv, i);
|
||
if (ts_node_is_null(rp) || !ts_node_is_named(rp)) continue;
|
||
if (strcmp(ts_node_type(rp), "parameter_declaration") != 0) continue;
|
||
|
||
TSNode rtype = ts_node_child_by_field_name(rp, "type", 4);
|
||
const CBMType* recv_type = go_parse_type_node(ctx, rtype);
|
||
|
||
// Find receiver name
|
||
uint32_t rpnc = ts_node_child_count(rp);
|
||
for (uint32_t j = 0; j < rpnc; j++) {
|
||
TSNode rc = ts_node_child(rp, j);
|
||
if (!ts_node_is_null(rc) && ts_node_is_named(rc) &&
|
||
strcmp(ts_node_type(rc), "identifier") == 0) {
|
||
char* rname = lsp_node_text(ctx, rc);
|
||
if (rname && strcmp(rname, "_") != 0) {
|
||
cbm_scope_bind(ctx->current_scope, rname, recv_type);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Walk function body
|
||
TSNode body = ts_node_child_by_field_name(func_node, "body", 4);
|
||
if (!ts_node_is_null(body)) {
|
||
resolve_calls_in_node(ctx, body);
|
||
}
|
||
|
||
// Restore scope
|
||
ctx->current_scope = saved_scope;
|
||
}
|
||
|
||
// --- Process entire file ---
|
||
|
||
void go_lsp_process_file(GoLSPContext* ctx, TSNode root) {
|
||
if (ts_node_is_null(root)) return;
|
||
|
||
// Collect top-level children once (O(n)); ts_node_child(root,i) is O(i) → O(n²).
|
||
uint32_t kn = 0;
|
||
TSNode* kids = cbm_lsp_collect_children(ctx->arena, root, &kn);
|
||
|
||
// Pass 1: Bind package-level var/const declarations into root scope.
|
||
// Must happen before processing functions so all globals are visible.
|
||
for (uint32_t i = 0; i < kn; i++) {
|
||
TSNode child = kids[i];
|
||
const char* kind = ts_node_type(child);
|
||
|
||
if (strcmp(kind, "var_declaration") == 0 || strcmp(kind, "const_declaration") == 0) {
|
||
uint32_t vnc = ts_node_child_count(child);
|
||
for (uint32_t j = 0; j < vnc; j++) {
|
||
TSNode spec = ts_node_child(child, j);
|
||
if (ts_node_is_null(spec) || !ts_node_is_named(spec)) continue;
|
||
go_process_statement(ctx, spec);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Pass 2: Process function/method bodies
|
||
for (uint32_t i = 0; i < kn; i++) {
|
||
TSNode child = kids[i];
|
||
const char* kind = ts_node_type(child);
|
||
|
||
if (strcmp(kind, "function_declaration") == 0 ||
|
||
strcmp(kind, "method_declaration") == 0) {
|
||
process_function(ctx, child);
|
||
}
|
||
}
|
||
}
|
||
|
||
// --- Helper: parse Go return type text into CBMType ---
|
||
|
||
const CBMType* cbm_parse_return_type_text(CBMArena* a, const char* text, const char* module_qn) {
|
||
if (!text || !text[0]) return cbm_type_unknown();
|
||
|
||
// Pointer: *Foo
|
||
if (text[0] == '*') {
|
||
return cbm_type_pointer(a, cbm_parse_return_type_text(a, text + 1, module_qn));
|
||
}
|
||
|
||
// Slice: []Foo
|
||
if (text[0] == '[' && text[1] == ']') {
|
||
return cbm_type_slice(a, cbm_parse_return_type_text(a, text + 2, module_qn));
|
||
}
|
||
|
||
// Builtin types
|
||
static const char* builtins[] = {
|
||
"int","int8","int16","int32","int64",
|
||
"uint","uint8","uint16","uint32","uint64",
|
||
"float32","float64","string","bool","byte","rune","error",
|
||
"any","uintptr",
|
||
NULL
|
||
};
|
||
for (const char** b = builtins; *b; b++) {
|
||
if (strcmp(text, *b) == 0) return cbm_type_builtin(a, text);
|
||
}
|
||
|
||
// Named type: assume local to module
|
||
return cbm_type_named(a, cbm_arena_sprintf(a, "%s.%s", module_qn, text));
|
||
}
|
||
|
||
// --- Entry point: build registry from file defs + run LSP ---
|
||
|
||
void cbm_run_go_lsp(CBMArena* arena, CBMFileResult* result,
|
||
const char* source, int source_len, TSNode root) {
|
||
|
||
CBMTypeRegistry reg;
|
||
cbm_registry_init(®, arena);
|
||
|
||
// Register Go stdlib types/functions
|
||
cbm_go_stdlib_register(®, arena);
|
||
|
||
const char* module_qn = result->module_qn;
|
||
|
||
// Phase 1: Register all types and functions from the 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;
|
||
|
||
// Register every type-like container (Class/Struct/Type/Interface/Enum/
|
||
// Trait). Struct included so a Go `type T struct {...}` (now labelled
|
||
// "Struct") is registered as a type and its methods/embedding resolve.
|
||
if (cbm_label_is_type_like(d->label)) {
|
||
CBMRegisteredType rt;
|
||
memset(&rt, 0, sizeof(rt));
|
||
rt.qualified_name = d->qualified_name;
|
||
rt.short_name = d->name;
|
||
rt.is_interface = d->label && strcmp(d->label, "Interface") == 0;
|
||
|
||
// Populate embedded_types from base_classes (Go struct embedding)
|
||
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];
|
||
// Strip pointer prefix for embedded *Type
|
||
while (bc[0] == '*') bc++;
|
||
// Qualify the embedded type name
|
||
embedded[j] = cbm_arena_sprintf(arena, "%s.%s", module_qn, bc);
|
||
}
|
||
embedded[bc_count] = NULL;
|
||
rt.embedded_types = embedded;
|
||
}
|
||
}
|
||
|
||
cbm_registry_add_type(®, rt);
|
||
}
|
||
|
||
// Register Function/Method nodes
|
||
if (d->label && (strcmp(d->label, "Function") == 0 || strcmp(d->label, "Method") == 0)) {
|
||
CBMRegisteredFunc rf;
|
||
memset(&rf, 0, sizeof(rf));
|
||
rf.qualified_name = d->qualified_name;
|
||
rf.short_name = d->name;
|
||
|
||
// Build FUNC type from return_types
|
||
const CBMType** ret_types = NULL;
|
||
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] = cbm_parse_return_type_text(arena, d->return_types[j], module_qn);
|
||
}
|
||
ret_types[count] = NULL;
|
||
}
|
||
} else if (d->return_type && d->return_type[0]) {
|
||
// Fallback: single return_type string
|
||
ret_types = (const CBMType**)cbm_arena_alloc(arena, 2 * sizeof(const CBMType*));
|
||
ret_types[0] = cbm_parse_return_type_text(arena, d->return_type, module_qn);
|
||
ret_types[1] = NULL;
|
||
}
|
||
|
||
// Build param types from param_types array
|
||
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] = cbm_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);
|
||
|
||
// For methods, extract receiver type from receiver text
|
||
if (strcmp(d->label, "Method") == 0 && d->receiver && d->receiver[0]) {
|
||
// Receiver format: "(name *Type)" or "(name Type)"
|
||
// Extract type name: skip parens, skip first identifier, strip *
|
||
const char* r = d->receiver;
|
||
while (*r == '(' || *r == ' ') r++;
|
||
// Skip receiver name
|
||
while (*r && *r != ' ' && *r != '*') r++;
|
||
while (*r == ' ' || *r == '*') r++;
|
||
// Now r points to the type name
|
||
const char* end = r;
|
||
while (*end && *end != ')' && *end != ' ') end++;
|
||
if (end > r) {
|
||
char* type_name = cbm_arena_strndup(arena, r, end - r);
|
||
rf.receiver_type = cbm_arena_sprintf(arena, "%s.%s", module_qn, type_name);
|
||
|
||
// Also register this method under the type
|
||
const CBMRegisteredType* existing = cbm_registry_lookup_type(®, rf.receiver_type);
|
||
if (!existing) {
|
||
// Auto-create the type entry
|
||
CBMRegisteredType auto_type;
|
||
memset(&auto_type, 0, sizeof(auto_type));
|
||
auto_type.qualified_name = rf.receiver_type;
|
||
auto_type.short_name = type_name;
|
||
cbm_registry_add_type(®, auto_type);
|
||
}
|
||
}
|
||
}
|
||
|
||
cbm_registry_add_func(®, rf);
|
||
}
|
||
}
|
||
|
||
// Phase 1b: Scan AST for struct definitions to populate embedded_types
|
||
{
|
||
// Collect top-level children once (O(n)); ts_node_child(root,i) is O(i) → O(n²).
|
||
uint32_t rkn = 0;
|
||
TSNode* rkids = cbm_lsp_collect_children(arena, root, &rkn);
|
||
for (uint32_t i = 0; i < rkn; i++) {
|
||
TSNode top = rkids[i];
|
||
const char* tk = ts_node_type(top);
|
||
// type_declaration contains type_spec children
|
||
if (strcmp(tk, "type_declaration") != 0) continue;
|
||
uint32_t td_nc = ts_node_child_count(top);
|
||
for (uint32_t j = 0; j < td_nc; j++) {
|
||
TSNode spec = ts_node_child(top, j);
|
||
if (ts_node_is_null(spec) || !ts_node_is_named(spec)) continue;
|
||
const char* spec_kind = ts_node_type(spec);
|
||
|
||
// type_alias: type Foo = Bar
|
||
if (strcmp(spec_kind, "type_alias") == 0) {
|
||
TSNode alias_name = ts_node_child_by_field_name(spec, "name", 4);
|
||
TSNode alias_type = ts_node_child_by_field_name(spec, "type", 4);
|
||
if (!ts_node_is_null(alias_name) && !ts_node_is_null(alias_type)) {
|
||
char* aname = cbm_node_text(arena, alias_name, source);
|
||
char* atarget = cbm_node_text(arena, alias_type, source);
|
||
if (aname && aname[0] && atarget && atarget[0]) {
|
||
const char* alias_type_qn = cbm_arena_sprintf(arena, "%s.%s", module_qn, aname);
|
||
const char* alias_target_qn = cbm_arena_sprintf(arena, "%s.%s", module_qn, atarget);
|
||
bool found_a = false;
|
||
for (int ti = 0; ti < reg.type_count; ti++) {
|
||
if (reg.types[ti].qualified_name &&
|
||
strcmp(reg.types[ti].qualified_name, alias_type_qn) == 0) {
|
||
reg.types[ti].alias_of = alias_target_qn;
|
||
found_a = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!found_a) {
|
||
CBMRegisteredType alias_rt;
|
||
memset(&alias_rt, 0, sizeof(alias_rt));
|
||
alias_rt.qualified_name = alias_type_qn;
|
||
alias_rt.short_name = aname;
|
||
alias_rt.alias_of = alias_target_qn;
|
||
cbm_registry_add_type(®, alias_rt);
|
||
}
|
||
}
|
||
}
|
||
continue;
|
||
}
|
||
|
||
if (strcmp(spec_kind, "type_spec") != 0) continue;
|
||
|
||
TSNode name_node = ts_node_child_by_field_name(spec, "name", 4);
|
||
TSNode type_node = ts_node_child_by_field_name(spec, "type", 4);
|
||
if (ts_node_is_null(name_node) || ts_node_is_null(type_node)) continue;
|
||
|
||
char* type_name = cbm_node_text(arena,name_node, source);
|
||
if (!type_name || !type_name[0]) continue;
|
||
const char* type_qn = cbm_arena_sprintf(arena, "%s.%s", module_qn, type_name);
|
||
|
||
// Interface type: extract method names for satisfaction checking
|
||
if (strcmp(ts_node_type(type_node), "interface_type") == 0) {
|
||
const char* iface_methods[64];
|
||
int iface_method_count = 0;
|
||
uint32_t inl_nc = ts_node_named_child_count(type_node);
|
||
for (uint32_t k = 0; k < inl_nc && iface_method_count < 63; k++) {
|
||
TSNode child = ts_node_named_child(type_node, k);
|
||
if (ts_node_is_null(child)) continue;
|
||
const char* ck = ts_node_type(child);
|
||
// method_spec: MethodName(params) returns
|
||
if (strcmp(ck, "method_spec") == 0 || strcmp(ck, "method_elem") == 0) {
|
||
TSNode mname = ts_node_child_by_field_name(child, "name", 4);
|
||
if (!ts_node_is_null(mname)) {
|
||
char* mn = cbm_node_text(arena, mname, source);
|
||
if (mn && mn[0]) {
|
||
iface_methods[iface_method_count++] = mn;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (iface_method_count > 0) {
|
||
for (int ti = 0; ti < reg.type_count; ti++) {
|
||
if (!reg.types[ti].qualified_name ||
|
||
strcmp(reg.types[ti].qualified_name, type_qn) != 0) continue;
|
||
const char** names = (const char**)cbm_arena_alloc(arena,
|
||
(iface_method_count + 1) * sizeof(const char*));
|
||
for (int mi = 0; mi < iface_method_count; mi++) {
|
||
names[mi] = iface_methods[mi];
|
||
}
|
||
names[iface_method_count] = NULL;
|
||
reg.types[ti].method_names = names;
|
||
break;
|
||
}
|
||
}
|
||
continue;
|
||
}
|
||
|
||
if (strcmp(ts_node_type(type_node), "struct_type") != 0) continue;
|
||
|
||
// Find field_declaration_list inside struct_type
|
||
TSNode field_list = ts_node_child_by_field_name(type_node, "body", 4);
|
||
if (ts_node_is_null(field_list)) {
|
||
// Some grammars use first named child
|
||
if (ts_node_named_child_count(type_node) > 0)
|
||
field_list = ts_node_named_child(type_node, 0);
|
||
}
|
||
if (ts_node_is_null(field_list)) continue;
|
||
|
||
// Scan field_declarations for embeds and named fields
|
||
const char* embeds[16];
|
||
int embed_count = 0;
|
||
const char* fld_names[64];
|
||
const CBMType* fld_types[64];
|
||
int fld_count = 0;
|
||
|
||
// Create a temporary LSP context for parsing field types
|
||
GoLSPContext tmp_ctx;
|
||
memset(&tmp_ctx, 0, sizeof(tmp_ctx));
|
||
tmp_ctx.arena = arena;
|
||
tmp_ctx.source = source;
|
||
tmp_ctx.source_len = (int)strlen(source);
|
||
tmp_ctx.registry = ®
|
||
tmp_ctx.package_qn = module_qn;
|
||
|
||
uint32_t fl_nc = ts_node_child_count(field_list);
|
||
for (uint32_t k = 0; k < fl_nc; k++) {
|
||
TSNode field = ts_node_child(field_list, k);
|
||
if (ts_node_is_null(field) || !ts_node_is_named(field)) continue;
|
||
if (strcmp(ts_node_type(field), "field_declaration") != 0) continue;
|
||
|
||
TSNode fname = ts_node_child_by_field_name(field, "name", 4);
|
||
TSNode ftype = ts_node_child_by_field_name(field, "type", 4);
|
||
|
||
if (ts_node_is_null(fname) && !ts_node_is_null(ftype)) {
|
||
// Embedded field: has type but no name
|
||
if (embed_count < 15) {
|
||
char* embed_text = cbm_node_text(arena, ftype, source);
|
||
if (embed_text && embed_text[0]) {
|
||
const char* et = embed_text;
|
||
while (*et == '*') et++;
|
||
embeds[embed_count++] = cbm_arena_sprintf(arena, "%s.%s", module_qn, et);
|
||
}
|
||
}
|
||
} else if (!ts_node_is_null(fname) && !ts_node_is_null(ftype) && fld_count < 63) {
|
||
// Named field: name + type
|
||
char* fn = cbm_node_text(arena, fname, source);
|
||
if (fn && fn[0]) {
|
||
fld_names[fld_count] = fn;
|
||
fld_types[fld_count] = go_parse_type_node(&tmp_ctx, ftype);
|
||
fld_count++;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Find the registered type and update it
|
||
for (int ti = 0; ti < reg.type_count; ti++) {
|
||
if (!reg.types[ti].qualified_name ||
|
||
strcmp(reg.types[ti].qualified_name, type_qn) != 0) continue;
|
||
|
||
if (embed_count > 0) {
|
||
const char** arr = (const char**)cbm_arena_alloc(arena,
|
||
(embed_count + 1) * sizeof(const char*));
|
||
for (int ei = 0; ei < embed_count; ei++) arr[ei] = embeds[ei];
|
||
arr[embed_count] = NULL;
|
||
reg.types[ti].embedded_types = arr;
|
||
}
|
||
if (fld_count > 0) {
|
||
const char** names = (const char**)cbm_arena_alloc(arena,
|
||
(fld_count + 1) * sizeof(const char*));
|
||
const CBMType** types = (const CBMType**)cbm_arena_alloc(arena,
|
||
(fld_count + 1) * sizeof(const CBMType*));
|
||
for (int fi = 0; fi < fld_count; fi++) {
|
||
names[fi] = fld_names[fi];
|
||
types[fi] = fld_types[fi];
|
||
}
|
||
names[fld_count] = NULL;
|
||
types[fld_count] = NULL;
|
||
reg.types[ti].field_names = names;
|
||
reg.types[ti].field_types = types;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Phase 1c: Extract type parameters from generic function declarations
|
||
extract_type_params_from_ast(arena, ®, root, source, module_qn);
|
||
|
||
// Phase 2: Build LSP context and run
|
||
GoLSPContext lsp_ctx;
|
||
go_lsp_init(&lsp_ctx, arena, source, source_len, ®, module_qn, &result->resolved_calls);
|
||
|
||
// Add imports
|
||
for (int i = 0; i < result->imports.count; i++) {
|
||
CBMImport* imp = &result->imports.items[i];
|
||
if (imp->local_name && imp->module_path) {
|
||
go_lsp_add_import(&lsp_ctx, imp->local_name, imp->module_path);
|
||
}
|
||
}
|
||
|
||
// Process the file
|
||
go_lsp_process_file(&lsp_ctx, root);
|
||
}
|
||
|
||
// --- Cross-file LSP: parse source, build registry from defs, run LSP ---
|
||
|
||
// Helper: split "|"-separated string into array of CBMType*.
|
||
static const CBMType** split_pipe_types(CBMArena* a, const char* text, const char* module_qn) {
|
||
if (!text || !text[0]) return NULL;
|
||
|
||
// Count separators
|
||
int count = 1;
|
||
for (const char* p = text; *p; p++) {
|
||
if (*p == '|') count++;
|
||
}
|
||
|
||
const CBMType** arr = (const CBMType**)cbm_arena_alloc(a, (count + 1) * sizeof(const CBMType*));
|
||
if (!arr) return NULL;
|
||
|
||
// Split and parse each type
|
||
char* buf = cbm_arena_strdup(a, text);
|
||
int idx = 0;
|
||
char* start = buf;
|
||
for (char* p = buf; ; p++) {
|
||
if (*p == '|' || *p == '\0') {
|
||
char save = *p;
|
||
*p = '\0';
|
||
if (start[0]) {
|
||
arr[idx++] = cbm_parse_return_type_text(a, start, module_qn);
|
||
}
|
||
if (save == '\0') break;
|
||
start = p + 1;
|
||
}
|
||
}
|
||
arr[idx] = NULL;
|
||
return idx > 0 ? arr : NULL;
|
||
}
|
||
|
||
// Helper: split "|"-separated string into array of const char*.
|
||
static const char** split_pipe_strings(CBMArena* a, const char* text) {
|
||
if (!text || !text[0]) return NULL;
|
||
|
||
int count = 1;
|
||
for (const char* p = text; *p; p++) {
|
||
if (*p == '|') count++;
|
||
}
|
||
|
||
const char** arr = (const char**)cbm_arena_alloc(a, (count + 1) * sizeof(const char*));
|
||
if (!arr) return NULL;
|
||
|
||
char* buf = cbm_arena_strdup(a, text);
|
||
int idx = 0;
|
||
char* start = buf;
|
||
for (char* p = buf; ; p++) {
|
||
if (*p == '|' || *p == '\0') {
|
||
char save = *p;
|
||
*p = '\0';
|
||
if (start[0]) {
|
||
arr[idx++] = cbm_arena_strdup(a, start);
|
||
}
|
||
if (save == '\0') break;
|
||
start = p + 1;
|
||
}
|
||
}
|
||
arr[idx] = NULL;
|
||
return idx > 0 ? arr : NULL;
|
||
}
|
||
|
||
// Helper: parse "|"-separated "name:type" field definitions and populate a registered type.
|
||
// Format: "Binder:Binder|Name:string|Count:int"
|
||
// type_text is resolved relative to def_module_qn.
|
||
static void parse_field_defs_into_type(CBMArena* arena, CBMTypeRegistry* reg,
|
||
const char* type_qn, const char* field_defs, const char* def_module_qn) {
|
||
if (!field_defs || !field_defs[0] || !type_qn) return;
|
||
|
||
// Count fields
|
||
int count = 1;
|
||
for (const char* p = field_defs; *p; p++) {
|
||
if (*p == '|') count++;
|
||
}
|
||
if (count > 63) count = 63;
|
||
|
||
const char** names = (const char**)cbm_arena_alloc(arena, (count + 1) * sizeof(const char*));
|
||
const CBMType** types = (const CBMType**)cbm_arena_alloc(arena, (count + 1) * sizeof(const CBMType*));
|
||
if (!names || !types) return;
|
||
|
||
char* buf = cbm_arena_strdup(arena, field_defs);
|
||
int idx = 0;
|
||
char* start = buf;
|
||
for (char* p = buf; ; p++) {
|
||
if (*p == '|' || *p == '\0') {
|
||
char save = *p;
|
||
*p = '\0';
|
||
// Parse "name:type"
|
||
char* colon = NULL;
|
||
for (char* q = start; *q; q++) {
|
||
if (*q == ':') { colon = q; break; }
|
||
}
|
||
if (colon && idx < count) {
|
||
*colon = '\0';
|
||
names[idx] = cbm_arena_strdup(arena, start);
|
||
types[idx] = cbm_parse_return_type_text(arena, colon + 1, def_module_qn);
|
||
idx++;
|
||
}
|
||
if (save == '\0') break;
|
||
start = p + 1;
|
||
}
|
||
}
|
||
names[idx] = NULL;
|
||
types[idx] = NULL;
|
||
|
||
if (idx > 0) {
|
||
// Find the registered type and update field info
|
||
for (int ti = 0; ti < reg->type_count; ti++) {
|
||
if (reg->types[ti].qualified_name &&
|
||
strcmp(reg->types[ti].qualified_name, type_qn) == 0) {
|
||
reg->types[ti].field_names = names;
|
||
reg->types[ti].field_types = types;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// --- Phase 1c: Extract type parameters from generic function declarations ---
|
||
|
||
// Helper: parse a tree-sitter type AST node with type param awareness.
|
||
// Like go_parse_type_node but checks type_identifier against type_param_names first,
|
||
// and works without a full GoLSPContext (uses raw arena/source/module_qn).
|
||
static const CBMType* parse_type_node_with_params(CBMArena* arena, TSNode node,
|
||
const char* source, const char* module_qn, const char** type_param_names) {
|
||
if (ts_node_is_null(node)) return cbm_type_unknown();
|
||
|
||
const char* kind = ts_node_type(node);
|
||
|
||
// type_identifier: check type params first, then resolve as named type
|
||
if (strcmp(kind, "type_identifier") == 0) {
|
||
char* name = cbm_node_text(arena, node, source);
|
||
if (!name) return cbm_type_unknown();
|
||
// Check type param names
|
||
if (type_param_names) {
|
||
for (int i = 0; type_param_names[i]; i++) {
|
||
if (strcmp(name, type_param_names[i]) == 0) {
|
||
return cbm_type_type_param(arena, name);
|
||
}
|
||
}
|
||
}
|
||
// Builtin types
|
||
if (strcmp(name, "int") == 0 || strcmp(name, "string") == 0 ||
|
||
strcmp(name, "bool") == 0 || strcmp(name, "float64") == 0 ||
|
||
strcmp(name, "float32") == 0 || strcmp(name, "byte") == 0 ||
|
||
strcmp(name, "rune") == 0 || strcmp(name, "error") == 0 ||
|
||
strcmp(name, "any") == 0 || strcmp(name, "int8") == 0 ||
|
||
strcmp(name, "int16") == 0 || strcmp(name, "int32") == 0 ||
|
||
strcmp(name, "int64") == 0 || strcmp(name, "uint") == 0 ||
|
||
strcmp(name, "uint8") == 0 || strcmp(name, "uint16") == 0 ||
|
||
strcmp(name, "uint32") == 0 || strcmp(name, "uint64") == 0 ||
|
||
strcmp(name, "uintptr") == 0) {
|
||
return cbm_type_builtin(arena, name);
|
||
}
|
||
return cbm_type_named(arena, cbm_arena_sprintf(arena, "%s.%s", module_qn, name));
|
||
}
|
||
|
||
// type_elem: wrapper in type_arguments
|
||
if (strcmp(kind, "type_elem") == 0 && ts_node_named_child_count(node) > 0) {
|
||
return parse_type_node_with_params(arena, ts_node_named_child(node, 0),
|
||
source, module_qn, type_param_names);
|
||
}
|
||
|
||
// qualified_type: pkg.Type
|
||
if (strcmp(kind, "qualified_type") == 0) {
|
||
TSNode pkg_node = ts_node_child_by_field_name(node, "package", 7);
|
||
TSNode name_node = ts_node_child_by_field_name(node, "name", 4);
|
||
if (!ts_node_is_null(pkg_node) && !ts_node_is_null(name_node)) {
|
||
char* pkg = cbm_node_text(arena, pkg_node, source);
|
||
char* name = cbm_node_text(arena, name_node, source);
|
||
if (pkg && name) {
|
||
return cbm_type_named(arena, cbm_arena_sprintf(arena, "%s.%s", pkg, name));
|
||
}
|
||
}
|
||
return cbm_type_unknown();
|
||
}
|
||
|
||
// pointer_type: *T
|
||
if (strcmp(kind, "pointer_type") == 0) {
|
||
uint32_t nc = ts_node_named_child_count(node);
|
||
if (nc > 0)
|
||
return cbm_type_pointer(arena, parse_type_node_with_params(arena,
|
||
ts_node_named_child(node, nc - 1), source, module_qn, type_param_names));
|
||
return cbm_type_unknown();
|
||
}
|
||
|
||
// slice_type: []T
|
||
if (strcmp(kind, "slice_type") == 0) {
|
||
TSNode elem = ts_node_child_by_field_name(node, "element", 7);
|
||
if (ts_node_is_null(elem) && ts_node_named_child_count(node) > 0)
|
||
elem = ts_node_named_child(node, ts_node_named_child_count(node) - 1);
|
||
return cbm_type_slice(arena, parse_type_node_with_params(arena,
|
||
elem, source, module_qn, type_param_names));
|
||
}
|
||
|
||
// array_type: [N]T
|
||
if (strcmp(kind, "array_type") == 0) {
|
||
TSNode elem = ts_node_child_by_field_name(node, "element", 7);
|
||
if (ts_node_is_null(elem) && ts_node_named_child_count(node) > 0)
|
||
elem = ts_node_named_child(node, ts_node_named_child_count(node) - 1);
|
||
return cbm_type_slice(arena, parse_type_node_with_params(arena,
|
||
elem, source, module_qn, type_param_names));
|
||
}
|
||
|
||
// map_type: map[K]V
|
||
if (strcmp(kind, "map_type") == 0) {
|
||
TSNode key = ts_node_child_by_field_name(node, "key", 3);
|
||
TSNode value = ts_node_child_by_field_name(node, "value", 5);
|
||
return cbm_type_map(arena,
|
||
parse_type_node_with_params(arena, key, source, module_qn, type_param_names),
|
||
parse_type_node_with_params(arena, value, source, module_qn, type_param_names));
|
||
}
|
||
|
||
// channel_type: chan T
|
||
if (strcmp(kind, "channel_type") == 0) {
|
||
TSNode value = ts_node_child_by_field_name(node, "value", 5);
|
||
if (ts_node_is_null(value) && ts_node_named_child_count(node) > 0)
|
||
value = ts_node_named_child(node, ts_node_named_child_count(node) - 1);
|
||
char* text = cbm_node_text(arena, node, source);
|
||
int dir = 0;
|
||
if (text) {
|
||
if (strncmp(text, "chan<-", 6) == 0 || strncmp(text, "chan <-", 7) == 0) dir = 1;
|
||
else if (strncmp(text, "<-chan", 6) == 0 || strncmp(text, "<- chan", 7) == 0) dir = 2;
|
||
}
|
||
return cbm_type_channel(arena, parse_type_node_with_params(arena,
|
||
value, source, module_qn, type_param_names), dir);
|
||
}
|
||
|
||
// function_type: func(T) R — full parsing with type param awareness
|
||
if (strcmp(kind, "function_type") == 0) {
|
||
TSNode params_node = ts_node_child_by_field_name(node, "parameters", 10);
|
||
TSNode result_node = ts_node_child_by_field_name(node, "result", 6);
|
||
|
||
const CBMType* param_types_arr[16];
|
||
int pc = 0;
|
||
if (!ts_node_is_null(params_node)) {
|
||
uint32_t pnc = ts_node_child_count(params_node);
|
||
for (uint32_t i = 0; i < pnc && pc < 15; i++) {
|
||
TSNode child = ts_node_child(params_node, i);
|
||
if (ts_node_is_null(child) || !ts_node_is_named(child)) continue;
|
||
if (strcmp(ts_node_type(child), "parameter_declaration") == 0) {
|
||
TSNode pt = ts_node_child_by_field_name(child, "type", 4);
|
||
if (!ts_node_is_null(pt))
|
||
param_types_arr[pc++] = parse_type_node_with_params(arena,
|
||
pt, source, module_qn, type_param_names);
|
||
}
|
||
}
|
||
}
|
||
param_types_arr[pc] = NULL;
|
||
|
||
const CBMType* ret_types_arr[16];
|
||
int rc = 0;
|
||
if (!ts_node_is_null(result_node)) {
|
||
if (strcmp(ts_node_type(result_node), "parameter_list") == 0) {
|
||
uint32_t rnc = ts_node_child_count(result_node);
|
||
for (uint32_t i = 0; i < rnc && rc < 15; i++) {
|
||
TSNode child = ts_node_child(result_node, i);
|
||
if (ts_node_is_null(child) || !ts_node_is_named(child)) continue;
|
||
TSNode rt = ts_node_child_by_field_name(child, "type", 4);
|
||
if (ts_node_is_null(rt)) rt = child;
|
||
ret_types_arr[rc++] = parse_type_node_with_params(arena,
|
||
rt, source, module_qn, type_param_names);
|
||
}
|
||
} else {
|
||
ret_types_arr[rc++] = parse_type_node_with_params(arena,
|
||
result_node, source, module_qn, type_param_names);
|
||
}
|
||
}
|
||
ret_types_arr[rc] = NULL;
|
||
|
||
const CBMType** pt = pc > 0 ? (const CBMType**)param_types_arr : NULL;
|
||
const CBMType** rt = rc > 0 ? (const CBMType**)ret_types_arr : NULL;
|
||
return cbm_type_func(arena, NULL, pt, rt);
|
||
}
|
||
|
||
// interface_type
|
||
if (strcmp(kind, "interface_type") == 0) {
|
||
CBMType* t = (CBMType*)cbm_arena_alloc(arena, sizeof(CBMType));
|
||
memset(t, 0, sizeof(CBMType));
|
||
t->kind = CBM_TYPE_INTERFACE;
|
||
return t;
|
||
}
|
||
|
||
// parenthesized_type: (T)
|
||
if (strcmp(kind, "parenthesized_type") == 0 && ts_node_named_child_count(node) > 0) {
|
||
return parse_type_node_with_params(arena, ts_node_named_child(node, 0),
|
||
source, module_qn, type_param_names);
|
||
}
|
||
|
||
// generic_type: Type[T1, T2]
|
||
if (strcmp(kind, "generic_type") == 0) {
|
||
TSNode type_node = ts_node_child_by_field_name(node, "type", 4);
|
||
if (!ts_node_is_null(type_node))
|
||
return parse_type_node_with_params(arena, type_node, source, module_qn, type_param_names);
|
||
}
|
||
|
||
return cbm_type_unknown();
|
||
}
|
||
|
||
// Scan AST for generic function declarations and set type_param_names + re-parse
|
||
// return types on matching registered functions.
|
||
static void extract_type_params_from_ast(CBMArena* arena, CBMTypeRegistry* reg,
|
||
TSNode root, const char* source, const char* module_qn) {
|
||
|
||
// Collect top-level children once (O(n)); ts_node_child(root,i) is O(i) → O(n²).
|
||
uint32_t rkn = 0;
|
||
TSNode* rkids = cbm_lsp_collect_children(arena, root, &rkn);
|
||
for (uint32_t i = 0; i < rkn; i++) {
|
||
TSNode top = rkids[i];
|
||
if (strcmp(ts_node_type(top), "function_declaration") != 0) continue;
|
||
|
||
// Check for type_parameter_list (field name: "type_parameters", 15 chars)
|
||
TSNode tp_list = ts_node_child_by_field_name(top, "type_parameters", 15);
|
||
if (ts_node_is_null(tp_list)) continue;
|
||
|
||
// Get function name
|
||
TSNode name_node = ts_node_child_by_field_name(top, "name", 4);
|
||
if (ts_node_is_null(name_node)) continue;
|
||
char* func_name = cbm_node_text(arena, name_node, source);
|
||
if (!func_name || !func_name[0]) continue;
|
||
|
||
// Extract type param names from type_parameter_declaration children
|
||
const char* params[16];
|
||
int param_count = 0;
|
||
uint32_t tp_nc = ts_node_child_count(tp_list);
|
||
for (uint32_t j = 0; j < tp_nc && param_count < 15; j++) {
|
||
TSNode child = ts_node_child(tp_list, j);
|
||
if (ts_node_is_null(child) || !ts_node_is_named(child)) continue;
|
||
if (strcmp(ts_node_type(child), "type_parameter_declaration") != 0) continue;
|
||
TSNode pname = ts_node_child_by_field_name(child, "name", 4);
|
||
if (ts_node_is_null(pname)) {
|
||
// Fallback: first type_identifier child
|
||
uint32_t cc = ts_node_child_count(child);
|
||
for (uint32_t k = 0; k < cc; k++) {
|
||
TSNode c = ts_node_child(child, k);
|
||
if (!ts_node_is_null(c) && ts_node_is_named(c) &&
|
||
strcmp(ts_node_type(c), "type_identifier") == 0) {
|
||
pname = c;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
if (!ts_node_is_null(pname)) {
|
||
char* pn = cbm_node_text(arena, pname, source);
|
||
if (pn && pn[0]) {
|
||
params[param_count++] = cbm_arena_strdup(arena, pn);
|
||
}
|
||
}
|
||
}
|
||
if (param_count == 0) continue;
|
||
params[param_count] = NULL;
|
||
|
||
// Build arena-allocated type_param_names array
|
||
const char** tp_names = (const char**)cbm_arena_alloc(arena, (param_count + 1) * sizeof(const char*));
|
||
for (int j = 0; j <= param_count; j++) tp_names[j] = params[j];
|
||
|
||
// Find the matching registered function and set type_param_names
|
||
const char* func_qn = cbm_arena_sprintf(arena, "%s.%s", module_qn, func_name);
|
||
for (int fi = 0; fi < reg->func_count; fi++) {
|
||
if (reg->funcs[fi].qualified_name &&
|
||
strcmp(reg->funcs[fi].qualified_name, func_qn) == 0) {
|
||
reg->funcs[fi].type_param_names = tp_names;
|
||
|
||
// Re-parse return types with type param awareness
|
||
if (reg->funcs[fi].signature && reg->funcs[fi].signature->kind == CBM_TYPE_FUNC &&
|
||
reg->funcs[fi].signature->data.func.return_types) {
|
||
const CBMType** old_rets = reg->funcs[fi].signature->data.func.return_types;
|
||
int ret_count = 0;
|
||
while (old_rets[ret_count]) ret_count++;
|
||
|
||
// Check if any return type is a NAMED that matches a type param
|
||
bool needs_reparse = false;
|
||
for (int ri = 0; ri < ret_count && !needs_reparse; ri++) {
|
||
const CBMType* check = old_rets[ri];
|
||
// Unwrap slice/pointer to get inner named type
|
||
if (check->kind == CBM_TYPE_SLICE && check->data.slice.elem)
|
||
check = check->data.slice.elem;
|
||
else if (check->kind == CBM_TYPE_POINTER && check->data.pointer.elem)
|
||
check = check->data.pointer.elem;
|
||
if (check->kind == CBM_TYPE_NAMED) {
|
||
const char* qn = check->data.named.qualified_name;
|
||
const char* dot = strrchr(qn, '.');
|
||
const char* short_name = dot ? dot + 1 : qn;
|
||
for (int pi = 0; pi < param_count; pi++) {
|
||
if (strcmp(short_name, tp_names[pi]) == 0) {
|
||
needs_reparse = true;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (needs_reparse) {
|
||
TSNode result_node = ts_node_child_by_field_name(top, "result", 6);
|
||
if (!ts_node_is_null(result_node)) {
|
||
const CBMType** new_rets = (const CBMType**)cbm_arena_alloc(arena,
|
||
(ret_count + 1) * sizeof(const CBMType*));
|
||
|
||
if (strcmp(ts_node_type(result_node), "parameter_list") == 0) {
|
||
int idx = 0;
|
||
uint32_t rnc = ts_node_child_count(result_node);
|
||
for (uint32_t ri = 0; ri < rnc && idx < ret_count; ri++) {
|
||
TSNode rchild = ts_node_child(result_node, ri);
|
||
if (ts_node_is_null(rchild) || !ts_node_is_named(rchild)) continue;
|
||
TSNode rtype = ts_node_child_by_field_name(rchild, "type", 4);
|
||
if (ts_node_is_null(rtype)) rtype = rchild;
|
||
new_rets[idx++] = parse_type_node_with_params(arena,
|
||
rtype, source, module_qn, tp_names);
|
||
}
|
||
new_rets[idx] = NULL;
|
||
} else {
|
||
new_rets[0] = parse_type_node_with_params(arena,
|
||
result_node, source, module_qn, tp_names);
|
||
new_rets[1] = NULL;
|
||
}
|
||
|
||
CBMType* new_sig = (CBMType*)cbm_arena_alloc(arena, sizeof(CBMType));
|
||
*new_sig = *(CBMType*)reg->funcs[fi].signature;
|
||
new_sig->data.func.return_types = new_rets;
|
||
reg->funcs[fi].signature = new_sig;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Also re-parse param types with TYPE_PARAM awareness (for implicit generics)
|
||
if (reg->funcs[fi].signature && reg->funcs[fi].signature->kind == CBM_TYPE_FUNC &&
|
||
reg->funcs[fi].signature->data.func.param_types) {
|
||
const CBMType** old_params = reg->funcs[fi].signature->data.func.param_types;
|
||
int pc = 0;
|
||
while (old_params[pc]) pc++;
|
||
// Re-parse from AST parameter list
|
||
TSNode params_node = ts_node_child_by_field_name(top, "parameters", 10);
|
||
if (!ts_node_is_null(params_node)) {
|
||
const CBMType** new_params = (const CBMType**)cbm_arena_alloc(arena,
|
||
(pc + 1) * sizeof(const CBMType*));
|
||
int idx = 0;
|
||
uint32_t pnc = ts_node_child_count(params_node);
|
||
for (uint32_t pi = 0; pi < pnc && idx < pc; pi++) {
|
||
TSNode pchild = ts_node_child(params_node, pi);
|
||
if (ts_node_is_null(pchild) || !ts_node_is_named(pchild)) continue;
|
||
if (strcmp(ts_node_type(pchild), "parameter_declaration") != 0) continue;
|
||
TSNode ptype = ts_node_child_by_field_name(pchild, "type", 4);
|
||
if (ts_node_is_null(ptype)) continue;
|
||
new_params[idx++] = parse_type_node_with_params(arena,
|
||
ptype, source, module_qn, tp_names);
|
||
}
|
||
new_params[idx] = NULL;
|
||
if (idx > 0) {
|
||
CBMType* sig = (CBMType*)reg->funcs[fi].signature;
|
||
if (sig != reg->funcs[fi].signature) {
|
||
// Already rebuilt — update in place
|
||
((CBMType*)reg->funcs[fi].signature)->data.func.param_types = new_params;
|
||
} else {
|
||
CBMType* new_sig = (CBMType*)cbm_arena_alloc(arena, sizeof(CBMType));
|
||
*new_sig = *sig;
|
||
new_sig->data.func.param_types = new_params;
|
||
reg->funcs[fi].signature = new_sig;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Forward: tree-sitter Go language (from lang_specs.c)
|
||
extern const TSLanguage* tree_sitter_go(void);
|
||
|
||
void cbm_run_go_lsp_cross(
|
||
CBMArena* arena,
|
||
const char* source, int source_len,
|
||
const char* module_qn,
|
||
CBMLSPDef* defs, int def_count,
|
||
const char** import_names, const char** import_qns, int import_count,
|
||
TSTree* cached_tree,
|
||
CBMResolvedCallArray* out)
|
||
{
|
||
if (!source || source_len <= 0) return;
|
||
|
||
// 1. 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;
|
||
ts_parser_set_language(parser, tree_sitter_go());
|
||
tree = ts_parser_parse_string(parser, NULL, source, source_len);
|
||
owns_tree = true;
|
||
if (!tree) { ts_parser_delete(parser); return; }
|
||
}
|
||
TSNode root = ts_tree_root_node(tree);
|
||
|
||
// 2. Build registry
|
||
CBMTypeRegistry reg;
|
||
cbm_registry_init(®, arena);
|
||
cbm_go_stdlib_register(®, arena);
|
||
|
||
// Register all defs (file-local + cross-file).
|
||
// Perf: borrow strings from defs[] directly — they live in the
|
||
// caller's arena which outlives this call, so cbm_arena_strdup is
|
||
// wasted work. On kubernetes (~110k defs × 11k files × 5 strdups
|
||
// ~= 6B mallocs) this alone saves ~90s of resolve wall time.
|
||
for (int i = 0; i < def_count; i++) {
|
||
CBMLSPDef* d = &defs[i];
|
||
if (!d->qualified_name || !d->short_name || !d->label) continue;
|
||
|
||
const char* def_mod = d->def_module_qn ? d->def_module_qn : module_qn;
|
||
|
||
// Every type-like container (Type/Class/Struct/Interface/Enum/Trait).
|
||
// Struct included so Go structs (now labelled "Struct") register as types.
|
||
if (cbm_label_is_type_like(d->label)) {
|
||
CBMRegisteredType rt;
|
||
memset(&rt, 0, sizeof(rt));
|
||
rt.qualified_name = d->qualified_name; // borrowed
|
||
rt.short_name = d->short_name; // borrowed
|
||
rt.is_interface = d->is_interface || strcmp(d->label, "Interface") == 0;
|
||
rt.embedded_types = split_pipe_strings(arena, d->embedded_types);
|
||
|
||
// Set method_names for interfaces from "|"-separated string
|
||
if (rt.is_interface && d->method_names_str && d->method_names_str[0]) {
|
||
rt.method_names = split_pipe_strings(arena, d->method_names_str);
|
||
}
|
||
|
||
cbm_registry_add_type(®, rt);
|
||
|
||
// Populate struct field types from field_defs
|
||
if (d->field_defs && d->field_defs[0]) {
|
||
parse_field_defs_into_type(arena, ®, rt.qualified_name, d->field_defs, def_mod);
|
||
}
|
||
}
|
||
|
||
// Function/Method
|
||
if (strcmp(d->label, "Function") == 0 || strcmp(d->label, "Method") == 0) {
|
||
CBMRegisteredFunc rf;
|
||
memset(&rf, 0, sizeof(rf));
|
||
rf.qualified_name = d->qualified_name; // borrowed
|
||
rf.short_name = d->short_name; // borrowed
|
||
|
||
// Build FUNC type from return_types text
|
||
const CBMType** ret_types = split_pipe_types(arena, d->return_types, def_mod);
|
||
rf.signature = cbm_type_func(arena, NULL, NULL, ret_types);
|
||
|
||
// Method receiver
|
||
if (strcmp(d->label, "Method") == 0 && d->receiver_type && d->receiver_type[0]) {
|
||
rf.receiver_type = d->receiver_type; // borrowed
|
||
// Auto-create type entry if not exists
|
||
if (!cbm_registry_lookup_type(®, rf.receiver_type)) {
|
||
CBMRegisteredType auto_type;
|
||
memset(&auto_type, 0, sizeof(auto_type));
|
||
auto_type.qualified_name = rf.receiver_type;
|
||
const char* dot = strrchr(d->receiver_type, '.');
|
||
auto_type.short_name = dot ? dot + 1 : rf.receiver_type; // borrowed substring
|
||
cbm_registry_add_type(®, auto_type);
|
||
}
|
||
}
|
||
|
||
cbm_registry_add_func(®, rf);
|
||
}
|
||
}
|
||
|
||
// 3. Phase 1b: Scan AST for struct definitions to populate embedded_types
|
||
{
|
||
// Collect top-level children once (O(n)); ts_node_child(root,i) is O(i) → O(n²).
|
||
uint32_t rkn = 0;
|
||
TSNode* rkids = cbm_lsp_collect_children(arena, root, &rkn);
|
||
for (uint32_t i = 0; i < rkn; i++) {
|
||
TSNode top = rkids[i];
|
||
if (strcmp(ts_node_type(top), "type_declaration") != 0) continue;
|
||
uint32_t td_nc = ts_node_child_count(top);
|
||
for (uint32_t j = 0; j < td_nc; j++) {
|
||
TSNode spec = ts_node_child(top, j);
|
||
if (ts_node_is_null(spec) || !ts_node_is_named(spec)) continue;
|
||
const char* spec_kind2 = ts_node_type(spec);
|
||
|
||
// type_alias: type Foo = Bar
|
||
if (strcmp(spec_kind2, "type_alias") == 0) {
|
||
TSNode alias_name = ts_node_child_by_field_name(spec, "name", 4);
|
||
TSNode alias_type = ts_node_child_by_field_name(spec, "type", 4);
|
||
if (!ts_node_is_null(alias_name) && !ts_node_is_null(alias_type)) {
|
||
char* aname = cbm_node_text(arena, alias_name, source);
|
||
char* atarget = cbm_node_text(arena, alias_type, source);
|
||
if (aname && aname[0] && atarget && atarget[0]) {
|
||
const char* alias_type_qn = cbm_arena_sprintf(arena, "%s.%s", module_qn, aname);
|
||
const char* alias_target_qn = cbm_arena_sprintf(arena, "%s.%s", module_qn, atarget);
|
||
bool found_a = false;
|
||
for (int ti = 0; ti < reg.type_count; ti++) {
|
||
if (reg.types[ti].qualified_name &&
|
||
strcmp(reg.types[ti].qualified_name, alias_type_qn) == 0) {
|
||
reg.types[ti].alias_of = alias_target_qn;
|
||
found_a = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!found_a) {
|
||
CBMRegisteredType alias_rt;
|
||
memset(&alias_rt, 0, sizeof(alias_rt));
|
||
alias_rt.qualified_name = alias_type_qn;
|
||
alias_rt.short_name = aname;
|
||
alias_rt.alias_of = alias_target_qn;
|
||
cbm_registry_add_type(®, alias_rt);
|
||
}
|
||
}
|
||
}
|
||
continue;
|
||
}
|
||
|
||
if (strcmp(spec_kind2, "type_spec") != 0) continue;
|
||
|
||
TSNode name_node = ts_node_child_by_field_name(spec, "name", 4);
|
||
TSNode type_node = ts_node_child_by_field_name(spec, "type", 4);
|
||
if (ts_node_is_null(name_node) || ts_node_is_null(type_node)) continue;
|
||
if (strcmp(ts_node_type(type_node), "struct_type") != 0) continue;
|
||
|
||
char* type_name = cbm_node_text(arena, name_node, source);
|
||
if (!type_name || !type_name[0]) continue;
|
||
const char* type_qn = cbm_arena_sprintf(arena, "%s.%s", module_qn, type_name);
|
||
|
||
TSNode field_list = ts_node_child_by_field_name(type_node, "body", 4);
|
||
if (ts_node_is_null(field_list)) {
|
||
if (ts_node_named_child_count(type_node) > 0)
|
||
field_list = ts_node_named_child(type_node, 0);
|
||
}
|
||
if (ts_node_is_null(field_list)) continue;
|
||
|
||
const char* embeds[16];
|
||
int embed_count = 0;
|
||
const char* fld_names[64];
|
||
const CBMType* fld_types[64];
|
||
int fld_count = 0;
|
||
|
||
GoLSPContext tmp_ctx;
|
||
memset(&tmp_ctx, 0, sizeof(tmp_ctx));
|
||
tmp_ctx.arena = arena;
|
||
tmp_ctx.source = source;
|
||
tmp_ctx.source_len = source_len;
|
||
tmp_ctx.registry = ®
|
||
tmp_ctx.package_qn = module_qn;
|
||
tmp_ctx.import_local_names = import_names;
|
||
tmp_ctx.import_package_qns = import_qns;
|
||
tmp_ctx.import_count = import_count;
|
||
|
||
uint32_t fl_nc = ts_node_child_count(field_list);
|
||
for (uint32_t k = 0; k < fl_nc; k++) {
|
||
TSNode field = ts_node_child(field_list, k);
|
||
if (ts_node_is_null(field) || !ts_node_is_named(field)) continue;
|
||
if (strcmp(ts_node_type(field), "field_declaration") != 0) continue;
|
||
TSNode fname = ts_node_child_by_field_name(field, "name", 4);
|
||
TSNode ftype = ts_node_child_by_field_name(field, "type", 4);
|
||
if (ts_node_is_null(fname) && !ts_node_is_null(ftype)) {
|
||
if (embed_count < 15) {
|
||
char* embed_text = cbm_node_text(arena, ftype, source);
|
||
if (embed_text && embed_text[0]) {
|
||
const char* et = embed_text;
|
||
while (*et == '*') et++;
|
||
embeds[embed_count++] = cbm_arena_sprintf(arena, "%s.%s", module_qn, et);
|
||
}
|
||
}
|
||
} else if (!ts_node_is_null(fname) && !ts_node_is_null(ftype) && fld_count < 63) {
|
||
char* fn = cbm_node_text(arena, fname, source);
|
||
if (fn && fn[0]) {
|
||
fld_names[fld_count] = fn;
|
||
fld_types[fld_count] = go_parse_type_node(&tmp_ctx, ftype);
|
||
fld_count++;
|
||
}
|
||
}
|
||
}
|
||
|
||
for (int ti = 0; ti < reg.type_count; ti++) {
|
||
if (!reg.types[ti].qualified_name ||
|
||
strcmp(reg.types[ti].qualified_name, type_qn) != 0) continue;
|
||
|
||
if (embed_count > 0) {
|
||
const char** arr = (const char**)cbm_arena_alloc(arena,
|
||
(embed_count + 1) * sizeof(const char*));
|
||
for (int ei = 0; ei < embed_count; ei++) arr[ei] = embeds[ei];
|
||
arr[embed_count] = NULL;
|
||
reg.types[ti].embedded_types = arr;
|
||
}
|
||
if (fld_count > 0) {
|
||
const char** names = (const char**)cbm_arena_alloc(arena,
|
||
(fld_count + 1) * sizeof(const char*));
|
||
const CBMType** types = (const CBMType**)cbm_arena_alloc(arena,
|
||
(fld_count + 1) * sizeof(const CBMType*));
|
||
for (int fi = 0; fi < fld_count; fi++) {
|
||
names[fi] = fld_names[fi];
|
||
types[fi] = fld_types[fi];
|
||
}
|
||
names[fld_count] = NULL;
|
||
types[fld_count] = NULL;
|
||
reg.types[ti].field_names = names;
|
||
reg.types[ti].field_types = types;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 3b. Phase 1c: Extract type params from generic function declarations
|
||
extract_type_params_from_ast(arena, ®, root, source, module_qn);
|
||
|
||
// 3c. Finalize registry — builds hash buckets for O(1) lookups in
|
||
// cbm_registry_lookup_method / lookup_type. Without this, every
|
||
// lookup falls through to the O(N) linear scan in type_registry.c,
|
||
// turning the resolution pass below into an O(N·C·F) cost per file
|
||
// (kubernetes: ~64B strcmps observed = 35min). Must come AFTER all
|
||
// mutations (Phase 1b/1c above) and BEFORE the LSP context init.
|
||
cbm_registry_finalize(®);
|
||
|
||
// 4. Build LSP context and run
|
||
GoLSPContext ctx;
|
||
go_lsp_init(&ctx, arena, source, source_len, ®, module_qn, out);
|
||
|
||
for (int i = 0; i < import_count; i++) {
|
||
if (import_names[i] && import_qns[i]) {
|
||
go_lsp_add_import(&ctx, import_names[i], import_qns[i]);
|
||
}
|
||
}
|
||
|
||
go_lsp_process_file(&ctx, root);
|
||
|
||
// 5. Cleanup — only free if we allocated
|
||
if (owns_tree) {
|
||
ts_tree_delete(tree);
|
||
if (parser) ts_parser_delete(parser);
|
||
}
|
||
}
|
||
|
||
/* ── Tier 2: pre-built per-language registry (gopls package summary pattern) ──
|
||
*
|
||
* cbm_go_build_cross_registry runs the registry build ONCE per project (in
|
||
* pipeline.c, before the parallel resolve workers fire). The returned
|
||
* registry is finalized (O(1) lookups) and shared READ-ONLY across all
|
||
* workers in the resolve phase.
|
||
*
|
||
* cbm_run_go_lsp_cross_with_registry is the per-file entrypoint used by
|
||
* the resolve worker. It SKIPS the registry build (uses the pre-built reg)
|
||
* AND skips Phase 1b/1c AST scans (those mutate the registry, which would
|
||
* race with other workers reading the shared reg). Accuracy trade-off:
|
||
* file-local type aliases and struct embeddings discovered ONLY by Phase
|
||
* 1b (and not already in defs[] embedded_types/field_defs strings) are
|
||
* missed. In practice the per-file LSP during extract already captures
|
||
* those via the def strings. */
|
||
CBMTypeRegistry* cbm_go_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_go_stdlib_register(reg, arena);
|
||
|
||
for (int i = 0; i < def_count; i++) {
|
||
CBMLSPDef* d = &defs[i];
|
||
if (!d->qualified_name || !d->short_name || !d->label) continue;
|
||
/* Filter to Go defs only — the all_defs[] array is mixed-language. */
|
||
if (d->lang != CBM_LANG_GO) continue;
|
||
/* In the pre-built path every def carries its own def_module_qn
|
||
* (set by cbm_pxc_collect_all_defs). There is no caller module to
|
||
* fall back to — this registry is project-wide, not per-file. */
|
||
const char* def_mod = d->def_module_qn ? d->def_module_qn : "";
|
||
|
||
// Every type-like container (Type/Class/Struct/Interface/Enum/Trait).
|
||
// Struct included so Go structs (now labelled "Struct") register as types.
|
||
if (cbm_label_is_type_like(d->label)) {
|
||
CBMRegisteredType rt;
|
||
memset(&rt, 0, sizeof(rt));
|
||
rt.qualified_name = d->qualified_name; /* borrowed */
|
||
rt.short_name = d->short_name;
|
||
rt.is_interface = d->is_interface || strcmp(d->label, "Interface") == 0;
|
||
rt.embedded_types = split_pipe_strings(arena, d->embedded_types);
|
||
if (rt.is_interface && d->method_names_str && d->method_names_str[0]) {
|
||
rt.method_names = split_pipe_strings(arena, d->method_names_str);
|
||
}
|
||
cbm_registry_add_type(reg, rt);
|
||
if (d->field_defs && d->field_defs[0]) {
|
||
parse_field_defs_into_type(arena, reg, rt.qualified_name,
|
||
d->field_defs, def_mod);
|
||
}
|
||
}
|
||
|
||
if (strcmp(d->label, "Function") == 0 || strcmp(d->label, "Method") == 0) {
|
||
CBMRegisteredFunc rf;
|
||
memset(&rf, 0, sizeof(rf));
|
||
rf.qualified_name = d->qualified_name; /* borrowed */
|
||
rf.short_name = d->short_name;
|
||
const CBMType** ret_types = split_pipe_types(arena, d->return_types, def_mod);
|
||
rf.signature = cbm_type_func(arena, NULL, NULL, ret_types);
|
||
if (strcmp(d->label, "Method") == 0 && d->receiver_type && d->receiver_type[0]) {
|
||
rf.receiver_type = d->receiver_type;
|
||
if (!cbm_registry_lookup_type(reg, rf.receiver_type)) {
|
||
CBMRegisteredType auto_type;
|
||
memset(&auto_type, 0, sizeof(auto_type));
|
||
auto_type.qualified_name = rf.receiver_type;
|
||
const char* dot = strrchr(d->receiver_type, '.');
|
||
auto_type.short_name = dot ? dot + 1 : rf.receiver_type;
|
||
cbm_registry_add_type(reg, auto_type);
|
||
}
|
||
}
|
||
cbm_registry_add_func(reg, rf);
|
||
}
|
||
}
|
||
|
||
cbm_registry_finalize(reg);
|
||
reg->read_only = true; /* seal: shared Tier-2 registry is read-only during resolve */
|
||
return reg;
|
||
}
|
||
|
||
void cbm_run_go_lsp_cross_with_registry(
|
||
CBMArena* arena,
|
||
const char* source, int source_len,
|
||
const char* module_qn,
|
||
CBMTypeRegistry* reg,
|
||
const char** import_names, const char** import_qns, int import_count,
|
||
TSTree* cached_tree,
|
||
CBMResolvedCallArray* out) {
|
||
if (!source || source_len <= 0 || !reg) return;
|
||
|
||
TSParser* parser = NULL;
|
||
TSTree* tree = cached_tree;
|
||
bool owns_tree = false;
|
||
if (!tree) {
|
||
parser = ts_parser_new();
|
||
if (!parser) return;
|
||
ts_parser_set_language(parser, tree_sitter_go());
|
||
tree = ts_parser_parse_string(parser, NULL, source, source_len);
|
||
owns_tree = true;
|
||
if (!tree) { ts_parser_delete(parser); return; }
|
||
}
|
||
TSNode root = ts_tree_root_node(tree);
|
||
|
||
/* Phase 1b/1c (per-file AST mutations on registry) DELIBERATELY SKIPPED —
|
||
* shared registry must stay immutable across parallel workers. */
|
||
|
||
GoLSPContext ctx;
|
||
go_lsp_init(&ctx, arena, source, source_len, reg, module_qn, out);
|
||
for (int i = 0; i < import_count; i++) {
|
||
if (import_names[i] && import_qns[i]) {
|
||
go_lsp_add_import(&ctx, import_names[i], import_qns[i]);
|
||
}
|
||
}
|
||
go_lsp_process_file(&ctx, root);
|
||
|
||
if (owns_tree) {
|
||
ts_tree_delete(tree);
|
||
if (parser) ts_parser_delete(parser);
|
||
}
|
||
}
|
||
|
||
/* ── Tier 3: AST-walk-free metadata-driven cross-file resolver ────
|
||
*
|
||
* KEY INSIGHT: the per-file LSP during extract ALREADY emits one
|
||
* CBMResolvedCall per call site, with strategy="lsp_unresolved" for
|
||
* calls it couldn't resolve. For those unresolved entries:
|
||
*
|
||
* * "symbol_not_in_registry" (go_lsp.c:1142) → callee_qn is
|
||
* "pkg_name.field_name" (literal local import alias)
|
||
* * "method_not_found" (go_lsp.c:1242) → callee_qn is
|
||
* "<FullyQualifiedReceiverTypeQN>.<MethodName>" — receiver type
|
||
* was ALREADY inferred by per-file LSP, just the method wasn't
|
||
* in the per-file registry (cross-file)
|
||
* * "function_not_in_registry" (go_lsp.c:1266) → callee_qn is
|
||
* the bare function name (likely a same-package symbol we
|
||
* missed, or a cross-file package function used unqualified)
|
||
* * "unknown_receiver_type" (go_lsp.c:1248) → per-file LSP
|
||
* couldn't infer the receiver type. Cross-LSP can't either
|
||
* without re-walking — leave unresolved.
|
||
*
|
||
* So cross-LSP work reduces to:
|
||
* for each unresolved entry → look up callee_qn (or pkg.x via
|
||
* import map) in the global registry → emit resolved version
|
||
* on top. NO TREE-SITTER PARSE. NO AST WALK. Just hash lookups.
|
||
*
|
||
* This is what the user meant: walk the AST ONCE (during extract),
|
||
* capture everything cross-LSP needs as metadata, and let cross-LSP
|
||
* be a pure lookup pass. */
|
||
int cbm_go_fast_resolve_qualified_calls(
|
||
CBMFileResult* result,
|
||
CBMTypeRegistry* reg,
|
||
const char** import_names, const char** import_qns, int import_count) {
|
||
if (!result || !reg) return 0;
|
||
/* Snapshot the count: we append to resolved_calls inside the loop,
|
||
* but only want to scan the original unresolved entries. */
|
||
int initial_count = result->resolved_calls.count;
|
||
int newly_resolved = 0;
|
||
|
||
for (int i = 0; i < initial_count; i++) {
|
||
const CBMResolvedCall* uc = &result->resolved_calls.items[i];
|
||
if (!uc->strategy || !uc->callee_qn || !uc->caller_qn) continue;
|
||
if (strcmp(uc->strategy, "lsp_unresolved") != 0) continue;
|
||
|
||
/* Case 1: callee_qn is already a fully-qualified type/pkg
|
||
* symbol that per-file LSP couldn't find in its local registry
|
||
* but the global registry has. (method_not_found with NAMED
|
||
* receiver, or any other case where callee_qn looks like a
|
||
* full QN). Direct lookup. */
|
||
const CBMRegisteredFunc* f = cbm_registry_lookup_func(reg, uc->callee_qn);
|
||
|
||
/* Case 2: callee_qn is "local_pkg_alias.suffix" — the alias
|
||
* needs translating through the file's import map to get the
|
||
* real module QN. */
|
||
if (!f) {
|
||
const char* dot = strchr(uc->callee_qn, '.');
|
||
if (dot) {
|
||
size_t prefix_len = (size_t)(dot - uc->callee_qn);
|
||
if (prefix_len > 0 && prefix_len < 256) {
|
||
char prefix[256];
|
||
memcpy(prefix, uc->callee_qn, prefix_len);
|
||
prefix[prefix_len] = '\0';
|
||
const char* suffix = dot + 1;
|
||
for (int j = 0; j < import_count; j++) {
|
||
if (import_names[j] && import_qns[j] &&
|
||
strcmp(prefix, import_names[j]) == 0) {
|
||
char fq[1024];
|
||
int n = snprintf(fq, sizeof(fq), "%s.%s",
|
||
import_qns[j], suffix);
|
||
if (n > 0 && n < (int)sizeof(fq)) {
|
||
f = cbm_registry_lookup_func(reg, fq);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!f) continue;
|
||
|
||
/* Emit a resolved entry. cbm_pipeline_find_lsp_resolution
|
||
* picks the highest-confidence match, so the unresolved entry
|
||
* stays (harmless duplicate) but our resolved entry wins. */
|
||
CBMResolvedCall rc;
|
||
rc.caller_qn = uc->caller_qn;
|
||
rc.callee_qn = f->qualified_name; /* borrowed from pipeline arena */
|
||
rc.strategy = "lsp_strategy_cross_file";
|
||
rc.confidence = 0.92f;
|
||
rc.reason = NULL;
|
||
cbm_resolvedcall_push(&result->resolved_calls, &result->arena, rc);
|
||
newly_resolved++;
|
||
}
|
||
|
||
/* Return value mirrors the old contract (count of items still
|
||
* needing the slow path) but is always 0 now — caller should
|
||
* unconditionally skip the slow path for Go. */
|
||
(void)newly_resolved;
|
||
return 0;
|
||
}
|
||
|
||
// --- Batch cross-file LSP ---
|
||
|
||
void cbm_batch_go_lsp_cross(
|
||
CBMArena* arena,
|
||
CBMBatchGoLSPFile* files, int file_count,
|
||
CBMResolvedCallArray* out)
|
||
{
|
||
if (!files || file_count <= 0 || !out) return;
|
||
|
||
for (int f = 0; f < file_count; f++) {
|
||
CBMBatchGoLSPFile* 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_go_lsp_cross(
|
||
&file_arena,
|
||
file->source, file->source_len,
|
||
file->module_qn,
|
||
file->defs, file->def_count,
|
||
file->import_names, file->import_qns, file->import_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);
|
||
}
|
||
}
|