diff --git a/internal/cbm/extract_calls.c b/internal/cbm/extract_calls.c index 5bb7e9b73..98b924b8c 100644 --- a/internal/cbm/extract_calls.c +++ b/internal/cbm/extract_calls.c @@ -885,9 +885,8 @@ static bool is_url_or_topic_keyword(const char *key) { // arg (e.g. Go `SendMessageInput{QueueUrl: ...}`, `PublishInput{TopicArn: ...}`). // Case-insensitive so QueueUrl/QueueURL/queue_url all match. static bool is_queue_topic_field(const char *key) { - static const char *fields[] = {"QueueUrl", "QueueURL", "TopicArn", "TopicARN", - "QueueName", "TopicName", "QueueArn", "QueueARN", - "Destination", NULL}; + static const char *fields[] = {"QueueUrl", "QueueURL", "TopicArn", "TopicARN", "QueueName", + "TopicName", "QueueArn", "QueueARN", "Destination", NULL}; if (!key || !key[0]) { return false; } @@ -1080,8 +1079,7 @@ static const char *extract_handler_arg(CBMExtractCtx *ctx, TSNode args) { for (uint32_t ai = HANDLER_START_IDX; ai < nc && ai < MAX_HANDLER_SCAN; ai++) { TSNode arg2 = ts_node_named_child(args, ai); /* PHP wraps each argument in an `argument` node — unwrap to the value. */ - if (strcmp(ts_node_type(arg2), "argument") == 0 && - ts_node_named_child_count(arg2) > 0) { + if (strcmp(ts_node_type(arg2), "argument") == 0 && ts_node_named_child_count(arg2) > 0) { arg2 = ts_node_named_child(arg2, 0); } const char *ak2 = ts_node_type(arg2); @@ -1093,8 +1091,8 @@ static const char *extract_handler_arg(CBMExtractCtx *ctx, TSNode args) { return cbm_node_text(ctx->arena, arg2, ctx->source); } if (is_string_like(ak2)) { - const char *h = normalize_string_handler(ctx->arena, - cbm_node_text(ctx->arena, arg2, ctx->source)); + const char *h = + normalize_string_handler(ctx->arena, cbm_node_text(ctx->arena, arg2, ctx->source)); if (h && h[0]) { return h; } @@ -1138,11 +1136,6 @@ void handle_calls(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec, Walk call.start_line = (int)ts_node_start_point(node).row + TS_LINE_OFFSET; TSNode args = ts_node_child_by_field_name(node, TS_FIELD("arguments")); - if (getenv("CBM_DBG_ARGS") && callee && (strstr(callee,"get")||strstr(callee,"Get")||strstr(callee,"client")||strstr(callee,"Route"))) { - fprintf(stderr, "[DBGARGS] callee=%s node=%s args_null=%d", callee, ts_node_type(node), ts_node_is_null(args)); - if (!ts_node_is_null(args)) { uint32_t n=ts_node_named_child_count(args); fprintf(stderr, " nargs=%u:", n); for(uint32_t i=0;i= out_cap) { return; } @@ -2977,8 +2977,7 @@ static void extract_class_def(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec if (!ts_node_is_null(inner) && strcmp(ts_node_type(inner), "binary_expression") == 0 && ts_node_named_child_count(inner) > 0) { name_node = ts_node_named_child(inner, 0); /* LHS of `<:` */ - } else if (!ts_node_is_null(inner) && - strcmp(ts_node_type(inner), "identifier") == 0) { + } else if (!ts_node_is_null(inner) && strcmp(ts_node_type(inner), "identifier") == 0) { name_node = inner; } else { name_node = cbm_find_child_by_kind(th, "identifier"); @@ -3078,8 +3077,8 @@ static void extract_class_def(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec } break; case CBM_LANG_GLEAM: // type_definition > type_name > type_identifier - name_node = find_first_descendant_by_kind(node, "type_identifier", - CBM_DESCENDANT_MAX_DEPTH); + name_node = + find_first_descendant_by_kind(node, "type_identifier", CBM_DESCENDANT_MAX_DEPTH); break; case CBM_LANG_RESCRIPT: { // type_declaration > type_binding(name=type_identifier) TSNode binding = cbm_find_child_by_kind(node, "type_binding"); @@ -3092,8 +3091,7 @@ static void extract_class_def(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec break; } case CBM_LANG_FSHARP: { // type_definition > *_type_defn > type_name > identifier - TSNode tn = find_first_descendant_by_kind(node, "type_name", - CBM_DESCENDANT_MAX_DEPTH); + TSNode tn = find_first_descendant_by_kind(node, "type_name", CBM_DESCENDANT_MAX_DEPTH); if (!ts_node_is_null(tn)) { name_node = ts_node_child_by_field_name(tn, TS_FIELD("type_name")); if (ts_node_is_null(name_node)) { @@ -3130,8 +3128,7 @@ static void extract_class_def(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec } case CBM_LANG_PASCAL: { // declClass is nested; name lives on the parent declType TSNode parent = ts_node_parent(node); - if (!ts_node_is_null(parent) && - strcmp(ts_node_type(parent), "declType") == 0) { + if (!ts_node_is_null(parent) && strcmp(ts_node_type(parent), "declType") == 0) { name_node = ts_node_child_by_field_name(parent, TS_FIELD("name")); } break; @@ -3175,9 +3172,8 @@ static void extract_class_def(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec // registry only indexes Function/Method/Class/Interface labels), letting // `inherit Base` resolve into an INHERITS edge. if (ctx->language == CBM_LANG_FSHARP && strcmp(label, "Type") == 0) { - if (!ts_node_is_null( - find_first_descendant_by_kind(node, "primary_constr_args", - CBM_DESCENDANT_MAX_DEPTH)) || + if (!ts_node_is_null(find_first_descendant_by_kind(node, "primary_constr_args", + CBM_DESCENDANT_MAX_DEPTH)) || !ts_node_is_null(find_first_descendant_by_kind(node, "class_inherits_decl", CBM_DESCENDANT_MAX_DEPTH))) { label = "Class"; @@ -5182,8 +5178,9 @@ static void extract_lisp_def(CBMExtractCtx *ctx, TSNode node) { * `ERROR` node, so the outer class is never recognized as a `class_declaration` * and disappears from the graph: * - * class MyClass { companion object : Factory() { ... } } // anon companion w/ delegation - * class Tree { inner class Node : BaseNode() { ... } } // inner + delegation + * class MyClass { companion object : Factory() { ... } } // anon companion w/ + * delegation class Tree { inner class Node : BaseNode() { ... } } // inner + + * delegation * * Inside the ERROR node the tokens are still present as a flat child list: * `class`/`object` keyword token → simple_identifier/type_identifier (name) @@ -5243,16 +5240,14 @@ static void recover_kotlin_error_classes(CBMExtractCtx *ctx, TSNode err_node) { /* delegation_specifier → user_type (directly or under * constructor_invocation) → type_identifier; strip generic args. */ TSNode ut = ts_node_named_child(sib, 0); - if (!ts_node_is_null(ut) && - strcmp(ts_node_type(ut), "constructor_invocation") == 0) { + if (!ts_node_is_null(ut) && strcmp(ts_node_type(ut), "constructor_invocation") == 0) { ut = ts_node_named_child(ut, 0); } if (ts_node_is_null(ut)) { continue; } TSNode ti = ut; - if (strcmp(ts_node_type(ut), "user_type") == 0 && - ts_node_named_child_count(ut) > 0) { + if (strcmp(ts_node_type(ut), "user_type") == 0 && ts_node_named_child_count(ut) > 0) { ti = ts_node_named_child(ut, 0); } push_base_text(a, ti, ctx->source, bases, MAX_BASES_MINUS_1, &bcount); @@ -5268,8 +5263,8 @@ static void recover_kotlin_error_classes(CBMExtractCtx *ctx, TSNode err_node) { def.end_line = ts_node_end_point(err_node).row + TS_LINE_OFFSET; def.is_exported = cbm_is_exported(name, ctx->language); if (bcount > 0) { - const char **result = (const char **)cbm_arena_alloc( - a, (size_t)(bcount + NULL_TERM) * sizeof(const char *)); + const char **result = (const char **)cbm_arena_alloc(a, (size_t)(bcount + NULL_TERM) * + sizeof(const char *)); if (result) { for (int k = 0; k < bcount; k++) { result[k] = bases[k]; diff --git a/internal/cbm/extract_imports.c b/internal/cbm/extract_imports.c index 8208e1b23..717068a2b 100644 --- a/internal/cbm/extract_imports.c +++ b/internal/cbm/extract_imports.c @@ -1465,7 +1465,6 @@ static void parse_embedded_imports(CBMExtractCtx *ctx) { } } - // --- Namespace / package declaration capture --- // Java/Kotlin/C#/PHP put the file's symbols inside a namespace/package whose // name is NOT reflected in the path-based QN scheme. Capturing it lets the @@ -1699,8 +1698,7 @@ static void lisp_push_clause_modules(CBMExtractCtx *ctx, TSNode clause) { TSNode item = ts_node_named_child(clause, j); const char *ik = ts_node_type(item); /* `[app.util :as u]` — the module is the vector's first symbol. */ - if (strcmp(ik, "vec_lit") == 0 || strcmp(ik, "list_lit") == 0 || - strcmp(ik, "list") == 0) { + if (strcmp(ik, "vec_lit") == 0 || strcmp(ik, "list_lit") == 0 || strcmp(ik, "list") == 0) { uint32_t vc = ts_node_named_child_count(item); if (vc > 0) { lisp_push_module(ctx, ts_node_named_child(item, 0)); @@ -1807,22 +1805,23 @@ static void lisp_process_list(CBMExtractCtx *ctx, TSNode node) { } } -/* Recursively walk lisp lists. Fennel binds requires inside other forms +/* Iteratively walk lisp lists. Fennel binds requires inside other forms * (`(local util (require :util))`), so we must descend into every list, not - * just root children. */ -static void lisp_walk(CBMExtractCtx *ctx, TSNode node) { // NOLINT(misc-no-recursion) - const char *nt = ts_node_type(node); - if (strcmp(nt, "list") == 0 || strcmp(nt, "list_lit") == 0) { - lisp_process_list(ctx, node); - } - uint32_t cc = ts_node_named_child_count(node); - for (uint32_t i = 0; i < cc; i++) { - lisp_walk(ctx, ts_node_named_child(node, i)); - } -} - + * just root children. Stack-based (not recursive) to avoid deep-nesting stack + * overflow, matching the other walkers in this file. */ static void parse_lisp_imports(CBMExtractCtx *ctx) { - lisp_walk(ctx, ctx->root); + CBMArena *a = ctx->arena; + TSNodeStack stack; + ts_nstack_init(&stack, a, CBM_SZ_512); + ts_nstack_push(&stack, a, ctx->root); + while (stack.count > 0) { + TSNode node = ts_nstack_pop(&stack); + const char *nt = ts_node_type(node); + if (strcmp(nt, "list") == 0 || strcmp(nt, "list_lit") == 0) { + lisp_process_list(ctx, node); + } + ts_nstack_push_children(&stack, a, node); + } } // --- Starlark imports --- @@ -2089,11 +2088,10 @@ static void push_path_import(CBMExtractCtx *ctx, TSNode node) { // back to text that still contains a directive keyword. static void push_string_descendant_import(CBMExtractCtx *ctx, TSNode node) { TSNode hit = node; - static const char *content_kinds[] = {"string_content", "str_literal", - "slStringLiteralPart", "include_path", - "attribute_value", "path_fragment", - "string_fragment", "literal_content", - NULL}; + static const char *content_kinds[] = { + "string_content", "str_literal", "slStringLiteralPart", + "include_path", "attribute_value", "path_fragment", + "string_fragment", "literal_content", NULL}; for (const char **k = content_kinds; *k; k++) { if (find_first_descendant_of(node, *k, &hit)) { push_path_import(ctx, hit); @@ -2101,7 +2099,7 @@ static void push_string_descendant_import(CBMExtractCtx *ctx, TSNode node) { } } /* Quoted string nodes (Just/Pkl/Jsonnet variants): strip the quotes. */ - static const char *string_kinds[] = {"string", "string_value", "string_literal", + static const char *string_kinds[] = {"string", "string_value", "string_literal", "static_string", "stringConstant", NULL}; for (const char **k = string_kinds; *k; k++) { if (find_first_descendant_of(node, *k, &hit)) { @@ -2271,9 +2269,8 @@ static void parse_jsonnet_imports(CBMExtractCtx *ctx) { while (stack.count > 0) { TSNode node = ts_nstack_pop(&stack); const char *k = ts_node_type(node); - if (ts_node_is_named(node) && - (strcmp(k, "import") == 0 || strcmp(k, "importstr") == 0 || - strcmp(k, "importbin") == 0)) { + if (ts_node_is_named(node) && (strcmp(k, "import") == 0 || strcmp(k, "importstr") == 0 || + strcmp(k, "importbin") == 0)) { push_string_descendant_import(ctx, node); } ts_nstack_push_children(&stack, ctx->arena, node); diff --git a/internal/cbm/extract_semantic.c b/internal/cbm/extract_semantic.c index 545f9a49e..9379f7171 100644 --- a/internal/cbm/extract_semantic.c +++ b/internal/cbm/extract_semantic.c @@ -204,8 +204,8 @@ static TSNode resolve_write_lhs_node(TSNode node) { return left; } const char *nk = ts_node_type(node); - if (strcmp(nk, "postfix_unary_expression") == 0 || - strcmp(nk, "prefix_unary_expression") == 0 || strcmp(nk, "update_expression") == 0) { + if (strcmp(nk, "postfix_unary_expression") == 0 || strcmp(nk, "prefix_unary_expression") == 0 || + strcmp(nk, "update_expression") == 0) { // Only ++/-- mutate their operand. Other unary postfix/prefix forms // (C# null-forgiving `x!`, address-of `&x`, deref `*x`, logical `!x`) // READ the operand — never treat them as writes. @@ -230,9 +230,10 @@ static TSNode resolve_write_lhs_node(TSNode node) { TSNode c = ts_node_named_child(node, i); const char *ck = ts_node_type(c); if (strcmp(ck, "identifier") == 0 || strcmp(ck, "simple_identifier") == 0 || - strcmp(ck, "member_access_expression") == 0 || strcmp(ck, "field_expression") == 0 || - strcmp(ck, "field_access") == 0 || strcmp(ck, "selector_expression") == 0 || - strcmp(ck, "subscript_expression") == 0 || strcmp(ck, "index_expression") == 0) { + strcmp(ck, "member_access_expression") == 0 || + strcmp(ck, "field_expression") == 0 || strcmp(ck, "field_access") == 0 || + strcmp(ck, "selector_expression") == 0 || strcmp(ck, "subscript_expression") == 0 || + strcmp(ck, "index_expression") == 0) { return c; } } diff --git a/internal/cbm/extract_unified.c b/internal/cbm/extract_unified.c index 3c22a44e9..7274158f0 100644 --- a/internal/cbm/extract_unified.c +++ b/internal/cbm/extract_unified.c @@ -775,7 +775,8 @@ static bool is_export_of_declaration(TSNode node) { for (uint32_t i = 0; i < n; i++) { const char *ck = ts_node_type(ts_node_child(node, i)); if (strcmp(ck, "function_declaration") == 0 || strcmp(ck, "class_declaration") == 0 || - strcmp(ck, "lexical_declaration") == 0 || strcmp(ck, "abstract_class_declaration") == 0 || + strcmp(ck, "lexical_declaration") == 0 || + strcmp(ck, "abstract_class_declaration") == 0 || strcmp(ck, "interface_declaration") == 0 || strcmp(ck, "enum_declaration") == 0 || strcmp(ck, "type_alias_declaration") == 0 || strcmp(ck, "variable_declaration") == 0 || strcmp(ck, "generator_function_declaration") == 0) { diff --git a/internal/cbm/helpers.c b/internal/cbm/helpers.c index 213d06ffa..1efa6b819 100644 --- a/internal/cbm/helpers.c +++ b/internal/cbm/helpers.c @@ -131,10 +131,10 @@ static const char *java_keywords[] = { * (`data`, `open`, `sealed`, `suspend`, …) are context-sensitive and usable as * identifiers, so they are intentionally omitted. */ static const char *kotlin_keywords[] = { - "as", "break", "class", "continue", "do", "else", "false", "for", - "fun", "if", "in", "interface", "is", "null", "object", "package", - "return", "super", "this", "throw", "true", "try", "typealias", "typeof", - "val", "var", "when", "while", NULL}; + "as", "break", "class", "continue", "do", "else", "false", "for", + "fun", "if", "in", "interface", "is", "null", "object", "package", + "return", "super", "this", "throw", "true", "try", "typealias", "typeof", + "val", "var", "when", "while", NULL}; static const char *generic_keywords[] = { "true", "false", "null", "nil", "None", "undefined", "void", "if", diff --git a/internal/cbm/lang_specs.c b/internal/cbm/lang_specs.c index 44c455b8b..d00387194 100644 --- a/internal/cbm/lang_specs.c +++ b/internal/cbm/lang_specs.c @@ -363,8 +363,7 @@ static const char *cs_class_types[] = {"class_declaration", "type_declaration", NULL}; static const char *cs_module_types[] = {"compilation_unit", NULL}; -static const char *cs_call_types[] = {"invocation_expression", "object_creation_expression", - NULL}; +static const char *cs_call_types[] = {"invocation_expression", "object_creation_expression", NULL}; static const char *cs_import_types[] = {"using_directive", "namespace_use_declaration", "using_statement", "namespace_declaration", NULL}; static const char *cs_branch_types[] = {"if_statement", "for_statement", "foreach_statement", @@ -389,10 +388,9 @@ static const char *php_class_types[] = {"trait_declaration", "enum_declaration", static const char *php_import_types[] = {"extends", "include", "namespace_use_declaration", "require", "use_declaration", NULL}; static const char *php_module_types[] = {"program", NULL}; -static const char *php_call_types[] = {"member_call_expression", "scoped_call_expression", - "function_call_expression", - "object_creation_expression", - "nullsafe_member_call_expression", NULL}; +static const char *php_call_types[] = { + "member_call_expression", "scoped_call_expression", "function_call_expression", + "object_creation_expression", "nullsafe_member_call_expression", NULL}; static const char *php_branch_types[] = {"if_statement", "for_statement", "foreach_statement", "while_statement", "switch_statement", "case_statement", "try_statement", "catch_clause", NULL}; @@ -418,8 +416,9 @@ static const char *scala_class_types[] = {"class_definition", "object_definition "trait_definition", "enum_definition", "type_definition", NULL}; static const char *scala_module_types[] = {"compilation_unit", NULL}; -static const char *scala_call_types[] = {"call_expression", "generic_function", "field_expression", - "infix_expression", "instance_expression", NULL}; +static const char *scala_call_types[] = {"call_expression", "generic_function", + "field_expression", "infix_expression", + "instance_expression", NULL}; static const char *scala_import_types[] = {"import_declaration", "extends", "import", "using_directive", NULL}; static const char *scala_branch_types[] = { @@ -1528,8 +1527,8 @@ static const char *smithy_class_types[] = {"structure_statement", "union_stateme static const char *smithy_field_types[] = {"shape_member", NULL}; static const char *smithy_import_types[] = {"use_statement", NULL}; static const char *smithy_module_types[] = {"source_file", NULL}; -static const char *wit_func_types[] = {"func_item", "resource_method", "export_item", - "import_item", NULL}; +static const char *wit_func_types[] = {"func_item", "resource_method", "export_item", "import_item", + NULL}; static const char *wit_class_types[] = {"record_item", "resource_item", "enum_items", "variant_items", "flags_items", NULL}; static const char *wit_field_types[] = {"record_field", NULL}; diff --git a/internal/cbm/lsp/java_lsp.c b/internal/cbm/lsp/java_lsp.c index f4c81c817..48e8284ae 100644 --- a/internal/cbm/lsp/java_lsp.c +++ b/internal/cbm/lsp/java_lsp.c @@ -40,11 +40,11 @@ #include #include -#define JAVA_LSP_MAX_EVAL_DEPTH 32 -#define JAVA_LSP_MAX_STMT_DEPTH 256 -#define JAVA_LSP_MAX_INHERIT_HOPS 32 -#define JAVA_LSP_MAX_OVERLOADS 64 -#define JAVA_LSP_BUF 1024 +#define JAVA_LSP_MAX_EVAL_DEPTH 32 +#define JAVA_LSP_MAX_STMT_DEPTH 256 +#define JAVA_LSP_MAX_INHERIT_HOPS 32 +#define JAVA_LSP_MAX_OVERLOADS 64 +#define JAVA_LSP_BUF 1024 /* Forward declarations ─────────────────────────────────────────────── */ @@ -59,7 +59,7 @@ static void process_local_var_decl(JavaLSPContext *ctx, TSNode node); static void process_enhanced_for(JavaLSPContext *ctx, TSNode node); static void process_block(JavaLSPContext *ctx, TSNode node); static void java_emit_resolved(JavaLSPContext *ctx, const char *callee_qn, const char *strategy, - float confidence); + float confidence); static void java_emit_unresolved(JavaLSPContext *ctx, const char *expr_text, const char *reason); static const CBMType *eval_method_invocation(JavaLSPContext *ctx, TSNode node); static const CBMType *eval_object_creation(JavaLSPContext *ctx, TSNode node); @@ -84,17 +84,15 @@ static bool is_node_kind(TSNode node, const char *kind); static TSNode child_by_kind(TSNode parent, const char *kind); static const CBMType *box_primitive(CBMArena *a, const char *prim); static int count_call_args(TSNode call_node); -static const CBMType *propagate_template(CBMArena *a, const char *recv_qn, - const char *method_name, +static const CBMType *propagate_template(CBMArena *a, const char *recv_qn, const char *method_name, const CBMType *const *recv_targs, int recv_targ_count, const CBMType *return_t); static bool method_implies_lambda_args(const char *recv_qn, const char *method_name, - const CBMType *const *targs, int targ_count, - int *out_arity, const CBMType **out_param0, - const CBMType **out_param1); + const CBMType *const *targs, int targ_count, int *out_arity, + const CBMType **out_param0, const CBMType **out_param1); static void resolve_method_reference(JavaLSPContext *ctx, TSNode mref, - const CBMRegisteredFunc *outer_resolved, - int arg_index, const CBMType *recv_type); + const CBMRegisteredFunc *outer_resolved, int arg_index, + const CBMType *recv_type); static bool is_map_like(const char *qn); /* ── Built-in primitive table ─────────────────────────────────────── */ @@ -117,39 +115,76 @@ static const char *JAVA_FALLBACK_PACKAGES[] = { NULL, }; -static const char *JAVA_PRIMITIVES[] = { - "boolean", "byte", "char", "short", "int", "long", "float", "double", "void", NULL -}; +static const char *JAVA_PRIMITIVES[] = {"boolean", "byte", "char", "short", "int", + "long", "float", "double", "void", NULL}; -static const char *JAVA_BOXED[] = { - "java.lang.Boolean", "java.lang.Byte", "java.lang.Character", "java.lang.Short", - "java.lang.Integer", "java.lang.Long", "java.lang.Float", "java.lang.Double", - "java.lang.Void", NULL -}; +static const char *JAVA_BOXED[] = {"java.lang.Boolean", "java.lang.Byte", + "java.lang.Character", "java.lang.Short", + "java.lang.Integer", "java.lang.Long", + "java.lang.Float", "java.lang.Double", + "java.lang.Void", NULL}; static bool is_java_primitive(const char *name) { - if (!name) return false; + if (!name) + return false; for (int i = 0; JAVA_PRIMITIVES[i]; i++) { - if (strcmp(name, JAVA_PRIMITIVES[i]) == 0) return true; + if (strcmp(name, JAVA_PRIMITIVES[i]) == 0) + return true; } return false; } /* Auto-imported java.lang single-type names. The JLS §7.5.5 says all classes * in java.lang are imported on demand into every compilation unit. */ -static const char *JAVA_LANG_TYPES[] = { - "Object", "String", "StringBuilder", "StringBuffer", "CharSequence", - "Boolean", "Byte", "Character", "Short", "Integer", "Long", "Float", "Double", - "Number", "Math", "System", "Thread", "Runnable", "Iterable", "Comparable", - "Cloneable", "Class", "ClassLoader", "Throwable", "Exception", "Error", - "RuntimeException", "NullPointerException", "IllegalArgumentException", - "IllegalStateException", "IndexOutOfBoundsException", "ArrayIndexOutOfBoundsException", - "ArithmeticException", "ClassCastException", "ClassNotFoundException", - "NumberFormatException", "UnsupportedOperationException", "Enum", "Record", - "AutoCloseable", "Process", "ProcessBuilder", "StackTraceElement", - "Override", "Deprecated", "SuppressWarnings", "FunctionalInterface", - "Void", NULL -}; +static const char *JAVA_LANG_TYPES[] = {"Object", + "String", + "StringBuilder", + "StringBuffer", + "CharSequence", + "Boolean", + "Byte", + "Character", + "Short", + "Integer", + "Long", + "Float", + "Double", + "Number", + "Math", + "System", + "Thread", + "Runnable", + "Iterable", + "Comparable", + "Cloneable", + "Class", + "ClassLoader", + "Throwable", + "Exception", + "Error", + "RuntimeException", + "NullPointerException", + "IllegalArgumentException", + "IllegalStateException", + "IndexOutOfBoundsException", + "ArrayIndexOutOfBoundsException", + "ArithmeticException", + "ClassCastException", + "ClassNotFoundException", + "NumberFormatException", + "UnsupportedOperationException", + "Enum", + "Record", + "AutoCloseable", + "Process", + "ProcessBuilder", + "StackTraceElement", + "Override", + "Deprecated", + "SuppressWarnings", + "FunctionalInterface", + "Void", + NULL}; /* ── Helpers ──────────────────────────────────────────────────────── */ @@ -158,16 +193,19 @@ static char *java_node_text(JavaLSPContext *ctx, TSNode node) { } static bool is_node_kind(TSNode node, const char *kind) { - if (ts_node_is_null(node)) return false; + if (ts_node_is_null(node)) + return false; return strcmp(ts_node_type(node), kind) == 0; } static TSNode child_by_kind(TSNode parent, const char *kind) { - if (ts_node_is_null(parent)) return parent; + if (ts_node_is_null(parent)) + return parent; uint32_t n = ts_node_named_child_count(parent); for (uint32_t i = 0; i < n; i++) { TSNode c = ts_node_named_child(parent, i); - if (strcmp(ts_node_type(c), kind) == 0) return c; + if (strcmp(ts_node_type(c), kind) == 0) + return c; } TSNode null_node; memset(&null_node, 0, sizeof(null_node)); @@ -175,51 +213,57 @@ static TSNode child_by_kind(TSNode parent, const char *kind) { } static int count_call_args(TSNode call_node) { - if (ts_node_is_null(call_node)) return 0; + if (ts_node_is_null(call_node)) + return 0; TSNode args = ts_node_child_by_field_name(call_node, "arguments", 9); - if (ts_node_is_null(args)) return 0; + if (ts_node_is_null(args)) + return 0; return (int)ts_node_named_child_count(args); } /* Strip generic parameters from a type text (List → List). */ static const char *strip_generics(CBMArena *a, const char *type_text) { - if (!type_text) return NULL; + if (!type_text) + return NULL; const char *lt = strchr(type_text, '<'); - if (!lt) return type_text; + if (!lt) + return type_text; return cbm_arena_strndup(a, type_text, (size_t)(lt - type_text)); } /* Strip array suffix (`int[]` → `int`, `String[][]` → `String`, dim=2). */ static const char *unwrap_array_text(CBMArena *a, const char *type_text, int *out_dim) { - if (out_dim) *out_dim = 0; - if (!type_text) return NULL; + if (out_dim) + *out_dim = 0; + if (!type_text) + return NULL; size_t n = strlen(type_text); int dim = 0; while (n >= 2 && type_text[n - 1] == ']' && type_text[n - 2] == '[') { n -= 2; dim++; } - if (out_dim) *out_dim = dim; - if (dim == 0) return type_text; + if (out_dim) + *out_dim = dim; + if (dim == 0) + return type_text; return cbm_arena_strndup(a, type_text, n); } /* Map primitive name → boxed wrapper QN. */ static const CBMType *box_primitive(CBMArena *a, const char *prim) { - if (!prim) return cbm_type_unknown(); + if (!prim) + return cbm_type_unknown(); static const char *map[][2] = { - {"boolean", "java.lang.Boolean"}, - {"byte", "java.lang.Byte"}, - {"char", "java.lang.Character"}, - {"short", "java.lang.Short"}, - {"int", "java.lang.Integer"}, - {"long", "java.lang.Long"}, - {"float", "java.lang.Float"}, - {"double", "java.lang.Double"}, + {"boolean", "java.lang.Boolean"}, {"byte", "java.lang.Byte"}, + {"char", "java.lang.Character"}, {"short", "java.lang.Short"}, + {"int", "java.lang.Integer"}, {"long", "java.lang.Long"}, + {"float", "java.lang.Float"}, {"double", "java.lang.Double"}, {"void", "java.lang.Void"}, }; for (size_t i = 0; i < sizeof(map) / sizeof(map[0]); i++) { - if (strcmp(prim, map[i][0]) == 0) return cbm_type_named(a, map[i][1]); + if (strcmp(prim, map[i][0]) == 0) + return cbm_type_named(a, map[i][1]); } return cbm_type_unknown(); } @@ -227,8 +271,8 @@ static const CBMType *box_primitive(CBMArena *a, const char *prim) { /* ── Initialization ───────────────────────────────────────────────── */ void java_lsp_init(JavaLSPContext *ctx, CBMArena *arena, const char *source, int source_len, - const CBMTypeRegistry *registry, const char *package_name, - const char *module_qn, CBMResolvedCallArray *out) { + const CBMTypeRegistry *registry, const char *package_name, const char *module_qn, + CBMResolvedCallArray *out) { memset(ctx, 0, sizeof(*ctx)); ctx->arena = arena; ctx->source = source; @@ -245,20 +289,21 @@ void java_lsp_init(JavaLSPContext *ctx, CBMArena *arena, const char *source, int void java_lsp_add_import(JavaLSPContext *ctx, const char *local_name, const char *target_qn, int kind) { - if (!ctx || !local_name || !target_qn) return; + if (!ctx || !local_name || !target_qn) + return; if (ctx->import_count >= ctx->import_cap) { int new_cap = ctx->import_cap == 0 ? 16 : ctx->import_cap * 2; - const char **new_names = (const char **)cbm_arena_alloc(ctx->arena, - (size_t)new_cap * sizeof(*new_names)); - const char **new_qns = (const char **)cbm_arena_alloc(ctx->arena, - (size_t)new_cap * sizeof(*new_qns)); + const char **new_names = + (const char **)cbm_arena_alloc(ctx->arena, (size_t)new_cap * sizeof(*new_names)); + const char **new_qns = + (const char **)cbm_arena_alloc(ctx->arena, (size_t)new_cap * sizeof(*new_qns)); int *new_kinds = (int *)cbm_arena_alloc(ctx->arena, (size_t)new_cap * sizeof(*new_kinds)); - if (!new_names || !new_qns || !new_kinds) return; + if (!new_names || !new_qns || !new_kinds) + return; if (ctx->import_count > 0) { memcpy(new_names, ctx->import_local_names, (size_t)ctx->import_count * sizeof(*new_names)); - memcpy(new_qns, ctx->import_target_qns, - (size_t)ctx->import_count * sizeof(*new_qns)); + memcpy(new_qns, ctx->import_target_qns, (size_t)ctx->import_count * sizeof(*new_qns)); memcpy(new_kinds, ctx->import_kinds, (size_t)ctx->import_count * sizeof(*new_kinds)); } ctx->import_local_names = new_names; @@ -275,9 +320,10 @@ void java_lsp_add_import(JavaLSPContext *ctx, const char *local_name, const char static void push_enclosing_class(JavaLSPContext *ctx, const char *class_qn) { if (ctx->enclosing_class_depth >= ctx->enclosing_class_cap) { int new_cap = ctx->enclosing_class_cap == 0 ? 8 : ctx->enclosing_class_cap * 2; - const char **arr = (const char **)cbm_arena_alloc(ctx->arena, - (size_t)new_cap * sizeof(*arr)); - if (!arr) return; + const char **arr = + (const char **)cbm_arena_alloc(ctx->arena, (size_t)new_cap * sizeof(*arr)); + if (!arr) + return; if (ctx->enclosing_class_depth > 0) { memcpy(arr, ctx->enclosing_class_stack, (size_t)ctx->enclosing_class_depth * sizeof(*arr)); @@ -289,7 +335,8 @@ static void push_enclosing_class(JavaLSPContext *ctx, const char *class_qn) { } static void pop_enclosing_class(JavaLSPContext *ctx) { - if (ctx->enclosing_class_depth > 0) ctx->enclosing_class_depth--; + if (ctx->enclosing_class_depth > 0) + ctx->enclosing_class_depth--; } /* ── Type-AST → CBMType ───────────────────────────────────────────── */ @@ -297,12 +344,15 @@ static void pop_enclosing_class(JavaLSPContext *ctx) { static const CBMType *parse_type_arguments(JavaLSPContext *ctx, TSNode targs_node); const CBMType *java_parse_type_node(JavaLSPContext *ctx, TSNode node) { - if (ts_node_is_null(node)) return cbm_type_unknown(); + if (ts_node_is_null(node)) + return cbm_type_unknown(); const char *kind = ts_node_type(node); /* primitives + void */ - if (strcmp(kind, "void_type") == 0) return cbm_type_builtin(ctx->arena, "void"); - if (strcmp(kind, "boolean_type") == 0) return cbm_type_builtin(ctx->arena, "boolean"); + if (strcmp(kind, "void_type") == 0) + return cbm_type_builtin(ctx->arena, "void"); + if (strcmp(kind, "boolean_type") == 0) + return cbm_type_builtin(ctx->arena, "boolean"); if (strcmp(kind, "integral_type") == 0 || strcmp(kind, "floating_point_type") == 0) { char *txt = java_node_text(ctx, node); return cbm_type_builtin(ctx->arena, txt ? txt : "int"); @@ -311,9 +361,11 @@ const CBMType *java_parse_type_node(JavaLSPContext *ctx, TSNode node) { /* type_identifier — bare name. Look up via resolver, then fallback. */ if (strcmp(kind, "type_identifier") == 0) { char *name = java_node_text(ctx, node); - if (!name) return cbm_type_unknown(); + if (!name) + return cbm_type_unknown(); const char *qn = java_resolve_type_name(ctx, name); - if (qn) return cbm_type_named(ctx->arena, qn); + if (qn) + return cbm_type_named(ctx->arena, qn); /* Could still be a type variable inside a generic method/class. */ return cbm_type_named(ctx->arena, name); } @@ -321,7 +373,8 @@ const CBMType *java_parse_type_node(JavaLSPContext *ctx, TSNode node) { /* scoped_type_identifier — Outer.Inner or pkg.Type. */ if (strcmp(kind, "scoped_type_identifier") == 0) { char *full = java_node_text(ctx, node); - if (!full) return cbm_type_unknown(); + if (!full) + return cbm_type_unknown(); /* Try treating the whole text as already-qualified. */ if (cbm_registry_lookup_type(ctx->registry, full)) { return cbm_type_named(ctx->arena, full); @@ -344,17 +397,18 @@ const CBMType *java_parse_type_node(JavaLSPContext *ctx, TSNode node) { * substitution depends on having both K and V available. */ if (strcmp(kind, "generic_type") == 0) { TSNode raw = ts_node_named_child(node, 0); - TSNode targs = ts_node_named_child_count(node) > 1 ? ts_node_named_child(node, 1) - : (TSNode){0}; + TSNode targs = + ts_node_named_child_count(node) > 1 ? ts_node_named_child(node, 1) : (TSNode){0}; const CBMType *base = java_parse_type_node(ctx, raw); const char *base_qn = NULL; - if (base && base->kind == CBM_TYPE_NAMED) base_qn = base->data.named.qualified_name; - if (!base_qn) return base; + if (base && base->kind == CBM_TYPE_NAMED) + base_qn = base->data.named.qualified_name; + if (!base_qn) + return base; /* Collect every type argument (K, V, R, ...). */ int arg_count = 0; const CBMType *arg_buf[16]; - if (!ts_node_is_null(targs) && - strcmp(ts_node_type(targs), "type_arguments") == 0) { + if (!ts_node_is_null(targs) && strcmp(ts_node_type(targs), "type_arguments") == 0) { uint32_t tn = ts_node_named_child_count(targs); for (uint32_t ti = 0; ti < tn && arg_count < 16; ti++) { arg_buf[arg_count++] = java_parse_type_node(ctx, ts_node_named_child(targs, ti)); @@ -363,10 +417,10 @@ const CBMType *java_parse_type_node(JavaLSPContext *ctx, TSNode node) { if (arg_count == 0) { arg_buf[arg_count++] = cbm_type_unknown(); } - const CBMType **args = (const CBMType **)cbm_arena_alloc(ctx->arena, - (size_t)(arg_count + 1) - * sizeof(*args)); - for (int i = 0; i < arg_count; i++) args[i] = arg_buf[i]; + const CBMType **args = + (const CBMType **)cbm_arena_alloc(ctx->arena, (size_t)(arg_count + 1) * sizeof(*args)); + for (int i = 0; i < arg_count; i++) + args[i] = arg_buf[i]; args[arg_count] = NULL; return cbm_type_template(ctx->arena, base_qn, args, arg_count); } @@ -384,40 +438,48 @@ const CBMType *java_parse_type_node(JavaLSPContext *ctx, TSNode node) { if (strcmp(kind, "type_parameter") == 0) { TSNode name_node = ts_node_named_child(node, 0); char *name = ts_node_is_null(name_node) ? NULL : java_node_text(ctx, name_node); - if (name) return cbm_type_type_param(ctx->arena, name); + if (name) + return cbm_type_type_param(ctx->arena, name); return cbm_type_unknown(); } /* wildcard `?` and `? extends T` collapse to UNKNOWN/upper bound. */ if (strcmp(kind, "wildcard") == 0) { uint32_t n = ts_node_named_child_count(node); - if (n > 0) return java_parse_type_node(ctx, ts_node_named_child(node, n - 1)); + if (n > 0) + return java_parse_type_node(ctx, ts_node_named_child(node, n - 1)); return cbm_type_unknown(); } /* Last resort: emit the raw text as a NAMED type. */ char *txt = java_node_text(ctx, node); - if (!txt) return cbm_type_unknown(); + if (!txt) + return cbm_type_unknown(); return cbm_type_named(ctx->arena, txt); } static const CBMType *parse_type_arguments(JavaLSPContext *ctx, TSNode targs_node) { - if (ts_node_is_null(targs_node)) return NULL; - if (strcmp(ts_node_type(targs_node), "type_arguments") != 0) return NULL; - if (ts_node_named_child_count(targs_node) == 0) return NULL; + if (ts_node_is_null(targs_node)) + return NULL; + if (strcmp(ts_node_type(targs_node), "type_arguments") != 0) + return NULL; + if (ts_node_named_child_count(targs_node) == 0) + return NULL; return java_parse_type_node(ctx, ts_node_named_child(targs_node, 0)); } /* ── Type-name resolution (JLS §6.5) ──────────────────────────────── */ const char *java_resolve_type_name(JavaLSPContext *ctx, const char *name) { - if (!name || !ctx) return NULL; + if (!name || !ctx) + return NULL; /* 0. Inside a nested class, prefer enclosing-class qualification: * `Outer.Inner` is reachable as `Inner` from inside `Outer`. */ for (int i = ctx->enclosing_class_depth - 1; i >= 0; i--) { const char *outer = ctx->enclosing_class_stack[i]; - if (!outer) continue; + if (!outer) + continue; char buf[JAVA_LSP_BUF]; int n = snprintf(buf, sizeof(buf), "%s.%s", outer, name); if (n > 0 && (size_t)n < sizeof(buf)) { @@ -440,7 +502,8 @@ const char *java_resolve_type_name(JavaLSPContext *ctx, const char *name) { /* 2. Single-type imports. */ for (int i = 0; i < ctx->import_count; i++) { - if (ctx->import_kinds[i] != CBM_JAVA_IMPORT_TYPE) continue; + if (ctx->import_kinds[i] != CBM_JAVA_IMPORT_TYPE) + continue; if (strcmp(ctx->import_local_names[i], name) == 0) { return ctx->import_target_qns[i]; } @@ -459,7 +522,8 @@ const char *java_resolve_type_name(JavaLSPContext *ctx, const char *name) { /* 4. On-demand imports. */ for (int i = 0; i < ctx->import_count; i++) { - if (ctx->import_kinds[i] != CBM_JAVA_IMPORT_ON_DEMAND) continue; + if (ctx->import_kinds[i] != CBM_JAVA_IMPORT_ON_DEMAND) + continue; char buf[JAVA_LSP_BUF]; int n = snprintf(buf, sizeof(buf), "%s.%s", ctx->import_target_qns[i], name); if (n > 0 && (size_t)n < sizeof(buf)) { @@ -518,8 +582,10 @@ const char *java_resolve_type_name(JavaLSPContext *ctx, const char *name) { /* ── Expression-type evaluation ───────────────────────────────────── */ const CBMType *java_eval_expr_type(JavaLSPContext *ctx, TSNode node) { - if (ts_node_is_null(node)) return cbm_type_unknown(); - if (ctx->eval_depth >= JAVA_LSP_MAX_EVAL_DEPTH) return cbm_type_unknown(); + if (ts_node_is_null(node)) + return cbm_type_unknown(); + if (ctx->eval_depth >= JAVA_LSP_MAX_EVAL_DEPTH) + return cbm_type_unknown(); ctx->eval_depth++; const CBMType *result = cbm_type_unknown(); const char *kind = ts_node_type(node); @@ -532,10 +598,8 @@ const CBMType *java_eval_expr_type(JavaLSPContext *ctx, TSNode node) { } /* Literals */ - if (strcmp(kind, "decimal_integer_literal") == 0 || - strcmp(kind, "hex_integer_literal") == 0 || - strcmp(kind, "binary_integer_literal") == 0 || - strcmp(kind, "octal_integer_literal") == 0) { + if (strcmp(kind, "decimal_integer_literal") == 0 || strcmp(kind, "hex_integer_literal") == 0 || + strcmp(kind, "binary_integer_literal") == 0 || strcmp(kind, "octal_integer_literal") == 0) { char *txt = java_node_text(ctx, node); if (txt) { size_t len = strlen(txt); @@ -600,7 +664,8 @@ const CBMType *java_eval_expr_type(JavaLSPContext *ctx, TSNode node) { /* Identifier — local var → field → import → type. */ if (strcmp(kind, "identifier") == 0) { char *name = java_node_text(ctx, node); - if (name) result = resolve_identifier_type(ctx, name); + if (name) + result = resolve_identifier_type(ctx, name); goto out; } @@ -676,7 +741,8 @@ const CBMType *java_eval_expr_type(JavaLSPContext *ctx, TSNode node) { /* Assignment expression — type of RHS. */ if (strcmp(kind, "assignment_expression") == 0) { TSNode rhs = ts_node_child_by_field_name(node, "right", 5); - if (!ts_node_is_null(rhs)) result = java_eval_expr_type(ctx, rhs); + if (!ts_node_is_null(rhs)) + result = java_eval_expr_type(ctx, rhs); goto out; } @@ -703,8 +769,8 @@ const CBMType *java_eval_expr_type(JavaLSPContext *ctx, TSNode node) { TSNode arm = ts_node_named_child(c, j); if (strcmp(ts_node_type(arm), "switch_rule") == 0) { if (ts_node_named_child_count(arm) > 0) { - TSNode body = ts_node_named_child(arm, ts_node_named_child_count(arm) - - 1); + TSNode body = + ts_node_named_child(arm, ts_node_named_child_count(arm) - 1); result = java_eval_expr_type(ctx, body); goto out; } @@ -725,28 +791,34 @@ out: * import (single static) → type alias (single class import) → on-demand * static → fall back to UNKNOWN. */ static const CBMType *resolve_identifier_type(JavaLSPContext *ctx, const char *name) { - if (!name) return cbm_type_unknown(); + if (!name) + return cbm_type_unknown(); /* Scope chain. */ const CBMType *t = cbm_scope_lookup(ctx->current_scope, name); - if (t && !cbm_type_is_unknown(t)) return t; + if (t && !cbm_type_is_unknown(t)) + return t; /* Enclosing-class fields, walking super chain. */ if (ctx->enclosing_class_qn) { const CBMType *ft = java_lookup_field_type(ctx, ctx->enclosing_class_qn, name); - if (ft && !cbm_type_is_unknown(ft)) return ft; + if (ft && !cbm_type_is_unknown(ft)) + return ft; } /* Outer classes. */ for (int i = ctx->enclosing_class_depth - 2; i >= 0; i--) { const char *outer = ctx->enclosing_class_stack[i]; - if (!outer) continue; + if (!outer) + continue; const CBMType *ft = java_lookup_field_type(ctx, outer, name); - if (ft && !cbm_type_is_unknown(ft)) return ft; + if (ft && !cbm_type_is_unknown(ft)) + return ft; } /* Static imports — could be a static field. */ for (int i = 0; i < ctx->import_count; i++) { - if (ctx->import_kinds[i] != CBM_JAVA_IMPORT_STATIC) continue; + if (ctx->import_kinds[i] != CBM_JAVA_IMPORT_STATIC) + continue; if (strcmp(ctx->import_local_names[i], name) == 0) { /* The target QN is e.g. `java.lang.System.out` — we don't have * a registered type for that; signal via the QN. */ @@ -755,7 +827,8 @@ static const CBMType *resolve_identifier_type(JavaLSPContext *ctx, const char *n if (last_dot) { char *cls = cbm_arena_strndup(ctx->arena, target, (size_t)(last_dot - target)); const CBMType *ft = java_lookup_field_type(ctx, cls, name); - if (ft && !cbm_type_is_unknown(ft)) return ft; + if (ft && !cbm_type_is_unknown(ft)) + return ft; } return cbm_type_unknown(); } @@ -763,7 +836,8 @@ static const CBMType *resolve_identifier_type(JavaLSPContext *ctx, const char *n /* Type name? Treat the identifier as a class reference. */ const char *type_qn = java_resolve_type_name(ctx, name); - if (type_qn) return cbm_type_named(ctx->arena, type_qn); + if (type_qn) + return cbm_type_named(ctx->arena, type_qn); return cbm_type_unknown(); } @@ -772,9 +846,11 @@ static const CBMType *resolve_identifier_type(JavaLSPContext *ctx, const char *n static const CBMType *eval_field_access(JavaLSPContext *ctx, TSNode node) { TSNode obj = ts_node_child_by_field_name(node, "object", 6); TSNode field = ts_node_child_by_field_name(node, "field", 5); - if (ts_node_is_null(field)) return cbm_type_unknown(); + if (ts_node_is_null(field)) + return cbm_type_unknown(); char *fname = java_node_text(ctx, field); - if (!fname) return cbm_type_unknown(); + if (!fname) + return cbm_type_unknown(); /* Special-case: System.out, System.err — common static fields. */ if (!ts_node_is_null(obj) && strcmp(ts_node_type(obj), "identifier") == 0) { @@ -790,8 +866,7 @@ static const CBMType *eval_field_access(JavaLSPContext *ctx, TSNode node) { } /* Special-case: `length` on array types is `int`. */ - const CBMType *recv = ts_node_is_null(obj) ? cbm_type_unknown() - : java_eval_expr_type(ctx, obj); + const CBMType *recv = ts_node_is_null(obj) ? cbm_type_unknown() : java_eval_expr_type(ctx, obj); if (recv && recv->kind == CBM_TYPE_SLICE && strcmp(fname, "length") == 0) { return cbm_type_builtin(ctx->arena, "int"); } @@ -801,29 +876,34 @@ static const CBMType *eval_field_access(JavaLSPContext *ctx, TSNode node) { * info even when the registered class lacks field metadata. */ if (!ts_node_is_null(obj) && strcmp(ts_node_type(obj), "this") == 0) { const CBMType *scope_t = cbm_scope_lookup(ctx->current_scope, fname); - if (scope_t && !cbm_type_is_unknown(scope_t)) return scope_t; + if (scope_t && !cbm_type_is_unknown(scope_t)) + return scope_t; } const CBMType *res = resolve_member_type(ctx, recv, fname); - if (res && !cbm_type_is_unknown(res)) return res; + if (res && !cbm_type_is_unknown(res)) + return res; /* Last resort: if receiver is `this` or an unresolved identifier, fall * back to scope chain by name. */ if (!ts_node_is_null(obj)) { const CBMType *scope_t = cbm_scope_lookup(ctx->current_scope, fname); - if (scope_t && !cbm_type_is_unknown(scope_t)) return scope_t; + if (scope_t && !cbm_type_is_unknown(scope_t)) + return scope_t; } return cbm_type_unknown(); } /* Lookup a field's type on a class, walking the parent chain. */ const CBMType *java_lookup_field_type(JavaLSPContext *ctx, const char *class_qn, - const char *field_name) { - if (!class_qn || !field_name) return cbm_type_unknown(); + const char *field_name) { + if (!class_qn || !field_name) + return cbm_type_unknown(); const char *cur = class_qn; for (int hops = 0; hops < JAVA_LSP_MAX_INHERIT_HOPS && cur; hops++) { const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, cur); - if (!rt) break; + if (!rt) + break; if (rt->field_names && rt->field_types) { for (int i = 0; rt->field_names[i]; i++) { if (strcmp(rt->field_names[i], field_name) == 0) { @@ -843,13 +923,18 @@ const CBMType *java_lookup_field_type(JavaLSPContext *ctx, const char *class_qn, /* Resolve `recv.member` for non-method member access. */ static const CBMType *resolve_member_type(JavaLSPContext *ctx, const CBMType *recv, const char *member_name) { - if (!recv || !member_name) return cbm_type_unknown(); + if (!recv || !member_name) + return cbm_type_unknown(); const CBMType *base = recv; - if (base->kind == CBM_TYPE_TEMPLATE) base = NULL; /* fall through to QN */ + if (base->kind == CBM_TYPE_TEMPLATE) + base = NULL; /* fall through to QN */ const char *recv_qn = NULL; - if (recv->kind == CBM_TYPE_NAMED) recv_qn = recv->data.named.qualified_name; - else if (recv->kind == CBM_TYPE_TEMPLATE) recv_qn = recv->data.template_type.template_name; - if (!recv_qn) return cbm_type_unknown(); + if (recv->kind == CBM_TYPE_NAMED) + recv_qn = recv->data.named.qualified_name; + else if (recv->kind == CBM_TYPE_TEMPLATE) + recv_qn = recv->data.template_type.template_name; + if (!recv_qn) + return cbm_type_unknown(); return java_lookup_field_type(ctx, recv_qn, member_name); } @@ -857,20 +942,23 @@ static const CBMType *resolve_member_type(JavaLSPContext *ctx, const CBMType *re const CBMRegisteredFunc *java_lookup_method(JavaLSPContext *ctx, const char *class_qn, const char *method_name, int arg_count) { - if (!class_qn || !method_name) return NULL; + if (!class_qn || !method_name) + return NULL; const char *cur = class_qn; const CBMRegisteredFunc *fallback = NULL; for (int hops = 0; hops < JAVA_LSP_MAX_INHERIT_HOPS && cur; hops++) { /* Try arg-count-aware lookup first. */ - const CBMRegisteredFunc *m = cbm_registry_lookup_method_by_args(ctx->registry, cur, - method_name, arg_count); - if (m) return m; + const CBMRegisteredFunc *m = + cbm_registry_lookup_method_by_args(ctx->registry, cur, method_name, arg_count); + if (m) + return m; /* Otherwise capture any name match as fallback. */ if (!fallback) { fallback = cbm_registry_lookup_method(ctx->registry, cur, method_name); } const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, cur); - if (!rt) break; + if (!rt) + break; if (rt->embedded_types && rt->embedded_types[0]) { cur = rt->embedded_types[0]; } else { @@ -883,10 +971,13 @@ const CBMRegisteredFunc *java_lookup_method(JavaLSPContext *ctx, const char *cla /* ── Method-invocation evaluation ─────────────────────────────────── */ static const CBMType *java_return_type_of(const CBMRegisteredFunc *f) { - if (!f || !f->signature) return cbm_type_unknown(); - if (f->signature->kind != CBM_TYPE_FUNC) return cbm_type_unknown(); + if (!f || !f->signature) + return cbm_type_unknown(); + if (f->signature->kind != CBM_TYPE_FUNC) + return cbm_type_unknown(); const CBMType *const *rets = f->signature->data.func.return_types; - if (!rets || !rets[0]) return cbm_type_unknown(); + if (!rets || !rets[0]) + return cbm_type_unknown(); return rets[0]; } @@ -909,43 +1000,55 @@ static const CBMType *java_return_type_of(const CBMRegisteredFunc *f) { * recognized parametric container. */ static bool is_value_typed_container(const char *qn) { static const char *known[] = { - "java.util.List", "java.util.ArrayList", "java.util.LinkedList", - "java.util.Vector", "java.util.Stack", "java.util.Set", - "java.util.HashSet", "java.util.TreeSet", "java.util.LinkedHashSet", - "java.util.Collection", "java.lang.Iterable", "java.util.Iterator", - "java.util.ListIterator", "java.util.Optional", "java.util.stream.Stream", - "java.util.Queue", "java.util.Deque", "java.util.ArrayDeque", + "java.util.List", "java.util.ArrayList", + "java.util.LinkedList", "java.util.Vector", + "java.util.Stack", "java.util.Set", + "java.util.HashSet", "java.util.TreeSet", + "java.util.LinkedHashSet", "java.util.Collection", + "java.lang.Iterable", "java.util.Iterator", + "java.util.ListIterator", "java.util.Optional", + "java.util.stream.Stream", "java.util.Queue", + "java.util.Deque", "java.util.ArrayDeque", "java.util.PriorityQueue", NULL, }; for (int i = 0; known[i]; i++) { - if (strcmp(known[i], qn) == 0) return true; + if (strcmp(known[i], qn) == 0) + return true; } return false; } static bool is_map_like(const char *qn) { static const char *known[] = { - "java.util.Map", "java.util.HashMap", - "java.util.TreeMap", "java.util.LinkedHashMap", + "java.util.Map", + "java.util.HashMap", + "java.util.TreeMap", + "java.util.LinkedHashMap", "java.util.concurrent.ConcurrentHashMap", - "java.util.concurrent.ConcurrentMap", NULL, + "java.util.concurrent.ConcurrentMap", + NULL, }; for (int i = 0; known[i]; i++) { - if (strcmp(known[i], qn) == 0) return true; + if (strcmp(known[i], qn) == 0) + return true; } return false; } static const CBMType *substitute_generic_return(JavaLSPContext *ctx, const char *recv_qn, const char *method_name, - const CBMType *const *arg_arr, - int targ_count, const CBMType *fallback) { + const CBMType *const *arg_arr, int targ_count, + const CBMType *fallback) { (void)ctx; - if (!recv_qn || !method_name || !arg_arr || targ_count <= 0) return fallback; - if (!fallback) return fallback; + if (!recv_qn || !method_name || !arg_arr || targ_count <= 0) + return fallback; + if (!fallback) + return fallback; /* Only rewrite Object returns. */ - if (fallback->kind != CBM_TYPE_NAMED) return fallback; - if (strcmp(fallback->data.named.qualified_name, "java.lang.Object") != 0) return fallback; + if (fallback->kind != CBM_TYPE_NAMED) + return fallback; + if (strcmp(fallback->data.named.qualified_name, "java.lang.Object") != 0) + return fallback; if (is_value_typed_container(recv_qn)) { /* For Iterator.next() and List.get/etc. — the element type is T0. */ @@ -965,11 +1068,10 @@ static const CBMType *substitute_generic_return(JavaLSPContext *ctx, const char } if (is_map_like(recv_qn) && targ_count >= 2) { if (strcmp(method_name, "get") == 0 || strcmp(method_name, "put") == 0 || - strcmp(method_name, "remove") == 0 || - strcmp(method_name, "getOrDefault") == 0 || + strcmp(method_name, "remove") == 0 || strcmp(method_name, "getOrDefault") == 0 || strcmp(method_name, "putIfAbsent") == 0 || - strcmp(method_name, "computeIfAbsent") == 0 || - strcmp(method_name, "compute") == 0 || strcmp(method_name, "merge") == 0) { + strcmp(method_name, "computeIfAbsent") == 0 || strcmp(method_name, "compute") == 0 || + strcmp(method_name, "merge") == 0) { return arg_arr[1] ? arg_arr[1] : fallback; } } @@ -987,8 +1089,7 @@ static const CBMType *substitute_generic_return(JavaLSPContext *ctx, const char strcmp(method_name, "apply") == 0) { return arg_arr[targ_count - 1] ? arg_arr[targ_count - 1] : fallback; } - if (strcmp(recv_qn, "java.util.function.Supplier") == 0 && - strcmp(method_name, "get") == 0) { + if (strcmp(recv_qn, "java.util.function.Supplier") == 0 && strcmp(method_name, "get") == 0) { return arg_arr[0] ? arg_arr[0] : fallback; } return fallback; @@ -997,37 +1098,45 @@ static const CBMType *substitute_generic_return(JavaLSPContext *ctx, const char static const CBMType *eval_method_invocation(JavaLSPContext *ctx, TSNode node) { TSNode obj = ts_node_child_by_field_name(node, "object", 6); TSNode name_node = ts_node_child_by_field_name(node, "name", 4); - if (ts_node_is_null(name_node)) return cbm_type_unknown(); + if (ts_node_is_null(name_node)) + return cbm_type_unknown(); char *mname = java_node_text(ctx, name_node); - if (!mname) return cbm_type_unknown(); + if (!mname) + return cbm_type_unknown(); int arity = count_call_args(node); /* No receiver: `foo()` — method is on enclosing class or static import. */ if (ts_node_is_null(obj)) { if (ctx->enclosing_class_qn) { - const CBMRegisteredFunc *f = java_lookup_method(ctx, ctx->enclosing_class_qn, mname, - arity); - if (f) return java_return_type_of(f); + const CBMRegisteredFunc *f = + java_lookup_method(ctx, ctx->enclosing_class_qn, mname, arity); + if (f) + return java_return_type_of(f); } for (int i = 0; i < ctx->import_count; i++) { - if (ctx->import_kinds[i] != CBM_JAVA_IMPORT_STATIC) continue; - if (strcmp(ctx->import_local_names[i], mname) != 0) continue; + if (ctx->import_kinds[i] != CBM_JAVA_IMPORT_STATIC) + continue; + if (strcmp(ctx->import_local_names[i], mname) != 0) + continue; const char *target = ctx->import_target_qns[i]; const char *last_dot = strrchr(target, '.'); - if (!last_dot) continue; + if (!last_dot) + continue; char *cls = cbm_arena_strndup(ctx->arena, target, (size_t)(last_dot - target)); const CBMRegisteredFunc *f = java_lookup_method(ctx, cls, mname, arity); - if (f) return java_return_type_of(f); + if (f) + return java_return_type_of(f); } return cbm_type_unknown(); } /* `super.method()` */ if (strcmp(ts_node_type(obj), "super") == 0) { - const char *super_qn = ctx->enclosing_super_qn ? ctx->enclosing_super_qn - : "java.lang.Object"; + const char *super_qn = + ctx->enclosing_super_qn ? ctx->enclosing_super_qn : "java.lang.Object"; const CBMRegisteredFunc *f = java_lookup_method(ctx, super_qn, mname, arity); - if (f) return java_return_type_of(f); + if (f) + return java_return_type_of(f); return cbm_type_unknown(); } @@ -1041,7 +1150,8 @@ static const CBMType *eval_method_invocation(JavaLSPContext *ctx, TSNode node) { /* Only treat as static if there's no local var of that name. */ if (cbm_type_is_unknown(cbm_scope_lookup(ctx->current_scope, oname))) { const CBMRegisteredFunc *f = java_lookup_method(ctx, cls_qn, mname, arity); - if (f) return java_return_type_of(f); + if (f) + return java_return_type_of(f); } } } @@ -1058,10 +1168,10 @@ static const CBMType *eval_method_invocation(JavaLSPContext *ctx, TSNode node) { const CBMType *ret = java_return_type_of(f); /* Generic substitution + carrier propagation. */ const CBMType *subst = substitute_generic_return( - ctx, base_qn, mname, - base->data.template_type.template_args, + ctx, base_qn, mname, base->data.template_type.template_args, base->data.template_type.arg_count, ret); - if (!subst) subst = ret; + if (!subst) + subst = ret; /* Wrap NAMED carriers (Stream, Iterator, Optional, ...) as * TEMPLATE so chained methods retain type-arg context. */ subst = propagate_template(ctx->arena, base_qn, mname, @@ -1072,9 +1182,10 @@ static const CBMType *eval_method_invocation(JavaLSPContext *ctx, TSNode node) { } } if (base && base->kind == CBM_TYPE_NAMED) { - const CBMRegisteredFunc *f = java_lookup_method(ctx, base->data.named.qualified_name, - mname, arity); - if (f) return java_return_type_of(f); + const CBMRegisteredFunc *f = + java_lookup_method(ctx, base->data.named.qualified_name, mname, arity); + if (f) + return java_return_type_of(f); } if (base && base->kind == CBM_TYPE_SLICE) { /* No methods on arrays beyond `length` (handled in field_access). */ @@ -1098,15 +1209,18 @@ static const CBMType *eval_object_creation(JavaLSPContext *ctx, TSNode node) { } } } - if (ts_node_is_null(type_node)) return cbm_type_unknown(); + if (ts_node_is_null(type_node)) + return cbm_type_unknown(); return java_parse_type_node(ctx, type_node); } static const CBMType *eval_array_access(JavaLSPContext *ctx, TSNode node) { TSNode arr = ts_node_child_by_field_name(node, "array", 5); - if (ts_node_is_null(arr)) return cbm_type_unknown(); + if (ts_node_is_null(arr)) + return cbm_type_unknown(); const CBMType *t = java_eval_expr_type(ctx, arr); - if (t && t->kind == CBM_TYPE_SLICE) return t->data.slice.elem; + if (t && t->kind == CBM_TYPE_SLICE) + return t->data.slice.elem; /* List[i] in Java is illegal — but at runtime we sometimes see * Collection.get-style indexing through the array_access node. Stay * conservative. */ @@ -1115,7 +1229,8 @@ static const CBMType *eval_array_access(JavaLSPContext *ctx, TSNode node) { static const CBMType *eval_cast(JavaLSPContext *ctx, TSNode node) { TSNode type_node = ts_node_child_by_field_name(node, "type", 4); - if (ts_node_is_null(type_node)) return cbm_type_unknown(); + if (ts_node_is_null(type_node)) + return cbm_type_unknown(); return java_parse_type_node(ctx, type_node); } @@ -1123,7 +1238,8 @@ static const CBMType *eval_ternary(JavaLSPContext *ctx, TSNode node) { TSNode then_n = ts_node_child_by_field_name(node, "consequence", 11); TSNode else_n = ts_node_child_by_field_name(node, "alternative", 11); const CBMType *t = java_eval_expr_type(ctx, then_n); - if (t && !cbm_type_is_unknown(t)) return t; + if (t && !cbm_type_is_unknown(t)) + return t; return java_eval_expr_type(ctx, else_n); } @@ -1134,9 +1250,11 @@ static bool is_string_concat(JavaLSPContext *ctx, TSNode node) { const CBMType *l = java_eval_expr_type(ctx, lhs); const CBMType *r = java_eval_expr_type(ctx, rhs); if (l && l->kind == CBM_TYPE_NAMED && - strcmp(l->data.named.qualified_name, "java.lang.String") == 0) return true; + strcmp(l->data.named.qualified_name, "java.lang.String") == 0) + return true; if (r && r->kind == CBM_TYPE_NAMED && - strcmp(r->data.named.qualified_name, "java.lang.String") == 0) return true; + strcmp(r->data.named.qualified_name, "java.lang.String") == 0) + return true; return false; } @@ -1146,12 +1264,14 @@ static const CBMType *eval_binary(JavaLSPContext *ctx, TSNode node) { uint32_t cn = ts_node_child_count(node); for (uint32_t i = 0; i < cn; i++) { TSNode c = ts_node_child(node, i); - if (ts_node_is_named(c)) continue; + if (ts_node_is_named(c)) + continue; op = java_node_text(ctx, c); break; } /* Fallback when the unnamed-child trick fails (older grammars). */ - if (!op) op = ""; + if (!op) + op = ""; if (strcmp(op, "==") == 0 || strcmp(op, "!=") == 0 || strcmp(op, "<") == 0 || strcmp(op, ">") == 0 || strcmp(op, "<=") == 0 || strcmp(op, ">=") == 0 || @@ -1172,11 +1292,13 @@ static const CBMType *eval_unary(JavaLSPContext *ctx, TSNode node) { uint32_t cn = ts_node_child_count(node); for (uint32_t i = 0; i < cn; i++) { TSNode c = ts_node_child(node, i); - if (ts_node_is_named(c)) continue; + if (ts_node_is_named(c)) + continue; op = java_node_text(ctx, c); break; } - if (op && strcmp(op, "!") == 0) return cbm_type_builtin(ctx->arena, "boolean"); + if (op && strcmp(op, "!") == 0) + return cbm_type_builtin(ctx->arena, "boolean"); TSNode operand = ts_node_child_by_field_name(node, "operand", 7); if (ts_node_is_null(operand) && ts_node_named_child_count(node) > 0) { operand = ts_node_named_child(node, 0); @@ -1201,7 +1323,8 @@ static const CBMType *eval_method_reference(JavaLSPContext *ctx, TSNode node) { /* ── Statement processing — bind into scope ───────────────────────── */ void java_process_statement(JavaLSPContext *ctx, TSNode node) { - if (ts_node_is_null(node) || ctx->statement_depth >= JAVA_LSP_MAX_STMT_DEPTH) return; + if (ts_node_is_null(node) || ctx->statement_depth >= JAVA_LSP_MAX_STMT_DEPTH) + return; ctx->statement_depth++; const char *kind = ts_node_type(node); @@ -1222,10 +1345,10 @@ void java_process_statement(JavaLSPContext *ctx, TSNode node) { if (rn) { const CBMType *rt = cbm_type_unknown(); bool is_var = false; - if (!ts_node_is_null(rtype) && - strcmp(ts_node_type(rtype), "type_identifier") == 0) { + if (!ts_node_is_null(rtype) && strcmp(ts_node_type(rtype), "type_identifier") == 0) { char *tt = java_node_text(ctx, rtype); - if (tt && strcmp(tt, "var") == 0) is_var = true; + if (tt && strcmp(tt, "var") == 0) + is_var = true; } if (!is_var && !ts_node_is_null(rtype)) { rt = java_parse_type_node(ctx, rtype); @@ -1244,7 +1367,8 @@ void java_process_statement(JavaLSPContext *ctx, TSNode node) { if (!ts_node_is_null(formal)) { TSNode pname = ts_node_child_by_field_name(formal, "name", 4); TSNode ptype = ts_node_child_by_field_name(formal, "type", 4); - if (ts_node_is_null(ptype)) ptype = child_by_kind(formal, "catch_type"); + if (ts_node_is_null(ptype)) + ptype = child_by_kind(formal, "catch_type"); char *pn = ts_node_is_null(pname) ? NULL : java_node_text(ctx, pname); if (pn) { /* catch_type may be a union_type — pick the first union member. */ @@ -1267,25 +1391,27 @@ void java_process_statement(JavaLSPContext *ctx, TSNode node) { static void process_local_var_decl(JavaLSPContext *ctx, TSNode node) { TSNode type_node = ts_node_child_by_field_name(node, "type", 4); - const CBMType *static_type = ts_node_is_null(type_node) ? cbm_type_unknown() - : java_parse_type_node(ctx, type_node); + const CBMType *static_type = + ts_node_is_null(type_node) ? cbm_type_unknown() : java_parse_type_node(ctx, type_node); bool is_var = - !ts_node_is_null(type_node) && strcmp(ts_node_type(type_node), "type_identifier") == 0 && - ({ + !ts_node_is_null(type_node) && strcmp(ts_node_type(type_node), "type_identifier") == 0 && ({ char *txt = java_node_text(ctx, type_node); - txt && strcmp(txt, "var") == 0; + txt &&strcmp(txt, "var") == 0; }); /* Walk variable_declarator children. */ uint32_t n = ts_node_named_child_count(node); for (uint32_t i = 0; i < n; i++) { TSNode c = ts_node_named_child(node, i); - if (strcmp(ts_node_type(c), "variable_declarator") != 0) continue; + if (strcmp(ts_node_type(c), "variable_declarator") != 0) + continue; TSNode name_node = ts_node_child_by_field_name(c, "name", 4); TSNode value_node = ts_node_child_by_field_name(c, "value", 5); - if (ts_node_is_null(name_node)) continue; + if (ts_node_is_null(name_node)) + continue; char *vname = java_node_text(ctx, name_node); - if (!vname) continue; + if (!vname) + continue; const CBMType *bind_type = static_type; if ((is_var || cbm_type_is_unknown(bind_type)) && !ts_node_is_null(value_node)) { bind_type = java_eval_expr_type(ctx, value_node); @@ -1299,20 +1425,21 @@ static void process_enhanced_for(JavaLSPContext *ctx, TSNode node) { TSNode name_node = ts_node_child_by_field_name(node, "name", 4); TSNode value_node = ts_node_child_by_field_name(node, "value", 5); char *vname = ts_node_is_null(name_node) ? NULL : java_node_text(ctx, name_node); - if (!vname) return; + if (!vname) + return; /* `var x : xs` — explicit-var inference. tree-sitter exposes "var" as a * type_identifier whose text is exactly "var". */ bool is_var = false; - if (!ts_node_is_null(type_node) && - strcmp(ts_node_type(type_node), "type_identifier") == 0) { + if (!ts_node_is_null(type_node) && strcmp(ts_node_type(type_node), "type_identifier") == 0) { char *tt = java_node_text(ctx, type_node); - if (tt && strcmp(tt, "var") == 0) is_var = true; + if (tt && strcmp(tt, "var") == 0) + is_var = true; } - const CBMType *t = - (is_var || ts_node_is_null(type_node)) ? cbm_type_unknown() - : java_parse_type_node(ctx, type_node); + const CBMType *t = (is_var || ts_node_is_null(type_node)) + ? cbm_type_unknown() + : java_parse_type_node(ctx, type_node); /* When type is `var` or unparseable, infer from the iterable. */ if (cbm_type_is_unknown(t) && !ts_node_is_null(value_node)) { @@ -1347,16 +1474,19 @@ static void process_field_decl(JavaLSPContext *ctx, TSNode node) { * declared variables with initializers so static-init blocks resolve * correctly. */ TSNode type_node = ts_node_child_by_field_name(node, "type", 4); - const CBMType *static_type = ts_node_is_null(type_node) ? cbm_type_unknown() - : java_parse_type_node(ctx, type_node); + const CBMType *static_type = + ts_node_is_null(type_node) ? cbm_type_unknown() : java_parse_type_node(ctx, type_node); uint32_t n = ts_node_named_child_count(node); for (uint32_t i = 0; i < n; i++) { TSNode c = ts_node_named_child(node, i); - if (strcmp(ts_node_type(c), "variable_declarator") != 0) continue; + if (strcmp(ts_node_type(c), "variable_declarator") != 0) + continue; TSNode name_node = ts_node_child_by_field_name(c, "name", 4); - if (ts_node_is_null(name_node)) continue; + if (ts_node_is_null(name_node)) + continue; char *fname = java_node_text(ctx, name_node); - if (!fname) continue; + if (!fname) + continue; cbm_scope_bind(ctx->current_scope, fname, static_type); } } @@ -1365,9 +1495,11 @@ static void process_method_decl(JavaLSPContext *ctx, TSNode node, const char *cl const char *super_qn) { /* Compute method QN. */ TSNode name_node = ts_node_child_by_field_name(node, "name", 4); - if (ts_node_is_null(name_node)) return; + if (ts_node_is_null(name_node)) + return; char *mname = java_node_text(ctx, name_node); - if (!mname) return; + if (!mname) + return; char *method_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", class_qn, mname); /* Save context. */ @@ -1394,11 +1526,13 @@ static void process_method_decl(JavaLSPContext *ctx, TSNode node, const char *cl } TSNode pname = ts_node_child_by_field_name(p, "name", 4); TSNode ptype = ts_node_child_by_field_name(p, "type", 4); - if (ts_node_is_null(pname)) continue; + if (ts_node_is_null(pname)) + continue; char *pn = java_node_text(ctx, pname); - if (!pn) continue; - const CBMType *pt = ts_node_is_null(ptype) ? cbm_type_unknown() - : java_parse_type_node(ctx, ptype); + if (!pn) + continue; + const CBMType *pt = + ts_node_is_null(ptype) ? cbm_type_unknown() : java_parse_type_node(ctx, ptype); if (strcmp(pk, "spread_parameter") == 0 && pt) { pt = cbm_type_slice(ctx->arena, pt); } @@ -1450,20 +1584,24 @@ static void process_constructor_decl(JavaLSPContext *ctx, TSNode node, const cha uint32_t n = ts_node_named_child_count(params); for (uint32_t i = 0; i < n; i++) { TSNode p = ts_node_named_child(params, i); - if (strcmp(ts_node_type(p), "formal_parameter") != 0) continue; + if (strcmp(ts_node_type(p), "formal_parameter") != 0) + continue; TSNode pname = ts_node_child_by_field_name(p, "name", 4); TSNode ptype = ts_node_child_by_field_name(p, "type", 4); - if (ts_node_is_null(pname)) continue; + if (ts_node_is_null(pname)) + continue; char *pn = java_node_text(ctx, pname); - if (!pn) continue; - const CBMType *pt = ts_node_is_null(ptype) ? cbm_type_unknown() - : java_parse_type_node(ctx, ptype); + if (!pn) + continue; + const CBMType *pt = + ts_node_is_null(ptype) ? cbm_type_unknown() : java_parse_type_node(ctx, ptype); cbm_scope_bind(ctx->current_scope, pn, pt); } } TSNode body = ts_node_child_by_field_name(node, "body", 4); - if (!ts_node_is_null(body)) process_block(ctx, body); + if (!ts_node_is_null(body)) + process_block(ctx, body); ctx->current_scope = saved_scope; ctx->enclosing_method_qn = saved_method; @@ -1474,7 +1612,8 @@ static void process_constructor_decl(JavaLSPContext *ctx, TSNode node, const cha /* Determine the class's super QN from the AST node. */ static const char *class_super_qn(JavaLSPContext *ctx, TSNode class_node) { TSNode super_node = ts_node_child_by_field_name(class_node, "superclass", 10); - if (ts_node_is_null(super_node)) return NULL; + if (ts_node_is_null(super_node)) + return NULL; /* superclass node has shape `extends T`; T is the named child. */ TSNode tnode = (TSNode){0}; if (ts_node_named_child_count(super_node) > 0) { @@ -1482,18 +1621,23 @@ static const char *class_super_qn(JavaLSPContext *ctx, TSNode class_node) { } else { tnode = super_node; } - if (ts_node_is_null(tnode)) return NULL; + if (ts_node_is_null(tnode)) + return NULL; const CBMType *t = java_parse_type_node(ctx, tnode); - if (t && t->kind == CBM_TYPE_NAMED) return t->data.named.qualified_name; - if (t && t->kind == CBM_TYPE_TEMPLATE) return t->data.template_type.template_name; + if (t && t->kind == CBM_TYPE_NAMED) + return t->data.named.qualified_name; + if (t && t->kind == CBM_TYPE_TEMPLATE) + return t->data.template_type.template_name; return NULL; } static void java_process_class_decl(JavaLSPContext *ctx, TSNode node) { TSNode name_node = ts_node_child_by_field_name(node, "name", 4); - if (ts_node_is_null(name_node)) return; + if (ts_node_is_null(name_node)) + return; char *cname = java_node_text(ctx, name_node); - if (!cname) return; + if (!cname) + return; /* Class QN. Outer.Inner naming: walk the enclosing-class stack. */ char *class_qn; @@ -1506,7 +1650,8 @@ static void java_process_class_decl(JavaLSPContext *ctx, TSNode node) { } const char *super_qn = class_super_qn(ctx, node); - if (!super_qn) super_qn = "java.lang.Object"; + if (!super_qn) + super_qn = "java.lang.Object"; /* Save context. */ const char *saved_class = ctx->enclosing_class_qn; @@ -1528,7 +1673,8 @@ static void java_process_class_decl(JavaLSPContext *ctx, TSNode node) { for (uint32_t i = 0; i < n; i++) { TSNode c = ts_node_named_child(body, i); const char *k = ts_node_type(c); - if (strcmp(k, "field_declaration") == 0) process_field_decl(ctx, c); + if (strcmp(k, "field_declaration") == 0) + process_field_decl(ctx, c); } /* Second pass: methods + constructors + nested types. */ for (uint32_t i = 0; i < n; i++) { @@ -1540,8 +1686,7 @@ static void java_process_class_decl(JavaLSPContext *ctx, TSNode node) { process_constructor_decl(ctx, c, class_qn, super_qn); } else if (strcmp(k, "class_declaration") == 0 || strcmp(k, "interface_declaration") == 0 || - strcmp(k, "enum_declaration") == 0 || - strcmp(k, "record_declaration") == 0 || + strcmp(k, "enum_declaration") == 0 || strcmp(k, "record_declaration") == 0 || strcmp(k, "annotation_type_declaration") == 0) { java_process_class_decl(ctx, c); } else if (strcmp(k, "static_initializer") == 0) { @@ -1561,7 +1706,8 @@ static void java_process_class_decl(JavaLSPContext *ctx, TSNode node) { } void java_lsp_process_file(JavaLSPContext *ctx, TSNode root) { - if (ts_node_is_null(root)) return; + if (ts_node_is_null(root)) + return; /* First scan: package_declaration + imports (already pushed via init). */ uint32_t n = ts_node_named_child_count(root); for (uint32_t i = 0; i < n; i++) { @@ -1583,12 +1729,16 @@ void java_lsp_process_file(JavaLSPContext *ctx, TSNode root) { TSNode cc = ts_node_child(c, j); if (!ts_node_is_named(cc)) { char *t = java_node_text(ctx, cc); - if (t && strcmp(t, "static") == 0) is_static = true; - if (t && strcmp(t, "*") == 0) is_on_demand = true; - if (t && strcmp(t, "asterisk") == 0) is_on_demand = true; + if (t && strcmp(t, "static") == 0) + is_static = true; + if (t && strcmp(t, "*") == 0) + is_on_demand = true; + if (t && strcmp(t, "asterisk") == 0) + is_on_demand = true; } else { const char *pk = ts_node_type(cc); - if (strcmp(pk, "asterisk") == 0) is_on_demand = true; + if (strcmp(pk, "asterisk") == 0) + is_on_demand = true; else if (strcmp(pk, "scoped_identifier") == 0 || strcmp(pk, "identifier") == 0) { path = java_node_text(ctx, cc); @@ -1629,8 +1779,9 @@ void java_lsp_process_file(JavaLSPContext *ctx, TSNode root) { /* ── Call-edge resolution ─────────────────────────────────────────── */ static void java_emit_resolved(JavaLSPContext *ctx, const char *callee_qn, const char *strategy, - float confidence) { - if (!ctx->resolved_calls || !ctx->enclosing_method_qn || !callee_qn) return; + float confidence) { + if (!ctx->resolved_calls || !ctx->enclosing_method_qn || !callee_qn) + return; CBMResolvedCall rc; rc.caller_qn = ctx->enclosing_method_qn; rc.callee_qn = callee_qn; @@ -1641,7 +1792,8 @@ static void java_emit_resolved(JavaLSPContext *ctx, const char *callee_qn, const } static void java_emit_unresolved(JavaLSPContext *ctx, const char *expr_text, const char *reason) { - if (!ctx->resolved_calls || !ctx->enclosing_method_qn) return; + if (!ctx->resolved_calls || !ctx->enclosing_method_qn) + return; CBMResolvedCall rc; rc.caller_qn = ctx->enclosing_method_qn; rc.callee_qn = expr_text ? expr_text : "?"; @@ -1654,20 +1806,21 @@ static void java_emit_unresolved(JavaLSPContext *ctx, const char *expr_text, con static void resolve_method_call(JavaLSPContext *ctx, TSNode call) { TSNode obj = ts_node_child_by_field_name(call, "object", 6); TSNode name_node = ts_node_child_by_field_name(call, "name", 4); - if (ts_node_is_null(name_node)) return; + if (ts_node_is_null(name_node)) + return; char *mname = java_node_text(ctx, name_node); - if (!mname) return; + if (!mname) + return; int arity = count_call_args(call); /* Bare call: `foo()`. */ if (ts_node_is_null(obj)) { if (ctx->enclosing_class_qn) { - const CBMRegisteredFunc *f = java_lookup_method(ctx, ctx->enclosing_class_qn, mname, - arity); + const CBMRegisteredFunc *f = + java_lookup_method(ctx, ctx->enclosing_class_qn, mname, arity); if (f) { const char *strategy = "lsp_type_dispatch"; - if (f->receiver_type && - strcmp(f->receiver_type, ctx->enclosing_class_qn) != 0) { + if (f->receiver_type && strcmp(f->receiver_type, ctx->enclosing_class_qn) != 0) { strategy = "lsp_inherited_dispatch"; } java_emit_resolved(ctx, f->qualified_name, strategy, 0.95f); @@ -1678,7 +1831,8 @@ static void resolve_method_call(JavaLSPContext *ctx, TSNode call) { * call against each enclosing-class scope. Walk outer classes. */ for (int i = ctx->enclosing_class_depth - 2; i >= 0; i--) { const char *outer = ctx->enclosing_class_stack[i]; - if (!outer) continue; + if (!outer) + continue; const CBMRegisteredFunc *f = java_lookup_method(ctx, outer, mname, arity); if (f) { java_emit_resolved(ctx, f->qualified_name, "lsp_outer_dispatch", 0.92f); @@ -1687,11 +1841,14 @@ static void resolve_method_call(JavaLSPContext *ctx, TSNode call) { } /* Static import. */ for (int i = 0; i < ctx->import_count; i++) { - if (ctx->import_kinds[i] != CBM_JAVA_IMPORT_STATIC) continue; - if (strcmp(ctx->import_local_names[i], mname) != 0) continue; + if (ctx->import_kinds[i] != CBM_JAVA_IMPORT_STATIC) + continue; + if (strcmp(ctx->import_local_names[i], mname) != 0) + continue; const char *target = ctx->import_target_qns[i]; const char *last_dot = strrchr(target, '.'); - if (!last_dot) continue; + if (!last_dot) + continue; char *cls = cbm_arena_strndup(ctx->arena, target, (size_t)(last_dot - target)); const CBMRegisteredFunc *f = java_lookup_method(ctx, cls, mname, arity); if (f) { @@ -1710,8 +1867,8 @@ static void resolve_method_call(JavaLSPContext *ctx, TSNode call) { /* `super.method()` */ if (strcmp(ts_node_type(obj), "super") == 0) { - const char *super_qn = ctx->enclosing_super_qn ? ctx->enclosing_super_qn - : "java.lang.Object"; + const char *super_qn = + ctx->enclosing_super_qn ? ctx->enclosing_super_qn : "java.lang.Object"; const CBMRegisteredFunc *f = java_lookup_method(ctx, super_qn, mname, arity); if (f) { java_emit_resolved(ctx, f->qualified_name, "lsp_super_dispatch", 0.95f); @@ -1724,8 +1881,8 @@ static void resolve_method_call(JavaLSPContext *ctx, TSNode call) { /* `this.method()` */ if (strcmp(ts_node_type(obj), "this") == 0) { if (ctx->enclosing_class_qn) { - const CBMRegisteredFunc *f = java_lookup_method(ctx, ctx->enclosing_class_qn, mname, - arity); + const CBMRegisteredFunc *f = + java_lookup_method(ctx, ctx->enclosing_class_qn, mname, arity); if (f) { java_emit_resolved(ctx, f->qualified_name, "lsp_this_dispatch", 0.95f); return; @@ -1754,8 +1911,10 @@ static void resolve_method_call(JavaLSPContext *ctx, TSNode call) { const CBMType *recv = java_eval_expr_type(ctx, obj); const CBMType *base = recv; const char *recv_qn = NULL; - if (base && base->kind == CBM_TYPE_NAMED) recv_qn = base->data.named.qualified_name; - else if (base && base->kind == CBM_TYPE_TEMPLATE) recv_qn = base->data.template_type.template_name; + if (base && base->kind == CBM_TYPE_NAMED) + recv_qn = base->data.named.qualified_name; + else if (base && base->kind == CBM_TYPE_TEMPLATE) + recv_qn = base->data.template_type.template_name; if (recv_qn) { const CBMRegisteredFunc *f = java_lookup_method(ctx, recv_qn, mname, arity); @@ -1775,7 +1934,8 @@ static void resolve_method_call(JavaLSPContext *ctx, TSNode call) { int impl_count = 0; for (int ti = 0; ti < ctx->registry->type_count && impl_count < 2; ti++) { const CBMRegisteredType *cand = &ctx->registry->types[ti]; - if (cand->is_interface || !cand->qualified_name || cand->alias_of) continue; + if (cand->is_interface || !cand->qualified_name || cand->alias_of) + continue; bool has = false; if (cand->method_names) { for (int mi = 0; cand->method_names[mi]; mi++) { @@ -1785,7 +1945,8 @@ static void resolve_method_call(JavaLSPContext *ctx, TSNode call) { } } } - if (!has) continue; + if (!has) + continue; /* Walk parent chain to confirm it's actually a subtype of rt. */ const char *cur = cand->qualified_name; bool subtype = false; @@ -1795,7 +1956,8 @@ static void resolve_method_call(JavaLSPContext *ctx, TSNode call) { break; } const CBMRegisteredType *par = cbm_registry_lookup_type(ctx->registry, cur); - if (!par || !par->embedded_types || !par->embedded_types[0]) break; + if (!par || !par->embedded_types || !par->embedded_types[0]) + break; /* Walk all parents — pick the first match. */ bool advanced = false; for (int pi = 0; par->embedded_types[pi]; pi++) { @@ -1805,8 +1967,10 @@ static void resolve_method_call(JavaLSPContext *ctx, TSNode call) { break; } } - if (subtype) break; - if (!advanced) cur = par->embedded_types[0]; + if (subtype) + break; + if (!advanced) + cur = par->embedded_types[0]; } if (subtype) { sole_impl = cand->qualified_name; @@ -1814,19 +1978,19 @@ static void resolve_method_call(JavaLSPContext *ctx, TSNode call) { } } if (impl_count == 1 && sole_impl) { - const CBMRegisteredFunc *cf = cbm_registry_lookup_method(ctx->registry, sole_impl, - mname); + const CBMRegisteredFunc *cf = + cbm_registry_lookup_method(ctx->registry, sole_impl, mname); if (cf) { java_emit_resolved(ctx, cf->qualified_name, "lsp_interface_resolve", 0.85f); return; } } java_emit_resolved(ctx, cbm_arena_sprintf(ctx->arena, "%s.%s", recv_qn, mname), - "lsp_interface_dispatch", 0.80f); + "lsp_interface_dispatch", 0.80f); return; } java_emit_unresolved(ctx, cbm_arena_sprintf(ctx->arena, "%s.%s", recv_qn, mname), - "no_method_match"); + "no_method_match"); return; } @@ -1854,48 +2018,50 @@ static void resolve_method_call(JavaLSPContext *ctx, TSNode call) { * to include), Callable (java.util.concurrent). Adding more is one-line. */ typedef struct { - const char *qn; /* fully-qualified functional interface */ - const char *sam; /* the SAM method's short name */ - int arity; /* number of params on the SAM */ + const char *qn; /* fully-qualified functional interface */ + const char *sam; /* the SAM method's short name */ + int arity; /* number of params on the SAM */ } JavaSAMSpec; static const JavaSAMSpec JAVA_SAM_TABLE[] = { - {"java.util.function.Function", "apply", 1}, - {"java.util.function.BiFunction", "apply", 2}, - {"java.util.function.UnaryOperator", "apply", 1}, - {"java.util.function.BinaryOperator", "apply", 2}, - {"java.util.function.Predicate", "test", 1}, - {"java.util.function.BiPredicate", "test", 2}, - {"java.util.function.Consumer", "accept", 1}, - {"java.util.function.BiConsumer", "accept", 2}, - {"java.util.function.Supplier", "get", 0}, - {"java.util.function.IntFunction", "apply", 1}, - {"java.util.function.LongFunction", "apply", 1}, - {"java.util.function.DoubleFunction", "apply", 1}, - {"java.util.function.IntPredicate", "test", 1}, - {"java.util.function.LongPredicate", "test", 1}, - {"java.util.function.DoublePredicate", "test", 1}, - {"java.util.function.IntConsumer", "accept", 1}, - {"java.util.function.LongConsumer", "accept", 1}, - {"java.util.function.DoubleConsumer", "accept", 1}, - {"java.util.function.IntSupplier", "getAsInt", 0}, - {"java.util.function.LongSupplier", "getAsLong", 0}, - {"java.util.function.DoubleSupplier", "getAsDouble", 0}, - {"java.util.function.BooleanSupplier", "getAsBoolean", 0}, - {"java.util.function.ToIntFunction", "applyAsInt", 1}, - {"java.util.function.ToLongFunction", "applyAsLong", 1}, + {"java.util.function.Function", "apply", 1}, + {"java.util.function.BiFunction", "apply", 2}, + {"java.util.function.UnaryOperator", "apply", 1}, + {"java.util.function.BinaryOperator", "apply", 2}, + {"java.util.function.Predicate", "test", 1}, + {"java.util.function.BiPredicate", "test", 2}, + {"java.util.function.Consumer", "accept", 1}, + {"java.util.function.BiConsumer", "accept", 2}, + {"java.util.function.Supplier", "get", 0}, + {"java.util.function.IntFunction", "apply", 1}, + {"java.util.function.LongFunction", "apply", 1}, + {"java.util.function.DoubleFunction", "apply", 1}, + {"java.util.function.IntPredicate", "test", 1}, + {"java.util.function.LongPredicate", "test", 1}, + {"java.util.function.DoublePredicate", "test", 1}, + {"java.util.function.IntConsumer", "accept", 1}, + {"java.util.function.LongConsumer", "accept", 1}, + {"java.util.function.DoubleConsumer", "accept", 1}, + {"java.util.function.IntSupplier", "getAsInt", 0}, + {"java.util.function.LongSupplier", "getAsLong", 0}, + {"java.util.function.DoubleSupplier", "getAsDouble", 0}, + {"java.util.function.BooleanSupplier", "getAsBoolean", 0}, + {"java.util.function.ToIntFunction", "applyAsInt", 1}, + {"java.util.function.ToLongFunction", "applyAsLong", 1}, {"java.util.function.ToDoubleFunction", "applyAsDouble", 1}, - {"java.lang.Runnable", "run", 0}, - {"java.util.Comparator", "compare", 2}, - {"java.lang.Comparable", "compareTo", 1}, - {"java.util.concurrent.Callable", "call", 0}, + {"java.lang.Runnable", "run", 0}, + {"java.util.Comparator", "compare", 2}, + {"java.lang.Comparable", "compareTo", 1}, + {"java.util.concurrent.Callable", "call", 0}, {NULL, NULL, 0}, }; static const JavaSAMSpec *find_sam(const char *qn) { - if (!qn) return NULL; + if (!qn) + return NULL; for (int i = 0; JAVA_SAM_TABLE[i].qn; i++) { - if (strcmp(JAVA_SAM_TABLE[i].qn, qn) == 0) return &JAVA_SAM_TABLE[i]; + if (strcmp(JAVA_SAM_TABLE[i].qn, qn) == 0) + return &JAVA_SAM_TABLE[i]; } return NULL; } @@ -1905,7 +2071,8 @@ static const JavaSAMSpec *find_sam(const char *qn) { * parametric mapping isn't modeled (e.g. specialized primitive variants). */ static const CBMType *sam_param_type(CBMArena *a, const char *fi_qn, int param_idx, const CBMType *const *targs, int targ_count) { - if (!fi_qn) return cbm_type_unknown(); + if (!fi_qn) + return cbm_type_unknown(); /* Single-template-arg interfaces where SAM uses targ[0]. */ static const char *single_t[] = { @@ -1973,20 +2140,19 @@ static const CBMType *sam_param_type(CBMArena *a, const char *fi_qn, int param_i * matches. The lambda binder then uses these directly without going * through the SAM-spec table. */ static bool method_implies_lambda_args(const char *recv_qn, const char *method_name, - const CBMType *const *targs, int targ_count, - int *out_arity, const CBMType **out_param0, - const CBMType **out_param1) { + const CBMType *const *targs, int targ_count, int *out_arity, + const CBMType **out_param0, const CBMType **out_param1) { *out_arity = 0; *out_param0 = NULL; *out_param1 = NULL; - if (!recv_qn || !method_name || !targs || targ_count <= 0) return false; + if (!recv_qn || !method_name || !targs || targ_count <= 0) + return false; /* 1-param lambdas over T0 — Predicate / Consumer / Function shapes. */ static const char *one_arg_methods[] = { - "forEach", "filter", "map", "flatMap", "peek", - "removeIf", "anyMatch", "allMatch", "noneMatch", "takeWhile", - "dropWhile", "ifPresent", "ifPresentOrElse", "mapToInt", "mapToLong", - "mapToDouble", "filter", NULL, + "forEach", "filter", "map", "flatMap", "peek", "removeIf", + "anyMatch", "allMatch", "noneMatch", "takeWhile", "dropWhile", "ifPresent", + "ifPresentOrElse", "mapToInt", "mapToLong", "mapToDouble", "filter", NULL, }; for (int i = 0; one_arg_methods[i]; i++) { if (strcmp(method_name, one_arg_methods[i]) == 0) { @@ -2030,29 +2196,23 @@ static bool method_implies_lambda_args(const char *recv_qn, const char *method_n * String inside the filter lambda — without propagation, stream() would * return bare NAMED(Stream) with no template args, and the SAM binder * couldn't substitute T0. */ -static const CBMType *propagate_template(CBMArena *a, const char *recv_qn, - const char *method_name, +static const CBMType *propagate_template(CBMArena *a, const char *recv_qn, const char *method_name, const CBMType *const *recv_targs, int recv_targ_count, const CBMType *return_t) { - if (!return_t || return_t->kind != CBM_TYPE_NAMED) return return_t; - if (recv_targ_count <= 0 || !recv_targs) return return_t; + if (!return_t || return_t->kind != CBM_TYPE_NAMED) + return return_t; + if (recv_targ_count <= 0 || !recv_targs) + return return_t; const char *ret_qn = return_t->data.named.qualified_name; - if (!ret_qn) return return_t; + if (!ret_qn) + return return_t; /* Carriers that preserve T0. */ static const char *t0_carriers[] = { - "java.util.stream.Stream", - "java.util.Iterator", - "java.util.ListIterator", - "java.util.Spliterator", - "java.util.Optional", - "java.util.List", - "java.util.Set", - "java.util.Collection", - "java.lang.Iterable", - "java.util.Queue", - "java.util.Deque", - NULL, + "java.util.stream.Stream", "java.util.Iterator", "java.util.ListIterator", + "java.util.Spliterator", "java.util.Optional", "java.util.List", + "java.util.Set", "java.util.Collection", "java.lang.Iterable", + "java.util.Queue", "java.util.Deque", NULL, }; bool is_t0 = false; for (int i = 0; t0_carriers[i]; i++) { @@ -2063,7 +2223,8 @@ static const CBMType *propagate_template(CBMArena *a, const char *recv_qn, } if (is_t0) { const CBMType **args = (const CBMType **)cbm_arena_alloc(a, 2 * sizeof(*args)); - if (!args) return return_t; + if (!args) + return return_t; args[0] = recv_targs[0]; args[1] = NULL; return cbm_type_template(a, ret_qn, args, 1); @@ -2072,7 +2233,8 @@ static const CBMType *propagate_template(CBMArena *a, const char *recv_qn, /* Map.keySet → Set, Map.values → Collection, Map.entrySet → Set>. */ if (is_map_like(recv_qn) && recv_targ_count >= 2) { const CBMType **args = (const CBMType **)cbm_arena_alloc(a, 2 * sizeof(*args)); - if (!args) return return_t; + if (!args) + return return_t; if (strcmp(method_name, "keySet") == 0) { args[0] = recv_targs[0]; args[1] = NULL; @@ -2095,11 +2257,11 @@ static const CBMType *propagate_template(CBMArena *a, const char *recv_qn, * Returns a bitmask of arg indices that were handled here so the generic * walker can skip them. */ static uint32_t bind_lambda_args(JavaLSPContext *ctx, TSNode call_node, - const CBMRegisteredFunc *resolved, - const CBMType *recv_type) { + const CBMRegisteredFunc *resolved, const CBMType *recv_type) { uint32_t handled_mask = 0; TSNode args_node = ts_node_child_by_field_name(call_node, "arguments", 9); - if (ts_node_is_null(args_node)) return handled_mask; + if (ts_node_is_null(args_node)) + return handled_mask; const CBMType *const *param_types = NULL; if (resolved && resolved->signature && resolved->signature->kind == CBM_TYPE_FUNC) { param_types = resolved->signature->data.func.param_types; @@ -2129,7 +2291,8 @@ static uint32_t bind_lambda_args(JavaLSPContext *ctx, TSNode call_node, for (uint32_t i = 0; i < n && i < 32; i++) { TSNode arg = ts_node_named_child(args_node, i); const char *kind = ts_node_type(arg); - if (strcmp(kind, "lambda_expression") != 0) continue; + if (strcmp(kind, "lambda_expression") != 0) + continue; /* First try registry-driven SAM inference. */ const CBMType *expected = param_types ? param_types[i] : NULL; @@ -2139,8 +2302,8 @@ static uint32_t bind_lambda_args(JavaLSPContext *ctx, TSNode call_node, int h_arity = 0; const CBMType *h_p0 = NULL; const CBMType *h_p1 = NULL; - if (method_implies_lambda_args(recv_qn, mname, recv_targs, recv_targ_count, - &h_arity, &h_p0, &h_p1)) { + if (method_implies_lambda_args(recv_qn, mname, recv_targs, recv_targ_count, &h_arity, + &h_p0, &h_p1)) { TSNode params_node = ts_node_child_by_field_name(arg, "parameters", 10); TSNode body_node = ts_node_child_by_field_name(arg, "body", 4); if (!ts_node_is_null(body_node)) { @@ -2151,7 +2314,8 @@ static uint32_t bind_lambda_args(JavaLSPContext *ctx, TSNode call_node, const char *pn_kind = ts_node_type(params_node); if (strcmp(pn_kind, "identifier") == 0) { char *pname = java_node_text(ctx, params_node); - if (pname) cbm_scope_bind(ctx->current_scope, pname, h_p0); + if (pname) + cbm_scope_bind(ctx->current_scope, pname, h_p0); } else if (strcmp(pn_kind, "inferred_parameters") == 0 || strcmp(pn_kind, "formal_parameters") == 0) { uint32_t pc = ts_node_named_child_count(params_node); @@ -2166,7 +2330,8 @@ static uint32_t bind_lambda_args(JavaLSPContext *ctx, TSNode call_node, } else if (strcmp(pk, "formal_parameter") == 0) { TSNode pnname = ts_node_child_by_field_name(p, "name", 4); TSNode pntype = ts_node_child_by_field_name(p, "type", 4); - if (!ts_node_is_null(pnname)) pname = java_node_text(ctx, pnname); + if (!ts_node_is_null(pnname)) + pname = java_node_text(ctx, pnname); if (!ts_node_is_null(pntype)) { pt = java_parse_type_node(ctx, pntype); } @@ -2186,7 +2351,8 @@ static uint32_t bind_lambda_args(JavaLSPContext *ctx, TSNode call_node, continue; } } - if (!expected) continue; + if (!expected) + continue; /* Strip TEMPLATE wrapper to find the FI QN + per-call template * args. Note: the FI's own template args may be type variables @@ -2201,9 +2367,11 @@ static uint32_t bind_lambda_args(JavaLSPContext *ctx, TSNode call_node, fi_targs = expected->data.template_type.template_args; fi_targ_count = expected->data.template_type.arg_count; } - if (!fi_qn) continue; + if (!fi_qn) + continue; const JavaSAMSpec *sam = find_sam(fi_qn); - if (!sam) continue; + if (!sam) + continue; /* If the FI's targs are type-variables (TYPE_PARAM kind) referring to * the receiver's parameters, substitute them. We use the shape: when @@ -2240,7 +2408,8 @@ static uint32_t bind_lambda_args(JavaLSPContext *ctx, TSNode call_node, /* Find the body. */ TSNode body_node = ts_node_child_by_field_name(arg, "body", 4); - if (ts_node_is_null(body_node)) continue; + if (ts_node_is_null(body_node)) + continue; CBMScope *saved = ctx->current_scope; ctx->current_scope = cbm_scope_push(ctx->arena, saved); @@ -2251,8 +2420,8 @@ static uint32_t bind_lambda_args(JavaLSPContext *ctx, TSNode call_node, /* Shorthand `x -> body` */ char *pname = java_node_text(ctx, params_node); if (pname) { - const CBMType *pt = sam_param_type(ctx->arena, fi_qn, 0, - resolved_fi_targs, resolved_count); + const CBMType *pt = + sam_param_type(ctx->arena, fi_qn, 0, resolved_fi_targs, resolved_count); cbm_scope_bind(ctx->current_scope, pname, pt); } } else if (strcmp(pn_kind, "inferred_parameters") == 0 || @@ -2271,10 +2440,13 @@ static uint32_t bind_lambda_args(JavaLSPContext *ctx, TSNode call_node, } else if (strcmp(pk, "formal_parameter") == 0) { TSNode pnname = ts_node_child_by_field_name(p, "name", 4); TSNode pntype = ts_node_child_by_field_name(p, "type", 4); - if (!ts_node_is_null(pnname)) pname = java_node_text(ctx, pnname); - if (!ts_node_is_null(pntype)) pt = java_parse_type_node(ctx, pntype); - else pt = sam_param_type(ctx->arena, fi_qn, idx, resolved_fi_targs, - resolved_count); + if (!ts_node_is_null(pnname)) + pname = java_node_text(ctx, pnname); + if (!ts_node_is_null(pntype)) + pt = java_parse_type_node(ctx, pntype); + else + pt = sam_param_type(ctx->arena, fi_qn, idx, resolved_fi_targs, + resolved_count); } if (pname) { cbm_scope_bind(ctx->current_scope, pname, pt ? pt : cbm_type_unknown()); @@ -2299,15 +2471,17 @@ static uint32_t bind_lambda_args(JavaLSPContext *ctx, TSNode call_node, * Emits a CBMResolvedCall edge for the referenced method when we can pin * down the receiver type. Otherwise emits an unresolved diagnostic. */ static void resolve_method_reference(JavaLSPContext *ctx, TSNode mref, - const CBMRegisteredFunc *outer_resolved, - int arg_index, const CBMType *recv_type) { - if (ts_node_is_null(mref)) return; + const CBMRegisteredFunc *outer_resolved, int arg_index, + const CBMType *recv_type) { + if (ts_node_is_null(mref)) + return; /* method_reference shape: lhs `::` name. tree-sitter-java exposes the * LHS as a named child; the method-name token may be a named identifier * OR an unnamed `new` keyword (for constructor references like * `StringBuilder::new`). Handle both. */ uint32_t nc_named = ts_node_named_child_count(mref); - if (nc_named < 1) return; + if (nc_named < 1) + return; TSNode lhs = ts_node_named_child(mref, 0); /* Try the last named child first; if it's the same as the LHS (only one @@ -2330,13 +2504,16 @@ static void resolve_method_reference(JavaLSPContext *ctx, TSNode mref, } if (strcmp(ck, "identifier") == 0) { /* Skip if it's the LHS. */ - if (ts_node_eq(c, lhs)) continue; + if (ts_node_eq(c, lhs)) + continue; mname = java_node_text(ctx, c); - if (mname && mname[0]) break; + if (mname && mname[0]) + break; } } } - if (!mname || !mname[0]) return; + if (!mname || !mname[0]) + return; /* Determine arity: from the SAM of the outer call's expected param. */ int sam_arity = -1; @@ -2346,11 +2523,13 @@ static void resolve_method_reference(JavaLSPContext *ctx, TSNode mref, const CBMType *expected = outer_resolved->signature->data.func.param_types[arg_index]; if (expected) { const char *fi_qn = NULL; - if (expected->kind == CBM_TYPE_NAMED) fi_qn = expected->data.named.qualified_name; + if (expected->kind == CBM_TYPE_NAMED) + fi_qn = expected->data.named.qualified_name; else if (expected->kind == CBM_TYPE_TEMPLATE) fi_qn = expected->data.template_type.template_name; const JavaSAMSpec *sam = find_sam(fi_qn); - if (sam) sam_arity = sam->arity; + if (sam) + sam_arity = sam->arity; } } (void)recv_type; @@ -2358,17 +2537,20 @@ static void resolve_method_reference(JavaLSPContext *ctx, TSNode mref, /* Try to resolve the LHS as a type or expression. */ const CBMType *lhs_t = NULL; const char *lhs_kind = ts_node_type(lhs); - bool lhs_is_type = (strcmp(lhs_kind, "type_identifier") == 0 || - strcmp(lhs_kind, "scoped_type_identifier") == 0 || - strcmp(lhs_kind, "generic_type") == 0); + bool lhs_is_type = + (strcmp(lhs_kind, "type_identifier") == 0 || + strcmp(lhs_kind, "scoped_type_identifier") == 0 || strcmp(lhs_kind, "generic_type") == 0); const char *type_qn = NULL; if (lhs_is_type) { char *txt = java_node_text(ctx, lhs); - if (txt) type_qn = java_resolve_type_name(ctx, strip_generics(ctx->arena, txt)); - if (!type_qn && txt) type_qn = txt; + if (txt) + type_qn = java_resolve_type_name(ctx, strip_generics(ctx->arena, txt)); + if (!type_qn && txt) + type_qn = txt; } else { lhs_t = java_eval_expr_type(ctx, lhs); - if (lhs_t && lhs_t->kind == CBM_TYPE_NAMED) type_qn = lhs_t->data.named.qualified_name; + if (lhs_t && lhs_t->kind == CBM_TYPE_NAMED) + type_qn = lhs_t->data.named.qualified_name; else if (lhs_t && lhs_t->kind == CBM_TYPE_TEMPLATE) type_qn = lhs_t->data.template_type.template_name; } @@ -2381,13 +2563,12 @@ static void resolve_method_reference(JavaLSPContext *ctx, TSNode mref, if (strcmp(mname, "new") == 0) { const char *short_name = strrchr(type_qn, '.'); short_name = short_name ? short_name + 1 : type_qn; - const CBMRegisteredFunc *cf = cbm_registry_lookup_method(ctx->registry, type_qn, - short_name); + const CBMRegisteredFunc *cf = + cbm_registry_lookup_method(ctx->registry, type_qn, short_name); if (cf) { java_emit_resolved(ctx, cf->qualified_name, "lsp_method_ref_ctor", 0.90f); } else { - java_emit_resolved(ctx, - cbm_arena_sprintf(ctx->arena, "%s.%s", type_qn, short_name), + java_emit_resolved(ctx, cbm_arena_sprintf(ctx->arena, "%s.%s", type_qn, short_name), "lsp_method_ref_ctor_synth", 0.80f); } return; @@ -2405,7 +2586,8 @@ static void resolve_method_reference(JavaLSPContext *ctx, TSNode mref, m = java_lookup_method(ctx, type_qn, mname, try_arity - 1); } } - if (!m) m = java_lookup_method(ctx, type_qn, mname, 0); + if (!m) + m = java_lookup_method(ctx, type_qn, mname, 0); if (m) { java_emit_resolved(ctx, m->qualified_name, "lsp_method_ref", 0.90f); } else { @@ -2416,16 +2598,18 @@ static void resolve_method_reference(JavaLSPContext *ctx, TSNode mref, /* Resolve any method-reference args of a method call. */ static uint32_t bind_method_ref_args(JavaLSPContext *ctx, TSNode call_node, - const CBMRegisteredFunc *resolved, - const CBMType *recv_type) { + const CBMRegisteredFunc *resolved, const CBMType *recv_type) { uint32_t handled = 0; - if (!resolved) return handled; + if (!resolved) + return handled; TSNode args_node = ts_node_child_by_field_name(call_node, "arguments", 9); - if (ts_node_is_null(args_node)) return handled; + if (ts_node_is_null(args_node)) + return handled; uint32_t n = ts_node_named_child_count(args_node); for (uint32_t i = 0; i < n && i < 32; i++) { TSNode arg = ts_node_named_child(args_node, i); - if (strcmp(ts_node_type(arg), "method_reference") != 0) continue; + if (strcmp(ts_node_type(arg), "method_reference") != 0) + continue; resolve_method_reference(ctx, arg, resolved, (int)i, recv_type); handled |= ((uint32_t)1u << i); } @@ -2436,14 +2620,17 @@ static uint32_t bind_method_ref_args(JavaLSPContext *ctx, TSNode call_node, * NULL if the receiver type is unknown. Used by bind_lambda_args / * bind_method_ref_args before re-walking arguments — we need the resolved * method to know the SAM-typed parameter slot. */ -static const CBMRegisteredFunc * -lookup_method_for_call(JavaLSPContext *ctx, TSNode call, const CBMType **out_recv_type) { - if (out_recv_type) *out_recv_type = NULL; +static const CBMRegisteredFunc *lookup_method_for_call(JavaLSPContext *ctx, TSNode call, + const CBMType **out_recv_type) { + if (out_recv_type) + *out_recv_type = NULL; TSNode obj = ts_node_child_by_field_name(call, "object", 6); TSNode name_node = ts_node_child_by_field_name(call, "name", 4); - if (ts_node_is_null(name_node)) return NULL; + if (ts_node_is_null(name_node)) + return NULL; char *mname = java_node_text(ctx, name_node); - if (!mname) return NULL; + if (!mname) + return NULL; int arity = count_call_args(call); if (ts_node_is_null(obj)) { @@ -2453,8 +2640,7 @@ lookup_method_for_call(JavaLSPContext *ctx, TSNode call, const CBMType **out_rec return NULL; } if (strcmp(ts_node_type(obj), "super") == 0) { - const char *sq = ctx->enclosing_super_qn ? ctx->enclosing_super_qn - : "java.lang.Object"; + const char *sq = ctx->enclosing_super_qn ? ctx->enclosing_super_qn : "java.lang.Object"; return java_lookup_method(ctx, sq, mname, arity); } if (strcmp(ts_node_type(obj), "this") == 0) { @@ -2470,18 +2656,22 @@ lookup_method_for_call(JavaLSPContext *ctx, TSNode call, const CBMType **out_rec const char *cls_qn = java_resolve_type_name(ctx, oname); if (cls_qn) { const CBMRegisteredFunc *f = java_lookup_method(ctx, cls_qn, mname, arity); - if (f) return f; + if (f) + return f; } } } /* Instance dispatch. */ const CBMType *recv = java_eval_expr_type(ctx, obj); - if (out_recv_type) *out_recv_type = recv; + if (out_recv_type) + *out_recv_type = recv; const char *recv_qn = NULL; - if (recv && recv->kind == CBM_TYPE_NAMED) recv_qn = recv->data.named.qualified_name; + if (recv && recv->kind == CBM_TYPE_NAMED) + recv_qn = recv->data.named.qualified_name; else if (recv && recv->kind == CBM_TYPE_TEMPLATE) recv_qn = recv->data.template_type.template_name; - if (recv_qn) return java_lookup_method(ctx, recv_qn, mname, arity); + if (recv_qn) + return java_lookup_method(ctx, recv_qn, mname, arity); return NULL; } @@ -2489,7 +2679,8 @@ lookup_method_for_call(JavaLSPContext *ctx, TSNode call, const CBMType **out_rec * method_invocation / object_creation_expression and recursing into block * children with proper scope handling. */ static void java_resolve_calls_in_node(JavaLSPContext *ctx, TSNode node) { - if (ts_node_is_null(node)) return; + if (ts_node_is_null(node)) + return; const char *kind = ts_node_type(node); /* Standalone method_reference (e.g. `return StringBuilder::new;` or @@ -2521,11 +2712,13 @@ static void java_resolve_calls_in_node(JavaLSPContext *ctx, TSNode node) { TSNode args_node = ts_node_child_by_field_name(node, "arguments", 9); /* Walk receiver expression. */ TSNode obj = ts_node_child_by_field_name(node, "object", 6); - if (!ts_node_is_null(obj)) java_resolve_calls_in_node(ctx, obj); + if (!ts_node_is_null(obj)) + java_resolve_calls_in_node(ctx, obj); if (!ts_node_is_null(args_node)) { uint32_t n = ts_node_named_child_count(args_node); for (uint32_t i = 0; i < n; i++) { - if (handled & ((uint32_t)1u << i)) continue; + if (handled & ((uint32_t)1u << i)) + continue; TSNode c = ts_node_named_child(args_node, i); java_resolve_calls_in_node(ctx, c); } @@ -2539,24 +2732,26 @@ static void java_resolve_calls_in_node(JavaLSPContext *ctx, TSNode node) { if (!ts_node_is_null(type_node)) { const CBMType *t = java_parse_type_node(ctx, type_node); const char *qn = NULL; - if (t && t->kind == CBM_TYPE_NAMED) qn = t->data.named.qualified_name; - else if (t && t->kind == CBM_TYPE_TEMPLATE) qn = t->data.template_type.template_name; + if (t && t->kind == CBM_TYPE_NAMED) + qn = t->data.named.qualified_name; + else if (t && t->kind == CBM_TYPE_TEMPLATE) + qn = t->data.template_type.template_name; if (qn) { int arity = count_call_args(node); /* Constructor short name is the class's short name. */ const char *short_name = strrchr(qn, '.'); short_name = short_name ? short_name + 1 : qn; - const CBMRegisteredFunc *cf = cbm_registry_lookup_method_by_args(ctx->registry, qn, - short_name, - arity); - if (!cf) cf = cbm_registry_lookup_method(ctx->registry, qn, short_name); + const CBMRegisteredFunc *cf = + cbm_registry_lookup_method_by_args(ctx->registry, qn, short_name, arity); + if (!cf) + cf = cbm_registry_lookup_method(ctx->registry, qn, short_name); if (cf) { java_emit_resolved(ctx, cf->qualified_name, "lsp_constructor", 0.95f); } else { /* Synth a constructor QN — Class.Class — so downstream * still gets a resolvable edge. */ java_emit_resolved(ctx, cbm_arena_sprintf(ctx->arena, "%s.%s", qn, short_name), - "lsp_constructor_synth", 0.85f); + "lsp_constructor_synth", 0.85f); } } } @@ -2581,7 +2776,8 @@ static void java_resolve_calls_in_node(JavaLSPContext *ctx, TSNode node) { java_process_statement(ctx, node); /* Don't double-walk blocks (process_block handles its own scope). */ - if (strcmp(kind, "block") == 0) return; + if (strcmp(kind, "block") == 0) + return; uint32_t n = ts_node_named_child_count(node); for (uint32_t i = 0; i < n; i++) { @@ -2600,36 +2796,44 @@ static void java_resolve_calls_in_node(JavaLSPContext *ctx, TSNode node) { * *out_count to the number of args. */ static const char **split_generic_args(CBMArena *a, const char *inside, int *out_count) { *out_count = 0; - if (!inside || !inside[0]) return NULL; + if (!inside || !inside[0]) + return NULL; const char *args[16]; int count = 0; int depth = 0; const char *seg_start = inside; for (const char *p = inside; *p; p++) { - if (*p == '<') depth++; - else if (*p == '>') depth--; + if (*p == '<') + depth++; + else if (*p == '>') + depth--; else if (*p == ',' && depth == 0) { /* trim leading whitespace from seg_start */ - while (seg_start < p && (*seg_start == ' ' || *seg_start == '\t')) seg_start++; + while (seg_start < p && (*seg_start == ' ' || *seg_start == '\t')) + seg_start++; const char *seg_end = p; - while (seg_end > seg_start && (seg_end[-1] == ' ' || seg_end[-1] == '\t')) seg_end--; + while (seg_end > seg_start && (seg_end[-1] == ' ' || seg_end[-1] == '\t')) + seg_end--; if (count < 16 && seg_end > seg_start) { - args[count++] = cbm_arena_strndup(a, seg_start, - (size_t)(seg_end - seg_start)); + args[count++] = cbm_arena_strndup(a, seg_start, (size_t)(seg_end - seg_start)); } seg_start = p + 1; } } /* last segment */ - while (*seg_start == ' ' || *seg_start == '\t') seg_start++; + while (*seg_start == ' ' || *seg_start == '\t') + seg_start++; const char *seg_end = inside + strlen(inside); - while (seg_end > seg_start && (seg_end[-1] == ' ' || seg_end[-1] == '\t')) seg_end--; + while (seg_end > seg_start && (seg_end[-1] == ' ' || seg_end[-1] == '\t')) + seg_end--; if (count < 16 && seg_end > seg_start) { args[count++] = cbm_arena_strndup(a, seg_start, (size_t)(seg_end - seg_start)); } - if (count == 0) return NULL; + if (count == 0) + return NULL; const char **result = (const char **)cbm_arena_alloc(a, (size_t)(count + 1) * sizeof(*result)); - for (int i = 0; i < count; i++) result[i] = args[i]; + for (int i = 0; i < count; i++) + result[i] = args[i]; result[count] = NULL; *out_count = count; return result; @@ -2643,10 +2847,10 @@ static const char **split_generic_args(CBMArena *a, const char *inside, int *out * Preserves generic template arguments: "Consumer" parses to a * TEMPLATE("java.util.function.Consumer", [String]) so the SAM-binder * downstream can substitute the lambda's parameter type correctly. */ -static const CBMType *parse_param_text_full(CBMArena *a, const char *text, - const char *parent_class, const char *module_qn, - const CBMTypeRegistry *reg) { - if (!text) return cbm_type_unknown(); +static const CBMType *parse_param_text_full(CBMArena *a, const char *text, const char *parent_class, + const char *module_qn, const CBMTypeRegistry *reg) { + if (!text) + return cbm_type_unknown(); int dim = 0; const char *no_arr = unwrap_array_text(a, text, &dim); const char *no_gen = strip_generics(a, no_arr); @@ -2660,10 +2864,14 @@ static const CBMType *parse_param_text_full(CBMArena *a, const char *text, int depth = 0; const char *gt = NULL; for (const char *p = lt; *p; p++) { - if (*p == '<') depth++; + if (*p == '<') + depth++; else if (*p == '>') { depth--; - if (depth == 0) { gt = p; break; } + if (depth == 0) { + gt = p; + break; + } } } if (gt && gt > lt + 1) { @@ -2696,7 +2904,8 @@ static const CBMType *parse_param_text_full(CBMArena *a, const char *text, break; } const char *last_dot = strrchr(cur, '.'); - if (!last_dot) break; + if (!last_dot) + break; cur = cbm_arena_strndup(a, cur, (size_t)(last_dot - cur)); } } @@ -2706,8 +2915,8 @@ static const CBMType *parse_param_text_full(CBMArena *a, const char *text, * even if the import wasn't threaded into this codepath. */ if (!base && reg) { for (int i = 0; JAVA_FALLBACK_PACKAGES[i]; i++) { - const char *cand = cbm_arena_sprintf(a, "%s.%s", JAVA_FALLBACK_PACKAGES[i], - no_gen); + const char *cand = + cbm_arena_sprintf(a, "%s.%s", JAVA_FALLBACK_PACKAGES[i], no_gen); if (cbm_registry_lookup_type(reg, cand)) { base = cbm_type_named(a, cand); break; @@ -2724,16 +2933,16 @@ static const CBMType *parse_param_text_full(CBMArena *a, const char *text, } } } - if (!base) base = cbm_type_unknown(); + if (!base) + base = cbm_type_unknown(); /* If we extracted generic args and the base is NAMED, wrap as TEMPLATE * with each arg recursively parsed. */ if (gen_args_inner && base && base->kind == CBM_TYPE_NAMED) { int gc = 0; const char **arg_strs = split_generic_args(a, gen_args_inner, &gc); if (arg_strs && gc > 0) { - const CBMType **gargs = (const CBMType **)cbm_arena_alloc(a, - (size_t)(gc + 1) - * sizeof(*gargs)); + const CBMType **gargs = + (const CBMType **)cbm_arena_alloc(a, (size_t)(gc + 1) * sizeof(*gargs)); for (int i = 0; i < gc; i++) { gargs[i] = parse_param_text_full(a, arg_strs[i], parent_class, module_qn, reg); } @@ -2741,7 +2950,8 @@ static const CBMType *parse_param_text_full(CBMArena *a, const char *text, base = cbm_type_template(a, base->data.named.qualified_name, gargs, gc); } } - while (dim-- > 0) base = cbm_type_slice(a, base); + while (dim-- > 0) + base = cbm_type_slice(a, base); return base; } @@ -2761,12 +2971,14 @@ static void register_local_func_or_type_from_file(JavaLSPContext *ctx, CBMTypeRe * regardless of source order. */ for (int i = 0; i < result->defs.count; i++) { CBMDefinition *d = &result->defs.items[i]; - if (!d->qualified_name || !d->name || !d->label) continue; + if (!d->qualified_name || !d->name || !d->label) + continue; if (strcmp(d->label, "Class") != 0 && strcmp(d->label, "Interface") != 0 && strcmp(d->label, "Enum") != 0 && strcmp(d->label, "Type") != 0) { continue; } - if (cbm_registry_lookup_type(reg, d->qualified_name)) continue; + if (cbm_registry_lookup_type(reg, d->qualified_name)) + continue; CBMRegisteredType stub; memset(&stub, 0, sizeof(stub)); stub.qualified_name = d->qualified_name; @@ -2780,20 +2992,22 @@ static void register_local_func_or_type_from_file(JavaLSPContext *ctx, CBMTypeRe * registry entry in place via the type lookup (CBMRegisteredType *). */ for (int i = 0; i < result->defs.count; i++) { CBMDefinition *d = &result->defs.items[i]; - if (!d->qualified_name || !d->name || !d->label) continue; + if (!d->qualified_name || !d->name || !d->label) + continue; if (strcmp(d->label, "Class") != 0 && strcmp(d->label, "Interface") != 0 && strcmp(d->label, "Enum") != 0 && strcmp(d->label, "Type") != 0) { continue; } const CBMRegisteredType *existing = cbm_registry_lookup_type(reg, d->qualified_name); - if (!existing) continue; - CBMRegisteredType rt = *existing; /* Will be re-added; lookup_type returns the - registry's stored copy. We instead build a - local copy and let cbm_registry_add_type's - de-dup not apply — but since we already - added the stub in Pass 1a, we must mutate - in-place. The registry exposes its arrays; - do the in-place update directly. */ + if (!existing) + continue; + CBMRegisteredType rt = *existing; /* Will be re-added; lookup_type returns the + registry's stored copy. We instead build a + local copy and let cbm_registry_add_type's + de-dup not apply — but since we already + added the stub in Pass 1a, we must mutate + in-place. The registry exposes its arrays; + do the in-place update directly. */ (void)rt; /* Locate the actual entry in reg->types[] and write into it. */ CBMRegisteredType *slot = NULL; @@ -2805,14 +3019,15 @@ static void register_local_func_or_type_from_file(JavaLSPContext *ctx, CBMTypeRe break; } } - if (!slot) continue; + if (!slot) + continue; if (d->base_classes) { int bc_count = 0; - while (d->base_classes[bc_count]) bc_count++; + while (d->base_classes[bc_count]) + bc_count++; if (bc_count > 0) { - const char **emb = (const char **)cbm_arena_alloc(a, - (size_t)(bc_count + 1) - * sizeof(*emb)); + const char **emb = + (const char **)cbm_arena_alloc(a, (size_t)(bc_count + 1) * sizeof(*emb)); for (int j = 0; j < bc_count; j++) { const char *bc = d->base_classes[j]; if (!bc || !bc[0]) { @@ -2841,9 +3056,9 @@ static void register_local_func_or_type_from_file(JavaLSPContext *ctx, CBMTypeRe if (!parent_qn && d->qualified_name) { const char *last_dot_qn = strrchr(d->qualified_name, '.'); if (last_dot_qn) { - parent_qn = cbm_arena_strndup(a, d->qualified_name, - (size_t)(last_dot_qn - - d->qualified_name)); + parent_qn = + cbm_arena_strndup(a, d->qualified_name, + (size_t)(last_dot_qn - d->qualified_name)); } } if (!resolved && parent_qn) { @@ -2855,7 +3070,8 @@ static void register_local_func_or_type_from_file(JavaLSPContext *ctx, CBMTypeRe break; } const char *last_dot = strrchr(cur, '.'); - if (!last_dot) break; + if (!last_dot) + break; cur = cbm_arena_strndup(a, cur, (size_t)(last_dot - cur)); } } @@ -2892,7 +3108,8 @@ static void register_local_func_or_type_from_file(JavaLSPContext *ctx, CBMTypeRe /* Register methods. */ for (int i = 0; i < result->defs.count; i++) { CBMDefinition *d = &result->defs.items[i]; - if (!d->qualified_name || !d->name || !d->label) continue; + if (!d->qualified_name || !d->name || !d->label) + continue; if (strcmp(d->label, "Method") != 0 && strcmp(d->label, "Function") != 0 && strcmp(d->label, "Constructor") != 0) { continue; @@ -2910,12 +3127,12 @@ static void register_local_func_or_type_from_file(JavaLSPContext *ctx, CBMTypeRe const CBMType **ptypes = NULL; if (d->param_types) { int pc = 0; - while (d->param_types[pc]) pc++; + while (d->param_types[pc]) + pc++; if (pc > 0) { ptypes = (const CBMType **)cbm_arena_alloc(a, (size_t)(pc + 1) * sizeof(*ptypes)); for (int j = 0; j < pc; j++) { - ptypes[j] = parse_param_text_full(a, d->param_types[j], parent, module_qn, - reg); + ptypes[j] = parse_param_text_full(a, d->param_types[j], parent, module_qn, reg); } ptypes[pc] = NULL; } @@ -2958,9 +3175,11 @@ static void patch_method_signatures_from_ast(JavaLSPContext *ctx, CBMTypeRegistr static void patch_one_method(JavaLSPContext *ctx, CBMTypeRegistry *reg, TSNode method_node, const char *class_qn, bool is_constructor) { TSNode name_node = ts_node_child_by_field_name(method_node, "name", 4); - if (ts_node_is_null(name_node)) return; + if (ts_node_is_null(name_node)) + return; char *mname = java_node_text(ctx, name_node); - if (!mname) return; + if (!mname) + return; char *method_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", class_qn, mname); /* Find the registered func by exact QN. */ @@ -2972,17 +3191,20 @@ static void patch_one_method(JavaLSPContext *ctx, CBMTypeRegistry *reg, TSNode m break; } } - if (!slot) return; + if (!slot) + return; /* Re-extract param types from the AST. */ TSNode params = ts_node_child_by_field_name(method_node, "parameters", 10); - if (ts_node_is_null(params)) return; + if (ts_node_is_null(params)) + return; uint32_t pn = ts_node_named_child_count(params); - if (pn == 0) return; - const CBMType **ptypes = (const CBMType **)cbm_arena_alloc(ctx->arena, - (size_t)(pn + 1) - * sizeof(*ptypes)); - if (!ptypes) return; + if (pn == 0) + return; + const CBMType **ptypes = + (const CBMType **)cbm_arena_alloc(ctx->arena, (size_t)(pn + 1) * sizeof(*ptypes)); + if (!ptypes) + return; int idx = 0; for (uint32_t i = 0; i < pn; i++) { TSNode p = ts_node_named_child(params, i); @@ -2992,7 +3214,8 @@ static void patch_one_method(JavaLSPContext *ctx, CBMTypeRegistry *reg, TSNode m } TSNode ptype = ts_node_child_by_field_name(p, "type", 4); const CBMType *pt = cbm_type_unknown(); - if (!ts_node_is_null(ptype)) pt = java_parse_type_node(ctx, ptype); + if (!ts_node_is_null(ptype)) + pt = java_parse_type_node(ctx, ptype); if (strcmp(pk, "spread_parameter") == 0 && pt) { pt = cbm_type_slice(ctx->arena, pt); } @@ -3022,16 +3245,19 @@ static void patch_one_method(JavaLSPContext *ctx, CBMTypeRegistry *reg, TSNode m static void patch_method_signatures_from_ast(JavaLSPContext *ctx, CBMTypeRegistry *reg, TSNode class_node, const char *enclosing_class_qn) { - if (ts_node_is_null(class_node)) return; + if (ts_node_is_null(class_node)) + return; const char *kind = ts_node_type(class_node); if (strcmp(kind, "class_declaration") != 0 && strcmp(kind, "interface_declaration") != 0 && strcmp(kind, "enum_declaration") != 0 && strcmp(kind, "record_declaration") != 0) { return; } TSNode name_node = ts_node_child_by_field_name(class_node, "name", 4); - if (ts_node_is_null(name_node)) return; + if (ts_node_is_null(name_node)) + return; char *cname = java_node_text(ctx, name_node); - if (!cname) return; + if (!cname) + return; char *class_qn; if (enclosing_class_qn) { class_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", enclosing_class_qn, cname); @@ -3042,7 +3268,8 @@ static void patch_method_signatures_from_ast(JavaLSPContext *ctx, CBMTypeRegistr } TSNode body = ts_node_child_by_field_name(class_node, "body", 4); - if (ts_node_is_null(body)) return; + if (ts_node_is_null(body)) + return; const char *saved = ctx->enclosing_class_qn; ctx->enclosing_class_qn = class_qn; @@ -3058,8 +3285,7 @@ static void patch_method_signatures_from_ast(JavaLSPContext *ctx, CBMTypeRegistr patch_one_method(ctx, reg, c, class_qn, true); } else if (strcmp(ck, "class_declaration") == 0 || strcmp(ck, "interface_declaration") == 0 || - strcmp(ck, "enum_declaration") == 0 || - strcmp(ck, "record_declaration") == 0) { + strcmp(ck, "enum_declaration") == 0 || strcmp(ck, "record_declaration") == 0) { patch_method_signatures_from_ast(ctx, reg, c, class_qn); } } @@ -3076,25 +3302,25 @@ static void append_field_to_class(CBMTypeRegistry *reg, CBMArena *a, const char const char *field_name, const CBMType *ftype) { CBMRegisteredType *slot = NULL; for (int ti = 0; ti < reg->type_count; ti++) { - if (reg->types[ti].qualified_name && - strcmp(reg->types[ti].qualified_name, class_qn) == 0) { + if (reg->types[ti].qualified_name && strcmp(reg->types[ti].qualified_name, class_qn) == 0) { slot = ®->types[ti]; break; } } - if (!slot) return; + if (!slot) + return; int existing = 0; if (slot->field_names) { - while (slot->field_names[existing]) existing++; + while (slot->field_names[existing]) + existing++; } - const char **new_names = (const char **)cbm_arena_alloc(a, - (size_t)(existing + 2) - * sizeof(*new_names)); - const CBMType **new_types = (const CBMType **)cbm_arena_alloc(a, - (size_t)(existing + 2) - * sizeof(*new_types)); - if (!new_names || !new_types) return; + const char **new_names = + (const char **)cbm_arena_alloc(a, (size_t)(existing + 2) * sizeof(*new_names)); + const CBMType **new_types = + (const CBMType **)cbm_arena_alloc(a, (size_t)(existing + 2) * sizeof(*new_types)); + if (!new_names || !new_types) + return; for (int j = 0; j < existing; j++) { new_names[j] = slot->field_names[j]; new_types[j] = slot->field_types[j]; @@ -3112,16 +3338,19 @@ static void append_field_to_class(CBMTypeRegistry *reg, CBMArena *a, const char * field arrays. Recurses into nested type declarations. */ static void populate_class_fields_from_ast(JavaLSPContext *ctx, CBMTypeRegistry *reg, TSNode class_node, const char *enclosing_class_qn) { - if (ts_node_is_null(class_node)) return; + if (ts_node_is_null(class_node)) + return; const char *kind = ts_node_type(class_node); if (strcmp(kind, "class_declaration") != 0 && strcmp(kind, "interface_declaration") != 0 && strcmp(kind, "enum_declaration") != 0 && strcmp(kind, "record_declaration") != 0) { return; } TSNode name_node = ts_node_child_by_field_name(class_node, "name", 4); - if (ts_node_is_null(name_node)) return; + if (ts_node_is_null(name_node)) + return; char *cname = java_node_text(ctx, name_node); - if (!cname) return; + if (!cname) + return; char *class_qn; if (enclosing_class_qn) { @@ -3133,7 +3362,8 @@ static void populate_class_fields_from_ast(JavaLSPContext *ctx, CBMTypeRegistry } TSNode body = ts_node_child_by_field_name(class_node, "body", 4); - if (ts_node_is_null(body)) return; + if (ts_node_is_null(body)) + return; /* Snapshot enclosing-class stack so java_parse_type_node sees inner-class * scope when parsing field types like `Greeter` inside class `Main`. */ const char *saved_enc = ctx->enclosing_class_qn; @@ -3146,21 +3376,23 @@ static void populate_class_fields_from_ast(JavaLSPContext *ctx, CBMTypeRegistry const char *ck = ts_node_type(c); if (strcmp(ck, "field_declaration") == 0) { TSNode tn = ts_node_child_by_field_name(c, "type", 4); - const CBMType *ftype = ts_node_is_null(tn) ? cbm_type_unknown() - : java_parse_type_node(ctx, tn); + const CBMType *ftype = + ts_node_is_null(tn) ? cbm_type_unknown() : java_parse_type_node(ctx, tn); uint32_t fcc = ts_node_named_child_count(c); for (uint32_t j = 0; j < fcc; j++) { TSNode dec = ts_node_named_child(c, j); - if (strcmp(ts_node_type(dec), "variable_declarator") != 0) continue; + if (strcmp(ts_node_type(dec), "variable_declarator") != 0) + continue; TSNode dname = ts_node_child_by_field_name(dec, "name", 4); - if (ts_node_is_null(dname)) continue; + if (ts_node_is_null(dname)) + continue; char *fname = java_node_text(ctx, dname); - if (fname) append_field_to_class(reg, ctx->arena, class_qn, fname, ftype); + if (fname) + append_field_to_class(reg, ctx->arena, class_qn, fname, ftype); } } else if (strcmp(ck, "class_declaration") == 0 || strcmp(ck, "interface_declaration") == 0 || - strcmp(ck, "enum_declaration") == 0 || - strcmp(ck, "record_declaration") == 0) { + strcmp(ck, "enum_declaration") == 0 || strcmp(ck, "record_declaration") == 0) { populate_class_fields_from_ast(ctx, reg, c, class_qn); } } @@ -3174,7 +3406,8 @@ extern const TSLanguage *tree_sitter_java(void); void cbm_run_java_lsp(CBMArena *arena, CBMFileResult *result, const char *source, int source_len, TSNode root) { - if (!arena || !result || !source) return; + if (!arena || !result || !source) + return; CBMTypeRegistry reg; cbm_registry_init(®, arena); @@ -3186,8 +3419,7 @@ void cbm_run_java_lsp(CBMArena *arena, CBMFileResult *result, const char *source const char *module_qn = result->module_qn ? result->module_qn : ""; JavaLSPContext ctx; - java_lsp_init(&ctx, arena, source, source_len, ®, NULL, module_qn, - &result->resolved_calls); + java_lsp_init(&ctx, arena, source, source_len, ®, NULL, module_qn, &result->resolved_calls); /* Add imports collected by extract_imports.c — they are stored as * (local_name, module_path = full Java path). We translate to the LSP @@ -3200,7 +3432,8 @@ void cbm_run_java_lsp(CBMArena *arena, CBMFileResult *result, const char *source * pre-grammar-version edge cases. */ for (int i = 0; i < result->imports.count; i++) { CBMImport *imp = &result->imports.items[i]; - if (!imp->local_name || !imp->module_path) continue; + if (!imp->local_name || !imp->module_path) + continue; java_lsp_add_import(&ctx, imp->local_name, imp->module_path, CBM_JAVA_IMPORT_TYPE); } @@ -3232,7 +3465,8 @@ void cbm_run_java_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 (!arena || !source) return; + if (!arena || !source) + return; CBMTypeRegistry reg; cbm_registry_init(®, arena); @@ -3241,7 +3475,8 @@ void cbm_run_java_lsp_cross(CBMArena *arena, const char *source, int source_len, /* Register cross-file defs. */ for (int i = 0; i < def_count; i++) { CBMLSPDef *d = &defs[i]; - if (!d->qualified_name || !d->short_name || !d->label) continue; + if (!d->qualified_name || !d->short_name || !d->label) + continue; if (strcmp(d->label, "Class") == 0 || strcmp(d->label, "Interface") == 0 || strcmp(d->label, "Enum") == 0 || strcmp(d->label, "Type") == 0) { CBMRegisteredType rt; @@ -3252,18 +3487,22 @@ void cbm_run_java_lsp_cross(CBMArena *arena, const char *source, int source_len, /* Embedded types from "|"-separated text. */ if (d->embedded_types && d->embedded_types[0]) { int n = 1; - for (const char *p = d->embedded_types; *p; p++) if (*p == '|') n++; - const char **emb = (const char **)cbm_arena_alloc(arena, - (size_t)(n + 1) * sizeof(*emb)); + for (const char *p = d->embedded_types; *p; p++) + if (*p == '|') + n++; + const char **emb = + (const char **)cbm_arena_alloc(arena, (size_t)(n + 1) * sizeof(*emb)); int idx = 0; const char *start = d->embedded_types; while (*start) { const char *end = start; - while (*end && *end != '|') end++; + while (*end && *end != '|') + end++; if (end > start) { emb[idx++] = cbm_arena_strndup(arena, start, (size_t)(end - start)); } - if (!*end) break; + if (!*end) + break; start = end + 1; } emb[idx] = NULL; @@ -3312,7 +3551,8 @@ void cbm_run_java_lsp_cross(CBMArena *arena, const char *source, int source_len, ts_parser_delete(parser); owns_tree = true; } - if (!tree) return; + if (!tree) + return; TSNode root = ts_tree_root_node(tree); JavaLSPContext ctx; @@ -3320,18 +3560,21 @@ void cbm_run_java_lsp_cross(CBMArena *arena, const char *source, int source_len, /* Apply caller-supplied imports. */ for (int i = 0; i < import_count; i++) { - if (!import_names[i] || !import_qns[i]) continue; + if (!import_names[i] || !import_qns[i]) + continue; java_lsp_add_import(&ctx, import_names[i], import_qns[i], CBM_JAVA_IMPORT_TYPE); } java_lsp_process_file(&ctx, root); - if (owns_tree) ts_tree_delete(tree); + if (owns_tree) + ts_tree_delete(tree); } void cbm_batch_java_lsp_cross(CBMArena *arena, CBMBatchJavaLSPFile *files, int file_count, CBMResolvedCallArray *out) { - if (!files || file_count <= 0) return; + if (!files || file_count <= 0) + return; for (int i = 0; i < file_count; i++) { cbm_run_java_lsp_cross(arena, files[i].source, files[i].source_len, files[i].module_qn, files[i].defs, files[i].def_count, files[i].import_names, diff --git a/internal/cbm/lsp/kotlin_lsp.c b/internal/cbm/lsp/kotlin_lsp.c index a10369756..2b67da778 100644 --- a/internal/cbm/lsp/kotlin_lsp.c +++ b/internal/cbm/lsp/kotlin_lsp.c @@ -4113,7 +4113,8 @@ static void kt_register_cross_def(CBMTypeRegistry *reg, CBMArena *arena, const C n++; } } - const char **emb = (const char **)cbm_arena_alloc(arena, (size_t)(n + 1) * sizeof(*emb)); + const char **emb = + (const char **)cbm_arena_alloc(arena, (size_t)(n + 1) * sizeof(*emb)); int idx = 0; const char *start = d->embedded_types; while (*start) { diff --git a/internal/cbm/lsp/kotlin_lsp.h b/internal/cbm/lsp/kotlin_lsp.h index 4ebbe1b09..2a1ec79a2 100644 --- a/internal/cbm/lsp/kotlin_lsp.h +++ b/internal/cbm/lsp/kotlin_lsp.h @@ -59,10 +59,10 @@ * `foo` is resolved when used as a bare identifier. */ typedef enum { CBM_KT_USE_UNKNOWN = 0, - CBM_KT_USE_TYPE, /* class / interface / object / typealias */ - CBM_KT_USE_FUNCTION, /* top-level fun, extension fun */ - CBM_KT_USE_PROPERTY, /* top-level val/var */ - CBM_KT_USE_WILDCARD, /* import a.b.* — local_name is a.b prefix */ + CBM_KT_USE_TYPE, /* class / interface / object / typealias */ + CBM_KT_USE_FUNCTION, /* top-level fun, extension fun */ + CBM_KT_USE_PROPERTY, /* top-level val/var */ + CBM_KT_USE_WILDCARD, /* import a.b.* — local_name is a.b prefix */ } CBMKotlinUseKind; /* KotlinLSPContext — per-file state for Kotlin call resolution. */ @@ -74,27 +74,27 @@ typedef struct KotlinLSPContext { CBMScope *current_scope; /* Package context. Empty string for default package. */ - const char *package_qn; /* dotted form, e.g. "com.example.foo" */ - const char *module_qn; /* file-level QN, e.g. ".com.example.foo." */ - const char *project_name; /* project prefix (without trailing dot) */ - const char *file_class_qn; /* JVM file-class QN, ".Kt" */ - const char *rel_path; /* for diagnostics */ + const char *package_qn; /* dotted form, e.g. "com.example.foo" */ + const char *module_qn; /* file-level QN, e.g. ".com.example.foo." */ + const char *project_name; /* project prefix (without trailing dot) */ + const char *file_class_qn; /* JVM file-class QN, ".Kt" */ + const char *rel_path; /* for diagnostics */ /* Import map (parallel arrays). Kotlin imports are flat — no grouping * — but each may have an `as` alias. Wildcard imports record the * package prefix in `import_targets[i]` with `import_kinds[i] = WILDCARD` * and `import_locals[i] = NULL`. */ - const char **import_locals; /* alias name (or short name) used in code */ - const char **import_targets; /* full FQN being imported */ + const char **import_locals; /* alias name (or short name) used in code */ + const char **import_targets; /* full FQN being imported */ CBMKotlinUseKind *import_kinds; int import_count; int import_cap; /* Current declaration context. */ - const char *enclosing_func_qn; /* function we are resolving inside */ - const char *enclosing_class_qn; /* innermost class/interface/object QN, or NULL */ + const char *enclosing_func_qn; /* function we are resolving inside */ + const char *enclosing_class_qn; /* innermost class/interface/object QN, or NULL */ const char *enclosing_companion_qn; /* if inside a companion object body */ - const char *enclosing_super_qn; /* primary super-class QN of current class, or NULL */ + const char *enclosing_super_qn; /* primary super-class QN of current class, or NULL */ /* Current `this` and `super` types. */ const CBMType *this_type; @@ -116,9 +116,8 @@ typedef struct KotlinLSPContext { /* Initialize a KotlinLSPContext for processing one file. */ void kotlin_lsp_init(KotlinLSPContext *ctx, CBMArena *arena, const char *source, int source_len, - const CBMTypeRegistry *registry, const char *package_qn, - const char *module_qn, const char *project_name, const char *rel_path, - CBMResolvedCallArray *out); + const CBMTypeRegistry *registry, const char *package_qn, const char *module_qn, + const char *project_name, const char *rel_path, CBMResolvedCallArray *out); /* Add an import mapping. local_name is the name used in code (alias or * short name); target_qn is the full dotted FQN. For wildcard imports, @@ -155,8 +154,8 @@ const CBMType *kotlin_lookup_property_type(KotlinLSPContext *ctx, const char *cl /* Entry point: build registry from file defs + stdlib, then run resolution. * Called from cbm_extract_file() after definitions and imports have been * extracted. */ -void cbm_run_kotlin_lsp(CBMArena *arena, CBMFileResult *result, const char *source, - int source_len, TSNode root); +void cbm_run_kotlin_lsp(CBMArena *arena, CBMFileResult *result, const char *source, int source_len, + TSNode root); /* Cross-file LSP: build a registry from project-wide defs (local + cross-file) * + stdlib, re-parse the source if no cached tree, walk and resolve calls. diff --git a/internal/cbm/lsp/py_lsp.c b/internal/cbm/lsp/py_lsp.c index 126245997..2ffd4a1aa 100644 --- a/internal/cbm/lsp/py_lsp.c +++ b/internal/cbm/lsp/py_lsp.c @@ -19,29 +19,30 @@ #include // Forward decls -static void py_resolve_calls_in(PyLSPContext* ctx, TSNode node); -static const CBMType* py_eval_expr_type(PyLSPContext* ctx, TSNode node); -static void py_process_statement(PyLSPContext* ctx, TSNode node); -static const CBMRegisteredFunc* py_lookup_attribute(PyLSPContext* ctx, - const char* type_qn, const char* member_name); -static void py_emit_resolved_call(PyLSPContext* ctx, const char* callee_qn, - const char* strategy, float confidence); -static const CBMType* py_resolve_annotation(PyLSPContext* ctx, const char* ann); -static const CBMType* py_iterable_element_type(PyLSPContext* ctx, const CBMType* iter_type); -static const CBMType* py_lookup_field(PyLSPContext* ctx, const char* type_qn, - const char* field_name); -static void py_register_instance_field(PyLSPContext* ctx, const char* class_qn, - const char* field_name, const CBMType* field_type); -static void py_bind_for_target(PyLSPContext* ctx, TSNode left, const CBMType* elem_type); -static void py_register_lambda(PyLSPContext* ctx, const char* name, TSNode lambda_node); -static void py_register_dict_literal(PyLSPContext* ctx, const char* name, TSNode dict_node); -static TSNode py_lookup_lambda(PyLSPContext* ctx, const char* name); -static const char* py_lookup_dict_dispatch(PyLSPContext* ctx, const char* var, - const char* key); +static void py_resolve_calls_in(PyLSPContext *ctx, TSNode node); +static const CBMType *py_eval_expr_type(PyLSPContext *ctx, TSNode node); +static void py_process_statement(PyLSPContext *ctx, TSNode node); +static const CBMRegisteredFunc *py_lookup_attribute(PyLSPContext *ctx, const char *type_qn, + const char *member_name); +static void py_emit_resolved_call(PyLSPContext *ctx, const char *callee_qn, const char *strategy, + float confidence); +static const CBMType *py_resolve_annotation(PyLSPContext *ctx, const char *ann); +static const CBMType *py_iterable_element_type(PyLSPContext *ctx, const CBMType *iter_type); +static const CBMType *py_lookup_field(PyLSPContext *ctx, const char *type_qn, + const char *field_name); +static void py_register_instance_field(PyLSPContext *ctx, const char *class_qn, + const char *field_name, const CBMType *field_type); +static void py_bind_for_target(PyLSPContext *ctx, TSNode left, const CBMType *elem_type); +static void py_register_lambda(PyLSPContext *ctx, const char *name, TSNode lambda_node); +static void py_register_dict_literal(PyLSPContext *ctx, const char *name, TSNode dict_node); +static TSNode py_lookup_lambda(PyLSPContext *ctx, const char *name); +static const char *py_lookup_dict_dispatch(PyLSPContext *ctx, const char *var, const char *key); -void py_lsp_init(PyLSPContext* ctx, CBMArena* arena, const char* source, int source_len, - const CBMTypeRegistry* registry, const char* module_qn, CBMResolvedCallArray* out) { - if (!ctx) return; +void py_lsp_init(PyLSPContext *ctx, CBMArena *arena, const char *source, int source_len, + const CBMTypeRegistry *registry, const char *module_qn, + CBMResolvedCallArray *out) { + if (!ctx) + return; memset(ctx, 0, sizeof(PyLSPContext)); ctx->arena = arena; ctx->source = source; @@ -50,19 +51,21 @@ void py_lsp_init(PyLSPContext* ctx, CBMArena* arena, const char* source, int sou ctx->module_qn = module_qn; ctx->resolved_calls = out; ctx->current_scope = cbm_scope_push(arena, NULL); - const char* dbg = getenv("CBM_LSP_DEBUG"); + const char *dbg = getenv("CBM_LSP_DEBUG"); ctx->debug = dbg && dbg[0] && dbg[0] != '0'; } -void py_lsp_add_import(PyLSPContext* ctx, const char* local_name, const char* module_qn) { - if (!ctx || !local_name || !module_qn) return; +void py_lsp_add_import(PyLSPContext *ctx, const char *local_name, const char *module_qn) { + if (!ctx || !local_name || !module_qn) + return; int new_count = ctx->import_count + 1; - const char** names = (const char**)cbm_arena_alloc(ctx->arena, - (size_t)(new_count + 1) * sizeof(const char*)); - const char** qns = (const char**)cbm_arena_alloc(ctx->arena, - (size_t)(new_count + 1) * sizeof(const char*)); - if (!names || !qns) return; + const char **names = + (const char **)cbm_arena_alloc(ctx->arena, (size_t)(new_count + 1) * sizeof(const char *)); + const char **qns = + (const char **)cbm_arena_alloc(ctx->arena, (size_t)(new_count + 1) * sizeof(const char *)); + if (!names || !qns) + return; for (int i = 0; i < ctx->import_count; i++) { names[i] = ctx->import_local_names[i]; @@ -84,13 +87,17 @@ void py_lsp_add_import(PyLSPContext* ctx, const char* local_name, const char* mo * and has at least two components, it's a from-import; otherwise it's a * straight module import. Matches the shape produced by extract_imports.c. */ -static bool import_is_from_style(const char* local_name, const char* module_qn) { - if (!local_name || !module_qn) return false; +static bool import_is_from_style(const char *local_name, const char *module_qn) { + if (!local_name || !module_qn) + return false; size_t local_len = strlen(local_name); size_t mod_len = strlen(module_qn); - if (mod_len <= local_len) return false; - if (module_qn[mod_len - local_len - 1] != '.') return false; - if (strcmp(module_qn + mod_len - local_len, local_name) != 0) return false; + if (mod_len <= local_len) + return false; + if (module_qn[mod_len - local_len - 1] != '.') + return false; + if (strcmp(module_qn + mod_len - local_len, local_name) != 0) + return false; return true; } @@ -99,44 +106,49 @@ static bool import_is_from_style(const char* local_name, const char* module_qn) * `a.b` → MODULE(a.b), `a.b.c` → MODULE(a.b.c). The underlying CBMImport * already records local_name="c" / module_path="a.b.c"; we walk the * prefix chain in addition. */ -static void py_bind_dotted_prefixes(PyLSPContext* ctx, const char* qn) { - if (!ctx || !qn) return; - const char* p = qn; +static void py_bind_dotted_prefixes(PyLSPContext *ctx, const char *qn) { + if (!ctx || !qn) + return; + const char *p = qn; for (;;) { - const char* dot = strchr(p, '.'); - if (!dot) break; + const char *dot = strchr(p, '.'); + if (!dot) + break; size_t prefix_len = (size_t)(dot - qn); - char* prefix = (char*)cbm_arena_alloc(ctx->arena, prefix_len + 1); - if (!prefix) return; + char *prefix = (char *)cbm_arena_alloc(ctx->arena, prefix_len + 1); + if (!prefix) + return; memcpy(prefix, qn, prefix_len); prefix[prefix_len] = '\0'; // Also bind the *short top-level name* — for `import a.b.c`, // the source typically writes `a.b.c.fn()` and `a` must be in // scope as MODULE("a") (not the full QN, since attribute access // walks one segment at a time). - const char* short_name = strrchr(prefix, '.'); - const char* bind_short = short_name ? short_name + 1 : prefix; + const char *short_name = strrchr(prefix, '.'); + const char *bind_short = short_name ? short_name + 1 : prefix; if (cbm_type_is_unknown(cbm_scope_lookup(ctx->current_scope, bind_short))) { - cbm_scope_bind(ctx->current_scope, bind_short, - cbm_type_module(ctx->arena, prefix)); + cbm_scope_bind(ctx->current_scope, bind_short, cbm_type_module(ctx->arena, prefix)); } p = dot + 1; } } -void py_lsp_bind_imports(PyLSPContext* ctx) { - if (!ctx || !ctx->current_scope) return; +void py_lsp_bind_imports(PyLSPContext *ctx) { + if (!ctx || !ctx->current_scope) + return; for (int i = 0; i < ctx->import_count; i++) { - const char* local = ctx->import_local_names[i]; - const char* qn = ctx->import_module_qns[i]; - if (!local || !qn) continue; + const char *local = ctx->import_local_names[i]; + const char *qn = ctx->import_module_qns[i]; + if (!local || !qn) + continue; // Wildcard imports are recorded for traceability but cannot bind a // concrete name — skip the scope insertion. Phase 9 cross-file // logic will use the import map directly to find re-exports. - if (strcmp(local, "*") == 0) continue; + if (strcmp(local, "*") == 0) + continue; - const CBMType* t; + const CBMType *t; if (import_is_from_style(local, qn)) { // `from X import Y` — bind Y to NAMED(X.Y). Phase 6 attribute // resolution checks the registry to upgrade to MODULE / class @@ -159,21 +171,25 @@ void py_lsp_bind_imports(PyLSPContext* ctx) { } } -const CBMType* py_lsp_lookup_in_scope(const PyLSPContext* ctx, const char* name) { - if (!ctx) return cbm_type_unknown(); +const CBMType *py_lsp_lookup_in_scope(const PyLSPContext *ctx, const char *name) { + if (!ctx) + return cbm_type_unknown(); return cbm_scope_lookup(ctx->current_scope, name); } /* ── helpers: text + emit ─────────────────────────────────────── */ -static char* py_node_text(PyLSPContext* ctx, TSNode node) { - if (!ctx || ts_node_is_null(node)) return NULL; +static char *py_node_text(PyLSPContext *ctx, TSNode node) { + if (!ctx || ts_node_is_null(node)) + return NULL; uint32_t start = ts_node_start_byte(node); uint32_t end = ts_node_end_byte(node); - if (end <= start || (int)end > ctx->source_len) return NULL; + if (end <= start || (int)end > ctx->source_len) + return NULL; size_t len = (size_t)(end - start); - char* out = (char*)cbm_arena_alloc(ctx->arena, len + 1); - if (!out) return NULL; + char *out = (char *)cbm_arena_alloc(ctx->arena, len + 1); + if (!out) + return NULL; memcpy(out, ctx->source + start, len); out[len] = '\0'; return out; @@ -181,13 +197,15 @@ static char* py_node_text(PyLSPContext* ctx, TSNode node) { /* ── lambda + dict-literal-dispatch registries ────────────────── */ -static void py_register_lambda(PyLSPContext* ctx, const char* name, TSNode lambda_node) { - if (!ctx || !name) return; +static void py_register_lambda(PyLSPContext *ctx, const char *name, TSNode lambda_node) { + if (!ctx || !name) + return; if (ctx->lambda_count >= ctx->lambda_cap) { int new_cap = ctx->lambda_cap == 0 ? 8 : ctx->lambda_cap * 2; - CBMLambdaEntry* grown = (CBMLambdaEntry*)cbm_arena_alloc(ctx->arena, - (size_t)new_cap * sizeof(CBMLambdaEntry)); - if (!grown) return; + CBMLambdaEntry *grown = + (CBMLambdaEntry *)cbm_arena_alloc(ctx->arena, (size_t)new_cap * sizeof(CBMLambdaEntry)); + if (!grown) + return; if (ctx->lambdas && ctx->lambda_count > 0) { memcpy(grown, ctx->lambdas, (size_t)ctx->lambda_count * sizeof(CBMLambdaEntry)); } @@ -206,9 +224,10 @@ static void py_register_lambda(PyLSPContext* ctx, const char* name, TSNode lambd ctx->lambda_count++; } -static TSNode py_lookup_lambda(PyLSPContext* ctx, const char* name) { +static TSNode py_lookup_lambda(PyLSPContext *ctx, const char *name) { TSNode null_node = {0}; - if (!ctx || !name) return null_node; + if (!ctx || !name) + return null_node; for (int i = 0; i < ctx->lambda_count; i++) { if (ctx->lambdas[i].name && strcmp(ctx->lambdas[i].name, name) == 0) { return ctx->lambdas[i].lambda_node; @@ -218,14 +237,17 @@ static TSNode py_lookup_lambda(PyLSPContext* ctx, const char* name) { } /* Helper for stripping quotes from a tree-sitter `string` node text. */ -static char* py_string_literal_value(PyLSPContext* ctx, TSNode str_node) { - if (ts_node_is_null(str_node)) return NULL; - char* lit = py_node_text(ctx, str_node); - if (!lit) return NULL; +static char *py_string_literal_value(PyLSPContext *ctx, TSNode str_node) { + if (ts_node_is_null(str_node)) + return NULL; + char *lit = py_node_text(ctx, str_node); + if (!lit) + return NULL; size_t len = strlen(lit); if (len >= 2 && (lit[0] == '"' || lit[0] == '\'') && lit[len - 1] == lit[0]) { - char* out = (char*)cbm_arena_alloc(ctx->arena, len - 1); - if (!out) return NULL; + char *out = (char *)cbm_arena_alloc(ctx->arena, len - 1); + if (!out) + return NULL; memcpy(out, lit + 1, len - 2); out[len - 2] = '\0'; return out; @@ -233,42 +255,49 @@ static char* py_string_literal_value(PyLSPContext* ctx, TSNode str_node) { return lit; } -static void py_register_dict_literal(PyLSPContext* ctx, const char* var, TSNode dict_node) { - if (!ctx || !var || ts_node_is_null(dict_node)) return; +static void py_register_dict_literal(PyLSPContext *ctx, const char *var, TSNode dict_node) { + if (!ctx || !var || ts_node_is_null(dict_node)) + return; uint32_t nc = ts_node_named_child_count(dict_node); for (uint32_t i = 0; i < nc; i++) { TSNode pair = ts_node_named_child(dict_node, i); - if (strcmp(ts_node_type(pair), "pair") != 0) continue; + if (strcmp(ts_node_type(pair), "pair") != 0) + continue; TSNode key = ts_node_child_by_field_name(pair, "key", 3); TSNode value = ts_node_child_by_field_name(pair, "value", 5); - if (ts_node_is_null(key) || ts_node_is_null(value)) continue; - if (strcmp(ts_node_type(key), "string") != 0) continue; - if (strcmp(ts_node_type(value), "identifier") != 0) continue; - char* key_text = py_string_literal_value(ctx, key); - char* val_text = py_node_text(ctx, value); - if (!key_text || !val_text) continue; + if (ts_node_is_null(key) || ts_node_is_null(value)) + continue; + if (strcmp(ts_node_type(key), "string") != 0) + continue; + if (strcmp(ts_node_type(value), "identifier") != 0) + continue; + char *key_text = py_string_literal_value(ctx, key); + char *val_text = py_node_text(ctx, value); + if (!key_text || !val_text) + continue; // Resolve val_text to a registered function QN. - const CBMRegisteredFunc* f = cbm_registry_lookup_symbol(ctx->registry, - ctx->module_qn, val_text); + const CBMRegisteredFunc *f = + cbm_registry_lookup_symbol(ctx->registry, ctx->module_qn, val_text); if (!f) { f = cbm_registry_lookup_symbol(ctx->registry, "builtins", val_text); } - if (!f) continue; + if (!f) + continue; if (ctx->dict_literal_count >= ctx->dict_literal_cap) { int new_cap = ctx->dict_literal_cap == 0 ? 16 : ctx->dict_literal_cap * 2; - CBMDictLiteralEntry* grown = (CBMDictLiteralEntry*)cbm_arena_alloc( + CBMDictLiteralEntry *grown = (CBMDictLiteralEntry *)cbm_arena_alloc( ctx->arena, (size_t)new_cap * sizeof(CBMDictLiteralEntry)); - if (!grown) return; + if (!grown) + return; if (ctx->dict_literals && ctx->dict_literal_count > 0) { memcpy(grown, ctx->dict_literals, - (size_t)ctx->dict_literal_count * sizeof(CBMDictLiteralEntry)); + (size_t)ctx->dict_literal_count * sizeof(CBMDictLiteralEntry)); } ctx->dict_literals = grown; ctx->dict_literal_cap = new_cap; } - ctx->dict_literals[ctx->dict_literal_count].var_name = - cbm_arena_strdup(ctx->arena, var); + ctx->dict_literals[ctx->dict_literal_count].var_name = cbm_arena_strdup(ctx->arena, var); ctx->dict_literals[ctx->dict_literal_count].literal_key = cbm_arena_strdup(ctx->arena, key_text); ctx->dict_literals[ctx->dict_literal_count].target_qn = @@ -277,22 +306,23 @@ static void py_register_dict_literal(PyLSPContext* ctx, const char* var, TSNode } } -static const char* py_lookup_dict_dispatch(PyLSPContext* ctx, const char* var, - const char* key) { - if (!ctx || !var || !key) return NULL; +static const char *py_lookup_dict_dispatch(PyLSPContext *ctx, const char *var, const char *key) { + if (!ctx || !var || !key) + return NULL; for (int i = 0; i < ctx->dict_literal_count; i++) { - const CBMDictLiteralEntry* e = &ctx->dict_literals[i]; - if (e->var_name && e->literal_key && - strcmp(e->var_name, var) == 0 && strcmp(e->literal_key, key) == 0) { + const CBMDictLiteralEntry *e = &ctx->dict_literals[i]; + if (e->var_name && e->literal_key && strcmp(e->var_name, var) == 0 && + strcmp(e->literal_key, key) == 0) { return e->target_qn; } } return NULL; } -static void py_emit_resolved_call(PyLSPContext* ctx, const char* callee_qn, - const char* strategy, float confidence) { - if (!ctx || !ctx->resolved_calls || !callee_qn || !ctx->enclosing_func_qn) return; +static void py_emit_resolved_call(PyLSPContext *ctx, const char *callee_qn, const char *strategy, + float confidence) { + if (!ctx || !ctx->resolved_calls || !callee_qn || !ctx->enclosing_func_qn) + return; // Dedupe by (caller, callee). Bounded-window scan: most duplicate // emissions are nearby in time (same expression evaluated by both // resolver and emitter passes), so checking only the last DEDUP_WINDOW @@ -303,9 +333,8 @@ static void py_emit_resolved_call(PyLSPContext* ctx, const char* callee_qn, int n = ctx->resolved_calls->count; int start = n > DEDUP_WINDOW ? n - DEDUP_WINDOW : 0; for (int i = start; i < n; i++) { - CBMResolvedCall* rc = &ctx->resolved_calls->items[i]; - if (rc->caller_qn && rc->callee_qn && - strcmp(rc->caller_qn, ctx->enclosing_func_qn) == 0 && + CBMResolvedCall *rc = &ctx->resolved_calls->items[i]; + if (rc->caller_qn && rc->callee_qn && strcmp(rc->caller_qn, ctx->enclosing_func_qn) == 0 && strcmp(rc->callee_qn, callee_qn) == 0) { if (confidence > rc->confidence) { rc->confidence = confidence; @@ -325,45 +354,53 @@ static void py_emit_resolved_call(PyLSPContext* ctx, const char* callee_qn, /* ── helpers: registry-driven attribute lookup with depth cap ──── */ -static const CBMRegisteredFunc* py_lookup_attribute_depth(PyLSPContext* ctx, - const char* type_qn, const char* member_name, int depth) { - if (!ctx || !type_qn || !member_name) return NULL; - if (depth > CBM_LSP_MAX_LOOKUP_DEPTH) return NULL; +static const CBMRegisteredFunc *py_lookup_attribute_depth(PyLSPContext *ctx, const char *type_qn, + const char *member_name, int depth) { + if (!ctx || !type_qn || !member_name) + return NULL; + if (depth > CBM_LSP_MAX_LOOKUP_DEPTH) + return NULL; - const CBMRegisteredFunc* f = cbm_registry_lookup_method(ctx->registry, type_qn, member_name); - if (f) return f; + 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); + const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, type_qn); if (rt) { if (rt->alias_of) { f = py_lookup_attribute_depth(ctx, rt->alias_of, member_name, depth + 1); - if (f) return f; + if (f) + return f; } if (rt->embedded_types) { for (int i = 0; rt->embedded_types[i]; i++) { f = py_lookup_attribute_depth(ctx, rt->embedded_types[i], member_name, depth + 1); - if (f) return f; + if (f) + return f; } } } return NULL; } -static const CBMRegisteredFunc* py_lookup_attribute(PyLSPContext* ctx, - const char* type_qn, const char* member_name) { +static const CBMRegisteredFunc *py_lookup_attribute(PyLSPContext *ctx, const char *type_qn, + const char *member_name) { return py_lookup_attribute_depth(ctx, type_qn, member_name, 0); } /* Look up a field on a registered type. Walks alias / embedded chain * with the same depth cap as method lookup. Returns the field's type * or NULL if no match. */ -static const CBMType* py_lookup_field_depth(PyLSPContext* ctx, const char* type_qn, - const char* field_name, int depth) { - if (!ctx || !ctx->registry || !type_qn || !field_name) return NULL; - if (depth > CBM_LSP_MAX_LOOKUP_DEPTH) return NULL; +static const CBMType *py_lookup_field_depth(PyLSPContext *ctx, const char *type_qn, + const char *field_name, int depth) { + if (!ctx || !ctx->registry || !type_qn || !field_name) + return NULL; + if (depth > CBM_LSP_MAX_LOOKUP_DEPTH) + return NULL; - const CBMRegisteredType* rt = cbm_registry_lookup_type(ctx->registry, type_qn); - if (!rt) return NULL; + const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, type_qn); + if (!rt) + return NULL; if (rt->field_names && rt->field_types) { for (int i = 0; rt->field_names[i]; i++) { if (strcmp(rt->field_names[i], field_name) == 0) { @@ -372,21 +409,23 @@ static const CBMType* py_lookup_field_depth(PyLSPContext* ctx, const char* type_ } } if (rt->alias_of) { - const CBMType* a = py_lookup_field_depth(ctx, rt->alias_of, field_name, depth + 1); - if (a) return a; + const CBMType *a = py_lookup_field_depth(ctx, rt->alias_of, field_name, depth + 1); + if (a) + return a; } if (rt->embedded_types) { for (int i = 0; rt->embedded_types[i]; i++) { - const CBMType* e = py_lookup_field_depth(ctx, - rt->embedded_types[i], field_name, depth + 1); - if (e) return e; + const CBMType *e = + py_lookup_field_depth(ctx, rt->embedded_types[i], field_name, depth + 1); + if (e) + return e; } } return NULL; } -static const CBMType* py_lookup_field(PyLSPContext* ctx, const char* type_qn, - const char* field_name) { +static const CBMType *py_lookup_field(PyLSPContext *ctx, const char *type_qn, + const char *field_name) { return py_lookup_field_depth(ctx, type_qn, field_name, 0); } @@ -394,27 +433,29 @@ static const CBMType* py_lookup_field(PyLSPContext* ctx, const char* type_qn, * Idempotent — re-registering the same name overwrites the existing * entry. Mutates the array in place by allocating a new (slightly * larger) array on each call; not a hot path so the cost is acceptable. */ -static void py_register_instance_field(PyLSPContext* ctx, const char* class_qn, - const char* field_name, const CBMType* field_type) { - if (!ctx || !ctx->registry || !class_qn || !field_name || !field_type) return; +static void py_register_instance_field(PyLSPContext *ctx, const char *class_qn, + const char *field_name, const CBMType *field_type) { + if (!ctx || !ctx->registry || !class_qn || !field_name || !field_type) + return; // Find the type entry. cbm_registry_lookup_type returns a const pointer; // we need a mutable pointer into the registry's array. - CBMRegisteredType* rt = NULL; + CBMRegisteredType *rt = NULL; for (int i = 0; i < ctx->registry->type_count; i++) { - const char* qn = ctx->registry->types[i].qualified_name; + const char *qn = ctx->registry->types[i].qualified_name; if (qn && strcmp(qn, class_qn) == 0) { rt = &ctx->registry->types[i]; break; } } - if (!rt) return; + if (!rt) + return; // Overwrite if name already registered. if (rt->field_names && rt->field_types) { for (int i = 0; rt->field_names[i]; i++) { if (strcmp(rt->field_names[i], field_name) == 0) { - ((const CBMType**)rt->field_types)[i] = field_type; + ((const CBMType **)rt->field_types)[i] = field_type; return; } } @@ -422,14 +463,16 @@ static void py_register_instance_field(PyLSPContext* ctx, const char* class_qn, int existing = 0; if (rt->field_names) { - while (rt->field_names[existing]) existing++; + while (rt->field_names[existing]) + existing++; } int new_count = existing + 1; - const char** new_names = (const char**)cbm_arena_alloc(ctx->arena, - (size_t)(new_count + 1) * sizeof(const char*)); - const CBMType** new_types = (const CBMType**)cbm_arena_alloc(ctx->arena, - (size_t)(new_count + 1) * sizeof(const CBMType*)); - if (!new_names || !new_types) return; + const char **new_names = + (const char **)cbm_arena_alloc(ctx->arena, (size_t)(new_count + 1) * sizeof(const char *)); + const CBMType **new_types = (const CBMType **)cbm_arena_alloc( + ctx->arena, (size_t)(new_count + 1) * sizeof(const CBMType *)); + if (!new_names || !new_types) + return; for (int i = 0; i < existing; i++) { new_names[i] = rt->field_names[i]; new_types[i] = rt->field_types[i]; @@ -446,32 +489,42 @@ static void py_register_instance_field(PyLSPContext* ctx, const char* class_qn, /* Map a tree-sitter Python literal node to a BUILTIN type. Returns NULL if * the node isn't a simple literal. */ -static const CBMType* py_literal_type(PyLSPContext* ctx, TSNode node) { - const char* k = ts_node_type(node); - if (strcmp(k, "integer") == 0) return cbm_type_builtin(ctx->arena, "int"); - if (strcmp(k, "float") == 0) return cbm_type_builtin(ctx->arena, "float"); - if (strcmp(k, "string") == 0) return cbm_type_builtin(ctx->arena, "str"); - if (strcmp(k, "concatenated_string") == 0) return cbm_type_builtin(ctx->arena, "str"); +static const CBMType *py_literal_type(PyLSPContext *ctx, TSNode node) { + const char *k = ts_node_type(node); + if (strcmp(k, "integer") == 0) + return cbm_type_builtin(ctx->arena, "int"); + if (strcmp(k, "float") == 0) + return cbm_type_builtin(ctx->arena, "float"); + if (strcmp(k, "string") == 0) + return cbm_type_builtin(ctx->arena, "str"); + if (strcmp(k, "concatenated_string") == 0) + return cbm_type_builtin(ctx->arena, "str"); if (strcmp(k, "true") == 0 || strcmp(k, "false") == 0) return cbm_type_builtin(ctx->arena, "bool"); - if (strcmp(k, "none") == 0) return cbm_type_builtin(ctx->arena, "None"); + if (strcmp(k, "none") == 0) + return cbm_type_builtin(ctx->arena, "None"); // tuple / list / set / dict are handled separately by py_eval_expr_type // so it can build structured TUPLE / TEMPLATE types from element // types. Only fall through to BUILTIN here for empty literals. - if (strcmp(k, "list_comprehension") == 0) return cbm_type_builtin(ctx->arena, "list"); - if (strcmp(k, "dictionary_comprehension") == 0) return cbm_type_builtin(ctx->arena, "dict"); - if (strcmp(k, "set_comprehension") == 0) return cbm_type_builtin(ctx->arena, "set"); - if (strcmp(k, "generator_expression") == 0) return cbm_type_builtin(ctx->arena, "generator"); + if (strcmp(k, "list_comprehension") == 0) + return cbm_type_builtin(ctx->arena, "list"); + if (strcmp(k, "dictionary_comprehension") == 0) + return cbm_type_builtin(ctx->arena, "dict"); + if (strcmp(k, "set_comprehension") == 0) + return cbm_type_builtin(ctx->arena, "set"); + if (strcmp(k, "generator_expression") == 0) + return cbm_type_builtin(ctx->arena, "generator"); return NULL; } /* Substitute "Self" / "typing.Self" return types with the receiver type. * Walks the type recursively so `Optional[Self]` becomes `Optional[R]`. */ -static const CBMType* py_substitute_self(PyLSPContext* ctx, const CBMType* t, - const char* receiver_qn) { - if (!t || !receiver_qn) return t; +static const CBMType *py_substitute_self(PyLSPContext *ctx, const CBMType *t, + const char *receiver_qn) { + if (!t || !receiver_qn) + return t; if (t->kind == CBM_TYPE_NAMED && t->data.named.qualified_name) { - const char* qn = t->data.named.qualified_name; + const char *qn = t->data.named.qualified_name; if (strcmp(qn, "Self") == 0 || strcmp(qn, "typing.Self") == 0 || strcmp(qn, "typing_extensions.Self") == 0) { return cbm_type_named(ctx->arena, receiver_qn); @@ -479,9 +532,10 @@ static const CBMType* py_substitute_self(PyLSPContext* ctx, const CBMType* t, } if (t->kind == CBM_TYPE_UNION) { int n = t->data.union_type.count; - const CBMType** members = (const CBMType**)cbm_arena_alloc(ctx->arena, - (size_t)(n + 1) * sizeof(const CBMType*)); - if (!members) return t; + const CBMType **members = (const CBMType **)cbm_arena_alloc( + ctx->arena, (size_t)(n + 1) * sizeof(const CBMType *)); + if (!members) + return t; for (int i = 0; i < n; i++) { members[i] = py_substitute_self(ctx, t->data.union_type.members[i], receiver_qn); } @@ -493,20 +547,25 @@ static const CBMType* py_substitute_self(PyLSPContext* ctx, const CBMType* t, /* If `func_qn` is a registered function, return its return type. When * called on a receiver, pass receiver_qn to substitute Self return * types; pass NULL otherwise. */ -static const CBMType* py_func_return_type_recv(PyLSPContext* ctx, const char* func_qn, - const char* receiver_qn) { - if (!ctx || !func_qn) return cbm_type_unknown(); - const CBMRegisteredFunc* f = cbm_registry_lookup_func(ctx->registry, func_qn); - if (!f || !f->signature) return cbm_type_unknown(); - if (f->signature->kind != CBM_TYPE_FUNC) return cbm_type_unknown(); - const CBMType** rets = f->signature->data.func.return_types; - if (!rets || !rets[0]) return cbm_type_unknown(); - const CBMType* base; +static const CBMType *py_func_return_type_recv(PyLSPContext *ctx, const char *func_qn, + const char *receiver_qn) { + if (!ctx || !func_qn) + return cbm_type_unknown(); + const CBMRegisteredFunc *f = cbm_registry_lookup_func(ctx->registry, func_qn); + if (!f || !f->signature) + return cbm_type_unknown(); + if (f->signature->kind != CBM_TYPE_FUNC) + return cbm_type_unknown(); + const CBMType **rets = f->signature->data.func.return_types; + if (!rets || !rets[0]) + return cbm_type_unknown(); + const CBMType *base; if (!rets[1]) { base = rets[0]; } else { int count = 0; - while (rets[count]) count++; + while (rets[count]) + count++; base = cbm_type_tuple(ctx->arena, rets, count); } if (receiver_qn) { @@ -516,7 +575,7 @@ static const CBMType* py_func_return_type_recv(PyLSPContext* ctx, const char* fu } /* Convenience wrapper: no receiver substitution. */ -static const CBMType* py_func_return_type(PyLSPContext* ctx, const char* func_qn) { +static const CBMType *py_func_return_type(PyLSPContext *ctx, const char *func_qn) { return py_func_return_type_recv(ctx, func_qn, NULL); } @@ -528,18 +587,20 @@ static const CBMType* py_func_return_type(PyLSPContext* ctx, const char* func_qn * no template args -> UNKNOWN. */ /* Bind a `for X in ...` target. Supports identifier (single binding) and * pattern_list / tuple_pattern (destructure tuple element type). */ -static void py_bind_for_target(PyLSPContext* ctx, TSNode left, const CBMType* elem_type) { - if (ts_node_is_null(left) || !ctx) return; - const char* lk = ts_node_type(left); +static void py_bind_for_target(PyLSPContext *ctx, TSNode left, const CBMType *elem_type) { + if (ts_node_is_null(left) || !ctx) + return; + const char *lk = ts_node_type(left); if (strcmp(lk, "identifier") == 0) { - char* name = py_node_text(ctx, left); - if (name) cbm_scope_bind(ctx->current_scope, name, elem_type); + char *name = py_node_text(ctx, left); + if (name) + cbm_scope_bind(ctx->current_scope, name, elem_type); return; } if (strcmp(lk, "pattern_list") == 0 || strcmp(lk, "tuple_pattern") == 0 || strcmp(lk, "list_pattern") == 0) { // Destructure tuple element type element-by-element. - const CBMType* const* elems = NULL; + const CBMType *const *elems = NULL; int count = 0; if (elem_type) { if (elem_type->kind == CBM_TYPE_TUPLE) { @@ -555,43 +616,49 @@ static void py_bind_for_target(PyLSPContext* ctx, TSNode left, const CBMType* el uint32_t lc = ts_node_named_child_count(left); for (uint32_t i = 0; i < lc; i++) { TSNode tgt = ts_node_named_child(left, i); - if (ts_node_is_null(tgt)) continue; - const char* tk = ts_node_type(tgt); + if (ts_node_is_null(tgt)) + continue; + const char *tk = ts_node_type(tgt); if (strcmp(tk, "identifier") == 0) { - char* nm = py_node_text(ctx, tgt); - if (!nm) continue; - const CBMType* bind_type; - if (elems && (int)i < count && elems[i]) bind_type = elems[i]; - else bind_type = cbm_type_unknown(); + char *nm = py_node_text(ctx, tgt); + if (!nm) + continue; + const CBMType *bind_type; + if (elems && (int)i < count && elems[i]) + bind_type = elems[i]; + else + bind_type = cbm_type_unknown(); cbm_scope_bind(ctx->current_scope, nm, bind_type); } } } } -static const CBMType* py_iterable_element_type(PyLSPContext* ctx, const CBMType* iter_type) { - if (!iter_type) return cbm_type_unknown(); +static const CBMType *py_iterable_element_type(PyLSPContext *ctx, const CBMType *iter_type) { + if (!iter_type) + return cbm_type_unknown(); if (iter_type->kind == CBM_TYPE_TEMPLATE) { - const char* name = iter_type->data.template_type.template_name; - const CBMType** args = iter_type->data.template_type.template_args; + const char *name = iter_type->data.template_type.template_name; + const CBMType **args = iter_type->data.template_type.template_args; int n = iter_type->data.template_type.arg_count; - if (!name || n <= 0 || !args) return cbm_type_unknown(); + if (!name || n <= 0 || !args) + return cbm_type_unknown(); if (strcmp(name, "list") == 0 || strcmp(name, "set") == 0 || strcmp(name, "frozenset") == 0 || strcmp(name, "Iterable") == 0 || strcmp(name, "Iterator") == 0 || strcmp(name, "Sequence") == 0 || strcmp(name, "MutableSequence") == 0 || strcmp(name, "AsyncIterable") == 0 || strcmp(name, "AsyncIterator") == 0 || strcmp(name, "Generator") == 0 || - strcmp(name, "AsyncGenerator") == 0 || - strcmp(name, "Reversible") == 0 || strcmp(name, "Collection") == 0 || - strcmp(name, "Container") == 0 || strcmp(name, "deque") == 0 || - strcmp(name, "KeysView") == 0 || strcmp(name, "ValuesView") == 0 || - strcmp(name, "dict_keys") == 0 || strcmp(name, "dict_values") == 0) { + strcmp(name, "AsyncGenerator") == 0 || strcmp(name, "Reversible") == 0 || + strcmp(name, "Collection") == 0 || strcmp(name, "Container") == 0 || + strcmp(name, "deque") == 0 || strcmp(name, "KeysView") == 0 || + strcmp(name, "ValuesView") == 0 || strcmp(name, "dict_keys") == 0 || + strcmp(name, "dict_values") == 0) { return args[0]; } if (strcmp(name, "ItemsView") == 0 || strcmp(name, "dict_items") == 0) { // Iterating ItemsView[K, V] yields tuple[K, V]. if (n >= 2) { - const CBMType* elems[3] = { args[0], args[1], NULL }; + const CBMType *elems[3] = {args[0], args[1], NULL}; return cbm_type_tuple(ctx->arena, elems, 2); } return cbm_type_unknown(); @@ -603,7 +670,8 @@ static const CBMType* py_iterable_element_type(PyLSPContext* ctx, const CBMType* return args[0]; } if (strcmp(name, "tuple") == 0) { - if (n == 1) return args[0]; + if (n == 1) + return args[0]; return cbm_type_union(ctx->arena, args, n); } // Other generics: best-effort take first arg. @@ -612,23 +680,27 @@ static const CBMType* py_iterable_element_type(PyLSPContext* ctx, const CBMType* return cbm_type_unknown(); } -static const CBMType* py_eval_expr_type(PyLSPContext* ctx, TSNode node) { - if (!ctx || ts_node_is_null(node)) return cbm_type_unknown(); +static const CBMType *py_eval_expr_type(PyLSPContext *ctx, TSNode node) { + if (!ctx || ts_node_is_null(node)) + return cbm_type_unknown(); - const CBMType* lit = py_literal_type(ctx, node); - if (lit) return lit; + const CBMType *lit = py_literal_type(ctx, node); + if (lit) + return lit; - const char* k = ts_node_type(node); + const char *k = ts_node_type(node); // Tuple / list / set / dict literals: build structured types from // element types so unpacking and indexing work without losing // precision. py_literal_type returns BUILTIN — we override here. if (strcmp(k, "tuple") == 0) { uint32_t cn = ts_node_named_child_count(node); - if (cn == 0) return cbm_type_builtin(ctx->arena, "tuple"); - const CBMType** elems = (const CBMType**)cbm_arena_alloc(ctx->arena, - (size_t)(cn + 1) * sizeof(const CBMType*)); - if (!elems) return cbm_type_builtin(ctx->arena, "tuple"); + if (cn == 0) + return cbm_type_builtin(ctx->arena, "tuple"); + const CBMType **elems = (const CBMType **)cbm_arena_alloc( + ctx->arena, (size_t)(cn + 1) * sizeof(const CBMType *)); + if (!elems) + return cbm_type_builtin(ctx->arena, "tuple"); for (uint32_t i = 0; i < cn; i++) { elems[i] = py_eval_expr_type(ctx, ts_node_named_child(node, i)); } @@ -637,80 +709,97 @@ static const CBMType* py_eval_expr_type(PyLSPContext* ctx, TSNode node) { } if (strcmp(k, "list") == 0) { uint32_t cn = ts_node_named_child_count(node); - if (cn == 0) return cbm_type_builtin(ctx->arena, "list"); - const CBMType* first = py_eval_expr_type(ctx, ts_node_named_child(node, 0)); + if (cn == 0) + return cbm_type_builtin(ctx->arena, "list"); + const CBMType *first = py_eval_expr_type(ctx, ts_node_named_child(node, 0)); if (!first || cbm_type_is_unknown(first)) return cbm_type_builtin(ctx->arena, "list"); return cbm_type_template(ctx->arena, "list", &first, 1); } if (strcmp(k, "set") == 0) { uint32_t cn = ts_node_named_child_count(node); - if (cn == 0) return cbm_type_builtin(ctx->arena, "set"); - const CBMType* first = py_eval_expr_type(ctx, ts_node_named_child(node, 0)); + if (cn == 0) + return cbm_type_builtin(ctx->arena, "set"); + const CBMType *first = py_eval_expr_type(ctx, ts_node_named_child(node, 0)); if (!first || cbm_type_is_unknown(first)) return cbm_type_builtin(ctx->arena, "set"); return cbm_type_template(ctx->arena, "set", &first, 1); } if (strcmp(k, "dictionary") == 0) { uint32_t cn = ts_node_named_child_count(node); - if (cn == 0) return cbm_type_builtin(ctx->arena, "dict"); + if (cn == 0) + return cbm_type_builtin(ctx->arena, "dict"); for (uint32_t i = 0; i < cn; i++) { TSNode pair = ts_node_named_child(node, i); - if (strcmp(ts_node_type(pair), "pair") != 0) continue; + if (strcmp(ts_node_type(pair), "pair") != 0) + continue; TSNode key = ts_node_child_by_field_name(pair, "key", 3); TSNode value = ts_node_child_by_field_name(pair, "value", 5); - if (ts_node_is_null(key) || ts_node_is_null(value)) continue; - const CBMType* kt = py_eval_expr_type(ctx, key); - const CBMType* vt = py_eval_expr_type(ctx, value); - if (!kt || !vt) break; - const CBMType* args[3] = { kt, vt, NULL }; + if (ts_node_is_null(key) || ts_node_is_null(value)) + continue; + const CBMType *kt = py_eval_expr_type(ctx, key); + const CBMType *vt = py_eval_expr_type(ctx, value); + if (!kt || !vt) + break; + const CBMType *args[3] = {kt, vt, NULL}; return cbm_type_template(ctx->arena, "dict", args, 2); } return cbm_type_builtin(ctx->arena, "dict"); } if (strcmp(k, "identifier") == 0) { - char* name = py_node_text(ctx, node); - if (!name) return cbm_type_unknown(); - const CBMType* t = cbm_scope_lookup(ctx->current_scope, name); - if (!cbm_type_is_unknown(t)) return t; + char *name = py_node_text(ctx, node); + if (!name) + return cbm_type_unknown(); + const CBMType *t = cbm_scope_lookup(ctx->current_scope, name); + if (!cbm_type_is_unknown(t)) + return t; // Builtin globals: True / False / None at top level. if (strcmp(name, "True") == 0 || strcmp(name, "False") == 0) return cbm_type_builtin(ctx->arena, "bool"); - if (strcmp(name, "None") == 0) return cbm_type_builtin(ctx->arena, "None"); + if (strcmp(name, "None") == 0) + return cbm_type_builtin(ctx->arena, "None"); // Module/package-local function. - const CBMRegisteredFunc* f = cbm_registry_lookup_symbol(ctx->registry, - ctx->module_qn, name); - if (f) return py_func_return_type(ctx, f->qualified_name); + const CBMRegisteredFunc *f = + cbm_registry_lookup_symbol(ctx->registry, ctx->module_qn, name); + if (f) + return py_func_return_type(ctx, f->qualified_name); // Builtins fallback (range / len / list / str / int / etc.). For // builtin classes, this returns the class as NAMED so subsequent // attribute access works. f = cbm_registry_lookup_symbol(ctx->registry, "builtins", name); - if (f) return py_func_return_type(ctx, f->qualified_name); - const CBMRegisteredType* rt = cbm_registry_lookup_type(ctx->registry, - cbm_arena_sprintf(ctx->arena, "builtins.%s", name)); - if (rt) return cbm_type_builtin(ctx->arena, name); + if (f) + return py_func_return_type(ctx, f->qualified_name); + const CBMRegisteredType *rt = cbm_registry_lookup_type( + ctx->registry, cbm_arena_sprintf(ctx->arena, "builtins.%s", name)); + if (rt) + return cbm_type_builtin(ctx->arena, name); return cbm_type_unknown(); } if (strcmp(k, "attribute") == 0) { TSNode obj = ts_node_child_by_field_name(node, "object", 6); TSNode attr = ts_node_child_by_field_name(node, "attribute", 9); - if (ts_node_is_null(obj) || ts_node_is_null(attr)) return cbm_type_unknown(); - const CBMType* obj_type = py_eval_expr_type(ctx, obj); - if (obj_type) obj_type = cbm_type_resolve_alias(obj_type); - char* attr_name = py_node_text(ctx, attr); - if (!attr_name || !obj_type) return cbm_type_unknown(); + if (ts_node_is_null(obj) || ts_node_is_null(attr)) + return cbm_type_unknown(); + const CBMType *obj_type = py_eval_expr_type(ctx, obj); + if (obj_type) + obj_type = cbm_type_resolve_alias(obj_type); + char *attr_name = py_node_text(ctx, attr); + if (!attr_name || !obj_type) + return cbm_type_unknown(); if (obj_type->kind == CBM_TYPE_MODULE) { // module.attr — return type of the registered symbol if known. - const char* mod = obj_type->data.module.module_qn; - const CBMRegisteredFunc* f = cbm_registry_lookup_symbol(ctx->registry, mod, attr_name); - if (f) return py_func_return_type(ctx, f->qualified_name); + const char *mod = obj_type->data.module.module_qn; + const CBMRegisteredFunc *f = cbm_registry_lookup_symbol(ctx->registry, mod, attr_name); + if (f) + return py_func_return_type(ctx, f->qualified_name); // Could also be a class — return NAMED("mod.attr") - const char* qn = cbm_arena_sprintf(ctx->arena, "%s.%s", mod, attr_name); - const CBMRegisteredType* rt = cbm_registry_lookup_type(ctx->registry, qn); - if (rt) return cbm_type_named(ctx->arena, qn); + const char *qn = cbm_arena_sprintf(ctx->arena, "%s.%s", mod, attr_name); + const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, qn); + if (rt) + return cbm_type_named(ctx->arena, qn); // Submodule: if any registered function/type has qn starting // with ".." then mod.attr is itself a module. // Linear scan over registry funcs is O(R) per access; we @@ -718,59 +807,64 @@ static const CBMType* py_eval_expr_type(PyLSPContext* ctx, TSNode node) { // matched as a function/type above. With ~900 stdlib funcs // and many module-attr accesses per file, this can dominate // — keeping the loop tight and bailing early on first match. - const char* prefix = cbm_arena_sprintf(ctx->arena, "%s.", qn); + const char *prefix = cbm_arena_sprintf(ctx->arena, "%s.", qn); size_t prefix_len = strlen(prefix); bool is_submodule = false; for (int i = 0; i < ctx->registry->func_count; i++) { - const char* fqn = ctx->registry->funcs[i].qualified_name; + const char *fqn = ctx->registry->funcs[i].qualified_name; if (fqn && strncmp(fqn, prefix, prefix_len) == 0) { is_submodule = true; break; } } - if (is_submodule) return cbm_type_module(ctx->arena, qn); + if (is_submodule) + return cbm_type_module(ctx->arena, qn); return cbm_type_unknown(); } if (obj_type->kind == CBM_TYPE_NAMED) { - const CBMRegisteredFunc* f = py_lookup_attribute(ctx, - obj_type->data.named.qualified_name, attr_name); - if (f) return py_func_return_type_recv(ctx, f->qualified_name, - obj_type->data.named.qualified_name); + const CBMRegisteredFunc *f = + py_lookup_attribute(ctx, obj_type->data.named.qualified_name, attr_name); + if (f) + return py_func_return_type_recv(ctx, f->qualified_name, + obj_type->data.named.qualified_name); // Field fallback: instance attributes from __init__'s self.x = expr. - const CBMType* field = py_lookup_field(ctx, - obj_type->data.named.qualified_name, attr_name); - if (field) return field; + const CBMType *field = + py_lookup_field(ctx, obj_type->data.named.qualified_name, attr_name); + if (field) + return field; } if (obj_type->kind == CBM_TYPE_BUILTIN && obj_type->data.builtin.name) { // Builtins map to typeshed stdlib via "builtins.". - const char* recv_qn = cbm_arena_sprintf(ctx->arena, "builtins.%s", - obj_type->data.builtin.name); - const CBMRegisteredFunc* f = py_lookup_attribute(ctx, recv_qn, attr_name); - if (f) return py_func_return_type_recv(ctx, f->qualified_name, recv_qn); + const char *recv_qn = + cbm_arena_sprintf(ctx->arena, "builtins.%s", obj_type->data.builtin.name); + const CBMRegisteredFunc *f = py_lookup_attribute(ctx, recv_qn, attr_name); + if (f) + return py_func_return_type_recv(ctx, f->qualified_name, recv_qn); } - if (obj_type->kind == CBM_TYPE_TEMPLATE && - obj_type->data.template_type.template_name) { + if (obj_type->kind == CBM_TYPE_TEMPLATE && obj_type->data.template_type.template_name) { // dict[K, V].get / list[T].append etc — receiver is the // unparameterized container in typeshed: builtins.dict / // builtins.list / collections.abc.Mapping etc. - const char* tname = obj_type->data.template_type.template_name; + const char *tname = obj_type->data.template_type.template_name; // Try builtins. first, then bare tname. - const char* recv_qn = cbm_arena_sprintf(ctx->arena, "builtins.%s", tname); - const CBMRegisteredFunc* f = py_lookup_attribute(ctx, recv_qn, attr_name); + const char *recv_qn = cbm_arena_sprintf(ctx->arena, "builtins.%s", tname); + const CBMRegisteredFunc *f = py_lookup_attribute(ctx, recv_qn, attr_name); if (!f) { f = py_lookup_attribute(ctx, tname, attr_name); } - if (f) return py_func_return_type_recv(ctx, f->qualified_name, recv_qn); + if (f) + return py_func_return_type_recv(ctx, f->qualified_name, recv_qn); } if (obj_type->kind == CBM_TYPE_UNION) { int matches = 0; - const CBMRegisteredFunc* hit = NULL; - const char* hit_recv = NULL; + const CBMRegisteredFunc *hit = NULL; + const char *hit_recv = NULL; for (int i = 0; i < obj_type->data.union_type.count; i++) { - const CBMType* m = obj_type->data.union_type.members[i]; - if (!m || m->kind != CBM_TYPE_NAMED) continue; - const CBMRegisteredFunc* f = py_lookup_attribute(ctx, - m->data.named.qualified_name, attr_name); + const CBMType *m = obj_type->data.union_type.members[i]; + if (!m || m->kind != CBM_TYPE_NAMED) + continue; + const CBMRegisteredFunc *f = + py_lookup_attribute(ctx, m->data.named.qualified_name, attr_name); if (f) { matches++; hit = f; @@ -782,23 +876,28 @@ static const CBMType* py_eval_expr_type(PyLSPContext* ctx, TSNode node) { } // Field fallback for UNION: same single-match heuristic. int field_matches = 0; - const CBMType* field_hit = NULL; + const CBMType *field_hit = NULL; for (int i = 0; i < obj_type->data.union_type.count; i++) { - const CBMType* m = obj_type->data.union_type.members[i]; - if (!m || m->kind != CBM_TYPE_NAMED) continue; - const CBMType* fld = py_lookup_field(ctx, - m->data.named.qualified_name, attr_name); - if (fld) { field_matches++; field_hit = fld; } + const CBMType *m = obj_type->data.union_type.members[i]; + if (!m || m->kind != CBM_TYPE_NAMED) + continue; + const CBMType *fld = py_lookup_field(ctx, m->data.named.qualified_name, attr_name); + if (fld) { + field_matches++; + field_hit = fld; + } } - if (field_matches == 1 && field_hit) return field_hit; + if (field_matches == 1 && field_hit) + return field_hit; } return cbm_type_unknown(); } if (strcmp(k, "call") == 0) { TSNode fn = ts_node_child_by_field_name(node, "function", 8); - if (ts_node_is_null(fn)) return cbm_type_unknown(); - const char* fk = ts_node_type(fn); + if (ts_node_is_null(fn)) + return cbm_type_unknown(); + const char *fk = ts_node_type(fn); // Container method special-cases: dict.items() / .keys() / .values(), // list.copy(), set.copy() etc. Without a constraint solver we can't @@ -808,32 +907,31 @@ static const CBMType* py_eval_expr_type(PyLSPContext* ctx, TSNode node) { TSNode obj = ts_node_child_by_field_name(fn, "object", 6); TSNode attr = ts_node_child_by_field_name(fn, "attribute", 9); if (!ts_node_is_null(obj) && !ts_node_is_null(attr)) { - const CBMType* obj_type = py_eval_expr_type(ctx, obj); - char* mname = py_node_text(ctx, attr); + const CBMType *obj_type = py_eval_expr_type(ctx, obj); + char *mname = py_node_text(ctx, attr); if (mname && obj_type && obj_type->kind == CBM_TYPE_TEMPLATE) { - const char* tname = obj_type->data.template_type.template_name; - const CBMType** args = obj_type->data.template_type.template_args; + const char *tname = obj_type->data.template_type.template_name; + const CBMType **args = obj_type->data.template_type.template_args; int n = obj_type->data.template_type.arg_count; - bool is_dict_like = tname && (strcmp(tname, "dict") == 0 || - strcmp(tname, "Mapping") == 0 || - strcmp(tname, "MutableMapping") == 0 || - strcmp(tname, "defaultdict") == 0 || - strcmp(tname, "OrderedDict") == 0); - bool is_list_like = tname && (strcmp(tname, "list") == 0 || - strcmp(tname, "set") == 0 || - strcmp(tname, "frozenset") == 0 || - strcmp(tname, "deque") == 0); + bool is_dict_like = + tname && + (strcmp(tname, "dict") == 0 || strcmp(tname, "Mapping") == 0 || + strcmp(tname, "MutableMapping") == 0 || + strcmp(tname, "defaultdict") == 0 || strcmp(tname, "OrderedDict") == 0); + bool is_list_like = + tname && (strcmp(tname, "list") == 0 || strcmp(tname, "set") == 0 || + strcmp(tname, "frozenset") == 0 || strcmp(tname, "deque") == 0); if (is_dict_like && args && n >= 2) { if (strcmp(mname, "items") == 0) { - const CBMType* pair[3] = { args[0], args[1], NULL }; + const CBMType *pair[3] = {args[0], args[1], NULL}; return cbm_type_template(ctx->arena, "ItemsView", pair, 2); } if (strcmp(mname, "keys") == 0) { - const CBMType* k1[2] = { args[0], NULL }; + const CBMType *k1[2] = {args[0], NULL}; return cbm_type_template(ctx->arena, "KeysView", k1, 1); } if (strcmp(mname, "values") == 0) { - const CBMType* v1[2] = { args[1], NULL }; + const CBMType *v1[2] = {args[1], NULL}; return cbm_type_template(ctx->arena, "ValuesView", v1, 1); } if (strcmp(mname, "get") == 0) { @@ -844,12 +942,11 @@ static const CBMType* py_eval_expr_type(PyLSPContext* ctx, TSNode node) { return args[1]; } if (strcmp(mname, "copy") == 0) { - return obj_type; // dict[K, V] + return obj_type; // dict[K, V] } } if (is_list_like && args && n >= 1) { - if (strcmp(mname, "copy") == 0 || - strcmp(mname, "__iter__") == 0) { + if (strcmp(mname, "copy") == 0 || strcmp(mname, "__iter__") == 0) { return obj_type; } if (strcmp(mname, "pop") == 0) { @@ -866,7 +963,7 @@ static const CBMType* py_eval_expr_type(PyLSPContext* ctx, TSNode node) { bool is_cast = false; bool is_assert_type = false; if (strcmp(fk, "identifier") == 0) { - char* nm = py_node_text(ctx, fn); + char *nm = py_node_text(ctx, fn); if (nm) { is_cast = strcmp(nm, "cast") == 0; is_assert_type = strcmp(nm, "assert_type") == 0; @@ -876,8 +973,8 @@ static const CBMType* py_eval_expr_type(PyLSPContext* ctx, TSNode node) { TSNode aattr = ts_node_child_by_field_name(fn, "attribute", 9); if (!ts_node_is_null(aobj) && !ts_node_is_null(aattr) && strcmp(ts_node_type(aobj), "identifier") == 0) { - char* mod = py_node_text(ctx, aobj); - char* nm = py_node_text(ctx, aattr); + char *mod = py_node_text(ctx, aobj); + char *nm = py_node_text(ctx, aattr); if (mod && nm && strcmp(mod, "typing") == 0) { is_cast = strcmp(nm, "cast") == 0; is_assert_type = strcmp(nm, "assert_type") == 0; @@ -889,8 +986,9 @@ static const CBMType* py_eval_expr_type(PyLSPContext* ctx, TSNode node) { if (!ts_node_is_null(args) && ts_node_named_child_count(args) >= 2) { if (is_cast) { TSNode type_arg = ts_node_named_child(args, 0); - char* type_text = py_node_text(ctx, type_arg); - if (type_text) return py_resolve_annotation(ctx, type_text); + char *type_text = py_node_text(ctx, type_arg); + if (type_text) + return py_resolve_annotation(ctx, type_text); } else { // assert_type(x, T) returns x's type unchanged (it's a no-op). TSNode val_arg = ts_node_named_child(args, 0); @@ -900,40 +998,39 @@ static const CBMType* py_eval_expr_type(PyLSPContext* ctx, TSNode node) { } if (strcmp(fk, "identifier") == 0) { - char* fname = py_node_text(ctx, fn); - if (!fname) return cbm_type_unknown(); + char *fname = py_node_text(ctx, fn); + if (!fname) + return cbm_type_unknown(); // next(iter) — returns the iterable's element type. if (strcmp(fname, "next") == 0) { TSNode args = ts_node_child_by_field_name(node, "arguments", 9); - if (!ts_node_is_null(args) && - ts_node_named_child_count(args) >= 1) { - const CBMType* iter_type = py_eval_expr_type(ctx, - ts_node_named_child(args, 0)); - const CBMType* elem = py_iterable_element_type(ctx, iter_type); - if (elem && !cbm_type_is_unknown(elem)) return elem; + if (!ts_node_is_null(args) && ts_node_named_child_count(args) >= 1) { + const CBMType *iter_type = py_eval_expr_type(ctx, ts_node_named_child(args, 0)); + const CBMType *elem = py_iterable_element_type(ctx, iter_type); + if (elem && !cbm_type_is_unknown(elem)) + return elem; } } // iter(x) -> Iterator[element_of(x)] (best-effort: same as x). if (strcmp(fname, "iter") == 0) { TSNode args = ts_node_child_by_field_name(node, "arguments", 9); - if (!ts_node_is_null(args) && - ts_node_named_child_count(args) >= 1) { + if (!ts_node_is_null(args) && ts_node_named_child_count(args) >= 1) { return py_eval_expr_type(ctx, ts_node_named_child(args, 0)); } } // Constructor call: ClassName() returns NAMED(ClassName). - const CBMType* in_scope = cbm_scope_lookup(ctx->current_scope, fname); + const CBMType *in_scope = cbm_scope_lookup(ctx->current_scope, fname); if (!cbm_type_is_unknown(in_scope)) { if (in_scope->kind == CBM_TYPE_NAMED) { - const CBMRegisteredType* rt = cbm_registry_lookup_type(ctx->registry, - in_scope->data.named.qualified_name); - if (rt) return in_scope; // NAMED(ClassName) = instance type + const CBMRegisteredType *rt = cbm_registry_lookup_type( + ctx->registry, in_scope->data.named.qualified_name); + if (rt) + return in_scope; // NAMED(ClassName) = instance type } // CALLABLE in scope -> calling it returns its return type. if (in_scope->kind == CBM_TYPE_CALLABLE) { - return in_scope->data.callable.return_type - ? in_scope->data.callable.return_type - : cbm_type_unknown(); + return in_scope->data.callable.return_type ? in_scope->data.callable.return_type + : cbm_type_unknown(); } // TEMPLATE("Callable", [..., R]) — last template arg is the // return type; calling such a value yields R. @@ -946,27 +1043,29 @@ static const CBMType* py_eval_expr_type(PyLSPContext* ctx, TSNode node) { } } // Module-local function call. - const CBMRegisteredFunc* f = cbm_registry_lookup_symbol(ctx->registry, - ctx->module_qn, fname); - if (f) return py_func_return_type(ctx, f->qualified_name); + const CBMRegisteredFunc *f = + cbm_registry_lookup_symbol(ctx->registry, ctx->module_qn, fname); + if (f) + return py_func_return_type(ctx, f->qualified_name); // Builtins fallback: range / len / list / str / int / etc. f = cbm_registry_lookup_symbol(ctx->registry, "builtins", fname); - if (f) return py_func_return_type(ctx, f->qualified_name); + if (f) + return py_func_return_type(ctx, f->qualified_name); // Builtin class constructor: list() / dict() / set() / str() / ... - const CBMRegisteredType* rt = cbm_registry_lookup_type(ctx->registry, - cbm_arena_sprintf(ctx->arena, "builtins.%s", fname)); - if (rt) return cbm_type_builtin(ctx->arena, fname); + const CBMRegisteredType *rt = cbm_registry_lookup_type( + ctx->registry, cbm_arena_sprintf(ctx->arena, "builtins.%s", fname)); + if (rt) + return cbm_type_builtin(ctx->arena, fname); } // factory()() / (lambda x: x)(...) / etc — evaluate the function // expression's type, and if it's a callable, return its return. if (strcmp(fk, "call") == 0 || strcmp(fk, "parenthesized_expression") == 0 || strcmp(fk, "lambda") == 0) { - const CBMType* fn_type = py_eval_expr_type(ctx, fn); + const CBMType *fn_type = py_eval_expr_type(ctx, fn); if (fn_type && fn_type->kind == CBM_TYPE_CALLABLE) { - return fn_type->data.callable.return_type - ? fn_type->data.callable.return_type - : cbm_type_unknown(); + return fn_type->data.callable.return_type ? fn_type->data.callable.return_type + : cbm_type_unknown(); } if (fn_type && fn_type->kind == CBM_TYPE_TEMPLATE && fn_type->data.template_type.template_name && @@ -980,22 +1079,26 @@ static const CBMType* py_eval_expr_type(PyLSPContext* ctx, TSNode node) { if (strcmp(fk, "attribute") == 0) { TSNode obj = ts_node_child_by_field_name(fn, "object", 6); TSNode attr = ts_node_child_by_field_name(fn, "attribute", 9); - if (ts_node_is_null(obj) || ts_node_is_null(attr)) return cbm_type_unknown(); - const CBMType* obj_type = py_eval_expr_type(ctx, obj); - char* attr_name = py_node_text(ctx, attr); - if (!attr_name || !obj_type) return cbm_type_unknown(); + if (ts_node_is_null(obj) || ts_node_is_null(attr)) + return cbm_type_unknown(); + const CBMType *obj_type = py_eval_expr_type(ctx, obj); + char *attr_name = py_node_text(ctx, attr); + if (!attr_name || !obj_type) + return cbm_type_unknown(); if (obj_type->kind == CBM_TYPE_MODULE) { - const char* mod = obj_type->data.module.module_qn; - const CBMRegisteredFunc* f = cbm_registry_lookup_symbol(ctx->registry, - mod, attr_name); - if (f) return py_func_return_type(ctx, f->qualified_name); + const char *mod = obj_type->data.module.module_qn; + const CBMRegisteredFunc *f = + cbm_registry_lookup_symbol(ctx->registry, mod, attr_name); + if (f) + return py_func_return_type(ctx, f->qualified_name); } if (obj_type->kind == CBM_TYPE_NAMED) { - const CBMRegisteredFunc* f = py_lookup_attribute(ctx, - obj_type->data.named.qualified_name, attr_name); - if (f) return py_func_return_type_recv(ctx, f->qualified_name, - obj_type->data.named.qualified_name); + const CBMRegisteredFunc *f = + py_lookup_attribute(ctx, obj_type->data.named.qualified_name, attr_name); + if (f) + return py_func_return_type_recv(ctx, f->qualified_name, + obj_type->data.named.qualified_name); } } return cbm_type_unknown(); @@ -1004,42 +1107,54 @@ static const CBMType* py_eval_expr_type(PyLSPContext* ctx, TSNode node) { if (strcmp(k, "binary_operator") == 0) { TSNode left = ts_node_child_by_field_name(node, "left", 4); TSNode op = ts_node_child_by_field_name(node, "operator", 8); - const CBMType* lt = py_eval_expr_type(ctx, left); + const CBMType *lt = py_eval_expr_type(ctx, left); // Try the operator's dunder method on left's type. Falls back to // left-operand type when registry has no info (most common: int+int, // str+str, list+list — typing-by-left is correct in those cases). if (lt && lt->kind == CBM_TYPE_NAMED && !ts_node_is_null(op)) { - char* op_text = py_node_text(ctx, op); - const char* dunder = NULL; + char *op_text = py_node_text(ctx, op); + const char *dunder = NULL; if (op_text) { - if (strcmp(op_text, "+") == 0) dunder = "__add__"; - else if (strcmp(op_text, "-") == 0) dunder = "__sub__"; - else if (strcmp(op_text, "*") == 0) dunder = "__mul__"; - else if (strcmp(op_text, "/") == 0) dunder = "__truediv__"; - else if (strcmp(op_text, "//") == 0) dunder = "__floordiv__"; - else if (strcmp(op_text, "%") == 0) dunder = "__mod__"; - else if (strcmp(op_text, "**") == 0) dunder = "__pow__"; - else if (strcmp(op_text, "<<") == 0) dunder = "__lshift__"; - else if (strcmp(op_text, ">>") == 0) dunder = "__rshift__"; - else if (strcmp(op_text, "&") == 0) dunder = "__and__"; - else if (strcmp(op_text, "|") == 0) dunder = "__or__"; - else if (strcmp(op_text, "^") == 0) dunder = "__xor__"; - else if (strcmp(op_text, "@") == 0) dunder = "__matmul__"; + if (strcmp(op_text, "+") == 0) + dunder = "__add__"; + else if (strcmp(op_text, "-") == 0) + dunder = "__sub__"; + else if (strcmp(op_text, "*") == 0) + dunder = "__mul__"; + else if (strcmp(op_text, "/") == 0) + dunder = "__truediv__"; + else if (strcmp(op_text, "//") == 0) + dunder = "__floordiv__"; + else if (strcmp(op_text, "%") == 0) + dunder = "__mod__"; + else if (strcmp(op_text, "**") == 0) + dunder = "__pow__"; + else if (strcmp(op_text, "<<") == 0) + dunder = "__lshift__"; + else if (strcmp(op_text, ">>") == 0) + dunder = "__rshift__"; + else if (strcmp(op_text, "&") == 0) + dunder = "__and__"; + else if (strcmp(op_text, "|") == 0) + dunder = "__or__"; + else if (strcmp(op_text, "^") == 0) + dunder = "__xor__"; + else if (strcmp(op_text, "@") == 0) + dunder = "__matmul__"; } if (dunder) { - const CBMRegisteredFunc* f = py_lookup_attribute(ctx, - lt->data.named.qualified_name, dunder); + const CBMRegisteredFunc *f = + py_lookup_attribute(ctx, lt->data.named.qualified_name, dunder); if (f) { return py_func_return_type_recv(ctx, f->qualified_name, - lt->data.named.qualified_name); + lt->data.named.qualified_name); } } } return lt ? lt : cbm_type_unknown(); } - if (strcmp(k, "comparison_operator") == 0 || - strcmp(k, "boolean_operator") == 0 || + if (strcmp(k, "comparison_operator") == 0 || strcmp(k, "boolean_operator") == 0 || strcmp(k, "not_operator") == 0) { return cbm_type_builtin(ctx->arena, "bool"); } @@ -1048,9 +1163,9 @@ static const CBMType* py_eval_expr_type(PyLSPContext* ctx, TSNode node) { // a if cond else b — return union of a, b types uint32_t nc = ts_node_named_child_count(node); if (nc >= 2) { - const CBMType* a = py_eval_expr_type(ctx, ts_node_named_child(node, 0)); - const CBMType* b = py_eval_expr_type(ctx, ts_node_named_child(node, 2)); - const CBMType* members[2] = {a, b}; + const CBMType *a = py_eval_expr_type(ctx, ts_node_named_child(node, 0)); + const CBMType *b = py_eval_expr_type(ctx, ts_node_named_child(node, 2)); + const CBMType *members[2] = {a, b}; return cbm_type_union(ctx->arena, members, 2); } } @@ -1077,8 +1192,9 @@ static const CBMType* py_eval_expr_type(PyLSPContext* ctx, TSNode node) { // key returns the field's type. if (strcmp(k, "subscript") == 0) { TSNode value = ts_node_child_by_field_name(node, "value", 5); - if (ts_node_is_null(value)) return cbm_type_unknown(); - const CBMType* container = py_eval_expr_type(ctx, value); + if (ts_node_is_null(value)) + return cbm_type_unknown(); + const CBMType *container = py_eval_expr_type(ctx, value); // TypedDict literal-key subscript: d["foo"] where d: TD and TD has // class-body annotation `foo: Foo`. Field is registered via @@ -1086,9 +1202,9 @@ static const CBMType* py_eval_expr_type(PyLSPContext* ctx, TSNode node) { if (container && container->kind == CBM_TYPE_NAMED) { TSNode sub = ts_node_child_by_field_name(node, "subscript", 9); if (!ts_node_is_null(sub)) { - const char* sk = ts_node_type(sub); + const char *sk = ts_node_type(sub); if (strcmp(sk, "string") == 0) { - char* lit = py_node_text(ctx, sub); + char *lit = py_node_text(ctx, sub); if (lit) { // Strip surrounding quotes. size_t llen = strlen(lit); @@ -1097,9 +1213,10 @@ static const CBMType* py_eval_expr_type(PyLSPContext* ctx, TSNode node) { lit[llen - 1] = '\0'; lit++; } - const CBMType* field = py_lookup_field(ctx, - container->data.named.qualified_name, lit); - if (field) return field; + const CBMType *field = + py_lookup_field(ctx, container->data.named.qualified_name, lit); + if (field) + return field; } } } @@ -1109,10 +1226,11 @@ static const CBMType* py_eval_expr_type(PyLSPContext* ctx, TSNode node) { if (!container || container->kind != CBM_TYPE_TEMPLATE) { return cbm_type_unknown(); } - const char* tname = container->data.template_type.template_name; - const CBMType** args = container->data.template_type.template_args; + const char *tname = container->data.template_type.template_name; + const CBMType **args = container->data.template_type.template_args; int n = container->data.template_type.arg_count; - if (!tname || !args || n <= 0) return cbm_type_unknown(); + if (!tname || !args || n <= 0) + return cbm_type_unknown(); // Slice subscript returns the same container type: // list[T][1:3] -> list[T]; str[1:3] -> str (handled via BUILTIN @@ -1126,7 +1244,8 @@ static const CBMType* py_eval_expr_type(PyLSPContext* ctx, TSNode node) { strcmp(tname, "MutableMapping") == 0 || strcmp(tname, "defaultdict") == 0 || strcmp(tname, "OrderedDict") == 0 || strcmp(tname, "ChainMap") == 0) { // Value type is the second template arg. - if (n >= 2 && args[1]) return args[1]; + if (n >= 2 && args[1]) + return args[1]; return cbm_type_unknown(); } if (strcmp(tname, "list") == 0 || strcmp(tname, "set") == 0 || @@ -1137,13 +1256,15 @@ static const CBMType* py_eval_expr_type(PyLSPContext* ctx, TSNode node) { } if (strcmp(tname, "tuple") == 0) { if (!ts_node_is_null(sub) && strcmp(ts_node_type(sub), "integer") == 0) { - char* idx_text = py_node_text(ctx, sub); + char *idx_text = py_node_text(ctx, sub); if (idx_text) { int idx = atoi(idx_text); - if (idx >= 0 && idx < n) return args[idx]; + if (idx >= 0 && idx < n) + return args[idx]; } } - if (n == 1) return args[0]; + if (n == 1) + return args[0]; return cbm_type_union(ctx->arena, args, n); } // Other generics: best-effort first arg. @@ -1155,30 +1276,31 @@ static const CBMType* py_eval_expr_type(PyLSPContext* ctx, TSNode node) { /* ── statement processing: bind from assignments, for-loops, with-as ──── */ -static void py_process_statement(PyLSPContext* ctx, TSNode node) { - if (!ctx || ts_node_is_null(node)) return; - const char* k = ts_node_type(node); +static void py_process_statement(PyLSPContext *ctx, TSNode node) { + if (!ctx || ts_node_is_null(node)) + return; + const char *k = ts_node_type(node); if (strcmp(k, "assignment") == 0) { TSNode left = ts_node_child_by_field_name(node, "left", 4); TSNode right = ts_node_child_by_field_name(node, "right", 5); TSNode ann = ts_node_child_by_field_name(node, "type", 4); - const CBMType* rhs_type = ts_node_is_null(right) - ? cbm_type_unknown() - : py_eval_expr_type(ctx, right); + const CBMType *rhs_type = + ts_node_is_null(right) ? cbm_type_unknown() : py_eval_expr_type(ctx, right); // Annotated assignment: x: T = expr — annotation wins. bool has_annotation = !ts_node_is_null(ann); if (has_annotation) { - char* ann_text = py_node_text(ctx, ann); + char *ann_text = py_node_text(ctx, ann); if (ann_text && ann_text[0]) { rhs_type = py_resolve_annotation(ctx, ann_text); } } - if (ts_node_is_null(left)) return; - const char* lk = ts_node_type(left); + if (ts_node_is_null(left)) + return; + const char *lk = ts_node_type(left); // Tuple/list pattern unpacking: a, b = f() / [a, b] = f() / // (a, b) = f(). Each LHS element binds to the corresponding // element of the RHS tuple. *rest binds the remaining elements @@ -1187,7 +1309,7 @@ static void py_process_statement(PyLSPContext* ctx, TSNode node) { strcmp(lk, "list_pattern") == 0 || strcmp(lk, "expression_list") == 0) { uint32_t lc = ts_node_named_child_count(left); // Collect RHS element types. - const CBMType* const* rhs_elems = NULL; + const CBMType *const *rhs_elems = NULL; int rhs_count = 0; if (rhs_type) { if (rhs_type->kind == CBM_TYPE_TUPLE) { @@ -1195,7 +1317,7 @@ static void py_process_statement(PyLSPContext* ctx, TSNode node) { rhs_count = rhs_type->data.tuple.count; } else if (rhs_type->kind == CBM_TYPE_TEMPLATE && rhs_type->data.template_type.template_name) { - const char* tn = rhs_type->data.template_type.template_name; + const char *tn = rhs_type->data.template_type.template_name; if (strcmp(tn, "tuple") == 0) { rhs_elems = rhs_type->data.template_type.template_args; rhs_count = rhs_type->data.template_type.arg_count; @@ -1205,29 +1327,30 @@ static void py_process_statement(PyLSPContext* ctx, TSNode node) { // Element type for star-rest binding (best-effort element of // the iterable; for a pure tuple use UNKNOWN since the rest // is heterogeneous). - const CBMType* elem_type = py_iterable_element_type(ctx, rhs_type); + const CBMType *elem_type = py_iterable_element_type(ctx, rhs_type); for (uint32_t i = 0; i < lc; i++) { TSNode tgt = ts_node_named_child(left, i); - if (ts_node_is_null(tgt)) continue; - const char* tk = ts_node_type(tgt); + if (ts_node_is_null(tgt)) + continue; + const char *tk = ts_node_type(tgt); bool is_rest = false; TSNode bind_target = tgt; - if (strcmp(tk, "list_splat_pattern") == 0 || - strcmp(tk, "list_splat") == 0) { + if (strcmp(tk, "list_splat_pattern") == 0 || strcmp(tk, "list_splat") == 0) { is_rest = true; if (ts_node_named_child_count(tgt) > 0) { bind_target = ts_node_named_child(tgt, 0); } } - if (strcmp(ts_node_type(bind_target), "identifier") != 0) continue; - char* nm = py_node_text(ctx, bind_target); - if (!nm) continue; - const CBMType* bind_type; + if (strcmp(ts_node_type(bind_target), "identifier") != 0) + continue; + char *nm = py_node_text(ctx, bind_target); + if (!nm) + continue; + const CBMType *bind_type; if (is_rest) { - bind_type = elem_type - ? cbm_type_template(ctx->arena, "list", &elem_type, 1) - : cbm_type_unknown(); + bind_type = elem_type ? cbm_type_template(ctx->arena, "list", &elem_type, 1) + : cbm_type_unknown(); } else if (rhs_elems && (int)i < rhs_count && rhs_elems[i]) { bind_type = rhs_elems[i]; } else { @@ -1238,18 +1361,16 @@ static void py_process_statement(PyLSPContext* ctx, TSNode node) { return; } if (strcmp(lk, "identifier") == 0) { - char* name = py_node_text(ctx, left); + char *name = py_node_text(ctx, left); if (name) { cbm_scope_bind(ctx->current_scope, name, rhs_type); // Lambda registry: `fn = lambda x: ...`. - if (!ts_node_is_null(right) && - strcmp(ts_node_type(right), "lambda") == 0) { + if (!ts_node_is_null(right) && strcmp(ts_node_type(right), "lambda") == 0) { py_register_lambda(ctx, name, right); } // Dict literal dispatch table: `funcs = {"a": foo, "b": bar}` // with all values being known function QNs. - if (!ts_node_is_null(right) && - strcmp(ts_node_type(right), "dictionary") == 0) { + if (!ts_node_is_null(right) && strcmp(ts_node_type(right), "dictionary") == 0) { py_register_dict_literal(ctx, name, right); } } @@ -1260,14 +1381,12 @@ static void py_process_statement(PyLSPContext* ctx, TSNode node) { TSNode obj = ts_node_child_by_field_name(left, "object", 6); TSNode attr = ts_node_child_by_field_name(left, "attribute", 9); if (!ts_node_is_null(obj) && !ts_node_is_null(attr) && - strcmp(ts_node_type(obj), "identifier") == 0 && - ctx->enclosing_class_qn) { - char* obj_name = py_node_text(ctx, obj); - char* attr_name = py_node_text(ctx, attr); + strcmp(ts_node_type(obj), "identifier") == 0 && ctx->enclosing_class_qn) { + char *obj_name = py_node_text(ctx, obj); + char *attr_name = py_node_text(ctx, attr); if (obj_name && attr_name && (strcmp(obj_name, "self") == 0 || strcmp(obj_name, "cls") == 0)) { - py_register_instance_field(ctx, ctx->enclosing_class_qn, - attr_name, rhs_type); + py_register_instance_field(ctx, ctx->enclosing_class_qn, attr_name, rhs_type); } } } @@ -1277,23 +1396,22 @@ static void py_process_statement(PyLSPContext* ctx, TSNode node) { if (strcmp(k, "for_statement") == 0) { TSNode left = ts_node_child_by_field_name(node, "left", 4); TSNode right = ts_node_child_by_field_name(node, "right", 5); - const CBMType* elem_type = cbm_type_unknown(); + const CBMType *elem_type = cbm_type_unknown(); if (!ts_node_is_null(right)) { - const CBMType* iter_type = py_eval_expr_type(ctx, right); + const CBMType *iter_type = py_eval_expr_type(ctx, right); elem_type = py_iterable_element_type(ctx, iter_type); // For async for: try unwrapping AsyncIterator[T] / AsyncIterable[T] // / AsyncGenerator[T, S] — already handled in py_iterable_element_type. // If the iterable came from an explicit __aiter__ / __anext__ // call, look up __anext__'s return type. - if (cbm_type_is_unknown(elem_type) && iter_type && - iter_type->kind == CBM_TYPE_NAMED) { - const CBMRegisteredFunc* anext = py_lookup_attribute(ctx, - iter_type->data.named.qualified_name, "__anext__"); + if (cbm_type_is_unknown(elem_type) && iter_type && iter_type->kind == CBM_TYPE_NAMED) { + const CBMRegisteredFunc *anext = + py_lookup_attribute(ctx, iter_type->data.named.qualified_name, "__anext__"); if (anext) { - const CBMType* ret = py_func_return_type_recv(ctx, - anext->qualified_name, - iter_type->data.named.qualified_name); - if (ret && !cbm_type_is_unknown(ret)) elem_type = ret; + const CBMType *ret = py_func_return_type_recv( + ctx, anext->qualified_name, iter_type->data.named.qualified_name); + if (ret && !cbm_type_is_unknown(ret)) + elem_type = ret; } } } @@ -1313,11 +1431,13 @@ static void py_process_statement(PyLSPContext* ctx, TSNode node) { uint32_t nc = ts_node_named_child_count(node); for (uint32_t i = 0; i < nc; i++) { TSNode child = ts_node_named_child(node, i); - if (strcmp(ts_node_type(child), "with_clause") != 0) continue; + if (strcmp(ts_node_type(child), "with_clause") != 0) + continue; uint32_t cn = ts_node_named_child_count(child); for (uint32_t j = 0; j < cn; j++) { TSNode item = ts_node_named_child(child, j); - if (strcmp(ts_node_type(item), "with_item") != 0) continue; + if (strcmp(ts_node_type(item), "with_item") != 0) + continue; // Try field lookup first; fall back to as_pattern walk. TSNode value = ts_node_child_by_field_name(item, "value", 5); @@ -1330,8 +1450,10 @@ static void py_process_statement(PyLSPContext* ctx, TSNode node) { TSNode c = ts_node_named_child(item, k2); if (strcmp(ts_node_type(c), "as_pattern") == 0) { uint32_t ac = ts_node_named_child_count(c); - if (ac >= 1) value = ts_node_named_child(c, 0); - if (ac >= 2) alias = ts_node_named_child(c, 1); + if (ac >= 1) + value = ts_node_named_child(c, 0); + if (ac >= 2) + alias = ts_node_named_child(c, 1); // Field-based lookup as another option. if (ts_node_is_null(alias)) { alias = ts_node_child_by_field_name(c, "alias", 5); @@ -1341,39 +1463,37 @@ static void py_process_statement(PyLSPContext* ctx, TSNode node) { } } - if (ts_node_is_null(alias) || - strcmp(ts_node_type(alias), "identifier") != 0) { + if (ts_node_is_null(alias) || strcmp(ts_node_type(alias), "identifier") != 0) { // alias may itself be an as_pattern_target wrapping an // identifier; walk through it. - if (!ts_node_is_null(alias) && - ts_node_named_child_count(alias) > 0) { + if (!ts_node_is_null(alias) && ts_node_named_child_count(alias) > 0) { TSNode inner = ts_node_named_child(alias, 0); if (strcmp(ts_node_type(inner), "identifier") == 0) { alias = inner; } } } - if (ts_node_is_null(alias) || - strcmp(ts_node_type(alias), "identifier") != 0) continue; - char* name = py_node_text(ctx, alias); - if (!name) continue; - const CBMType* cm_type = ts_node_is_null(value) - ? cbm_type_unknown() : py_eval_expr_type(ctx, value); - const CBMType* bind_type = cm_type; + if (ts_node_is_null(alias) || strcmp(ts_node_type(alias), "identifier") != 0) + continue; + char *name = py_node_text(ctx, alias); + if (!name) + continue; + const CBMType *cm_type = + ts_node_is_null(value) ? cbm_type_unknown() : py_eval_expr_type(ctx, value); + const CBMType *bind_type = cm_type; if (cm_type && cm_type->kind == CBM_TYPE_NAMED) { // Try __aenter__ first (async with), fall back to // __enter__ (sync with). __aenter__ is a coroutine // returning T; we treat the return as T directly. - const CBMRegisteredFunc* enter = py_lookup_attribute(ctx, - cm_type->data.named.qualified_name, "__aenter__"); + const CBMRegisteredFunc *enter = + py_lookup_attribute(ctx, cm_type->data.named.qualified_name, "__aenter__"); if (!enter) { - enter = py_lookup_attribute(ctx, - cm_type->data.named.qualified_name, "__enter__"); + enter = py_lookup_attribute(ctx, cm_type->data.named.qualified_name, + "__enter__"); } if (enter) { - const CBMType* ret = py_func_return_type_recv(ctx, - enter->qualified_name, - cm_type->data.named.qualified_name); + const CBMType *ret = py_func_return_type_recv( + ctx, enter->qualified_name, cm_type->data.named.qualified_name); if (ret && !cbm_type_is_unknown(ret)) { bind_type = ret; } @@ -1392,22 +1512,26 @@ static void py_process_statement(PyLSPContext* ctx, TSNode node) { uint32_t nc = ts_node_named_child_count(node); for (uint32_t i = 0; i < nc; i++) { TSNode clause = ts_node_named_child(node, i); - if (strcmp(ts_node_type(clause), "except_clause") != 0) continue; + if (strcmp(ts_node_type(clause), "except_clause") != 0) + continue; TSNode exc_type = {0}; TSNode exc_name = {0}; uint32_t ccn = ts_node_named_child_count(clause); for (uint32_t j = 0; j < ccn; j++) { TSNode c = ts_node_named_child(clause, j); - const char* ck = ts_node_type(c); - if (strcmp(ck, "block") == 0) continue; + const char *ck = ts_node_type(c); + if (strcmp(ck, "block") == 0) + continue; if (strcmp(ck, "as_pattern") == 0) { uint32_t ac = ts_node_named_child_count(c); - if (ac >= 1) exc_type = ts_node_named_child(c, 0); - if (ac >= 2) exc_name = ts_node_named_child(c, 1); + if (ac >= 1) + exc_type = ts_node_named_child(c, 0); + if (ac >= 2) + exc_name = ts_node_named_child(c, 1); // alias may be wrapped in as_pattern_target if (!ts_node_is_null(exc_name)) { - const char* nk = ts_node_type(exc_name); + const char *nk = ts_node_type(exc_name); if (strcmp(nk, "as_pattern_target") == 0 && ts_node_named_child_count(exc_name) > 0) { exc_name = ts_node_named_child(exc_name, 0); @@ -1415,19 +1539,18 @@ static void py_process_statement(PyLSPContext* ctx, TSNode node) { } continue; } - if (ts_node_is_null(exc_type)) exc_type = c; - else if (ts_node_is_null(exc_name) && - strcmp(ck, "identifier") == 0) { + if (ts_node_is_null(exc_type)) + exc_type = c; + else if (ts_node_is_null(exc_name) && strcmp(ck, "identifier") == 0) { exc_name = c; } } if (!ts_node_is_null(exc_name) && !ts_node_is_null(exc_type) && strcmp(ts_node_type(exc_name), "identifier") == 0) { - char* nm = py_node_text(ctx, exc_name); - char* tn = py_node_text(ctx, exc_type); + char *nm = py_node_text(ctx, exc_name); + char *tn = py_node_text(ctx, exc_type); if (nm && tn) { - cbm_scope_bind(ctx->current_scope, nm, - py_resolve_annotation(ctx, tn)); + cbm_scope_bind(ctx->current_scope, nm, py_resolve_annotation(ctx, tn)); } } } @@ -1437,14 +1560,16 @@ static void py_process_statement(PyLSPContext* ctx, TSNode node) { /* ── Recursive walker: process statements + emit resolved_calls ── */ -static void py_emit_call_for(PyLSPContext* ctx, TSNode call_node) { +static void py_emit_call_for(PyLSPContext *ctx, TSNode call_node) { TSNode fn = ts_node_child_by_field_name(call_node, "function", 8); - if (ts_node_is_null(fn)) return; - const char* fk = ts_node_type(fn); + if (ts_node_is_null(fn)) + return; + const char *fk = ts_node_type(fn); if (strcmp(fk, "identifier") == 0) { - char* fname = py_node_text(ctx, fn); - if (!fname) return; + char *fname = py_node_text(ctx, fn); + if (!fname) + return; // Lambda invocation: `fn(args)` where fn was assigned a lambda. // Walk the lambda body for any nested call sites with the params // bound to the call's arg types. Resolved calls get caller_qn @@ -1454,7 +1579,7 @@ static void py_emit_call_for(PyLSPContext* ctx, TSNode call_node) { TSNode params = ts_node_child_by_field_name(lambda_node, "parameters", 10); TSNode body = ts_node_child_by_field_name(lambda_node, "body", 4); TSNode args = ts_node_child_by_field_name(call_node, "arguments", 9); - CBMScope* saved = ctx->current_scope; + CBMScope *saved = ctx->current_scope; ctx->current_scope = cbm_scope_push(ctx->arena, ctx->current_scope); // Bind each lambda param to the call-site arg's type. if (!ts_node_is_null(params) && !ts_node_is_null(args)) { @@ -1463,7 +1588,7 @@ static void py_emit_call_for(PyLSPContext* ctx, TSNode call_node) { for (uint32_t pi = 0; pi < pn && pi < an; pi++) { TSNode p = ts_node_named_child(params, pi); TSNode a = ts_node_named_child(args, pi); - const char* pname = NULL; + const char *pname = NULL; if (strcmp(ts_node_type(p), "identifier") == 0) { pname = py_node_text(ctx, p); } else if (strcmp(ts_node_type(p), "default_parameter") == 0 || @@ -1475,16 +1600,16 @@ static void py_emit_call_for(PyLSPContext* ctx, TSNode call_node) { } } if (pname) { - const CBMType* arg_type = py_eval_expr_type(ctx, a); + const CBMType *arg_type = py_eval_expr_type(ctx, a); cbm_scope_bind(ctx->current_scope, pname, arg_type); } } } // Set enclosing func to a synthetic so emissions // attribute correctly (find_resolved by "" works). - const char* prev_func = ctx->enclosing_func_qn; - ctx->enclosing_func_qn = cbm_arena_sprintf(ctx->arena, - "%s.", prev_func ? prev_func : ctx->module_qn); + const char *prev_func = ctx->enclosing_func_qn; + ctx->enclosing_func_qn = cbm_arena_sprintf(ctx->arena, "%s.", + prev_func ? prev_func : ctx->module_qn); if (!ts_node_is_null(body)) { py_resolve_calls_in(ctx, body); } @@ -1493,15 +1618,15 @@ static void py_emit_call_for(PyLSPContext* ctx, TSNode call_node) { return; } // Constructor call (ClassName()) - const CBMType* in_scope = cbm_scope_lookup(ctx->current_scope, fname); + const CBMType *in_scope = cbm_scope_lookup(ctx->current_scope, fname); if (!cbm_type_is_unknown(in_scope) && in_scope->kind == CBM_TYPE_NAMED) { - const char* qn = in_scope->data.named.qualified_name; + const char *qn = in_scope->data.named.qualified_name; py_emit_resolved_call(ctx, qn, "lsp_constructor", 0.85f); return; } // Module-local function - const CBMRegisteredFunc* f = cbm_registry_lookup_symbol(ctx->registry, - ctx->module_qn, fname); + const CBMRegisteredFunc *f = + cbm_registry_lookup_symbol(ctx->registry, ctx->module_qn, fname); if (f) { py_emit_resolved_call(ctx, f->qualified_name, "lsp_direct", 0.95f); return; @@ -1512,11 +1637,10 @@ static void py_emit_call_for(PyLSPContext* ctx, TSNode call_node) { py_emit_resolved_call(ctx, f->qualified_name, "lsp_builtin", 0.92f); return; } - const CBMRegisteredType* rt = cbm_registry_lookup_type(ctx->registry, - cbm_arena_sprintf(ctx->arena, "builtins.%s", fname)); + const CBMRegisteredType *rt = cbm_registry_lookup_type( + ctx->registry, cbm_arena_sprintf(ctx->arena, "builtins.%s", fname)); if (rt) { - py_emit_resolved_call(ctx, rt->qualified_name, - "lsp_builtin_constructor", 0.88f); + py_emit_resolved_call(ctx, rt->qualified_name, "lsp_builtin_constructor", 0.88f); return; } return; @@ -1530,10 +1654,10 @@ static void py_emit_call_for(PyLSPContext* ctx, TSNode call_node) { if (!ts_node_is_null(container) && !ts_node_is_null(key) && strcmp(ts_node_type(container), "identifier") == 0 && strcmp(ts_node_type(key), "string") == 0) { - char* var_name = py_node_text(ctx, container); - char* k_text = py_string_literal_value(ctx, key); + char *var_name = py_node_text(ctx, container); + char *k_text = py_string_literal_value(ctx, key); if (var_name && k_text) { - const char* tgt = py_lookup_dict_dispatch(ctx, var_name, k_text); + const char *tgt = py_lookup_dict_dispatch(ctx, var_name, k_text); if (tgt) { py_emit_resolved_call(ctx, tgt, "lsp_dict_dispatch", 0.86f); return; @@ -1545,39 +1669,37 @@ static void py_emit_call_for(PyLSPContext* ctx, TSNode call_node) { if (strcmp(fk, "attribute") == 0) { TSNode obj = ts_node_child_by_field_name(fn, "object", 6); TSNode attr = ts_node_child_by_field_name(fn, "attribute", 9); - if (ts_node_is_null(obj) || ts_node_is_null(attr)) return; - char* attr_name = py_node_text(ctx, attr); - if (!attr_name) return; + if (ts_node_is_null(obj) || ts_node_is_null(attr)) + return; + char *attr_name = py_node_text(ctx, attr); + if (!attr_name) + return; // super().method() — Python 3 super() returns a proxy bound to the // enclosing class's MRO. Resolve attr against the first base class // of the enclosing class (single-inheritance practical case). if (strcmp(ts_node_type(obj), "call") == 0) { TSNode super_fn = ts_node_child_by_field_name(obj, "function", 8); - if (!ts_node_is_null(super_fn) && - strcmp(ts_node_type(super_fn), "identifier") == 0) { - char* super_name = py_node_text(ctx, super_fn); - if (super_name && strcmp(super_name, "super") == 0 && - ctx->enclosing_class_qn) { - const CBMRegisteredType* enclosing = + if (!ts_node_is_null(super_fn) && strcmp(ts_node_type(super_fn), "identifier") == 0) { + char *super_name = py_node_text(ctx, super_fn); + if (super_name && strcmp(super_name, "super") == 0 && ctx->enclosing_class_qn) { + const CBMRegisteredType *enclosing = cbm_registry_lookup_type(ctx->registry, ctx->enclosing_class_qn); if (enclosing && enclosing->embedded_types) { for (int i = 0; enclosing->embedded_types[i]; i++) { - const CBMRegisteredFunc* f = py_lookup_attribute(ctx, - enclosing->embedded_types[i], attr_name); + const CBMRegisteredFunc *f = + py_lookup_attribute(ctx, enclosing->embedded_types[i], attr_name); if (f) { - py_emit_resolved_call(ctx, f->qualified_name, - "lsp_super", 0.88f); + py_emit_resolved_call(ctx, f->qualified_name, "lsp_super", 0.88f); return; } // Special case: super().__init__ — most parent // classes don't register __init__ with a return, // but we still want to emit the constructor edge. if (strcmp(attr_name, "__init__") == 0) { - const char* init_qn = cbm_arena_sprintf(ctx->arena, - "%s.__init__", enclosing->embedded_types[i]); - py_emit_resolved_call(ctx, init_qn, - "lsp_super_init", 0.85f); + const char *init_qn = cbm_arena_sprintf( + ctx->arena, "%s.__init__", enclosing->embedded_types[i]); + py_emit_resolved_call(ctx, init_qn, "lsp_super_init", 0.85f); return; } } @@ -1586,25 +1708,25 @@ static void py_emit_call_for(PyLSPContext* ctx, TSNode call_node) { } } - const CBMType* obj_type = py_eval_expr_type(ctx, obj); - if (!obj_type) return; + const CBMType *obj_type = py_eval_expr_type(ctx, obj); + if (!obj_type) + return; if (obj_type->kind == CBM_TYPE_MODULE) { - const char* mod = obj_type->data.module.module_qn; - const CBMRegisteredFunc* f = cbm_registry_lookup_symbol(ctx->registry, - mod, attr_name); + const char *mod = obj_type->data.module.module_qn; + const CBMRegisteredFunc *f = cbm_registry_lookup_symbol(ctx->registry, mod, attr_name); if (f) { py_emit_resolved_call(ctx, f->qualified_name, "lsp_module_attr", 0.92f); return; } // Best-effort: emit "module.attr" QN — Phase 9 cross-file may fix up. - const char* qn = cbm_arena_sprintf(ctx->arena, "%s.%s", mod, attr_name); + const char *qn = cbm_arena_sprintf(ctx->arena, "%s.%s", mod, attr_name); py_emit_resolved_call(ctx, qn, "lsp_module_attr_unresolved", 0.55f); return; } if (obj_type->kind == CBM_TYPE_NAMED) { - const CBMRegisteredFunc* f = py_lookup_attribute(ctx, - obj_type->data.named.qualified_name, attr_name); + const CBMRegisteredFunc *f = + py_lookup_attribute(ctx, obj_type->data.named.qualified_name, attr_name); if (f) { py_emit_resolved_call(ctx, f->qualified_name, "lsp_method", 0.9f); return; @@ -1612,19 +1734,18 @@ static void py_emit_call_for(PyLSPContext* ctx, TSNode call_node) { } if (obj_type->kind == CBM_TYPE_BUILTIN && obj_type->data.builtin.name) { // Builtins -> "builtins." in typeshed. - const char* recv_qn = cbm_arena_sprintf(ctx->arena, "builtins.%s", - obj_type->data.builtin.name); - const CBMRegisteredFunc* f = py_lookup_attribute(ctx, recv_qn, attr_name); + const char *recv_qn = + cbm_arena_sprintf(ctx->arena, "builtins.%s", obj_type->data.builtin.name); + const CBMRegisteredFunc *f = py_lookup_attribute(ctx, recv_qn, attr_name); if (f) { py_emit_resolved_call(ctx, f->qualified_name, "lsp_builtin_method", 0.9f); return; } } - if (obj_type->kind == CBM_TYPE_TEMPLATE && - obj_type->data.template_type.template_name) { - const char* tname = obj_type->data.template_type.template_name; - const char* recv_qn = cbm_arena_sprintf(ctx->arena, "builtins.%s", tname); - const CBMRegisteredFunc* f = py_lookup_attribute(ctx, recv_qn, attr_name); + if (obj_type->kind == CBM_TYPE_TEMPLATE && obj_type->data.template_type.template_name) { + const char *tname = obj_type->data.template_type.template_name; + const char *recv_qn = cbm_arena_sprintf(ctx->arena, "builtins.%s", tname); + const CBMRegisteredFunc *f = py_lookup_attribute(ctx, recv_qn, attr_name); if (!f) { f = py_lookup_attribute(ctx, tname, attr_name); } @@ -1636,16 +1757,22 @@ static void py_emit_call_for(PyLSPContext* ctx, TSNode call_node) { if (obj_type->kind == CBM_TYPE_UNION) { // Try each non-None member; if exactly one matches, emit. int matches = 0; - const CBMRegisteredFunc* hit = NULL; + const CBMRegisteredFunc *hit = NULL; for (int i = 0; i < obj_type->data.union_type.count; i++) { - const CBMType* m = obj_type->data.union_type.members[i]; - if (!m) continue; + const CBMType *m = obj_type->data.union_type.members[i]; + if (!m) + continue; if (m->kind == CBM_TYPE_BUILTIN && m->data.builtin.name && - strcmp(m->data.builtin.name, "None") == 0) continue; - if (m->kind != CBM_TYPE_NAMED) continue; - const CBMRegisteredFunc* f = py_lookup_attribute(ctx, - m->data.named.qualified_name, attr_name); - if (f) { matches++; hit = f; } + strcmp(m->data.builtin.name, "None") == 0) + continue; + if (m->kind != CBM_TYPE_NAMED) + continue; + const CBMRegisteredFunc *f = + py_lookup_attribute(ctx, m->data.named.qualified_name, attr_name); + if (f) { + matches++; + hit = f; + } } if (matches == 1 && hit) { py_emit_resolved_call(ctx, hit->qualified_name, "lsp_method_union", 0.85f); @@ -1658,24 +1785,31 @@ static void py_emit_call_for(PyLSPContext* ctx, TSNode call_node) { /* Detect `isinstance(x, T)` / `isinstance(x, (T1, T2))` and return * (var_name, narrowed_type). Returns true on match. */ -static bool py_match_isinstance(PyLSPContext* ctx, TSNode call_node, - char** out_name, const CBMType** out_type) { - if (ts_node_is_null(call_node)) return false; - if (strcmp(ts_node_type(call_node), "call") != 0) return false; +static bool py_match_isinstance(PyLSPContext *ctx, TSNode call_node, char **out_name, + const CBMType **out_type) { + if (ts_node_is_null(call_node)) + return false; + if (strcmp(ts_node_type(call_node), "call") != 0) + return false; TSNode fn = ts_node_child_by_field_name(call_node, "function", 8); - if (ts_node_is_null(fn) || strcmp(ts_node_type(fn), "identifier") != 0) return false; - char* fname = py_node_text(ctx, fn); - if (!fname || strcmp(fname, "isinstance") != 0) return false; + if (ts_node_is_null(fn) || strcmp(ts_node_type(fn), "identifier") != 0) + return false; + char *fname = py_node_text(ctx, fn); + if (!fname || strcmp(fname, "isinstance") != 0) + return false; TSNode args = ts_node_child_by_field_name(call_node, "arguments", 9); - if (ts_node_is_null(args) || ts_node_named_child_count(args) < 2) return false; + if (ts_node_is_null(args) || ts_node_named_child_count(args) < 2) + return false; TSNode var_node = ts_node_named_child(args, 0); TSNode type_node = ts_node_named_child(args, 1); if (ts_node_is_null(var_node) || strcmp(ts_node_type(var_node), "identifier") != 0) return false; - char* name = py_node_text(ctx, var_node); - if (!name) return false; - char* tname = py_node_text(ctx, type_node); - if (!tname) return false; + char *name = py_node_text(ctx, var_node); + if (!name) + return false; + char *tname = py_node_text(ctx, type_node); + if (!tname) + return false; *out_name = name; *out_type = py_resolve_annotation(ctx, tname); return true; @@ -1684,9 +1818,10 @@ static bool py_match_isinstance(PyLSPContext* ctx, TSNode call_node, /* Walk through parenthesized expressions and walrus expressions to find * the underlying identifier of an `is None` operand. Returns the * identifier's text or NULL. */ -static char* py_underlying_ident(PyLSPContext* ctx, TSNode node) { - if (ts_node_is_null(node)) return NULL; - const char* k = ts_node_type(node); +static char *py_underlying_ident(PyLSPContext *ctx, TSNode node) { + if (ts_node_is_null(node)) + return NULL; + const char *k = ts_node_type(node); if (strcmp(k, "identifier") == 0) { return py_node_text(ctx, node); } @@ -1695,8 +1830,7 @@ static char* py_underlying_ident(PyLSPContext* ctx, TSNode node) { return py_underlying_ident(ctx, ts_node_named_child(node, 0)); } } - if (strcmp(k, "named_expression") == 0 || - strcmp(k, "assignment_expression") == 0) { + if (strcmp(k, "named_expression") == 0 || strcmp(k, "assignment_expression") == 0) { TSNode left = ts_node_child_by_field_name(node, "name", 4); if (ts_node_is_null(left)) { left = ts_node_child_by_field_name(node, "left", 4); @@ -1712,9 +1846,10 @@ static char* py_underlying_ident(PyLSPContext* ctx, TSNode node) { * through parens and walrus to find the underlying identifier. * Returns 1 for "x is not None" (positive narrow), -1 for "x is None" * (negative), 0 otherwise. */ -static int py_match_is_none(PyLSPContext* ctx, TSNode test_node, char** out_name) { - if (ts_node_is_null(test_node)) return 0; - const char* k = ts_node_type(test_node); +static int py_match_is_none(PyLSPContext *ctx, TSNode test_node, char **out_name) { + if (ts_node_is_null(test_node)) + return 0; + const char *k = ts_node_type(test_node); if (strcmp(k, "comparison_operator") == 0) { uint32_t cn = ts_node_child_count(test_node); TSNode left = {0}, right = {0}; @@ -1723,30 +1858,43 @@ static int py_match_is_none(PyLSPContext* ctx, TSNode test_node, char** out_name bool first_named = true; for (uint32_t i = 0; i < cn; i++) { TSNode c = ts_node_child(test_node, i); - if (ts_node_is_null(c)) continue; + if (ts_node_is_null(c)) + continue; if (ts_node_is_named(c)) { - if (first_named) { left = c; first_named = false; } - else { right = c; } + if (first_named) { + left = c; + first_named = false; + } else { + right = c; + } continue; } // Anonymous operator token. Tree-sitter Python emits "is" or // "is not" as a single token via the sym__is_not literal. - char* tok = py_node_text(ctx, c); - if (!tok) continue; - if (strcmp(tok, "is") == 0) is_is = true; - else if (strcmp(tok, "is not") == 0) { is_is = true; is_not = true; } + char *tok = py_node_text(ctx, c); + if (!tok) + continue; + if (strcmp(tok, "is") == 0) + is_is = true; + else if (strcmp(tok, "is not") == 0) { + is_is = true; + is_not = true; + } } - if (!is_is || ts_node_is_null(left) || ts_node_is_null(right)) return 0; - char* l_text = py_node_text(ctx, left); - char* r_text = py_node_text(ctx, right); - if (!l_text || !r_text) return 0; - char* var_name = NULL; + if (!is_is || ts_node_is_null(left) || ts_node_is_null(right)) + return 0; + char *l_text = py_node_text(ctx, left); + char *r_text = py_node_text(ctx, right); + if (!l_text || !r_text) + return 0; + char *var_name = NULL; if (strcmp(r_text, "None") == 0) { var_name = py_underlying_ident(ctx, left); } else if (strcmp(l_text, "None") == 0) { var_name = py_underlying_ident(ctx, right); } - if (!var_name) return 0; + if (!var_name) + return 0; *out_name = var_name; return is_not ? 1 : -1; } @@ -1755,31 +1903,36 @@ static int py_match_is_none(PyLSPContext* ctx, TSNode test_node, char** out_name /* Compute the non-None component of a UNION type. If t is Optional[X] * (= Union[X, None]), return X; otherwise return t unchanged. */ -static const CBMType* py_strip_none(PyLSPContext* ctx, const CBMType* t) { - if (!t || t->kind != CBM_TYPE_UNION) return t; +static const CBMType *py_strip_none(PyLSPContext *ctx, const CBMType *t) { + if (!t || t->kind != CBM_TYPE_UNION) + return t; int n = t->data.union_type.count; int retained = 0; - const CBMType** kept = (const CBMType**)cbm_arena_alloc(ctx->arena, - (size_t)(n + 1) * sizeof(const CBMType*)); - if (!kept) return t; + const CBMType **kept = + (const CBMType **)cbm_arena_alloc(ctx->arena, (size_t)(n + 1) * sizeof(const CBMType *)); + if (!kept) + return t; for (int i = 0; i < n; i++) { - const CBMType* m = t->data.union_type.members[i]; + const CBMType *m = t->data.union_type.members[i]; if (m && m->kind == CBM_TYPE_BUILTIN && m->data.builtin.name && - strcmp(m->data.builtin.name, "None") == 0) continue; + strcmp(m->data.builtin.name, "None") == 0) + continue; kept[retained++] = m; } - if (retained == 0) return cbm_type_unknown(); - if (retained == 1) return kept[0]; + if (retained == 0) + return cbm_type_unknown(); + if (retained == 1) + return kept[0]; return cbm_type_union(ctx->arena, kept, retained); } /* Walk a node's subtree looking for walrus expressions and bind their * targets in the enclosing function scope (PEP 572 semantics). */ -static void py_bind_walrus_in(PyLSPContext* ctx, TSNode node) { - if (ts_node_is_null(node)) return; - const char* k = ts_node_type(node); - if (strcmp(k, "named_expression") == 0 || - strcmp(k, "assignment_expression") == 0) { +static void py_bind_walrus_in(PyLSPContext *ctx, TSNode node) { + if (ts_node_is_null(node)) + return; + const char *k = ts_node_type(node); + if (strcmp(k, "named_expression") == 0 || strcmp(k, "assignment_expression") == 0) { TSNode left = ts_node_child_by_field_name(node, "name", 4); if (ts_node_is_null(left)) { left = ts_node_child_by_field_name(node, "left", 4); @@ -1790,9 +1943,9 @@ static void py_bind_walrus_in(PyLSPContext* ctx, TSNode node) { } if (!ts_node_is_null(left) && !ts_node_is_null(right) && strcmp(ts_node_type(left), "identifier") == 0) { - char* name = py_node_text(ctx, left); + char *name = py_node_text(ctx, left); if (name) { - const CBMType* t = py_eval_expr_type(ctx, right); + const CBMType *t = py_eval_expr_type(ctx, right); cbm_scope_bind(ctx->current_scope, name, t); } } @@ -1807,22 +1960,22 @@ static void py_bind_walrus_in(PyLSPContext* ctx, TSNode node) { * `continue` — meaning execution doesn't fall through past the if-block. * Used for early-return narrowing of the negated condition. */ static bool py_block_terminates(TSNode block) { - if (ts_node_is_null(block)) return false; + if (ts_node_is_null(block)) + return false; uint32_t bnc = ts_node_named_child_count(block); - if (bnc == 0) return false; + if (bnc == 0) + return false; TSNode last = ts_node_named_child(block, bnc - 1); - const char* k = ts_node_type(last); - return strcmp(k, "return_statement") == 0 || - strcmp(k, "raise_statement") == 0 || - strcmp(k, "break_statement") == 0 || - strcmp(k, "continue_statement") == 0; + const char *k = ts_node_type(last); + return strcmp(k, "return_statement") == 0 || strcmp(k, "raise_statement") == 0 || + strcmp(k, "break_statement") == 0 || strcmp(k, "continue_statement") == 0; } /* Walk if_statement: push scope for consequence body when narrowing * applies, then recurse. If the consequence terminates (return/raise), * apply the negated narrow to the *enclosing* scope so subsequent * statements see the narrowed type. */ -static void py_walk_if_statement(PyLSPContext* ctx, TSNode if_node) { +static void py_walk_if_statement(PyLSPContext *ctx, TSNode if_node) { TSNode cond = ts_node_child_by_field_name(if_node, "condition", 9); TSNode body = ts_node_child_by_field_name(if_node, "consequence", 11); TSNode alt = ts_node_child_by_field_name(if_node, "alternative", 11); @@ -1836,17 +1989,16 @@ static void py_walk_if_statement(PyLSPContext* ctx, TSNode if_node) { // Recognize negated isinstance / is-None patterns for early-return // narrowing. bool neg_isinstance = false; - char* neg_name = NULL; - const CBMType* neg_type = NULL; + char *neg_name = NULL; + const CBMType *neg_type = NULL; int neg_none_kind = 0; - char* neg_none_var = NULL; - if (!ts_node_is_null(cond) && - strcmp(ts_node_type(cond), "not_operator") == 0 && + char *neg_none_var = NULL; + if (!ts_node_is_null(cond) && strcmp(ts_node_type(cond), "not_operator") == 0 && ts_node_named_child_count(cond) > 0) { TSNode inner = ts_node_named_child(cond, 0); if (strcmp(ts_node_type(inner), "call") == 0) { - char* nm = NULL; - const CBMType* ty = NULL; + char *nm = NULL; + const CBMType *ty = NULL; if (py_match_isinstance(ctx, inner, &nm, &ty)) { neg_isinstance = true; neg_name = nm; @@ -1858,9 +2010,9 @@ static void py_walk_if_statement(PyLSPContext* ctx, TSNode if_node) { } // `if x is None: return` — after the if block, x is non-None. { - char* nv = NULL; + char *nv = NULL; int nk = py_match_is_none(ctx, cond, &nv); - if (nk == -1) { // x is None + if (nk == -1) { // x is None neg_none_kind = -1; neg_none_var = nv; } @@ -1868,24 +2020,24 @@ static void py_walk_if_statement(PyLSPContext* ctx, TSNode if_node) { // Consequence: maybe narrow. if (!ts_node_is_null(body)) { - CBMScope* saved = ctx->current_scope; + CBMScope *saved = ctx->current_scope; ctx->current_scope = cbm_scope_push(ctx->arena, ctx->current_scope); // Try isinstance narrowing. if (!ts_node_is_null(cond) && strcmp(ts_node_type(cond), "call") == 0) { - char* name = NULL; - const CBMType* narrowed = NULL; + char *name = NULL; + const CBMType *narrowed = NULL; if (py_match_isinstance(ctx, cond, &name, &narrowed) && name && narrowed) { cbm_scope_bind(ctx->current_scope, name, narrowed); } } // Try `x is not None` narrowing. - char* none_var = NULL; + char *none_var = NULL; int none_kind = py_match_is_none(ctx, cond, &none_var); if (none_kind == 1 && none_var) { - const CBMType* current = cbm_scope_lookup(ctx->current_scope, none_var); + const CBMType *current = cbm_scope_lookup(ctx->current_scope, none_var); if (current && !cbm_type_is_unknown(current)) { - const CBMType* narrowed = py_strip_none(ctx, current); + const CBMType *narrowed = py_strip_none(ctx, current); cbm_scope_bind(ctx->current_scope, none_var, narrowed); } } @@ -1909,10 +2061,9 @@ static void py_walk_if_statement(PyLSPContext* ctx, TSNode if_node) { } if (neg_none_kind == -1 && neg_none_var) { // `if x is None: return` -> narrow x to non-None afterwards. - const CBMType* current = cbm_scope_lookup(ctx->current_scope, - neg_none_var); + const CBMType *current = cbm_scope_lookup(ctx->current_scope, neg_none_var); if (current && !cbm_type_is_unknown(current)) { - const CBMType* narrowed = py_strip_none(ctx, current); + const CBMType *narrowed = py_strip_none(ctx, current); cbm_scope_bind(ctx->current_scope, neg_none_var, narrowed); } } @@ -1920,21 +2071,35 @@ static void py_walk_if_statement(PyLSPContext* ctx, TSNode if_node) { } /* Map a Python infix operator token to its dunder method name. */ -static const char* py_binop_dunder(const char* op_text) { - if (!op_text) return NULL; - if (strcmp(op_text, "+") == 0) return "__add__"; - if (strcmp(op_text, "-") == 0) return "__sub__"; - if (strcmp(op_text, "*") == 0) return "__mul__"; - if (strcmp(op_text, "/") == 0) return "__truediv__"; - if (strcmp(op_text, "//") == 0) return "__floordiv__"; - if (strcmp(op_text, "%") == 0) return "__mod__"; - if (strcmp(op_text, "**") == 0) return "__pow__"; - if (strcmp(op_text, "<<") == 0) return "__lshift__"; - if (strcmp(op_text, ">>") == 0) return "__rshift__"; - if (strcmp(op_text, "&") == 0) return "__and__"; - if (strcmp(op_text, "|") == 0) return "__or__"; - if (strcmp(op_text, "^") == 0) return "__xor__"; - if (strcmp(op_text, "@") == 0) return "__matmul__"; +static const char *py_binop_dunder(const char *op_text) { + if (!op_text) + return NULL; + if (strcmp(op_text, "+") == 0) + return "__add__"; + if (strcmp(op_text, "-") == 0) + return "__sub__"; + if (strcmp(op_text, "*") == 0) + return "__mul__"; + if (strcmp(op_text, "/") == 0) + return "__truediv__"; + if (strcmp(op_text, "//") == 0) + return "__floordiv__"; + if (strcmp(op_text, "%") == 0) + return "__mod__"; + if (strcmp(op_text, "**") == 0) + return "__pow__"; + if (strcmp(op_text, "<<") == 0) + return "__lshift__"; + if (strcmp(op_text, ">>") == 0) + return "__rshift__"; + if (strcmp(op_text, "&") == 0) + return "__and__"; + if (strcmp(op_text, "|") == 0) + return "__or__"; + if (strcmp(op_text, "^") == 0) + return "__xor__"; + if (strcmp(op_text, "@") == 0) + return "__matmul__"; return NULL; } @@ -1947,10 +2112,10 @@ static const char* py_binop_dunder(const char* op_text) { * guessing the sole class that declares the dunder would mis-resolve ordinary * built-in subscripts/operators (`some_list[0]`, `a + b` on ints) onto an * unrelated user class, so we only resolve when the receiver type is known. */ -static void py_emit_dunder_call(PyLSPContext* ctx, const CBMType* recv, const char* dunder) { - if (!recv || recv->kind != CBM_TYPE_NAMED || !dunder) return; - const CBMRegisteredFunc* f = - py_lookup_attribute(ctx, recv->data.named.qualified_name, dunder); +static void py_emit_dunder_call(PyLSPContext *ctx, const CBMType *recv, const char *dunder) { + if (!recv || recv->kind != CBM_TYPE_NAMED || !dunder) + return; + const CBMRegisteredFunc *f = py_lookup_attribute(ctx, recv->data.named.qualified_name, dunder); if (f && f->qualified_name) { py_emit_resolved_call(ctx, f->qualified_name, "lsp_operator_dunder", 0.85f); /* A subscript (`s[k]`) / binary_operator (`a + b`) is not a syntactic @@ -1970,9 +2135,10 @@ static void py_emit_dunder_call(PyLSPContext* ctx, const CBMType* recv, const ch } } -static void py_resolve_calls_in(PyLSPContext* ctx, TSNode node) { - if (!ctx || ts_node_is_null(node)) return; - const char* k = ts_node_type(node); +static void py_resolve_calls_in(PyLSPContext *ctx, TSNode node) { + if (!ctx || ts_node_is_null(node)) + return; + const char *k = ts_node_type(node); // Statement-level binding effects. py_process_statement(ctx, node); @@ -1990,7 +2156,7 @@ static void py_resolve_calls_in(PyLSPContext* ctx, TSNode node) { TSNode left = ts_node_child_by_field_name(node, "left", 4); TSNode op = ts_node_child_by_field_name(node, "operator", 8); if (!ts_node_is_null(left) && !ts_node_is_null(op)) { - const char* dunder = py_binop_dunder(py_node_text(ctx, op)); + const char *dunder = py_binop_dunder(py_node_text(ctx, op)); if (dunder) { py_emit_dunder_call(ctx, py_eval_expr_type(ctx, left), dunder); } @@ -2014,9 +2180,8 @@ static void py_resolve_calls_in(PyLSPContext* ctx, TSNode node) { TSNode body = ts_node_child_by_field_name(node, "body", 4); // Subject identifier (best-effort — only narrow when the subject // is a single identifier). - char* subject_name = NULL; - if (!ts_node_is_null(subject) && - strcmp(ts_node_type(subject), "identifier") == 0) { + char *subject_name = NULL; + if (!ts_node_is_null(subject) && strcmp(ts_node_type(subject), "identifier") == 0) { subject_name = py_node_text(ctx, subject); } py_resolve_calls_in(ctx, subject); @@ -2024,8 +2189,9 @@ static void py_resolve_calls_in(PyLSPContext* ctx, TSNode node) { uint32_t bcc = ts_node_named_child_count(body); for (uint32_t i = 0; i < bcc; i++) { TSNode case_clause = ts_node_named_child(body, i); - if (strcmp(ts_node_type(case_clause), "case_clause") != 0) continue; - CBMScope* saved = ctx->current_scope; + if (strcmp(ts_node_type(case_clause), "case_clause") != 0) + continue; + CBMScope *saved = ctx->current_scope; ctx->current_scope = cbm_scope_push(ctx->arena, ctx->current_scope); // Pattern is the first non-block named child; consequence @@ -2034,9 +2200,13 @@ static void py_resolve_calls_in(PyLSPContext* ctx, TSNode node) { uint32_t cnc = ts_node_named_child_count(case_clause); for (uint32_t j = 0; j < cnc; j++) { TSNode c = ts_node_named_child(case_clause, j); - const char* ck = ts_node_type(c); - if (strcmp(ck, "block") == 0) { conseq = c; break; } - if (ts_node_is_null(pattern)) pattern = c; + const char *ck = ts_node_type(c); + if (strcmp(ck, "block") == 0) { + conseq = c; + break; + } + if (ts_node_is_null(pattern)) + pattern = c; } // The pattern field is a `case_pattern` wrapper around the @@ -2045,10 +2215,10 @@ static void py_resolve_calls_in(PyLSPContext* ctx, TSNode node) { // through both wrappers. TSNode actual_pattern = pattern; for (int unwrap = 0; unwrap < 3; unwrap++) { - if (ts_node_is_null(actual_pattern)) break; - const char* apk = ts_node_type(actual_pattern); - if ((strcmp(apk, "case_pattern") == 0 || - strcmp(apk, "pattern") == 0 || + if (ts_node_is_null(actual_pattern)) + break; + const char *apk = ts_node_type(actual_pattern); + if ((strcmp(apk, "case_pattern") == 0 || strcmp(apk, "pattern") == 0 || strcmp(apk, "_simple_pattern") == 0) && ts_node_named_child_count(actual_pattern) > 0) { actual_pattern = ts_node_named_child(actual_pattern, 0); @@ -2062,9 +2232,9 @@ static void py_resolve_calls_in(PyLSPContext* ctx, TSNode node) { strcmp(ts_node_type(actual_pattern), "class_pattern") == 0) { if (ts_node_named_child_count(actual_pattern) > 0) { TSNode cls = ts_node_named_child(actual_pattern, 0); - char* cls_text = py_node_text(ctx, cls); + char *cls_text = py_node_text(ctx, cls); if (cls_text) { - const CBMType* narrowed = py_resolve_annotation(ctx, cls_text); + const CBMType *narrowed = py_resolve_annotation(ctx, cls_text); cbm_scope_bind(ctx->current_scope, subject_name, narrowed); } } @@ -2074,9 +2244,9 @@ static void py_resolve_calls_in(PyLSPContext* ctx, TSNode node) { if (strcmp(ts_node_type(sub), "capture_pattern") == 0) { if (ts_node_named_child_count(sub) > 0) { TSNode id = ts_node_named_child(sub, 0); - char* nm = py_node_text(ctx, id); - if (nm) cbm_scope_bind(ctx->current_scope, nm, - cbm_type_unknown()); + char *nm = py_node_text(ctx, id); + if (nm) + cbm_scope_bind(ctx->current_scope, nm, cbm_type_unknown()); } } } @@ -2090,9 +2260,8 @@ static void py_resolve_calls_in(PyLSPContext* ctx, TSNode node) { if (subject_name && !ts_node_is_null(actual_pattern) && (strcmp(ts_node_type(actual_pattern), "list_pattern") == 0 || strcmp(ts_node_type(actual_pattern), "tuple_pattern") == 0)) { - const CBMType* subj_t = cbm_scope_lookup(ctx->current_scope, - subject_name); - const CBMType* elem_t = py_iterable_element_type(ctx, subj_t); + const CBMType *subj_t = cbm_scope_lookup(ctx->current_scope, subject_name); + const CBMType *elem_t = py_iterable_element_type(ctx, subj_t); uint32_t pn = ts_node_named_child_count(actual_pattern); for (uint32_t j = 0; j < pn; j++) { TSNode elem = ts_node_named_child(actual_pattern, j); @@ -2102,14 +2271,14 @@ static void py_resolve_calls_in(PyLSPContext* ctx, TSNode node) { ts_node_named_child_count(elem) > 0) { elem = ts_node_named_child(elem, 0); } - if (ts_node_is_null(elem)) continue; - const char* ek = ts_node_type(elem); + if (ts_node_is_null(elem)) + continue; + const char *ek = ts_node_type(elem); // Capture pattern: dotted_name wrapping identifier. - if (strcmp(ek, "dotted_name") == 0 && - ts_node_named_child_count(elem) > 0) { + if (strcmp(ek, "dotted_name") == 0 && ts_node_named_child_count(elem) > 0) { TSNode id = ts_node_named_child(elem, 0); if (strcmp(ts_node_type(id), "identifier") == 0) { - char* nm = py_node_text(ctx, id); + char *nm = py_node_text(ctx, id); if (nm && elem_t) { cbm_scope_bind(ctx->current_scope, nm, elem_t); } @@ -2117,7 +2286,7 @@ static void py_resolve_calls_in(PyLSPContext* ctx, TSNode node) { continue; } if (strcmp(ek, "identifier") == 0) { - char* nm = py_node_text(ctx, elem); + char *nm = py_node_text(ctx, elem); if (nm && elem_t) { cbm_scope_bind(ctx->current_scope, nm, elem_t); } @@ -2143,10 +2312,10 @@ static void py_resolve_calls_in(PyLSPContext* ctx, TSNode node) { } } if (!ts_node_is_null(id)) { - char* nm = py_node_text(ctx, id); + char *nm = py_node_text(ctx, id); if (nm && elem_t) { - const CBMType* lst = cbm_type_template( - ctx->arena, "list", &elem_t, 1); + const CBMType *lst = + cbm_type_template(ctx->arena, "list", &elem_t, 1); cbm_scope_bind(ctx->current_scope, nm, lst); } } @@ -2165,11 +2334,9 @@ static void py_resolve_calls_in(PyLSPContext* ctx, TSNode node) { // Comprehensions push a scope and bind for_in_clause loop vars to the // iterable's element type, then walk inner expressions. - if (strcmp(k, "list_comprehension") == 0 || - strcmp(k, "dictionary_comprehension") == 0 || - strcmp(k, "set_comprehension") == 0 || - strcmp(k, "generator_expression") == 0) { - CBMScope* saved = ctx->current_scope; + if (strcmp(k, "list_comprehension") == 0 || strcmp(k, "dictionary_comprehension") == 0 || + strcmp(k, "set_comprehension") == 0 || strcmp(k, "generator_expression") == 0) { + CBMScope *saved = ctx->current_scope; ctx->current_scope = cbm_scope_push(ctx->arena, ctx->current_scope); uint32_t cnc = ts_node_named_child_count(node); // First pass: bind for-clause vars (process in source order so @@ -2177,13 +2344,13 @@ static void py_resolve_calls_in(PyLSPContext* ctx, TSNode node) { // x bound by the time we evaluate x.ys). for (uint32_t i = 0; i < cnc; i++) { TSNode child = ts_node_named_child(node, i); - const char* ck = ts_node_type(child); + const char *ck = ts_node_type(child); if (strcmp(ck, "for_in_clause") == 0) { TSNode left = ts_node_child_by_field_name(child, "left", 4); TSNode right = ts_node_child_by_field_name(child, "right", 5); - const CBMType* elem_type = cbm_type_unknown(); + const CBMType *elem_type = cbm_type_unknown(); if (!ts_node_is_null(right)) { - const CBMType* iter_type = py_eval_expr_type(ctx, right); + const CBMType *iter_type = py_eval_expr_type(ctx, right); elem_type = py_iterable_element_type(ctx, iter_type); } py_bind_for_target(ctx, left, elem_type); @@ -2201,8 +2368,7 @@ static void py_resolve_calls_in(PyLSPContext* ctx, TSNode node) { // Recurse: children. We don't push scope for control-flow blocks // here (Python scoping is function-level apart from comprehension / // lambda / class), but we do for nested function / class / lambda. - if (strcmp(k, "function_definition") == 0 || - strcmp(k, "class_definition") == 0 || + if (strcmp(k, "function_definition") == 0 || strcmp(k, "class_definition") == 0 || strcmp(k, "lambda") == 0) { // These are processed by the top-level pass; skip recursion to // avoid double-walking their bodies. @@ -2223,16 +2389,21 @@ static void py_resolve_calls_in(PyLSPContext* ctx, TSNode node) { * builtin scalar names. Used by py_register_def, which runs before any * context is available. When module_qn is non-NULL, bare class names * are qualified as "." so registry lookups match. */ -static const CBMType* py_parse_type_text(CBMArena* arena, const char* ann); -static const CBMType* py_parse_type_text_qn(CBMArena* arena, const char* ann, - const char* module_qn); +static const CBMType *py_parse_type_text(CBMArena *arena, const char *ann); +static const CBMType *py_parse_type_text_qn(CBMArena *arena, const char *ann, + const char *module_qn); /* Trim ASCII whitespace from both ends of an arena-allocated copy. */ -static char* py_trim_ws(CBMArena* arena, const char* start, size_t len) { - while (len > 0 && (start[0] == ' ' || start[0] == '\t')) { start++; len--; } - while (len > 0 && (start[len - 1] == ' ' || start[len - 1] == '\t')) len--; - char* out = (char*)cbm_arena_alloc(arena, len + 1); - if (!out) return NULL; +static char *py_trim_ws(CBMArena *arena, const char *start, size_t len) { + while (len > 0 && (start[0] == ' ' || start[0] == '\t')) { + start++; + len--; + } + while (len > 0 && (start[len - 1] == ' ' || start[len - 1] == '\t')) + len--; + char *out = (char *)cbm_arena_alloc(arena, len + 1); + if (!out) + return NULL; memcpy(out, start, len); out[len] = '\0'; return out; @@ -2241,34 +2412,44 @@ static char* py_trim_ws(CBMArena* arena, const char* start, size_t len) { /* Split a comma-separated argument string at depth 0 (ignoring commas * inside [], (), {}). Returns NULL-terminated arena array of trimmed * substring copies. Caller passes the inside of `[...]`. */ -static const char** py_split_subscript_args(CBMArena* arena, const char* s, int* out_n) { - if (!s) { *out_n = 0; return NULL; } +static const char **py_split_subscript_args(CBMArena *arena, const char *s, int *out_n) { + if (!s) { + *out_n = 0; + return NULL; + } int cap = 8; - const char** out = (const char**)cbm_arena_alloc(arena, - (size_t)(cap + 1) * sizeof(const char*)); - if (!out) { *out_n = 0; return NULL; } + const char **out = + (const char **)cbm_arena_alloc(arena, (size_t)(cap + 1) * sizeof(const char *)); + if (!out) { + *out_n = 0; + return NULL; + } int n = 0; int depth = 0; size_t len = strlen(s); size_t start = 0; for (size_t i = 0; i <= len; i++) { char c = (i < len) ? s[i] : ','; - if (c == '[' || c == '(' || c == '{') depth++; - else if (c == ']' || c == ')' || c == '}') depth--; + if (c == '[' || c == '(' || c == '{') + depth++; + else if (c == ']' || c == ')' || c == '}') + depth--; else if (c == ',' && depth == 0) { - char* part = py_trim_ws(arena, s + start, i - start); + char *part = py_trim_ws(arena, s + start, i - start); if (part && part[0]) { if (n >= cap) { int new_cap = cap * 2; - const char** grown = (const char**)cbm_arena_alloc(arena, - (size_t)(new_cap + 1) * sizeof(const char*)); + const char **grown = (const char **)cbm_arena_alloc( + arena, (size_t)(new_cap + 1) * sizeof(const char *)); if (grown) { - for (int q = 0; q < n; q++) grown[q] = out[q]; + for (int q = 0; q < n; q++) + grown[q] = out[q]; out = grown; cap = new_cap; } } - if (n < cap) out[n++] = part; + if (n < cap) + out[n++] = part; } start = i + 1; } @@ -2278,13 +2459,14 @@ static const char** py_split_subscript_args(CBMArena* arena, const char* s, int* return out; } -static const CBMType* py_parse_type_text_qn(CBMArena* arena, const char* ann, - const char* module_qn) { - if (!ann || !ann[0]) return cbm_type_unknown(); +static const CBMType *py_parse_type_text_qn(CBMArena *arena, const char *ann, + const char *module_qn) { + if (!ann || !ann[0]) + return cbm_type_unknown(); size_t len = strlen(ann); if (len >= 2 && (ann[0] == '"' || ann[0] == '\'') && ann[len - 1] == ann[0]) { - char* unquoted = (char*)cbm_arena_alloc(arena, len - 1); + char *unquoted = (char *)cbm_arena_alloc(arena, len - 1); if (unquoted) { memcpy(unquoted, ann + 1, len - 2); unquoted[len - 2] = '\0'; @@ -2292,29 +2474,33 @@ static const CBMType* py_parse_type_text_qn(CBMArena* arena, const char* ann, } } - const char* lb = strchr(ann, '['); + const char *lb = strchr(ann, '['); if (lb && len > 0 && ann[len - 1] == ']') { size_t base_len = (size_t)(lb - ann); - char* base = (char*)cbm_arena_alloc(arena, base_len + 1); + char *base = (char *)cbm_arena_alloc(arena, base_len + 1); if (base) { memcpy(base, ann, base_len); base[base_len] = '\0'; - char* btrim = base; - while (*btrim == ' ') btrim++; + char *btrim = base; + while (*btrim == ' ') + btrim++; size_t blen = strlen(btrim); - while (blen > 0 && btrim[blen - 1] == ' ') { btrim[blen - 1] = '\0'; blen--; } + while (blen > 0 && btrim[blen - 1] == ' ') { + btrim[blen - 1] = '\0'; + blen--; + } size_t inner_start = (size_t)(lb - ann) + 1; size_t inner_len = len - inner_start - 1; - char* args_text = (char*)cbm_arena_alloc(arena, inner_len + 1); + char *args_text = (char *)cbm_arena_alloc(arena, inner_len + 1); if (args_text) { memcpy(args_text, ann + inner_start, inner_len); args_text[inner_len] = '\0'; int arg_n = 0; - const char** arg_strs = py_split_subscript_args(arena, args_text, &arg_n); - const CBMType** arg_types = NULL; + const char **arg_strs = py_split_subscript_args(arena, args_text, &arg_n); + const CBMType **arg_types = NULL; if (arg_n > 0 && arg_strs) { - arg_types = (const CBMType**)cbm_arena_alloc(arena, - (size_t)(arg_n + 1) * sizeof(const CBMType*)); + arg_types = (const CBMType **)cbm_arena_alloc( + arena, (size_t)(arg_n + 1) * sizeof(const CBMType *)); if (arg_types) { for (int i = 0; i < arg_n; i++) { arg_types[i] = py_parse_type_text_qn(arena, arg_strs[i], module_qn); @@ -2322,8 +2508,7 @@ static const CBMType* py_parse_type_text_qn(CBMArena* arena, const char* ann, arg_types[arg_n] = NULL; } } - if (strcmp(btrim, "Optional") == 0 || - strcmp(btrim, "typing.Optional") == 0) { + if (strcmp(btrim, "Optional") == 0 || strcmp(btrim, "typing.Optional") == 0) { if (arg_types && arg_n >= 1 && arg_types[0]) { return cbm_type_optional(arena, arg_types[0]); } @@ -2339,17 +2524,30 @@ static const CBMType* py_parse_type_text_qn(CBMArena* arena, const char* ann, // (drop metadata), Mapped[T] (SQLAlchemy 2.0), and // typing_extensions/typing variants of all these. Returns // the wrapped type T. - static const char* wrapper_names[] = { - "ClassVar", "Final", "InitVar", "ReadOnly", - "Required", "NotRequired", "Annotated", - "Mapped", "WriteOnlyMapped", "DynamicMapped", // SQLAlchemy - "typing.ClassVar", "typing.Final", "typing.Annotated", - "typing.Required", "typing.NotRequired", "typing.ReadOnly", - "typing_extensions.ClassVar", "typing_extensions.Final", - "typing_extensions.Annotated", "typing_extensions.Required", - "typing_extensions.NotRequired", "typing_extensions.ReadOnly", - "dataclasses.InitVar", - NULL}; + static const char *wrapper_names[] = {"ClassVar", + "Final", + "InitVar", + "ReadOnly", + "Required", + "NotRequired", + "Annotated", + "Mapped", + "WriteOnlyMapped", + "DynamicMapped", // SQLAlchemy + "typing.ClassVar", + "typing.Final", + "typing.Annotated", + "typing.Required", + "typing.NotRequired", + "typing.ReadOnly", + "typing_extensions.ClassVar", + "typing_extensions.Final", + "typing_extensions.Annotated", + "typing_extensions.Required", + "typing_extensions.NotRequired", + "typing_extensions.ReadOnly", + "dataclasses.InitVar", + NULL}; for (int wi = 0; wrapper_names[wi]; wi++) { if (strcmp(btrim, wrapper_names[wi]) == 0) { if (arg_types && arg_n >= 1 && arg_types[0]) { @@ -2375,8 +2573,8 @@ static const CBMType* py_parse_type_text_qn(CBMArena* arena, const char* ann, { int cap = 4; - const char** out = (const char**)cbm_arena_alloc(arena, - (size_t)(cap + 1) * sizeof(const char*)); + const char **out = + (const char **)cbm_arena_alloc(arena, (size_t)(cap + 1) * sizeof(const char *)); if (out) { int onum = 0; int d2 = 0; @@ -2384,18 +2582,21 @@ static const CBMType* py_parse_type_text_qn(CBMArena* arena, const char* ann, bool seen_pipe = false; for (size_t j = 0; j <= len; j++) { char cc = (j < len) ? ann[j] : '|'; - if (cc == '[' || cc == '(' || cc == '{') d2++; - else if (cc == ']' || cc == ')' || cc == '}') d2--; + if (cc == '[' || cc == '(' || cc == '{') + d2++; + else if (cc == ']' || cc == ')' || cc == '}') + d2--; else if (cc == '|' && d2 == 0) { seen_pipe = (j < len); - char* p = py_trim_ws(arena, ann + start, j - start); - if (p && p[0] && onum < cap) out[onum++] = p; + char *p = py_trim_ws(arena, ann + start, j - start); + if (p && p[0] && onum < cap) + out[onum++] = p; start = j + 1; } } if (seen_pipe && onum >= 2) { - const CBMType** members = (const CBMType**)cbm_arena_alloc(arena, - (size_t)(onum + 1) * sizeof(const CBMType*)); + const CBMType **members = (const CBMType **)cbm_arena_alloc( + arena, (size_t)(onum + 1) * sizeof(const CBMType *)); if (members) { for (int j = 0; j < onum; j++) { members[j] = py_parse_type_text_qn(arena, out[j], module_qn); @@ -2406,8 +2607,8 @@ static const CBMType* py_parse_type_text_qn(CBMArena* arena, const char* ann, } } - static const char* builtins[] = {"int", "str", "bool", "float", "bytes", - "None", "complex", "bytearray", "object", "type", NULL}; + static const char *builtins[] = {"int", "str", "bool", "float", "bytes", "None", + "complex", "bytearray", "object", "type", NULL}; for (int i = 0; builtins[i]; i++) { if (strcmp(ann, builtins[i]) == 0) { return cbm_type_builtin(arena, ann); @@ -2417,24 +2618,47 @@ static const CBMType* py_parse_type_text_qn(CBMArena* arena, const char* ann, // contain a dot. Skips obvious typing-module names so `Optional` / // `Self` / etc. don't end up qualified to the consumer's module. if (module_qn && !strchr(ann, '.')) { - static const char* typing_names[] = {"Self", "Any", "None", "True", "False", - "Iterator", "Iterable", "Generator", "AsyncIterator", "AsyncIterable", - "Sequence", "MutableSequence", "Mapping", "MutableMapping", "Collection", - "Container", "Reversible", "Optional", "Union", "Callable", - "Awaitable", "Coroutine", "TypeVar", "ParamSpec", NULL}; + static const char *typing_names[] = {"Self", + "Any", + "None", + "True", + "False", + "Iterator", + "Iterable", + "Generator", + "AsyncIterator", + "AsyncIterable", + "Sequence", + "MutableSequence", + "Mapping", + "MutableMapping", + "Collection", + "Container", + "Reversible", + "Optional", + "Union", + "Callable", + "Awaitable", + "Coroutine", + "TypeVar", + "ParamSpec", + NULL}; bool is_typing = false; for (int i = 0; typing_names[i]; i++) { - if (strcmp(ann, typing_names[i]) == 0) { is_typing = true; break; } + if (strcmp(ann, typing_names[i]) == 0) { + is_typing = true; + break; + } } if (!is_typing) { - const char* qn = cbm_arena_sprintf(arena, "%s.%s", module_qn, ann); + const char *qn = cbm_arena_sprintf(arena, "%s.%s", module_qn, ann); return cbm_type_named(arena, qn); } } return cbm_type_named(arena, ann); } -static const CBMType* py_parse_type_text(CBMArena* arena, const char* ann) { +static const CBMType *py_parse_type_text(CBMArena *arena, const char *ann) { return py_parse_type_text_qn(arena, ann, NULL); } @@ -2445,13 +2669,14 @@ static const CBMType* py_parse_type_text(CBMArena* arena, const char* ann) { * `Optional[Foo]` / `Union[A, B]` into TEMPLATE / UNION shapes so * subsequent code can reach into element types for comprehensions * etc. */ -static const CBMType* py_resolve_annotation(PyLSPContext* ctx, const char* ann) { - if (!ann || !ann[0]) return cbm_type_unknown(); +static const CBMType *py_resolve_annotation(PyLSPContext *ctx, const char *ann) { + if (!ann || !ann[0]) + return cbm_type_unknown(); // Strip outer quotes for forward references: `"Foo"` -> `Foo`. size_t len = strlen(ann); if (len >= 2 && (ann[0] == '"' || ann[0] == '\'') && ann[len - 1] == ann[0]) { - char* unquoted = (char*)cbm_arena_alloc(ctx->arena, len - 1); + char *unquoted = (char *)cbm_arena_alloc(ctx->arena, len - 1); if (unquoted) { memcpy(unquoted, ann + 1, len - 2); unquoted[len - 2] = '\0'; @@ -2461,32 +2686,36 @@ static const CBMType* py_resolve_annotation(PyLSPContext* ctx, const char* ann) // Generic subscript: `list[Foo]` / `Optional[Foo]` / `Union[A, B]` / // `dict[K, V]`. Parse the base name + comma-split args at depth 0. - const char* lb = strchr(ann, '['); + const char *lb = strchr(ann, '['); if (lb && len > 0 && ann[len - 1] == ']') { size_t base_len = (size_t)(lb - ann); - char* base = (char*)cbm_arena_alloc(ctx->arena, base_len + 1); + char *base = (char *)cbm_arena_alloc(ctx->arena, base_len + 1); if (base) { memcpy(base, ann, base_len); base[base_len] = '\0'; // Trim leading/trailing whitespace from base. - char* btrim = base; - while (*btrim == ' ') btrim++; + char *btrim = base; + while (*btrim == ' ') + btrim++; size_t blen = strlen(btrim); - while (blen > 0 && btrim[blen - 1] == ' ') { btrim[blen - 1] = '\0'; blen--; } + while (blen > 0 && btrim[blen - 1] == ' ') { + btrim[blen - 1] = '\0'; + blen--; + } // Args text: between [ and ] size_t inner_start = (size_t)(lb - ann) + 1; size_t inner_len = len - inner_start - 1; - char* args_text = (char*)cbm_arena_alloc(ctx->arena, inner_len + 1); + char *args_text = (char *)cbm_arena_alloc(ctx->arena, inner_len + 1); if (args_text) { memcpy(args_text, ann + inner_start, inner_len); args_text[inner_len] = '\0'; int arg_n = 0; - const char** arg_strs = py_split_subscript_args(ctx->arena, args_text, &arg_n); + const char **arg_strs = py_split_subscript_args(ctx->arena, args_text, &arg_n); // Recursively resolve each arg to a CBMType. - const CBMType** arg_types = NULL; + const CBMType **arg_types = NULL; if (arg_n > 0 && arg_strs) { - arg_types = (const CBMType**)cbm_arena_alloc(ctx->arena, - (size_t)(arg_n + 1) * sizeof(const CBMType*)); + arg_types = (const CBMType **)cbm_arena_alloc( + ctx->arena, (size_t)(arg_n + 1) * sizeof(const CBMType *)); if (arg_types) { for (int i = 0; i < arg_n; i++) { arg_types[i] = py_resolve_annotation(ctx, arg_strs[i]); @@ -2496,8 +2725,7 @@ static const CBMType* py_resolve_annotation(PyLSPContext* ctx, const char* ann) } // Optional[X] -> UNION(X, None) - if (strcmp(btrim, "Optional") == 0 || - strcmp(btrim, "typing.Optional") == 0) { + if (strcmp(btrim, "Optional") == 0 || strcmp(btrim, "typing.Optional") == 0) { if (arg_types && arg_n >= 1 && arg_types[0]) { return cbm_type_optional(ctx->arena, arg_types[0]); } @@ -2512,19 +2740,33 @@ static const CBMType* py_resolve_annotation(PyLSPContext* ctx, const char* ann) // ClassVar / Final / InitVar / ReadOnly / Required / // NotRequired / Annotated / Mapped (SQLAlchemy) / Type[T]. // Returns the wrapped type T directly. - static const char* wrapper_names[] = { - "ClassVar", "Final", "InitVar", "ReadOnly", - "Required", "NotRequired", "Annotated", - "Mapped", "WriteOnlyMapped", "DynamicMapped", - "Type", "type", - "typing.ClassVar", "typing.Final", "typing.Annotated", - "typing.Required", "typing.NotRequired", "typing.ReadOnly", - "typing.Type", - "typing_extensions.ClassVar", "typing_extensions.Final", - "typing_extensions.Annotated", "typing_extensions.Required", - "typing_extensions.NotRequired", "typing_extensions.ReadOnly", - "dataclasses.InitVar", - NULL}; + static const char *wrapper_names[] = {"ClassVar", + "Final", + "InitVar", + "ReadOnly", + "Required", + "NotRequired", + "Annotated", + "Mapped", + "WriteOnlyMapped", + "DynamicMapped", + "Type", + "type", + "typing.ClassVar", + "typing.Final", + "typing.Annotated", + "typing.Required", + "typing.NotRequired", + "typing.ReadOnly", + "typing.Type", + "typing_extensions.ClassVar", + "typing_extensions.Final", + "typing_extensions.Annotated", + "typing_extensions.Required", + "typing_extensions.NotRequired", + "typing_extensions.ReadOnly", + "dataclasses.InitVar", + NULL}; for (int wi = 0; wrapper_names[wi]; wi++) { if (strcmp(btrim, wrapper_names[wi]) == 0) { if (arg_types && arg_n >= 1 && arg_types[0]) { @@ -2546,36 +2788,43 @@ static const CBMType* py_resolve_annotation(PyLSPContext* ctx, const char* ann) int depth = 0; for (size_t i = 0; i < len; i++) { char c = ann[i]; - if (c == '[' || c == '(' || c == '{') depth++; - else if (c == ']' || c == ')' || c == '}') depth--; + if (c == '[' || c == '(' || c == '{') + depth++; + else if (c == ']' || c == ')' || c == '}') + depth--; else if (c == '|' && depth == 0) { int n = 0; - const char** parts = py_split_subscript_args(ctx->arena, ann, &n); + const char **parts = py_split_subscript_args(ctx->arena, ann, &n); // py_split splits on commas — manually split on '|' at depth 0 (void)parts; (void)n; // Simpler: walk again splitting on '|'. int cap = 4; - const char** out = (const char**)cbm_arena_alloc(ctx->arena, - (size_t)(cap + 1) * sizeof(const char*)); - if (!out) break; + const char **out = (const char **)cbm_arena_alloc( + ctx->arena, (size_t)(cap + 1) * sizeof(const char *)); + if (!out) + break; int onum = 0; int d2 = 0; size_t start = 0; for (size_t j = 0; j <= len; j++) { char cc = (j < len) ? ann[j] : '|'; - if (cc == '[' || cc == '(' || cc == '{') d2++; - else if (cc == ']' || cc == ')' || cc == '}') d2--; + if (cc == '[' || cc == '(' || cc == '{') + d2++; + else if (cc == ']' || cc == ')' || cc == '}') + d2--; else if (cc == '|' && d2 == 0) { - char* p = py_trim_ws(ctx->arena, ann + start, j - start); - if (p && p[0] && onum < cap) out[onum++] = p; + char *p = py_trim_ws(ctx->arena, ann + start, j - start); + if (p && p[0] && onum < cap) + out[onum++] = p; start = j + 1; } } if (onum >= 2) { - const CBMType** members = (const CBMType**)cbm_arena_alloc(ctx->arena, - (size_t)(onum + 1) * sizeof(const CBMType*)); - if (!members) break; + const CBMType **members = (const CBMType **)cbm_arena_alloc( + ctx->arena, (size_t)(onum + 1) * sizeof(const CBMType *)); + if (!members) + break; for (int j = 0; j < onum; j++) { members[j] = py_resolve_annotation(ctx, out[j]); } @@ -2586,16 +2835,18 @@ static const CBMType* py_resolve_annotation(PyLSPContext* ctx, const char* ann) } } - const CBMType* t = cbm_scope_lookup(ctx->current_scope, ann); - if (!cbm_type_is_unknown(t)) return t; + const CBMType *t = cbm_scope_lookup(ctx->current_scope, ann); + if (!cbm_type_is_unknown(t)) + return t; if (ctx->module_qn) { - const char* qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, ann); - const CBMRegisteredType* rt = cbm_registry_lookup_type(ctx->registry, qn); - if (rt) return cbm_type_named(ctx->arena, qn); + const char *qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, ann); + const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, qn); + if (rt) + return cbm_type_named(ctx->arena, qn); } // Common builtin names go to BUILTIN. - static const char* builtins[] = {"int", "str", "bool", "float", "bytes", - "None", "complex", "bytearray", "object", "type", NULL}; + static const char *builtins[] = {"int", "str", "bool", "float", "bytes", "None", + "complex", "bytearray", "object", "type", NULL}; for (int i = 0; builtins[i]; i++) { if (strcmp(ann, builtins[i]) == 0) { return cbm_type_builtin(ctx->arena, ann); @@ -2604,12 +2855,13 @@ static const CBMType* py_resolve_annotation(PyLSPContext* ctx, const char* ann) return cbm_type_named(ctx->arena, ann); } -static void py_bind_parameters(PyLSPContext* ctx, TSNode params) { - if (ts_node_is_null(params)) return; +static void py_bind_parameters(PyLSPContext *ctx, TSNode params) { + if (ts_node_is_null(params)) + return; uint32_t nc = ts_node_named_child_count(params); for (uint32_t i = 0; i < nc; i++) { TSNode p = ts_node_named_child(params, i); - const char* pk = ts_node_type(p); + const char *pk = ts_node_type(p); TSNode ident_node = {0}; TSNode type_node = {0}; @@ -2635,10 +2887,9 @@ static void py_bind_parameters(PyLSPContext* ctx, TSNode params) { // list_splat_pattern / dictionary_splat_pattern. Handle here. bool is_splat = false; bool is_kwargs = false; - if (strcmp(pk, "typed_parameter") == 0 && - ts_node_named_child_count(p) > 0) { + if (strcmp(pk, "typed_parameter") == 0 && ts_node_named_child_count(p) > 0) { TSNode first = ts_node_named_child(p, 0); - const char* fk = ts_node_type(first); + const char *fk = ts_node_type(first); if (strcmp(fk, "list_splat_pattern") == 0) { is_splat = true; if (ts_node_named_child_count(first) > 0) @@ -2649,14 +2900,16 @@ static void py_bind_parameters(PyLSPContext* ctx, TSNode params) { ident_node = ts_node_named_child(first, 0); } } - if (ts_node_is_null(ident_node)) continue; + if (ts_node_is_null(ident_node)) + continue; - char* name = py_node_text(ctx, ident_node); - if (!name) continue; + char *name = py_node_text(ctx, ident_node); + if (!name) + continue; - const CBMType* t = cbm_type_unknown(); + const CBMType *t = cbm_type_unknown(); if (!ts_node_is_null(type_node)) { - char* ann = py_node_text(ctx, type_node); + char *ann = py_node_text(ctx, type_node); t = py_resolve_annotation(ctx, ann); } // Splat parameter wrapping: *args: T -> tuple[T, ...]; **kwargs: T @@ -2666,25 +2919,27 @@ static void py_bind_parameters(PyLSPContext* ctx, TSNode params) { t = cbm_type_template(ctx->arena, "tuple", &t, 1); } if (is_kwargs && t && !cbm_type_is_unknown(t)) { - const CBMType* str_t = cbm_type_builtin(ctx->arena, "str"); - const CBMType* args[3] = { str_t, t, NULL }; + const CBMType *str_t = cbm_type_builtin(ctx->arena, "str"); + const CBMType *args[3] = {str_t, t, NULL}; t = cbm_type_template(ctx->arena, "dict", args, 2); } cbm_scope_bind(ctx->current_scope, name, t); } } -static void py_process_function(PyLSPContext* ctx, TSNode func_node, const char* container_qn) { +static void py_process_function(PyLSPContext *ctx, TSNode func_node, const char *container_qn) { TSNode name_node = ts_node_child_by_field_name(func_node, "name", 4); - if (ts_node_is_null(name_node)) return; - char* fname = py_node_text(ctx, name_node); - if (!fname || !fname[0]) return; + if (ts_node_is_null(name_node)) + return; + char *fname = py_node_text(ctx, name_node); + if (!fname || !fname[0]) + return; - const char* prev_func = ctx->enclosing_func_qn; - const char* base_qn = container_qn ? container_qn : ctx->module_qn; + const char *prev_func = ctx->enclosing_func_qn; + const char *base_qn = container_qn ? container_qn : ctx->module_qn; ctx->enclosing_func_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", base_qn, fname); - CBMScope* saved = ctx->current_scope; + CBMScope *saved = ctx->current_scope; ctx->current_scope = cbm_scope_push(ctx->arena, ctx->current_scope); TSNode params = ts_node_child_by_field_name(func_node, "parameters", 10); @@ -2694,9 +2949,9 @@ static void py_process_function(PyLSPContext* ctx, TSNode func_node, const char* // wins over the unannotated `self` / `cls` parameter declaration. if (ctx->enclosing_class_qn) { cbm_scope_bind(ctx->current_scope, "self", - cbm_type_named(ctx->arena, ctx->enclosing_class_qn)); + cbm_type_named(ctx->arena, ctx->enclosing_class_qn)); cbm_scope_bind(ctx->current_scope, "cls", - cbm_type_named(ctx->arena, ctx->enclosing_class_qn)); + cbm_type_named(ctx->arena, ctx->enclosing_class_qn)); } TSNode body = ts_node_child_by_field_name(func_node, "body", 4); @@ -2706,7 +2961,7 @@ static void py_process_function(PyLSPContext* ctx, TSNode func_node, const char* uint32_t bnc = ts_node_named_child_count(body); for (uint32_t i = 0; i < bnc; i++) { TSNode c = ts_node_named_child(body, i); - const char* ck = ts_node_type(c); + const char *ck = ts_node_type(c); if (strcmp(ck, "function_definition") == 0) { py_process_function(ctx, c, ctx->enclosing_func_qn); } else if (strcmp(ck, "decorated_definition") == 0) { @@ -2724,20 +2979,23 @@ static void py_process_function(PyLSPContext* ctx, TSNode func_node, const char* } /* Return true iff func_node's name is __init__ or __post_init__. */ -static bool py_is_init_method(PyLSPContext* ctx, TSNode func_node) { +static bool py_is_init_method(PyLSPContext *ctx, TSNode func_node) { TSNode name = ts_node_child_by_field_name(func_node, "name", 4); - if (ts_node_is_null(name)) return false; - char* nm = py_node_text(ctx, name); + if (ts_node_is_null(name)) + return false; + char *nm = py_node_text(ctx, name); return nm && (strcmp(nm, "__init__") == 0 || strcmp(nm, "__post_init__") == 0); } -static void py_process_class(PyLSPContext* ctx, TSNode class_node) { +static void py_process_class(PyLSPContext *ctx, TSNode class_node) { TSNode name_node = ts_node_child_by_field_name(class_node, "name", 4); - if (ts_node_is_null(name_node)) return; - char* cname = py_node_text(ctx, name_node); - if (!cname || !cname[0]) return; + if (ts_node_is_null(name_node)) + return; + char *cname = py_node_text(ctx, name_node); + if (!cname || !cname[0]) + return; - const char* prev_class = ctx->enclosing_class_qn; + const char *prev_class = ctx->enclosing_class_qn; ctx->enclosing_class_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, cname); TSNode body = ts_node_child_by_field_name(class_node, "body", 4); @@ -2749,23 +3007,21 @@ static void py_process_class(PyLSPContext* ctx, TSNode class_node) { // reference them. for (uint32_t i = 0; i < bnc; i++) { TSNode c = ts_node_named_child(body, i); - const char* ck = ts_node_type(c); - if (strcmp(ck, "expression_statement") == 0 && - ts_node_named_child_count(c) > 0) { + const char *ck = ts_node_type(c); + if (strcmp(ck, "expression_statement") == 0 && ts_node_named_child_count(c) > 0) { TSNode inner = ts_node_named_child(c, 0); - const char* ik = ts_node_type(inner); + const char *ik = ts_node_type(inner); // class C: x: int = 1 → bind x as field if (strcmp(ik, "assignment") == 0) { TSNode left = ts_node_child_by_field_name(inner, "left", 4); TSNode ann = ts_node_child_by_field_name(inner, "type", 4); if (!ts_node_is_null(left) && !ts_node_is_null(ann) && strcmp(ts_node_type(left), "identifier") == 0) { - char* fname = py_node_text(ctx, left); - char* atext = py_node_text(ctx, ann); + char *fname = py_node_text(ctx, left); + char *atext = py_node_text(ctx, ann); if (fname && atext) { - const CBMType* ft = py_resolve_annotation(ctx, atext); - py_register_instance_field(ctx, - ctx->enclosing_class_qn, fname, ft); + const CBMType *ft = py_resolve_annotation(ctx, atext); + py_register_instance_field(ctx, ctx->enclosing_class_qn, fname, ft); } } } @@ -2774,13 +3030,14 @@ static void py_process_class(PyLSPContext* ctx, TSNode class_node) { // Second pass: __init__ (so self.x assignments populate fields). for (uint32_t i = 0; i < bnc; i++) { TSNode c = ts_node_named_child(body, i); - const char* ck = ts_node_type(c); + const char *ck = ts_node_type(c); TSNode fn_node = c; bool is_decorated = strcmp(ck, "decorated_definition") == 0; if (is_decorated) { fn_node = ts_node_child_by_field_name(c, "definition", 10); if (ts_node_is_null(fn_node) || - strcmp(ts_node_type(fn_node), "function_definition") != 0) continue; + strcmp(ts_node_type(fn_node), "function_definition") != 0) + continue; } else if (strcmp(ck, "function_definition") != 0) { continue; } @@ -2791,7 +3048,7 @@ static void py_process_class(PyLSPContext* ctx, TSNode class_node) { // Third pass: every other method (and nested classes). for (uint32_t i = 0; i < bnc; i++) { TSNode c = ts_node_named_child(body, i); - const char* ck = ts_node_type(c); + const char *ck = ts_node_type(c); if (strcmp(ck, "function_definition") == 0) { if (!py_is_init_method(ctx, c)) { py_process_function(ctx, c, ctx->enclosing_class_qn); @@ -2815,23 +3072,28 @@ static void py_process_class(PyLSPContext* ctx, TSNode class_node) { /* Bind every Class / Type definition from the registry into the root scope * as NAMED(qn). Lets bare references like `Foo()` and `c: Foo` resolve to * the registered class type. */ -static void py_bind_module_classes(PyLSPContext* ctx) { - if (!ctx || !ctx->registry || !ctx->module_qn) return; - const CBMRegisteredType* types = ctx->registry->types; +static void py_bind_module_classes(PyLSPContext *ctx) { + if (!ctx || !ctx->registry || !ctx->module_qn) + return; + const CBMRegisteredType *types = ctx->registry->types; int n = ctx->registry->type_count; size_t prefix_len = strlen(ctx->module_qn); for (int i = 0; i < n; i++) { - const char* qn = types[i].qualified_name; - const char* sname = types[i].short_name; - if (!qn || !sname) continue; - if (strncmp(qn, ctx->module_qn, prefix_len) != 0) continue; - if (qn[prefix_len] != '.') continue; + const char *qn = types[i].qualified_name; + const char *sname = types[i].short_name; + if (!qn || !sname) + continue; + if (strncmp(qn, ctx->module_qn, prefix_len) != 0) + continue; + if (qn[prefix_len] != '.') + continue; cbm_scope_bind(ctx->current_scope, sname, cbm_type_named(ctx->arena, qn)); } } -void py_lsp_process_file(PyLSPContext* ctx, TSNode root) { - if (!ctx || ts_node_is_null(root)) return; +void py_lsp_process_file(PyLSPContext *ctx, TSNode root) { + if (!ctx || ts_node_is_null(root)) + return; py_lsp_bind_imports(ctx); py_bind_module_classes(ctx); @@ -2842,19 +3104,20 @@ void py_lsp_process_file(PyLSPContext* ctx, TSNode root) { py_process_statement(ctx, c); } // Pass 2: top-level calls (rare) and nested definitions. - const char* prev_func = ctx->enclosing_func_qn; + const char *prev_func = ctx->enclosing_func_qn; ctx->enclosing_func_qn = cbm_arena_sprintf(ctx->arena, "%s.__module__", ctx->module_qn); for (uint32_t i = 0; i < nc; i++) { TSNode c = ts_node_named_child(root, i); - const char* ck = ts_node_type(c); + const char *ck = ts_node_type(c); if (strcmp(ck, "function_definition") == 0) { py_process_function(ctx, c, NULL); } else if (strcmp(ck, "class_definition") == 0) { py_process_class(ctx, c); } else if (strcmp(ck, "decorated_definition") == 0) { TSNode def = ts_node_child_by_field_name(c, "definition", 10); - if (ts_node_is_null(def)) continue; - const char* dk = ts_node_type(def); + if (ts_node_is_null(def)) + continue; + const char *dk = ts_node_type(def); if (strcmp(dk, "function_definition") == 0) { py_process_function(ctx, def, NULL); } else if (strcmp(dk, "class_definition") == 0) { @@ -2869,9 +3132,10 @@ void py_lsp_process_file(PyLSPContext* ctx, TSNode root) { } /* Register one definition into the registry. Returns true if recognized. */ -static bool py_register_def(CBMArena* arena, CBMTypeRegistry* reg, CBMDefinition* d, - const char* module_qn) { - if (!d || !d->qualified_name || !d->name || !d->label) return false; +static bool py_register_def(CBMArena *arena, CBMTypeRegistry *reg, CBMDefinition *d, + const char *module_qn) { + if (!d || !d->qualified_name || !d->name || !d->label) + return false; if (strcmp(d->label, "Class") == 0 || strcmp(d->label, "Type") == 0) { CBMRegisteredType rt; @@ -2884,56 +3148,66 @@ static bool py_register_def(CBMArena* arena, CBMTypeRegistry* reg, CBMDefinition // list as a single base_classes[0] entry. Split commas and strip // parens / whitespace / generic subscripts to recover usable QNs. int cap = 8; - const char** embedded = (const char**)cbm_arena_alloc(arena, - (size_t)(cap + 1) * sizeof(const char*)); + const char **embedded = + (const char **)cbm_arena_alloc(arena, (size_t)(cap + 1) * sizeof(const char *)); int n = 0; for (int j = 0; d->base_classes[j]; j++) { - const char* raw = d->base_classes[j]; + const char *raw = d->base_classes[j]; size_t raw_len = strlen(raw); // Trim outer parens if present. size_t lo = 0; size_t hi = raw_len; - while (lo < hi && (raw[lo] == '(' || raw[lo] == ' ')) lo++; - while (hi > lo && (raw[hi - 1] == ')' || raw[hi - 1] == ' ')) hi--; + while (lo < hi && (raw[lo] == '(' || raw[lo] == ' ')) + lo++; + while (hi > lo && (raw[hi - 1] == ')' || raw[hi - 1] == ' ')) + hi--; // Split on commas at depth 0 (ignore commas inside [], ()). size_t start = lo; int depth = 0; for (size_t k = lo; k <= hi; k++) { char c = (k < hi) ? raw[k] : ','; - if (c == '[' || c == '(') depth++; - else if (c == ']' || c == ')') depth--; + if (c == '[' || c == '(') + depth++; + else if (c == ']' || c == ')') + depth--; else if (c == ',' && depth == 0) { size_t s = start; size_t e = k; - while (s < e && raw[s] == ' ') s++; - while (e > s && raw[e - 1] == ' ') e--; + while (s < e && raw[s] == ' ') + s++; + while (e > s && raw[e - 1] == ' ') + e--; // Strip generic subscript: "Foo[T]" -> "Foo" size_t lb = s; - while (lb < e && raw[lb] != '[') lb++; + while (lb < e && raw[lb] != '[') + lb++; size_t name_end = lb; // Skip keyword args like "metaclass=Meta" (contains '=') size_t eq = s; - while (eq < name_end && raw[eq] != '=') eq++; + while (eq < name_end && raw[eq] != '=') + eq++; if (eq >= name_end && name_end > s) { size_t blen = name_end - s; - char* bname = (char*)cbm_arena_alloc(arena, blen + 1); + char *bname = (char *)cbm_arena_alloc(arena, blen + 1); if (bname) { memcpy(bname, raw + s, blen); bname[blen] = '\0'; if (n >= cap) { int new_cap = cap * 2; - const char** grown = (const char**)cbm_arena_alloc(arena, - (size_t)(new_cap + 1) * sizeof(const char*)); + const char **grown = (const char **)cbm_arena_alloc( + arena, (size_t)(new_cap + 1) * sizeof(const char *)); if (grown) { - for (int q = 0; q < n; q++) grown[q] = embedded[q]; + for (int q = 0; q < n; q++) + grown[q] = embedded[q]; embedded = grown; cap = new_cap; } } if (n < cap) { - embedded[n++] = strchr(bname, '.') - ? bname - : cbm_arena_sprintf(arena, "%s.%s", module_qn, bname); + embedded[n++] = + strchr(bname, '.') + ? bname + : cbm_arena_sprintf(arena, "%s.%s", module_qn, bname); } } } @@ -2942,7 +3216,8 @@ static bool py_register_def(CBMArena* arena, CBMTypeRegistry* reg, CBMDefinition } } embedded[n] = NULL; - if (n > 0) rt.embedded_types = embedded; + if (n > 0) + rt.embedded_types = embedded; } cbm_registry_add_type(reg, rt); return true; @@ -2957,17 +3232,18 @@ static bool py_register_def(CBMArena* arena, CBMTypeRegistry* reg, CBMDefinition // Translate Python decorators into flags + carry the QN list. if (d->decorators) { int dec_count = 0; - while (d->decorators[dec_count]) dec_count++; + while (d->decorators[dec_count]) + dec_count++; if (dec_count > 0) { - const char** dec_qns = (const char**)cbm_arena_alloc(arena, - (size_t)(dec_count + 1) * sizeof(const char*)); + const char **dec_qns = (const char **)cbm_arena_alloc( + arena, (size_t)(dec_count + 1) * sizeof(const char *)); for (int j = 0; j < dec_count; j++) { - const char* dec = d->decorators[j]; + const char *dec = d->decorators[j]; dec_qns[j] = cbm_arena_strdup(arena, dec); // Match by short name (last "." segment) so both // "property" and "functools.cache" / "abc.abstractmethod" // forms light up. - const char* short_dec = strrchr(dec, '.'); + const char *short_dec = strrchr(dec, '.'); short_dec = short_dec ? short_dec + 1 : dec; if (strcmp(short_dec, "property") == 0) rf.flags |= CBM_FUNC_FLAG_PROPERTY; @@ -2987,29 +3263,29 @@ static bool py_register_def(CBMArena* arena, CBMTypeRegistry* reg, CBMDefinition } } - const CBMType** ret_types = NULL; + const CBMType **ret_types = NULL; // Prefer d->return_type (full text) when it has subscript brackets // — extract_defs.c::extract_return_types strips them in its array // form, which loses subscript args like Optional[Foo]. d->return_type // is the cbm_node_text of the whole annotation node, which is the // form py_parse_type_text_qn expects. - bool prefer_full = - d->return_type && d->return_type[0] && strchr(d->return_type, '['); + bool prefer_full = d->return_type && d->return_type[0] && strchr(d->return_type, '['); if (prefer_full) { - ret_types = (const CBMType**)cbm_arena_alloc(arena, 2 * sizeof(const CBMType*)); + ret_types = (const CBMType **)cbm_arena_alloc(arena, 2 * sizeof(const CBMType *)); ret_types[0] = py_parse_type_text_qn(arena, d->return_type, module_qn); ret_types[1] = NULL; } else if (d->return_types && d->return_types[0]) { int count = 0; - while (d->return_types[count]) count++; - ret_types = (const CBMType**)cbm_arena_alloc(arena, - (size_t)(count + 1) * sizeof(const CBMType*)); + while (d->return_types[count]) + count++; + ret_types = (const CBMType **)cbm_arena_alloc(arena, (size_t)(count + 1) * + sizeof(const CBMType *)); for (int j = 0; j < count; j++) { ret_types[j] = py_parse_type_text_qn(arena, d->return_types[j], module_qn); } ret_types[count] = NULL; } else if (d->return_type && d->return_type[0]) { - ret_types = (const CBMType**)cbm_arena_alloc(arena, 2 * sizeof(const CBMType*)); + ret_types = (const CBMType **)cbm_arena_alloc(arena, 2 * sizeof(const CBMType *)); ret_types[0] = py_parse_type_text_qn(arena, d->return_type, module_qn); ret_types[1] = NULL; } @@ -3018,8 +3294,8 @@ static bool py_register_def(CBMArena* arena, CBMTypeRegistry* reg, CBMDefinition if (strcmp(d->label, "Method") == 0) { // Receiver type: the enclosing class. Python def metadata may // record the class name in `receiver` or via the QN itself. - const char* qn = d->qualified_name; - const char* last_dot = strrchr(qn, '.'); + const char *qn = d->qualified_name; + const char *last_dot = strrchr(qn, '.'); if (last_dot && last_dot != qn) { size_t prefix_len = (size_t)(last_dot - qn); rf.receiver_type = cbm_arena_strndup(arena, qn, prefix_len); @@ -3033,16 +3309,17 @@ static bool py_register_def(CBMArena* arena, CBMTypeRegistry* reg, CBMDefinition /* ── cbm_run_py_lsp: single-file entry point ──────────────────── */ -void cbm_run_py_lsp(CBMArena* arena, CBMFileResult* result, - const char* source, int source_len, TSNode root) { - if (!arena || !result) return; +void cbm_run_py_lsp(CBMArena *arena, CBMFileResult *result, const char *source, int source_len, + TSNode root) { + if (!arena || !result) + return; CBMTypeRegistry reg; cbm_registry_init(®, arena); cbm_python_stdlib_register(®, arena); - const char* module_qn = result->module_qn; + const char *module_qn = result->module_qn; // Register the file's own definitions so calls inside this file can // resolve via the registry. @@ -3057,7 +3334,7 @@ void cbm_run_py_lsp(CBMArena* arena, CBMFileResult* result, ctx.syn_calls = &result->calls; for (int i = 0; i < result->imports.count; i++) { - CBMImport* imp = &result->imports.items[i]; + CBMImport *imp = &result->imports.items[i]; if (imp->local_name && imp->module_path) { py_lsp_add_import(&ctx, imp->local_name, imp->module_path); } @@ -3068,27 +3345,33 @@ void cbm_run_py_lsp(CBMArena* arena, CBMFileResult* result, /* ── Cross-file + batch ───────────────────────────────────────── */ -extern const TSLanguage* tree_sitter_python(void); +extern const TSLanguage *tree_sitter_python(void); /* Split a "|"-separated list into a NULL-terminated array of arena copies. */ -static const char** py_split_pipe(CBMArena* arena, const char* text) { - if (!text || !text[0]) return NULL; +static const char **py_split_pipe(CBMArena *arena, const char *text) { + if (!text || !text[0]) + return NULL; int count = 1; - for (const char* p = text; *p; p++) if (*p == '|') count++; - const char** out = (const char**)cbm_arena_alloc(arena, - (size_t)(count + 1) * sizeof(const char*)); - if (!out) return NULL; + for (const char *p = text; *p; p++) + if (*p == '|') + count++; + const char **out = + (const char **)cbm_arena_alloc(arena, (size_t)(count + 1) * sizeof(const char *)); + if (!out) + return NULL; int idx = 0; - const char* start = text; - for (const char* p = text; ; p++) { + const char *start = text; + for (const char *p = text;; p++) { if (*p == '|' || *p == '\0') { size_t n = (size_t)(p - start); - char* s = (char*)cbm_arena_alloc(arena, n + 1); - if (!s) return NULL; + char *s = (char *)cbm_arena_alloc(arena, n + 1); + if (!s) + return NULL; memcpy(s, start, n); s[n] = '\0'; out[idx++] = s; - if (*p == '\0') break; + if (*p == '\0') + break; start = p + 1; } } @@ -3098,11 +3381,12 @@ static const char** py_split_pipe(CBMArena* arena, const char* text) { /* Build a registry from CBMLSPDef[] supplied by the caller — covers both * the source file's own defs and cross-file referenced defs. */ -static void py_register_lsp_defs(CBMArena* arena, CBMTypeRegistry* reg, - CBMLSPDef* defs, int def_count) { +static void py_register_lsp_defs(CBMArena *arena, CBMTypeRegistry *reg, CBMLSPDef *defs, + int def_count) { for (int i = 0; i < def_count; i++) { - CBMLSPDef* d = &defs[i]; - if (!d->qualified_name || !d->short_name || !d->label) continue; + CBMLSPDef *d = &defs[i]; + if (!d->qualified_name || !d->short_name || !d->label) + continue; if (strcmp(d->label, "Type") == 0 || strcmp(d->label, "Class") == 0 || strcmp(d->label, "Interface") == 0 || strcmp(d->label, "Protocol") == 0) { @@ -3110,9 +3394,8 @@ static void py_register_lsp_defs(CBMArena* arena, CBMTypeRegistry* reg, memset(&rt, 0, sizeof(rt)); rt.qualified_name = d->qualified_name; /* borrowed — d outlives this call */ rt.short_name = d->short_name; - rt.is_interface = d->is_interface || - strcmp(d->label, "Interface") == 0 || - strcmp(d->label, "Protocol") == 0; + rt.is_interface = d->is_interface || strcmp(d->label, "Interface") == 0 || + strcmp(d->label, "Protocol") == 0; rt.embedded_types = py_split_pipe(arena, d->embedded_types); if (d->method_names_str && d->method_names_str[0]) { rt.method_names = py_split_pipe(arena, d->method_names_str); @@ -3127,14 +3410,15 @@ static void py_register_lsp_defs(CBMArena* arena, CBMTypeRegistry* reg, rf.short_name = d->short_name; // Build FUNC type from "|"-separated return types. - const char** ret_strs = py_split_pipe(arena, d->return_types); - const CBMType** ret_types = NULL; + const char **ret_strs = py_split_pipe(arena, d->return_types); + const CBMType **ret_types = NULL; if (ret_strs) { int n = 0; - while (ret_strs[n]) n++; + while (ret_strs[n]) + n++; if (n > 0) { - ret_types = (const CBMType**)cbm_arena_alloc(arena, - (size_t)(n + 1) * sizeof(const CBMType*)); + ret_types = (const CBMType **)cbm_arena_alloc( + arena, (size_t)(n + 1) * sizeof(const CBMType *)); for (int j = 0; j < n; j++) { ret_types[j] = cbm_type_named(arena, ret_strs[j]); } @@ -3149,7 +3433,7 @@ static void py_register_lsp_defs(CBMArena* arena, CBMTypeRegistry* reg, CBMRegisteredType auto_t; memset(&auto_t, 0, sizeof(auto_t)); auto_t.qualified_name = rf.receiver_type; - const char* dot = strrchr(d->receiver_type, '.'); + const char *dot = strrchr(d->receiver_type, '.'); auto_t.short_name = dot ? dot + 1 : rf.receiver_type; /* borrowed substring */ cbm_registry_add_type(reg, auto_t); } @@ -3159,26 +3443,27 @@ static void py_register_lsp_defs(CBMArena* arena, CBMTypeRegistry* reg, } } -void cbm_run_py_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 (!arena || !source || source_len <= 0 || !out) return; +void cbm_run_py_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 (!arena || !source || source_len <= 0 || !out) + return; - TSParser* parser = NULL; - TSTree* tree = cached_tree; + TSParser *parser = NULL; + TSTree *tree = cached_tree; bool owns_tree = false; if (!tree) { parser = ts_parser_new(); - if (!parser) return; + if (!parser) + return; ts_parser_set_language(parser, tree_sitter_python()); tree = ts_parser_parse_string(parser, NULL, source, (uint32_t)source_len); owns_tree = true; - if (!tree) { ts_parser_delete(parser); return; } + if (!tree) { + ts_parser_delete(parser); + return; + } } TSNode root = ts_tree_root_node(tree); @@ -3201,23 +3486,27 @@ void cbm_run_py_lsp_cross( } py_lsp_process_file(&ctx, root); - if (owns_tree && tree) ts_tree_delete(tree); - if (parser) ts_parser_delete(parser); + if (owns_tree && tree) + ts_tree_delete(tree); + if (parser) + ts_parser_delete(parser); } /* ── Tier 2: pre-built per-language registry (mirrors Go pilot) ── */ -CBMTypeRegistry* cbm_py_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; +CBMTypeRegistry *cbm_py_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_python_stdlib_register(reg, arena); /* Filter to Python defs only — defs[] is mixed-language all_defs. */ for (int i = 0; i < def_count; i++) { - CBMLSPDef* d = &defs[i]; - if (d->lang != CBM_LANG_PYTHON) continue; + CBMLSPDef *d = &defs[i]; + if (d->lang != CBM_LANG_PYTHON) + continue; /* Reuse the existing register fn on a single-def slice (n=1 inline). */ py_register_lsp_defs(arena, reg, d, 1); } @@ -3226,26 +3515,28 @@ CBMTypeRegistry* cbm_py_build_cross_registry( return reg; } -void cbm_run_py_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 (!arena || !source || source_len <= 0 || !out || !reg) return; +void cbm_run_py_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 (!arena || !source || source_len <= 0 || !out || !reg) + return; - TSParser* parser = NULL; - TSTree* tree = cached_tree; + TSParser *parser = NULL; + TSTree *tree = cached_tree; bool owns_tree = false; if (!tree) { parser = ts_parser_new(); - if (!parser) return; + if (!parser) + return; ts_parser_set_language(parser, tree_sitter_python()); tree = ts_parser_parse_string(parser, NULL, source, (uint32_t)source_len); owns_tree = true; - if (!tree) { ts_parser_delete(parser); return; } + if (!tree) { + ts_parser_delete(parser); + return; + } } TSNode root = ts_tree_root_node(tree); @@ -3258,20 +3549,22 @@ void cbm_run_py_lsp_cross_with_registry( } py_lsp_process_file(&ctx, root); - if (owns_tree && tree) ts_tree_delete(tree); - if (parser) ts_parser_delete(parser); + if (owns_tree && tree) + ts_tree_delete(tree); + if (parser) + ts_parser_delete(parser); } -void cbm_batch_py_lsp_cross( - CBMArena* arena, - CBMBatchPyLSPFile* files, int file_count, - CBMResolvedCallArray* out) { - if (!arena || !files || file_count <= 0 || !out) return; +void cbm_batch_py_lsp_cross(CBMArena *arena, CBMBatchPyLSPFile *files, int file_count, + CBMResolvedCallArray *out) { + if (!arena || !files || file_count <= 0 || !out) + return; for (int f = 0; f < file_count; f++) { - CBMBatchPyLSPFile* file = &files[f]; + CBMBatchPyLSPFile *file = &files[f]; memset(&out[f], 0, sizeof(CBMResolvedCallArray)); - if (!file->source || file->source_len <= 0) continue; + if (!file->source || file->source_len <= 0) + continue; CBMArena file_arena; cbm_arena_init(&file_arena); @@ -3279,29 +3572,25 @@ void cbm_batch_py_lsp_cross( CBMResolvedCallArray file_out; memset(&file_out, 0, sizeof(file_out)); - cbm_run_py_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); + cbm_run_py_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); if (file_out.count > 0) { out[f].count = file_out.count; - out[f].items = (CBMResolvedCall*)cbm_arena_alloc(arena, - (size_t)file_out.count * sizeof(CBMResolvedCall)); + out[f].items = (CBMResolvedCall *)cbm_arena_alloc(arena, (size_t)file_out.count * + sizeof(CBMResolvedCall)); if (out[f].items) { 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; + 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; + dst->reason = src->reason ? cbm_arena_strdup(arena, src->reason) : NULL; } } else { out[f].count = 0; @@ -3315,7 +3604,7 @@ void cbm_batch_py_lsp_cross( /* ── Stdlib stub — Phase 10 replaces with auto-generated body ─── */ #ifndef CBM_PYTHON_STDLIB_GENERATED -void cbm_python_stdlib_register(CBMTypeRegistry* reg, CBMArena* arena) { +void cbm_python_stdlib_register(CBMTypeRegistry *reg, CBMArena *arena) { (void)reg; (void)arena; } diff --git a/internal/cbm/lsp/py_lsp.h b/internal/cbm/lsp/py_lsp.h index 5548d4ef3..57553fd3b 100644 --- a/internal/cbm/lsp/py_lsp.h +++ b/internal/cbm/lsp/py_lsp.h @@ -5,47 +5,47 @@ #include "scope.h" #include "type_registry.h" #include "../cbm.h" -#include "go_lsp.h" // CBMLSPDef, CBMResolvedCallArray reused across languages +#include "go_lsp.h" // CBMLSPDef, CBMResolvedCallArray reused across languages // Lambda body record. When a lambda is assigned to a name (`fn = // lambda x: x.method()`), we stash its parameter list + body so the // next call site (`fn(arg)`) can do call-site driven inference. typedef struct { - const char* name; // bound name, e.g. "fn" - TSNode lambda_node; // the lambda AST node + const char *name; // bound name, e.g. "fn" + TSNode lambda_node; // the lambda AST node } CBMLambdaEntry; // Function-as-dict-value entry. When `funcs = {"a": foo, "b": bar}` is // seen, we record the per-key target QN so `funcs["a"]()` emits an // edge to foo. typedef struct { - const char* var_name; // e.g. "funcs" - const char* literal_key; // e.g. "a" - const char* target_qn; // e.g. "test.main.foo" + const char *var_name; // e.g. "funcs" + const char *literal_key; // e.g. "a" + const char *target_qn; // e.g. "test.main.foo" } CBMDictLiteralEntry; // PyLSPContext holds state for one Python file's type evaluation. typedef struct { - CBMArena* arena; - const char* source; + CBMArena *arena; + const char *source; int source_len; - const CBMTypeRegistry* registry; - CBMScope* current_scope; + const CBMTypeRegistry *registry; + CBMScope *current_scope; // Import map: local_name -> module_qn (arena-allocated, NULL-terminated). // Built from CBMFileResult.imports. - const char** import_local_names; - const char** import_module_qns; + const char **import_local_names; + const char **import_module_qns; int import_count; // Current function/class context for resolving `self`/`cls` and emitting // caller QNs. - const char* enclosing_func_qn; - const char* enclosing_class_qn; - const char* module_qn; + const char *enclosing_func_qn; + const char *enclosing_class_qn; + const char *module_qn; // Output: resolved calls accumulate here. - CBMResolvedCallArray* resolved_calls; + CBMResolvedCallArray *resolved_calls; // Syntactic-call list (result->calls), borrowed from the per-file // extraction result. The downstream pipeline only turns a resolved_call @@ -57,15 +57,15 @@ typedef struct { // non-NULL, py_emit_dunder_call injects a matching synthetic CBMCall so the // recovered dunder call becomes a real CALLS edge. NULL in the cross-file // path (no result->calls available there). Mirrors RustLSPContext.syn_calls. - CBMCallArray* syn_calls; + CBMCallArray *syn_calls; // Lambda registry: simple linear list, looked up by name. - CBMLambdaEntry* lambdas; + CBMLambdaEntry *lambdas; int lambda_count; int lambda_cap; // Dict-literal-as-dispatch-table tracking. - CBMDictLiteralEntry* dict_literals; + CBMDictLiteralEntry *dict_literals; int dict_literal_count; int dict_literal_cap; @@ -74,77 +74,69 @@ typedef struct { } PyLSPContext; // Initialize a PyLSPContext for processing one file. -void py_lsp_init(PyLSPContext* ctx, CBMArena* arena, const char* source, int source_len, - const CBMTypeRegistry* registry, const char* module_qn, CBMResolvedCallArray* out); +void py_lsp_init(PyLSPContext *ctx, CBMArena *arena, const char *source, int source_len, + const CBMTypeRegistry *registry, const char *module_qn, CBMResolvedCallArray *out); // Add an import mapping (call once per CBMImport entry). -void py_lsp_add_import(PyLSPContext* ctx, const char* local_name, const char* module_qn); +void py_lsp_add_import(PyLSPContext *ctx, const char *local_name, const char *module_qn); // Bind every recorded import into the root scope. Idempotent. Called from // py_lsp_process_file before AST traversal. Exposed for unit tests that // don't need a tree-sitter parse. -void py_lsp_bind_imports(PyLSPContext* ctx); +void py_lsp_bind_imports(PyLSPContext *ctx); // Test hook: lookup a name in the context's scope chain. Returns // cbm_type_unknown() if not bound. -const CBMType* py_lsp_lookup_in_scope(const PyLSPContext* ctx, const char* name); +const CBMType *py_lsp_lookup_in_scope(const PyLSPContext *ctx, const char *name); // Process all functions/classes in a file's AST, evaluating types and resolving calls. // root must be the tree-sitter Python `module` node. -void py_lsp_process_file(PyLSPContext* ctx, TSNode root); +void py_lsp_process_file(PyLSPContext *ctx, TSNode root); // Entry point: build registry from file's own defs + run LSP resolution. // Mirrors cbm_run_go_lsp / cbm_run_c_lsp. -void cbm_run_py_lsp(CBMArena* arena, CBMFileResult* result, - const char* source, int source_len, TSNode root); +void cbm_run_py_lsp(CBMArena *arena, CBMFileResult *result, const char *source, int source_len, + TSNode root); // Register Python stdlib types and functions into a registry. // Auto-generated by scripts/gen-py-stdlib.py from typeshed. // Phase 10 fills the body; Phase 2 ships a no-op so linkage works. -void cbm_python_stdlib_register(CBMTypeRegistry* reg, CBMArena* arena); +void cbm_python_stdlib_register(CBMTypeRegistry *reg, CBMArena *arena); // --- Cross-file LSP resolution --- -void cbm_run_py_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, // NULL = parse internally - CBMResolvedCallArray* out); +void cbm_run_py_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, // NULL = parse internally + CBMResolvedCallArray *out); /* Tier 2: pre-built per-language registry (mirrors Go pilot). * Filters all_defs to Python entries, builds + finalizes once. */ -CBMTypeRegistry* cbm_py_build_cross_registry( - CBMArena* arena, CBMLSPDef* defs, int def_count); +CBMTypeRegistry *cbm_py_build_cross_registry(CBMArena *arena, CBMLSPDef *defs, int def_count); -void cbm_run_py_lsp_cross_with_registry( - CBMArena* arena, - const char* source, int source_len, - const char* module_qn, - CBMTypeRegistry* reg, // pre-built, finalized, READ-ONLY - const char** import_names, const char** import_qns, int import_count, - TSTree* cached_tree, - CBMResolvedCallArray* out); +void cbm_run_py_lsp_cross_with_registry(CBMArena *arena, const char *source, int source_len, + const char *module_qn, + CBMTypeRegistry *reg, // pre-built, finalized, READ-ONLY + const char **import_names, const char **import_qns, + int import_count, TSTree *cached_tree, + CBMResolvedCallArray *out); // --- Batch cross-file LSP --- typedef struct { - const char* source; + const char *source; int source_len; - const char* module_qn; - TSTree* cached_tree; // from TSTree caching (NULL = parse internally) - CBMLSPDef* defs; // combined file-local + cross-file defs + const char *module_qn; + TSTree *cached_tree; // from TSTree caching (NULL = parse internally) + CBMLSPDef *defs; // combined file-local + cross-file defs int def_count; - const char** import_names; // parallel arrays, import_count long - const char** import_qns; + const char **import_names; // parallel arrays, import_count long + const char **import_qns; int import_count; } CBMBatchPyLSPFile; -void cbm_batch_py_lsp_cross( - CBMArena* arena, - CBMBatchPyLSPFile* files, int file_count, - CBMResolvedCallArray* out); +void cbm_batch_py_lsp_cross(CBMArena *arena, CBMBatchPyLSPFile *files, int file_count, + CBMResolvedCallArray *out); #endif // CBM_LSP_PY_LSP_H diff --git a/internal/cbm/lsp/rust_lsp.c b/internal/cbm/lsp/rust_lsp.c index c7103581b..4ef4bdf7b 100644 --- a/internal/cbm/lsp/rust_lsp.c +++ b/internal/cbm/lsp/rust_lsp.c @@ -38,27 +38,28 @@ * ════════════════════════════════════════════════════════════════════ */ /* Forward declarations for early callers in the file. */ -static void rust_resolve_calls_in_node(RustLSPContext* ctx, TSNode node); -static void rust_emit_resolved_call(RustLSPContext* ctx, const char* callee_qn, - const char* strategy, float confidence); -static void rust_inject_syn_call(RustLSPContext* ctx, const char* callee_qn); -static void rust_emit_unresolved_call(RustLSPContext* ctx, const char* expr_text, - const char* reason); -static const CBMType* rust_lookup_field(RustLSPContext* ctx, - const char* type_qn, const char* field_name, int depth); -static const CBMRegisteredFunc* rust_lookup_method_in_trait(RustLSPContext* ctx, - const char* trait_qn, const char* method_name); -static char* rust_node_text(RustLSPContext* ctx, TSNode node); -static const char* convert_path_to_qn(CBMArena* arena, const char* path); -static bool rust_type_derefs_to_first_arg(const char* type_qn); -static const char* rust_lookup_type_param_bound(RustLSPContext* ctx, - const char* name); -static void rust_collect_bounds_from_text(RustLSPContext* ctx, const char* text); -static void rust_record_type_param_bound(RustLSPContext* ctx, - const char* param_name, const char* trait_qn); +static void rust_resolve_calls_in_node(RustLSPContext *ctx, TSNode node); +static void rust_emit_resolved_call(RustLSPContext *ctx, const char *callee_qn, + const char *strategy, float confidence); +static void rust_inject_syn_call(RustLSPContext *ctx, const char *callee_qn); +static void rust_emit_unresolved_call(RustLSPContext *ctx, const char *expr_text, + const char *reason); +static const CBMType *rust_lookup_field(RustLSPContext *ctx, const char *type_qn, + const char *field_name, int depth); +static const CBMRegisteredFunc *rust_lookup_method_in_trait(RustLSPContext *ctx, + const char *trait_qn, + const char *method_name); +static char *rust_node_text(RustLSPContext *ctx, TSNode node); +static const char *convert_path_to_qn(CBMArena *arena, const char *path); +static bool rust_type_derefs_to_first_arg(const char *type_qn); +static const char *rust_lookup_type_param_bound(RustLSPContext *ctx, const char *name); +static void rust_collect_bounds_from_text(RustLSPContext *ctx, const char *text); +static void rust_record_type_param_bound(RustLSPContext *ctx, const char *param_name, + const char *trait_qn); -void rust_lsp_init(RustLSPContext* ctx, CBMArena* arena, const char* source, int source_len, - const CBMTypeRegistry* registry, const char* module_qn, CBMResolvedCallArray* out) { +void rust_lsp_init(RustLSPContext *ctx, CBMArena *arena, const char *source, int source_len, + const CBMTypeRegistry *registry, const char *module_qn, + CBMResolvedCallArray *out) { memset(ctx, 0, sizeof(RustLSPContext)); ctx->arena = arena; ctx->source = source; @@ -68,25 +69,27 @@ void rust_lsp_init(RustLSPContext* ctx, CBMArena* arena, const char* source, int ctx->resolved_calls = out; ctx->current_scope = cbm_scope_push(arena, NULL); - const char* dbg = getenv("CBM_LSP_DEBUG"); + const char *dbg = getenv("CBM_LSP_DEBUG"); ctx->debug = (dbg && dbg[0]); } /* Doubling-array push of a `(local, full-path)` use entry. */ -void rust_lsp_add_use(RustLSPContext* ctx, const char* local_name, const char* module_path) { +void rust_lsp_add_use(RustLSPContext *ctx, const char *local_name, const char *module_path) { if (!ctx || !local_name || !module_path) { return; } if (ctx->use_count % 32 == 0) { int new_cap = ctx->use_count + 32; - const char** nl = (const char**)cbm_arena_alloc(ctx->arena, (new_cap + 1) * sizeof(char*)); - const char** np = (const char**)cbm_arena_alloc(ctx->arena, (new_cap + 1) * sizeof(char*)); + const char **nl = + (const char **)cbm_arena_alloc(ctx->arena, (new_cap + 1) * sizeof(char *)); + const char **np = + (const char **)cbm_arena_alloc(ctx->arena, (new_cap + 1) * sizeof(char *)); if (!nl || !np) { return; } if (ctx->use_local_names && ctx->use_count > 0) { - memcpy(nl, ctx->use_local_names, ctx->use_count * sizeof(char*)); - memcpy(np, ctx->use_module_paths, ctx->use_count * sizeof(char*)); + memcpy(nl, ctx->use_local_names, ctx->use_count * sizeof(char *)); + memcpy(np, ctx->use_module_paths, ctx->use_count * sizeof(char *)); } ctx->use_local_names = nl; ctx->use_module_paths = np; @@ -96,25 +99,26 @@ void rust_lsp_add_use(RustLSPContext* ctx, const char* local_name, const char* m ctx->use_count++; } -void rust_lsp_add_glob(RustLSPContext* ctx, const char* module_qn) { +void rust_lsp_add_glob(RustLSPContext *ctx, const char *module_qn) { if (!ctx || !module_qn) { return; } if (ctx->glob_count % 16 == 0) { int new_cap = ctx->glob_count + 16; - const char** ng = (const char**)cbm_arena_alloc(ctx->arena, (new_cap + 1) * sizeof(char*)); + const char **ng = + (const char **)cbm_arena_alloc(ctx->arena, (new_cap + 1) * sizeof(char *)); if (!ng) { return; } if (ctx->glob_module_qns && ctx->glob_count > 0) { - memcpy(ng, ctx->glob_module_qns, ctx->glob_count * sizeof(char*)); + memcpy(ng, ctx->glob_module_qns, ctx->glob_count * sizeof(char *)); } ctx->glob_module_qns = ng; } ctx->glob_module_qns[ctx->glob_count++] = cbm_arena_strdup(ctx->arena, module_qn); } -static char* rust_node_text(RustLSPContext* ctx, TSNode node) { +static char *rust_node_text(RustLSPContext *ctx, TSNode node) { return cbm_node_text(ctx->arena, node, ctx->source); } @@ -123,18 +127,15 @@ static char* rust_node_text(RustLSPContext* ctx, TSNode node) { * ════════════════════════════════════════════════════════════════════ */ /* Rust primitive types that the grammar reports as `primitive_type`. */ -static const char* RUST_PRIMITIVES[] = { - "i8", "i16", "i32", "i64", "i128", "isize", - "u8", "u16", "u32", "u64", "u128", "usize", - "f32", "f64", "bool", "char", "str", "()", "!", - NULL -}; +static const char *RUST_PRIMITIVES[] = {"i8", "i16", "i32", "i64", "i128", "isize", "u8", + "u16", "u32", "u64", "u128", "usize", "f32", "f64", + "bool", "char", "str", "()", "!", NULL}; -static bool is_rust_primitive(const char* name) { +static bool is_rust_primitive(const char *name) { if (!name) { return false; } - for (const char** p = RUST_PRIMITIVES; *p; p++) { + for (const char **p = RUST_PRIMITIVES; *p; p++) { if (strcmp(*p, name) == 0) { return true; } @@ -144,15 +145,28 @@ static bool is_rust_primitive(const char* name) { /* Names of macros that behave like println-family: side effects only, * return type `()`. */ -static bool is_void_macro(const char* name) { +static bool is_void_macro(const char *name) { if (!name) { return false; } - static const char* m[] = {"println", "print", "eprintln", "eprint", "panic", - "unimplemented", "todo", "unreachable", "assert", "assert_eq", "assert_ne", - "debug_assert", "debug_assert_eq", "debug_assert_ne", "writeln", "write", - NULL}; - for (const char** p = m; *p; p++) { + static const char *m[] = {"println", + "print", + "eprintln", + "eprint", + "panic", + "unimplemented", + "todo", + "unreachable", + "assert", + "assert_eq", + "assert_ne", + "debug_assert", + "debug_assert_eq", + "debug_assert_ne", + "writeln", + "write", + NULL}; + for (const char **p = m; *p; p++) { if (strcmp(*p, name) == 0) { return true; } @@ -161,7 +175,7 @@ static bool is_void_macro(const char* name) { } /* Names of macros that produce a `String` value. */ -static bool is_string_macro(const char* name) { +static bool is_string_macro(const char *name) { if (!name) { return false; } @@ -173,38 +187,35 @@ static bool is_string_macro(const char* name) { /* Prelude trait names whose method short-names we treat as universally * available (for emit-on-best-effort when we cannot pin down the trait * impl). Borrowed from `core::prelude::v1`. */ -static bool is_prelude_trait_method(const char* method_name) { +static bool is_prelude_trait_method(const char *method_name) { if (!method_name) { return false; } - static const char* m[] = { - /* Clone / Copy / Default */ - "clone", "default", - /* PartialEq / Eq / PartialOrd / Ord */ - "eq", "ne", "cmp", "partial_cmp", "lt", "le", "gt", "ge", - /* Hash */ - "hash", - /* Display / Debug */ - "fmt", "to_string", - /* From / Into / TryFrom / TryInto */ - "from", "into", "try_from", "try_into", - /* AsRef / AsMut / Borrow / BorrowMut */ - "as_ref", "as_mut", "borrow", "borrow_mut", - /* Deref */ - "deref", "deref_mut", - /* Drop */ - "drop", - /* Iterator (most-used subset) */ - "next", "iter", "iter_mut", "into_iter", - "map", "filter", "fold", "for_each", "collect", "count", "sum", - "max", "min", "any", "all", "find", "position", "enumerate", - "zip", "chain", "take", "skip", "rev", "cloned", "copied", - "by_ref", "step_by", "flat_map", "flatten", "filter_map", "peekable", - /* Future */ - "poll", - NULL - }; - for (const char** p = m; *p; p++) { + static const char *m[] = {/* Clone / Copy / Default */ + "clone", "default", + /* PartialEq / Eq / PartialOrd / Ord */ + "eq", "ne", "cmp", "partial_cmp", "lt", "le", "gt", "ge", + /* Hash */ + "hash", + /* Display / Debug */ + "fmt", "to_string", + /* From / Into / TryFrom / TryInto */ + "from", "into", "try_from", "try_into", + /* AsRef / AsMut / Borrow / BorrowMut */ + "as_ref", "as_mut", "borrow", "borrow_mut", + /* Deref */ + "deref", "deref_mut", + /* Drop */ + "drop", + /* Iterator (most-used subset) */ + "next", "iter", "iter_mut", "into_iter", "map", "filter", "fold", + "for_each", "collect", "count", "sum", "max", "min", "any", "all", + "find", "position", "enumerate", "zip", "chain", "take", "skip", + "rev", "cloned", "copied", "by_ref", "step_by", "flat_map", "flatten", + "filter_map", "peekable", + /* Future */ + "poll", NULL}; + for (const char **p = m; *p; p++) { if (strcmp(*p, method_name) == 0) { return true; } @@ -218,12 +229,12 @@ static bool is_prelude_trait_method(const char* method_name) { /* Return the last `::`-separated segment of a Rust path (`std::io::Read` → * `Read`). Pointer aliases into `path` — caller does not own. */ -static const char* path_last_segment(const char* path) { +static const char *path_last_segment(const char *path) { if (!path || !path[0]) { return path; } - const char* p = path; - const char* last = path; + const char *p = path; + const char *last = path; while (*p) { if (p[0] == ':' && p[1] == ':') { last = p + 2; @@ -237,12 +248,12 @@ static const char* path_last_segment(const char* path) { /* Convert a Rust path with `::` separators into our internal QN form using * `.` separators. Always allocates a fresh string. */ -static const char* convert_path_to_qn(CBMArena* arena, const char* path) { +static const char *convert_path_to_qn(CBMArena *arena, const char *path) { if (!path || !path[0]) { return path; } size_t len = strlen(path); - char* out = (char*)cbm_arena_alloc(arena, len + 1); + char *out = (char *)cbm_arena_alloc(arena, len + 1); if (!out) { return path; } @@ -265,21 +276,24 @@ static const char* convert_path_to_qn(CBMArena* arena, const char* path) { * arguments, so we collapse `head::::tail` to `head::tail`. * * Modifies `path` in place. Safe on NULL. */ -static void rust_strip_turbofish(char* path) { - if (!path) return; - char* read = path; - char* write = path; +static void rust_strip_turbofish(char *path) { + if (!path) + return; + char *read = path; + char *write = path; while (*read) { if (read[0] == ':' && read[1] == ':' && read[2] == '<') { /* Skip ::< … > balanced. */ int depth = 1; - const char* p = read + 3; + const char *p = read + 3; while (*p && depth > 0) { - if (*p == '<') depth++; - else if (*p == '>') depth--; + if (*p == '<') + depth++; + else if (*p == '>') + depth--; p++; } - read = (char*)p; + read = (char *)p; continue; } *write++ = *read++; @@ -289,7 +303,7 @@ static void rust_strip_turbofish(char* path) { /* Look up a `use` alias and return its fully-qualified module path, * or NULL if absent. The returned pointer aliases into the use map. */ -static const char* rust_resolve_use(RustLSPContext* ctx, const char* local_name) { +static const char *rust_resolve_use(RustLSPContext *ctx, const char *local_name) { if (!ctx || !local_name) { return NULL; } @@ -306,70 +320,73 @@ static const char* rust_resolve_use(RustLSPContext* ctx, const char* local_name) * resolve without an explicit `use`. The list mirrors `core::prelude::v1` * + `alloc::prelude` + `std::prelude::v1`. The mapping is consulted before * the project-local fallback so prelude names always win. */ -typedef struct { const char* name; const char* qn; } RustPreludeEntry; +typedef struct { + const char *name; + const char *qn; +} RustPreludeEntry; -static const RustPreludeEntry RUST_PRELUDE[] = { - {"String", "alloc.string.String"}, - {"ToString", "alloc.string.ToString"}, - {"Vec", "alloc.vec.Vec"}, - {"VecDeque", "alloc.collections.VecDeque"}, - {"HashMap", "alloc.collections.HashMap"}, - {"BTreeMap", "alloc.collections.BTreeMap"}, - {"HashSet", "alloc.collections.HashSet"}, - {"BTreeSet", "alloc.collections.BTreeSet"}, - {"Box", "alloc.boxed.Box"}, - {"Rc", "alloc.rc.Rc"}, - {"Arc", "alloc.sync.Arc"}, - {"Option", "core.option.Option"}, - {"Some", "core.option.Option.Some"}, - {"None", "core.option.Option.None"}, - {"Result", "core.result.Result"}, - {"Ok", "core.result.Result.Ok"}, - {"Err", "core.result.Result.Err"}, - {"Iterator", "core.iter.Iterator"}, - {"IntoIterator","core.iter.IntoIterator"}, - {"Future", "core.future.Future"}, - {"Clone", "core.clone.Clone"}, - {"Copy", "core.marker.Copy"}, - {"Send", "core.marker.Send"}, - {"Sync", "core.marker.Sync"}, - {"Default", "core.default.Default"}, - {"PartialEq", "core.cmp.PartialEq"}, - {"Eq", "core.cmp.Eq"}, - {"PartialOrd", "core.cmp.PartialOrd"}, - {"Ord", "core.cmp.Ord"}, - {"Hash", "core.hash.Hash"}, - {"Display", "core.fmt.Display"}, - {"Debug", "core.fmt.Debug"}, - {"From", "core.convert.From"}, - {"Into", "core.convert.Into"}, - {"TryFrom", "core.convert.TryFrom"}, - {"TryInto", "core.convert.TryInto"}, - {"AsRef", "core.convert.AsRef"}, - {"AsMut", "core.convert.AsMut"}, - {"Borrow", "core.borrow.Borrow"}, - {"BorrowMut", "core.borrow.BorrowMut"}, - {"Deref", "core.ops.Deref"}, - {"DerefMut", "core.ops.DerefMut"}, - {"Drop", "core.ops.Drop"}, - {"RefCell", "core.cell.RefCell"}, - {"Cell", "core.cell.Cell"}, - {"Mutex", "std.sync.Mutex"}, - {"RwLock", "std.sync.RwLock"}, - {NULL, NULL} -}; +static const RustPreludeEntry RUST_PRELUDE[] = {{"String", "alloc.string.String"}, + {"ToString", "alloc.string.ToString"}, + {"Vec", "alloc.vec.Vec"}, + {"VecDeque", "alloc.collections.VecDeque"}, + {"HashMap", "alloc.collections.HashMap"}, + {"BTreeMap", "alloc.collections.BTreeMap"}, + {"HashSet", "alloc.collections.HashSet"}, + {"BTreeSet", "alloc.collections.BTreeSet"}, + {"Box", "alloc.boxed.Box"}, + {"Rc", "alloc.rc.Rc"}, + {"Arc", "alloc.sync.Arc"}, + {"Option", "core.option.Option"}, + {"Some", "core.option.Option.Some"}, + {"None", "core.option.Option.None"}, + {"Result", "core.result.Result"}, + {"Ok", "core.result.Result.Ok"}, + {"Err", "core.result.Result.Err"}, + {"Iterator", "core.iter.Iterator"}, + {"IntoIterator", "core.iter.IntoIterator"}, + {"Future", "core.future.Future"}, + {"Clone", "core.clone.Clone"}, + {"Copy", "core.marker.Copy"}, + {"Send", "core.marker.Send"}, + {"Sync", "core.marker.Sync"}, + {"Default", "core.default.Default"}, + {"PartialEq", "core.cmp.PartialEq"}, + {"Eq", "core.cmp.Eq"}, + {"PartialOrd", "core.cmp.PartialOrd"}, + {"Ord", "core.cmp.Ord"}, + {"Hash", "core.hash.Hash"}, + {"Display", "core.fmt.Display"}, + {"Debug", "core.fmt.Debug"}, + {"From", "core.convert.From"}, + {"Into", "core.convert.Into"}, + {"TryFrom", "core.convert.TryFrom"}, + {"TryInto", "core.convert.TryInto"}, + {"AsRef", "core.convert.AsRef"}, + {"AsMut", "core.convert.AsMut"}, + {"Borrow", "core.borrow.Borrow"}, + {"BorrowMut", "core.borrow.BorrowMut"}, + {"Deref", "core.ops.Deref"}, + {"DerefMut", "core.ops.DerefMut"}, + {"Drop", "core.ops.Drop"}, + {"RefCell", "core.cell.RefCell"}, + {"Cell", "core.cell.Cell"}, + {"Mutex", "std.sync.Mutex"}, + {"RwLock", "std.sync.RwLock"}, + {NULL, NULL}}; -static const char* rust_lookup_prelude(const char* name) { - if (!name) return NULL; - for (const RustPreludeEntry* e = RUST_PRELUDE; e->name; e++) { - if (strcmp(e->name, name) == 0) return e->qn; +static const char *rust_lookup_prelude(const char *name) { + if (!name) + return NULL; + for (const RustPreludeEntry *e = RUST_PRELUDE; e->name; e++) { + if (strcmp(e->name, name) == 0) + return e->qn; } return NULL; } /* Strip a leading `&` / `&mut` reference prefix from a textual type so we * can compare the inner head segment against builtins. */ -static const char* skip_ref_prefix(const char* text) { +static const char *skip_ref_prefix(const char *text) { if (!text) { return text; } @@ -408,15 +425,15 @@ static const char* skip_ref_prefix(const char* text) { * * The returned string is arena-owned; in case (6) we return the input * with `::` already converted to `.`. */ -static const char* rust_resolve_path_expr(RustLSPContext* ctx, const char* path) { +static const char *rust_resolve_path_expr(RustLSPContext *ctx, const char *path) { if (!ctx || !path || !path[0]) { return path; } /* Self:: handling — we treat the receiver type's QN as the head. */ if (strncmp(path, "Self::", 6) == 0 && ctx->self_type_qn) { - return cbm_arena_sprintf(ctx->arena, "%s.%s", - ctx->self_type_qn, convert_path_to_qn(ctx->arena, path + 6)); + return cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->self_type_qn, + convert_path_to_qn(ctx->arena, path + 6)); } if (strcmp(path, "Self") == 0 && ctx->self_type_qn) { return ctx->self_type_qn; @@ -427,9 +444,9 @@ static const char* rust_resolve_path_expr(RustLSPContext* ctx, const char* path) * forms `module_qn` as `..`, so * the first two segments are project + crate root. */ if (strncmp(path, "crate::", 7) == 0 && ctx->module_qn) { - const char* p = ctx->module_qn; + const char *p = ctx->module_qn; int dots = 0; - const char* second_dot = NULL; + const char *second_dot = NULL; for (; *p; p++) { if (*p == '.') { if (++dots == 2) { @@ -438,33 +455,33 @@ static const char* rust_resolve_path_expr(RustLSPContext* ctx, const char* path) } } } - size_t crate_len = second_dot ? (size_t)(second_dot - ctx->module_qn) - : strlen(ctx->module_qn); - char* crate_buf = cbm_arena_strndup(ctx->arena, ctx->module_qn, crate_len); - return cbm_arena_sprintf(ctx->arena, "%s.%s", - crate_buf, convert_path_to_qn(ctx->arena, path + 7)); + size_t crate_len = + second_dot ? (size_t)(second_dot - ctx->module_qn) : strlen(ctx->module_qn); + char *crate_buf = cbm_arena_strndup(ctx->arena, ctx->module_qn, crate_len); + return cbm_arena_sprintf(ctx->arena, "%s.%s", crate_buf, + convert_path_to_qn(ctx->arena, path + 7)); } /* super:: → drop last segment of module_qn. */ if (strncmp(path, "super::", 7) == 0 && ctx->module_qn) { - const char* dot = strrchr(ctx->module_qn, '.'); + const char *dot = strrchr(ctx->module_qn, '.'); if (dot) { - char* parent = cbm_arena_strndup(ctx->arena, ctx->module_qn, - (size_t)(dot - ctx->module_qn)); - return cbm_arena_sprintf(ctx->arena, "%s.%s", - parent, convert_path_to_qn(ctx->arena, path + 7)); + char *parent = + cbm_arena_strndup(ctx->arena, ctx->module_qn, (size_t)(dot - ctx->module_qn)); + return cbm_arena_sprintf(ctx->arena, "%s.%s", parent, + convert_path_to_qn(ctx->arena, path + 7)); } } /* Find first "::" — split into head + tail. */ - const char* sep = strstr(path, "::"); + const char *sep = strstr(path, "::"); if (!sep) { - const char* full = rust_resolve_use(ctx, path); + const char *full = rust_resolve_use(ctx, path); if (full) { return convert_path_to_qn(ctx->arena, full); } /* Prelude name (e.g. `String`, `Vec`)? */ - const char* prelude = rust_lookup_prelude(path); + const char *prelude = rust_lookup_prelude(path); if (prelude) { return prelude; } @@ -475,23 +492,23 @@ static const char* rust_resolve_path_expr(RustLSPContext* ctx, const char* path) return path; } - char* head = cbm_arena_strndup(ctx->arena, path, (size_t)(sep - path)); - const char* tail = sep + 2; + char *head = cbm_arena_strndup(ctx->arena, path, (size_t)(sep - path)); + const char *tail = sep + 2; - const char* full = rust_resolve_use(ctx, head); + const char *full = rust_resolve_use(ctx, head); if (full) { /* The use-map's full path already includes `head` as the last * segment; concat its parent with the rest. */ - const char* full_dotted = convert_path_to_qn(ctx->arena, full); - const char* tail_dotted = convert_path_to_qn(ctx->arena, tail); + const char *full_dotted = convert_path_to_qn(ctx->arena, full); + const char *tail_dotted = convert_path_to_qn(ctx->arena, tail); return cbm_arena_sprintf(ctx->arena, "%s.%s", full_dotted, tail_dotted); } /* Prelude head: `String::from` → `alloc.string.String.from`. */ - const char* prelude = rust_lookup_prelude(head); + const char *prelude = rust_lookup_prelude(head); if (prelude) { return cbm_arena_sprintf(ctx->arena, "%s.%s", prelude, - convert_path_to_qn(ctx->arena, tail)); + convert_path_to_qn(ctx->arena, tail)); } /* Cargo-manifest aware routing — when a Cargo.toml has been parsed @@ -499,22 +516,21 @@ static const char* rust_resolve_path_expr(RustLSPContext* ctx, const char* path) * workspace member, return the canonical form `.` so * the resolver doesn't pollute the module-prefix space. */ if (ctx->cargo_manifest) { - const CBMCargoManifest* m = - (const CBMCargoManifest*)ctx->cargo_manifest; - const CBMCargoMember* mem = cbm_cargo_find_member(m, head); + const CBMCargoManifest *m = (const CBMCargoManifest *)ctx->cargo_manifest; + const CBMCargoMember *mem = cbm_cargo_find_member(m, head); if (mem) { /* Workspace member: route to `.` so the * pipeline's cross-crate resolution can match it. */ - return cbm_arena_sprintf(ctx->arena, "%s.%s", - head, convert_path_to_qn(ctx->arena, tail)); + return cbm_arena_sprintf(ctx->arena, "%s.%s", head, + convert_path_to_qn(ctx->arena, tail)); } if (cbm_cargo_is_known_dep(m, head)) { /* Declared dependency: same canonical form. The actual * methods may have been pre-seeded by rust_crates_seed.c; * otherwise the call is correctly attributed to an * external crate rather than fabricated locally. */ - return cbm_arena_sprintf(ctx->arena, "%s.%s", - head, convert_path_to_qn(ctx->arena, tail)); + return cbm_arena_sprintf(ctx->arena, "%s.%s", head, + convert_path_to_qn(ctx->arena, tail)); } } @@ -530,7 +546,7 @@ static const char* rust_resolve_path_expr(RustLSPContext* ctx, const char* path) * `scoped_identifier` node. We deliberately walk the named children * rather than using the literal source text so we do not preserve * whitespace or trailing turbofish noise. */ -static char* gather_scoped_path(RustLSPContext* ctx, TSNode node) { +static char *gather_scoped_path(RustLSPContext *ctx, TSNode node) { /* Fall back to the raw source text — the grammar already produces a * tight `path::to::name` literal under the node. */ return rust_node_text(ctx, node); @@ -538,32 +554,32 @@ static char* gather_scoped_path(RustLSPContext* ctx, TSNode node) { /* Resolve a textual Rust path (with `::`) into a registered type's QN, or * NULL if no match. */ -static const char* resolve_path_to_type_qn(RustLSPContext* ctx, const char* path) { +static const char *resolve_path_to_type_qn(RustLSPContext *ctx, const char *path) { if (!ctx || !path || !path[0]) { return NULL; } if (is_rust_primitive(path)) { return NULL; } - const char* qn = rust_resolve_path_expr(ctx, path); + const char *qn = rust_resolve_path_expr(ctx, path); if (!qn) { return NULL; } if (cbm_registry_lookup_type(ctx->registry, qn)) { return qn; } - return qn; /* may not be registered yet but caller can still wrap as NAMED */ + return qn; /* may not be registered yet but caller can still wrap as NAMED */ } -const CBMType* rust_parse_type_node(RustLSPContext* ctx, TSNode node) { +const CBMType *rust_parse_type_node(RustLSPContext *ctx, TSNode node) { if (ts_node_is_null(node)) { return cbm_type_unknown(); } - const char* kind = ts_node_type(node); + const char *kind = ts_node_type(node); /* primitive_type: i32, bool, char, str, … */ if (strcmp(kind, "primitive_type") == 0) { - char* name = rust_node_text(ctx, node); + char *name = rust_node_text(ctx, node); if (!name) { return cbm_type_unknown(); } @@ -572,7 +588,7 @@ const CBMType* rust_parse_type_node(RustLSPContext* ctx, TSNode node) { /* type_identifier: simple named type */ if (strcmp(kind, "type_identifier") == 0) { - char* name = rust_node_text(ctx, node); + char *name = rust_node_text(ctx, node); if (!name) { return cbm_type_unknown(); } @@ -582,17 +598,17 @@ const CBMType* rust_parse_type_node(RustLSPContext* ctx, TSNode node) { if (strcmp(name, "Self") == 0 && ctx->self_type_qn) { return cbm_type_named(ctx->arena, ctx->self_type_qn); } - const char* qn = rust_resolve_path_expr(ctx, name); + const char *qn = rust_resolve_path_expr(ctx, name); return cbm_type_named(ctx->arena, qn); } /* scoped_type_identifier: A::B::C */ if (strcmp(kind, "scoped_type_identifier") == 0) { - char* path = gather_scoped_path(ctx, node); + char *path = gather_scoped_path(ctx, node); if (!path) { return cbm_type_unknown(); } - const char* qn = rust_resolve_path_expr(ctx, path); + const char *qn = rust_resolve_path_expr(ctx, path); return cbm_type_named(ctx->arena, qn); } @@ -605,7 +621,7 @@ const CBMType* rust_parse_type_node(RustLSPContext* ctx, TSNode node) { inner = ts_node_named_child(node, nc - 1); } } - const CBMType* elem = rust_parse_type_node(ctx, inner); + const CBMType *elem = rust_parse_type_node(ctx, inner); return cbm_type_reference(ctx->arena, elem); } @@ -644,7 +660,7 @@ const CBMType* rust_parse_type_node(RustLSPContext* ctx, TSNode node) { /* tuple_type: (T1, T2, …) */ if (strcmp(kind, "tuple_type") == 0) { - const CBMType* elems[16]; + const CBMType *elems[16]; int count = 0; uint32_t nc = ts_node_named_child_count(node); for (uint32_t i = 0; i < nc && count < 16; i++) { @@ -675,21 +691,21 @@ const CBMType* rust_parse_type_node(RustLSPContext* ctx, TSNode node) { if (ts_node_is_null(tn) && ts_node_named_child_count(node) > 0) { tn = ts_node_named_child(node, 0); } - char* head = rust_node_text(ctx, tn); + char *head = rust_node_text(ctx, tn); if (!head) { return cbm_type_unknown(); } - const char* head_qn = rust_resolve_path_expr(ctx, head); + const char *head_qn = rust_resolve_path_expr(ctx, head); /* Gather type_arguments. */ TSNode args = ts_node_child_by_field_name(node, "type_arguments", 14); - const CBMType* targs[16]; + const CBMType *targs[16]; int targ_count = 0; if (!ts_node_is_null(args)) { uint32_t anc = ts_node_named_child_count(args); for (uint32_t i = 0; i < anc && targ_count < 15; i++) { TSNode tc = ts_node_named_child(args, i); - const char* tk = ts_node_type(tc); + const char *tk = ts_node_type(tc); /* Skip lifetime arguments — we ignore lifetimes entirely. */ if (strcmp(tk, "lifetime") == 0) { continue; @@ -753,8 +769,8 @@ const CBMType* rust_parse_type_node(RustLSPContext* ctx, TSNode node) { * area that tree-sitter would produce in `rust_parse_type_node` but * without a parser. This is the same trade-off `cbm_rust_parse_return_type_text` * makes for Go. */ -static const CBMType* parse_type_text_with_params(CBMArena* arena, const char* text, - const char* module_qn, const char** type_params) { +static const CBMType *parse_type_text_with_params(CBMArena *arena, const char *text, + const char *module_qn, const char **type_params) { if (!text || !text[0]) { return cbm_type_unknown(); } @@ -767,32 +783,39 @@ static const CBMType* parse_type_text_with_params(CBMArena* arena, const char* t * binder. We don't reason about explicit lifetimes anywhere, so * dropping it leaves the rest of the type untouched. */ if (strncmp(text, "for<", 4) == 0) { - const char* p = text + 4; + const char *p = text + 4; int depth = 1; while (*p && depth > 0) { - if (*p == '<') depth++; - else if (*p == '>') depth--; + if (*p == '<') + depth++; + else if (*p == '>') + depth--; p++; } - while (*p == ' ') p++; + while (*p == ' ') + p++; text = p; - if (!*text) return cbm_type_unknown(); + if (!*text) + return cbm_type_unknown(); } /* Bare leading lifetime (e.g. `'a`) — accept and skip, treating * the rest of the text as the actual type. Rare outside of HRTBs * but cheap to handle. */ if (text[0] == '\'' && (isalpha((unsigned char)text[1]) || text[1] == '_')) { - const char* p = text + 1; - while (*p && (isalnum((unsigned char)*p) || *p == '_')) p++; - while (*p == ' ') p++; + const char *p = text + 1; + while (*p && (isalnum((unsigned char)*p) || *p == '_')) + p++; + while (*p == ' ') + p++; text = p; - if (!*text) return cbm_type_unknown(); + if (!*text) + return cbm_type_unknown(); } /* Reference: &T or &'a T or &mut T or &'a mut T */ if (text[0] == '&') { - const char* p = text + 1; + const char *p = text + 1; if (*p == '\'') { p++; while (*p && (isalnum((unsigned char)*p) || *p == '_')) { @@ -805,38 +828,40 @@ static const CBMType* parse_type_text_with_params(CBMArena* arena, const char* t if (strncmp(p, "mut ", 4) == 0) { p += 4; } - const CBMType* elem = parse_type_text_with_params(arena, p, module_qn, type_params); + const CBMType *elem = parse_type_text_with_params(arena, p, module_qn, type_params); return cbm_type_reference(arena, elem); } /* Pointer: *const T / *mut T */ if (text[0] == '*') { - const char* p = text + 1; + const char *p = text + 1; if (strncmp(p, "const ", 6) == 0) { p += 6; } else if (strncmp(p, "mut ", 4) == 0) { p += 4; } - return cbm_type_pointer(arena, parse_type_text_with_params(arena, p, module_qn, type_params)); + return cbm_type_pointer(arena, + parse_type_text_with_params(arena, p, module_qn, type_params)); } /* Slice: [T] */ if (text[0] == '[' && text[strlen(text) - 1] == ']') { - const char* p = text + 1; - const char* end = text + strlen(text) - 1; + const char *p = text + 1; + const char *end = text + strlen(text) - 1; size_t inner_len = (size_t)(end - p); - char* inner = cbm_arena_strndup(arena, p, inner_len); + char *inner = cbm_arena_strndup(arena, p, inner_len); /* Array form `[T; N]` — strip the count. */ - char* semi = strchr(inner, ';'); + char *semi = strchr(inner, ';'); if (semi) { *semi = '\0'; /* Trim trailing whitespace. */ - char* q = semi - 1; + char *q = semi - 1; while (q > inner && isspace((unsigned char)*q)) { *q-- = '\0'; } } - return cbm_type_slice(arena, parse_type_text_with_params(arena, inner, module_qn, type_params)); + return cbm_type_slice(arena, + parse_type_text_with_params(arena, inner, module_qn, type_params)); } /* Unit / never */ @@ -849,15 +874,17 @@ static const CBMType* parse_type_text_with_params(CBMArena* arena, const char* t /* Tuple: (T1, T2, …) — only when not a single parenthesised type. */ if (text[0] == '(' && text[strlen(text) - 1] == ')') { - const char* p = text + 1; + const char *p = text + 1; size_t inner_len = strlen(text) - 2; - char* inner = cbm_arena_strndup(arena, p, inner_len); + char *inner = cbm_arena_strndup(arena, p, inner_len); /* Detect comma at top level. */ int depth = 0; bool has_comma = false; - for (char* q = inner; *q; q++) { - if (*q == '<' || *q == '(' || *q == '[') depth++; - else if (*q == '>' || *q == ')' || *q == ']') depth--; + for (char *q = inner; *q; q++) { + if (*q == '<' || *q == '(' || *q == '[') + depth++; + else if (*q == '>' || *q == ')' || *q == ']') + depth--; else if (*q == ',' && depth == 0) { has_comma = true; break; @@ -867,61 +894,73 @@ static const CBMType* parse_type_text_with_params(CBMArena* arena, const char* t return parse_type_text_with_params(arena, inner, module_qn, type_params); } /* Split by top-level commas. */ - const CBMType* elems[16]; + const CBMType *elems[16]; int count = 0; - char* start = inner; + char *start = inner; depth = 0; - for (char* q = inner; ; q++) { - if (*q == '<' || *q == '(' || *q == '[') depth++; - else if (*q == '>' || *q == ')' || *q == ']') depth--; + for (char *q = inner;; q++) { + if (*q == '<' || *q == '(' || *q == '[') + depth++; + else if (*q == '>' || *q == ')' || *q == ']') + depth--; if ((*q == ',' && depth == 0) || *q == '\0') { char save = *q; *q = '\0'; /* Trim. */ - while (*start == ' ') start++; + while (*start == ' ') + start++; if (count < 15 && *start) { - elems[count++] = parse_type_text_with_params(arena, start, module_qn, type_params); + elems[count++] = + parse_type_text_with_params(arena, start, module_qn, type_params); } - if (save == '\0') break; + if (save == '\0') + break; start = q + 1; } } - if (count == 0) return cbm_type_builtin(arena, "()"); - if (count == 1) return elems[0]; + if (count == 0) + return cbm_type_builtin(arena, "()"); + if (count == 1) + return elems[0]; return cbm_type_tuple(arena, elems, count); } /* Generic head: head */ - const char* lt = strchr(text, '<'); + const char *lt = strchr(text, '<'); if (lt) { - const char* gt = text + strlen(text) - 1; + const char *gt = text + strlen(text) - 1; if (*gt == '>') { size_t head_len = (size_t)(lt - text); - char* head = cbm_arena_strndup(arena, text, head_len); + char *head = cbm_arena_strndup(arena, text, head_len); /* Recursive split of args by top-level commas. */ - const char* args = lt + 1; + const char *args = lt + 1; size_t args_len = (size_t)(gt - args); - char* abuf = cbm_arena_strndup(arena, args, args_len); - const CBMType* targs[16]; + char *abuf = cbm_arena_strndup(arena, args, args_len); + const CBMType *targs[16]; int targ_count = 0; int depth = 0; - char* start = abuf; - for (char* q = abuf; ; q++) { - if (*q == '<' || *q == '(' || *q == '[') depth++; - else if (*q == '>' || *q == ')' || *q == ']') depth--; + char *start = abuf; + for (char *q = abuf;; q++) { + if (*q == '<' || *q == '(' || *q == '[') + depth++; + else if (*q == '>' || *q == ')' || *q == ']') + depth--; if ((*q == ',' && depth == 0) || *q == '\0') { char save = *q; *q = '\0'; - while (*start == ' ') start++; + while (*start == ' ') + start++; /* Skip lifetime args. */ if (*start != '\'' && *start && targ_count < 15) { - targs[targ_count++] = parse_type_text_with_params(arena, start, module_qn, type_params); + targs[targ_count++] = + parse_type_text_with_params(arena, start, module_qn, type_params); } - if (save == '\0') break; + if (save == '\0') + break; start = q + 1; } } - const char* head_qn = head; + const char *head_qn = head; if (is_rust_primitive(head)) { /* Primitives don't take generics in practice except for str ref — pass through. */ return cbm_type_builtin(arena, head); @@ -958,8 +997,8 @@ static const CBMType* parse_type_text_with_params(CBMArena* arena, const char* t } /* Public-ish helper used by the cross-file path. */ -static const CBMType* rust_parse_return_type_text(CBMArena* arena, const char* text, - const char* module_qn) { +static const CBMType *rust_parse_return_type_text(CBMArena *arena, const char *text, + const char *module_qn) { return parse_type_text_with_params(arena, text, module_qn, NULL); } @@ -970,8 +1009,8 @@ static const CBMType* rust_parse_return_type_text(CBMArena* arena, const char* t /* Recursively substitute every `TYPE_PARAM` reference in `t` whose name * matches `params[i]` with `args[i]`. Preserves structure for composite * types. */ -static const CBMType* rust_substitute_type(CBMArena* arena, const CBMType* t, - const char** params, const CBMType** args) { +static const CBMType *rust_substitute_type(CBMArena *arena, const CBMType *t, const char **params, + const CBMType **args) { if (!t || !params || !args) { return t; } @@ -984,22 +1023,24 @@ static const CBMType* rust_substitute_type(CBMArena* arena, const CBMType* t, } return t; case CBM_TYPE_REFERENCE: - return cbm_type_reference(arena, rust_substitute_type(arena, t->data.reference.elem, params, args)); + return cbm_type_reference( + arena, rust_substitute_type(arena, t->data.reference.elem, params, args)); case CBM_TYPE_POINTER: - return cbm_type_pointer(arena, rust_substitute_type(arena, t->data.pointer.elem, params, args)); + return cbm_type_pointer(arena, + rust_substitute_type(arena, t->data.pointer.elem, params, args)); case CBM_TYPE_SLICE: return cbm_type_slice(arena, rust_substitute_type(arena, t->data.slice.elem, params, args)); case CBM_TYPE_TEMPLATE: { - const CBMType* new_args[16]; + const CBMType *new_args[16]; int n = 0; for (; n < t->data.template_type.arg_count && n < 16; n++) { - new_args[n] = rust_substitute_type(arena, t->data.template_type.template_args[n], - params, args); + new_args[n] = + rust_substitute_type(arena, t->data.template_type.template_args[n], params, args); } return cbm_type_template(arena, t->data.template_type.template_name, new_args, n); } case CBM_TYPE_TUPLE: { - const CBMType* new_elems[16]; + const CBMType *new_elems[16]; int n = 0; for (; n < t->data.tuple.count && n < 16; n++) { new_elems[n] = rust_substitute_type(arena, t->data.tuple.elems[n], params, args); @@ -1015,8 +1056,9 @@ static const CBMType* rust_substitute_type(CBMArena* arena, const CBMType* t, * structurally against `arg_type`; whenever a `TYPE_PARAM` is bound for * the first time, store the corresponding `arg_type`. Subsequent * conflicting bindings are ignored (best-effort). */ -static void rust_unify_type(const CBMType* param_type, const CBMType* arg_type, - const char** type_param_names, const CBMType** inferred, int param_count) { +static void rust_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; } @@ -1033,16 +1075,17 @@ static void rust_unify_type(const CBMType* param_type, const CBMType* arg_type, } if (param_type->kind == CBM_TYPE_REFERENCE && arg_type->kind == CBM_TYPE_REFERENCE) { rust_unify_type(param_type->data.reference.elem, arg_type->data.reference.elem, - type_param_names, inferred, param_count); + type_param_names, inferred, param_count); return; } if (param_type->kind == CBM_TYPE_REFERENCE) { - rust_unify_type(param_type->data.reference.elem, arg_type, type_param_names, inferred, param_count); + rust_unify_type(param_type->data.reference.elem, arg_type, type_param_names, inferred, + param_count); return; } if (param_type->kind == CBM_TYPE_SLICE && arg_type->kind == CBM_TYPE_SLICE) { - rust_unify_type(param_type->data.slice.elem, arg_type->data.slice.elem, - type_param_names, inferred, param_count); + rust_unify_type(param_type->data.slice.elem, arg_type->data.slice.elem, type_param_names, + inferred, param_count); return; } if (param_type->kind == CBM_TYPE_TEMPLATE && arg_type->kind == CBM_TYPE_TEMPLATE) { @@ -1050,8 +1093,8 @@ static void rust_unify_type(const CBMType* param_type, const CBMType* arg_type, int ac = param_type->data.template_type.arg_count; for (int i = 0; i < ac; i++) { rust_unify_type(param_type->data.template_type.template_args[i], - arg_type->data.template_type.template_args[i], - type_param_names, inferred, param_count); + arg_type->data.template_type.template_args[i], type_param_names, + inferred, param_count); } } return; @@ -1063,16 +1106,15 @@ static void rust_unify_type(const CBMType* param_type, const CBMType* arg_type, int ac = arg_type->data.tuple.count; int min_ = pc < ac ? pc : ac; for (int i = 0; i < min_; i++) { - rust_unify_type(param_type->data.tuple.elems[i], - arg_type->data.tuple.elems[i], - type_param_names, inferred, param_count); + rust_unify_type(param_type->data.tuple.elems[i], arg_type->data.tuple.elems[i], + type_param_names, inferred, param_count); } return; } /* POINTER unification. */ if (param_type->kind == CBM_TYPE_POINTER && arg_type->kind == CBM_TYPE_POINTER) { rust_unify_type(param_type->data.pointer.elem, arg_type->data.pointer.elem, - type_param_names, inferred, param_count); + type_param_names, inferred, param_count); return; } /* Bidirectional fallback: if `arg_type` (rather than `param_type`) @@ -1091,47 +1133,49 @@ static void rust_unify_type(const CBMType* param_type, const CBMType* arg_type, * This is the post-solve step of HM-lite: after `rust_unify_type` has * filled the `inferred` array, this helper walks a target type and * rewrites every `TYPE_PARAM` reference. */ -static const CBMType* rust_apply_subst(CBMArena* arena, const CBMType* t, - const char** names, const CBMType** inferred, int count) { - if (!t) return t; +static const CBMType *rust_apply_subst(CBMArena *arena, const CBMType *t, const char **names, + const CBMType **inferred, int count) { + if (!t) + return t; switch (t->kind) { case CBM_TYPE_TYPE_PARAM: for (int i = 0; i < count; i++) { - if (inferred[i] && names[i] && - strcmp(t->data.type_param.name, names[i]) == 0) { + if (inferred[i] && names[i] && strcmp(t->data.type_param.name, names[i]) == 0) { return inferred[i]; } } return t; case CBM_TYPE_REFERENCE: - return cbm_type_reference(arena, - rust_apply_subst(arena, t->data.reference.elem, names, inferred, count)); + return cbm_type_reference( + arena, rust_apply_subst(arena, t->data.reference.elem, names, inferred, count)); case CBM_TYPE_POINTER: - return cbm_type_pointer(arena, - rust_apply_subst(arena, t->data.pointer.elem, names, inferred, count)); + return cbm_type_pointer( + arena, rust_apply_subst(arena, t->data.pointer.elem, names, inferred, count)); case CBM_TYPE_SLICE: return cbm_type_slice(arena, - rust_apply_subst(arena, t->data.slice.elem, names, inferred, count)); + rust_apply_subst(arena, t->data.slice.elem, names, inferred, count)); case CBM_TYPE_TEMPLATE: { int n = t->data.template_type.arg_count; - if (n <= 0) return t; - const CBMType* new_args[16]; - if (n > 16) n = 16; + if (n <= 0) + return t; + const CBMType *new_args[16]; + if (n > 16) + n = 16; for (int i = 0; i < n; i++) { - new_args[i] = rust_apply_subst(arena, - t->data.template_type.template_args[i], names, inferred, count); + new_args[i] = rust_apply_subst(arena, t->data.template_type.template_args[i], names, + inferred, count); } - return cbm_type_template(arena, t->data.template_type.template_name, - new_args, n); + return cbm_type_template(arena, t->data.template_type.template_name, new_args, n); } case CBM_TYPE_TUPLE: { int n = t->data.tuple.count; - if (n <= 0) return t; - const CBMType* new_elems[16]; - if (n > 16) n = 16; + if (n <= 0) + return t; + const CBMType *new_elems[16]; + if (n > 16) + n = 16; for (int i = 0; i < n; i++) { - new_elems[i] = rust_apply_subst(arena, t->data.tuple.elems[i], - names, inferred, count); + new_elems[i] = rust_apply_subst(arena, t->data.tuple.elems[i], names, inferred, count); } return cbm_type_tuple(arena, new_elems, n); } @@ -1146,19 +1190,19 @@ static const CBMType* rust_apply_subst(CBMArena* arena, const CBMType* t, /* Evaluate the type of a literal child like `integer_literal`, `float_literal`, * `string_literal`, `char_literal`, `boolean_literal`. */ -static const CBMType* rust_eval_literal_type(RustLSPContext* ctx, const char* kind, TSNode node) { +static const CBMType *rust_eval_literal_type(RustLSPContext *ctx, const char *kind, TSNode node) { if (strcmp(kind, "integer_literal") == 0) { - char* text = rust_node_text(ctx, node); + char *text = rust_node_text(ctx, node); /* Look at suffix. */ if (text) { /* Strip leading `-` if any. */ - const char* p = text; - if (*p == '-') p++; + const char *p = text; + if (*p == '-') + p++; /* Find suffix start (first non-digit/non-_/non-x/non-X/non-hex). */ - while (*p && (isdigit((unsigned char)*p) || *p == '_' || *p == '.' || - *p == 'x' || *p == 'X' || *p == 'b' || *p == 'B' || - *p == 'o' || *p == 'O' || (*p >= 'a' && *p <= 'f') || - (*p >= 'A' && *p <= 'F'))) { + while (*p && (isdigit((unsigned char)*p) || *p == '_' || *p == '.' || *p == 'x' || + *p == 'X' || *p == 'b' || *p == 'B' || *p == 'o' || *p == 'O' || + (*p >= 'a' && *p <= 'f') || (*p >= 'A' && *p <= 'F'))) { p++; } if (*p) { @@ -1168,9 +1212,9 @@ static const CBMType* rust_eval_literal_type(RustLSPContext* ctx, const char* ki return cbm_type_builtin(ctx->arena, "i32"); } if (strcmp(kind, "float_literal") == 0) { - char* text = rust_node_text(ctx, node); + char *text = rust_node_text(ctx, node); if (text) { - const char* p = text; + const char *p = text; while (*p && (isdigit((unsigned char)*p) || *p == '.' || *p == 'e' || *p == 'E' || *p == '+' || *p == '-' || *p == '_')) { p++; @@ -1198,46 +1242,46 @@ static const CBMType* rust_eval_literal_type(RustLSPContext* ctx, const char* ki /* Look up the registered method or field type for a field/method-style * access. Order: inherent method → field → trait method (with single-impl * preference). */ -static const CBMType* rust_eval_member_access(RustLSPContext* ctx, const CBMType* recv, - const char* member); +static const CBMType *rust_eval_member_access(RustLSPContext *ctx, const CBMType *recv, + const char *member); -const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { +const CBMType *rust_eval_expr_type(RustLSPContext *ctx, TSNode node) { if (ts_node_is_null(node)) { return cbm_type_unknown(); } - const char* kind = ts_node_type(node); + const char *kind = ts_node_type(node); /* Identifier: scope or registered symbol. */ if (strcmp(kind, "identifier") == 0) { - char* name = rust_node_text(ctx, node); + char *name = rust_node_text(ctx, node); if (!name) { return cbm_type_unknown(); } - const CBMType* t = cbm_scope_lookup(ctx->current_scope, name); + const CBMType *t = cbm_scope_lookup(ctx->current_scope, name); if (!cbm_type_is_unknown(t)) { return t; } /* Module-level function. */ - const CBMRegisteredFunc* f = cbm_registry_lookup_symbol(ctx->registry, - ctx->module_qn, name); + const CBMRegisteredFunc *f = + cbm_registry_lookup_symbol(ctx->registry, ctx->module_qn, name); if (f && f->signature) { return f->signature; } /* Use-aliased symbol: resolve path then look up. */ - const char* full = rust_resolve_use(ctx, name); + const char *full = rust_resolve_use(ctx, name); if (full) { - const char* qn = convert_path_to_qn(ctx->arena, full); - const CBMRegisteredFunc* uf = cbm_registry_lookup_func(ctx->registry, qn); + const char *qn = convert_path_to_qn(ctx->arena, full); + const CBMRegisteredFunc *uf = cbm_registry_lookup_func(ctx->registry, qn); if (uf && uf->signature) { return uf->signature; } - const CBMRegisteredType* ut = cbm_registry_lookup_type(ctx->registry, qn); + const CBMRegisteredType *ut = cbm_registry_lookup_type(ctx->registry, qn); if (ut) { return cbm_type_named(ctx->arena, qn); } } /* Same-module type? */ - const char* type_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, name); + const char *type_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, name); if (cbm_registry_lookup_type(ctx->registry, type_qn)) { return cbm_type_named(ctx->arena, type_qn); } @@ -1251,15 +1295,15 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { /* scoped_identifier: A::B::C — could be a function or a type. */ if (strcmp(kind, "scoped_identifier") == 0) { - char* path = rust_node_text(ctx, node); + char *path = rust_node_text(ctx, node); if (!path) { return cbm_type_unknown(); } - const char* qn = rust_resolve_path_expr(ctx, path); + const char *qn = rust_resolve_path_expr(ctx, path); if (!qn) { return cbm_type_unknown(); } - const CBMRegisteredFunc* f = cbm_registry_lookup_func(ctx->registry, qn); + const CBMRegisteredFunc *f = cbm_registry_lookup_func(ctx->registry, qn); if (f && f->signature) { return f->signature; } @@ -1284,17 +1328,17 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { if (ts_node_is_null(value) || ts_node_is_null(field)) { return cbm_type_unknown(); } - const CBMType* recv = rust_eval_expr_type(ctx, value); - const char* fk = ts_node_type(field); + const CBMType *recv = rust_eval_expr_type(ctx, value); + const char *fk = ts_node_type(field); if (strcmp(fk, "integer_literal") == 0) { /* Tuple field access. */ if (recv) { - const CBMType* base = recv; + const CBMType *base = recv; while (base && base->kind == CBM_TYPE_REFERENCE) { base = base->data.reference.elem; } if (base && base->kind == CBM_TYPE_TUPLE) { - char* idx_text = rust_node_text(ctx, field); + char *idx_text = rust_node_text(ctx, field); int idx = atoi(idx_text); if (idx >= 0 && idx < base->data.tuple.count) { return base->data.tuple.elems[idx]; @@ -1303,7 +1347,7 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { } return cbm_type_unknown(); } - char* fname = rust_node_text(ctx, field); + char *fname = rust_node_text(ctx, field); if (!fname) { return cbm_type_unknown(); } @@ -1317,30 +1361,30 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { if (ts_node_is_null(func_node)) { return cbm_type_unknown(); } - const char* fk = ts_node_type(func_node); + const char *fk = ts_node_type(func_node); /* Constructor of a tuple struct, unit-like struct, or stdlib * factory invoked via path. Try the registry; on miss, also try * UFCS-style method lookup to catch `String::new()`, * `Vec::new()`, etc. */ if (strcmp(fk, "identifier") == 0 || strcmp(fk, "scoped_identifier") == 0) { - char* path = rust_node_text(ctx, func_node); + char *path = rust_node_text(ctx, func_node); if (path) { /* Strip ALL turbofish (`::<...>`) so the lookup below * ignores explicit type arguments — handles forms like * `Vec::::new` and `parse::`. */ rust_strip_turbofish(path); - const char* qn = rust_resolve_path_expr(ctx, path); + const char *qn = rust_resolve_path_expr(ctx, path); if (qn) { - const CBMRegisteredType* rt = cbm_registry_lookup_type(ctx->registry, qn); + const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, qn); if (rt) { return cbm_type_named(ctx->arena, qn); } - const CBMRegisteredFunc* f = cbm_registry_lookup_func(ctx->registry, qn); + const CBMRegisteredFunc *f = cbm_registry_lookup_func(ctx->registry, qn); if (f && f->signature && f->signature->kind == CBM_TYPE_FUNC) { - const CBMType* const* rt_arr = f->signature->data.func.return_types; + const CBMType *const *rt_arr = f->signature->data.func.return_types; if (rt_arr && rt_arr[0]) { - const CBMType* ret = rt_arr[0]; + const CBMType *ret = rt_arr[0]; /* Constructor-style on a stdlib receiver with * `unknown` return — substitute the receiver * type so chains keep typing. The heuristic @@ -1354,8 +1398,7 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { * we also try to capture the first call * argument's type as a TEMPLATE arg so the * Deref chain has something to peel. */ - if (f->receiver_type && cbm_type_is_unknown(ret) && - f->short_name && + if (f->receiver_type && cbm_type_is_unknown(ret) && f->short_name && (strcmp(f->short_name, "new") == 0 || strcmp(f->short_name, "default") == 0 || strcmp(f->short_name, "open") == 0 || @@ -1372,17 +1415,16 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { * capture the first arg's type so * `let b = Box::new(x); b.method()` * dispatches via Deref. */ - bool is_wrapper = - strcmp(f->short_name, "new") == 0 && - rust_type_derefs_to_first_arg(f->receiver_type); + bool is_wrapper = strcmp(f->short_name, "new") == 0 && + rust_type_derefs_to_first_arg(f->receiver_type); if (is_wrapper && !ts_node_is_null(args_node)) { uint32_t anc = ts_node_named_child_count(args_node); if (anc > 0) { - const CBMType* arg_t = rust_eval_expr_type(ctx, - ts_node_named_child(args_node, 0)); + const CBMType *arg_t = rust_eval_expr_type( + ctx, ts_node_named_child(args_node, 0)); if (arg_t && !cbm_type_is_unknown(arg_t)) { - return cbm_type_template(ctx->arena, - f->receiver_type, &arg_t, 1); + return cbm_type_template(ctx->arena, f->receiver_type, + &arg_t, 1); } } } @@ -1398,23 +1440,24 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { } /* UFCS path lookup: split off short name and try * `cbm_registry_lookup_method`. */ - const char* dot = strrchr(qn, '.'); + const char *dot = strrchr(qn, '.'); if (dot) { - char* head = cbm_arena_strndup(ctx->arena, qn, (size_t)(dot - qn)); - const char* short_name = dot + 1; - const CBMRegisteredFunc* m = cbm_registry_lookup_method_aliased(ctx->registry, - head, short_name); + char *head = cbm_arena_strndup(ctx->arena, qn, (size_t)(dot - qn)); + const char *short_name = dot + 1; + const CBMRegisteredFunc *m = + cbm_registry_lookup_method_aliased(ctx->registry, head, short_name); if (!m && ctx->module_qn) { - const char* full_head = cbm_arena_sprintf(ctx->arena, "%s.%s", - ctx->module_qn, head); - m = cbm_registry_lookup_method_aliased(ctx->registry, - full_head, short_name); - if (m) head = (char*)full_head; + const char *full_head = + cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, head); + m = cbm_registry_lookup_method_aliased(ctx->registry, full_head, + short_name); + if (m) + head = (char *)full_head; } if (m && m->signature && m->signature->kind == CBM_TYPE_FUNC && m->signature->data.func.return_types && m->signature->data.func.return_types[0]) { - const CBMType* ret = m->signature->data.func.return_types[0]; + const CBMType *ret = m->signature->data.func.return_types[0]; /* Substitute Self / unknown returns with the * receiver type so chained calls keep typing. */ if (ret->kind == CBM_TYPE_NAMED && @@ -1433,11 +1476,10 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { !ts_node_is_null(args_node)) { uint32_t anc = ts_node_named_child_count(args_node); if (anc > 0) { - const CBMType* arg_t = rust_eval_expr_type(ctx, - ts_node_named_child(args_node, 0)); + const CBMType *arg_t = rust_eval_expr_type( + ctx, ts_node_named_child(args_node, 0)); if (arg_t && !cbm_type_is_unknown(arg_t)) { - return cbm_type_template(ctx->arena, - head, &arg_t, 1); + return cbm_type_template(ctx->arena, head, &arg_t, 1); } } } @@ -1455,28 +1497,28 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { TSNode value = ts_node_child_by_field_name(func_node, "value", 5); TSNode field = ts_node_child_by_field_name(func_node, "field", 5); if (!ts_node_is_null(value) && !ts_node_is_null(field)) { - const CBMType* recv = rust_eval_expr_type(ctx, value); - char* mname = rust_node_text(ctx, field); + const CBMType *recv = rust_eval_expr_type(ctx, value); + char *mname = rust_node_text(ctx, field); if (mname && recv) { - const CBMType* base = recv; + const CBMType *base = recv; while (base && base->kind == CBM_TYPE_REFERENCE) { base = base->data.reference.elem; } /* Template Vec method handling */ if (base && base->kind == CBM_TYPE_TEMPLATE) { - const char* tname = base->data.template_type.template_name; + const char *tname = base->data.template_type.template_name; /* Iterator-producing methods on Vec/&[T]/Option/Result/HashMap. */ if (strstr(tname, "Vec") || strstr(tname, "VecDeque") || strstr(tname, "HashSet") || strstr(tname, "BTreeSet")) { if (strcmp(mname, "iter") == 0 || strcmp(mname, "iter_mut") == 0 || - strcmp(mname, "into_iter") == 0 || - strcmp(mname, "drain") == 0) { + strcmp(mname, "into_iter") == 0 || strcmp(mname, "drain") == 0) { /* Iterator — represent loosely as the elem type for our * downstream chain calls. Even when the elem type is not * known, return Iterator (with no args) so further chain calls * can still attribute via Iterator's registered methods. */ if (base->data.template_type.arg_count > 0) { - return cbm_type_template(ctx->arena, "core.iter.Iterator", + return cbm_type_template( + ctx->arena, "core.iter.Iterator", &base->data.template_type.template_args[0], 1); } return cbm_type_template(ctx->arena, "core.iter.Iterator", NULL, 0); @@ -1496,24 +1538,23 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { if (strcmp(mname, "first") == 0 || strcmp(mname, "last") == 0 || strcmp(mname, "get") == 0 || strcmp(mname, "pop") == 0) { if (base->data.template_type.arg_count > 0) { - const CBMType* opt_args[1] = { - base->data.template_type.template_args[0] - }; + const CBMType *opt_args[1] = { + base->data.template_type.template_args[0]}; return cbm_type_template(ctx->arena, "core.option.Option", - opt_args, 1); + opt_args, 1); } } if (strcmp(mname, "as_slice") == 0) { if (base->data.template_type.arg_count > 0) { - return cbm_type_reference(ctx->arena, + return cbm_type_reference( + ctx->arena, cbm_type_slice(ctx->arena, - base->data.template_type.template_args[0])); + base->data.template_type.template_args[0])); } } } if (strstr(tname, "Option")) { - if (strcmp(mname, "unwrap") == 0 || - strcmp(mname, "expect") == 0 || + if (strcmp(mname, "unwrap") == 0 || strcmp(mname, "expect") == 0 || strcmp(mname, "unwrap_or") == 0 || strcmp(mname, "unwrap_or_default") == 0 || strcmp(mname, "unwrap_or_else") == 0) { @@ -1526,27 +1567,28 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { } if (strcmp(mname, "as_ref") == 0) { if (base->data.template_type.arg_count > 0) { - const CBMType* arg0 = cbm_type_reference(ctx->arena, - base->data.template_type.template_args[0]); + const CBMType *arg0 = cbm_type_reference( + ctx->arena, base->data.template_type.template_args[0]); return cbm_type_template(ctx->arena, "core.option.Option", - &arg0, 1); + &arg0, 1); } } } if (strstr(tname, "Result")) { - if (strcmp(mname, "unwrap") == 0 || - strcmp(mname, "expect") == 0 || + if (strcmp(mname, "unwrap") == 0 || strcmp(mname, "expect") == 0 || strcmp(mname, "unwrap_or") == 0) { if (base->data.template_type.arg_count > 0) { return base->data.template_type.template_args[0]; } } - if (strcmp(mname, "ok") == 0 && base->data.template_type.arg_count > 0) { - const CBMType* a0 = base->data.template_type.template_args[0]; + if (strcmp(mname, "ok") == 0 && + base->data.template_type.arg_count > 0) { + const CBMType *a0 = base->data.template_type.template_args[0]; return cbm_type_template(ctx->arena, "core.option.Option", &a0, 1); } - if (strcmp(mname, "err") == 0 && base->data.template_type.arg_count > 1) { - const CBMType* a1 = base->data.template_type.template_args[1]; + if (strcmp(mname, "err") == 0 && + base->data.template_type.arg_count > 1) { + const CBMType *a1 = base->data.template_type.template_args[1]; return cbm_type_template(ctx->arena, "core.option.Option", &a1, 1); } if (strcmp(mname, "is_ok") == 0 || strcmp(mname, "is_err") == 0) { @@ -1559,24 +1601,21 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { /* Often Vec; without turbofish info we model as Vec. */ if (base->data.template_type.arg_count > 0) { return cbm_type_template(ctx->arena, "alloc.vec.Vec", - base->data.template_type.template_args, - base->data.template_type.arg_count); + base->data.template_type.template_args, + base->data.template_type.arg_count); } } if (strcmp(mname, "count") == 0 || strcmp(mname, "len") == 0) { return cbm_type_builtin(ctx->arena, "usize"); } - if (strcmp(mname, "next") == 0 || - strcmp(mname, "last") == 0 || - strcmp(mname, "nth") == 0 || - strcmp(mname, "find") == 0 || - strcmp(mname, "max") == 0 || - strcmp(mname, "min") == 0 || - strcmp(mname, "max_by") == 0 || - strcmp(mname, "min_by") == 0) { + if (strcmp(mname, "next") == 0 || strcmp(mname, "last") == 0 || + strcmp(mname, "nth") == 0 || strcmp(mname, "find") == 0 || + strcmp(mname, "max") == 0 || strcmp(mname, "min") == 0 || + strcmp(mname, "max_by") == 0 || strcmp(mname, "min_by") == 0) { if (base->data.template_type.arg_count > 0) { - const CBMType* a0 = base->data.template_type.template_args[0]; - return cbm_type_template(ctx->arena, "core.option.Option", &a0, 1); + const CBMType *a0 = base->data.template_type.template_args[0]; + return cbm_type_template(ctx->arena, "core.option.Option", &a0, + 1); } } if (strcmp(mname, "filter") == 0 || strcmp(mname, "take") == 0 || @@ -1584,14 +1623,12 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { strcmp(mname, "cloned") == 0 || strcmp(mname, "copied") == 0 || strcmp(mname, "step_by") == 0 || strcmp(mname, "fuse") == 0 || strcmp(mname, "peekable") == 0 || strcmp(mname, "by_ref") == 0 || - strcmp(mname, "take_while") == 0 || strcmp(mname, "skip_while") == 0 || - strcmp(mname, "inspect") == 0) { - return base; /* preserves Iterator */ + strcmp(mname, "take_while") == 0 || + strcmp(mname, "skip_while") == 0 || strcmp(mname, "inspect") == 0) { + return base; /* preserves Iterator */ } - if ((strcmp(mname, "sum") == 0 || - strcmp(mname, "product") == 0 || - strcmp(mname, "fold") == 0 || - strcmp(mname, "reduce") == 0) && + if ((strcmp(mname, "sum") == 0 || strcmp(mname, "product") == 0 || + strcmp(mname, "fold") == 0 || strcmp(mname, "reduce") == 0) && base->data.template_type.arg_count > 0) { return base->data.template_type.template_args[0]; } @@ -1599,17 +1636,17 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { return cbm_type_builtin(ctx->arena, "bool"); } if (strcmp(mname, "position") == 0) { - const CBMType* usize = cbm_type_builtin(ctx->arena, "usize"); - return cbm_type_template(ctx->arena, "core.option.Option", &usize, 1); + const CBMType *usize = cbm_type_builtin(ctx->arena, "usize"); + return cbm_type_template(ctx->arena, "core.option.Option", &usize, + 1); } if (strcmp(mname, "enumerate") == 0 && base->data.template_type.arg_count > 0) { /* Iterator<(usize, T)>. */ - const CBMType* pair[2] = { + const CBMType *pair[2] = { cbm_type_builtin(ctx->arena, "usize"), - base->data.template_type.template_args[0] - }; - const CBMType* tup = cbm_type_tuple(ctx->arena, pair, 2); + base->data.template_type.template_args[0]}; + const CBMType *tup = cbm_type_tuple(ctx->arena, pair, 2); return cbm_type_template(ctx->arena, "core.iter.Iterator", &tup, 1); } } @@ -1622,35 +1659,36 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { strcmp(mname, "contains_key") == 0) { return cbm_type_builtin(ctx->arena, "bool"); } - if (strcmp(mname, "get") == 0 || - strcmp(mname, "get_mut") == 0 || + if (strcmp(mname, "get") == 0 || strcmp(mname, "get_mut") == 0 || strcmp(mname, "remove") == 0) { if (base->data.template_type.arg_count > 1) { - const CBMType* v = base->data.template_type.template_args[1]; - return cbm_type_template(ctx->arena, "core.option.Option", &v, 1); + const CBMType *v = base->data.template_type.template_args[1]; + return cbm_type_template(ctx->arena, "core.option.Option", &v, + 1); } } - if (strcmp(mname, "iter") == 0 || - strcmp(mname, "iter_mut") == 0) { + if (strcmp(mname, "iter") == 0 || strcmp(mname, "iter_mut") == 0) { /* Iterator<(K, V)>. */ if (base->data.template_type.arg_count > 1) { - const CBMType* pair[2] = { + const CBMType *pair[2] = { base->data.template_type.template_args[0], - base->data.template_type.template_args[1] - }; - const CBMType* tup = cbm_type_tuple(ctx->arena, pair, 2); - return cbm_type_template(ctx->arena, "core.iter.Iterator", &tup, 1); + base->data.template_type.template_args[1]}; + const CBMType *tup = cbm_type_tuple(ctx->arena, pair, 2); + return cbm_type_template(ctx->arena, "core.iter.Iterator", &tup, + 1); } } if (strcmp(mname, "keys") == 0 && base->data.template_type.arg_count > 0) { return cbm_type_template(ctx->arena, "core.iter.Iterator", - &base->data.template_type.template_args[0], 1); + &base->data.template_type.template_args[0], + 1); } if (strcmp(mname, "values") == 0 && base->data.template_type.arg_count > 1) { return cbm_type_template(ctx->arena, "core.iter.Iterator", - &base->data.template_type.template_args[1], 1); + &base->data.template_type.template_args[1], + 1); } } } @@ -1659,16 +1697,16 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { * return type so chains like * `String::new().to_uppercase().len()` keep typing * across each link. */ - const CBMType* t = rust_eval_member_access(ctx, recv, mname); - if (t && t->kind == CBM_TYPE_FUNC && - t->data.func.return_types && + const CBMType *t = rust_eval_member_access(ctx, recv, mname); + if (t && t->kind == CBM_TYPE_FUNC && t->data.func.return_types && t->data.func.return_types[0]) { - const CBMType* ret = t->data.func.return_types[0]; + const CBMType *ret = t->data.func.return_types[0]; /* Self -> receiver. */ if (ret->kind == CBM_TYPE_NAMED && strcmp(ret->data.named.qualified_name, "Self") == 0) { - const CBMType* rb = recv; - while (rb && rb->kind == CBM_TYPE_REFERENCE) rb = rb->data.reference.elem; + const CBMType *rb = recv; + while (rb && rb->kind == CBM_TYPE_REFERENCE) + rb = rb->data.reference.elem; if (rb && rb->kind == CBM_TYPE_NAMED) { return cbm_type_named(ctx->arena, rb->data.named.qualified_name); } @@ -1681,9 +1719,9 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { } /* Fallback: function expression's return type. */ - const CBMType* func_type = rust_eval_expr_type(ctx, func_node); - if (func_type && func_type->kind == CBM_TYPE_FUNC && - func_type->data.func.return_types && func_type->data.func.return_types[0]) { + const CBMType *func_type = rust_eval_expr_type(ctx, func_node); + if (func_type && func_type->kind == CBM_TYPE_FUNC && func_type->data.func.return_types && + func_type->data.func.return_types[0]) { return func_type->data.func.return_types[0]; } if (func_type && func_type->kind == CBM_TYPE_NAMED) { @@ -1698,7 +1736,7 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { if (ts_node_is_null(mname) && ts_node_named_child_count(node) > 0) { mname = ts_node_named_child(node, 0); } - char* name = rust_node_text(ctx, mname); + char *name = rust_node_text(ctx, mname); if (!name) { return cbm_type_unknown(); } @@ -1711,7 +1749,7 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { uint32_t nc = ts_node_named_child_count(args); for (uint32_t i = 0; i < nc; i++) { TSNode c = ts_node_named_child(args, i); - const CBMType* elem = rust_eval_expr_type(ctx, c); + const CBMType *elem = rust_eval_expr_type(ctx, c); if (elem && !cbm_type_is_unknown(elem)) { return cbm_type_template(ctx->arena, "alloc.vec.Vec", &elem, 1); } @@ -1740,18 +1778,19 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { /* unary_expression — *x dereferences, !x is bool, -x same as operand */ if (strcmp(kind, "unary_expression") == 0) { TSNode operand = ts_node_named_child_count(node) > 0 - ? ts_node_named_child(node, ts_node_named_child_count(node) - 1) - : (TSNode){0}; - char* op = NULL; + ? ts_node_named_child(node, ts_node_named_child_count(node) - 1) + : (TSNode){0}; + char *op = NULL; for (uint32_t i = 0; i < ts_node_child_count(node); i++) { TSNode c = ts_node_child(node, i); if (!ts_node_is_named(c)) { op = rust_node_text(ctx, c); - if (op) break; + if (op) + break; } } - const CBMType* inner = ts_node_is_null(operand) - ? cbm_type_unknown() : rust_eval_expr_type(ctx, operand); + const CBMType *inner = + ts_node_is_null(operand) ? cbm_type_unknown() : rust_eval_expr_type(ctx, operand); if (op && strcmp(op, "*") == 0) { if (inner && inner->kind == CBM_TYPE_REFERENCE) { return inner->data.reference.elem; @@ -1772,12 +1811,13 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { TSNode left = ts_node_child_by_field_name(node, "left", 4); for (uint32_t i = 0; i < ts_node_child_count(node); i++) { TSNode c = ts_node_child(node, i); - if (ts_node_is_named(c)) continue; - char* op = rust_node_text(ctx, c); - if (!op) continue; - if (strcmp(op, "==") == 0 || strcmp(op, "!=") == 0 || - strcmp(op, "<") == 0 || strcmp(op, ">") == 0 || - strcmp(op, "<=") == 0 || strcmp(op, ">=") == 0 || + if (ts_node_is_named(c)) + continue; + char *op = rust_node_text(ctx, c); + if (!op) + continue; + if (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"); } @@ -1795,18 +1835,21 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { if (ts_node_is_null(value) && ts_node_named_child_count(node) > 0) { value = ts_node_named_child(node, 0); } - const CBMType* op_type = rust_eval_expr_type(ctx, value); - const CBMType* base = op_type; - while (base && base->kind == CBM_TYPE_REFERENCE) base = base->data.reference.elem; + const CBMType *op_type = rust_eval_expr_type(ctx, value); + const CBMType *base = op_type; + while (base && base->kind == CBM_TYPE_REFERENCE) + base = base->data.reference.elem; if (base && base->kind == CBM_TYPE_SLICE) { return base->data.slice.elem; } if (base && base->kind == CBM_TYPE_TEMPLATE) { - const char* nm = base->data.template_type.template_name; - if ((strstr(nm, "Vec") || strstr(nm, "VecDeque")) && base->data.template_type.arg_count > 0) { + const char *nm = base->data.template_type.template_name; + if ((strstr(nm, "Vec") || strstr(nm, "VecDeque")) && + base->data.template_type.arg_count > 0) { return base->data.template_type.template_args[0]; } - if ((strstr(nm, "HashMap") || strstr(nm, "BTreeMap")) && base->data.template_type.arg_count > 1) { + if ((strstr(nm, "HashMap") || strstr(nm, "BTreeMap")) && + base->data.template_type.arg_count > 1) { return base->data.template_type.template_args[1]; } } @@ -1820,9 +1863,9 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { /* try_expression: e? — peel one Result / Option layer. */ if (strcmp(kind, "try_expression") == 0 && ts_node_named_child_count(node) > 0) { - const CBMType* inner = rust_eval_expr_type(ctx, ts_node_named_child(node, 0)); + const CBMType *inner = rust_eval_expr_type(ctx, ts_node_named_child(node, 0)); if (inner && inner->kind == CBM_TYPE_TEMPLATE) { - const char* nm = inner->data.template_type.template_name; + const char *nm = inner->data.template_type.template_name; if ((strstr(nm, "Result") || strstr(nm, "Option")) && inner->data.template_type.arg_count > 0) { return inner->data.template_type.template_args[0]; @@ -1833,9 +1876,8 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { /* await_expression: future.await — peel one Future / Poll layer naively. */ if (strcmp(kind, "await_expression") == 0 && ts_node_named_child_count(node) > 0) { - const CBMType* inner = rust_eval_expr_type(ctx, ts_node_named_child(node, 0)); - if (inner && inner->kind == CBM_TYPE_TEMPLATE && - inner->data.template_type.arg_count > 0) { + const CBMType *inner = rust_eval_expr_type(ctx, ts_node_named_child(node, 0)); + if (inner && inner->kind == CBM_TYPE_TEMPLATE && inner->data.template_type.arg_count > 0) { return inner->data.template_type.template_args[0]; } return inner; @@ -1851,7 +1893,7 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { /* tuple_expression */ if (strcmp(kind, "tuple_expression") == 0) { - const CBMType* elems[16]; + const CBMType *elems[16]; int count = 0; uint32_t nc = ts_node_named_child_count(node); for (uint32_t i = 0; i < nc && count < 16; i++) { @@ -1869,7 +1911,7 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { /* array_expression: [a, b, c] */ if (strcmp(kind, "array_expression") == 0) { if (ts_node_named_child_count(node) > 0) { - const CBMType* elem = rust_eval_expr_type(ctx, ts_node_named_child(node, 0)); + const CBMType *elem = rust_eval_expr_type(ctx, ts_node_named_child(node, 0)); return cbm_type_slice(ctx->arena, elem); } return cbm_type_unknown(); @@ -1879,11 +1921,11 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { * Iterator so chains like `(0..n).map(...).count()` keep * typing. The element type is the type of the start/end expr. */ if (strcmp(kind, "range_expression") == 0) { - const CBMType* elem_type = NULL; + const CBMType *elem_type = NULL; uint32_t nc = ts_node_named_child_count(node); for (uint32_t i = 0; i < nc; i++) { TSNode c = ts_node_named_child(node, i); - const CBMType* t = rust_eval_expr_type(ctx, c); + const CBMType *t = rust_eval_expr_type(ctx, c); if (t && !cbm_type_is_unknown(t)) { elem_type = t; break; @@ -1907,7 +1949,7 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { name_node = ts_node_named_child(node, 0); } if (!ts_node_is_null(name_node)) { - char* path = rust_node_text(ctx, name_node); + char *path = rust_node_text(ctx, name_node); if (path) { return cbm_type_named(ctx->arena, rust_resolve_path_expr(ctx, path)); } @@ -1946,11 +1988,9 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { bool found = false; for (uint32_t i = nc; i > 0; i--) { TSNode c = ts_node_named_child(node, i - 1); - const char* ck = ts_node_type(c); - if (strcmp(ck, "expression_statement") != 0 && - strcmp(ck, "let_declaration") != 0 && - strcmp(ck, "empty_statement") != 0 && - strcmp(ck, "line_comment") != 0 && + const char *ck = ts_node_type(c); + if (strcmp(ck, "expression_statement") != 0 && strcmp(ck, "let_declaration") != 0 && + strcmp(ck, "empty_statement") != 0 && strcmp(ck, "line_comment") != 0 && strcmp(ck, "block_comment") != 0) { last = c; found = true; @@ -1997,87 +2037,88 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node) { * hint is used post-synthesis: if synthesis returned an under-specified * type (unknown, or a TEMPLATE without args, or a NAMED that the hint * refines), we substitute the hint when it matches structurally. */ -const CBMType* rust_eval_expr_typed(RustLSPContext* ctx, TSNode node, - const CBMType* expected) { - const CBMType* synth = rust_eval_expr_type(ctx, node); - if (!expected) return synth; +const CBMType *rust_eval_expr_typed(RustLSPContext *ctx, TSNode node, const CBMType *expected) { + const CBMType *synth = rust_eval_expr_type(ctx, node); + if (!expected) + return synth; if (!synth || cbm_type_is_unknown(synth)) { return expected; } /* Template with same head and missing args -> use expected. */ if (synth->kind == CBM_TYPE_TEMPLATE && expected->kind == CBM_TYPE_TEMPLATE && - synth->data.template_type.template_name && - expected->data.template_type.template_name && + synth->data.template_type.template_name && expected->data.template_type.template_name && strcmp(synth->data.template_type.template_name, expected->data.template_type.template_name) == 0 && - synth->data.template_type.arg_count == 0 && - expected->data.template_type.arg_count > 0) { + synth->data.template_type.arg_count == 0 && expected->data.template_type.arg_count > 0) { return expected; } /* NAMED matches expected NAMED — no refinement needed. */ if (synth->kind == CBM_TYPE_NAMED && expected->kind == CBM_TYPE_TEMPLATE && - synth->data.named.qualified_name && - expected->data.template_type.template_name && - strcmp(synth->data.named.qualified_name, - expected->data.template_type.template_name) == 0) { - return expected; /* refine NAMED → TEMPLATE with args */ + synth->data.named.qualified_name && expected->data.template_type.template_name && + strcmp(synth->data.named.qualified_name, expected->data.template_type.template_name) == 0) { + return expected; /* refine NAMED → TEMPLATE with args */ } return synth; } /* Member access type: returns the CBMType for `recv.field_or_method`. * Methods return their FUNC signature; fields return the field type. */ -static const CBMType* rust_eval_member_access(RustLSPContext* ctx, const CBMType* recv, - const char* member) { - if (!recv || !member) return cbm_type_unknown(); +static const CBMType *rust_eval_member_access(RustLSPContext *ctx, const CBMType *recv, + const char *member) { + if (!recv || !member) + return cbm_type_unknown(); /* Auto-deref through references. */ - const CBMType* base = recv; + const CBMType *base = recv; while (base && (base->kind == CBM_TYPE_REFERENCE || base->kind == CBM_TYPE_POINTER)) { - base = (base->kind == CBM_TYPE_REFERENCE) - ? base->data.reference.elem - : base->data.pointer.elem; + base = (base->kind == CBM_TYPE_REFERENCE) ? base->data.reference.elem + : base->data.pointer.elem; } - if (!base) return cbm_type_unknown(); + if (!base) + return cbm_type_unknown(); - const char* type_qn = NULL; - const CBMType** template_args = NULL; + const char *type_qn = NULL; + const CBMType **template_args = NULL; int template_count = 0; - const char** template_params = NULL; + const char **template_params = NULL; if (base->kind == CBM_TYPE_NAMED) { type_qn = base->data.named.qualified_name; } else if (base->kind == CBM_TYPE_TEMPLATE) { type_qn = base->data.template_type.template_name; - template_args = (const CBMType**)base->data.template_type.template_args; + template_args = (const CBMType **)base->data.template_type.template_args; template_count = base->data.template_type.arg_count; } else if (base->kind == CBM_TYPE_BUILTIN) { /* Map a few primitives into stdlib QNs so registered methods on * `str`/`String`/integers are findable. */ - const char* nm = base->data.builtin.name; - if (strcmp(nm, "str") == 0) type_qn = "core.str"; - else if (strcmp(nm, "String") == 0) type_qn = "alloc.string.String"; - else type_qn = nm; + const char *nm = base->data.builtin.name; + if (strcmp(nm, "str") == 0) + type_qn = "core.str"; + else if (strcmp(nm, "String") == 0) + type_qn = "alloc.string.String"; + else + type_qn = nm; } else if (base->kind == CBM_TYPE_SLICE) { type_qn = "core.slice"; } else { return cbm_type_unknown(); } - if (!type_qn) return cbm_type_unknown(); + if (!type_qn) + return cbm_type_unknown(); /* Check inherent method first. */ - const CBMRegisteredFunc* method = rust_lookup_method(ctx, type_qn, member); + const CBMRegisteredFunc *method = rust_lookup_method(ctx, type_qn, member); if (method && method->signature) { if (template_args && method->type_param_names) { template_params = method->type_param_names; - const CBMType* sub = rust_substitute_type(ctx->arena, method->signature, - template_params, template_args); + const CBMType *sub = + rust_substitute_type(ctx->arena, method->signature, template_params, template_args); return sub; } /* Substitute any Self placeholder on the return type. */ - const CBMType* sig = method->signature; + const CBMType *sig = method->signature; if (sig && sig->kind == CBM_TYPE_FUNC && sig->data.func.return_types && sig->data.func.return_types[0]) { - const CBMType* ret = sig->data.func.return_types[0]; + const CBMType *ret = sig->data.func.return_types[0]; if (ret && ret->kind == CBM_TYPE_NAMED && strcmp(ret->data.named.qualified_name, "Self") == 0) { return cbm_type_named(ctx->arena, type_qn); @@ -2087,7 +2128,7 @@ static const CBMType* rust_eval_member_access(RustLSPContext* ctx, const CBMType } /* Field on the type's RegisteredType. */ - const CBMType* ft = rust_lookup_field(ctx, type_qn, member, 0); + const CBMType *ft = rust_lookup_field(ctx, type_qn, member, 0); if (ft) { if (template_args && template_params) { return rust_substitute_type(ctx->arena, ft, template_params, template_args); @@ -2101,24 +2142,28 @@ static const CBMType* rust_eval_member_access(RustLSPContext* ctx, const CBMType * 7. Method dispatch + field lookup * ════════════════════════════════════════════════════════════════════ */ -static const CBMType* rust_lookup_field(RustLSPContext* ctx, - const char* type_qn, const char* field_name, int depth) { - if (!type_qn || !field_name || depth > CBM_LSP_MAX_LOOKUP_DEPTH) return NULL; - const CBMRegisteredType* rt = cbm_registry_lookup_type(ctx->registry, type_qn); - if (!rt) return NULL; - if (rt->alias_of) return rust_lookup_field(ctx, rt->alias_of, field_name, depth + 1); +static const CBMType *rust_lookup_field(RustLSPContext *ctx, const char *type_qn, + const char *field_name, int depth) { + if (!type_qn || !field_name || depth > CBM_LSP_MAX_LOOKUP_DEPTH) + return NULL; + const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, type_qn); + if (!rt) + return NULL; + if (rt->alias_of) + return rust_lookup_field(ctx, rt->alias_of, field_name, depth + 1); if (rt->field_names) { for (int i = 0; rt->field_names[i]; i++) { - if (strcmp(rt->field_names[i], field_name) == 0 && - rt->field_types && rt->field_types[i]) { + if (strcmp(rt->field_names[i], field_name) == 0 && rt->field_types && + rt->field_types[i]) { return rt->field_types[i]; } } } if (rt->embedded_types) { for (int i = 0; rt->embedded_types[i]; i++) { - const CBMType* f = rust_lookup_field(ctx, rt->embedded_types[i], field_name, depth + 1); - if (f) return f; + const CBMType *f = rust_lookup_field(ctx, rt->embedded_types[i], field_name, depth + 1); + if (f) + return f; } } return NULL; @@ -2129,21 +2174,27 @@ static const CBMType* rust_lookup_field(RustLSPContext* ctx, * `` where inner is taken from the receiver's * template args. We don't store U literally; instead we let the call * site pass `template_args[idx]` as the new receiver type for retry. */ -static bool rust_type_derefs_to_first_arg(const char* type_qn) { - if (!type_qn) return false; - static const char* derefable[] = { - "alloc.boxed.Box", "std.boxed.Box", - "alloc.rc.Rc", "std.rc.Rc", - "alloc.sync.Arc", "std.sync.Arc", - "core.cell.RefCell", "std.cell.RefCell", - "core.cell.Cell", "std.cell.Cell", - "std.sync.MutexGuard", "std.sync.RwLockReadGuard", - "std.sync.RwLockWriteGuard", - "core.pin.Pin", - NULL - }; - for (const char** p = derefable; *p; p++) { - if (strcmp(*p, type_qn) == 0) return true; +static bool rust_type_derefs_to_first_arg(const char *type_qn) { + if (!type_qn) + return false; + static const char *derefable[] = {"alloc.boxed.Box", + "std.boxed.Box", + "alloc.rc.Rc", + "std.rc.Rc", + "alloc.sync.Arc", + "std.sync.Arc", + "core.cell.RefCell", + "std.cell.RefCell", + "core.cell.Cell", + "std.cell.Cell", + "std.sync.MutexGuard", + "std.sync.RwLockReadGuard", + "std.sync.RwLockWriteGuard", + "core.pin.Pin", + NULL}; + for (const char **p = derefable; *p; p++) { + if (strcmp(*p, type_qn) == 0) + return true; } return false; } @@ -2152,8 +2203,9 @@ static bool rust_type_derefs_to_first_arg(const char* type_qn) { * for a registered `embedded_types` entry whose tail is "Target=" or, * for stdlib smart pointers, peel the first template arg. Returns NULL * if no Deref relationship is known. */ -static const CBMType* rust_deref_step(RustLSPContext* ctx, const CBMType* t) { - if (!t) return NULL; +static const CBMType *rust_deref_step(RustLSPContext *ctx, const CBMType *t) { + if (!t) + return NULL; /* Smart pointers: Box, Rc, Arc, etc. The grammar gives us a * TEMPLATE; the inner type is the first template arg. */ if (t->kind == CBM_TYPE_TEMPLATE && t->data.template_type.template_name) { @@ -2175,11 +2227,11 @@ static const CBMType* rust_deref_step(RustLSPContext* ctx, const CBMType* t) { * `:DerefTarget:` — set up by `impl Deref for X` * post-processing. (Currently not produced by extract_defs; the * call still works for stdlib types.) */ - const CBMRegisteredType* rt = cbm_registry_lookup_type(ctx->registry, - t->data.named.qualified_name); + const CBMRegisteredType *rt = + cbm_registry_lookup_type(ctx->registry, t->data.named.qualified_name); if (rt && rt->embedded_types) { for (int i = 0; rt->embedded_types[i]; i++) { - const char* e = rt->embedded_types[i]; + const char *e = rt->embedded_types[i]; static const char prefix[] = "DerefTarget:"; if (strncmp(e, prefix, sizeof(prefix) - 1) == 0) { return cbm_type_named(ctx->arena, e + sizeof(prefix) - 1); @@ -2190,46 +2242,54 @@ static const CBMType* rust_deref_step(RustLSPContext* ctx, const CBMType* t) { return NULL; } -static const CBMRegisteredFunc* rust_lookup_method_depth(RustLSPContext* 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; +static const CBMRegisteredFunc *rust_lookup_method_depth(RustLSPContext *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 inherent method lookup. */ - const CBMRegisteredFunc* f = cbm_registry_lookup_method_aliased(ctx->registry, type_qn, member_name); - if (f) return f; + const CBMRegisteredFunc *f = + cbm_registry_lookup_method_aliased(ctx->registry, type_qn, member_name); + if (f) + return f; /* Follow alias / embedded chain. */ - const CBMRegisteredType* rt = cbm_registry_lookup_type(ctx->registry, type_qn); + const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, type_qn); if (rt) { if (rt->alias_of) { f = rust_lookup_method_depth(ctx, rt->alias_of, member_name, depth + 1); - if (f) return f; + if (f) + return f; } if (rt->embedded_types) { for (int i = 0; rt->embedded_types[i]; i++) { /* Skip the synthetic DerefTarget marker — handled by the * caller in the receiver-walk loop. */ - if (strncmp(rt->embedded_types[i], "DerefTarget:", 12) == 0) continue; + if (strncmp(rt->embedded_types[i], "DerefTarget:", 12) == 0) + continue; /* Skip bound markers used for type-param trait bound * recording — those live under "Bound:". */ if (strncmp(rt->embedded_types[i], "Bound:", 6) == 0) { /* Dispatch through the bound trait. */ - f = rust_lookup_method_depth(ctx, rt->embedded_types[i] + 6, - member_name, depth + 1); - if (f) return f; + f = rust_lookup_method_depth(ctx, rt->embedded_types[i] + 6, member_name, + depth + 1); + if (f) + return f; continue; } f = rust_lookup_method_depth(ctx, rt->embedded_types[i], member_name, depth + 1); - if (f) return f; + if (f) + return f; } } } return NULL; } -const CBMRegisteredFunc* rust_lookup_method(RustLSPContext* ctx, - const char* type_qn, const char* member_name) { +const CBMRegisteredFunc *rust_lookup_method(RustLSPContext *ctx, const char *type_qn, + const char *member_name) { return rust_lookup_method_depth(ctx, type_qn, member_name, 0); } @@ -2238,10 +2298,12 @@ const CBMRegisteredFunc* rust_lookup_method(RustLSPContext* ctx, * whether the type's `embedded_types` contains `trait_qn` (we treat * `impl Trait for Type` as registering the trait QN as an embedded type * of the receiver). */ -static const CBMRegisteredFunc* rust_lookup_method_in_trait(RustLSPContext* ctx, - const char* trait_qn, const char* method_name) { - if (!ctx || !trait_qn || !method_name) return NULL; - const CBMTypeRegistry* reg = ctx->registry; +static const CBMRegisteredFunc *rust_lookup_method_in_trait(RustLSPContext *ctx, + const char *trait_qn, + const char *method_name) { + if (!ctx || !trait_qn || !method_name) + return NULL; + const CBMTypeRegistry *reg = ctx->registry; /* Trait method is also registered on the trait itself with `receiver_type` * set to the trait QN (default impls / signatures). */ return cbm_registry_lookup_method(reg, trait_qn, method_name); @@ -2250,42 +2312,52 @@ static const CBMRegisteredFunc* rust_lookup_method_in_trait(RustLSPContext* ctx, /* For a method call where the receiver is a `dyn Trait` or `impl Trait`, * try to resolve through the trait's known impls. Returns the chosen * concrete method, the trait method (default impl), or NULL. */ -static const CBMRegisteredFunc* rust_resolve_trait_method(RustLSPContext* ctx, - const char* receiver_type_qn, const char* method_name, int* out_impl_count) { - if (out_impl_count) *out_impl_count = 0; - if (!ctx || !receiver_type_qn || !method_name) return NULL; - const CBMTypeRegistry* reg = ctx->registry; +static const CBMRegisteredFunc *rust_resolve_trait_method(RustLSPContext *ctx, + const char *receiver_type_qn, + const char *method_name, + int *out_impl_count) { + if (out_impl_count) + *out_impl_count = 0; + if (!ctx || !receiver_type_qn || !method_name) + return NULL; + const CBMTypeRegistry *reg = ctx->registry; /* First try inherent method on the receiver itself, following any * type aliases (so `std.sync.Arc.clone` resolves through to * `alloc.sync.Arc.clone` in the registry). */ - const CBMRegisteredFunc* inh = cbm_registry_lookup_method_aliased(reg, receiver_type_qn, method_name); + const CBMRegisteredFunc *inh = + cbm_registry_lookup_method_aliased(reg, receiver_type_qn, method_name); if (inh) { - if (out_impl_count) *out_impl_count = 1; + if (out_impl_count) + *out_impl_count = 1; return inh; } /* Look at every type whose embedded_types include the receiver_type_qn * (treated as a trait): pick the single-impl case. */ - const CBMRegisteredFunc* unique = NULL; + const CBMRegisteredFunc *unique = NULL; int impls = 0; for (int ti = 0; ti < reg->type_count && impls < 3; ti++) { - const CBMRegisteredType* t = ®->types[ti]; - if (!t->embedded_types) continue; + const CBMRegisteredType *t = ®->types[ti]; + if (!t->embedded_types) + continue; for (int j = 0; t->embedded_types[j]; j++) { if (strcmp(t->embedded_types[j], receiver_type_qn) == 0) { - const CBMRegisteredFunc* mf = cbm_registry_lookup_method(reg, - t->qualified_name, method_name); + const CBMRegisteredFunc *mf = + cbm_registry_lookup_method(reg, t->qualified_name, method_name); if (mf) { impls++; - if (impls == 1) unique = mf; + if (impls == 1) + unique = mf; } break; } } } - if (out_impl_count) *out_impl_count = impls; - if (impls == 1) return unique; + if (out_impl_count) + *out_impl_count = impls; + if (impls == 1) + return unique; return rust_lookup_method_in_trait(ctx, receiver_type_qn, method_name); } @@ -2297,11 +2369,11 @@ static const CBMRegisteredFunc* rust_resolve_trait_method(RustLSPContext* ctx, * call expressions. We do this so calls inside `vec![foo()]`, * `assert_eq!(a.bar(), 0)`, and `dbg!(get_value())` still get attributed * to the enclosing function. */ -static void rust_walk_macro_tokens(RustLSPContext* ctx, TSNode node) { - if (ts_node_is_null(node)) return; - const char* kind = ts_node_type(node); - if (strcmp(kind, "call_expression") == 0 || - strcmp(kind, "macro_invocation") == 0 || +static void rust_walk_macro_tokens(RustLSPContext *ctx, TSNode node) { + if (ts_node_is_null(node)) + return; + const char *kind = ts_node_type(node); + if (strcmp(kind, "call_expression") == 0 || strcmp(kind, "macro_invocation") == 0 || strcmp(kind, "field_expression") == 0) { rust_resolve_calls_in_node(ctx, node); return; @@ -2309,7 +2381,8 @@ static void rust_walk_macro_tokens(RustLSPContext* ctx, TSNode node) { uint32_t nc = ts_node_child_count(node); for (uint32_t i = 0; i < nc; i++) { TSNode c = ts_node_child(node, i); - if (!ts_node_is_null(c)) rust_walk_macro_tokens(ctx, c); + if (!ts_node_is_null(c)) + rust_walk_macro_tokens(ctx, c); } } @@ -2318,18 +2391,29 @@ static void rust_walk_macro_tokens(RustLSPContext* ctx, TSNode node) { * `Index::index`, etc. Returns NULL for operators with no overloadable * trait method (comparison/logical operators route through PartialEq / * PartialOrd whose methods we don't model here — sound to skip). */ -static const char* rust_binop_trait_method(const char* op_text) { - if (!op_text) return NULL; - if (strcmp(op_text, "+") == 0) return "add"; - if (strcmp(op_text, "-") == 0) return "sub"; - if (strcmp(op_text, "*") == 0) return "mul"; - if (strcmp(op_text, "/") == 0) return "div"; - if (strcmp(op_text, "%") == 0) return "rem"; - if (strcmp(op_text, "&") == 0) return "bitand"; - if (strcmp(op_text, "|") == 0) return "bitor"; - if (strcmp(op_text, "^") == 0) return "bitxor"; - if (strcmp(op_text, "<<") == 0) return "shl"; - if (strcmp(op_text, ">>") == 0) return "shr"; +static const char *rust_binop_trait_method(const char *op_text) { + if (!op_text) + return NULL; + if (strcmp(op_text, "+") == 0) + return "add"; + if (strcmp(op_text, "-") == 0) + return "sub"; + if (strcmp(op_text, "*") == 0) + return "mul"; + if (strcmp(op_text, "/") == 0) + return "div"; + if (strcmp(op_text, "%") == 0) + return "rem"; + if (strcmp(op_text, "&") == 0) + return "bitand"; + if (strcmp(op_text, "|") == 0) + return "bitor"; + if (strcmp(op_text, "^") == 0) + return "bitxor"; + if (strcmp(op_text, "<<") == 0) + return "shl"; + if (strcmp(op_text, ">>") == 0) + return "shr"; return NULL; } @@ -2339,20 +2423,22 @@ static const char* rust_binop_trait_method(const char* op_text) { * T::index). Sound-only: we emit nothing when the operand type is unknown, * primitive, or the type has no such method registered — so we never guess on * built-in arithmetic. */ -static void rust_emit_operator_call(RustLSPContext* ctx, const CBMType* recv, - const char* method) { - if (!recv || !method) return; - const CBMType* base = recv; +static void rust_emit_operator_call(RustLSPContext *ctx, const CBMType *recv, const char *method) { + if (!recv || !method) + return; + const CBMType *base = recv; while (base && (base->kind == CBM_TYPE_REFERENCE || base->kind == CBM_TYPE_POINTER)) { - base = (base->kind == CBM_TYPE_REFERENCE) - ? base->data.reference.elem : base->data.pointer.elem; + base = (base->kind == CBM_TYPE_REFERENCE) ? base->data.reference.elem + : base->data.pointer.elem; } /* Only user-defined named types — built-in arithmetic must not emit. */ - if (!base || base->kind != CBM_TYPE_NAMED) return; - const char* type_qn = base->data.named.qualified_name; - if (!type_qn || is_rust_primitive(type_qn)) return; + if (!base || base->kind != CBM_TYPE_NAMED) + return; + const char *type_qn = base->data.named.qualified_name; + if (!type_qn || is_rust_primitive(type_qn)) + return; int impl_count = 0; - const CBMRegisteredFunc* m = rust_resolve_trait_method(ctx, type_qn, method, &impl_count); + const CBMRegisteredFunc *m = rust_resolve_trait_method(ctx, type_qn, method, &impl_count); if (m && m->qualified_name) { rust_emit_resolved_call(ctx, m->qualified_name, "lsp_operator_trait", CBM_RUST_CONF_OPERATOR); @@ -2375,22 +2461,19 @@ static void rust_emit_operator_call(RustLSPContext* ctx, const CBMType* recv, * default for macro recursion safety). */ typedef struct RustMacroRule { - const char* macro_name; - const char* pattern_text; /* left-hand side (without outer brackets) */ + const char *macro_name; + const char *pattern_text; /* left-hand side (without outer brackets) */ int pattern_len; - const char* transcriber_text; + const char *transcriber_text; int transcriber_len; } RustMacroRule; /* Strip a single outer pair of matching brackets from a token-tree * text representation. Returns the inner span via out_text/out_len. * If no brackets, returns the original. */ -static void rust_macro_strip_outer(const char* tt, int len, - const char** out_text, int* out_len) { - if (len >= 2 && - ((tt[0] == '{' && tt[len - 1] == '}') || - (tt[0] == '(' && tt[len - 1] == ')') || - (tt[0] == '[' && tt[len - 1] == ']'))) { +static void rust_macro_strip_outer(const char *tt, int len, const char **out_text, int *out_len) { + if (len >= 2 && ((tt[0] == '{' && tt[len - 1] == '}') || (tt[0] == '(' && tt[len - 1] == ')') || + (tt[0] == '[' && tt[len - 1] == ']'))) { *out_text = tt + 1; *out_len = len - 2; } else { @@ -2399,40 +2482,45 @@ static void rust_macro_strip_outer(const char* tt, int len, } } -static void rust_record_macro_rule(RustLSPContext* ctx, const char* macro_name, - TSNode pattern, TSNode transcriber) { - if (!ctx || !macro_name || ts_node_is_null(transcriber)) return; +static void rust_record_macro_rule(RustLSPContext *ctx, const char *macro_name, TSNode pattern, + TSNode transcriber) { + if (!ctx || !macro_name || ts_node_is_null(transcriber)) + return; if (ctx->macro_rules_count % 16 == 0) { int new_cap = ctx->macro_rules_count + 16; - struct RustMacroRule** narr = (struct RustMacroRule**)cbm_arena_alloc( - ctx->arena, new_cap * sizeof(struct RustMacroRule*)); - if (!narr) return; + struct RustMacroRule **narr = (struct RustMacroRule **)cbm_arena_alloc( + ctx->arena, new_cap * sizeof(struct RustMacroRule *)); + if (!narr) + return; if (ctx->macro_rules_arr && ctx->macro_rules_count > 0) { memcpy(narr, ctx->macro_rules_arr, - ctx->macro_rules_count * sizeof(struct RustMacroRule*)); + ctx->macro_rules_count * sizeof(struct RustMacroRule *)); } ctx->macro_rules_arr = narr; } - RustMacroRule* r = (RustMacroRule*)cbm_arena_alloc(ctx->arena, sizeof(*r)); - if (!r) return; + RustMacroRule *r = (RustMacroRule *)cbm_arena_alloc(ctx->arena, sizeof(*r)); + if (!r) + return; memset(r, 0, sizeof(*r)); r->macro_name = cbm_arena_strdup(ctx->arena, macro_name); /* Cache pattern text for matching at invocation time. */ if (!ts_node_is_null(pattern)) { - char* pt = cbm_node_text(ctx->arena, pattern, ctx->source); + char *pt = cbm_node_text(ctx->arena, pattern, ctx->source); if (pt) { - const char* inner; int inner_len; + const char *inner; + int inner_len; rust_macro_strip_outer(pt, (int)strlen(pt), &inner, &inner_len); r->pattern_text = cbm_arena_strndup(ctx->arena, inner, (size_t)inner_len); r->pattern_len = inner_len; } } - char* tt = cbm_node_text(ctx->arena, transcriber, ctx->source); + char *tt = cbm_node_text(ctx->arena, transcriber, ctx->source); if (tt) { int len = (int)strlen(tt); - const char* inner; int inner_len; + const char *inner; + int inner_len; rust_macro_strip_outer(tt, len, &inner, &inner_len); r->transcriber_text = cbm_arena_strndup(ctx->arena, inner, (size_t)inner_len); r->transcriber_len = inner_len; @@ -2440,23 +2528,29 @@ static void rust_record_macro_rule(RustLSPContext* ctx, const char* macro_name, ctx->macro_rules_arr[ctx->macro_rules_count++] = r; } -static void rust_collect_macro_rules(RustLSPContext* ctx, TSNode root) { - if (ts_node_is_null(root)) return; +static void rust_collect_macro_rules(RustLSPContext *ctx, TSNode root) { + if (ts_node_is_null(root)) + return; uint32_t nc = ts_node_child_count(root); for (uint32_t i = 0; i < nc; i++) { TSNode c = ts_node_child(root, i); - if (ts_node_is_null(c)) continue; - const char* k = ts_node_type(c); + if (ts_node_is_null(c)) + continue; + const char *k = ts_node_type(c); if (strcmp(k, "macro_definition") == 0) { TSNode name_node = ts_node_child_by_field_name(c, "name", 4); - if (ts_node_is_null(name_node)) continue; - char* macro_name = rust_node_text(ctx, name_node); - if (!macro_name) continue; + if (ts_node_is_null(name_node)) + continue; + char *macro_name = rust_node_text(ctx, name_node); + if (!macro_name) + continue; uint32_t mnc = ts_node_child_count(c); for (uint32_t j = 0; j < mnc; j++) { TSNode rule = ts_node_child(c, j); - if (ts_node_is_null(rule) || !ts_node_is_named(rule)) continue; - if (strcmp(ts_node_type(rule), "macro_rule") != 0) continue; + if (ts_node_is_null(rule) || !ts_node_is_named(rule)) + continue; + if (strcmp(ts_node_type(rule), "macro_rule") != 0) + continue; TSNode left = ts_node_child_by_field_name(rule, "left", 4); TSNode right = ts_node_child_by_field_name(rule, "right", 5); if (!ts_node_is_null(right)) { @@ -2465,7 +2559,8 @@ static void rust_collect_macro_rules(RustLSPContext* ctx, TSNode root) { } } else if (strcmp(k, "mod_item") == 0) { TSNode body = ts_node_child_by_field_name(c, "body", 4); - if (!ts_node_is_null(body)) rust_collect_macro_rules(ctx, body); + if (!ts_node_is_null(body)) + rust_collect_macro_rules(ctx, body); } } } @@ -2485,11 +2580,11 @@ static void rust_collect_macro_rules(RustLSPContext* ctx, TSNode root) { * resolver. */ #define RUST_MACRO_MAX_BINDINGS 32 -#define RUST_MACRO_MAX_REPS 32 +#define RUST_MACRO_MAX_REPS 32 typedef struct { - char name[32]; /* metavar name (without `$`) */ - const char* value; /* substring of the invocation args */ + char name[32]; /* metavar name (without `$`) */ + const char *value; /* substring of the invocation args */ int value_len; } MacroBinding; @@ -2500,21 +2595,25 @@ typedef struct { * collects an array of values (one per iteration). */ struct { char name[32]; - const char** values; /* arena-allocated array of strings */ - int* lengths; + const char **values; /* arena-allocated array of strings */ + int *lengths; int count; } reps[RUST_MACRO_MAX_REPS]; int rep_count; } MacroEnv; /* Skip leading ASCII whitespace and Rust line comments. */ -static int macro_skip_ws(const char* s, int len, int from) { +static int macro_skip_ws(const char *s, int len, int from) { while (from < len) { char c = s[from]; - if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { from++; continue; } + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { + from++; + continue; + } if (c == '/' && from + 1 < len && s[from + 1] == '/') { from += 2; - while (from < len && s[from] != '\n') from++; + while (from < len && s[from] != '\n') + from++; continue; } break; @@ -2524,18 +2623,21 @@ static int macro_skip_ws(const char* s, int len, int from) { /* Consume an identifier (ASCII rust ident). Returns end position or * `from` if no identifier. */ -static int macro_consume_ident(const char* s, int len, int from) { +static int macro_consume_ident(const char *s, int len, int from) { int p = from; - if (p >= len) return from; + if (p >= len) + return from; char c = s[p]; - if (!(c == '_' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))) return from; + if (!(c == '_' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))) + return from; p++; while (p < len) { c = s[p]; if (c == '_' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) { p++; - } else break; + } else + break; } return p; } @@ -2543,14 +2645,19 @@ static int macro_consume_ident(const char* s, int len, int from) { /* Consume a balanced bracket group starting at `from` (which must * point at `(`, `[`, or `{`). Returns end position (just after the * close bracket) or `from` if not a bracket. */ -static int macro_consume_balanced(const char* s, int len, int from) { - if (from >= len) return from; +static int macro_consume_balanced(const char *s, int len, int from) { + if (from >= len) + return from; char open = s[from]; char close = 0; - if (open == '(') close = ')'; - else if (open == '[') close = ']'; - else if (open == '{') close = '}'; - else return from; + if (open == '(') + close = ')'; + else if (open == '[') + close = ']'; + else if (open == '{') + close = '}'; + else + return from; int depth = 1; int p = from + 1; while (p < len && depth > 0) { @@ -2559,24 +2666,36 @@ static int macro_consume_balanced(const char* s, int len, int from) { /* Skip string literal. */ p++; while (p < len && s[p] != '"') { - if (s[p] == '\\' && p + 1 < len) p += 2; - else p++; + if (s[p] == '\\' && p + 1 < len) + p += 2; + else + p++; } - if (p < len) p++; + if (p < len) + p++; continue; } if (c == '\'') { /* Could be lifetime or char literal — accept char. */ int q = p + 1; - if (q < len && s[q] == '\\') { q++; if (q < len) q++; } - else if (q < len) q++; - if (q < len && s[q] == '\'') { p = q + 1; continue; } + if (q < len && s[q] == '\\') { + q++; + if (q < len) + q++; + } else if (q < len) + q++; + if (q < len && s[q] == '\'') { + p = q + 1; + continue; + } /* otherwise treat as lifetime — skip until non-ident. */ p = macro_consume_ident(s, len, p + 1); continue; } - if (c == '(' || c == '[' || c == '{') depth++; - else if (c == ')' || c == ']' || c == '}') depth--; + if (c == '(' || c == '[' || c == '{') + depth++; + else if (c == ')' || c == ']' || c == '}') + depth--; p++; } return p; @@ -2584,11 +2703,12 @@ static int macro_consume_balanced(const char* s, int len, int from) { /* Consume a fragment from the input matching the given fragment kind. * Returns end position or `from` if the consumption failed. */ -static int macro_consume_fragment(const char* s, int len, int from, - const char* frag) { +static int macro_consume_fragment(const char *s, int len, int from, const char *frag) { from = macro_skip_ws(s, len, from); - if (from >= len) return from; - if (!frag) frag = "tt"; + if (from >= len) + return from; + if (!frag) + frag = "tt"; if (strcmp(frag, "ident") == 0) { return macro_consume_ident(s, len, from); @@ -2596,22 +2716,28 @@ static int macro_consume_fragment(const char* s, int len, int from, if (strcmp(frag, "literal") == 0) { /* Numeric, string, char, bool literal. */ char c = s[from]; - if (c == '"') return macro_consume_balanced(s, len, from); + if (c == '"') + return macro_consume_balanced(s, len, from); if (c == '\'') { int q = from + 1; - if (q < len && s[q] == '\\') { q++; if (q < len) q++; } - else if (q < len) q++; - if (q < len && s[q] == '\'') return q + 1; + if (q < len && s[q] == '\\') { + q++; + if (q < len) + q++; + } else if (q < len) + q++; + if (q < len && s[q] == '\'') + return q + 1; return from; } if (c == '-' || (c >= '0' && c <= '9')) { int p = from; - if (c == '-') p++; - while (p < len && ((s[p] >= '0' && s[p] <= '9') || s[p] == '_' || - s[p] == '.' || s[p] == 'x' || s[p] == 'X' || - s[p] == 'b' || s[p] == 'o' || - (s[p] >= 'a' && s[p] <= 'f') || - (s[p] >= 'A' && s[p] <= 'F'))) p++; + if (c == '-') + p++; + while (p < len && ((s[p] >= '0' && s[p] <= '9') || s[p] == '_' || s[p] == '.' || + s[p] == 'x' || s[p] == 'X' || s[p] == 'b' || s[p] == 'o' || + (s[p] >= 'a' && s[p] <= 'f') || (s[p] >= 'A' && s[p] <= 'F'))) + p++; /* Allow type suffix. */ int after = macro_consume_ident(s, len, p); return after; @@ -2630,7 +2756,8 @@ static int macro_consume_fragment(const char* s, int len, int from, if (c == '_' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-') { int p = macro_consume_ident(s, len, from); - if (p > from) return p; + if (p > from) + return p; return macro_consume_fragment(s, len, from, "literal"); } return from + 1; @@ -2638,7 +2765,8 @@ static int macro_consume_fragment(const char* s, int len, int from, /* expr / ty / path / pat / stmt / block — balance brackets, stop * at top-level `,` or end of input. */ if (strcmp(frag, "block") == 0) { - if (s[from] == '{') return macro_consume_balanced(s, len, from); + if (s[from] == '{') + return macro_consume_balanced(s, len, from); return from; } /* For expr/ty/path/pat/stmt: greedy balanced span. */ @@ -2648,19 +2776,26 @@ static int macro_consume_fragment(const char* s, int len, int from, char c = s[p]; if (c == '"' || c == '\'') { int q = macro_consume_fragment(s, len, p, "literal"); - if (q == p) { p++; continue; } + if (q == p) { + p++; + continue; + } p = q; continue; } if (c == '(' || c == '[' || c == '{') { int q = macro_consume_balanced(s, len, p); - if (q == p) break; + if (q == p) + break; p = q; continue; } - if (c == ')' || c == ']' || c == '}') break; - if (depth == 0 && c == ',') break; - if (strcmp(frag, "stmt") == 0 && c == ';') break; + if (c == ')' || c == ']' || c == '}') + break; + if (depth == 0 && c == ',') + break; + if (strcmp(frag, "stmt") == 0 && c == ';') + break; p++; } return p; @@ -2668,11 +2803,13 @@ static int macro_consume_fragment(const char* s, int len, int from, /* Bind a metavar in the env. Returns false if the table is full or the * name doesn't fit. */ -static bool macro_env_bind(MacroEnv* env, const char* name, int name_len, - const char* val, int val_len) { - if (env->count >= RUST_MACRO_MAX_BINDINGS) return false; - if (name_len <= 0 || name_len >= 32) return false; - MacroBinding* b = &env->kv[env->count++]; +static bool macro_env_bind(MacroEnv *env, const char *name, int name_len, const char *val, + int val_len) { + if (env->count >= RUST_MACRO_MAX_BINDINGS) + return false; + if (name_len <= 0 || name_len >= 32) + return false; + MacroBinding *b = &env->kv[env->count++]; memcpy(b->name, name, name_len); b->name[name_len] = '\0'; b->value = val; @@ -2680,9 +2817,10 @@ static bool macro_env_bind(MacroEnv* env, const char* name, int name_len, return true; } -static const MacroBinding* macro_env_lookup(const MacroEnv* env, const char* name) { +static const MacroBinding *macro_env_lookup(const MacroEnv *env, const char *name) { for (int i = 0; i < env->count; i++) { - if (strcmp(env->kv[i].name, name) == 0) return &env->kv[i]; + if (strcmp(env->kv[i].name, name) == 0) + return &env->kv[i]; } return NULL; } @@ -2690,15 +2828,16 @@ static const MacroBinding* macro_env_lookup(const MacroEnv* env, const char* nam /* Match a pattern against an input. Pattern fragments like `$x:expr` * bind into `env`. Returns true if the pattern matches the whole input * (or up to the end of pattern, with trailing whitespace in input). */ -static bool macro_pattern_match(const char* pat, int pat_len, - const char* in, int in_len, MacroEnv* env) { - int pp = 0; /* pattern pos */ - int ip = 0; /* input pos */ +static bool macro_pattern_match(const char *pat, int pat_len, const char *in, int in_len, + MacroEnv *env) { + int pp = 0; /* pattern pos */ + int ip = 0; /* input pos */ while (pp < pat_len) { pp = macro_skip_ws(pat, pat_len, pp); ip = macro_skip_ws(in, in_len, ip); - if (pp >= pat_len) break; + if (pp >= pat_len) + break; char pc = pat[pp]; @@ -2713,8 +2852,7 @@ static bool macro_pattern_match(const char* pat, int pat_len, int after = macro_consume_balanced(pat, pat_len, pp); pp = after; /* Skip optional separator + kind (one char each). */ - if (pp < pat_len && pat[pp] != ' ' && pat[pp] != '$' && - pat[pp] != '\n') { + if (pp < pat_len && pat[pp] != ' ' && pat[pp] != '$' && pat[pp] != '\n') { pp++; } if (pp < pat_len && (pat[pp] == '*' || pat[pp] == '+' || pat[pp] == '?')) { @@ -2723,16 +2861,16 @@ static bool macro_pattern_match(const char* pat, int pat_len, /* Consume the rest of the input greedy until we hit * the next literal pattern char. */ int next_lit = pp; - while (next_lit < pat_len && - (pat[next_lit] == ' ' || pat[next_lit] == '\t' || - pat[next_lit] == '\n' || pat[next_lit] == '\r')) { + while (next_lit < pat_len && (pat[next_lit] == ' ' || pat[next_lit] == '\t' || + pat[next_lit] == '\n' || pat[next_lit] == '\r')) { next_lit++; } if (next_lit >= pat_len) { ip = in_len; } else { char target = pat[next_lit]; - while (ip < in_len && in[ip] != target) ip++; + while (ip < in_len && in[ip] != target) + ip++; } continue; } @@ -2740,8 +2878,9 @@ static bool macro_pattern_match(const char* pat, int pat_len, int name_start = pp; pp = macro_consume_ident(pat, pat_len, pp); int name_end = pp; - if (name_end == name_start) return false; - const char* frag = "tt"; + if (name_end == name_start) + return false; + const char *frag = "tt"; if (pp < pat_len && pat[pp] == ':') { pp++; int frag_start = pp; @@ -2764,17 +2903,19 @@ static bool macro_pattern_match(const char* pat, int pat_len, return false; } ip = val_end; - if (!macro_env_bind(env, pat + name_start, name_end - name_start, - in + val_start, val_end - val_start)) { + if (!macro_env_bind(env, pat + name_start, name_end - name_start, in + val_start, + val_end - val_start)) { return false; } continue; } /* Literal: must match input verbatim. */ - if (ip >= in_len) return false; + if (ip >= in_len) + return false; char ic = in[ip]; - if (pc != ic) return false; + if (pc != ic) + return false; pp++; ip++; } @@ -2785,13 +2926,13 @@ static bool macro_pattern_match(const char* pat, int pat_len, /* Substitute env bindings into the transcriber text. Allocates a * fresh string in the arena. */ -static char* macro_substitute(CBMArena* arena, - const char* xs, int xs_len, const MacroEnv* env) { +static char *macro_substitute(CBMArena *arena, const char *xs, int xs_len, const MacroEnv *env) { /* Estimate output size: each metavar expansion could be up to * ~256 bytes; bound the total at xs_len * 4 + 4KB. */ int cap = xs_len * 4 + 4096; - char* out = (char*)cbm_arena_alloc(arena, cap + 1); - if (!out) return NULL; + char *out = (char *)cbm_arena_alloc(arena, cap + 1); + if (!out) + return NULL; int op = 0; int xp = 0; while (xp < xs_len) { @@ -2804,8 +2945,7 @@ static char* macro_substitute(CBMArena* arena, int after = macro_consume_balanced(xs, xs_len, xp + 1); int body_end = after - 1; /* Emit the body recursively substituted. */ - char* inner = macro_substitute(arena, - xs + body_start, body_end - body_start, env); + char *inner = macro_substitute(arena, xs + body_start, body_end - body_start, env); if (inner) { int il = (int)strlen(inner); if (op + il < cap) { @@ -2815,7 +2955,8 @@ static char* macro_substitute(CBMArena* arena, } xp = after; /* Skip optional separator + kind. */ - if (xp < xs_len && xs[xp] != ' ' && xs[xp] != '\n') xp++; + if (xp < xs_len && xs[xp] != ' ' && xs[xp] != '\n') + xp++; if (xp < xs_len && (xs[xp] == '*' || xs[xp] == '+' || xs[xp] == '?')) { xp++; } @@ -2830,7 +2971,7 @@ static char* macro_substitute(CBMArena* arena, if (nl < 32) { memcpy(name_buf, xs + name_start, nl); name_buf[nl] = '\0'; - const MacroBinding* b = macro_env_lookup(env, name_buf); + const MacroBinding *b = macro_env_lookup(env, name_buf); if (b && b->value && op + b->value_len < cap) { memcpy(out + op, b->value, b->value_len); op += b->value_len; @@ -2840,7 +2981,8 @@ static char* macro_substitute(CBMArena* arena, } } } - if (op < cap) out[op++] = c; + if (op < cap) + out[op++] = c; xp++; } out[op] = '\0'; @@ -2849,24 +2991,25 @@ static char* macro_substitute(CBMArena* arena, /* Re-parse a user macro's transcriber body as a synthetic Rust * function and walk it for calls. */ -extern const TSLanguage* tree_sitter_rust(void); -static void rust_expand_user_macro(RustLSPContext* ctx, const char* mname, - TSNode invocation) { - if (!ctx || !mname || !ctx->macro_rules_arr) return; - if (ctx->macro_expand_depth >= 8) return; +extern const TSLanguage *tree_sitter_rust(void); +static void rust_expand_user_macro(RustLSPContext *ctx, const char *mname, TSNode invocation) { + if (!ctx || !mname || !ctx->macro_rules_arr) + return; + if (ctx->macro_expand_depth >= 8) + return; /* Try each rule until one matches; first to match wins. */ - RustMacroRule* hit = NULL; + RustMacroRule *hit = NULL; MacroEnv env; memset(&env, 0, sizeof(env)); /* Extract the invocation argument text. */ - const char* inv_args = NULL; + const char *inv_args = NULL; int inv_args_len = 0; if (!ts_node_is_null(invocation)) { TSNode args_tt = ts_node_child_by_field_name(invocation, "arguments", 9); if (!ts_node_is_null(args_tt)) { - char* at = cbm_node_text(ctx->arena, args_tt, ctx->source); + char *at = cbm_node_text(ctx->arena, args_tt, ctx->source); if (at) { rust_macro_strip_outer(at, (int)strlen(at), &inv_args, &inv_args_len); } @@ -2874,48 +3017,54 @@ static void rust_expand_user_macro(RustLSPContext* ctx, const char* mname, } for (int i = 0; i < ctx->macro_rules_count; i++) { - RustMacroRule* r = ctx->macro_rules_arr[i]; - if (strcmp(r->macro_name, mname) != 0) continue; + RustMacroRule *r = ctx->macro_rules_arr[i]; + if (strcmp(r->macro_name, mname) != 0) + continue; memset(&env, 0, sizeof(env)); if (r->pattern_text && inv_args && - macro_pattern_match(r->pattern_text, r->pattern_len, - inv_args, inv_args_len, &env)) { + macro_pattern_match(r->pattern_text, r->pattern_len, inv_args, inv_args_len, &env)) { hit = r; break; } /* Fall back to first-rule match without arg substitution if * the pattern doesn't match — still expand to walk the body. */ - if (!hit) hit = r; + if (!hit) + hit = r; } - if (!hit || !hit->transcriber_text || hit->transcriber_len <= 0) return; + if (!hit || !hit->transcriber_text || hit->transcriber_len <= 0) + return; /* Substitute the bound metavars into the transcriber body. */ - char* substituted = macro_substitute(ctx->arena, - hit->transcriber_text, hit->transcriber_len, &env); - if (!substituted) return; + char *substituted = + macro_substitute(ctx->arena, hit->transcriber_text, hit->transcriber_len, &env); + if (!substituted) + return; /* Wrap and parse. */ - char* wrapped = cbm_arena_sprintf(ctx->arena, - "fn __cbm_macro_expand() { %s; }\n", substituted); - if (!wrapped) return; + char *wrapped = cbm_arena_sprintf(ctx->arena, "fn __cbm_macro_expand() { %s; }\n", substituted); + if (!wrapped) + return; - TSParser* parser = ts_parser_new(); - if (!parser) return; + TSParser *parser = ts_parser_new(); + if (!parser) + return; ts_parser_set_language(parser, tree_sitter_rust()); - TSTree* tree = ts_parser_parse_string(parser, NULL, wrapped, - (uint32_t)strlen(wrapped)); + TSTree *tree = ts_parser_parse_string(parser, NULL, wrapped, (uint32_t)strlen(wrapped)); if (tree) { ctx->macro_expand_depth++; TSNode root = ts_tree_root_node(tree); uint32_t rnc = ts_node_child_count(root); for (uint32_t i = 0; i < rnc; i++) { TSNode top = ts_node_child(root, i); - if (ts_node_is_null(top)) continue; - if (strcmp(ts_node_type(top), "function_item") != 0) continue; + if (ts_node_is_null(top)) + continue; + if (strcmp(ts_node_type(top), "function_item") != 0) + continue; TSNode body = ts_node_child_by_field_name(top, "body", 4); - if (ts_node_is_null(body)) continue; + if (ts_node_is_null(body)) + continue; /* Swap source so node_text inside reads from `wrapped`. */ - const char* saved_source = ctx->source; + const char *saved_source = ctx->source; int saved_len = ctx->source_len; ctx->source = wrapped; ctx->source_len = (int)strlen(wrapped); @@ -2940,9 +3089,11 @@ static void rust_expand_user_macro(RustLSPContext* ctx, const char* mname, * makes. We wrap the args in a block so each comma-separated argument parses * as its own statement; the current scope (params/locals) is preserved so * typed receivers still resolve. */ -static void rust_resolve_macro_arg_exprs(RustLSPContext* ctx, TSNode invocation) { - if (!ctx || ts_node_is_null(invocation)) return; - if (ctx->macro_expand_depth >= 8) return; +static void rust_resolve_macro_arg_exprs(RustLSPContext *ctx, TSNode invocation) { + if (!ctx || ts_node_is_null(invocation)) + return; + if (ctx->macro_expand_depth >= 8) + return; /* The grammar exposes the argument list as a `token_tree` child rather * than via an `arguments` field, so locate it by node type. */ @@ -2955,28 +3106,34 @@ static void rust_resolve_macro_arg_exprs(RustLSPContext* ctx, TSNode invocation) break; } } - if (ts_node_is_null(args_tt)) return; - char* at = cbm_node_text(ctx->arena, args_tt, ctx->source); - if (!at) return; - const char* inner; int inner_len; + if (ts_node_is_null(args_tt)) + return; + char *at = cbm_node_text(ctx->arena, args_tt, ctx->source); + if (!at) + return; + const char *inner; + int inner_len; rust_macro_strip_outer(at, (int)strlen(at), &inner, &inner_len); - if (inner_len <= 0) return; - char* arg_text = cbm_arena_strndup(ctx->arena, inner, (size_t)inner_len); - if (!arg_text) return; + if (inner_len <= 0) + return; + char *arg_text = cbm_arena_strndup(ctx->arena, inner, (size_t)inner_len); + if (!arg_text) + return; /* Wrap the comma-separated arguments in a tuple expression so the whole * thing parses as one valid expression (a trailing format-spec arg like * `width = w` would otherwise break statement parsing). Calls inside any * element are still walked. */ - char* wrapped = cbm_arena_sprintf(ctx->arena, - "fn __cbm_macro_args() { let _ = (%s); }\n", arg_text); - if (!wrapped) return; + char *wrapped = + cbm_arena_sprintf(ctx->arena, "fn __cbm_macro_args() { let _ = (%s); }\n", arg_text); + if (!wrapped) + return; - TSParser* parser = ts_parser_new(); - if (!parser) return; + TSParser *parser = ts_parser_new(); + if (!parser) + return; ts_parser_set_language(parser, tree_sitter_rust()); - TSTree* tree = ts_parser_parse_string(parser, NULL, wrapped, - (uint32_t)strlen(wrapped)); + TSTree *tree = ts_parser_parse_string(parser, NULL, wrapped, (uint32_t)strlen(wrapped)); if (tree) { TSNode root = ts_tree_root_node(tree); /* Bail out if the synthetic source failed to parse cleanly — a @@ -2987,11 +3144,14 @@ static void rust_resolve_macro_arg_exprs(RustLSPContext* ctx, TSNode invocation) uint32_t rnc = ts_node_child_count(root); for (uint32_t i = 0; i < rnc; i++) { TSNode top = ts_node_child(root, i); - if (ts_node_is_null(top)) continue; - if (strcmp(ts_node_type(top), "function_item") != 0) continue; + if (ts_node_is_null(top)) + continue; + if (strcmp(ts_node_type(top), "function_item") != 0) + continue; TSNode body = ts_node_child_by_field_name(top, "body", 4); - if (ts_node_is_null(body)) continue; - const char* saved_source = ctx->source; + if (ts_node_is_null(body)) + continue; + const char *saved_source = ctx->source; int saved_len = ctx->source_len; ctx->source = wrapped; ctx->source_len = (int)strlen(wrapped); @@ -3018,12 +3178,13 @@ static void rust_resolve_macro_arg_exprs(RustLSPContext* ctx, TSNode invocation) /* Recursively bind every identifier inside a pattern node to the given * fallback type. Handles tuple_pattern, struct_pattern, tuple_struct_pattern, * ref_pattern, mut_pattern, captured_pattern, identifier. */ -static void rust_bind_pattern(RustLSPContext* ctx, TSNode pattern, const CBMType* type) { - if (ts_node_is_null(pattern)) return; - const char* kind = ts_node_type(pattern); +static void rust_bind_pattern(RustLSPContext *ctx, TSNode pattern, const CBMType *type) { + if (ts_node_is_null(pattern)) + return; + const char *kind = ts_node_type(pattern); if (strcmp(kind, "identifier") == 0) { - char* name = rust_node_text(ctx, pattern); + char *name = rust_node_text(ctx, pattern); if (name && strcmp(name, "_") != 0) { cbm_scope_bind(ctx->current_scope, name, type); } @@ -3033,13 +3194,14 @@ static void rust_bind_pattern(RustLSPContext* ctx, TSNode pattern, const CBMType /* name @ subpattern */ TSNode name_node = ts_node_child_by_field_name(pattern, "name", 4); if (!ts_node_is_null(name_node)) { - char* name = rust_node_text(ctx, name_node); + char *name = rust_node_text(ctx, name_node); if (name && strcmp(name, "_") != 0) { cbm_scope_bind(ctx->current_scope, name, type); } } TSNode sub = ts_node_child_by_field_name(pattern, "pattern", 7); - if (!ts_node_is_null(sub)) rust_bind_pattern(ctx, sub, type); + if (!ts_node_is_null(sub)) + rust_bind_pattern(ctx, sub, type); return; } if (strcmp(kind, "ref_pattern") == 0 || strcmp(kind, "mut_pattern") == 0 || @@ -3050,11 +3212,12 @@ static void rust_bind_pattern(RustLSPContext* ctx, TSNode pattern, const CBMType return; } if (strcmp(kind, "tuple_pattern") == 0) { - const CBMType* base = type; - while (base && base->kind == CBM_TYPE_REFERENCE) base = base->data.reference.elem; + const CBMType *base = type; + while (base && base->kind == CBM_TYPE_REFERENCE) + base = base->data.reference.elem; uint32_t nc = ts_node_named_child_count(pattern); for (uint32_t i = 0; i < nc; i++) { - const CBMType* elem_t = cbm_type_unknown(); + const CBMType *elem_t = cbm_type_unknown(); if (base && base->kind == CBM_TYPE_TUPLE && (int)i < base->data.tuple.count) { elem_t = base->data.tuple.elems[i]; } @@ -3064,9 +3227,10 @@ static void rust_bind_pattern(RustLSPContext* ctx, TSNode pattern, const CBMType } if (strcmp(kind, "tuple_struct_pattern") == 0) { /* Some(x), Ok(x), Err(e) — peel one Option/Result/template. */ - const CBMType* base = type; - while (base && base->kind == CBM_TYPE_REFERENCE) base = base->data.reference.elem; - const CBMType* inner = cbm_type_unknown(); + const CBMType *base = type; + while (base && base->kind == CBM_TYPE_REFERENCE) + base = base->data.reference.elem; + const CBMType *inner = cbm_type_unknown(); if (base && base->kind == CBM_TYPE_TEMPLATE && base->data.template_type.arg_count > 0) { inner = base->data.template_type.template_args[0]; } @@ -3081,24 +3245,29 @@ static void rust_bind_pattern(RustLSPContext* ctx, TSNode pattern, const CBMType /* For each field_pattern, bind the local name to the field's type. */ TSNode body = ts_node_child_by_field_name(pattern, "body", 4); TSNode iter = ts_node_is_null(body) ? pattern : body; - const CBMType* base = type; - while (base && base->kind == CBM_TYPE_REFERENCE) base = base->data.reference.elem; - const char* type_qn = NULL; - if (base && base->kind == CBM_TYPE_NAMED) type_qn = base->data.named.qualified_name; + const CBMType *base = type; + while (base && base->kind == CBM_TYPE_REFERENCE) + base = base->data.reference.elem; + const char *type_qn = NULL; + if (base && base->kind == CBM_TYPE_NAMED) + type_qn = base->data.named.qualified_name; else if (base && base->kind == CBM_TYPE_TEMPLATE) type_qn = base->data.template_type.template_name; uint32_t nc = ts_node_named_child_count(iter); for (uint32_t i = 0; i < nc; i++) { TSNode fp = ts_node_named_child(iter, i); - const char* fk = ts_node_type(fp); + const char *fk = ts_node_type(fp); if (strcmp(fk, "field_pattern") == 0) { TSNode name_node = ts_node_child_by_field_name(fp, "name", 4); TSNode pat_node = ts_node_child_by_field_name(fp, "pattern", 7); - char* fname = rust_node_text(ctx, name_node); - if (!fname) continue; - const CBMType* ft = type_qn ? rust_lookup_field(ctx, type_qn, fname, 0) : cbm_type_unknown(); - if (!ft) ft = cbm_type_unknown(); + char *fname = rust_node_text(ctx, name_node); + if (!fname) + continue; + const CBMType *ft = + type_qn ? rust_lookup_field(ctx, type_qn, fname, 0) : cbm_type_unknown(); + if (!ft) + ft = cbm_type_unknown(); if (!ts_node_is_null(pat_node)) { rust_bind_pattern(ctx, pat_node, ft); } else { @@ -3106,9 +3275,10 @@ static void rust_bind_pattern(RustLSPContext* ctx, TSNode pattern, const CBMType } } else if (strcmp(fk, "shorthand_field_identifier") == 0 || strcmp(fk, "identifier") == 0) { - char* fname = rust_node_text(ctx, fp); + char *fname = rust_node_text(ctx, fp); if (fname && strcmp(fname, "_") != 0) { - const CBMType* ft = type_qn ? rust_lookup_field(ctx, type_qn, fname, 0) : cbm_type_unknown(); + const CBMType *ft = + type_qn ? rust_lookup_field(ctx, type_qn, fname, 0) : cbm_type_unknown(); cbm_scope_bind(ctx->current_scope, fname, ft ? ft : cbm_type_unknown()); } } @@ -3125,9 +3295,10 @@ static void rust_bind_pattern(RustLSPContext* ctx, TSNode pattern, const CBMType /* Other patterns we ignore for binding purposes. */ } -void rust_process_statement(RustLSPContext* ctx, TSNode node) { - if (ts_node_is_null(node)) return; - const char* kind = ts_node_type(node); +void rust_process_statement(RustLSPContext *ctx, TSNode node) { + if (ts_node_is_null(node)) + return; + const char *kind = ts_node_type(node); /* let_declaration: let pat: T = expr; * @@ -3140,18 +3311,20 @@ void rust_process_statement(RustLSPContext* ctx, TSNode node) { TSNode tn = ts_node_child_by_field_name(node, "type", 4); TSNode val = ts_node_child_by_field_name(node, "value", 5); - const CBMType* annotated = NULL; + const CBMType *annotated = NULL; if (!ts_node_is_null(tn)) { annotated = rust_parse_type_node(ctx, tn); } - const CBMType* let_type = annotated; + const CBMType *let_type = annotated; if ((!let_type || cbm_type_is_unknown(let_type)) && !ts_node_is_null(val)) { /* Synthesis: evaluate the RHS with the (possibly NULL) * annotated type as a hint. */ let_type = rust_eval_expr_typed(ctx, val, annotated); } - if (!let_type) let_type = cbm_type_unknown(); - if (!ts_node_is_null(pat)) rust_bind_pattern(ctx, pat, let_type); + if (!let_type) + let_type = cbm_type_unknown(); + if (!ts_node_is_null(pat)) + rust_bind_pattern(ctx, pat, let_type); return; } @@ -3159,11 +3332,12 @@ void rust_process_statement(RustLSPContext* ctx, TSNode node) { if (strcmp(kind, "const_item") == 0 || strcmp(kind, "static_item") == 0) { TSNode name_node = ts_node_child_by_field_name(node, "name", 4); TSNode tn = ts_node_child_by_field_name(node, "type", 4); - const CBMType* type = ts_node_is_null(tn) ? cbm_type_unknown() - : rust_parse_type_node(ctx, tn); + const CBMType *type = + ts_node_is_null(tn) ? cbm_type_unknown() : rust_parse_type_node(ctx, tn); if (!ts_node_is_null(name_node)) { - char* name = rust_node_text(ctx, name_node); - if (name) cbm_scope_bind(ctx->current_scope, name, type); + char *name = rust_node_text(ctx, name_node); + if (name) + cbm_scope_bind(ctx->current_scope, name, type); } return; } @@ -3179,20 +3353,23 @@ void rust_process_statement(RustLSPContext* ctx, TSNode node) { * callee_name, matching how the resolver's short-name comparison works. Only * used for calls the syntactic extractor cannot see (operator desugaring, * macro-hidden method calls). */ -static void rust_inject_syn_call(RustLSPContext* ctx, const char* callee_qn) { - if (!ctx || !ctx->syn_calls || !callee_qn || !ctx->enclosing_func_qn) return; - const char* dot = strrchr(callee_qn, '.'); - const char* short_name = dot ? dot + 1 : callee_qn; - if (!short_name || !short_name[0]) return; +static void rust_inject_syn_call(RustLSPContext *ctx, const char *callee_qn) { + if (!ctx || !ctx->syn_calls || !callee_qn || !ctx->enclosing_func_qn) + return; + const char *dot = strrchr(callee_qn, '.'); + const char *short_name = dot ? dot + 1 : callee_qn; + if (!short_name || !short_name[0]) + return; CBMCall call = {0}; call.callee_name = cbm_arena_strdup(ctx->arena, short_name); call.enclosing_func_qn = ctx->enclosing_func_qn; cbm_calls_push(ctx->syn_calls, ctx->arena, call); } -static void rust_emit_resolved_call(RustLSPContext* ctx, const char* callee_qn, - const char* strategy, float confidence) { - if (!ctx || !ctx->resolved_calls || !callee_qn || !ctx->enclosing_func_qn) return; +static void rust_emit_resolved_call(RustLSPContext *ctx, const char *callee_qn, + const char *strategy, float confidence) { + if (!ctx || !ctx->resolved_calls || !callee_qn || !ctx->enclosing_func_qn) + return; CBMResolvedCall rc = { .caller_qn = ctx->enclosing_func_qn, .callee_qn = callee_qn, @@ -3206,9 +3383,10 @@ static void rust_emit_resolved_call(RustLSPContext* ctx, const char* callee_qn, } } -static void rust_emit_unresolved_call(RustLSPContext* ctx, const char* expr_text, - const char* reason) { - if (!ctx || !ctx->resolved_calls || !ctx->enclosing_func_qn) return; +static void rust_emit_unresolved_call(RustLSPContext *ctx, const char *expr_text, + const char *reason) { + if (!ctx || !ctx->resolved_calls || !ctx->enclosing_func_qn) + return; CBMResolvedCall rc = { .caller_qn = ctx->enclosing_func_qn, .callee_qn = expr_text ? expr_text : "?", @@ -3220,11 +3398,12 @@ static void rust_emit_unresolved_call(RustLSPContext* ctx, const char* expr_text } /* Entry hook: classify a call_expression and emit the best edge we can. */ -static void rust_resolve_call_expression(RustLSPContext* ctx, TSNode node) { +static void rust_resolve_call_expression(RustLSPContext *ctx, TSNode node) { 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; - const char* fk = ts_node_type(func_node); + if (ts_node_is_null(func_node)) + return; + const char *fk = ts_node_type(func_node); /* Method call via field_expression. The grammar can also expose * `s.cast::(x)` as a `generic_function` callee whose inner @@ -3239,43 +3418,49 @@ static void rust_resolve_call_expression(RustLSPContext* ctx, TSNode node) { if (strcmp(fk, "field_expression") == 0) { TSNode value = ts_node_child_by_field_name(func_node, "value", 5); TSNode field = ts_node_child_by_field_name(func_node, "field", 5); - if (ts_node_is_null(value) || ts_node_is_null(field)) return; - char* mname = rust_node_text(ctx, field); - if (!mname) return; + if (ts_node_is_null(value) || ts_node_is_null(field)) + return; + char *mname = rust_node_text(ctx, field); + if (!mname) + return; /* Strip turbofish from the method name in case the grammar * embedded it as part of the field token. */ rust_strip_turbofish(mname); - const CBMType* recv = rust_eval_expr_type(ctx, value); - const CBMType* base = recv; + const CBMType *recv = rust_eval_expr_type(ctx, value); + const CBMType *base = recv; while (base && (base->kind == CBM_TYPE_REFERENCE || base->kind == CBM_TYPE_POINTER)) { - base = (base->kind == CBM_TYPE_REFERENCE) - ? base->data.reference.elem : base->data.pointer.elem; + base = (base->kind == CBM_TYPE_REFERENCE) ? base->data.reference.elem + : base->data.pointer.elem; } - const char* type_qn = NULL; - if (base && base->kind == CBM_TYPE_NAMED) type_qn = base->data.named.qualified_name; - else if (base && base->kind == CBM_TYPE_TEMPLATE) type_qn = base->data.template_type.template_name; + const char *type_qn = NULL; + if (base && base->kind == CBM_TYPE_NAMED) + type_qn = base->data.named.qualified_name; + else if (base && base->kind == CBM_TYPE_TEMPLATE) + type_qn = base->data.template_type.template_name; else if (base && base->kind == CBM_TYPE_BUILTIN) { - const char* nm = base->data.builtin.name; - if (strcmp(nm, "str") == 0) type_qn = "core.str"; - else if (strcmp(nm, "String") == 0) type_qn = "alloc.string.String"; + const char *nm = base->data.builtin.name; + if (strcmp(nm, "str") == 0) + type_qn = "core.str"; + else if (strcmp(nm, "String") == 0) + type_qn = "alloc.string.String"; else if (is_rust_primitive(nm)) { /* Primitive integer / float / char / bool / unit / never * methods are registered under their primitive name. */ type_qn = nm; } - } - else if (base && base->kind == CBM_TYPE_SLICE) { + } else if (base && base->kind == CBM_TYPE_SLICE) { /* `&[T]` / `[T]` method dispatch. */ type_qn = "core.slice"; } if (type_qn) { int impl_count = 0; - const CBMRegisteredFunc* m = rust_resolve_trait_method(ctx, type_qn, mname, &impl_count); + const CBMRegisteredFunc *m = + rust_resolve_trait_method(ctx, type_qn, mname, &impl_count); if (m) { - const char* strategy = "lsp_method_dispatch"; + const char *strategy = "lsp_method_dispatch"; float conf = CBM_RUST_CONF_METHOD; if (m->receiver_type && strcmp(m->receiver_type, type_qn) != 0) { strategy = "lsp_trait_dispatch"; @@ -3289,30 +3474,39 @@ static void rust_resolve_call_expression(RustLSPContext* ctx, TSNode node) { /* Walk the Deref chain — `Box::method` may live on `T`, * `Rc>::method` peels two levels, etc. We bound * the chain at 8 hops to mirror the rust-analyzer cap. */ - const CBMType* cur = base; + const CBMType *cur = base; for (int hop = 0; hop < 8; hop++) { - const CBMType* next = rust_deref_step(ctx, cur); - if (!next) break; + const CBMType *next = rust_deref_step(ctx, cur); + if (!next) + break; /* Unwrap reference layers introduced by deref. */ - while (next && (next->kind == CBM_TYPE_REFERENCE || next->kind == CBM_TYPE_POINTER)) { - next = (next->kind == CBM_TYPE_REFERENCE) - ? next->data.reference.elem : next->data.pointer.elem; + while (next && + (next->kind == CBM_TYPE_REFERENCE || next->kind == CBM_TYPE_POINTER)) { + next = (next->kind == CBM_TYPE_REFERENCE) ? next->data.reference.elem + : next->data.pointer.elem; } - if (!next) break; - const char* next_qn = NULL; - if (next->kind == CBM_TYPE_NAMED) next_qn = next->data.named.qualified_name; - else if (next->kind == CBM_TYPE_TEMPLATE) next_qn = next->data.template_type.template_name; + if (!next) + break; + const char *next_qn = NULL; + if (next->kind == CBM_TYPE_NAMED) + next_qn = next->data.named.qualified_name; + else if (next->kind == CBM_TYPE_TEMPLATE) + next_qn = next->data.template_type.template_name; else if (next->kind == CBM_TYPE_BUILTIN) { - const char* nm = next->data.builtin.name; - if (strcmp(nm, "str") == 0) next_qn = "core.str"; - else if (strcmp(nm, "String") == 0) next_qn = "alloc.string.String"; + const char *nm = next->data.builtin.name; + if (strcmp(nm, "str") == 0) + next_qn = "core.str"; + else if (strcmp(nm, "String") == 0) + next_qn = "alloc.string.String"; } - if (!next_qn) break; + if (!next_qn) + break; int hop_impls = 0; - const CBMRegisteredFunc* hm = rust_resolve_trait_method(ctx, next_qn, mname, &hop_impls); + const CBMRegisteredFunc *hm = + rust_resolve_trait_method(ctx, next_qn, mname, &hop_impls); if (hm) { - rust_emit_resolved_call(ctx, hm->qualified_name, - "lsp_deref_dispatch", CBM_RUST_CONF_PROMOTED); + rust_emit_resolved_call(ctx, hm->qualified_name, "lsp_deref_dispatch", + CBM_RUST_CONF_PROMOTED); return; } cur = next; @@ -3323,17 +3517,18 @@ static void rust_resolve_call_expression(RustLSPContext* ctx, TSNode node) { * the bound trait. */ { /* Just the local tail name. */ - const char* short_qn = type_qn; - const char* dot = strrchr(type_qn, '.'); - if (dot) short_qn = dot + 1; - const char* bound = rust_lookup_type_param_bound(ctx, short_qn); + const char *short_qn = type_qn; + const char *dot = strrchr(type_qn, '.'); + if (dot) + short_qn = dot + 1; + const char *bound = rust_lookup_type_param_bound(ctx, short_qn); if (bound) { int bimpls = 0; - const CBMRegisteredFunc* bm = rust_resolve_trait_method( - ctx, bound, mname, &bimpls); + const CBMRegisteredFunc *bm = + rust_resolve_trait_method(ctx, bound, mname, &bimpls); if (bm) { - rust_emit_resolved_call(ctx, bm->qualified_name, - "lsp_bound_dispatch", CBM_RUST_CONF_TRAIT_AMB); + rust_emit_resolved_call(ctx, bm->qualified_name, "lsp_bound_dispatch", + CBM_RUST_CONF_TRAIT_AMB); return; } } @@ -3341,23 +3536,20 @@ static void rust_resolve_call_expression(RustLSPContext* ctx, TSNode node) { /* Prelude trait method best-effort. */ if (is_prelude_trait_method(mname)) { - rust_emit_resolved_call(ctx, - cbm_arena_sprintf(ctx->arena, "%s.%s", type_qn, mname), - "lsp_prelude_trait", CBM_RUST_CONF_TRAIT_AMB); + rust_emit_resolved_call(ctx, cbm_arena_sprintf(ctx->arena, "%s.%s", type_qn, mname), + "lsp_prelude_trait", CBM_RUST_CONF_TRAIT_AMB); return; } - rust_emit_unresolved_call(ctx, - cbm_arena_sprintf(ctx->arena, "%s.%s", type_qn, mname), - "method_not_found"); + rust_emit_unresolved_call(ctx, cbm_arena_sprintf(ctx->arena, "%s.%s", type_qn, mname), + "method_not_found"); return; } /* Receiver type is unknown — record best-effort with the textual * receiver path so downstream can still see what we tried. */ - char* recv_text = rust_node_text(ctx, value); - rust_emit_unresolved_call(ctx, - cbm_arena_sprintf(ctx->arena, "%s.%s", - recv_text ? recv_text : "?", mname), + char *recv_text = rust_node_text(ctx, value); + rust_emit_unresolved_call( + ctx, cbm_arena_sprintf(ctx->arena, "%s.%s", recv_text ? recv_text : "?", mname), "unknown_receiver_type"); return; } @@ -3368,15 +3560,18 @@ static void rust_resolve_call_expression(RustLSPContext* ctx, TSNode node) { TSNode actual_func = func_node; if (strcmp(fk, "generic_function") == 0) { TSNode inner = ts_node_child_by_field_name(actual_func, "function", 8); - if (!ts_node_is_null(inner)) actual_func = inner; + if (!ts_node_is_null(inner)) + actual_func = inner; } - char* path = rust_node_text(ctx, actual_func); - if (!path) return; + char *path = rust_node_text(ctx, actual_func); + if (!path) + return; /* Strip ALL turbofish (`Vec::::new` → `Vec::new`). */ rust_strip_turbofish(path); - const char* qn = rust_resolve_path_expr(ctx, path); - if (!qn) return; + const char *qn = rust_resolve_path_expr(ctx, path); + if (!qn) + return; /* Try registered free function first. Also try module-prefixed * fallback so `Logger::new` (which resolves to "Logger.new") @@ -3386,7 +3581,7 @@ static void rust_resolve_call_expression(RustLSPContext* ctx, TSNode node) { return; } if (ctx->module_qn && strstr(qn, ".") == NULL) { - const char* full = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, qn); + const char *full = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, qn); if (cbm_registry_lookup_func(ctx->registry, full)) { rust_emit_resolved_call(ctx, full, "lsp_direct", CBM_RUST_CONF_DIRECT); return; @@ -3394,36 +3589,37 @@ static void rust_resolve_call_expression(RustLSPContext* ctx, TSNode node) { } /* UFCS form: T::method or trait_qn::method. */ - const char* dot = strrchr(qn, '.'); + const char *dot = strrchr(qn, '.'); if (dot) { - char* head = cbm_arena_strndup(ctx->arena, qn, (size_t)(dot - qn)); - const char* short_name = dot + 1; - const CBMRegisteredFunc* m = cbm_registry_lookup_method_aliased(ctx->registry, - head, short_name); + char *head = cbm_arena_strndup(ctx->arena, qn, (size_t)(dot - qn)); + const char *short_name = dot + 1; + const CBMRegisteredFunc *m = + cbm_registry_lookup_method_aliased(ctx->registry, head, short_name); if (!m && ctx->module_qn) { /* Fall back to module-qualified head: `Logger.new` → * `.Logger.new`. */ - const char* full_head = cbm_arena_sprintf(ctx->arena, "%s.%s", - ctx->module_qn, head); + const char *full_head = + cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, head); m = cbm_registry_lookup_method_aliased(ctx->registry, full_head, short_name); } if (m) { rust_emit_resolved_call(ctx, m->qualified_name, - strcmp(short_name, "new") == 0 ? "lsp_constructor" : "lsp_ufcs", - CBM_RUST_CONF_UFCS); + strcmp(short_name, "new") == 0 ? "lsp_constructor" + : "lsp_ufcs", + CBM_RUST_CONF_UFCS); return; } /* Trait method through single-impl dispatch. */ int impls = 0; - const CBMRegisteredFunc* tm = rust_resolve_trait_method(ctx, head, short_name, &impls); + const CBMRegisteredFunc *tm = rust_resolve_trait_method(ctx, head, short_name, &impls); if (!tm && ctx->module_qn) { - const char* full_head = cbm_arena_sprintf(ctx->arena, "%s.%s", - ctx->module_qn, head); + const char *full_head = + cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, head); tm = rust_resolve_trait_method(ctx, full_head, short_name, &impls); } if (tm) { - rust_emit_resolved_call(ctx, tm->qualified_name, - impls == 1 ? "lsp_trait_ufcs" : "lsp_trait_ufcs_amb", + rust_emit_resolved_call( + ctx, tm->qualified_name, impls == 1 ? "lsp_trait_ufcs" : "lsp_trait_ufcs_amb", impls == 1 ? CBM_RUST_CONF_TRAIT_SOLE : CBM_RUST_CONF_TRAIT_AMB); return; } @@ -3435,7 +3631,7 @@ static void rust_resolve_call_expression(RustLSPContext* ctx, TSNode node) { * foo; use foo::bar; bar()` a chance to resolve when the * intermediate module wasn't tracked through an explicit * use-map entry. */ - const char* tail = strrchr(path, ':'); + const char *tail = strrchr(path, ':'); if (tail && tail > path && tail[-1] == ':') { tail += 1; } else { @@ -3445,24 +3641,29 @@ static void rust_resolve_call_expression(RustLSPContext* ctx, TSNode node) { /* Crate prefix is the first dotted segment of module_qn after * the project name, but for simplicity we just match on * "starts with first dot-segment". */ - const char* first_dot = strchr(ctx->module_qn, '.'); - size_t crate_len = first_dot ? (size_t)(first_dot - ctx->module_qn) - : strlen(ctx->module_qn); - const CBMRegisteredFunc* unique = NULL; + const char *first_dot = strchr(ctx->module_qn, '.'); + size_t crate_len = + first_dot ? (size_t)(first_dot - ctx->module_qn) : strlen(ctx->module_qn); + const CBMRegisteredFunc *unique = NULL; int matches = 0; for (int i = 0; i < ctx->registry->func_count && matches < 2; i++) { - const CBMRegisteredFunc* f = &ctx->registry->funcs[i]; - if (!f->short_name || !f->qualified_name) continue; - if (f->receiver_type) continue; /* free functions only */ - if (strcmp(f->short_name, tail) != 0) continue; + const CBMRegisteredFunc *f = &ctx->registry->funcs[i]; + if (!f->short_name || !f->qualified_name) + continue; + if (f->receiver_type) + continue; /* free functions only */ + if (strcmp(f->short_name, tail) != 0) + continue; /* Crate-scoped: QN must start with the same prefix. */ - if (strncmp(f->qualified_name, ctx->module_qn, crate_len) != 0) continue; + if (strncmp(f->qualified_name, ctx->module_qn, crate_len) != 0) + continue; matches++; - if (matches == 1) unique = f; + if (matches == 1) + unique = f; } if (matches == 1 && unique) { - rust_emit_resolved_call(ctx, unique->qualified_name, - "lsp_short_name_unique", CBM_RUST_CONF_PROMOTED); + rust_emit_resolved_call(ctx, unique->qualified_name, "lsp_short_name_unique", + CBM_RUST_CONF_PROMOTED); return; } } @@ -3475,14 +3676,16 @@ static void rust_resolve_call_expression(RustLSPContext* ctx, TSNode node) { /* Walk every node in a function body, recording calls and refining scope * for control-flow constructs that bind variables. */ -#define CBM_RUST_EVAL_STEP_CAP 200000 /* per-file budget */ -static void rust_resolve_calls_in_node(RustLSPContext* ctx, TSNode node) { - if (ts_node_is_null(node)) return; +#define CBM_RUST_EVAL_STEP_CAP 200000 /* per-file budget */ +static void rust_resolve_calls_in_node(RustLSPContext *ctx, TSNode node) { + if (ts_node_is_null(node)) + return; /* Pathological-input guard: bail out once we've spent too many * eval steps on this file. Prevents hangs on adversarial input. */ - if (ctx->eval_step_count > CBM_RUST_EVAL_STEP_CAP) return; + if (ctx->eval_step_count > CBM_RUST_EVAL_STEP_CAP) + return; ctx->eval_step_count++; - const char* kind = ts_node_type(node); + const char *kind = ts_node_type(node); /* Bind variables introduced by this statement. */ rust_process_statement(ctx, node); @@ -3500,30 +3703,31 @@ static void rust_resolve_calls_in_node(RustLSPContext* ctx, TSNode node) { TSNode val = ts_node_child_by_field_name(fn, "value", 5); TSNode fld = ts_node_child_by_field_name(fn, "field", 5); if (!ts_node_is_null(val) && !ts_node_is_null(fld)) { - char* mname = rust_node_text(ctx, fld); - static const char* item_methods[] = { - "map", "filter", "for_each", "find", "position", "any", "all", - "take_while", "skip_while", "filter_map", "inspect", - "max_by", "min_by", "max_by_key", "min_by_key", "sort_by_key", - NULL - }; + char *mname = rust_node_text(ctx, fld); + static const char *item_methods[] = { + "map", "filter", "for_each", "find", "position", "any", + "all", "take_while", "skip_while", "filter_map", "inspect", "max_by", + "min_by", "max_by_key", "min_by_key", "sort_by_key", NULL}; bool is_item_method = false; if (mname) { - for (const char** mm = item_methods; *mm; mm++) { - if (strcmp(mname, *mm) == 0) { is_item_method = true; break; } + for (const char **mm = item_methods; *mm; mm++) { + if (strcmp(mname, *mm) == 0) { + is_item_method = true; + break; + } } } if (is_item_method) { - const CBMType* recv = rust_eval_expr_type(ctx, val); - const CBMType* base = recv; - while (base && (base->kind == CBM_TYPE_REFERENCE || - base->kind == CBM_TYPE_POINTER)) { - base = (base->kind == CBM_TYPE_REFERENCE) - ? base->data.reference.elem : base->data.pointer.elem; + const CBMType *recv = rust_eval_expr_type(ctx, val); + const CBMType *base = recv; + while (base && + (base->kind == CBM_TYPE_REFERENCE || base->kind == CBM_TYPE_POINTER)) { + base = (base->kind == CBM_TYPE_REFERENCE) ? base->data.reference.elem + : base->data.pointer.elem; } if (base && base->kind == CBM_TYPE_TEMPLATE && base->data.template_type.arg_count > 0) { - const char* tn = base->data.template_type.template_name; + const char *tn = base->data.template_type.template_name; if (tn && (strstr(tn, "Iterator") || strstr(tn, "Vec") || strstr(tn, "VecDeque") || strstr(tn, "Option") || strstr(tn, "Result") || strstr(tn, "HashSet") || @@ -3552,9 +3756,10 @@ static void rust_resolve_calls_in_node(RustLSPContext* ctx, TSNode node) { if (!ts_node_is_null(left)) { for (uint32_t i = 0; i < ts_node_child_count(node); i++) { TSNode c = ts_node_child(node, i); - if (ts_node_is_named(c)) continue; - char* op = rust_node_text(ctx, c); - const char* method = rust_binop_trait_method(op); + if (ts_node_is_named(c)) + continue; + char *op = rust_node_text(ctx, c); + const char *method = rust_binop_trait_method(op); if (method) { rust_emit_operator_call(ctx, rust_eval_expr_type(ctx, left), method); } @@ -3576,11 +3781,11 @@ static void rust_resolve_calls_in_node(RustLSPContext* ctx, TSNode node) { if (strcmp(kind, "macro_invocation") == 0) { TSNode mname_node = ts_node_child_by_field_name(node, "macro", 5); if (!ts_node_is_null(mname_node)) { - char* mname = rust_node_text(ctx, mname_node); + char *mname = rust_node_text(ctx, mname_node); if (mname) { /* For known std macros emit a synthetic call under their * canonical paths so trace tools can see the dependency. */ - const char* path = NULL; + const char *path = NULL; /* Macros whose arguments are ordinary Rust expressions — their * call sites are lost to tree-sitter's macro tokenisation, so * re-parse the args to recover calls like `format!("{}", @@ -3593,8 +3798,7 @@ static void rust_resolve_calls_in_node(RustLSPContext* ctx, TSNode node) { strcmp(mname, "assert") == 0 || strcmp(mname, "assert_eq") == 0 || strcmp(mname, "assert_ne") == 0 || strcmp(mname, "debug_assert") == 0 || strcmp(mname, "debug_assert_eq") == 0 || - strcmp(mname, "debug_assert_ne") == 0 || - strcmp(mname, "dbg") == 0; + strcmp(mname, "debug_assert_ne") == 0 || strcmp(mname, "dbg") == 0; if (expr_arg_macro) { rust_resolve_macro_arg_exprs(ctx, node); } @@ -3616,8 +3820,7 @@ static void rust_resolve_calls_in_node(RustLSPContext* ctx, TSNode node) { strcmp(mname, "include_bytes") == 0) { /* Equivalent for data inclusion. */ path = cbm_arena_sprintf(ctx->arena, "core.macros.%s", mname); - } else if (strcmp(mname, "env") == 0 || - strcmp(mname, "option_env") == 0) { + } else if (strcmp(mname, "env") == 0 || strcmp(mname, "option_env") == 0) { /* Compile-time env var read. Specifically: an * `include!(concat!(env!("OUT_DIR"), …))` pattern * indicates code generated by a build.rs that we @@ -3634,22 +3837,20 @@ static void rust_resolve_calls_in_node(RustLSPContext* ctx, TSNode node) { } } TSNode args = ts_node_child_by_field_name(node, "arguments", 9); - if (!ts_node_is_null(args)) rust_walk_macro_tokens(ctx, args); + if (!ts_node_is_null(args)) + rust_walk_macro_tokens(ctx, args); /* Don't recurse normally below — we already drilled into args. */ return; } /* Push a fresh scope for blocks and constructs introducing new bindings. */ - bool push_scope = (strcmp(kind, "block") == 0 || - strcmp(kind, "if_expression") == 0 || - strcmp(kind, "if_let_expression") == 0 || - strcmp(kind, "while_expression") == 0 || - strcmp(kind, "while_let_expression") == 0 || - strcmp(kind, "for_expression") == 0 || - strcmp(kind, "match_arm") == 0 || - strcmp(kind, "closure_expression") == 0); + bool push_scope = + (strcmp(kind, "block") == 0 || strcmp(kind, "if_expression") == 0 || + strcmp(kind, "if_let_expression") == 0 || strcmp(kind, "while_expression") == 0 || + strcmp(kind, "while_let_expression") == 0 || strcmp(kind, "for_expression") == 0 || + strcmp(kind, "match_arm") == 0 || strcmp(kind, "closure_expression") == 0); - CBMScope* saved = ctx->current_scope; + CBMScope *saved = ctx->current_scope; if (push_scope) { ctx->current_scope = cbm_scope_push(ctx->arena, ctx->current_scope); } @@ -3663,7 +3864,7 @@ static void rust_resolve_calls_in_node(RustLSPContext* ctx, TSNode node) { TSNode pat = ts_node_child_by_field_name(node, "pattern", 7); TSNode val = ts_node_child_by_field_name(node, "value", 5); if (!ts_node_is_null(pat) && !ts_node_is_null(val)) { - const CBMType* vt = rust_eval_expr_type(ctx, val); + const CBMType *vt = rust_eval_expr_type(ctx, val); rust_bind_pattern(ctx, pat, vt); } } @@ -3674,7 +3875,7 @@ static void rust_resolve_calls_in_node(RustLSPContext* ctx, TSNode node) { if (!ts_node_is_null(cond)) { uint32_t nc2 = ts_node_named_child_count(cond); /* Walk one level down for let_condition / let_chain. */ - const char* ck = ts_node_type(cond); + const char *ck = ts_node_type(cond); TSNode targets[8]; int tcount = 0; if (strcmp(ck, "let_condition") == 0) { @@ -3692,7 +3893,7 @@ static void rust_resolve_calls_in_node(RustLSPContext* ctx, TSNode node) { TSNode pat = ts_node_child_by_field_name(lc, "pattern", 7); TSNode val = ts_node_child_by_field_name(lc, "value", 5); if (!ts_node_is_null(pat) && !ts_node_is_null(val)) { - const CBMType* vt = rust_eval_expr_type(ctx, val); + const CBMType *vt = rust_eval_expr_type(ctx, val); rust_bind_pattern(ctx, pat, vt); } } @@ -3704,25 +3905,25 @@ static void rust_resolve_calls_in_node(RustLSPContext* ctx, TSNode node) { TSNode pat = ts_node_child_by_field_name(node, "pattern", 7); TSNode val = ts_node_child_by_field_name(node, "value", 5); if (!ts_node_is_null(pat) && !ts_node_is_null(val)) { - const CBMType* vt = rust_eval_expr_type(ctx, val); - const CBMType* base = vt; - while (base && base->kind == CBM_TYPE_REFERENCE) base = base->data.reference.elem; - const CBMType* elem = cbm_type_unknown(); + const CBMType *vt = rust_eval_expr_type(ctx, val); + const CBMType *base = vt; + while (base && base->kind == CBM_TYPE_REFERENCE) + base = base->data.reference.elem; + const CBMType *elem = cbm_type_unknown(); if (base && base->kind == CBM_TYPE_SLICE) { elem = base->data.slice.elem; } else if (base && base->kind == CBM_TYPE_TEMPLATE) { - const char* nm = base->data.template_type.template_name; + const char *nm = base->data.template_type.template_name; if ((strstr(nm, "Vec") || strstr(nm, "VecDeque") || strstr(nm, "Iterator") || - strstr(nm, "Range")) && base->data.template_type.arg_count > 0) { + strstr(nm, "Range")) && + base->data.template_type.arg_count > 0) { elem = base->data.template_type.template_args[0]; } if ((strstr(nm, "HashMap") || strstr(nm, "BTreeMap")) && base->data.template_type.arg_count > 1) { /* Iter over (K, V) tuples. */ - const CBMType* pair[2] = { - base->data.template_type.template_args[0], - base->data.template_type.template_args[1] - }; + const CBMType *pair[2] = {base->data.template_type.template_args[0], + base->data.template_type.template_args[1]}; elem = cbm_type_tuple(ctx->arena, pair, 2); } } @@ -3749,7 +3950,7 @@ static void rust_resolve_calls_in_node(RustLSPContext* ctx, TSNode node) { * After binding, clear the pending type so it doesn't leak into a * sibling closure. */ if (strcmp(kind, "closure_expression") == 0) { - const CBMType* hint = ctx->pending_closure_param_type; + const CBMType *hint = ctx->pending_closure_param_type; ctx->pending_closure_param_type = NULL; TSNode params = ts_node_child_by_field_name(node, "parameters", 10); if (!ts_node_is_null(params)) { @@ -3760,11 +3961,12 @@ static void rust_resolve_calls_in_node(RustLSPContext* ctx, TSNode node) { * annotation if present; otherwise treat the whole node * as the pattern. */ TSNode pat = p; - const CBMType* bound = (i == 0 && hint) ? hint : cbm_type_unknown(); + const CBMType *bound = (i == 0 && hint) ? hint : cbm_type_unknown(); if (strcmp(ts_node_type(p), "parameter") == 0) { TSNode tn = ts_node_child_by_field_name(p, "type", 4); TSNode pn = ts_node_child_by_field_name(p, "pattern", 7); - if (!ts_node_is_null(pn)) pat = pn; + if (!ts_node_is_null(pn)) + pat = pn; if (!ts_node_is_null(tn)) { bound = rust_parse_type_node(ctx, tn); } @@ -3789,16 +3991,18 @@ static void rust_resolve_calls_in_node(RustLSPContext* ctx, TSNode node) { } /* Process a single function: bind parameters / `self`, then walk body. */ -static void rust_process_function(RustLSPContext* ctx, TSNode func_node, const char* parent_qn) { +static void rust_process_function(RustLSPContext *ctx, TSNode func_node, const char *parent_qn) { TSNode name_node = ts_node_child_by_field_name(func_node, "name", 4); - if (ts_node_is_null(name_node)) return; - char* name = rust_node_text(ctx, name_node); - if (!name || !name[0]) return; + if (ts_node_is_null(name_node)) + return; + char *name = rust_node_text(ctx, name_node); + if (!name || !name[0]) + return; - const char* prefix = parent_qn ? parent_qn : ctx->module_qn; + const char *prefix = parent_qn ? parent_qn : ctx->module_qn; ctx->enclosing_func_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", prefix, name); - CBMScope* saved = ctx->current_scope; + CBMScope *saved = ctx->current_scope; ctx->current_scope = cbm_scope_push(ctx->arena, ctx->current_scope); /* Chalk-lite: capture the active function's type-parameter bounds @@ -3807,13 +4011,15 @@ static void rust_process_function(RustLSPContext* ctx, TSNode func_node, const c int saved_bound_count = ctx->type_param_bound_count; TSNode tp_list = ts_node_child_by_field_name(func_node, "type_parameters", 15); if (!ts_node_is_null(tp_list)) { - char* tp_text = rust_node_text(ctx, tp_list); - if (tp_text) rust_collect_bounds_from_text(ctx, tp_text); + char *tp_text = rust_node_text(ctx, tp_list); + if (tp_text) + rust_collect_bounds_from_text(ctx, tp_text); } TSNode where_clause = ts_node_child_by_field_name(func_node, "where_clause", 12); if (!ts_node_is_null(where_clause)) { - char* wt = rust_node_text(ctx, where_clause); - if (wt) rust_collect_bounds_from_text(ctx, wt); + char *wt = rust_node_text(ctx, where_clause); + if (wt) + rust_collect_bounds_from_text(ctx, wt); } /* Bind self for impl/trait methods. */ @@ -3822,12 +4028,12 @@ static void rust_process_function(RustLSPContext* ctx, TSNode func_node, const c uint32_t pc = ts_node_named_child_count(params); for (uint32_t i = 0; i < pc; i++) { TSNode p = ts_node_named_child(params, i); - const char* pk = ts_node_type(p); + const char *pk = ts_node_type(p); if (strcmp(pk, "self_parameter") == 0) { if (ctx->self_type_qn) { /* Determine if &self / &mut self / self by reference. */ - char* text = rust_node_text(ctx, p); - const CBMType* self_t = cbm_type_named(ctx->arena, ctx->self_type_qn); + char *text = rust_node_text(ctx, p); + const CBMType *self_t = cbm_type_named(ctx->arena, ctx->self_type_qn); if (text && strchr(text, '&')) { self_t = cbm_type_reference(ctx->arena, self_t); } @@ -3838,9 +4044,10 @@ static void rust_process_function(RustLSPContext* ctx, TSNode func_node, const c if (strcmp(pk, "parameter") == 0) { TSNode pat = ts_node_child_by_field_name(p, "pattern", 7); TSNode tn = ts_node_child_by_field_name(p, "type", 4); - const CBMType* pt = ts_node_is_null(tn) ? cbm_type_unknown() - : rust_parse_type_node(ctx, tn); - if (!ts_node_is_null(pat)) rust_bind_pattern(ctx, pat, pt); + const CBMType *pt = + ts_node_is_null(tn) ? cbm_type_unknown() : rust_parse_type_node(ctx, tn); + if (!ts_node_is_null(pat)) + rust_bind_pattern(ctx, pat, pt); } } } @@ -3861,18 +4068,19 @@ static void rust_process_function(RustLSPContext* ctx, TSNode func_node, const c * with the appropriate `self_type_qn` (and `self_trait_qn` for trait impls). */ /* Chalk-lite: record a `T: Trait` bound in the per-function bound * environment. The arrays grow by 8 to keep allocs cheap. */ -static void rust_record_type_param_bound(RustLSPContext* ctx, - const char* param_name, const char* trait_qn) { - if (!ctx || !param_name || !trait_qn) return; +static void rust_record_type_param_bound(RustLSPContext *ctx, const char *param_name, + const char *trait_qn) { + if (!ctx || !param_name || !trait_qn) + return; /* Grow by 8s. */ if (ctx->type_param_bound_count % 8 == 0) { int new_cap = ctx->type_param_bound_count + 8; - void* narr = cbm_arena_alloc(ctx->arena, - new_cap * sizeof(*ctx->type_param_bounds)); - if (!narr) return; + void *narr = cbm_arena_alloc(ctx->arena, new_cap * sizeof(*ctx->type_param_bounds)); + if (!narr) + return; if (ctx->type_param_bounds && ctx->type_param_bound_count > 0) { memcpy(narr, ctx->type_param_bounds, - ctx->type_param_bound_count * sizeof(*ctx->type_param_bounds)); + ctx->type_param_bound_count * sizeof(*ctx->type_param_bounds)); } ctx->type_param_bounds = narr; } @@ -3885,9 +4093,9 @@ static void rust_record_type_param_bound(RustLSPContext* ctx, /* Look up the first trait bound for a given type-param name. Returns * NULL if `name` has no bound recorded. */ -static const char* rust_lookup_type_param_bound(RustLSPContext* ctx, - const char* name) { - if (!ctx || !name) return NULL; +static const char *rust_lookup_type_param_bound(RustLSPContext *ctx, const char *name) { + if (!ctx || !name) + return NULL; for (int i = 0; i < ctx->type_param_bound_count; i++) { if (strcmp(ctx->type_param_bounds[i].param_name, name) == 0) { return ctx->type_param_bounds[i].trait_qn; @@ -3905,8 +4113,9 @@ static const char* rust_lookup_type_param_bound(RustLSPContext* ctx, * `where T: Clone, U: Iterator` * * Multiple bounds are split on `+` (top-level), entries on `,`. */ -static void rust_collect_bounds_from_text(RustLSPContext* ctx, const char* text) { - if (!ctx || !text) return; +static void rust_collect_bounds_from_text(RustLSPContext *ctx, const char *text) { + if (!ctx || !text) + return; /* Walk text, find segments separated by `,` at top depth. For each * segment, split on `:` to get (param, bound-list); split bounds on `+` * at top depth. Resolve each bound through the path resolver to its QN. */ @@ -3914,45 +4123,45 @@ static void rust_collect_bounds_from_text(RustLSPContext* ctx, const char* text) int from = 0; while (from < len) { /* Skip whitespace + leading 'where'/punct. */ - while (from < len && (text[from] == ' ' || text[from] == '\n' || - text[from] == '<' || text[from] == ',' || - text[from] == '>' || text[from] == 'w')) { - if (text[from] == 'w' && from + 5 < len && - strncmp(text + from, "where", 5) == 0) { + while (from < len && (text[from] == ' ' || text[from] == '\n' || text[from] == '<' || + text[from] == ',' || text[from] == '>' || text[from] == 'w')) { + if (text[from] == 'w' && from + 5 < len && strncmp(text + from, "where", 5) == 0) { from += 5; } else { from++; } } - if (from >= len) break; + if (from >= len) + break; /* Param name. */ int name_start = from; if (text[from] == '\'') { /* Lifetime — skip. */ from++; - while (from < len && (isalnum((unsigned char)text[from]) || - text[from] == '_')) from++; + while (from < len && (isalnum((unsigned char)text[from]) || text[from] == '_')) + from++; continue; } - while (from < len && (isalnum((unsigned char)text[from]) || - text[from] == '_')) from++; + while (from < len && (isalnum((unsigned char)text[from]) || text[from] == '_')) + from++; int name_end = from; if (name_end == name_start) { from++; continue; } - char* pname = cbm_arena_strndup(ctx->arena, - text + name_start, (size_t)(name_end - name_start)); + char *pname = + cbm_arena_strndup(ctx->arena, text + name_start, (size_t)(name_end - name_start)); /* Look for `:`. */ - while (from < len && (text[from] == ' ' || text[from] == '\t')) from++; + while (from < len && (text[from] == ' ' || text[from] == '\t')) + from++; if (from >= len || text[from] != ':') { /* No bound; skip to next entry. */ - while (from < len && text[from] != ',' && text[from] != '>' && - text[from] != '\n') from++; + while (from < len && text[from] != ',' && text[from] != '>' && text[from] != '\n') + from++; continue; } - from++; /* consume `:` */ + from++; /* consume `:` */ /* Bound list: split on `+` at depth 0, terminated by `,` / `>` / * end of where clause. */ @@ -3960,32 +4169,33 @@ static void rust_collect_bounds_from_text(RustLSPContext* ctx, const char* text) int bound_start = from; while (from < len) { char c = text[from]; - if (c == '<' || c == '(' || c == '[') depth++; + if (c == '<' || c == '(' || c == '[') + depth++; else if (c == '>' || c == ')' || c == ']') { - if (depth == 0) break; + if (depth == 0) + break; depth--; } else if (depth == 0 && (c == '+' || c == ',' || c == '\n')) { /* End of one bound. */ int b_end = from; /* Trim trailing whitespace. */ - while (b_end > bound_start && - (text[b_end - 1] == ' ' || text[b_end - 1] == '\t')) { + while (b_end > bound_start && (text[b_end - 1] == ' ' || text[b_end - 1] == '\t')) { b_end--; } /* Trim leading whitespace. */ int b_start = bound_start; - while (b_start < b_end && - (text[b_start] == ' ' || text[b_start] == '\t')) { + while (b_start < b_end && (text[b_start] == ' ' || text[b_start] == '\t')) { b_start++; } if (b_end > b_start) { - char* btext = cbm_arena_strndup(ctx->arena, - text + b_start, (size_t)(b_end - b_start)); + char *btext = + cbm_arena_strndup(ctx->arena, text + b_start, (size_t)(b_end - b_start)); /* Strip any `<…>` associated-type suffix for the * trait QN lookup — we keep the trait name only. */ - char* langle = strchr(btext, '<'); - if (langle) *langle = '\0'; - const char* qn = rust_resolve_path_expr(ctx, btext); + char *langle = strchr(btext, '<'); + if (langle) + *langle = '\0'; + const char *qn = rust_resolve_path_expr(ctx, btext); if (qn) { rust_record_type_param_bound(ctx, pname, qn); } @@ -4001,42 +4211,48 @@ static void rust_collect_bounds_from_text(RustLSPContext* ctx, const char* text) from++; } /* Advance past entry terminator. */ - if (from < len && (text[from] == ',' || text[from] == '\n')) from++; + if (from < len && (text[from] == ',' || text[from] == '\n')) + from++; } } /* Helper: does `name` appear in the impl's `` type * parameter list? Used to detect blanket impls (impl X for T). */ -static bool rust_impl_has_type_param(RustLSPContext* ctx, TSNode impl_node, - const char* name) { - if (!name) return false; +static bool rust_impl_has_type_param(RustLSPContext *ctx, TSNode impl_node, const char *name) { + if (!name) + return false; TSNode tp = ts_node_child_by_field_name(impl_node, "type_parameters", 15); - if (ts_node_is_null(tp)) return false; + if (ts_node_is_null(tp)) + return false; uint32_t nc = ts_node_named_child_count(tp); for (uint32_t i = 0; i < nc; i++) { TSNode c = ts_node_named_child(tp, i); - const char* ck = ts_node_type(c); - if (strcmp(ck, "type_identifier") == 0 || - strcmp(ck, "constrained_type_parameter") == 0) { - char* nm = rust_node_text(ctx, c); - if (!nm) continue; + const char *ck = ts_node_type(c); + if (strcmp(ck, "type_identifier") == 0 || strcmp(ck, "constrained_type_parameter") == 0) { + char *nm = rust_node_text(ctx, c); + if (!nm) + continue; /* For constrained_type_parameter, the name is the * first identifier-y child. */ if (strcmp(ck, "constrained_type_parameter") == 0) { TSNode lhs = ts_node_child_by_field_name(c, "left", 4); - if (!ts_node_is_null(lhs)) nm = rust_node_text(ctx, lhs); + if (!ts_node_is_null(lhs)) + nm = rust_node_text(ctx, lhs); } - if (nm && strcmp(nm, name) == 0) return true; + if (nm && strcmp(nm, name) == 0) + return true; } } return false; } -static void rust_process_impl(RustLSPContext* ctx, TSNode impl_node) { +static void rust_process_impl(RustLSPContext *ctx, TSNode impl_node) { TSNode type_node = ts_node_child_by_field_name(impl_node, "type", 4); - if (ts_node_is_null(type_node)) return; - char* type_text = rust_node_text(ctx, type_node); - if (!type_text) return; + if (ts_node_is_null(type_node)) + return; + char *type_text = rust_node_text(ctx, type_node); + if (!type_text) + return; /* Detect blanket impl: `impl ForeignTrait for T { ... }` * where type_text is a name that appears in the impl's type @@ -4044,26 +4260,29 @@ static void rust_process_impl(RustLSPContext* ctx, TSNode impl_node) { * any T satisfying the bound. We register the methods on the trait * QN itself so dispatch through T: Trait finds them. */ TSNode trait_node = ts_node_child_by_field_name(impl_node, "trait", 5); - bool is_blanket = !ts_node_is_null(trait_node) && - rust_impl_has_type_param(ctx, impl_node, type_text); - const char* effective_recv = NULL; + bool is_blanket = + !ts_node_is_null(trait_node) && rust_impl_has_type_param(ctx, impl_node, type_text); + const char *effective_recv = NULL; if (is_blanket) { - char* tt = rust_node_text(ctx, trait_node); - if (tt) effective_recv = rust_resolve_path_expr(ctx, tt); + char *tt = rust_node_text(ctx, trait_node); + if (tt) + effective_recv = rust_resolve_path_expr(ctx, tt); } else { effective_recv = rust_resolve_path_expr(ctx, type_text); } - if (!effective_recv) return; + if (!effective_recv) + return; - const char* saved_self = ctx->self_type_qn; - const char* saved_trait = ctx->self_trait_qn; + const char *saved_self = ctx->self_type_qn; + const char *saved_trait = ctx->self_trait_qn; ctx->self_type_qn = effective_recv; ctx->self_trait_qn = NULL; if (!ts_node_is_null(trait_node) && !is_blanket) { - char* tt = rust_node_text(ctx, trait_node); - if (tt) ctx->self_trait_qn = rust_resolve_path_expr(ctx, tt); + char *tt = rust_node_text(ctx, trait_node); + if (tt) + ctx->self_trait_qn = rust_resolve_path_expr(ctx, tt); } TSNode body = ts_node_child_by_field_name(impl_node, "body", 4); @@ -4071,8 +4290,9 @@ static void rust_process_impl(RustLSPContext* ctx, TSNode impl_node) { uint32_t nc = ts_node_child_count(body); for (uint32_t i = 0; i < nc; i++) { TSNode c = ts_node_child(body, i); - if (ts_node_is_null(c) || !ts_node_is_named(c)) continue; - const char* ck = ts_node_type(c); + if (ts_node_is_null(c) || !ts_node_is_named(c)) + continue; + const char *ck = ts_node_type(c); if (strcmp(ck, "function_item") == 0) { rust_process_function(ctx, c, effective_recv); } @@ -4083,8 +4303,9 @@ static void rust_process_impl(RustLSPContext* ctx, TSNode impl_node) { ctx->self_trait_qn = saved_trait; } -void rust_lsp_process_file(RustLSPContext* ctx, TSNode root) { - if (ts_node_is_null(root)) return; +void rust_lsp_process_file(RustLSPContext *ctx, TSNode root) { + if (ts_node_is_null(root)) + return; /* Pass 0: collect macro_rules! definitions before walking bodies so * macro_invocation handlers can expand user-defined macros. */ @@ -4101,24 +4322,29 @@ void rust_lsp_process_file(RustLSPContext* ctx, TSNode root) { uint32_t rnc = ts_node_child_count(root); for (uint32_t i = 0; i < rnc; i++) { TSNode c = ts_node_child(root, i); - if (ts_node_is_null(c)) continue; - if (strcmp(ts_node_type(c), "mod_item") != 0) continue; + if (ts_node_is_null(c)) + continue; + if (strcmp(ts_node_type(c), "mod_item") != 0) + continue; /* Inline mod has a `body` field; bare decl does not. */ TSNode body = ts_node_child_by_field_name(c, "body", 4); - if (!ts_node_is_null(body)) continue; /* inline */ + if (!ts_node_is_null(body)) + continue; /* inline */ TSNode mname = ts_node_child_by_field_name(c, "name", 4); - if (ts_node_is_null(mname)) continue; - char* name = rust_node_text(ctx, mname); - if (!name) continue; + if (ts_node_is_null(mname)) + continue; + char *name = rust_node_text(ctx, mname); + if (!name) + continue; /* Surface as a synthetic CALLS edge from "" to * the sibling module so the cross-file pass picks it up * via short-name fallback. We attribute it to the file's * synthetic module-scope caller. */ - const char* save_caller = ctx->enclosing_func_qn; + const char *save_caller = ctx->enclosing_func_qn; ctx->enclosing_func_qn = ctx->module_qn; rust_emit_resolved_call(ctx, - cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, name), - "lsp_mod_decl", 0.70f); + cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, name), + "lsp_mod_decl", 0.70f); ctx->enclosing_func_qn = save_caller; } } @@ -4127,8 +4353,9 @@ void rust_lsp_process_file(RustLSPContext* ctx, TSNode root) { uint32_t nc = ts_node_child_count(root); for (uint32_t i = 0; i < nc; i++) { TSNode c = ts_node_child(root, i); - if (ts_node_is_null(c)) continue; - const char* ck = ts_node_type(c); + if (ts_node_is_null(c)) + continue; + const char *ck = ts_node_type(c); if (strcmp(ck, "const_item") == 0 || strcmp(ck, "static_item") == 0) { rust_process_statement(ctx, c); } @@ -4137,8 +4364,9 @@ void rust_lsp_process_file(RustLSPContext* ctx, TSNode root) { /* Pass 2: walk every top-level item. */ for (uint32_t i = 0; i < nc; i++) { TSNode c = ts_node_child(root, i); - if (ts_node_is_null(c)) continue; - const char* ck = ts_node_type(c); + if (ts_node_is_null(c)) + continue; + const char *ck = ts_node_type(c); if (strcmp(ck, "function_item") == 0) { rust_process_function(ctx, c, NULL); } else if (strcmp(ck, "impl_item") == 0) { @@ -4150,8 +4378,9 @@ void rust_lsp_process_file(RustLSPContext* ctx, TSNode root) { uint32_t mnc = ts_node_child_count(body); for (uint32_t j = 0; j < mnc; j++) { TSNode mc = ts_node_child(body, j); - if (ts_node_is_null(mc)) continue; - const char* mck = ts_node_type(mc); + if (ts_node_is_null(mc)) + continue; + const char *mck = ts_node_type(mc); if (strcmp(mck, "function_item") == 0) { rust_process_function(ctx, mc, NULL); } else if (strcmp(mck, "impl_item") == 0) { @@ -4174,33 +4403,40 @@ void rust_lsp_process_file(RustLSPContext* ctx, TSNode root) { * use_list | use_as_clause | use_wildcard. * * We expand each of these into one or more (alias, full-path) entries. */ -static void rust_collect_uses(RustLSPContext* ctx, TSNode root) { +static void rust_collect_uses(RustLSPContext *ctx, TSNode root) { /* Recursive walker. */ - typedef struct stack_t { TSNode node; struct stack_t* prev; } stack_t; - stack_t* top = (stack_t*)cbm_arena_alloc(ctx->arena, sizeof(stack_t)); + typedef struct stack_t { + TSNode node; + struct stack_t *prev; + } stack_t; + stack_t *top = (stack_t *)cbm_arena_alloc(ctx->arena, sizeof(stack_t)); top->node = root; top->prev = NULL; while (top) { TSNode n = top->node; top = top->prev; - if (ts_node_is_null(n)) continue; - const char* k = ts_node_type(n); + if (ts_node_is_null(n)) + continue; + const char *k = ts_node_type(n); if (strcmp(k, "use_declaration") == 0) { - char* full = rust_node_text(ctx, n); + char *full = rust_node_text(ctx, n); if (full) { - if (strncmp(full, "use ", 4) == 0) full += 4; + if (strncmp(full, "use ", 4) == 0) + full += 4; size_t len = strlen(full); - if (len > 0 && full[len - 1] == ';') full[len - 1] = '\0'; + if (len > 0 && full[len - 1] == ';') + full[len - 1] = '\0'; /* Trim leading whitespace. */ - while (*full == ' ') full++; + while (*full == ' ') + full++; /* Detect glob. */ size_t flen = strlen(full); if (flen >= 3 && strcmp(full + flen - 3, "::*") == 0) { - char* mod = cbm_arena_strndup(ctx->arena, full, flen - 3); + char *mod = cbm_arena_strndup(ctx->arena, full, flen - 3); rust_lsp_add_glob(ctx, convert_path_to_qn(ctx->arena, mod)); } else if (flen >= 1 && full[flen - 1] == '}') { /* Brace list: prefix::{a, b as c, d}. */ - char* lbr = strchr(full, '{'); + char *lbr = strchr(full, '{'); if (lbr) { size_t prefix_len = (size_t)(lbr - full); /* Strip trailing "::" from prefix. */ @@ -4208,49 +4444,55 @@ static void rust_collect_uses(RustLSPContext* ctx, TSNode root) { full[prefix_len - 2] == ':') { prefix_len -= 2; } - char* prefix = cbm_arena_strndup(ctx->arena, full, prefix_len); - char* body = cbm_arena_strdup(ctx->arena, lbr + 1); + char *prefix = cbm_arena_strndup(ctx->arena, full, prefix_len); + char *body = cbm_arena_strdup(ctx->arena, lbr + 1); size_t blen = strlen(body); - if (blen > 0 && body[blen - 1] == '}') body[blen - 1] = '\0'; - char* save = NULL; - char* tok = strtok_r(body, ",", &save); + if (blen > 0 && body[blen - 1] == '}') + body[blen - 1] = '\0'; + char *save = NULL; + char *tok = strtok_r(body, ",", &save); while (tok) { - while (*tok == ' ') tok++; - char* eb = tok + strlen(tok) - 1; - while (eb > tok && *eb == ' ') *eb-- = '\0'; + while (*tok == ' ') + tok++; + char *eb = tok + strlen(tok) - 1; + while (eb > tok && *eb == ' ') + *eb-- = '\0'; if (*tok == '\0') { tok = strtok_r(NULL, ",", &save); continue; } /* `Read` or `Read as R`. */ - char* asp = strstr(tok, " as "); - char* alias = NULL; - char* path_part = tok; + char *asp = strstr(tok, " as "); + char *alias = NULL; + char *path_part = tok; if (asp) { *asp = '\0'; alias = asp + 4; - while (*alias == ' ') alias++; + while (*alias == ' ') + alias++; } else { - alias = (char*)path_last_segment(tok); + alias = (char *)path_last_segment(tok); } - char* full_path = (strcmp(tok, "self") == 0) - ? cbm_arena_strdup(ctx->arena, prefix) - : cbm_arena_sprintf(ctx->arena, "%s::%s", prefix, path_part); + char *full_path = + (strcmp(tok, "self") == 0) + ? cbm_arena_strdup(ctx->arena, prefix) + : cbm_arena_sprintf(ctx->arena, "%s::%s", prefix, path_part); rust_lsp_add_use(ctx, alias, full_path); tok = strtok_r(NULL, ",", &save); } } } else { /* Single path; possibly followed by ` as X`. */ - char* asp = strstr(full, " as "); - char* alias = NULL; - char* path_part = full; + char *asp = strstr(full, " as "); + char *alias = NULL; + char *path_part = full; if (asp) { *asp = '\0'; alias = asp + 4; - while (*alias == ' ') alias++; + while (*alias == ' ') + alias++; } else { - alias = (char*)path_last_segment(full); + alias = (char *)path_last_segment(full); } rust_lsp_add_use(ctx, alias, path_part); } @@ -4262,8 +4504,9 @@ static void rust_collect_uses(RustLSPContext* ctx, TSNode root) { uint32_t nc = ts_node_child_count(n); for (uint32_t i = 0; i < nc; i++) { TSNode c = ts_node_child(n, i); - if (ts_node_is_null(c)) continue; - stack_t* nx = (stack_t*)cbm_arena_alloc(ctx->arena, sizeof(stack_t)); + if (ts_node_is_null(c)) + continue; + stack_t *nx = (stack_t *)cbm_arena_alloc(ctx->arena, sizeof(stack_t)); nx->node = c; nx->prev = top; top = nx; @@ -4274,17 +4517,18 @@ static void rust_collect_uses(RustLSPContext* ctx, TSNode root) { /* Build the registry from the per-file `result->defs`, `result->impl_traits`, * and a Rust prelude seed. */ -static void rust_build_registry_from_defs(CBMArena* arena, CBMTypeRegistry* reg, - CBMFileResult* result, const char* module_qn, - TSNode root, const char* source) { +static void rust_build_registry_from_defs(CBMArena *arena, CBMTypeRegistry *reg, + CBMFileResult *result, const char *module_qn, TSNode root, + const char *source) { cbm_registry_init(reg, arena); cbm_rust_stdlib_register(reg, arena); /* Phase A: register every Class/Type/Trait/Function/Method definition. */ for (int i = 0; i < result->defs.count; i++) { - CBMDefinition* d = &result->defs.items[i]; - if (!d->qualified_name || !d->name) continue; + CBMDefinition *d = &result->defs.items[i]; + if (!d->qualified_name || !d->name) + continue; if (d->label && (strcmp(d->label, "Class") == 0 || strcmp(d->label, "Type") == 0 || strcmp(d->label, "Interface") == 0 || strcmp(d->label, "Trait") == 0)) { @@ -4292,8 +4536,8 @@ static void rust_build_registry_from_defs(CBMArena* arena, CBMTypeRegistry* reg, memset(&rt, 0, sizeof(rt)); rt.qualified_name = d->qualified_name; rt.short_name = d->name; - rt.is_interface = (strcmp(d->label, "Interface") == 0 || - strcmp(d->label, "Trait") == 0); + rt.is_interface = + (strcmp(d->label, "Interface") == 0 || strcmp(d->label, "Trait") == 0); cbm_registry_add_type(reg, rt); } @@ -4305,32 +4549,36 @@ static void rust_build_registry_from_defs(CBMArena* arena, CBMTypeRegistry* reg, rf.min_params = -1; /* Build FUNC sig from return_types / param_types. */ - const CBMType** ret_types = NULL; + const CBMType **ret_types = NULL; if (d->return_types) { int count = 0; - while (d->return_types[count]) count++; + while (d->return_types[count]) + count++; if (count > 0) { - ret_types = (const CBMType**)cbm_arena_alloc(arena, - (count + 1) * sizeof(const CBMType*)); + ret_types = (const CBMType **)cbm_arena_alloc( + arena, (count + 1) * sizeof(const CBMType *)); for (int j = 0; j < count; j++) { - ret_types[j] = rust_parse_return_type_text(arena, d->return_types[j], module_qn); + ret_types[j] = + rust_parse_return_type_text(arena, d->return_types[j], module_qn); } ret_types[count] = NULL; } } else if (d->return_type && d->return_type[0]) { - ret_types = (const CBMType**)cbm_arena_alloc(arena, 2 * sizeof(const CBMType*)); + ret_types = (const CBMType **)cbm_arena_alloc(arena, 2 * sizeof(const CBMType *)); ret_types[0] = rust_parse_return_type_text(arena, d->return_type, module_qn); ret_types[1] = NULL; } - const CBMType** param_types = NULL; + const CBMType **param_types = NULL; if (d->param_types) { int count = 0; - while (d->param_types[count]) count++; + while (d->param_types[count]) + count++; if (count > 0) { - param_types = (const CBMType**)cbm_arena_alloc(arena, - (count + 1) * sizeof(const CBMType*)); + param_types = (const CBMType **)cbm_arena_alloc( + arena, (count + 1) * sizeof(const CBMType *)); for (int j = 0; j < count; j++) { - param_types[j] = rust_parse_return_type_text(arena, d->param_types[j], module_qn); + param_types[j] = + rust_parse_return_type_text(arena, d->param_types[j], module_qn); } param_types[count] = NULL; } @@ -4343,7 +4591,7 @@ static void rust_build_registry_from_defs(CBMArena* arena, CBMTypeRegistry* reg, CBMRegisteredType auto_t; memset(&auto_t, 0, sizeof(auto_t)); auto_t.qualified_name = rf.receiver_type; - const char* dot = strrchr(rf.receiver_type, '.'); + const char *dot = strrchr(rf.receiver_type, '.'); auto_t.short_name = dot ? cbm_arena_strdup(arena, dot + 1) : rf.receiver_type; cbm_registry_add_type(reg, auto_t); } @@ -4359,30 +4607,35 @@ static void rust_build_registry_from_defs(CBMArena* arena, CBMTypeRegistry* reg, uint32_t nc = ts_node_child_count(root); for (uint32_t i = 0; i < nc; i++) { TSNode top = ts_node_child(root, i); - if (ts_node_is_null(top)) continue; - const char* tk = ts_node_type(top); + if (ts_node_is_null(top)) + continue; + const char *tk = ts_node_type(top); if (strcmp(tk, "struct_item") == 0) { TSNode name_node = ts_node_child_by_field_name(top, "name", 4); TSNode body = ts_node_child_by_field_name(top, "body", 4); - if (ts_node_is_null(name_node) || ts_node_is_null(body)) continue; - char* tn = cbm_node_text(arena, name_node, source); - if (!tn || !tn[0]) continue; - const char* type_qn = cbm_arena_sprintf(arena, "%s.%s", module_qn, tn); + if (ts_node_is_null(name_node) || ts_node_is_null(body)) + continue; + char *tn = cbm_node_text(arena, name_node, source); + if (!tn || !tn[0]) + continue; + const char *type_qn = cbm_arena_sprintf(arena, "%s.%s", module_qn, tn); /* Iterate field_declaration_list / ordered_field_declaration_list. */ if (strcmp(ts_node_type(body), "field_declaration_list") == 0) { uint32_t fc = ts_node_named_child_count(body); - const char* fld_names[64]; - const CBMType* fld_types[64]; + const char *fld_names[64]; + const CBMType *fld_types[64]; int fld_count = 0; for (uint32_t j = 0; j < fc && fld_count < 63; j++) { TSNode fd = ts_node_named_child(body, j); - if (strcmp(ts_node_type(fd), "field_declaration") != 0) continue; + if (strcmp(ts_node_type(fd), "field_declaration") != 0) + continue; TSNode fn = ts_node_child_by_field_name(fd, "name", 4); TSNode ft = ts_node_child_by_field_name(fd, "type", 4); - char* fname = cbm_node_text(arena, fn, source); - if (!fname) continue; + char *fname = cbm_node_text(arena, fn, source); + if (!fname) + continue; /* Build a temporary context for parsing types. */ RustLSPContext tmp; memset(&tmp, 0, sizeof(tmp)); @@ -4391,7 +4644,7 @@ static void rust_build_registry_from_defs(CBMArena* arena, CBMTypeRegistry* reg, tmp.source_len = (int)strlen(source); tmp.registry = reg; tmp.module_qn = module_qn; - const CBMType* ft_t = rust_parse_type_node(&tmp, ft); + const CBMType *ft_t = rust_parse_type_node(&tmp, ft); fld_names[fld_count] = fname; fld_types[fld_count] = ft_t; fld_count++; @@ -4400,10 +4653,10 @@ static void rust_build_registry_from_defs(CBMArena* arena, CBMTypeRegistry* reg, for (int ti = 0; ti < reg->type_count; ti++) { if (reg->types[ti].qualified_name && strcmp(reg->types[ti].qualified_name, type_qn) == 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*)); + 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]; @@ -4422,34 +4675,41 @@ static void rust_build_registry_from_defs(CBMArena* arena, CBMTypeRegistry* reg, if (strcmp(tk, "trait_item") == 0) { TSNode name_node = ts_node_child_by_field_name(top, "name", 4); TSNode body = ts_node_child_by_field_name(top, "body", 4); - if (ts_node_is_null(name_node)) continue; - char* tn = cbm_node_text(arena, name_node, source); - if (!tn || !tn[0]) continue; - const char* trait_qn = cbm_arena_sprintf(arena, "%s.%s", module_qn, tn); + if (ts_node_is_null(name_node)) + continue; + char *tn = cbm_node_text(arena, name_node, source); + if (!tn || !tn[0]) + continue; + const char *trait_qn = cbm_arena_sprintf(arena, "%s.%s", module_qn, tn); /* Mark as interface and collect method names. */ for (int ti = 0; ti < reg->type_count; ti++) { - if (!reg->types[ti].qualified_name) continue; + if (!reg->types[ti].qualified_name) + continue; if (strcmp(reg->types[ti].qualified_name, trait_qn) == 0) { reg->types[ti].is_interface = true; if (!ts_node_is_null(body)) { - const char* methods[64]; + const char *methods[64]; int mc = 0; uint32_t bc = ts_node_named_child_count(body); for (uint32_t j = 0; j < bc && mc < 63; j++) { TSNode item = ts_node_named_child(body, j); - const char* ik = ts_node_type(item); + const char *ik = ts_node_type(item); if (strcmp(ik, "function_item") != 0 && - strcmp(ik, "function_signature_item") != 0) continue; + strcmp(ik, "function_signature_item") != 0) + continue; TSNode mn = ts_node_child_by_field_name(item, "name", 4); - if (ts_node_is_null(mn)) continue; - char* mname = cbm_node_text(arena, mn, source); - if (mname) methods[mc++] = mname; + if (ts_node_is_null(mn)) + continue; + char *mname = cbm_node_text(arena, mn, source); + if (mname) + methods[mc++] = mname; } if (mc > 0) { - const char** arr = (const char**)cbm_arena_alloc(arena, - (mc + 1) * sizeof(const char*)); - for (int mi = 0; mi < mc; mi++) arr[mi] = methods[mi]; + const char **arr = (const char **)cbm_arena_alloc( + arena, (mc + 1) * sizeof(const char *)); + for (int mi = 0; mi < mc; mi++) + arr[mi] = methods[mi]; arr[mc] = NULL; reg->types[ti].method_names = arr; } @@ -4478,22 +4738,27 @@ static void rust_build_registry_from_defs(CBMArena* arena, CBMTypeRegistry* reg, uint32_t rnc = ts_node_child_count(root); for (uint32_t i = 0; i < rnc; i++) { TSNode top = ts_node_child(root, i); - if (ts_node_is_null(top) || - strcmp(ts_node_type(top), "function_item") != 0) continue; + if (ts_node_is_null(top) || strcmp(ts_node_type(top), "function_item") != 0) + continue; TSNode mn = ts_node_child_by_field_name(top, "name", 4); TSNode rtn = ts_node_child_by_field_name(top, "return_type", 11); - if (ts_node_is_null(mn) || ts_node_is_null(rtn)) continue; - char* fname = cbm_node_text(arena, mn, source); - if (!fname) continue; - const CBMType* ret = rust_parse_type_node(&tmp, rtn); - const char* fn_qn = cbm_arena_sprintf(arena, "%s.%s", module_qn, fname); + if (ts_node_is_null(mn) || ts_node_is_null(rtn)) + continue; + char *fname = cbm_node_text(arena, mn, source); + if (!fname) + continue; + const CBMType *ret = rust_parse_type_node(&tmp, rtn); + const char *fn_qn = cbm_arena_sprintf(arena, "%s.%s", module_qn, fname); for (int k = 0; k < reg->func_count; k++) { - CBMRegisteredFunc* rf = ®->funcs[k]; - if (!rf->qualified_name) continue; - if (rf->receiver_type) continue; /* free fns only */ - if (strcmp(rf->qualified_name, fn_qn) != 0) continue; - const CBMType** ret_arr = (const CBMType**)cbm_arena_alloc(arena, - 2 * sizeof(const CBMType*)); + CBMRegisteredFunc *rf = ®->funcs[k]; + if (!rf->qualified_name) + continue; + if (rf->receiver_type) + continue; /* free fns only */ + if (strcmp(rf->qualified_name, fn_qn) != 0) + continue; + const CBMType **ret_arr = + (const CBMType **)cbm_arena_alloc(arena, 2 * sizeof(const CBMType *)); ret_arr[0] = ret; ret_arr[1] = NULL; rf->signature = cbm_type_func(arena, NULL, NULL, ret_arr); @@ -4521,124 +4786,129 @@ static void rust_build_registry_from_defs(CBMArena* arena, CBMTypeRegistry* reg, { /* Curated derive → (trait QN, [methods with sig sketch]) table. */ struct DeriveMethod { - const char* short_name; - const char* return_type; /* QN or NULL for unknown */ - bool is_static; /* no `self` (e.g. `default`, `parse`) */ + const char *short_name; + const char *return_type; /* QN or NULL for unknown */ + bool is_static; /* no `self` (e.g. `default`, `parse`) */ }; struct DeriveImpl { - const char* derive_name; - const char* trait_qn; - struct DeriveMethod methods[4]; /* NULL-terminated by empty short_name */ + const char *derive_name; + const char *trait_qn; + struct DeriveMethod methods[4]; /* NULL-terminated by empty short_name */ }; static const struct DeriveImpl derives[] = { - {"Clone", "core.clone.Clone", - {{"clone", NULL, false}, {NULL, NULL, false}}}, - {"Copy", "core.marker.Copy", - {{NULL, NULL, false}}}, /* marker — no methods */ - {"Debug", "core.fmt.Debug", - {{"fmt", NULL, false}, {NULL, NULL, false}}}, - {"Display", "core.fmt.Display", - {{"fmt", NULL, false}, {NULL, NULL, false}}}, - {"Default", "core.default.Default", - {{"default", NULL, true}, {NULL, NULL, false}}}, - {"PartialEq", "core.cmp.PartialEq", + {"Clone", "core.clone.Clone", {{"clone", NULL, false}, {NULL, NULL, false}}}, + {"Copy", "core.marker.Copy", {{NULL, NULL, false}}}, /* marker — no methods */ + {"Debug", "core.fmt.Debug", {{"fmt", NULL, false}, {NULL, NULL, false}}}, + {"Display", "core.fmt.Display", {{"fmt", NULL, false}, {NULL, NULL, false}}}, + {"Default", "core.default.Default", {{"default", NULL, true}, {NULL, NULL, false}}}, + {"PartialEq", + "core.cmp.PartialEq", {{"eq", "bool", false}, {"ne", "bool", false}, {NULL, NULL, false}}}, - {"Eq", "core.cmp.Eq", - {{NULL, NULL, false}}}, /* marker only */ - {"PartialOrd", "core.cmp.PartialOrd", - {{"partial_cmp", NULL, false}, {"lt", "bool", false}, - {"le", "bool", false}, {NULL, NULL, false}}}, - {"Ord", "core.cmp.Ord", - {{"cmp", NULL, false}, {NULL, NULL, false}}}, - {"Hash", "core.hash.Hash", - {{"hash", "()", false}, {NULL, NULL, false}}}, - {"Send", "core.marker.Send", - {{NULL, NULL, false}}}, - {"Sync", "core.marker.Sync", - {{NULL, NULL, false}}}, + {"Eq", "core.cmp.Eq", {{NULL, NULL, false}}}, /* marker only */ + {"PartialOrd", + "core.cmp.PartialOrd", + {{"partial_cmp", NULL, false}, + {"lt", "bool", false}, + {"le", "bool", false}, + {NULL, NULL, false}}}, + {"Ord", "core.cmp.Ord", {{"cmp", NULL, false}, {NULL, NULL, false}}}, + {"Hash", "core.hash.Hash", {{"hash", "()", false}, {NULL, NULL, false}}}, + {"Send", "core.marker.Send", {{NULL, NULL, false}}}, + {"Sync", "core.marker.Sync", {{NULL, NULL, false}}}, /* serde — extremely common. */ - {"Serialize", "serde.Serialize", - {{"serialize", NULL, false}, {NULL, NULL, false}}}, - {"Deserialize","serde.Deserialize", + {"Serialize", "serde.Serialize", {{"serialize", NULL, false}, {NULL, NULL, false}}}, + {"Deserialize", + "serde.Deserialize", {{"deserialize", NULL, true}, {NULL, NULL, false}}}, /* clap derive — synthesizes the Parser interface. */ - {"Parser", "clap.Parser", - {{"parse", NULL, true}, {"try_parse", NULL, true}, - {"parse_from", NULL, true}, {"try_parse_from", NULL, true}}}, - {"Args", "clap.Args", - {{NULL, NULL, false}}}, - {"Subcommand", "clap.Subcommand", - {{NULL, NULL, false}}}, - {"ValueEnum", "clap.ValueEnum", - {{NULL, NULL, false}}}, + {"Parser", + "clap.Parser", + {{"parse", NULL, true}, + {"try_parse", NULL, true}, + {"parse_from", NULL, true}, + {"try_parse_from", NULL, true}}}, + {"Args", "clap.Args", {{NULL, NULL, false}}}, + {"Subcommand", "clap.Subcommand", {{NULL, NULL, false}}}, + {"ValueEnum", "clap.ValueEnum", {{NULL, NULL, false}}}, /* thiserror — adds the Error impl. */ - {"Error", "core.error.Error", - {{NULL, NULL, false}}}, + {"Error", "core.error.Error", {{NULL, NULL, false}}}, }; - const int derive_count = - (int)(sizeof(derives) / sizeof(derives[0])); + const int derive_count = (int)(sizeof(derives) / sizeof(derives[0])); for (int i = 0; i < result->defs.count; i++) { - CBMDefinition* d = &result->defs.items[i]; - if (!d->qualified_name || !d->name) continue; - if (!d->label || (strcmp(d->label, "Class") != 0 && - strcmp(d->label, "Type") != 0)) continue; - if (!d->decorators) continue; + CBMDefinition *d = &result->defs.items[i]; + if (!d->qualified_name || !d->name) + continue; + if (!d->label || (strcmp(d->label, "Class") != 0 && strcmp(d->label, "Type") != 0)) + continue; + if (!d->decorators) + continue; /* Scan decorator strings for `#[derive(...)]`. */ for (int di = 0; d->decorators[di]; di++) { - const char* dec = d->decorators[di]; - const char* p = strstr(dec, "derive"); - if (!p) continue; - const char* lparen = strchr(p, '('); - if (!lparen) continue; - const char* rparen = strchr(lparen, ')'); - if (!rparen) continue; + const char *dec = d->decorators[di]; + const char *p = strstr(dec, "derive"); + if (!p) + continue; + const char *lparen = strchr(p, '('); + if (!lparen) + continue; + const char *rparen = strchr(lparen, ')'); + if (!rparen) + continue; /* Now walk between the parens, splitting on comma. */ - const char* q = lparen + 1; + const char *q = lparen + 1; while (q < rparen) { - while (q < rparen && (*q == ' ' || *q == ',')) q++; + while (q < rparen && (*q == ' ' || *q == ',')) + q++; /* Find the end of the identifier (may be qualified * like `serde::Serialize`). We grab the trailing * segment as the derive name. */ - const char* tok_start = q; - while (q < rparen && *q != ',' && *q != ' ') q++; - if (q == tok_start) break; + const char *tok_start = q; + while (q < rparen && *q != ',' && *q != ' ') + q++; + if (q == tok_start) + break; /* Trailing-segment after the last `::`. */ - const char* short_start = tok_start; - for (const char* r = tok_start; r < q - 1; r++) { - if (r[0] == ':' && r[1] == ':') short_start = r + 2; + const char *short_start = tok_start; + for (const char *r = tok_start; r < q - 1; r++) { + if (r[0] == ':' && r[1] == ':') + short_start = r + 2; } size_t name_len = (size_t)(q - short_start); - if (name_len == 0 || name_len > 64) continue; + if (name_len == 0 || name_len > 64) + continue; /* Look up in curated table. */ for (int di2 = 0; di2 < derive_count; di2++) { - const struct DeriveImpl* di_entry = &derives[di2]; + const struct DeriveImpl *di_entry = &derives[di2]; size_t entry_len = strlen(di_entry->derive_name); - if (entry_len != name_len) continue; - if (strncmp(di_entry->derive_name, short_start, name_len) != 0) continue; + if (entry_len != name_len) + continue; + if (strncmp(di_entry->derive_name, short_start, name_len) != 0) + continue; /* Found a matching curated derive. Register the * trait QN as an embedded_type on the receiver * AND synthesize the method entries. */ - CBMRegisteredType* rt = NULL; + CBMRegisteredType *rt = NULL; for (int ti = 0; ti < reg->type_count; ti++) { if (reg->types[ti].qualified_name && - strcmp(reg->types[ti].qualified_name, - d->qualified_name) == 0) { + strcmp(reg->types[ti].qualified_name, d->qualified_name) == 0) { rt = ®->types[ti]; break; } } - if (!rt) break; + if (!rt) + break; /* Append trait QN to embedded_types. */ int existing = 0; if (rt->embedded_types) { - while (rt->embedded_types[existing]) existing++; + while (rt->embedded_types[existing]) + existing++; } - const char** new_arr = (const char**)cbm_arena_alloc(arena, - (existing + 2) * sizeof(const char*)); + const char **new_arr = (const char **)cbm_arena_alloc( + arena, (existing + 2) * sizeof(const char *)); for (int k = 0; k < existing; k++) { new_arr[k] = rt->embedded_types[k]; } @@ -4648,20 +4918,19 @@ static void rust_build_registry_from_defs(CBMArena* arena, CBMTypeRegistry* reg, /* Synthesize methods. Bound `mi < 4` BEFORE dereferencing * methods[mi] so we never read methods[4] (OOB). */ - for (int mi = 0; - mi < 4 && di_entry->methods[mi].short_name; mi++) { - const struct DeriveMethod* dm = &di_entry->methods[mi]; + for (int mi = 0; mi < 4 && di_entry->methods[mi].short_name; mi++) { + const struct DeriveMethod *dm = &di_entry->methods[mi]; CBMRegisteredFunc rf; memset(&rf, 0, sizeof(rf)); rf.short_name = dm->short_name; - rf.qualified_name = cbm_arena_sprintf(arena, "%s.%s", - d->qualified_name, dm->short_name); + rf.qualified_name = cbm_arena_sprintf(arena, "%s.%s", d->qualified_name, + dm->short_name); /* Static methods (default/parse) have no * receiver; method calls treat them as * static path lookups via UFCS. */ rf.receiver_type = d->qualified_name; rf.min_params = -1; - const CBMType* ret_t = cbm_type_unknown(); + const CBMType *ret_t = cbm_type_unknown(); if (dm->return_type) { if (strcmp(dm->return_type, "bool") == 0) { ret_t = cbm_type_builtin(arena, "bool"); @@ -4672,8 +4941,8 @@ static void rust_build_registry_from_defs(CBMArena* arena, CBMTypeRegistry* reg, /* `default()`, `parse()` return Self. */ ret_t = cbm_type_named(arena, d->qualified_name); } - const CBMType** ra = (const CBMType**)cbm_arena_alloc(arena, - 2 * sizeof(const CBMType*)); + const CBMType **ra = (const CBMType **)cbm_arena_alloc( + arena, 2 * sizeof(const CBMType *)); ra[0] = ret_t; ra[1] = NULL; rf.signature = cbm_type_func(arena, NULL, NULL, ra); @@ -4695,13 +4964,16 @@ static void rust_build_registry_from_defs(CBMArena* arena, CBMTypeRegistry* reg, uint32_t rnc = ts_node_child_count(root); for (uint32_t i = 0; i < rnc; i++) { TSNode top = ts_node_child(root, i); - if (ts_node_is_null(top) || strcmp(ts_node_type(top), "impl_item") != 0) continue; + if (ts_node_is_null(top) || strcmp(ts_node_type(top), "impl_item") != 0) + continue; TSNode type_node = ts_node_child_by_field_name(top, "type", 4); TSNode body = ts_node_child_by_field_name(top, "body", 4); - if (ts_node_is_null(type_node) || ts_node_is_null(body)) continue; - char* type_name = cbm_node_text(arena, type_node, source); - if (!type_name || !type_name[0]) continue; - const char* type_qn = cbm_arena_sprintf(arena, "%s.%s", module_qn, type_name); + if (ts_node_is_null(type_node) || ts_node_is_null(body)) + continue; + char *type_name = cbm_node_text(arena, type_node, source); + if (!type_name || !type_name[0]) + continue; + const char *type_qn = cbm_arena_sprintf(arena, "%s.%s", module_qn, type_name); RustLSPContext tmp; memset(&tmp, 0, sizeof(tmp)); @@ -4715,14 +4987,18 @@ static void rust_build_registry_from_defs(CBMArena* arena, CBMTypeRegistry* reg, uint32_t bnc = ts_node_child_count(body); for (uint32_t j = 0; j < bnc; j++) { TSNode item = ts_node_child(body, j); - if (ts_node_is_null(item) || !ts_node_is_named(item)) continue; - if (strcmp(ts_node_type(item), "function_item") != 0) continue; + if (ts_node_is_null(item) || !ts_node_is_named(item)) + continue; + if (strcmp(ts_node_type(item), "function_item") != 0) + continue; TSNode mn = ts_node_child_by_field_name(item, "name", 4); TSNode rtn = ts_node_child_by_field_name(item, "return_type", 11); - if (ts_node_is_null(mn) || ts_node_is_null(rtn)) continue; - char* mname = cbm_node_text(arena, mn, source); - if (!mname) continue; - const CBMType* ret = rust_parse_type_node(&tmp, rtn); + if (ts_node_is_null(mn) || ts_node_is_null(rtn)) + continue; + char *mname = cbm_node_text(arena, mn, source); + if (!mname) + continue; + const CBMType *ret = rust_parse_type_node(&tmp, rtn); /* Substitute Self -> receiver type so chains work. */ if (ret && ret->kind == CBM_TYPE_NAMED && strcmp(ret->data.named.qualified_name, "Self") == 0) { @@ -4730,12 +5006,15 @@ static void rust_build_registry_from_defs(CBMArena* arena, CBMTypeRegistry* reg, } /* Patch the registered function's signature. */ for (int k = 0; k < reg->func_count; k++) { - CBMRegisteredFunc* rf = ®->funcs[k]; - if (!rf->receiver_type || !rf->short_name) continue; - if (strcmp(rf->receiver_type, type_qn) != 0) continue; - if (strcmp(rf->short_name, mname) != 0) continue; - const CBMType** ret_arr = (const CBMType**)cbm_arena_alloc(arena, - 2 * sizeof(const CBMType*)); + CBMRegisteredFunc *rf = ®->funcs[k]; + if (!rf->receiver_type || !rf->short_name) + continue; + if (strcmp(rf->receiver_type, type_qn) != 0) + continue; + if (strcmp(rf->short_name, mname) != 0) + continue; + const CBMType **ret_arr = + (const CBMType **)cbm_arena_alloc(arena, 2 * sizeof(const CBMType *)); ret_arr[0] = ret; ret_arr[1] = NULL; rf->signature = cbm_type_func(arena, NULL, NULL, ret_arr); @@ -4748,14 +5027,15 @@ static void rust_build_registry_from_defs(CBMArena* arena, CBMTypeRegistry* reg, /* Phase C: encode `impl Trait for Type` as `embedded_types` on the * receiver type so trait dispatch can find them. */ for (int i = 0; i < result->impl_traits.count; i++) { - CBMImplTrait* it = &result->impl_traits.items[i]; - if (!it->struct_name || !it->trait_name) continue; - const char* recv_qn = cbm_arena_sprintf(arena, "%s.%s", module_qn, it->struct_name); - const char* trait_qn = strstr(it->trait_name, "::") - ? convert_path_to_qn(arena, it->trait_name) - : cbm_arena_sprintf(arena, "%s.%s", module_qn, it->trait_name); + CBMImplTrait *it = &result->impl_traits.items[i]; + if (!it->struct_name || !it->trait_name) + continue; + const char *recv_qn = cbm_arena_sprintf(arena, "%s.%s", module_qn, it->struct_name); + const char *trait_qn = strstr(it->trait_name, "::") + ? convert_path_to_qn(arena, it->trait_name) + : cbm_arena_sprintf(arena, "%s.%s", module_qn, it->trait_name); - CBMRegisteredType* rt = NULL; + CBMRegisteredType *rt = NULL; for (int ti = 0; ti < reg->type_count; ti++) { if (reg->types[ti].qualified_name && strcmp(reg->types[ti].qualified_name, recv_qn) == 0) { @@ -4767,7 +5047,7 @@ static void rust_build_registry_from_defs(CBMArena* arena, CBMTypeRegistry* reg, CBMRegisteredType auto_t; memset(&auto_t, 0, sizeof(auto_t)); auto_t.qualified_name = recv_qn; - const char* dot = strrchr(recv_qn, '.'); + const char *dot = strrchr(recv_qn, '.'); auto_t.short_name = dot ? dot + 1 : recv_qn; cbm_registry_add_type(reg, auto_t); rt = ®->types[reg->type_count - 1]; @@ -4775,22 +5055,25 @@ static void rust_build_registry_from_defs(CBMArena* arena, CBMTypeRegistry* reg, /* Append trait_qn to embedded_types. */ int existing = 0; if (rt->embedded_types) { - while (rt->embedded_types[existing]) existing++; + while (rt->embedded_types[existing]) + existing++; } - const char** new_arr = (const char**)cbm_arena_alloc(arena, - (existing + 2) * sizeof(const char*)); - for (int j = 0; j < existing; j++) new_arr[j] = rt->embedded_types[j]; + const char **new_arr = + (const char **)cbm_arena_alloc(arena, (existing + 2) * sizeof(const char *)); + for (int j = 0; j < existing; j++) + new_arr[j] = rt->embedded_types[j]; new_arr[existing] = trait_qn; new_arr[existing + 1] = NULL; rt->embedded_types = new_arr; } } -void cbm_run_rust_lsp_with_manifest(CBMArena* arena, CBMFileResult* result, - const char* source, int source_len, TSNode root, - const struct CBMCargoManifest* manifest) { - if (!arena || !result || !source) return; - const char* module_qn = result->module_qn ? result->module_qn : "rust"; +void cbm_run_rust_lsp_with_manifest(CBMArena *arena, CBMFileResult *result, const char *source, + int source_len, TSNode root, + const struct CBMCargoManifest *manifest) { + if (!arena || !result || !source) + return; + const char *module_qn = result->module_qn ? result->module_qn : "rust"; CBMTypeRegistry reg; rust_build_registry_from_defs(arena, ®, result, module_qn, root, source); @@ -4808,7 +5091,7 @@ void cbm_run_rust_lsp_with_manifest(CBMArena* arena, CBMFileResult* result, rust_collect_uses(&ctx, root); /* Bridge any extracted CBMImports the unified extractor saw. */ for (int i = 0; i < result->imports.count; i++) { - CBMImport* imp = &result->imports.items[i]; + CBMImport *imp = &result->imports.items[i]; if (imp->local_name && imp->module_path) { rust_lsp_add_use(&ctx, imp->local_name, imp->module_path); } @@ -4822,8 +5105,8 @@ void cbm_run_rust_lsp_with_manifest(CBMArena* arena, CBMFileResult* result, cbm_rust_synth_proc_macro_edges(arena, result); } -void cbm_run_rust_lsp(CBMArena* arena, CBMFileResult* result, - const char* source, int source_len, TSNode root) { +void cbm_run_rust_lsp(CBMArena *arena, CBMFileResult *result, const char *source, int source_len, + TSNode root) { cbm_run_rust_lsp_with_manifest(arena, result, source, source_len, root, NULL); } @@ -4831,29 +5114,29 @@ void cbm_run_rust_lsp(CBMArena* arena, CBMFileResult* result, * 12. Cross-file + batch * ════════════════════════════════════════════════════════════════════ */ -extern const TSLanguage* tree_sitter_rust(void); +extern const TSLanguage *tree_sitter_rust(void); -void cbm_run_rust_lsp_cross( - CBMArena* arena, - const char* source, int source_len, - const char* module_qn, - CBMRustLSPDef* 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 || !out) return; +void cbm_run_rust_lsp_cross(CBMArena *arena, const char *source, int source_len, + const char *module_qn, CBMRustLSPDef *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 || !out) + return; - TSParser* parser = NULL; - TSTree* tree = cached_tree; + TSParser *parser = NULL; + TSTree *tree = cached_tree; bool owns_tree = false; if (!tree) { parser = ts_parser_new(); - if (!parser) return; + if (!parser) + return; ts_parser_set_language(parser, tree_sitter_rust()); tree = ts_parser_parse_string(parser, NULL, source, source_len); owns_tree = true; - if (!tree) { ts_parser_delete(parser); return; } + if (!tree) { + ts_parser_delete(parser); + return; + } } TSNode root = ts_tree_root_node(tree); @@ -4863,9 +5146,10 @@ void cbm_run_rust_lsp_cross( cbm_rust_stdlib_register(®, arena); for (int i = 0; i < def_count; i++) { - CBMRustLSPDef* 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; + CBMRustLSPDef *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; if (strcmp(d->label, "Type") == 0 || strcmp(d->label, "Class") == 0 || strcmp(d->label, "Interface") == 0 || strcmp(d->label, "Trait") == 0) { @@ -4886,25 +5170,27 @@ void cbm_run_rust_lsp_cross( rf.min_params = -1; /* Build sig from return_types text. */ - const CBMType** ret_types = NULL; + const CBMType **ret_types = NULL; if (d->return_types && d->return_types[0]) { int count = 1; - for (const char* p = d->return_types; *p; p++) { - if (*p == '|') count++; + for (const char *p = d->return_types; *p; p++) { + if (*p == '|') + count++; } - ret_types = (const CBMType**)cbm_arena_alloc(arena, - (count + 1) * sizeof(const CBMType*)); + ret_types = + (const CBMType **)cbm_arena_alloc(arena, (count + 1) * sizeof(const CBMType *)); int idx = 0; - char* buf = cbm_arena_strdup(arena, d->return_types); - char* start = buf; - for (char* p = buf; ; p++) { + char *buf = cbm_arena_strdup(arena, d->return_types); + char *start = buf; + for (char *p = buf;; p++) { if (*p == '|' || *p == '\0') { char save = *p; *p = '\0'; if (start[0]) { ret_types[idx++] = rust_parse_return_type_text(arena, start, def_mod); } - if (save == '\0') break; + if (save == '\0') + break; start = p + 1; } } @@ -4918,7 +5204,7 @@ void cbm_run_rust_lsp_cross( CBMRegisteredType auto_t; memset(&auto_t, 0, sizeof(auto_t)); auto_t.qualified_name = rf.receiver_type; - const char* dot = strrchr(d->receiver_type, '.'); + const char *dot = strrchr(d->receiver_type, '.'); auto_t.short_name = dot ? cbm_arena_strdup(arena, dot + 1) : rf.receiver_type; cbm_registry_add_type(®, auto_t); } @@ -4928,7 +5214,7 @@ void cbm_run_rust_lsp_cross( /* If trait_qn set: encode embedded_type linkage on receiver. */ if (rf.receiver_type && d->trait_qn && d->trait_qn[0]) { - CBMRegisteredType* rt = NULL; + CBMRegisteredType *rt = NULL; for (int ti = 0; ti < reg.type_count; ti++) { if (reg.types[ti].qualified_name && strcmp(reg.types[ti].qualified_name, rf.receiver_type) == 0) { @@ -4938,10 +5224,13 @@ void cbm_run_rust_lsp_cross( } if (rt) { int existing = 0; - if (rt->embedded_types) while (rt->embedded_types[existing]) existing++; - const char** new_arr = (const char**)cbm_arena_alloc(arena, - (existing + 2) * sizeof(const char*)); - for (int j = 0; j < existing; j++) new_arr[j] = rt->embedded_types[j]; + if (rt->embedded_types) + while (rt->embedded_types[existing]) + existing++; + const char **new_arr = (const char **)cbm_arena_alloc( + arena, (existing + 2) * sizeof(const char *)); + for (int j = 0; j < existing; j++) + new_arr[j] = rt->embedded_types[j]; new_arr[existing] = cbm_arena_strdup(arena, d->trait_qn); new_arr[existing + 1] = NULL; rt->embedded_types = new_arr; @@ -4966,21 +5255,21 @@ void cbm_run_rust_lsp_cross( if (owns_tree) { ts_tree_delete(tree); - if (parser) ts_parser_delete(parser); + if (parser) + ts_parser_delete(parser); } } -void cbm_batch_rust_lsp_cross( - CBMArena* arena, - CBMBatchRustLSPFile* files, int file_count, - CBMResolvedCallArray* out) -{ - if (!files || file_count <= 0 || !out) return; +void cbm_batch_rust_lsp_cross(CBMArena *arena, CBMBatchRustLSPFile *files, int file_count, + CBMResolvedCallArray *out) { + if (!files || file_count <= 0 || !out) + return; for (int f = 0; f < file_count; f++) { - CBMBatchRustLSPFile* file = &files[f]; + CBMBatchRustLSPFile *file = &files[f]; memset(&out[f], 0, sizeof(CBMResolvedCallArray)); - if (!file->source || file->source_len <= 0) continue; + if (!file->source || file->source_len <= 0) + continue; CBMArena file_arena; cbm_arena_init(&file_arena); @@ -4988,27 +5277,22 @@ void cbm_batch_rust_lsp_cross( CBMResolvedCallArray file_out; memset(&file_out, 0, sizeof(file_out)); - cbm_run_rust_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); + cbm_run_rust_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); if (file_out.count > 0) { out[f].count = file_out.count; - out[f].items = (CBMResolvedCall*)cbm_arena_alloc(arena, - file_out.count * sizeof(CBMResolvedCall)); + 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]; + 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->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; + dst->reason = src->reason ? cbm_arena_strdup(arena, src->reason) : NULL; } } diff --git a/internal/cbm/lsp/rust_lsp.h b/internal/cbm/lsp/rust_lsp.h index 6227b6661..9b9439ac3 100644 --- a/internal/cbm/lsp/rust_lsp.h +++ b/internal/cbm/lsp/rust_lsp.h @@ -38,49 +38,49 @@ struct CBMCargoManifest; * shared override resolver (`pipeline/lsp_resolve.h`) only admits entries * scoring >= CBM_LSP_CONFIDENCE_FLOOR (0.6). Numbers here mirror the Go * LSP so the call-edge mix from a polyglot project stays comparable. */ -#define CBM_RUST_CONF_DIRECT 0.95f /* path::to::func or alias hit */ -#define CBM_RUST_CONF_METHOD 0.95f /* inherent method dispatch */ -#define CBM_RUST_CONF_TRAIT_SOLE 0.92f /* trait method, single impl */ -#define CBM_RUST_CONF_TRAIT_AMB 0.85f /* trait method, many impls */ -#define CBM_RUST_CONF_UFCS 0.93f /* T::method() / Self::new() */ -#define CBM_RUST_CONF_PROMOTED 0.90f /* through Deref / blanket impl */ -#define CBM_RUST_CONF_MACRO_KNOWN 0.85f /* known std macro mapped to fn */ -#define CBM_RUST_CONF_OPERATOR 0.88f /* a+b → T::add (operator trait) */ +#define CBM_RUST_CONF_DIRECT 0.95f /* path::to::func or alias hit */ +#define CBM_RUST_CONF_METHOD 0.95f /* inherent method dispatch */ +#define CBM_RUST_CONF_TRAIT_SOLE 0.92f /* trait method, single impl */ +#define CBM_RUST_CONF_TRAIT_AMB 0.85f /* trait method, many impls */ +#define CBM_RUST_CONF_UFCS 0.93f /* T::method() / Self::new() */ +#define CBM_RUST_CONF_PROMOTED 0.90f /* through Deref / blanket impl */ +#define CBM_RUST_CONF_MACRO_KNOWN 0.85f /* known std macro mapped to fn */ +#define CBM_RUST_CONF_OPERATOR 0.88f /* a+b → T::add (operator trait) */ /* Rust-flavoured LSP context: one per file, lifetime tied to a single * `cbm_extract_file()` invocation (or the cross-file caller's arena). */ typedef struct { - CBMArena* arena; - const char* source; + CBMArena *arena; + const char *source; int source_len; - const CBMTypeRegistry* registry; - CBMScope* current_scope; + const CBMTypeRegistry *registry; + CBMScope *current_scope; /* `use` map: parallel arrays mapping a local-name (the last segment, or * the `as` alias) to its full module path (`std::collections::HashMap`, * `crate::foo::Bar`). Glob imports go in `glob_module_qns` instead. */ - const char** use_local_names; - const char** use_module_paths; + const char **use_local_names; + const char **use_module_paths; int use_count; - const char** glob_module_qns; + const char **glob_module_qns; int glob_count; /* Module-qualified name for this file (e.g. "..foo"). */ - const char* module_qn; + const char *module_qn; /* Enclosing function context. `enclosing_func_qn` is the QN we attach * to every emitted CBMResolvedCall as `caller_qn`. */ - const char* enclosing_func_qn; + const char *enclosing_func_qn; /* `Self` resolution: when inside `impl T { ... }` or * `impl Trait for T { ... }`, `self_type_qn` is `T`'s QN. NULL outside * an impl. `self_trait_qn` is the trait QN (only set for trait impls) * — used when emitting trait method calls so we can prefer concrete * implementations over the trait method when only one impl exists. */ - const char* self_type_qn; - const char* self_trait_qn; + const char *self_type_qn; + const char *self_trait_qn; /* Closure parameter inference: when the call resolver descends into a * call's argument list and the callee is a method like @@ -88,7 +88,7 @@ typedef struct { * of the closure's first param here. The closure_expression handler * consumes it (and clears it) when binding params, so a chain like * `vec.iter().map(|x| x.method())` resolves `x.method()` correctly. */ - const CBMType* pending_closure_param_type; + const CBMType *pending_closure_param_type; /* User-defined `macro_rules!` definitions collected during the * pre-walk phase. Each rule stores its pattern + transcriber text; @@ -96,7 +96,7 @@ typedef struct { * against each rule and then substitutes/re-walks the expansion. * * The arrays are doubling-grown out of `arena`. */ - struct RustMacroRule** macro_rules_arr; + struct RustMacroRule **macro_rules_arr; int macro_rules_count; /* Recursion guard for macro expansion. Real macro_rules! can be @@ -119,7 +119,7 @@ typedef struct { * falling through to module-prefix fallback. NULL when no manifest * is available — the resolver still works, just without workspace * awareness. Owned by the caller; the RustLSPContext only borrows. */ - const struct CBMCargoManifest* cargo_manifest; + const struct CBMCargoManifest *cargo_manifest; /* Chalk-lite trait-bound environment for the *currently-active* * function or impl. Populated at function/impl entry from the @@ -132,19 +132,19 @@ typedef struct { * Arrays are arena-allocated and reset on every function entry. */ struct { - const char* param_name; /* "T", "U", "Item" */ - const char* trait_qn; /* "core.clone.Clone" */ + const char *param_name; /* "T", "U", "Item" */ + const char *trait_qn; /* "core.clone.Clone" */ } *type_param_bounds; int type_param_bound_count; struct { - const char* alias_name; /* "U" */ - const char* aliased_to; /* "T.Item" (representational) */ + const char *alias_name; /* "U" */ + const char *aliased_to; /* "T.Item" (representational) */ } *type_param_aliases; int type_param_alias_count; /* Output: resolved (and unresolved-with-reason) calls accumulate here. */ - CBMResolvedCallArray* resolved_calls; + CBMResolvedCallArray *resolved_calls; /* Syntactic-call list (result->calls), borrowed from the per-file * extraction result. The downstream pipeline only turns a resolved_call @@ -156,7 +156,7 @@ typedef struct { * see them. When this is non-NULL the resolver injects matching synthetic * CBMCall entries so those recovered calls become real edges. NULL in the * cross-file path (no result available). */ - CBMCallArray* syn_calls; + CBMCallArray *syn_calls; /* While >0, rust_emit_resolved_call also injects a matching synthetic * CBMCall into `syn_calls` so the recovered call becomes an edge. Set @@ -169,23 +169,24 @@ typedef struct { } RustLSPContext; /* Initialise an empty context for processing one file. */ -void rust_lsp_init(RustLSPContext* ctx, CBMArena* arena, const char* source, int source_len, - const CBMTypeRegistry* registry, const char* module_qn, CBMResolvedCallArray* out); +void rust_lsp_init(RustLSPContext *ctx, CBMArena *arena, const char *source, int source_len, + const CBMTypeRegistry *registry, const char *module_qn, + CBMResolvedCallArray *out); /* Register a `use` alias. `local_name` may be the last segment of the path * (`HashMap` for `use std::collections::HashMap`) or an `as` alias. The * full `module_path` is stored verbatim (e.g. `std::collections::HashMap`). * Glob imports (`use foo::*`) go through `rust_lsp_add_glob` instead. */ -void rust_lsp_add_use(RustLSPContext* ctx, const char* local_name, const char* module_path); -void rust_lsp_add_glob(RustLSPContext* ctx, const char* module_qn); +void rust_lsp_add_use(RustLSPContext *ctx, const char *local_name, const char *module_path); +void rust_lsp_add_glob(RustLSPContext *ctx, const char *module_qn); /* Process every function/method in the file, walking statements and * evaluating expression types as we go. */ -void rust_lsp_process_file(RustLSPContext* ctx, TSNode root); +void rust_lsp_process_file(RustLSPContext *ctx, TSNode root); /* Evaluate the static type of a Rust expression node. Returns * `cbm_type_unknown()` for anything we cannot type. */ -const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node); +const CBMType *rust_eval_expr_type(RustLSPContext *ctx, TSNode node); /* Bidirectional variant: evaluate an expression with an `expected` * hint available. The hint is used to: @@ -199,27 +200,27 @@ const CBMType* rust_eval_expr_type(RustLSPContext* ctx, TSNode node); * `expected` may be NULL (no hint). If the synthesised type matches * the expected, returns it; if a hint resolves an ambiguity that the * synthesis path cannot, returns the hint-substituted form. */ -const CBMType* rust_eval_expr_typed(RustLSPContext* ctx, TSNode node, const CBMType* expected); +const CBMType *rust_eval_expr_typed(RustLSPContext *ctx, TSNode node, const CBMType *expected); /* Convert a `*type*` AST node (type_identifier, scoped_type_identifier, * reference_type, primitive_type, …) into a CBMType. */ -const CBMType* rust_parse_type_node(RustLSPContext* ctx, TSNode node); +const CBMType *rust_parse_type_node(RustLSPContext *ctx, TSNode node); /* Bind the bindings introduced by a let/for/match-pattern statement into * the current scope. */ -void rust_process_statement(RustLSPContext* ctx, TSNode node); +void rust_process_statement(RustLSPContext *ctx, TSNode node); /* Look up an inherent method or field promoted through Deref / embedded * trait blanket impls. Returns NULL if nothing matches. */ -const CBMRegisteredFunc* rust_lookup_method(RustLSPContext* ctx, - const char* type_qn, const char* member_name); +const CBMRegisteredFunc *rust_lookup_method(RustLSPContext *ctx, const char *type_qn, + const char *member_name); /* Entry point — called from `cbm_extract_file()` after the unified * extractor has filled `result->defs`, `result->imports`, and * `result->impl_traits`. Builds a per-file registry, parses the `use` * graph, and walks every function body emitting CBMResolvedCall entries. */ -void cbm_run_rust_lsp(CBMArena* arena, CBMFileResult* result, - const char* source, int source_len, TSNode root); +void cbm_run_rust_lsp(CBMArena *arena, CBMFileResult *result, const char *source, int source_len, + TSNode root); /* Synthesize call edges for curated attribute proc-macros. * @@ -232,15 +233,15 @@ void cbm_run_rust_lsp(CBMArena* arena, CBMFileResult* result, * direct call attributions. * * Called automatically from `cbm_run_rust_lsp_with_manifest`. */ -void cbm_rust_synth_proc_macro_edges(CBMArena* arena, CBMFileResult* result); +void cbm_rust_synth_proc_macro_edges(CBMArena *arena, CBMFileResult *result); /* Same as `cbm_run_rust_lsp`, but accepts an optional Cargo manifest * so the resolver can route paths whose head is a workspace member / * declared dependency. Pass NULL to fall back to the manifest-free * behaviour. */ -void cbm_run_rust_lsp_with_manifest(CBMArena* arena, CBMFileResult* result, - const char* source, int source_len, TSNode root, - const struct CBMCargoManifest* manifest); +void cbm_run_rust_lsp_with_manifest(CBMArena *arena, CBMFileResult *result, const char *source, + int source_len, TSNode root, + const struct CBMCargoManifest *manifest); /* Register a curated subset of the Rust core/alloc/std prelude into the * given registry. The seed is intentionally compact (~150 types, ~600 @@ -248,13 +249,13 @@ void cbm_run_rust_lsp_with_manifest(CBMArena* arena, CBMFileResult* result, * plus the prelude trait method names (`clone`, `to_string`, `into`, …). * Generated from `scripts/gen-rust-stdlib.py` if present, otherwise the * hand-written `rust_stdlib_data.c` module is used. */ -void cbm_rust_stdlib_register(CBMTypeRegistry* reg, CBMArena* arena); +void cbm_rust_stdlib_register(CBMTypeRegistry *reg, CBMArena *arena); /* Register seeds for commonly-used external crates (serde, tokio, * anyhow, clap, regex, log, futures, parking_lot, once_cell, chrono, * uuid, reqwest, rayon). Best-effort curated entries — RUST_LSP_FOLLOWUP * §A3. Calls into crates not in this seed remain `unresolved`. */ -void cbm_rust_crates_register(CBMTypeRegistry* reg, CBMArena* arena); +void cbm_rust_crates_register(CBMTypeRegistry *reg, CBMArena *arena); /* --- Cross-file LSP resolution --- */ @@ -263,46 +264,40 @@ void cbm_rust_crates_register(CBMTypeRegistry* reg, CBMArena* arena); * — see that header for field semantics. We declare a separate typedef * to allow Rust-specific fields without touching the Go ABI. */ typedef struct { - const char* qualified_name; - const char* short_name; - const char* label; /* "Function", "Method", "Type", "Trait" */ - const char* receiver_type; /* for methods: receiver type QN (NULL for free fns) */ - const char* def_module_qn; /* module QN where this def lives */ - const char* return_types; /* "|"-separated return type texts */ - const char* embedded_types; /* "|"-separated embedded type QNs */ - const char* field_defs; /* "|"-separated "name:type" pairs */ - const char* method_names_str;/* "|"-separated method names for traits */ - const char* trait_qn; /* impl Trait for Type → set on Method defs */ - bool is_interface; /* true for traits */ + const char *qualified_name; + const char *short_name; + const char *label; /* "Function", "Method", "Type", "Trait" */ + const char *receiver_type; /* for methods: receiver type QN (NULL for free fns) */ + const char *def_module_qn; /* module QN where this def lives */ + const char *return_types; /* "|"-separated return type texts */ + const char *embedded_types; /* "|"-separated embedded type QNs */ + const char *field_defs; /* "|"-separated "name:type" pairs */ + const char *method_names_str; /* "|"-separated method names for traits */ + const char *trait_qn; /* impl Trait for Type → set on Method defs */ + bool is_interface; /* true for traits */ } CBMRustLSPDef; /* Run cross-file resolution on a single file. */ -void cbm_run_rust_lsp_cross( - CBMArena* arena, - const char* source, int source_len, - const char* module_qn, - CBMRustLSPDef* defs, int def_count, - const char** import_names, const char** import_qns, int import_count, - TSTree* cached_tree, - CBMResolvedCallArray* out); +void cbm_run_rust_lsp_cross(CBMArena *arena, const char *source, int source_len, + const char *module_qn, CBMRustLSPDef *defs, int def_count, + const char **import_names, const char **import_qns, int import_count, + TSTree *cached_tree, CBMResolvedCallArray *out); /* Per-file input for batch cross-file Rust LSP processing. */ typedef struct { - const char* source; + const char *source; int source_len; - const char* module_qn; - TSTree* cached_tree; - CBMRustLSPDef* defs; + const char *module_qn; + TSTree *cached_tree; + CBMRustLSPDef *defs; int def_count; - const char** import_names; - const char** import_qns; + const char **import_names; + const char **import_qns; int import_count; } CBMBatchRustLSPFile; /* Process several files in one CGo call (per-file arenas, result copy). */ -void cbm_batch_rust_lsp_cross( - CBMArena* arena, - CBMBatchRustLSPFile* files, int file_count, - CBMResolvedCallArray* out); +void cbm_batch_rust_lsp_cross(CBMArena *arena, CBMBatchRustLSPFile *files, int file_count, + CBMResolvedCallArray *out); #endif /* CBM_LSP_RUST_LSP_H */ diff --git a/src/pipeline/pass_calls.c b/src/pipeline/pass_calls.c index 92fbd3f4c..b0b28371e 100644 --- a/src/pipeline/pass_calls.c +++ b/src/pipeline/pass_calls.c @@ -198,11 +198,9 @@ static void handle_route_registration(cbm_pipeline_ctx_t *ctx, const CBMCall *ca "{\"callee\":\"%s\",\"url_path\":\"%s\",\"via\":\"route_registration\"}", call->callee_name, call->first_string_arg); cbm_gbuf_insert_edge(ctx->gbuf, source_node->id, route_id, "CALLS", props); - if (getenv("CBM_DBG_ROUTE")) fprintf(stderr, "[DBG] route_reg callee=%s path=%s handler=%s\n", call->callee_name, call->first_string_arg, call->second_arg_name?call->second_arg_name:"(null)"); if (call->second_arg_name != NULL && call->second_arg_name[0] != '\0') { cbm_resolution_t hres = cbm_registry_resolve(ctx->registry, call->second_arg_name, module_qn, imp_keys, imp_vals, imp_count); - if (getenv("CBM_DBG_ROUTE")) fprintf(stderr, "[DBG] handler '%s' resolved=%s\n", call->second_arg_name, hres.qualified_name?hres.qualified_name:"(null)"); if (hres.qualified_name != NULL && hres.qualified_name[0] != '\0') { const cbm_gbuf_node_t *handler = cbm_gbuf_find_by_qn(ctx->gbuf, hres.qualified_name); if (handler != NULL) { @@ -283,8 +281,6 @@ static void emit_classified_edge(cbm_pipeline_ctx_t *ctx, const CBMCall *call, const cbm_resolution_t *res, const char *module_qn, const char **imp_keys, const char **imp_vals, int imp_count) { cbm_svc_kind_t svc = cbm_service_pattern_match(res->qualified_name); - if (getenv("CBM_DBG_SVC") && call->callee_name && (strstr(call->callee_name,"Route")||strstr(call->callee_name,"client")||strstr(call->callee_name,"Client")||strstr(call->callee_name,"RestClient"))) - fprintf(stderr, "[DBGSVC] callee=%s resolved=%s svc=%d strarg=%s\n", call->callee_name, res->qualified_name?res->qualified_name:"(null)", svc, call->first_string_arg?call->first_string_arg:"(null)"); if (svc == CBM_SVC_ROUTE_REG && call->first_string_arg && call->first_string_arg[0] == '/') { handle_route_registration(ctx, call, source, module_qn, imp_keys, imp_vals, imp_count); return; diff --git a/src/pipeline/pass_k8s.c b/src/pipeline/pass_k8s.c index 5b6b9cf96..f75c8dd03 100644 --- a/src/pipeline/pass_k8s.c +++ b/src/pipeline/pass_k8s.c @@ -275,11 +275,9 @@ static void k8s_scan_labels(const char *source, k8s_record_t *rec) { char val[K8S_LABEL_LEN]; if (k8s_split_kv(trimmed, key, sizeof(key), val, sizeof(val))) { /* Build the current dotted path for context decisions. */ - bool under_selector = - (depth >= 1 && strcmp(stack[depth - 1].key, "selector") == 0); + bool under_selector = (depth >= 1 && strcmp(stack[depth - 1].key, "selector") == 0); bool under_labels = (depth >= 1 && strcmp(stack[depth - 1].key, "labels") == 0); - bool under_metadata = - (depth >= 1 && strcmp(stack[depth - 1].key, "metadata") == 0); + bool under_metadata = (depth >= 1 && strcmp(stack[depth - 1].key, "metadata") == 0); if (val[0] == '\0') { /* Block-opening key: push onto the path stack. */ @@ -586,9 +584,9 @@ static int parse_requirements_deps(cbm_pipeline_ctx_t *ctx, const cbm_gbuf_node_ if (t[0] && t[0] != '#' && t[0] != '-' && strstr(t, "://") == NULL) { char name[CBM_SZ_256]; size_t n = 0; - while (t[n] && t[n] != '=' && t[n] != '<' && t[n] != '>' && t[n] != '!' && t[n] != '~' && - t[n] != '[' && t[n] != ';' && t[n] != ' ' && t[n] != '\t' && t[n] != '\r' && - n + 1 < sizeof(name)) { + while (t[n] && t[n] != '=' && t[n] != '<' && t[n] != '>' && t[n] != '!' && + t[n] != '~' && t[n] != '[' && t[n] != ';' && t[n] != ' ' && t[n] != '\t' && + t[n] != '\r' && n + 1 < sizeof(name)) { name[n] = t[n]; n++; } @@ -675,8 +673,7 @@ int cbm_pipeline_pass_k8s(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *files, * pass and contain no "Resource" definitions. Pass the already- * read source buffer so handle_k8s_manifest does not re-read. */ (void)cached; /* cached YAML result intentionally discarded */ - k8s_record_t *rec = - (recs.count < recs.cap) ? &recs.items[recs.count] : NULL; + k8s_record_t *rec = (recs.count < recs.cap) ? &recs.items[recs.count] : NULL; handle_k8s_manifest(ctx, path, rel, source, src_len, rec); if (rec && rec->node_id > 0) { recs.count++; diff --git a/src/pipeline/pass_lsp_cross.c b/src/pipeline/pass_lsp_cross.c index 09f4638cc..a279956d6 100644 --- a/src/pipeline/pass_lsp_cross.c +++ b/src/pipeline/pass_lsp_cross.c @@ -399,8 +399,8 @@ void cbm_pxc_run_one(CBMLanguage lang, CBMFileResult *r, const char *source, int imp_qns, imp_count, tree, &out); break; case CBM_LANG_KOTLIN: - cbm_run_kotlin_lsp_cross(&scratch, source, source_len, module_qn, defs, def_count, imp_names, - imp_qns, imp_count, tree, &out); + cbm_run_kotlin_lsp_cross(&scratch, source, source_len, module_qn, defs, def_count, + imp_names, imp_qns, imp_count, tree, &out); break; default: break; diff --git a/src/pipeline/pass_parallel.c b/src/pipeline/pass_parallel.c index fadf20374..60fc67282 100644 --- a/src/pipeline/pass_parallel.c +++ b/src/pipeline/pass_parallel.c @@ -1941,8 +1941,8 @@ static void resolve_def_decorators(resolve_ctx_t *rc, resolve_worker_state_t *ws * and the edge target IDs are remapped consistently. */ char syn_qn[CBM_SZ_512]; snprintf(syn_qn, sizeof(syn_qn), "", fn); - dn_id = cbm_gbuf_upsert_node(ws->local_edge_buf, "Decorator", fn, syn_qn, "", 0, 0, - "{}"); + dn_id = + cbm_gbuf_upsert_node(ws->local_edge_buf, "Decorator", fn, syn_qn, "", 0, 0, "{}"); } if (dn_id != 0 && node->id != dn_id) { char dp[CBM_SZ_256]; diff --git a/src/pipeline/pass_pkgmap.c b/src/pipeline/pass_pkgmap.c index c26f7451b..de301aff6 100644 --- a/src/pipeline/pass_pkgmap.c +++ b/src/pipeline/pass_pkgmap.c @@ -1275,8 +1275,9 @@ static bool import_targetable_label(const char *label) { * File/Module-node QN (extension is stripped by fqn_module). Returns a borrowed * node or NULL. Several filename conventions are tried in turn. */ static const cbm_gbuf_node_t *resolve_sibling_file(const cbm_pipeline_ctx_t *ctx, - const char *source_rel, const char *source_file_qn, - const char *module_path) { + const char *source_rel, + const char *source_file_qn, + const char *module_path) { if (!module_path || !module_path[0]) { return NULL; } diff --git a/src/pipeline/pass_semantic.c b/src/pipeline/pass_semantic.c index 3bf514de4..fe30bb34a 100644 --- a/src/pipeline/pass_semantic.c +++ b/src/pipeline/pass_semantic.c @@ -252,7 +252,8 @@ static int check_go_class_implements(cbm_pipeline_ctx_t *ctx, const cbm_gbuf_nod * DEFINES_METHOD edges from the class. */ const cbm_gbuf_edge_t **cls_dm = NULL; int cls_dm_count = 0; - cbm_gbuf_find_edges_by_source_type(ctx->gbuf, cls->id, "DEFINES_METHOD", &cls_dm, &cls_dm_count); + cbm_gbuf_find_edges_by_source_type(ctx->gbuf, cls->id, "DEFINES_METHOD", &cls_dm, + &cls_dm_count); char prefix[CBM_SZ_512]; snprintf(prefix, sizeof(prefix), "%s.", cls->qualified_name); @@ -365,8 +366,7 @@ static void resolve_decorator(cbm_pipeline_ctx_t *ctx, const cbm_gbuf_node_t *no } cbm_resolution_t res = cbm_registry_resolve(ctx->registry, func_name, module_qn, imp_keys, imp_vals, imp_count); - if ((!res.qualified_name || res.qualified_name[0] == '\0') && - !strchr(func_name, '.')) { + if ((!res.qualified_name || res.qualified_name[0] == '\0') && !strchr(func_name, '.')) { /* C# attributes are referenced by their short name (`[Log]`) but declared * with the conventional `Attribute` suffix (`class LogAttribute`). Retry * with the suffix appended so the declaration resolves. */ @@ -432,8 +432,9 @@ static void sem_process_def_edges(cbm_pipeline_ctx_t *ctx, const CBMDefinition * * (Java `implements`, C# `: IFace`, TS `implements`); a Class/ * Type/Enum base is plain INHERITS. */ const char *base_label = cbm_registry_label_of(ctx->registry, base_qn); - const char *edge_type = - (base_label && strcmp(base_label, "Interface") == 0) ? "IMPLEMENTS" : "INHERITS"; + const char *edge_type = (base_label && strcmp(base_label, "Interface") == 0) + ? "IMPLEMENTS" + : "INHERITS"; cbm_gbuf_insert_edge(ctx->gbuf, node->id, base_node->id, edge_type, "{}"); (*inherits_count)++; } diff --git a/src/pipeline/pipeline.c b/src/pipeline/pipeline.c index cdf5a3cc4..c080a2852 100644 --- a/src/pipeline/pipeline.c +++ b/src/pipeline/pipeline.c @@ -399,9 +399,8 @@ static int process_one_infra_binding(cbm_gbuf_t *gbuf, const CBMInfraBinding *ib * upsert its Route node so the binding maps even when no code-side dispatch * call created the node first (e.g. a standalone scheduler/subscription * manifest). */ - topic_route_id = - cbm_gbuf_upsert_node(gbuf, "Route", ib->source_name, topic_route_qn, rel_path, 0, 0, - ib->broker ? ib->broker : "async"); + topic_route_id = cbm_gbuf_upsert_node(gbuf, "Route", ib->source_name, topic_route_qn, + rel_path, 0, 0, ib->broker ? ib->broker : "async"); if (topic_route_id <= 0) { return 0; } diff --git a/tests/test_extraction_inheritance.c b/tests/test_extraction_inheritance.c index 063e8d763..cd4e6fec2 100644 --- a/tests/test_extraction_inheritance.c +++ b/tests/test_extraction_inheritance.c @@ -60,8 +60,7 @@ static CBMDefinition *find_def(CBMFileResult *r, const char *label, const char *name) { for (int i = 0; i < r->defs.count; i++) { CBMDefinition *d = &r->defs.items[i]; - if (d->label && strcmp(d->label, label) == 0 && - d->name && strcmp(d->name, name) == 0) + if (d->label && strcmp(d->label, label) == 0 && d->name && strcmp(d->name, name) == 0) return d; } return NULL; @@ -79,9 +78,11 @@ static CBMDefinition *find_def_any(CBMFileResult *r, const char *name) { /* Return 1 if base_classes contains `want` exactly. */ static int bases_contain(CBMDefinition *d, const char *want) { - if (!d->base_classes) return 0; + if (!d->base_classes) + return 0; for (const char **b = d->base_classes; *b; b++) { - if (strcmp(*b, want) == 0) return 1; + if (strcmp(*b, want) == 0) + return 1; } return 0; } @@ -90,18 +91,22 @@ static int bases_contain(CBMDefinition *d, const char *want) { * Used to assert that keywords like "extends" / "implements" / ":" do NOT * appear inside any captured base name. */ static int bases_contain_substr(CBMDefinition *d, const char *substr) { - if (!d->base_classes) return 0; + if (!d->base_classes) + return 0; for (const char **b = d->base_classes; *b; b++) { - if (strstr(*b, substr)) return 1; + if (strstr(*b, substr)) + return 1; } return 0; } /* Count entries in a NULL-terminated base_classes array. */ static int bases_count(CBMDefinition *d) { - if (!d->base_classes) return 0; + if (!d->base_classes) + return 0; int n = 0; - for (const char **b = d->base_classes; *b; b++) n++; + for (const char **b = d->base_classes; *b; b++) + n++; return n; } @@ -111,23 +116,26 @@ static int bases_count(CBMDefinition *d) { * The extractor uses "Class", "Interface", "Struct" — we try all three in * find_def_flex() below. */ typedef struct { - CBMLanguage lang; - const char *path; /* file path hint (sets language via extension too) */ - const char *src; /* source snippet */ - const char *class_name; /* name to look up */ - const char *expected_bases[8]; /* NULL-terminated list of real type names to find */ + CBMLanguage lang; + const char *path; /* file path hint (sets language via extension too) */ + const char *src; /* source snippet */ + const char *class_name; /* name to look up */ + const char *expected_bases[8]; /* NULL-terminated list of real type names to find */ /* bad_strings: substrings that must NOT appear inside any base_classes entry. * Used to catch keyword-capture bugs. NULL-terminated, or {NULL} if unused. */ - const char *bad_strings[6]; - int min_base_count; /* >= this many entries expected (0 = don't check count) */ + const char *bad_strings[6]; + int min_base_count; /* >= this many entries expected (0 = don't check count) */ } inherit_case_t; /* Find def trying Class / Interface / Struct / any-label fallback. */ static CBMDefinition *find_def_flex(CBMFileResult *r, const char *name) { CBMDefinition *d; - if ((d = find_def(r, "Class", name))) return d; - if ((d = find_def(r, "Interface", name))) return d; - if ((d = find_def(r, "Struct", name))) return d; + if ((d = find_def(r, "Class", name))) + return d; + if ((d = find_def(r, "Interface", name))) + return d; + if ((d = find_def(r, "Struct", name))) + return d; /* Fallback: any label — covers languages where the extractor uses custom labels */ return find_def_any(r, name); } @@ -140,9 +148,8 @@ static CBMDefinition *find_def_flex(CBMFileResult *r, const char *name) { * can include the case description in the failure message. */ static int run_inherit_case(const inherit_case_t *tc) { - CBMFileResult *r = cbm_extract_file(tc->src, (int)strlen(tc->src), - tc->lang, "t", tc->path, - 0, NULL, NULL); + CBMFileResult *r = + cbm_extract_file(tc->src, (int)strlen(tc->src), tc->lang, "t", tc->path, 0, NULL, NULL); if (!r) { printf(" FAIL [%s] cbm_extract_file returned NULL\n", tc->class_name); return 1; @@ -173,8 +180,8 @@ static int run_inherit_case(const inherit_case_t *tc) { int ok = 1; for (int i = 0; tc->expected_bases[i]; i++) { if (!bases_contain(cls, tc->expected_bases[i])) { - printf(" FAIL [%s] expected base \"%s\" not found in base_classes\n", - tc->class_name, tc->expected_bases[i]); + printf(" FAIL [%s] expected base \"%s\" not found in base_classes\n", tc->class_name, + tc->expected_bases[i]); ok = 0; } } @@ -192,8 +199,8 @@ static int run_inherit_case(const inherit_case_t *tc) { if (tc->min_base_count > 0) { int cnt = bases_count(cls); if (cnt < tc->min_base_count) { - printf(" FAIL [%s] base_classes has %d entries, expected >= %d\n", - tc->class_name, cnt, tc->min_base_count); + printf(" FAIL [%s] base_classes has %d entries, expected >= %d\n", tc->class_name, + cnt, tc->min_base_count); ok = 0; } } @@ -203,17 +210,18 @@ static int run_inherit_case(const inherit_case_t *tc) { } /* Convenience macro: run a table of cases, accumulate failures. */ -#define RUN_CASES(table) \ - do { \ - int _fail = 0; \ - int _n = (int)(sizeof(table) / sizeof(table[0])); \ - for (int _i = 0; _i < _n; _i++) { \ - if (run_inherit_case(&table[_i]) != 0) _fail++; \ - } \ - if (_fail > 0) { \ +#define RUN_CASES(table) \ + do { \ + int _fail = 0; \ + int _n = (int)(sizeof(table) / sizeof(table[0])); \ + for (int _i = 0; _i < _n; _i++) { \ + if (run_inherit_case(&table[_i]) != 0) \ + _fail++; \ + } \ + if (_fail > 0) { \ printf(" FAIL %d / %d cases above failed\n", _fail, _n); \ return 1; \ - } \ + } \ } while (0) /* ═══════════════════════════════════════════════════════════════════ @@ -227,97 +235,88 @@ static int run_inherit_case(const inherit_case_t *tc) { static const inherit_case_t python_cases[] = { /* ── single base (bare identifier) ────────────────────────── */ - { - CBM_LANG_PYTHON, "m.py", - "class Animal:\n pass\n\nclass Dog(Animal):\n pass\n", - "Dog", - {"Animal", NULL}, - {"(", ")", NULL}, - 1 - }, + {CBM_LANG_PYTHON, + "m.py", + "class Animal:\n pass\n\nclass Dog(Animal):\n pass\n", + "Dog", + {"Animal", NULL}, + {"(", ")", NULL}, + 1}, /* ── multiple bases ─────────────────────────────────────────── */ - { - CBM_LANG_PYTHON, "m.py", - "class A: pass\nclass B: pass\nclass C(A, B): pass\n", - "C", - {"A", "B", NULL}, - {"(", ")", NULL}, - 2 - }, + {CBM_LANG_PYTHON, + "m.py", + "class A: pass\nclass B: pass\nclass C(A, B): pass\n", + "C", + {"A", "B", NULL}, + {"(", ")", NULL}, + 2}, /* ── base + mixin ───────────────────────────────────────────── */ - { - CBM_LANG_PYTHON, "m.py", - "class Base: pass\nclass Mixin: pass\nclass Service(Base, Mixin): pass\n", - "Service", - {"Base", "Mixin", NULL}, - {"(", ")", NULL}, - 2 - }, + {CBM_LANG_PYTHON, + "m.py", + "class Base: pass\nclass Mixin: pass\nclass Service(Base, Mixin): pass\n", + "Service", + {"Base", "Mixin", NULL}, + {"(", ")", NULL}, + 2}, /* ── generic base (subscript, e.g. Generic[T]) ──────────────── */ - { - CBM_LANG_PYTHON, "m.py", - "from typing import Generic, TypeVar\nT = TypeVar('T')\n" - "class Stack(Generic[T]):\n pass\n", - "Stack", - {"Generic", NULL}, - {"(", ")", NULL}, - 1 - }, + {CBM_LANG_PYTHON, + "m.py", + "from typing import Generic, TypeVar\nT = TypeVar('T')\n" + "class Stack(Generic[T]):\n pass\n", + "Stack", + {"Generic", NULL}, + {"(", ")", NULL}, + 1}, /* ── qualified base (dotted: module.Base) ───────────────────── */ - { - CBM_LANG_PYTHON, "m.py", - "import django.db\nclass MyModel(django.db.Model):\n pass\n", - "MyModel", - {"django.db.Model", NULL}, - {"(", ")", NULL}, - 1 - }, + {CBM_LANG_PYTHON, + "m.py", + "import django.db\nclass MyModel(django.db.Model):\n pass\n", + "MyModel", + {"django.db.Model", NULL}, + {"(", ")", NULL}, + 1}, /* ── abstract base (abc.ABC) ────────────────────────────────── */ - { - CBM_LANG_PYTHON, "m.py", - "from abc import ABC, abstractmethod\nclass Shape(ABC):\n @abstractmethod\n def area(self): pass\n", - "Shape", - {"ABC", NULL}, - {"(", ")", NULL}, - 1 - }, + {CBM_LANG_PYTHON, + "m.py", + "from abc import ABC, abstractmethod\nclass Shape(ABC):\n @abstractmethod\n def " + "area(self): pass\n", + "Shape", + {"ABC", NULL}, + {"(", ")", NULL}, + 1}, /* ── dataclass with base ────────────────────────────────────── */ - { - CBM_LANG_PYTHON, "m.py", - "from dataclasses import dataclass\n@dataclass\nclass Point:\n x: float\n\n" - "@dataclass\nclass Point3D(Point):\n z: float\n", - "Point3D", - {"Point", NULL}, - {"(", ")", NULL}, - 1 - }, + {CBM_LANG_PYTHON, + "m.py", + "from dataclasses import dataclass\n@dataclass\nclass Point:\n x: float\n\n" + "@dataclass\nclass Point3D(Point):\n z: float\n", + "Point3D", + {"Point", NULL}, + {"(", ")", NULL}, + 1}, /* ── exception subclass ─────────────────────────────────────── */ - { - CBM_LANG_PYTHON, "m.py", - "class AppError(Exception): pass\n", - "AppError", - {"Exception", NULL}, - {"(", ")", NULL}, - 1 - }, + {CBM_LANG_PYTHON, + "m.py", + "class AppError(Exception): pass\n", + "AppError", + {"Exception", NULL}, + {"(", ")", NULL}, + 1}, /* ── three bases ────────────────────────────────────────────── */ - { - CBM_LANG_PYTHON, "m.py", - "class X: pass\nclass Y: pass\nclass Z: pass\nclass Multi(X, Y, Z): pass\n", - "Multi", - {"X", "Y", "Z", NULL}, - {"(", ")", NULL}, - 3 - }, + {CBM_LANG_PYTHON, + "m.py", + "class X: pass\nclass Y: pass\nclass Z: pass\nclass Multi(X, Y, Z): pass\n", + "Multi", + {"X", "Y", "Z", NULL}, + {"(", ")", NULL}, + 3}, /* ── base with keyword argument (metaclass=) should not bleed ── */ - { - CBM_LANG_PYTHON, "m.py", - "class Meta: pass\nclass MyClass(object, metaclass=Meta): pass\n", - "MyClass", - {"object", NULL}, - {"(", ")", "metaclass", NULL}, - 1 - }, + {CBM_LANG_PYTHON, + "m.py", + "class Meta: pass\nclass MyClass(object, metaclass=Meta): pass\n", + "MyClass", + {"object", NULL}, + {"(", ")", "metaclass", NULL}, + 1}, }; TEST(inherit_python) { @@ -331,188 +330,170 @@ TEST(inherit_python) { static const inherit_case_t java_cases[] = { /* ── extends only ───────────────────────────────────────────── */ - { - CBM_LANG_JAVA, "Svc.java", - "public class Dog extends Animal { }", - "Dog", - {"Animal", NULL}, - {"extends", "implements", NULL}, - 1 - }, + {CBM_LANG_JAVA, + "Svc.java", + "public class Dog extends Animal { }", + "Dog", + {"Animal", NULL}, + {"extends", "implements", NULL}, + 1}, /* ── implements single interface ─────────────────────────────── */ - { - CBM_LANG_JAVA, "Svc.java", - "public class ConcreteList implements List { }", - "ConcreteList", - {"List", NULL}, - {"extends", "implements", NULL}, - 1 - }, + {CBM_LANG_JAVA, + "Svc.java", + "public class ConcreteList implements List { }", + "ConcreteList", + {"List", NULL}, + {"extends", "implements", NULL}, + 1}, /* ── extends + implements one ────────────────────────────────── */ - { - CBM_LANG_JAVA, "Svc.java", - "public class MyThread extends Thread implements Runnable { }", - "MyThread", - {"Thread", "Runnable", NULL}, - {"extends", "implements", NULL}, - 2 - }, + {CBM_LANG_JAVA, + "Svc.java", + "public class MyThread extends Thread implements Runnable { }", + "MyThread", + {"Thread", "Runnable", NULL}, + {"extends", "implements", NULL}, + 2}, /* ── extends + implements two (regression for #279) ─────────── */ - { - CBM_LANG_JAVA, "Svc.java", - "public class DefaultLinkTool extends DefaultDiagramTool implements ILinkTool, Closeable { }", - "DefaultLinkTool", - {"DefaultDiagramTool", "ILinkTool", "Closeable", NULL}, - {"extends", "implements", NULL}, - 3 - }, + {CBM_LANG_JAVA, + "Svc.java", + "public class DefaultLinkTool extends DefaultDiagramTool implements ILinkTool, Closeable { }", + "DefaultLinkTool", + {"DefaultDiagramTool", "ILinkTool", "Closeable", NULL}, + {"extends", "implements", NULL}, + 3}, /* ── implements three interfaces ─────────────────────────────── */ - { - CBM_LANG_JAVA, "Svc.java", - "public class Svc implements A, B, C { }", - "Svc", - {"A", "B", "C", NULL}, - {"extends", "implements", NULL}, - 3 - }, + {CBM_LANG_JAVA, + "Svc.java", + "public class Svc implements A, B, C { }", + "Svc", + {"A", "B", "C", NULL}, + {"extends", "implements", NULL}, + 3}, /* ── generic base (extends List) ─────────────────────── */ - { - CBM_LANG_JAVA, "Svc.java", - "import java.util.*; public class MyList extends ArrayList { }", - "MyList", - {"ArrayList", NULL}, - {"extends", "implements", NULL}, - 1 - }, + {CBM_LANG_JAVA, + "Svc.java", + "import java.util.*; public class MyList extends ArrayList { }", + "MyList", + {"ArrayList", NULL}, + {"extends", "implements", NULL}, + 1}, /* ── generic implements (Comparable) ──────────────────────── */ - { - CBM_LANG_JAVA, "Svc.java", - "public class Box implements Comparable> { public int compareTo(Box o) { return 0; } }", - "Box", - {"Comparable", NULL}, - {"extends", "implements", NULL}, - 1 - }, + {CBM_LANG_JAVA, + "Svc.java", + "public class Box implements Comparable> { public int compareTo(Box o) { return " + "0; } }", + "Box", + {"Comparable", NULL}, + {"extends", "implements", NULL}, + 1}, /* ── abstract class extends ──────────────────────────────────── */ - { - CBM_LANG_JAVA, "Svc.java", - "public abstract class AbstractSvc extends BaseService { protected abstract void run(); }", - "AbstractSvc", - {"BaseService", NULL}, - {"extends", "implements", NULL}, - 1 - }, + {CBM_LANG_JAVA, + "Svc.java", + "public abstract class AbstractSvc extends BaseService { protected abstract void run(); }", + "AbstractSvc", + {"BaseService", NULL}, + {"extends", "implements", NULL}, + 1}, /* ── interface extends interface ─────────────────────────────── */ - { - CBM_LANG_JAVA, "Svc.java", - "public interface ReadWriteRepo extends ReadRepo, WriteRepo { }", - "ReadWriteRepo", - {"ReadRepo", "WriteRepo", NULL}, - {"extends", "implements", NULL}, - 2 - }, + {CBM_LANG_JAVA, + "Svc.java", + "public interface ReadWriteRepo extends ReadRepo, WriteRepo { }", + "ReadWriteRepo", + {"ReadRepo", "WriteRepo", NULL}, + {"extends", "implements", NULL}, + 2}, /* ── enum implements interface ───────────────────────────────── */ - { - CBM_LANG_JAVA, "Svc.java", - "public enum Status implements Displayable { OPEN, CLOSED; public String display() { return name(); } }", - "Status", - {"Displayable", NULL}, - {"extends", "implements", NULL}, - 1 - }, + {CBM_LANG_JAVA, + "Svc.java", + "public enum Status implements Displayable { OPEN, CLOSED; public String display() { return " + "name(); } }", + "Status", + {"Displayable", NULL}, + {"extends", "implements", NULL}, + 1}, /* ── qualified (imported) type name ─────────────────────────── */ - { - CBM_LANG_JAVA, "Svc.java", - "public class Handler extends java.net.ServerSocket { }", - "Handler", - {"java.net.ServerSocket", NULL}, - {"extends", "implements", NULL}, - 1 - }, + {CBM_LANG_JAVA, + "Svc.java", + "public class Handler extends java.net.ServerSocket { }", + "Handler", + {"java.net.ServerSocket", NULL}, + {"extends", "implements", NULL}, + 1}, /* ── extends + implements four ───────────────────────────────── */ - { - CBM_LANG_JAVA, "Svc.java", - "public class Mega extends Base implements A, B, C, D { }", - "Mega", - {"Base", "A", "B", "C", "D", NULL}, - {"extends", "implements", NULL}, - 5 - }, + {CBM_LANG_JAVA, + "Svc.java", + "public class Mega extends Base implements A, B, C, D { }", + "Mega", + {"Base", "A", "B", "C", "D", NULL}, + {"extends", "implements", NULL}, + 5}, /* ── nested class with base ──────────────────────────────────── */ - { - CBM_LANG_JAVA, "Svc.java", - "public class Outer { public static class Inner extends Outer { } }", - "Inner", - {"Outer", NULL}, - {"extends", "implements", NULL}, - 1 - }, + {CBM_LANG_JAVA, + "Svc.java", + "public class Outer { public static class Inner extends Outer { } }", + "Inner", + {"Outer", NULL}, + {"extends", "implements", NULL}, + 1}, /* ── record implements interface (Java 16+) ──────────────────── */ - { - CBM_LANG_JAVA, "Svc.java", - "public record Point(int x, int y) implements Comparable { " - "public int compareTo(Point o) { return Integer.compare(x, o.x); } }", - "Point", - {"Comparable", NULL}, - {"extends", "implements", NULL}, - 1 - }, + {CBM_LANG_JAVA, + "Svc.java", + "public record Point(int x, int y) implements Comparable { " + "public int compareTo(Point o) { return Integer.compare(x, o.x); } }", + "Point", + {"Comparable", NULL}, + {"extends", "implements", NULL}, + 1}, /* ── sealed class (Java 17+) ─────────────────────────────────── */ - { - CBM_LANG_JAVA, "Svc.java", - "public sealed class Shape permits Circle, Rectangle { }", - "Shape", - {NULL}, /* permitted-types are not base_classes of Shape; no bases to assert */ - {NULL}, - 0 - }, + {CBM_LANG_JAVA, + "Svc.java", + "public sealed class Shape permits Circle, Rectangle { }", + "Shape", + {NULL}, /* permitted-types are not base_classes of Shape; no bases to assert */ + {NULL}, + 0}, /* ── class implements Serializable ──────────────────────────── */ - { - CBM_LANG_JAVA, "Svc.java", - "import java.io.*; public class Data extends BaseData implements Serializable, Cloneable { }", - "Data", - {"BaseData", "Serializable", "Cloneable", NULL}, - {"extends", "implements", NULL}, - 3 - }, + {CBM_LANG_JAVA, + "Svc.java", + "import java.io.*; public class Data extends BaseData implements Serializable, Cloneable { }", + "Data", + {"BaseData", "Serializable", "Cloneable", NULL}, + {"extends", "implements", NULL}, + 3}, /* ── generic class extends generic base ──────────────────────── */ - { - CBM_LANG_JAVA, "Svc.java", - "public class Pair extends AbstractPair implements Iterable { " - "public java.util.Iterator iterator() { return null; } }", - "Pair", - {"AbstractPair", "Iterable", NULL}, - {"extends", "implements", NULL}, - 2 - }, + {CBM_LANG_JAVA, + "Svc.java", + "public class Pair extends AbstractPair implements Iterable { " + "public java.util.Iterator iterator() { return null; } }", + "Pair", + {"AbstractPair", "Iterable", NULL}, + {"extends", "implements", NULL}, + 2}, /* ── class extending Exception ───────────────────────────────── */ - { - CBM_LANG_JAVA, "Svc.java", - "public class AppException extends RuntimeException { " - "public AppException(String msg) { super(msg); } }", - "AppException", - {"RuntimeException", NULL}, - {"extends", "implements", NULL}, - 1 - }, + {CBM_LANG_JAVA, + "Svc.java", + "public class AppException extends RuntimeException { " + "public AppException(String msg) { super(msg); } }", + "AppException", + {"RuntimeException", NULL}, + {"extends", "implements", NULL}, + 1}, /* ── multiple interfaces no extends ──────────────────────────── */ - { - CBM_LANG_JAVA, "Svc.java", - "public class Codec implements Encoder, Decoder, Closeable { }", - "Codec", - {"Encoder", "Decoder", "Closeable", NULL}, - {"extends", "implements", NULL}, - 3 - }, + {CBM_LANG_JAVA, + "Svc.java", + "public class Codec implements Encoder, Decoder, Closeable { }", + "Codec", + {"Encoder", "Decoder", "Closeable", NULL}, + {"extends", "implements", NULL}, + 3}, /* ── annotated class with base ───────────────────────────────── */ - { - CBM_LANG_JAVA, "Svc.java", - "@Override public class AnnotatedSvc extends BaseSvc { }", - "AnnotatedSvc", - {"BaseSvc", NULL}, - {"extends", "implements", NULL}, - 1 - }, + {CBM_LANG_JAVA, + "Svc.java", + "@Override public class AnnotatedSvc extends BaseSvc { }", + "AnnotatedSvc", + {"BaseSvc", NULL}, + {"extends", "implements", NULL}, + 1}, }; TEST(inherit_java) { @@ -526,190 +507,170 @@ TEST(inherit_java) { static const inherit_case_t csharp_cases[] = { /* ── single base class ──────────────────────────────────────── */ - { - CBM_LANG_CSHARP, "Svc.cs", - "public class Dog : Animal { }", - "Dog", - {"Animal", NULL}, - {":", NULL}, - 1 - }, + {CBM_LANG_CSHARP, + "Svc.cs", + "public class Dog : Animal { }", + "Dog", + {"Animal", NULL}, + {":", NULL}, + 1}, /* ── implements single interface ─────────────────────────────── */ - { - CBM_LANG_CSHARP, "Svc.cs", - "public class Repo : IRepository { }", - "Repo", - {"IRepository", NULL}, - {":", NULL}, - 1 - }, + {CBM_LANG_CSHARP, + "Svc.cs", + "public class Repo : IRepository { }", + "Repo", + {"IRepository", NULL}, + {":", NULL}, + 1}, /* ── base + interface ────────────────────────────────────────── */ - { - CBM_LANG_CSHARP, "Svc.cs", - "public class Service : BaseService, IService { }", - "Service", - {"BaseService", "IService", NULL}, - {":", NULL}, - 2 - }, + {CBM_LANG_CSHARP, + "Svc.cs", + "public class Service : BaseService, IService { }", + "Service", + {"BaseService", "IService", NULL}, + {":", NULL}, + 2}, /* ── base + two interfaces ───────────────────────────────────── */ - { - CBM_LANG_CSHARP, "Svc.cs", - "public class Worker : BackgroundService, IWorker, IDisposable { }", - "Worker", - {"BackgroundService", "IWorker", "IDisposable", NULL}, - {":", NULL}, - 3 - }, + {CBM_LANG_CSHARP, + "Svc.cs", + "public class Worker : BackgroundService, IWorker, IDisposable { }", + "Worker", + {"BackgroundService", "IWorker", "IDisposable", NULL}, + {":", NULL}, + 3}, /* ── generic base ────────────────────────────────────────────── */ - { - CBM_LANG_CSHARP, "Svc.cs", - "public class OrderList : List { }", - "OrderList", - {"List", NULL}, - {":", NULL}, - 1 - }, + {CBM_LANG_CSHARP, + "Svc.cs", + "public class OrderList : List { }", + "OrderList", + {"List", NULL}, + {":", NULL}, + 1}, /* ── generic base + interface ────────────────────────────────── */ - { - CBM_LANG_CSHARP, "Svc.cs", - "public class Stack : Collection, IStack { }", - "Stack", - {"Collection", "IStack", NULL}, - {":", NULL}, - 2 - }, + {CBM_LANG_CSHARP, + "Svc.cs", + "public class Stack : Collection, IStack { }", + "Stack", + {"Collection", "IStack", NULL}, + {":", NULL}, + 2}, /* ── interface extends two interfaces ────────────────────────── */ - { - CBM_LANG_CSHARP, "Svc.cs", - "public interface ICrud : IRead, IWrite { }", - "ICrud", - {"IRead", "IWrite", NULL}, - {":", NULL}, - 2 - }, + {CBM_LANG_CSHARP, + "Svc.cs", + "public interface ICrud : IRead, IWrite { }", + "ICrud", + {"IRead", "IWrite", NULL}, + {":", NULL}, + 2}, /* ── abstract class with base ────────────────────────────────── */ - { - CBM_LANG_CSHARP, "Svc.cs", - "public abstract class AbstractHandler : BaseHandler { protected abstract void Handle(); }", - "AbstractHandler", - {"BaseHandler", NULL}, - {":", NULL}, - 1 - }, + {CBM_LANG_CSHARP, + "Svc.cs", + "public abstract class AbstractHandler : BaseHandler { protected abstract void Handle(); }", + "AbstractHandler", + {"BaseHandler", NULL}, + {":", NULL}, + 1}, /* ── sealed class ────────────────────────────────────────────── */ - { - CBM_LANG_CSHARP, "Svc.cs", - "public sealed class SingletonService : BaseService { }", - "SingletonService", - {"BaseService", NULL}, - {":", NULL}, - 1 - }, + {CBM_LANG_CSHARP, + "Svc.cs", + "public sealed class SingletonService : BaseService { }", + "SingletonService", + {"BaseService", NULL}, + {":", NULL}, + 1}, /* ── namespace-qualified base ────────────────────────────────── */ - { - CBM_LANG_CSHARP, "Svc.cs", - "namespace App { public class Handler : System.Net.Http.HttpMessageHandler { " - "protected override System.Threading.Tasks.Task " - "SendAsync(System.Net.Http.HttpRequestMessage r, System.Threading.CancellationToken c) " - "{ return null; } } }", - "Handler", - {"System.Net.Http.HttpMessageHandler", NULL}, - {":", NULL}, - 1 - }, + {CBM_LANG_CSHARP, + "Svc.cs", + "namespace App { public class Handler : System.Net.Http.HttpMessageHandler { " + "protected override System.Threading.Tasks.Task " + "SendAsync(System.Net.Http.HttpRequestMessage r, System.Threading.CancellationToken c) " + "{ return null; } } }", + "Handler", + {"System.Net.Http.HttpMessageHandler", NULL}, + {":", NULL}, + 1}, /* ── partial class with base ─────────────────────────────────── */ - { - CBM_LANG_CSHARP, "Svc.cs", - "public partial class PartialSvc : BaseSvc, IPartial { }", - "PartialSvc", - {"BaseSvc", "IPartial", NULL}, - {":", NULL}, - 2 - }, + {CBM_LANG_CSHARP, + "Svc.cs", + "public partial class PartialSvc : BaseSvc, IPartial { }", + "PartialSvc", + {"BaseSvc", "IPartial", NULL}, + {":", NULL}, + 2}, /* ── record with base ────────────────────────────────────────── */ - { - CBM_LANG_CSHARP, "Svc.cs", - "public record OrderRecord(int Id) : BaseRecord(Id), IRecord { }", - "OrderRecord", - {"BaseRecord", "IRecord", NULL}, - {":", NULL}, - 2 - }, + {CBM_LANG_CSHARP, + "Svc.cs", + "public record OrderRecord(int Id) : BaseRecord(Id), IRecord { }", + "OrderRecord", + {"BaseRecord", "IRecord", NULL}, + {":", NULL}, + 2}, /* ── struct implements interface ─────────────────────────────── */ - { - CBM_LANG_CSHARP, "Svc.cs", - "public struct Point : IEquatable { public bool Equals(Point other) => true; }", - "Point", - {"IEquatable", NULL}, - {":", NULL}, - 1 - }, + {CBM_LANG_CSHARP, + "Svc.cs", + "public struct Point : IEquatable { public bool Equals(Point other) => true; }", + "Point", + {"IEquatable", NULL}, + {":", NULL}, + 1}, /* ── class with four interfaces ──────────────────────────────── */ - { - CBM_LANG_CSHARP, "Svc.cs", - "public class Mega : Base, IA, IB, IC, ID { }", - "Mega", - {"Base", "IA", "IB", "IC", "ID", NULL}, - {":", NULL}, - 5 - }, + {CBM_LANG_CSHARP, + "Svc.cs", + "public class Mega : Base, IA, IB, IC, ID { }", + "Mega", + {"Base", "IA", "IB", "IC", "ID", NULL}, + {":", NULL}, + 5}, /* ── exception subclass ─────────────────────────────────────── */ - { - CBM_LANG_CSHARP, "Svc.cs", - "public class AppException : Exception { public AppException(string msg) : base(msg) {} }", - "AppException", - {"Exception", NULL}, - {":", NULL}, - 1 - }, + {CBM_LANG_CSHARP, + "Svc.cs", + "public class AppException : Exception { public AppException(string msg) : base(msg) {} }", + "AppException", + {"Exception", NULL}, + {":", NULL}, + 1}, /* ── nested class with base ──────────────────────────────────── */ - { - CBM_LANG_CSHARP, "Svc.cs", - "public class Outer { public class Inner : Outer { } }", - "Inner", - {"Outer", NULL}, - {":", NULL}, - 1 - }, + {CBM_LANG_CSHARP, + "Svc.cs", + "public class Outer { public class Inner : Outer { } }", + "Inner", + {"Outer", NULL}, + {":", NULL}, + 1}, /* ── class in namespace with base ───────────────────────────── */ - { - CBM_LANG_CSHARP, "Svc.cs", - "namespace MyApp.Services { public class OrderSvc : BaseOrderSvc, IOrderSvc { } }", - "OrderSvc", - {"BaseOrderSvc", "IOrderSvc", NULL}, - {":", NULL}, - 2 - }, + {CBM_LANG_CSHARP, + "Svc.cs", + "namespace MyApp.Services { public class OrderSvc : BaseOrderSvc, IOrderSvc { } }", + "OrderSvc", + {"BaseOrderSvc", "IOrderSvc", NULL}, + {":", NULL}, + 2}, /* ── interface with single parent ───────────────────────────── */ - { - CBM_LANG_CSHARP, "Svc.cs", - "public interface IAdvancedService : IBasicService { void AdvancedOp(); }", - "IAdvancedService", - {"IBasicService", NULL}, - {":", NULL}, - 1 - }, + {CBM_LANG_CSHARP, + "Svc.cs", + "public interface IAdvancedService : IBasicService { void AdvancedOp(); }", + "IAdvancedService", + {"IBasicService", NULL}, + {":", NULL}, + 1}, /* ── generic class implements generic interface ───────────────── */ - { - CBM_LANG_CSHARP, "Svc.cs", - "public class Repo : BaseRepo, IRepo where T : class { }", - "Repo", - {"BaseRepo", "IRepo", NULL}, - {":", "where", NULL}, - 2 - }, + {CBM_LANG_CSHARP, + "Svc.cs", + "public class Repo : BaseRepo, IRepo where T : class { }", + "Repo", + {"BaseRepo", "IRepo", NULL}, + {":", "where", NULL}, + 2}, /* ── class with IDisposable + IAsyncDisposable ───────────────── */ - { - CBM_LANG_CSHARP, "Svc.cs", - "public class Resource : IDisposable, IAsyncDisposable { " - "public void Dispose() {} " - "public System.Threading.Tasks.ValueTask DisposeAsync() => default; }", - "Resource", - {"IDisposable", "IAsyncDisposable", NULL}, - {":", NULL}, - 2 - }, + {CBM_LANG_CSHARP, + "Svc.cs", + "public class Resource : IDisposable, IAsyncDisposable { " + "public void Dispose() {} " + "public System.Threading.Tasks.ValueTask DisposeAsync() => default; }", + "Resource", + {"IDisposable", "IAsyncDisposable", NULL}, + {":", NULL}, + 2}, }; TEST(inherit_csharp) { @@ -723,195 +684,176 @@ TEST(inherit_csharp) { static const inherit_case_t cpp_cases[] = { /* ── public single base ─────────────────────────────────────── */ - { - CBM_LANG_CPP, "svc.cpp", - "class Dog : public Animal { };", - "Dog", - {"Animal", NULL}, - {"public", "private", "protected", ":", NULL}, - 1 - }, + {CBM_LANG_CPP, + "svc.cpp", + "class Dog : public Animal { };", + "Dog", + {"Animal", NULL}, + {"public", "private", "protected", ":", NULL}, + 1}, /* ── private base ────────────────────────────────────────────── */ - { - CBM_LANG_CPP, "svc.cpp", - "class Impl : private Base { };", - "Impl", - {"Base", NULL}, - {"public", "private", "protected", ":", NULL}, - 1 - }, + {CBM_LANG_CPP, + "svc.cpp", + "class Impl : private Base { };", + "Impl", + {"Base", NULL}, + {"public", "private", "protected", ":", NULL}, + 1}, /* ── multiple inheritance ────────────────────────────────────── */ - { - CBM_LANG_CPP, "svc.cpp", - "class C : public A, public B { };", - "C", - {"A", "B", NULL}, - {"public", "private", "protected", ":", NULL}, - 2 - }, + {CBM_LANG_CPP, + "svc.cpp", + "class C : public A, public B { };", + "C", + {"A", "B", NULL}, + {"public", "private", "protected", ":", NULL}, + 2}, /* ── struct with base ────────────────────────────────────────── */ - { - CBM_LANG_CPP, "svc.cpp", - "struct Derived : Base { int x; };", - "Derived", - {"Base", NULL}, - {":", NULL}, - 1 - }, + {CBM_LANG_CPP, + "svc.cpp", + "struct Derived : Base { int x; };", + "Derived", + {"Base", NULL}, + {":", NULL}, + 1}, /* ── virtual inheritance ─────────────────────────────────────── */ - { - CBM_LANG_CPP, "svc.cpp", - "class D : public virtual B1, public virtual B2 { };", - "D", - {"B1", "B2", NULL}, - {"virtual", "public", ":", NULL}, - 2 - }, + {CBM_LANG_CPP, + "svc.cpp", + "class D : public virtual B1, public virtual B2 { };", + "D", + {"B1", "B2", NULL}, + {"virtual", "public", ":", NULL}, + 2}, /* ── template base ───────────────────────────────────────────── */ - { - CBM_LANG_CPP, "svc.cpp", - "template class Stack : public std::vector { };", - "Stack", - {"std::vector", NULL}, - /* qualified base: `::` is legitimate, so the bare-`:` separator-leak - * guard does not apply here (it would match inside `std::vector`). */ - {"public", "template", NULL}, - 1 - }, + {CBM_LANG_CPP, + "svc.cpp", + "template class Stack : public std::vector { };", + "Stack", + {"std::vector", NULL}, + /* qualified base: `::` is legitimate, so the bare-`:` separator-leak + * guard does not apply here (it would match inside `std::vector`). */ + {"public", "template", NULL}, + 1}, /* ── CRTP pattern ────────────────────────────────────────────── */ - { - CBM_LANG_CPP, "svc.cpp", - "template class Base { };\n" - "class Concrete : public Base { };", - "Concrete", - {"Base", NULL}, - {"public", ":", NULL}, - 1 - }, + {CBM_LANG_CPP, + "svc.cpp", + "template class Base { };\n" + "class Concrete : public Base { };", + "Concrete", + {"Base", NULL}, + {"public", ":", NULL}, + 1}, /* ── abstract class (pure virtual) with public base ──────────── */ - { - CBM_LANG_CPP, "svc.cpp", - "class AbstractLogger : public ILogger { public: virtual void log(const char*) = 0; };", - "AbstractLogger", - {"ILogger", NULL}, - {"public", ":", NULL}, - 1 - }, + {CBM_LANG_CPP, + "svc.cpp", + "class AbstractLogger : public ILogger { public: virtual void log(const char*) = 0; };", + "AbstractLogger", + {"ILogger", NULL}, + {"public", ":", NULL}, + 1}, /* ── class in namespace ──────────────────────────────────────── */ - { - CBM_LANG_CPP, "svc.cpp", - "namespace net { class Socket : public BaseSocket { }; }", - "Socket", - {"BaseSocket", NULL}, - {"public", ":", NULL}, - 1 - }, + {CBM_LANG_CPP, + "svc.cpp", + "namespace net { class Socket : public BaseSocket { }; }", + "Socket", + {"BaseSocket", NULL}, + {"public", ":", NULL}, + 1}, /* ── three-way diamond inheritance ──────────────────────────── */ - { - CBM_LANG_CPP, "svc.cpp", - "class A { }; class B : public A { }; class C : public A { };\n" - "class D : public B, public C { };", - "D", - {"B", "C", NULL}, - {"public", ":", NULL}, - 2 - }, + {CBM_LANG_CPP, + "svc.cpp", + "class A { }; class B : public A { }; class C : public A { };\n" + "class D : public B, public C { };", + "D", + {"B", "C", NULL}, + {"public", ":", NULL}, + 2}, /* ── fully qualified base name ───────────────────────────────── */ - { - CBM_LANG_CPP, "svc.cpp", - "class MyStream : public std::ostream { public: MyStream() : std::ostream(nullptr) {} };", - "MyStream", - {"std::ostream", NULL}, - /* qualified base: bare-`:` guard omitted (matches inside `std::ostream`). */ - {"public", NULL}, - 1 - }, + {CBM_LANG_CPP, + "svc.cpp", + "class MyStream : public std::ostream { public: MyStream() : std::ostream(nullptr) {} };", + "MyStream", + {"std::ostream", NULL}, + /* qualified base: bare-`:` guard omitted (matches inside `std::ostream`). */ + {"public", NULL}, + 1}, /* ── protected base ──────────────────────────────────────────── */ - { - CBM_LANG_CPP, "svc.cpp", - "class Node : protected TreeNode { };", - "Node", - {"TreeNode", NULL}, - {"protected", ":", NULL}, - 1 - }, + {CBM_LANG_CPP, + "svc.cpp", + "class Node : protected TreeNode { };", + "Node", + {"TreeNode", NULL}, + {"protected", ":", NULL}, + 1}, /* ── multiple bases with mixed access ────────────────────────── */ - { - CBM_LANG_CPP, "svc.cpp", - "class Widget : public Drawable, private EventHandler, protected Serializable { };", - "Widget", - {"Drawable", "EventHandler", "Serializable", NULL}, - {"public", "private", "protected", ":", NULL}, - 3 - }, + {CBM_LANG_CPP, + "svc.cpp", + "class Widget : public Drawable, private EventHandler, protected Serializable { };", + "Widget", + {"Drawable", "EventHandler", "Serializable", NULL}, + {"public", "private", "protected", ":", NULL}, + 3}, /* ── exception class from std::exception ─────────────────────── */ - { - CBM_LANG_CPP, "svc.cpp", - "#include \nclass AppError : public std::runtime_error { " - "public: AppError(const char* m) : std::runtime_error(m) {} };", - "AppError", - {"std::runtime_error", NULL}, - /* qualified base: bare-`:` guard omitted (matches inside `std::runtime_error`). */ - {"public", NULL}, - 1 - }, + {CBM_LANG_CPP, + "svc.cpp", + "#include \nclass AppError : public std::runtime_error { " + "public: AppError(const char* m) : std::runtime_error(m) {} };", + "AppError", + {"std::runtime_error", NULL}, + /* qualified base: bare-`:` guard omitted (matches inside `std::runtime_error`). */ + {"public", NULL}, + 1}, /* ── template class with multiple template base types ────────── */ - { - CBM_LANG_CPP, "svc.cpp", - "template\n" - "class LruCache : public Cache, public Observable { };", - "LruCache", - {"Cache", "Observable", NULL}, - {"public", ":", NULL}, - 2 - }, + {CBM_LANG_CPP, + "svc.cpp", + "template\n" + "class LruCache : public Cache, public Observable { };", + "LruCache", + {"Cache", "Observable", NULL}, + {"public", ":", NULL}, + 2}, /* ── nested class with base ──────────────────────────────────── */ - { - CBM_LANG_CPP, "svc.cpp", - "class Outer { public: class Inner : public Base { }; };", - "Inner", - {"Base", NULL}, - {"public", ":", NULL}, - 1 - }, + {CBM_LANG_CPP, + "svc.cpp", + "class Outer { public: class Inner : public Base { }; };", + "Inner", + {"Base", NULL}, + {"public", ":", NULL}, + 1}, /* ── class using final specifier ─────────────────────────────── */ - { - CBM_LANG_CPP, "svc.cpp", - "class Leaf final : public Node { };", - "Leaf", - {"Node", NULL}, - {"public", "final", ":", NULL}, - 1 - }, + {CBM_LANG_CPP, + "svc.cpp", + "class Leaf final : public Node { };", + "Leaf", + {"Node", NULL}, + {"public", "final", ":", NULL}, + 1}, /* ── struct with scoped base ─────────────────────────────────── */ - { - CBM_LANG_CPP, "svc.cpp", - "struct MyVisitor : public boost::static_visitor { int operator()(int x) { return x; } };", - "MyVisitor", - {"boost::static_visitor", NULL}, - /* qualified base: bare-`:` guard omitted (matches inside `boost::static_visitor`). */ - {"public", NULL}, - 1 - }, + {CBM_LANG_CPP, + "svc.cpp", + "struct MyVisitor : public boost::static_visitor { int operator()(int x) { return x; } " + "};", + "MyVisitor", + {"boost::static_visitor", NULL}, + /* qualified base: bare-`:` guard omitted (matches inside `boost::static_visitor`). */ + {"public", NULL}, + 1}, /* ── policy-based design (two template base policies) ────────── */ - { - CBM_LANG_CPP, "svc.cpp", - "template\n" - "class Engine : public StoragePolicy, public LogPolicy { };", - "Engine", - {"StoragePolicy", "LogPolicy", NULL}, - {"public", ":", NULL}, - 2 - }, + {CBM_LANG_CPP, + "svc.cpp", + "template\n" + "class Engine : public StoragePolicy, public LogPolicy { };", + "Engine", + {"StoragePolicy", "LogPolicy", NULL}, + {"public", ":", NULL}, + 2}, /* ── empty base optimization (EBO) ──────────────────────────── */ - { - CBM_LANG_CPP, "svc.cpp", - "class EboContainer : private Allocator, public ContainerBase { };", - "EboContainer", - {"Allocator", "ContainerBase", NULL}, - {"private", "public", ":", NULL}, - 2 - }, + {CBM_LANG_CPP, + "svc.cpp", + "class EboContainer : private Allocator, public ContainerBase { };", + "EboContainer", + {"Allocator", "ContainerBase", NULL}, + {"private", "public", ":", NULL}, + 2}, }; TEST(inherit_cpp) { @@ -928,174 +870,156 @@ TEST(inherit_cpp) { static const inherit_case_t typescript_cases[] = { /* ── extends single class ────────────────────────────────────── */ - { - CBM_LANG_TYPESCRIPT, "svc.ts", - "class Dog extends Animal { bark(): void {} }", - "Dog", - {"Animal", NULL}, - {"extends", "implements", NULL}, - 1 - }, + {CBM_LANG_TYPESCRIPT, + "svc.ts", + "class Dog extends Animal { bark(): void {} }", + "Dog", + {"Animal", NULL}, + {"extends", "implements", NULL}, + 1}, /* ── implements single interface ─────────────────────────────── */ - { - CBM_LANG_TYPESCRIPT, "svc.ts", - "class Repo implements IRepository { save(x: any) {} }", - "Repo", - {"IRepository", NULL}, - {"extends", "implements", NULL}, - 1 - }, + {CBM_LANG_TYPESCRIPT, + "svc.ts", + "class Repo implements IRepository { save(x: any) {} }", + "Repo", + {"IRepository", NULL}, + {"extends", "implements", NULL}, + 1}, /* ── extends + implements ────────────────────────────────────── */ - { - CBM_LANG_TYPESCRIPT, "svc.ts", - "class Service extends BaseService implements IService { run() {} }", - "Service", - {"BaseService", "IService", NULL}, - {"extends", "implements", NULL}, - 2 - }, + {CBM_LANG_TYPESCRIPT, + "svc.ts", + "class Service extends BaseService implements IService { run() {} }", + "Service", + {"BaseService", "IService", NULL}, + {"extends", "implements", NULL}, + 2}, /* ── implements two interfaces ───────────────────────────────── */ - { - CBM_LANG_TYPESCRIPT, "svc.ts", - "class Codec implements Encoder, Decoder { encode(x: any) {} decode(x: any) {} }", - "Codec", - {"Encoder", "Decoder", NULL}, - {"extends", "implements", NULL}, - 2 - }, + {CBM_LANG_TYPESCRIPT, + "svc.ts", + "class Codec implements Encoder, Decoder { encode(x: any) {} decode(x: any) {} }", + "Codec", + {"Encoder", "Decoder", NULL}, + {"extends", "implements", NULL}, + 2}, /* ── extends + implements three ──────────────────────────────── */ - { - CBM_LANG_TYPESCRIPT, "svc.ts", - "class Mega extends Base implements A, B, C { }", - "Mega", - {"Base", "A", "B", "C", NULL}, - {"extends", "implements", NULL}, - 4 - }, + {CBM_LANG_TYPESCRIPT, + "svc.ts", + "class Mega extends Base implements A, B, C { }", + "Mega", + {"Base", "A", "B", "C", NULL}, + {"extends", "implements", NULL}, + 4}, /* ── generic base ────────────────────────────────────────────── */ - { - CBM_LANG_TYPESCRIPT, "svc.ts", - "class Stack extends Array implements IStack { push(x: T) { return 0; } }", - "Stack", - {"Array", "IStack", NULL}, - {"extends", "implements", NULL}, - 2 - }, + {CBM_LANG_TYPESCRIPT, + "svc.ts", + "class Stack extends Array implements IStack { push(x: T) { return 0; } }", + "Stack", + {"Array", "IStack", NULL}, + {"extends", "implements", NULL}, + 2}, /* ── interface extends interface ─────────────────────────────── */ - { - CBM_LANG_TYPESCRIPT, "svc.ts", - "interface ReadWriteRepo extends ReadRepo, WriteRepo { }", - "ReadWriteRepo", - {"ReadRepo", "WriteRepo", NULL}, - {"extends", "implements", NULL}, - 2 - }, + {CBM_LANG_TYPESCRIPT, + "svc.ts", + "interface ReadWriteRepo extends ReadRepo, WriteRepo { }", + "ReadWriteRepo", + {"ReadRepo", "WriteRepo", NULL}, + {"extends", "implements", NULL}, + 2}, /* ── abstract class ──────────────────────────────────────────── */ - { - CBM_LANG_TYPESCRIPT, "svc.ts", - "abstract class AbstractSvc extends BaseService { abstract run(): void; }", - "AbstractSvc", - {"BaseService", NULL}, - {"extends", "implements", NULL}, - 1 - }, + {CBM_LANG_TYPESCRIPT, + "svc.ts", + "abstract class AbstractSvc extends BaseService { abstract run(): void; }", + "AbstractSvc", + {"BaseService", NULL}, + {"extends", "implements", NULL}, + 1}, /* ── class extends Error ─────────────────────────────────────── */ - { - CBM_LANG_TYPESCRIPT, "svc.ts", - "class AppError extends Error { constructor(msg: string) { super(msg); } }", - "AppError", - {"Error", NULL}, - {"extends", "implements", NULL}, - 1 - }, + {CBM_LANG_TYPESCRIPT, + "svc.ts", + "class AppError extends Error { constructor(msg: string) { super(msg); } }", + "AppError", + {"Error", NULL}, + {"extends", "implements", NULL}, + 1}, /* ── class extends EventEmitter ──────────────────────────────── */ - { - CBM_LANG_TYPESCRIPT, "svc.ts", - "import { EventEmitter } from 'events';\n" - "class Bus extends EventEmitter { emit(ev: string) { return super.emit(ev); } }", - "Bus", - {"EventEmitter", NULL}, - {"extends", "implements", NULL}, - 1 - }, + {CBM_LANG_TYPESCRIPT, + "svc.ts", + "import { EventEmitter } from 'events';\n" + "class Bus extends EventEmitter { emit(ev: string) { return super.emit(ev); } }", + "Bus", + {"EventEmitter", NULL}, + {"extends", "implements", NULL}, + 1}, /* ── generic class extends generic base ──────────────────────── */ - { - CBM_LANG_TYPESCRIPT, "svc.ts", - "class Pair extends AbstractPair implements Iterable { " - "[Symbol.iterator]() { return this as any; } }", - "Pair", - {"AbstractPair", "Iterable", NULL}, - {"extends", "implements", NULL}, - 2 - }, + {CBM_LANG_TYPESCRIPT, + "svc.ts", + "class Pair extends AbstractPair implements Iterable { " + "[Symbol.iterator]() { return this as any; } }", + "Pair", + {"AbstractPair", "Iterable", NULL}, + {"extends", "implements", NULL}, + 2}, /* ── class implements multiple generic interfaces ─────────────── */ - { - CBM_LANG_TYPESCRIPT, "svc.ts", - "class Handler implements Middleware, Disposable { " - "handle(req: Request): Response { return null as any; } dispose() {} }", - "Handler", - {"Middleware", "Disposable", NULL}, - {"extends", "implements", NULL}, - 2 - }, + {CBM_LANG_TYPESCRIPT, + "svc.ts", + "class Handler implements Middleware, Disposable { " + "handle(req: Request): Response { return null as any; } dispose() {} }", + "Handler", + {"Middleware", "Disposable", NULL}, + {"extends", "implements", NULL}, + 2}, /* ── class in module namespace ───────────────────────────────── */ - { - CBM_LANG_TYPESCRIPT, "svc.ts", - "export class OrderService extends BaseOrderService implements IOrderService { }", - "OrderService", - {"BaseOrderService", "IOrderService", NULL}, - {"extends", "implements", NULL}, - 2 - }, + {CBM_LANG_TYPESCRIPT, + "svc.ts", + "export class OrderService extends BaseOrderService implements IOrderService { }", + "OrderService", + {"BaseOrderService", "IOrderService", NULL}, + {"extends", "implements", NULL}, + 2}, /* ── mixin target class (concrete class consuming a mixin) ──────── */ - { - CBM_LANG_TYPESCRIPT, "svc.ts", - "class Loggable { log(msg: string) { console.log(msg); } }\n" - "class Logger extends Loggable implements ILogger { info(msg: string) { this.log(msg); } }", - "Logger", - {"Loggable", "ILogger", NULL}, - {"extends", "implements", NULL}, - 2 - }, + {CBM_LANG_TYPESCRIPT, + "svc.ts", + "class Loggable { log(msg: string) { console.log(msg); } }\n" + "class Logger extends Loggable implements ILogger { info(msg: string) { this.log(msg); } }", + "Logger", + {"Loggable", "ILogger", NULL}, + {"extends", "implements", NULL}, + 2}, /* ── decorator + extends ─────────────────────────────────────── */ - { - CBM_LANG_TYPESCRIPT, "svc.ts", - "function Injectable() { return (c: any) => c; }\n" - "@Injectable()\nclass UserService extends BaseUserService { }", - "UserService", - {"BaseUserService", NULL}, - {"extends", "implements", NULL}, - 1 - }, + {CBM_LANG_TYPESCRIPT, + "svc.ts", + "function Injectable() { return (c: any) => c; }\n" + "@Injectable()\nclass UserService extends BaseUserService { }", + "UserService", + {"BaseUserService", NULL}, + {"extends", "implements", NULL}, + 1}, /* ── class implementing multiple inferred generics ───────────── */ - { - CBM_LANG_TYPESCRIPT, "svc.ts", - "class BinaryTree extends Tree implements Traversable, Serializable { " - "traverse() {} serialize() { return ''; } }", - "BinaryTree", - {"Tree", "Traversable", "Serializable", NULL}, - {"extends", "implements", NULL}, - 3 - }, + {CBM_LANG_TYPESCRIPT, + "svc.ts", + "class BinaryTree extends Tree implements Traversable, Serializable { " + "traverse() {} serialize() { return ''; } }", + "BinaryTree", + {"Tree", "Traversable", "Serializable", NULL}, + {"extends", "implements", NULL}, + 3}, /* ── React component extends ──────────────────────────────────── */ - { - CBM_LANG_TYPESCRIPT, "svc.ts", - "import React from 'react';\n" - "class MyComponent extends React.Component<{}, {}> { render() { return null; } }", - "MyComponent", - {"React.Component", NULL}, - {"extends", "implements", NULL}, - 1 - }, + {CBM_LANG_TYPESCRIPT, + "svc.ts", + "import React from 'react';\n" + "class MyComponent extends React.Component<{}, {}> { render() { return null; } }", + "MyComponent", + {"React.Component", NULL}, + {"extends", "implements", NULL}, + 1}, /* ── exception hierarchy ─────────────────────────────────────── */ - { - CBM_LANG_TYPESCRIPT, "svc.ts", - "class NetworkError extends AppError implements Retryable { retry() {} }", - "NetworkError", - {"AppError", "Retryable", NULL}, - {"extends", "implements", NULL}, - 2 - }, + {CBM_LANG_TYPESCRIPT, + "svc.ts", + "class NetworkError extends AppError implements Retryable { retry() {} }", + "NetworkError", + {"AppError", "Retryable", NULL}, + {"extends", "implements", NULL}, + 2}, }; TEST(inherit_typescript) { @@ -1109,104 +1033,94 @@ TEST(inherit_typescript) { static const inherit_case_t tsx_cases[] = { /* ── React.Component subclass ────────────────────────────────── */ - { - CBM_LANG_TSX, "App.tsx", - "import React from 'react';\n" - "class App extends React.Component<{}, {}> { render() { return
; } }", - "App", - {"React.Component", NULL}, - {"extends", "implements", NULL}, - 1 - }, + {CBM_LANG_TSX, + "App.tsx", + "import React from 'react';\n" + "class App extends React.Component<{}, {}> { render() { return
; } }", + "App", + {"React.Component", NULL}, + {"extends", "implements", NULL}, + 1}, /* ── PureComponent subclass ──────────────────────────────────── */ - { - CBM_LANG_TSX, "Widget.tsx", - "import { PureComponent } from 'react';\n" - "class Widget extends PureComponent { render() { return null; } }", - "Widget", - {"PureComponent", NULL}, - {"extends", "implements", NULL}, - 1 - }, + {CBM_LANG_TSX, + "Widget.tsx", + "import { PureComponent } from 'react';\n" + "class Widget extends PureComponent { render() { return null; } }", + "Widget", + {"PureComponent", NULL}, + {"extends", "implements", NULL}, + 1}, /* ── component + interface ───────────────────────────────────── */ - { - CBM_LANG_TSX, "Form.tsx", - "import { Component } from 'react';\n" - "class Form extends Component implements IForm { " - "render() { return
; } submit() {} }", - "Form", - {"Component", "IForm", NULL}, - {"extends", "implements", NULL}, - 2 - }, + {CBM_LANG_TSX, + "Form.tsx", + "import { Component } from 'react';\n" + "class Form extends Component implements IForm { " + "render() { return ; } submit() {} }", + "Form", + {"Component", "IForm", NULL}, + {"extends", "implements", NULL}, + 2}, /* ── abstract component ──────────────────────────────────────── */ - { - CBM_LANG_TSX, "Base.tsx", - "import { Component } from 'react';\n" - "abstract class BaseView extends Component { abstract renderContent(): JSX.Element; }", - "BaseView", - {"Component", NULL}, - {"extends", "implements", NULL}, - 1 - }, + {CBM_LANG_TSX, + "Base.tsx", + "import { Component } from 'react';\n" + "abstract class BaseView extends Component { abstract renderContent(): JSX.Element; }", + "BaseView", + {"Component", NULL}, + {"extends", "implements", NULL}, + 1}, /* ── non-React class in .tsx ─────────────────────────────────── */ - { - CBM_LANG_TSX, "store.tsx", - "class UserStore extends BaseStore implements IStore { load() {} }", - "UserStore", - {"BaseStore", "IStore", NULL}, - {"extends", "implements", NULL}, - 2 - }, + {CBM_LANG_TSX, + "store.tsx", + "class UserStore extends BaseStore implements IStore { load() {} }", + "UserStore", + {"BaseStore", "IStore", NULL}, + {"extends", "implements", NULL}, + 2}, /* ── generic component ───────────────────────────────────────── */ - { - CBM_LANG_TSX, "List.tsx", - "import { Component } from 'react';\n" - "class List extends Component<{ items: T[] }> { render() { return
    ; } }", - "List", - {"Component", NULL}, - {"extends", "implements", NULL}, - 1 - }, + {CBM_LANG_TSX, + "List.tsx", + "import { Component } from 'react';\n" + "class List extends Component<{ items: T[] }> { render() { return
      ; } }", + "List", + {"Component", NULL}, + {"extends", "implements", NULL}, + 1}, /* ── interface extends in tsx file ───────────────────────────── */ - { - CBM_LANG_TSX, "types.tsx", - "interface IAdvanced extends IBasic, IExtended { doAdvanced(): void; }", - "IAdvanced", - {"IBasic", "IExtended", NULL}, - {"extends", "implements", NULL}, - 2 - }, + {CBM_LANG_TSX, + "types.tsx", + "interface IAdvanced extends IBasic, IExtended { doAdvanced(): void; }", + "IAdvanced", + {"IBasic", "IExtended", NULL}, + {"extends", "implements", NULL}, + 2}, /* ── multiple interfaces + base ──────────────────────────────── */ - { - CBM_LANG_TSX, "Mega.tsx", - "class MegaComponent extends React.Component implements Serializable, Disposable { " - "render() { return null; } }", - "MegaComponent", - {"React.Component", "Serializable", "Disposable", NULL}, - {"extends", "implements", NULL}, - 3 - }, + {CBM_LANG_TSX, + "Mega.tsx", + "class MegaComponent extends React.Component implements Serializable, Disposable { " + "render() { return null; } }", + "MegaComponent", + {"React.Component", "Serializable", "Disposable", NULL}, + {"extends", "implements", NULL}, + 3}, /* ── error boundary component ────────────────────────────────── */ - { - CBM_LANG_TSX, "ErrorBoundary.tsx", - "import { Component, ReactNode, ErrorInfo } from 'react';\n" - "class ErrorBoundary extends Component<{ children: ReactNode }, { hasError: boolean }> { " - "componentDidCatch(err: Error, info: ErrorInfo) {} render() { return null; } }", - "ErrorBoundary", - {"Component", NULL}, - {"extends", "implements", NULL}, - 1 - }, + {CBM_LANG_TSX, + "ErrorBoundary.tsx", + "import { Component, ReactNode, ErrorInfo } from 'react';\n" + "class ErrorBoundary extends Component<{ children: ReactNode }, { hasError: boolean }> { " + "componentDidCatch(err: Error, info: ErrorInfo) {} render() { return null; } }", + "ErrorBoundary", + {"Component", NULL}, + {"extends", "implements", NULL}, + 1}, /* ── class extending custom hook class ───────────────────────── */ - { - CBM_LANG_TSX, "hook.tsx", - "class AdvancedHook extends BaseHook implements IHook { use() {} }", - "AdvancedHook", - {"BaseHook", "IHook", NULL}, - {"extends", "implements", NULL}, - 2 - }, + {CBM_LANG_TSX, + "hook.tsx", + "class AdvancedHook extends BaseHook implements IHook { use() {} }", + "AdvancedHook", + {"BaseHook", "IHook", NULL}, + {"extends", "implements", NULL}, + 2}, }; TEST(inherit_tsx) { @@ -1224,181 +1138,164 @@ TEST(inherit_tsx) { static const inherit_case_t php_cases[] = { /* ── extends single class ────────────────────────────────────── */ - { - CBM_LANG_PHP, "Svc.php", - "value; } }", - "Status", - {"HasLabel", NULL}, - {"implements", NULL}, - 1 - }, + {CBM_LANG_PHP, + "Svc.php", + "value; } }", + "Status", + {"HasLabel", NULL}, + {"implements", NULL}, + 1}, /* ── extends with constructor promotion ──────────────────────── */ - { - CBM_LANG_PHP, "Svc.php", - "\nclass Box> : Container()", - "Box", - {"Container", NULL}, - {":", NULL}, - 1 - }, + {CBM_LANG_KOTLIN, + "Svc.kt", + "open class Container\nclass Box> : Container()", + "Box", + {"Container", NULL}, + {":", NULL}, + 1}, /* ── companion object (no base — sanity check, no crash) ─────── */ - { - CBM_LANG_KOTLIN, "Svc.kt", - "class MyClass { companion object : Factory() { override fun create() = MyClass() } }", - "MyClass", - {NULL}, /* outer class has no base; we just want no crash */ - {NULL}, - 0 - }, + {CBM_LANG_KOTLIN, + "Svc.kt", + "class MyClass { companion object : Factory() { override fun create() = MyClass() } " + "}", + "MyClass", + {NULL}, /* outer class has no base; we just want no crash */ + {NULL}, + 0}, /* ── enum class implementing interface ───────────────────────── */ - { - CBM_LANG_KOTLIN, "Svc.kt", - "interface Displayable { fun display(): String }\n" - "enum class Status : Displayable { OPEN, CLOSED; override fun display() = name }", - "Status", - {"Displayable", NULL}, - {":", NULL}, - 1 - }, + {CBM_LANG_KOTLIN, + "Svc.kt", + "interface Displayable { fun display(): String }\n" + "enum class Status : Displayable { OPEN, CLOSED; override fun display() = name }", + "Status", + {"Displayable", NULL}, + {":", NULL}, + 1}, /* ── class extending Exception ───────────────────────────────── */ - { - CBM_LANG_KOTLIN, "Svc.kt", - "class AppException(message: String) : Exception(message)", - "AppException", - {"Exception", NULL}, - {":", NULL}, - 1 - }, + {CBM_LANG_KOTLIN, + "Svc.kt", + "class AppException(message: String) : Exception(message)", + "AppException", + {"Exception", NULL}, + {":", NULL}, + 1}, /* ── implements Comparable ───────────────────────────────────── */ - { - CBM_LANG_KOTLIN, "Svc.kt", - "class Version(val major: Int, val minor: Int) : Comparable { " - "override fun compareTo(other: Version) = compareValuesBy(this, other, { it.major }, { it.minor }) }", - "Version", - {"Comparable", NULL}, - {":", NULL}, - 1 - }, + {CBM_LANG_KOTLIN, + "Svc.kt", + "class Version(val major: Int, val minor: Int) : Comparable { " + "override fun compareTo(other: Version) = compareValuesBy(this, other, { it.major }, { " + "it.minor }) }", + "Version", + {"Comparable", NULL}, + {":", NULL}, + 1}, /* ── open class hierarchy ────────────────────────────────────── */ - { - CBM_LANG_KOTLIN, "Svc.kt", - "open class Vehicle(val speed: Int)\nopen class MotorVehicle(speed: Int) : Vehicle(speed)\n" - "class Car(speed: Int) : MotorVehicle(speed)", - "Car", - {"MotorVehicle", NULL}, - {":", NULL}, - 1 - }, + {CBM_LANG_KOTLIN, + "Svc.kt", + "open class Vehicle(val speed: Int)\nopen class MotorVehicle(speed: Int) : Vehicle(speed)\n" + "class Car(speed: Int) : MotorVehicle(speed)", + "Car", + {"MotorVehicle", NULL}, + {":", NULL}, + 1}, /* ── inner class with base ───────────────────────────────────── */ - { - CBM_LANG_KOTLIN, "Svc.kt", - "open class BaseNode\nclass Tree { inner class Node : BaseNode() { var value: Int = 0 } }", - "Node", - {"BaseNode", NULL}, - {":", NULL}, - 1 - }, + {CBM_LANG_KOTLIN, + "Svc.kt", + "open class BaseNode\nclass Tree { inner class Node : BaseNode() { var value: Int = 0 } }", + "Node", + {"BaseNode", NULL}, + {":", NULL}, + 1}, /* ── fun interface (SAM) ─────────────────────────────────────── */ - { - CBM_LANG_KOTLIN, "Svc.kt", - "fun interface Transformer { fun transform(input: String): String }\n" - "class UpperTransformer : Transformer { override fun transform(input: String) = input.uppercase() }", - "UpperTransformer", - {"Transformer", NULL}, - {":", NULL}, - 1 - }, + {CBM_LANG_KOTLIN, + "Svc.kt", + "fun interface Transformer { fun transform(input: String): String }\n" + "class UpperTransformer : Transformer { override fun transform(input: String) = " + "input.uppercase() }", + "UpperTransformer", + {"Transformer", NULL}, + {":", NULL}, + 1}, /* ── three bases (base + two interfaces) ─────────────────────── */ - { - CBM_LANG_KOTLIN, "Svc.kt", - "open class BaseWorker\ninterface Runnable { fun run() }\ninterface Stoppable { fun stop() }\n" - "class Worker : BaseWorker(), Runnable, Stoppable { override fun run() {} override fun stop() {} }", - "Worker", - {"BaseWorker", "Runnable", "Stoppable", NULL}, - {":", NULL}, - 3 - }, + {CBM_LANG_KOTLIN, + "Svc.kt", + "open class BaseWorker\ninterface Runnable { fun run() }\ninterface Stoppable { fun stop() }\n" + "class Worker : BaseWorker(), Runnable, Stoppable { override fun run() {} override fun stop() " + "{} }", + "Worker", + {"BaseWorker", "Runnable", "Stoppable", NULL}, + {":", NULL}, + 3}, /* ── annotation class with base ─────────────────────────────────*/ - { - CBM_LANG_KOTLIN, "Svc.kt", - "annotation class MyAnnotation\nclass AnnotatedClass : BaseClass() { @MyAnnotation fun doWork() {} }", - "AnnotatedClass", - {"BaseClass", NULL}, - {":", NULL}, - 1 - }, + {CBM_LANG_KOTLIN, + "Svc.kt", + "annotation class MyAnnotation\nclass AnnotatedClass : BaseClass() { @MyAnnotation fun " + "doWork() {} }", + "AnnotatedClass", + {"BaseClass", NULL}, + {":", NULL}, + 1}, /* ── value class (inline, Kotlin 1.5+) ──────────────────────── */ - { - CBM_LANG_KOTLIN, "Svc.kt", - "interface Printable { fun print() }\n" - "@JvmInline value class UserId(val id: String) : Printable { override fun print() = println(id) }", - "UserId", - {"Printable", NULL}, - {":", NULL}, - 1 - }, + {CBM_LANG_KOTLIN, + "Svc.kt", + "interface Printable { fun print() }\n" + "@JvmInline value class UserId(val id: String) : Printable { override fun print() = " + "println(id) }", + "UserId", + {"Printable", NULL}, + {":", NULL}, + 1}, /* ── class with type-projected bound ─────────────────────────── */ - { - CBM_LANG_KOTLIN, "Svc.kt", - "open class Repository\nclass UserRepository : Repository()", - "UserRepository", - {"Repository", NULL}, - {":", NULL}, - 1 - }, + {CBM_LANG_KOTLIN, + "Svc.kt", + "open class Repository\nclass UserRepository : Repository()", + "UserRepository", + {"Repository", NULL}, + {":", NULL}, + 1}, }; TEST(inherit_kotlin) { @@ -1625,8 +1510,8 @@ TEST(inherit_kotlin) { static int rust_has_impl(CBMFileResult *r, const char *trait_name, const char *struct_name) { for (int i = 0; i < r->impl_traits.count; i++) { CBMImplTrait *it = &r->impl_traits.items[i]; - if (it->trait_name && strcmp(it->trait_name, trait_name) == 0 && - it->struct_name && strcmp(it->struct_name, struct_name) == 0) + if (it->trait_name && strcmp(it->trait_name, trait_name) == 0 && it->struct_name && + strcmp(it->struct_name, struct_name) == 0) return 1; } return 0; @@ -1639,18 +1524,16 @@ typedef struct { } rust_impl_case_t; static int run_rust_impl(const rust_impl_case_t *tc) { - CBMFileResult *r = cbm_extract_file(tc->src, (int)strlen(tc->src), - CBM_LANG_RUST, "t", "lib.rs", + CBMFileResult *r = cbm_extract_file(tc->src, (int)strlen(tc->src), CBM_LANG_RUST, "t", "lib.rs", 0, NULL, NULL); if (!r) { - printf(" FAIL [impl %s for %s] extract returned NULL\n", - tc->trait_name, tc->struct_name); + printf(" FAIL [impl %s for %s] extract returned NULL\n", tc->trait_name, tc->struct_name); return 1; } int found = rust_has_impl(r, tc->trait_name, tc->struct_name); if (!found) { - printf(" FAIL [impl %s for %s] not found in impl_traits\n", - tc->trait_name, tc->struct_name); + printf(" FAIL [impl %s for %s] not found in impl_traits\n", tc->trait_name, + tc->struct_name); cbm_free_result(r); return 1; } @@ -1658,144 +1541,116 @@ static int run_rust_impl(const rust_impl_case_t *tc) { return 0; } -#define RUN_RUST_CASES(table) \ - do { \ - int _fail = 0; \ - int _n = (int)(sizeof(table) / sizeof(table[0])); \ - for (int _i = 0; _i < _n; _i++) { \ - if (run_rust_impl(&table[_i]) != 0) _fail++; \ - } \ - if (_fail > 0) { \ +#define RUN_RUST_CASES(table) \ + do { \ + int _fail = 0; \ + int _n = (int)(sizeof(table) / sizeof(table[0])); \ + for (int _i = 0; _i < _n; _i++) { \ + if (run_rust_impl(&table[_i]) != 0) \ + _fail++; \ + } \ + if (_fail > 0) { \ printf(" FAIL %d / %d Rust impl cases above failed\n", _fail, _n); \ - return 1; \ - } \ + return 1; \ + } \ } while (0) static const rust_impl_case_t rust_cases[] = { /* ── basic trait impl ───────────────────────────────────────── */ - { - "trait Greet { fn greet(&self) -> String; }\n" - "struct Dog;\nimpl Greet for Dog { fn greet(&self) -> String { \"Woof\".into() } }", - "Greet", "Dog" - }, + {"trait Greet { fn greet(&self) -> String; }\n" + "struct Dog;\nimpl Greet for Dog { fn greet(&self) -> String { \"Woof\".into() } }", + "Greet", "Dog"}, /* ── Display trait ───────────────────────────────────────────── */ - { - "use std::fmt;\nstruct Point { x: f64, y: f64 }\n" - "impl fmt::Display for Point { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, \"({},{})\", self.x, self.y) } }", - "fmt::Display", "Point" - }, + {"use std::fmt;\nstruct Point { x: f64, y: f64 }\n" + "impl fmt::Display for Point { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { " + "write!(f, \"({},{})\", self.x, self.y) } }", + "fmt::Display", "Point"}, /* ── Iterator trait ──────────────────────────────────────────── */ - { - "struct Counter { count: u32 }\n" - "impl Iterator for Counter { type Item = u32; fn next(&mut self) -> Option { if self.count < 5 { self.count += 1; Some(self.count) } else { None } } }", - "Iterator", "Counter" - }, + {"struct Counter { count: u32 }\n" + "impl Iterator for Counter { type Item = u32; fn next(&mut self) -> Option { if " + "self.count < 5 { self.count += 1; Some(self.count) } else { None } } }", + "Iterator", "Counter"}, /* ── From trait ──────────────────────────────────────────────── */ - { - "struct Meters(f64);\nstruct Feet(f64);\n" - "impl From for Meters { fn from(f: Feet) -> Self { Meters(f.0 * 0.3048) } }", - "From", "Meters" - }, + {"struct Meters(f64);\nstruct Feet(f64);\n" + "impl From for Meters { fn from(f: Feet) -> Self { Meters(f.0 * 0.3048) } }", + "From", "Meters"}, /* ── Default trait ───────────────────────────────────────────── */ - { - "struct Config { debug: bool, level: u32 }\n" - "impl Default for Config { fn default() -> Self { Config { debug: false, level: 0 } } }", - "Default", "Config" - }, + {"struct Config { debug: bool, level: u32 }\n" + "impl Default for Config { fn default() -> Self { Config { debug: false, level: 0 } } }", + "Default", "Config"}, /* ── PartialEq + Eq ──────────────────────────────────────────── */ - { - "struct Id(u64);\nimpl PartialEq for Id { fn eq(&self, other: &Self) -> bool { self.0 == other.0 } }\nimpl Eq for Id {}", - "PartialEq", "Id" - }, + {"struct Id(u64);\nimpl PartialEq for Id { fn eq(&self, other: &Self) -> bool { self.0 == " + "other.0 } }\nimpl Eq for Id {}", + "PartialEq", "Id"}, /* ── Clone + Copy ────────────────────────────────────────────── */ - { - "#[derive(Copy)]\nstruct Tag(u8);\nimpl Clone for Tag { fn clone(&self) -> Self { *self } }", - "Clone", "Tag" - }, + {"#[derive(Copy)]\nstruct Tag(u8);\nimpl Clone for Tag { fn clone(&self) -> Self { *self } }", + "Clone", "Tag"}, /* ── custom trait for generic struct ─────────────────────────── */ - { - "trait Serializable { fn serialize(&self) -> Vec; }\n" - "struct Buffer { data: Vec }\n" - "impl Serializable for Buffer { fn serialize(&self) -> Vec { vec![] } }", - "Serializable", "Buffer" - }, + {"trait Serializable { fn serialize(&self) -> Vec; }\n" + "struct Buffer { data: Vec }\n" + "impl Serializable for Buffer { fn serialize(&self) -> Vec { vec![] } }", + "Serializable", "Buffer"}, /* ── Drop trait ──────────────────────────────────────────────── */ - { - "struct Resource { name: String }\n" - "impl Drop for Resource { fn drop(&mut self) { println!(\"drop {}\", self.name); } }", - "Drop", "Resource" - }, + {"struct Resource { name: String }\n" + "impl Drop for Resource { fn drop(&mut self) { println!(\"drop {}\", self.name); } }", + "Drop", "Resource"}, /* ── crate-qualified trait ───────────────────────────────────── */ - { - "mod io { pub trait Write { fn write(&mut self, buf: &[u8]) -> usize; } }\n" - "struct Socket;\nimpl io::Write for Socket { fn write(&mut self, buf: &[u8]) -> usize { buf.len() } }", - "io::Write", "Socket" - }, + {"mod io { pub trait Write { fn write(&mut self, buf: &[u8]) -> usize; } }\n" + "struct Socket;\nimpl io::Write for Socket { fn write(&mut self, buf: &[u8]) -> usize { " + "buf.len() } }", + "io::Write", "Socket"}, /* ── trait impl with associated type ────────────────────────── */ - { - "trait Producer { type Item; fn produce(&self) -> Self::Item; }\n" - "struct Factory;\nimpl Producer for Factory { type Item = u32; fn produce(&self) -> u32 { 42 } }", - "Producer", "Factory" - }, + {"trait Producer { type Item; fn produce(&self) -> Self::Item; }\n" + "struct Factory;\nimpl Producer for Factory { type Item = u32; fn produce(&self) -> u32 { 42 " + "} }", + "Producer", "Factory"}, /* ── Error trait ─────────────────────────────────────────────── */ - { - "use std::fmt;\n" - "#[derive(Debug)]\nstruct AppError { msg: String }\n" - "impl fmt::Display for AppError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, \"{}\", self.msg) } }\n" - "impl std::error::Error for AppError {}", - "std::error::Error", "AppError" - }, + {"use std::fmt;\n" + "#[derive(Debug)]\nstruct AppError { msg: String }\n" + "impl fmt::Display for AppError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { " + "write!(f, \"{}\", self.msg) } }\n" + "impl std::error::Error for AppError {}", + "std::error::Error", "AppError"}, /* ── Ord trait for newtype ────────────────────────────────────── */ - { - "use std::cmp::Ordering;\n#[derive(Eq, PartialEq)]\nstruct Priority(u8);\n" - "impl PartialOrd for Priority { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } }\n" - "impl Ord for Priority { fn cmp(&self, other: &Self) -> Ordering { self.0.cmp(&other.0) } }", - "Ord", "Priority" - }, + {"use std::cmp::Ordering;\n#[derive(Eq, PartialEq)]\nstruct Priority(u8);\n" + "impl PartialOrd for Priority { fn partial_cmp(&self, other: &Self) -> Option { " + "Some(self.cmp(other)) } }\n" + "impl Ord for Priority { fn cmp(&self, other: &Self) -> Ordering { self.0.cmp(&other.0) } }", + "Ord", "Priority"}, /* ── Index trait ─────────────────────────────────────────────── */ - { - "use std::ops::Index;\nstruct Matrix { data: Vec, cols: usize }\n" - "impl Index<(usize, usize)> for Matrix { type Output = f64; fn index(&self, idx: (usize, usize)) -> &f64 { &self.data[idx.0 * self.cols + idx.1] } }", - "Index", "Matrix" - }, + {"use std::ops::Index;\nstruct Matrix { data: Vec, cols: usize }\n" + "impl Index<(usize, usize)> for Matrix { type Output = f64; fn index(&self, idx: (usize, " + "usize)) -> &f64 { &self.data[idx.0 * self.cols + idx.1] } }", + "Index", "Matrix"}, /* ── Add operator overload ───────────────────────────────────── */ - { - "use std::ops::Add;\n#[derive(Clone, Copy)]\nstruct Vec2 { x: f32, y: f32 }\n" - "impl Add for Vec2 { type Output = Vec2; fn add(self, rhs: Vec2) -> Vec2 { Vec2 { x: self.x + rhs.x, y: self.y + rhs.y } } }", - "Add", "Vec2" - }, + {"use std::ops::Add;\n#[derive(Clone, Copy)]\nstruct Vec2 { x: f32, y: f32 }\n" + "impl Add for Vec2 { type Output = Vec2; fn add(self, rhs: Vec2) -> Vec2 { Vec2 { x: self.x + " + "rhs.x, y: self.y + rhs.y } } }", + "Add", "Vec2"}, /* ── Send + Sync safety impls ─────────────────────────────────── */ - { - "struct SharedState { data: std::sync::Mutex> }\n" - "unsafe impl Send for SharedState {}\nunsafe impl Sync for SharedState {}", - "Send", "SharedState" - }, + {"struct SharedState { data: std::sync::Mutex> }\n" + "unsafe impl Send for SharedState {}\nunsafe impl Sync for SharedState {}", + "Send", "SharedState"}, /* ── Deref coercion ──────────────────────────────────────────── */ - { - "use std::ops::Deref;\nstruct Wrapper(T);\n" - "impl Deref for Wrapper { type Target = T; fn deref(&self) -> &T { &self.0 } }", - "Deref", "Wrapper" - }, + {"use std::ops::Deref;\nstruct Wrapper(T);\n" + "impl Deref for Wrapper { type Target = T; fn deref(&self) -> &T { &self.0 } }", + "Deref", "Wrapper"}, /* ── AsRef trait ─────────────────────────────────────────────── */ - { - "struct Path(String);\nimpl AsRef for Path { fn as_ref(&self) -> &str { &self.0 } }", - "AsRef", "Path" - }, + {"struct Path(String);\nimpl AsRef for Path { fn as_ref(&self) -> &str { &self.0 } }", + "AsRef", "Path"}, /* ── Hash trait ──────────────────────────────────────────────── */ - { - "use std::hash::{Hash, Hasher};\n#[derive(Eq, PartialEq)]\nstruct Key(String);\n" - "impl Hash for Key { fn hash(&self, state: &mut H) { self.0.hash(state); } }", - "Hash", "Key" - }, + {"use std::hash::{Hash, Hasher};\n#[derive(Eq, PartialEq)]\nstruct Key(String);\n" + "impl Hash for Key { fn hash(&self, state: &mut H) { self.0.hash(state); } }", + "Hash", "Key"}, /* ── serde Serialize / Deserialize (external crate trait) ────── */ - { - "trait Serialize { fn serialize(&self) -> String; }\n" - "trait Deserialize { fn deserialize(s: &str) -> Self; }\n" - "struct JsonRecord { id: u64 }\n" - "impl Serialize for JsonRecord { fn serialize(&self) -> String { format!(\"{{\\\"id\\\":{}}}\", self.id) } }\n" - "impl Deserialize for JsonRecord { fn deserialize(_s: &str) -> Self { JsonRecord { id: 0 } } }", - "Serialize", "JsonRecord" - }, + {"trait Serialize { fn serialize(&self) -> String; }\n" + "trait Deserialize { fn deserialize(s: &str) -> Self; }\n" + "struct JsonRecord { id: u64 }\n" + "impl Serialize for JsonRecord { fn serialize(&self) -> String { " + "format!(\"{{\\\"id\\\":{}}}\", self.id) } }\n" + "impl Deserialize for JsonRecord { fn deserialize(_s: &str) -> Self { JsonRecord { id: 0 } } " + "}", + "Serialize", "JsonRecord"}, }; TEST(inherit_rust_impls) { @@ -1815,9 +1670,10 @@ SUITE(extraction_inheritance) { RUN_TEST(inherit_rust_impls); /* Languages expected RED (broken extractors — reproduce-first) */ - RUN_TEST(inherit_python); /* RED: identifier-node not matched in collect_bases_from_field */ - RUN_TEST(inherit_typescript); /* RED: keyword "extends"/"implements" captured instead of type name */ - RUN_TEST(inherit_tsx); /* RED: same keyword-capture bug as TypeScript */ - RUN_TEST(inherit_php); /* RED: base_classes never populated */ - RUN_TEST(inherit_kotlin); /* RED: `:` supertype syntax not parsed */ + RUN_TEST(inherit_python); /* RED: identifier-node not matched in collect_bases_from_field */ + RUN_TEST( + inherit_typescript); /* RED: keyword "extends"/"implements" captured instead of type name */ + RUN_TEST(inherit_tsx); /* RED: same keyword-capture bug as TypeScript */ + RUN_TEST(inherit_php); /* RED: base_classes never populated */ + RUN_TEST(inherit_kotlin); /* RED: `:` supertype syntax not parsed */ } diff --git a/tests/test_grammar_labels.c b/tests/test_grammar_labels.c index e28fcdd72..1eb956f30 100644 --- a/tests/test_grammar_labels.c +++ b/tests/test_grammar_labels.c @@ -276,8 +276,9 @@ TEST(grammar_label_goldens) { } } if (missing > 0) { - fprintf(stderr, " [LABEL] %d grammar(s) missing a golden — copy the [LABEL-CAPTURE] lines " - "into LABEL_GOLDENS\n", + fprintf(stderr, + " [LABEL] %d grammar(s) missing a golden — copy the [LABEL-CAPTURE] lines " + "into LABEL_GOLDENS\n", missing); } ASSERT_EQ(failures, 0); @@ -305,9 +306,9 @@ static int non_module_defs(CBMFileResult *r) { * config / markup grammars (json/yaml/css/…) are intentionally excluded — a * lone Module node is correct for them. */ static const char *MUST_EXTRACT_DEFS[] = { - "agda", "pony", "move", "cobol", "janet", "pine", "smali", "verilog", - "vhdl", "systemverilog", "protobuf", "graphql", "thrift", "capnp", "smithy", - "wit", "prisma", "cmake", "puppet", "tablegen", "assembly", "nasm", NULL}; + "agda", "pony", "move", "cobol", "janet", "pine", "smali", "verilog", + "vhdl", "systemverilog", "protobuf", "graphql", "thrift", "capnp", "smithy", "wit", + "prisma", "cmake", "puppet", "tablegen", "assembly", "nasm", NULL}; TEST(grammar_code_extracts_defs) { int failures = 0; @@ -335,8 +336,9 @@ TEST(grammar_code_extracts_defs) { int nd = non_module_defs(r); cbm_free_result(r); if (nd < 1) { - fprintf(stderr, " [CODE-DEFS] FAIL %-14s extracts 0 non-Module defs (under-extraction " - "gap — only a Module node)\n", + fprintf(stderr, + " [CODE-DEFS] FAIL %-14s extracts 0 non-Module defs (under-extraction " + "gap — only a Module node)\n", name); failures++; } diff --git a/tests/test_grammar_probe_g.c b/tests/test_grammar_probe_g.c index 3ae0580c6..185ca4bac 100644 --- a/tests/test_grammar_probe_g.c +++ b/tests/test_grammar_probe_g.c @@ -64,39 +64,45 @@ typedef struct { } GpgProj; typedef struct { - const char *name; /* relative filename, may include '/' for subdirs */ + const char *name; /* relative filename, may include '/' for subdirs */ const char *content; } GpgFile; static void gpg_to_fwd_slashes(char *p) { for (; *p; p++) { - if (*p == '\\') *p = '/'; + if (*p == '\\') + *p = '/'; } } static cbm_store_t *gpg_open_indexed(GpgProj *lp) { lp->project = cbm_project_name_from_path(lp->tmpdir); - if (!lp->project) return NULL; + if (!lp->project) + return NULL; const char *home = getenv("HOME"); - if (!home) home = "/tmp"; + if (!home) + home = "/tmp"; char cache_dir[512]; snprintf(cache_dir, sizeof(cache_dir), "%s/.cache/codebase-memory-mcp", home); cbm_mkdir(cache_dir); snprintf(lp->dbpath, sizeof(lp->dbpath), "%s/%s.db", cache_dir, lp->project); unlink(lp->dbpath); lp->srv = cbm_mcp_server_new(NULL); - if (!lp->srv) return NULL; + if (!lp->srv) + return NULL; char args[700]; snprintf(args, sizeof(args), "{\"repo_path\":\"%s\"}", lp->tmpdir); char *resp = cbm_mcp_handle_tool(lp->srv, "index_repository", args); - if (resp) free(resp); + if (resp) + free(resp); return cbm_store_open_path(lp->dbpath); } static cbm_store_t *gpg_index_files(GpgProj *lp, const GpgFile *files, int nfiles) { memset(lp, 0, sizeof(*lp)); snprintf(lp->tmpdir, sizeof(lp->tmpdir), "/tmp/cbm_gpg_XXXXXX"); - if (!cbm_mkdtemp(lp->tmpdir)) return NULL; + if (!cbm_mkdtemp(lp->tmpdir)) + return NULL; gpg_to_fwd_slashes(lp->tmpdir); for (int i = 0; i < nfiles; i++) { char path[700]; @@ -108,7 +114,8 @@ static cbm_store_t *gpg_index_files(GpgProj *lp, const GpgFile *files, int nfile *slash = '/'; } FILE *f = fopen(path, "wb"); - if (!f) return NULL; + if (!f) + return NULL; fputs(files[i].content, f); fclose(f); } @@ -116,8 +123,12 @@ static cbm_store_t *gpg_index_files(GpgProj *lp, const GpgFile *files, int nfile } static void gpg_cleanup(GpgProj *lp, cbm_store_t *store) { - if (store) cbm_store_close(store); - if (lp->srv) { cbm_mcp_server_free(lp->srv); lp->srv = NULL; } + if (store) + cbm_store_close(store); + if (lp->srv) { + cbm_mcp_server_free(lp->srv); + lp->srv = NULL; + } free(lp->project); lp->project = NULL; th_rmtree(lp->tmpdir); @@ -147,8 +158,8 @@ typedef struct { int classes; int variables; int sections; - int imports; /* IMPORTS edges */ - int depends; /* DEPENDS_ON edges */ + int imports; /* IMPORTS edges */ + int depends; /* DEPENDS_ON edges */ } GpgMetrics; static GpgMetrics gpg_metrics_files(const GpgFile *files, int nfiles) { @@ -156,14 +167,14 @@ static GpgMetrics gpg_metrics_files(const GpgFile *files, int nfiles) { cbm_store_t *store = gpg_index_files(&lp, files, nfiles); GpgMetrics m = {0}; if (store) { - m.ok = 1; + m.ok = 1; m.total_nodes = cbm_store_count_nodes(store, lp.project); - m.modules = gpg_count_label(store, lp.project, "Module"); - m.classes = gpg_count_label(store, lp.project, "Class"); - m.variables = gpg_count_label(store, lp.project, "Variable"); - m.sections = gpg_count_label(store, lp.project, "Section"); - m.imports = cbm_store_count_edges_by_type(store, lp.project, "IMPORTS"); - m.depends = cbm_store_count_edges_by_type(store, lp.project, "DEPENDS_ON"); + m.modules = gpg_count_label(store, lp.project, "Module"); + m.classes = gpg_count_label(store, lp.project, "Class"); + m.variables = gpg_count_label(store, lp.project, "Variable"); + m.sections = gpg_count_label(store, lp.project, "Section"); + m.imports = cbm_store_count_edges_by_type(store, lp.project, "IMPORTS"); + m.depends = cbm_store_count_edges_by_type(store, lp.project, "DEPENDS_ON"); } gpg_cleanup(&lp, store); return m; @@ -184,10 +195,9 @@ static GpgMetrics gpg_metrics(const char *filename, const char *content) { /* HTML: minimal page → at least 1 Module node, no crash. */ TEST(probe_html_minimal_page) { - GpgMetrics m = gpg_metrics("index.html", - "\n" - "Test\n" - "

      Hello

      \n"); + GpgMetrics m = gpg_metrics("index.html", "\n" + "Test\n" + "

      Hello

      \n"); ASSERT_TRUE(m.ok); /* GREEN: HTML always produces at least a Module node. */ ASSERT_TRUE(m.modules >= 1); @@ -198,15 +208,11 @@ TEST(probe_html_minimal_page) { * RED: no HTML import resolver; script/link attributes are not walked into * IMPORTS edges by the pipeline. */ TEST(probe_html_script_imports) { - static const GpgFile files[] = { - {"app.js", - "function init() { console.log('ready'); }\n"}, - {"index.html", - "\n" - "\n" - "\n" - "\n"} - }; + static const GpgFile files[] = {{"app.js", "function init() { console.log('ready'); }\n"}, + {"index.html", "\n" + "\n" + "\n" + "\n"}}; GpgMetrics m = gpg_metrics_files(files, 2); ASSERT_TRUE(m.ok); /* RED: HTML