chore: import upstream snapshot with attribution
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,174 @@
|
||||
#ifndef CBM_LSP_C_LSP_H
|
||||
#define CBM_LSP_C_LSP_H
|
||||
|
||||
#include "type_rep.h"
|
||||
#include "scope.h"
|
||||
#include "type_registry.h"
|
||||
#include "../cbm.h"
|
||||
#include "go_lsp.h" // for CBMLSPDef, CBMResolvedCallArray
|
||||
|
||||
// CLSPContext holds state for C/C++ expression type evaluation within a file.
|
||||
typedef struct {
|
||||
CBMArena *arena;
|
||||
const char *source;
|
||||
int source_len;
|
||||
const CBMTypeRegistry *registry;
|
||||
CBMScope *current_scope;
|
||||
|
||||
// Include map: header_path -> namespace QN prefix
|
||||
const char **include_paths;
|
||||
const char **include_ns_qns;
|
||||
int include_count;
|
||||
|
||||
// Namespace state
|
||||
const char *current_namespace; // current namespace QN (e.g., "proj.ns1.ns2")
|
||||
|
||||
// Using namespace directives
|
||||
const char **using_namespaces;
|
||||
int using_ns_count;
|
||||
int using_ns_cap;
|
||||
|
||||
// Using declarations: specific names imported into scope
|
||||
const char **using_decl_names;
|
||||
const char **using_decl_qns;
|
||||
int using_decl_count;
|
||||
int using_decl_cap;
|
||||
|
||||
// Namespace aliases: short -> full QN
|
||||
const char **ns_alias_names;
|
||||
const char **ns_alias_qns;
|
||||
int ns_alias_count;
|
||||
int ns_alias_cap;
|
||||
|
||||
// Current context
|
||||
const char *enclosing_func_qn;
|
||||
const char *enclosing_class_qn; // for implicit `this` resolution
|
||||
const char *module_qn;
|
||||
size_t module_qn_len; // cached strlen(module_qn); for stack-buffer QN building
|
||||
|
||||
// Negative-lookup memo for c_lookup_member_depth (depth==0 misses only).
|
||||
// Open-addressing uint64 hash SET; 0 is the empty-slot sentinel. Populated
|
||||
// ONLY when the Tier-2 registry is shared+read-only (registry_shared), where
|
||||
// the module-prefix/base-class/short-name cascades are pure, stable functions
|
||||
// of (type_qn, registry) — so a recorded miss can never turn into a hit. Lets
|
||||
// the hot resolve path skip the sprintf("%s.%s") strlen storm + the O(type_count)
|
||||
// short-name scan on repeated misses of the same (type_qn, member). malloc-owned;
|
||||
// freed at end of c_lsp_process_file.
|
||||
uint64_t *neg_memo;
|
||||
int neg_memo_cap; // power-of-two; 0 until first insert
|
||||
int neg_memo_count; // live entries (grow by rehash at 70% load)
|
||||
|
||||
// Output
|
||||
CBMResolvedCallArray *resolved_calls;
|
||||
|
||||
// Function pointer targets: var_name -> target function QN
|
||||
const char **fp_var_names;
|
||||
const char **fp_target_qns;
|
||||
int fp_count;
|
||||
int fp_cap;
|
||||
|
||||
// Template parameter defaults for current template scope
|
||||
const char **template_param_names; // e.g., ["T", "U"]
|
||||
const CBMType **template_param_defaults; // e.g., [int_type, NULL]
|
||||
int template_param_count;
|
||||
|
||||
// Pending template calls: member calls on TYPE_PARAM inside template functions.
|
||||
// At call sites with known arg types, these are resolved retroactively.
|
||||
struct {
|
||||
const char *func_qn; // enclosing template function
|
||||
const char *type_param; // e.g., "T"
|
||||
const char *method_name; // e.g., "draw"
|
||||
int arg_count;
|
||||
} *pending_template_calls;
|
||||
int pending_tc_count;
|
||||
int pending_tc_cap;
|
||||
|
||||
// Flags
|
||||
bool cpp_mode; // C++ features enabled
|
||||
bool in_template; // currently inside template declaration
|
||||
bool registry_shared; // ctx->registry is the Tier-2 cross registry, shared
|
||||
// READ-ONLY across resolve workers — never mutate it
|
||||
// (and never store per-worker arena pointers into it)
|
||||
bool debug;
|
||||
int eval_depth; // recursion depth for c_eval_expr_type (crash guard)
|
||||
int eval_steps; // total expression eval calls for current file (hang guard)
|
||||
int walk_depth; // c_resolve_calls_in_node self-recursion (AST nesting)
|
||||
} CLSPContext;
|
||||
|
||||
// --- API ---
|
||||
|
||||
void c_lsp_init(CLSPContext *ctx, CBMArena *arena, const char *source, int source_len,
|
||||
const CBMTypeRegistry *registry, const char *module_qn, bool cpp_mode,
|
||||
CBMResolvedCallArray *out);
|
||||
|
||||
void c_lsp_add_include(CLSPContext *ctx, const char *header_path, const char *ns_qn);
|
||||
|
||||
void c_lsp_process_file(CLSPContext *ctx, TSNode root);
|
||||
|
||||
const CBMType *c_eval_expr_type(CLSPContext *ctx, TSNode node);
|
||||
const CBMType *c_parse_type_node(CLSPContext *ctx, TSNode node);
|
||||
void c_process_statement(CLSPContext *ctx, TSNode node);
|
||||
|
||||
// Look up a member (method/field) on a type, traversing base classes.
|
||||
const CBMRegisteredFunc *c_lookup_member(CLSPContext *ctx, const char *type_qn,
|
||||
const char *member_name);
|
||||
|
||||
// Type simplification: unwrap refs, aliases, pointers (like clangd simplifyType).
|
||||
const CBMType *c_simplify_type(CLSPContext *ctx, const CBMType *t, bool unwrap_pointer);
|
||||
|
||||
// --- Entry points ---
|
||||
|
||||
// Single-file LSP: build registry from file defs + stdlib, run resolution.
|
||||
void cbm_run_c_lsp(CBMArena *arena, CBMFileResult *result, const char *source, int source_len,
|
||||
TSNode root, bool cpp_mode);
|
||||
|
||||
// Cross-file LSP: build registry from defs + stdlib, re-parse and resolve.
|
||||
void cbm_run_c_lsp_cross(CBMArena *arena, const char *source, int source_len, const char *module_qn,
|
||||
bool cpp_mode, CBMLSPDef *defs, int def_count, const char **include_paths,
|
||||
const char **include_ns_qns, int include_count,
|
||||
TSTree *cached_tree, // NULL = parse internally
|
||||
CBMResolvedCallArray *out);
|
||||
|
||||
// Tier 2: build a project-wide C/C++/CUDA registry ONCE from all defs
|
||||
// (filters by lang). Shared READ-ONLY across resolve workers. Def-driven
|
||||
// (no AST field collection) → identical entries to the per-file build.
|
||||
CBMTypeRegistry *cbm_c_build_cross_registry(CBMArena *arena, CBMLSPDef *defs, int def_count);
|
||||
|
||||
// Cross-file LSP using a pre-built shared registry (Tier 2). Skips the
|
||||
// per-file registry build; just parse + resolve.
|
||||
void cbm_run_c_lsp_cross_with_registry(CBMArena *arena, const char *source, int source_len,
|
||||
const char *module_qn, bool cpp_mode,
|
||||
CBMTypeRegistry *reg, // pre-built, finalized, READ-ONLY
|
||||
const char **include_paths, const char **include_ns_qns,
|
||||
int include_count,
|
||||
TSTree *cached_tree, // NULL = parse internally
|
||||
CBMResolvedCallArray *out);
|
||||
|
||||
// Register C stdlib types and functions into a registry.
|
||||
void cbm_c_stdlib_register(CBMTypeRegistry *reg, CBMArena *arena);
|
||||
|
||||
// Register C++ stdlib types and functions into a registry.
|
||||
void cbm_cpp_stdlib_register(CBMTypeRegistry *reg, CBMArena *arena);
|
||||
|
||||
// --- Batch cross-file LSP ---
|
||||
|
||||
// Per-file input for batch C/C++ LSP processing.
|
||||
typedef struct {
|
||||
const char *source;
|
||||
int source_len;
|
||||
const char *module_qn;
|
||||
bool cpp_mode;
|
||||
TSTree *cached_tree; // from TSTree caching (NULL = parse internally)
|
||||
CBMLSPDef *defs; // combined file-local + cross-file defs
|
||||
int def_count;
|
||||
const char **include_paths; // parallel arrays, include_count long
|
||||
const char **include_ns_qns;
|
||||
int include_count;
|
||||
} CBMBatchCLSPFile;
|
||||
|
||||
// Process multiple C/C++ files' cross-file LSP in one CGo call.
|
||||
// out must point to file_count pre-zeroed CBMResolvedCallArray structs.
|
||||
void cbm_batch_c_lsp_cross(CBMArena *arena, CBMBatchCLSPFile *files, int file_count,
|
||||
CBMResolvedCallArray *out);
|
||||
|
||||
#endif // CBM_LSP_C_LSP_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,179 @@
|
||||
#ifndef CBM_LSP_CS_LSP_H
|
||||
#define CBM_LSP_CS_LSP_H
|
||||
|
||||
#include "type_rep.h"
|
||||
#include "scope.h"
|
||||
#include "type_registry.h"
|
||||
#include "../cbm.h"
|
||||
#include "go_lsp.h" /* CBMLSPDef, CBMResolvedCall, CBMResolvedCallArray are reused */
|
||||
|
||||
/*
|
||||
* cs_lsp — C# Light Semantic Pass.
|
||||
*
|
||||
* Reverse-engineered from Roslyn's Binder pipeline (src/Compilers/CSharp/
|
||||
* Portable/Binder/Binder_*.cs). Mirrors the structure of go_lsp / c_lsp /
|
||||
* php_lsp / py_lsp so the shared pipeline (lsp_resolve.h) treats every
|
||||
* language identically.
|
||||
*
|
||||
* Coverage targets (≥90% parity vs Roslyn for typical user code):
|
||||
* - using / using static / using alias / global using
|
||||
* - file-scoped + block namespaces; nested namespaces
|
||||
* - classes, structs, records, interfaces, enums
|
||||
* - inheritance + interface implementation; partial classes
|
||||
* - methods (instance, static, generic, async, extension `this`)
|
||||
* - properties (auto, expression-bodied, full); indexers
|
||||
* - constructors + primary constructors (records / C# 12 classes)
|
||||
* - object creation `new T(...)` / target-typed `new(...)`
|
||||
* - var + explicit local types; foreach element inference
|
||||
* - tuples (literal + parameter)
|
||||
* - lambdas + delegate calls
|
||||
* - await: Task<T> → T, ValueTask<T> → T
|
||||
* - this / base / `base.Method()` calls
|
||||
* - cast `(T)x`, `x as T`, pattern `x is T y`
|
||||
* - null-conditional `?.`, null-coalescing `??`
|
||||
* - generic instantiation + type-parameter substitution
|
||||
* - extension method dispatch (`obj.Foo()` -> static Foo(this T self, ...))
|
||||
*
|
||||
* Out-of-scope (intentional):
|
||||
* - flow analysis-driven nullable narrowing
|
||||
* - LINQ query syntax (handled as method-syntax via `Select`/`Where` lookup)
|
||||
* - dynamic / reflection
|
||||
* - source generators / Roslyn analyzers
|
||||
*/
|
||||
|
||||
/* CSAlias / CSUsing — per-file using state.
|
||||
*
|
||||
* `using Foo.Bar;` -> namespace import (kind = NAMESPACE)
|
||||
* `using static Foo.Bar;` -> static-member import (kind = STATIC)
|
||||
* `using F = Foo.Bar;` -> alias (kind = ALIAS, local_name = "F")
|
||||
* `global using ...;` -> kept identical, marked is_global (rare in
|
||||
* a single file but handled for ASP.NET-style
|
||||
* Program.cs files).
|
||||
*/
|
||||
typedef enum {
|
||||
CBM_CS_USING_NAMESPACE = 0,
|
||||
CBM_CS_USING_STATIC,
|
||||
CBM_CS_USING_ALIAS,
|
||||
} CBMCSUsingKind;
|
||||
|
||||
typedef struct {
|
||||
CBMCSUsingKind kind;
|
||||
const char *local_name; /* alias name; "" for non-alias */
|
||||
const char *target_qn; /* dotted target QN */
|
||||
bool is_global; /* `global using` */
|
||||
} CBMCSUsing;
|
||||
|
||||
/* CSLSPContext — per-file type-evaluation state. */
|
||||
typedef struct {
|
||||
CBMArena *arena;
|
||||
const char *source;
|
||||
int source_len;
|
||||
const CBMTypeRegistry *registry;
|
||||
CBMScope *current_scope;
|
||||
|
||||
/* Namespace stack (innermost first). C# allows nested + file-scoped. */
|
||||
const char **namespace_stack;
|
||||
int namespace_count;
|
||||
int namespace_cap;
|
||||
|
||||
/* Active using directives in the current file. C# resolves bare names
|
||||
* by walking the namespace stack outward, then searching using
|
||||
* directives. */
|
||||
CBMCSUsing *usings;
|
||||
int using_count;
|
||||
int using_cap;
|
||||
|
||||
/* Enclosing class / struct / record / interface — the "type" body
|
||||
* we're currently inside. NULL outside type body. */
|
||||
const char *enclosing_class_qn;
|
||||
const char *enclosing_base_qn; /* base class QN; NULL if none */
|
||||
const char **enclosing_iface_qns; /* NULL-terminated; NULL if none */
|
||||
|
||||
/* Enclosing function/method/lambda. */
|
||||
const char *enclosing_func_qn;
|
||||
|
||||
/* Module QN for this file (matches what the unified extractor records). */
|
||||
const char *module_qn;
|
||||
|
||||
/* Output: resolved calls accumulate here. */
|
||||
CBMResolvedCallArray *resolved_calls;
|
||||
|
||||
/* Active type-parameter substitution map (for generic methods/types).
|
||||
* Parallel arrays. NULL-terminated. */
|
||||
const char **type_param_names;
|
||||
const CBMType **type_param_args;
|
||||
int type_param_count;
|
||||
|
||||
/* Recursion guard for cs_eval_expr_type. */
|
||||
int eval_depth;
|
||||
|
||||
/* Debug mode (CBM_LSP_DEBUG env). */
|
||||
bool debug;
|
||||
} CSLSPContext;
|
||||
|
||||
/* Initialize a CSLSPContext for processing one file. */
|
||||
void cs_lsp_init(CSLSPContext *ctx, CBMArena *arena, const char *source, int source_len,
|
||||
const CBMTypeRegistry *registry, const char *module_qn,
|
||||
CBMResolvedCallArray *out);
|
||||
|
||||
/* Append a using directive. local_name may be NULL/empty for non-alias kinds. */
|
||||
void cs_lsp_add_using(CSLSPContext *ctx, CBMCSUsingKind kind, const char *local_name,
|
||||
const char *target_qn, bool is_global);
|
||||
|
||||
/* Process a file's AST. */
|
||||
void cs_lsp_process_file(CSLSPContext *ctx, TSNode root);
|
||||
|
||||
/* Evaluate the type of an expression. Never returns NULL — falls back to
|
||||
* cbm_type_unknown(). */
|
||||
const CBMType *cs_eval_expr_type(CSLSPContext *ctx, TSNode node);
|
||||
|
||||
/* Convert a C# type-AST node to a CBMType. */
|
||||
const CBMType *cs_parse_type_node(CSLSPContext *ctx, TSNode node);
|
||||
|
||||
/* Resolve a bare or dotted type name against namespace stack + using map.
|
||||
* Returns dotted QN or NULL. */
|
||||
const char *cs_resolve_type_name(CSLSPContext *ctx, const char *name);
|
||||
|
||||
/* Look up a method on a type, walking base + interface chains. */
|
||||
const CBMRegisteredFunc *cs_lookup_method(CSLSPContext *ctx, const char *type_qn,
|
||||
const char *method_name);
|
||||
|
||||
/* Single-file entry: build registry from file defs + stdlib, run resolution. */
|
||||
void cbm_run_cs_lsp(CBMArena *arena, CBMFileResult *result, const char *source, int source_len,
|
||||
TSNode root);
|
||||
|
||||
/* Cross-file entry. Like Go/C: caller supplies pre-resolved defs from siblings. */
|
||||
void cbm_run_cs_lsp_cross(CBMArena *arena, const char *source, int source_len,
|
||||
const char *module_qn, CBMLSPDef *defs, int def_count,
|
||||
const char **using_targets, int using_count,
|
||||
TSTree *cached_tree, CBMResolvedCallArray *out);
|
||||
|
||||
/* Tier 2: build a project-wide C# registry ONCE from all defs (filters
|
||||
* by lang), shared READ-ONLY across resolve workers. Def-driven. */
|
||||
CBMTypeRegistry *cbm_cs_build_cross_registry(CBMArena *arena, CBMLSPDef *defs, int def_count);
|
||||
|
||||
/* Cross-file resolve using a pre-built shared registry (Tier 2). */
|
||||
void cbm_run_cs_lsp_cross_with_registry(CBMArena *arena, const char *source, int source_len,
|
||||
const char *module_qn, CBMTypeRegistry *reg,
|
||||
const char **using_targets, int using_count,
|
||||
TSTree *cached_tree, CBMResolvedCallArray *out);
|
||||
|
||||
/* Batch cross-file entry for one CGo call from the parallel pipeline. */
|
||||
typedef struct {
|
||||
const char *source;
|
||||
int source_len;
|
||||
const char *module_qn;
|
||||
TSTree *cached_tree;
|
||||
CBMLSPDef *defs;
|
||||
int def_count;
|
||||
const char **using_targets;
|
||||
int using_count;
|
||||
} CBMBatchCSLSPFile;
|
||||
|
||||
void cbm_batch_cs_lsp_cross(CBMArena *arena, CBMBatchCSLSPFile *files, int file_count,
|
||||
CBMResolvedCallArray *out);
|
||||
|
||||
/* Register .NET BCL stdlib types and functions. Generated. */
|
||||
void cbm_csharp_stdlib_register(CBMTypeRegistry *reg, CBMArena *arena);
|
||||
|
||||
#endif /* CBM_LSP_CS_LSP_H */
|
||||
@@ -0,0 +1,132 @@
|
||||
// C stdlib type information for C/C++ LSP type resolver.
|
||||
|
||||
#include "../type_rep.h"
|
||||
#include "../type_registry.h"
|
||||
#include <string.h>
|
||||
|
||||
// Helper macros for concise registration
|
||||
#define REG_FUNC(qn, short, ret_type) do { \
|
||||
memset(&rf, 0, sizeof(rf)); \
|
||||
rf.min_params = -1; \
|
||||
rf.qualified_name = (qn); \
|
||||
rf.short_name = (short); \
|
||||
rf.signature = cbm_type_func(arena, NULL, NULL, (const CBMType*[]){(ret_type), NULL}); \
|
||||
cbm_registry_add_func(reg, rf); \
|
||||
} while(0)
|
||||
|
||||
#define REG_TYPE(qn, short) do { \
|
||||
memset(&rt, 0, sizeof(rt)); \
|
||||
rt.qualified_name = (qn); \
|
||||
rt.short_name = (short); \
|
||||
cbm_registry_add_type(reg, rt); \
|
||||
} while(0)
|
||||
|
||||
void cbm_c_stdlib_register(CBMTypeRegistry* reg, CBMArena* arena) {
|
||||
CBMRegisteredFunc rf;
|
||||
CBMRegisteredType rt;
|
||||
|
||||
const CBMType* t_int = cbm_type_builtin(arena, "int");
|
||||
const CBMType* t_size_t = cbm_type_builtin(arena, "size_t");
|
||||
const CBMType* t_double = cbm_type_builtin(arena, "double");
|
||||
const CBMType* t_void = cbm_type_builtin(arena, "void");
|
||||
const CBMType* t_char_ptr = cbm_type_pointer(arena, cbm_type_builtin(arena, "char"));
|
||||
const CBMType* t_void_ptr = cbm_type_pointer(arena, t_void);
|
||||
|
||||
// FILE type
|
||||
REG_TYPE("FILE", "FILE");
|
||||
const CBMType* t_file_ptr = cbm_type_pointer(arena, cbm_type_named(arena, "FILE"));
|
||||
|
||||
// stdio.h
|
||||
REG_FUNC("fopen", "fopen", t_file_ptr);
|
||||
REG_FUNC("fclose", "fclose", t_int);
|
||||
REG_FUNC("fprintf", "fprintf", t_int);
|
||||
REG_FUNC("fread", "fread", t_size_t);
|
||||
REG_FUNC("fwrite", "fwrite", t_size_t);
|
||||
REG_FUNC("printf", "printf", t_int);
|
||||
REG_FUNC("scanf", "scanf", t_int);
|
||||
REG_FUNC("fgets", "fgets", t_char_ptr);
|
||||
REG_FUNC("fputs", "fputs", t_int);
|
||||
REG_FUNC("fseek", "fseek", t_int);
|
||||
REG_FUNC("ftell", "ftell", cbm_type_builtin(arena, "long"));
|
||||
REG_FUNC("rewind", "rewind", t_void);
|
||||
REG_FUNC("feof", "feof", t_int);
|
||||
REG_FUNC("ferror", "ferror", t_int);
|
||||
REG_FUNC("snprintf", "snprintf", t_int);
|
||||
REG_FUNC("sprintf", "sprintf", t_int);
|
||||
REG_FUNC("sscanf", "sscanf", t_int);
|
||||
REG_FUNC("getchar", "getchar", t_int);
|
||||
REG_FUNC("putchar", "putchar", t_int);
|
||||
REG_FUNC("puts", "puts", t_int);
|
||||
REG_FUNC("perror", "perror", t_void);
|
||||
|
||||
// stdlib.h
|
||||
REG_FUNC("malloc", "malloc", t_void_ptr);
|
||||
REG_FUNC("calloc", "calloc", t_void_ptr);
|
||||
REG_FUNC("realloc", "realloc", t_void_ptr);
|
||||
REG_FUNC("free", "free", t_void);
|
||||
REG_FUNC("atoi", "atoi", t_int);
|
||||
REG_FUNC("atof", "atof", t_double);
|
||||
REG_FUNC("atol", "atol", cbm_type_builtin(arena, "long"));
|
||||
REG_FUNC("strtol", "strtol", cbm_type_builtin(arena, "long"));
|
||||
REG_FUNC("strtod", "strtod", t_double);
|
||||
REG_FUNC("exit", "exit", t_void);
|
||||
REG_FUNC("abort", "abort", t_void);
|
||||
REG_FUNC("abs", "abs", t_int);
|
||||
REG_FUNC("rand", "rand", t_int);
|
||||
REG_FUNC("srand", "srand", t_void);
|
||||
REG_FUNC("qsort", "qsort", t_void);
|
||||
REG_FUNC("bsearch", "bsearch", t_void_ptr);
|
||||
REG_FUNC("getenv", "getenv", t_char_ptr);
|
||||
REG_FUNC("system", "system", t_int);
|
||||
|
||||
// string.h
|
||||
REG_FUNC("strlen", "strlen", t_size_t);
|
||||
REG_FUNC("strcmp", "strcmp", t_int);
|
||||
REG_FUNC("strncmp", "strncmp", t_int);
|
||||
REG_FUNC("strcpy", "strcpy", t_char_ptr);
|
||||
REG_FUNC("strncpy", "strncpy", t_char_ptr);
|
||||
REG_FUNC("strcat", "strcat", t_char_ptr);
|
||||
REG_FUNC("strncat", "strncat", t_char_ptr);
|
||||
REG_FUNC("strchr", "strchr", t_char_ptr);
|
||||
REG_FUNC("strrchr", "strrchr", t_char_ptr);
|
||||
REG_FUNC("strstr", "strstr", t_char_ptr);
|
||||
REG_FUNC("strtok", "strtok", t_char_ptr);
|
||||
REG_FUNC("memcpy", "memcpy", t_void_ptr);
|
||||
REG_FUNC("memmove", "memmove", t_void_ptr);
|
||||
REG_FUNC("memset", "memset", t_void_ptr);
|
||||
REG_FUNC("memcmp", "memcmp", t_int);
|
||||
|
||||
// ctype.h
|
||||
REG_FUNC("isalpha", "isalpha", t_int);
|
||||
REG_FUNC("isdigit", "isdigit", t_int);
|
||||
REG_FUNC("isalnum", "isalnum", t_int);
|
||||
REG_FUNC("isspace", "isspace", t_int);
|
||||
REG_FUNC("isupper", "isupper", t_int);
|
||||
REG_FUNC("islower", "islower", t_int);
|
||||
REG_FUNC("toupper", "toupper", t_int);
|
||||
REG_FUNC("tolower", "tolower", t_int);
|
||||
|
||||
// math.h
|
||||
REG_FUNC("sqrt", "sqrt", t_double);
|
||||
REG_FUNC("pow", "pow", t_double);
|
||||
REG_FUNC("sin", "sin", t_double);
|
||||
REG_FUNC("cos", "cos", t_double);
|
||||
REG_FUNC("tan", "tan", t_double);
|
||||
REG_FUNC("exp", "exp", t_double);
|
||||
REG_FUNC("log", "log", t_double);
|
||||
REG_FUNC("log10", "log10", t_double);
|
||||
REG_FUNC("fabs", "fabs", t_double);
|
||||
REG_FUNC("ceil", "ceil", t_double);
|
||||
REG_FUNC("floor", "floor", t_double);
|
||||
REG_FUNC("round", "round", t_double);
|
||||
|
||||
// assert.h / signal.h / time.h basics
|
||||
REG_FUNC("assert", "assert", t_void);
|
||||
REG_FUNC("signal", "signal", t_void);
|
||||
REG_FUNC("time", "time", cbm_type_builtin(arena, "time_t"));
|
||||
REG_FUNC("clock", "clock", cbm_type_builtin(arena, "clock_t"));
|
||||
REG_FUNC("difftime", "difftime", t_double);
|
||||
}
|
||||
|
||||
#undef REG_FUNC
|
||||
#undef REG_TYPE
|
||||
@@ -0,0 +1,947 @@
|
||||
// C++ stdlib type information for C/C++ LSP type resolver.
|
||||
|
||||
#include "../type_rep.h"
|
||||
#include "../type_registry.h"
|
||||
#include <string.h>
|
||||
|
||||
// Helper: register a method on a type
|
||||
static void reg_method(CBMTypeRegistry* reg, CBMArena* arena,
|
||||
const char* recv_qn, const char* method_name, const CBMType* ret_type) {
|
||||
CBMRegisteredFunc rf;
|
||||
memset(&rf, 0, sizeof(rf));
|
||||
rf.min_params = -1;
|
||||
// Build QN: recv_qn.method_name
|
||||
size_t rlen = strlen(recv_qn);
|
||||
size_t mlen = strlen(method_name);
|
||||
char* qn = (char*)cbm_arena_alloc(arena, rlen + 1 + mlen + 1);
|
||||
if (!qn) return;
|
||||
memcpy(qn, recv_qn, rlen);
|
||||
qn[rlen] = '.';
|
||||
memcpy(qn + rlen + 1, method_name, mlen);
|
||||
qn[rlen + 1 + mlen] = '\0';
|
||||
|
||||
rf.qualified_name = qn;
|
||||
rf.short_name = method_name;
|
||||
rf.receiver_type = recv_qn;
|
||||
rf.signature = cbm_type_func(arena, NULL, NULL,
|
||||
ret_type ? (const CBMType*[]){ret_type, NULL} : NULL);
|
||||
cbm_registry_add_func(reg, rf);
|
||||
}
|
||||
|
||||
// Helper: register a type
|
||||
static void reg_type(CBMTypeRegistry* reg, const char* qn, const char* short_name) {
|
||||
CBMRegisteredType rt;
|
||||
memset(&rt, 0, sizeof(rt));
|
||||
rt.qualified_name = qn;
|
||||
rt.short_name = short_name;
|
||||
cbm_registry_add_type(reg, rt);
|
||||
}
|
||||
|
||||
// Helper: register a type with field names
|
||||
static void reg_type_with_fields(CBMTypeRegistry* reg, CBMArena* arena,
|
||||
const char* qn, const char* short_name,
|
||||
const char** fnames, const CBMType** ftypes) {
|
||||
CBMRegisteredType rt;
|
||||
memset(&rt, 0, sizeof(rt));
|
||||
rt.qualified_name = qn;
|
||||
rt.short_name = short_name;
|
||||
rt.field_names = fnames;
|
||||
rt.field_types = ftypes;
|
||||
cbm_registry_add_type(reg, rt);
|
||||
}
|
||||
|
||||
// Helper: register a free function
|
||||
static void reg_func(CBMTypeRegistry* reg, CBMArena* arena,
|
||||
const char* qn, const char* short_name, const CBMType* ret_type) {
|
||||
CBMRegisteredFunc rf;
|
||||
memset(&rf, 0, sizeof(rf));
|
||||
rf.min_params = -1;
|
||||
rf.qualified_name = qn;
|
||||
rf.short_name = short_name;
|
||||
rf.signature = cbm_type_func(arena, NULL, NULL,
|
||||
ret_type ? (const CBMType*[]){ret_type, NULL} : NULL);
|
||||
cbm_registry_add_func(reg, rf);
|
||||
}
|
||||
|
||||
void cbm_cpp_stdlib_register(CBMTypeRegistry* reg, CBMArena* arena) {
|
||||
const CBMType* t_void = cbm_type_builtin(arena, "void");
|
||||
const CBMType* t_void_ptr = cbm_type_pointer(arena, t_void);
|
||||
const CBMType* t_bool = cbm_type_builtin(arena, "bool");
|
||||
const CBMType* t_int = cbm_type_builtin(arena, "int");
|
||||
const CBMType* t_size_t = cbm_type_builtin(arena, "size_t");
|
||||
const CBMType* t_long = cbm_type_builtin(arena, "long");
|
||||
const CBMType* t_char = cbm_type_builtin(arena, "char");
|
||||
const CBMType* t_char_ptr = cbm_type_pointer(arena, cbm_type_builtin(arena, "const char"));
|
||||
|
||||
// =========================================================================
|
||||
// std.string
|
||||
// =========================================================================
|
||||
const char* string_qn = "std.string";
|
||||
reg_type(reg, string_qn, "string");
|
||||
const CBMType* t_string = cbm_type_named(arena, string_qn);
|
||||
const CBMType* t_string_ref = cbm_type_reference(arena, t_string);
|
||||
|
||||
reg_method(reg, arena, string_qn, "c_str", t_char_ptr);
|
||||
reg_method(reg, arena, string_qn, "data", t_char_ptr);
|
||||
reg_method(reg, arena, string_qn, "size", t_size_t);
|
||||
reg_method(reg, arena, string_qn, "length", t_size_t);
|
||||
reg_method(reg, arena, string_qn, "capacity", t_size_t);
|
||||
reg_method(reg, arena, string_qn, "max_size", t_size_t);
|
||||
reg_method(reg, arena, string_qn, "empty", t_bool);
|
||||
reg_method(reg, arena, string_qn, "clear", t_void);
|
||||
reg_method(reg, arena, string_qn, "substr", t_string);
|
||||
reg_method(reg, arena, string_qn, "find", t_size_t);
|
||||
reg_method(reg, arena, string_qn, "rfind", t_size_t);
|
||||
reg_method(reg, arena, string_qn, "compare", t_int);
|
||||
reg_method(reg, arena, string_qn, "append", t_string_ref);
|
||||
reg_method(reg, arena, string_qn, "insert", t_string_ref);
|
||||
reg_method(reg, arena, string_qn, "erase", t_string_ref);
|
||||
reg_method(reg, arena, string_qn, "replace", t_string_ref);
|
||||
reg_method(reg, arena, string_qn, "push_back", t_void);
|
||||
reg_method(reg, arena, string_qn, "pop_back", t_void);
|
||||
reg_method(reg, arena, string_qn, "front", cbm_type_reference(arena, t_char));
|
||||
reg_method(reg, arena, string_qn, "back", cbm_type_reference(arena, t_char));
|
||||
reg_method(reg, arena, string_qn, "at", cbm_type_reference(arena, t_char));
|
||||
reg_method(reg, arena, string_qn, "operator[]", cbm_type_reference(arena, t_char));
|
||||
reg_method(reg, arena, string_qn, "begin", cbm_type_named(arena, "std.string.iterator"));
|
||||
reg_method(reg, arena, string_qn, "end", cbm_type_named(arena, "std.string.iterator"));
|
||||
reg_method(reg, arena, string_qn, "resize", t_void);
|
||||
reg_method(reg, arena, string_qn, "reserve", t_void);
|
||||
reg_method(reg, arena, string_qn, "starts_with", t_bool);
|
||||
reg_method(reg, arena, string_qn, "ends_with", t_bool);
|
||||
reg_method(reg, arena, string_qn, "contains", t_bool);
|
||||
reg_method(reg, arena, string_qn, "operator+", t_string);
|
||||
reg_method(reg, arena, string_qn, "operator+=", t_string_ref);
|
||||
// Also register as std.basic_string alias
|
||||
{
|
||||
CBMRegisteredType rt;
|
||||
memset(&rt, 0, sizeof(rt));
|
||||
rt.qualified_name = "std.basic_string";
|
||||
rt.short_name = "basic_string";
|
||||
rt.alias_of = string_qn;
|
||||
cbm_registry_add_type(reg, rt);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// std.string_view
|
||||
// =========================================================================
|
||||
const char* sv_qn = "std.string_view";
|
||||
reg_type(reg, sv_qn, "string_view");
|
||||
reg_method(reg, arena, sv_qn, "data", t_char_ptr);
|
||||
reg_method(reg, arena, sv_qn, "size", t_size_t);
|
||||
reg_method(reg, arena, sv_qn, "length", t_size_t);
|
||||
reg_method(reg, arena, sv_qn, "empty", t_bool);
|
||||
reg_method(reg, arena, sv_qn, "substr", cbm_type_named(arena, sv_qn));
|
||||
reg_method(reg, arena, sv_qn, "find", t_size_t);
|
||||
reg_method(reg, arena, sv_qn, "starts_with", t_bool);
|
||||
reg_method(reg, arena, sv_qn, "ends_with", t_bool);
|
||||
reg_method(reg, arena, sv_qn, "operator[]", t_char);
|
||||
|
||||
// =========================================================================
|
||||
// std.vector<T> — T is first template arg
|
||||
// =========================================================================
|
||||
const char* vec_qn = "std.vector";
|
||||
reg_type(reg, vec_qn, "vector");
|
||||
const CBMType* t_T = cbm_type_type_param(arena, "T");
|
||||
const CBMType* t_T_ref = cbm_type_reference(arena, t_T);
|
||||
const CBMType* t_T_ptr = cbm_type_pointer(arena, t_T);
|
||||
|
||||
reg_method(reg, arena, vec_qn, "push_back", t_void);
|
||||
reg_method(reg, arena, vec_qn, "emplace_back", t_void);
|
||||
reg_method(reg, arena, vec_qn, "pop_back", t_void);
|
||||
reg_method(reg, arena, vec_qn, "size", t_size_t);
|
||||
reg_method(reg, arena, vec_qn, "capacity", t_size_t);
|
||||
reg_method(reg, arena, vec_qn, "empty", t_bool);
|
||||
reg_method(reg, arena, vec_qn, "clear", t_void);
|
||||
reg_method(reg, arena, vec_qn, "resize", t_void);
|
||||
reg_method(reg, arena, vec_qn, "reserve", t_void);
|
||||
reg_method(reg, arena, vec_qn, "shrink_to_fit", t_void);
|
||||
reg_method(reg, arena, vec_qn, "front", t_T_ref);
|
||||
reg_method(reg, arena, vec_qn, "back", t_T_ref);
|
||||
reg_method(reg, arena, vec_qn, "at", t_T_ref);
|
||||
reg_method(reg, arena, vec_qn, "data", t_T_ptr);
|
||||
reg_method(reg, arena, vec_qn, "operator[]", t_T_ref);
|
||||
reg_method(reg, arena, vec_qn, "begin", cbm_type_named(arena, "std.vector.iterator"));
|
||||
reg_method(reg, arena, vec_qn, "end", cbm_type_named(arena, "std.vector.iterator"));
|
||||
reg_method(reg, arena, vec_qn, "erase", cbm_type_named(arena, "std.vector.iterator"));
|
||||
reg_method(reg, arena, vec_qn, "insert", cbm_type_named(arena, "std.vector.iterator"));
|
||||
|
||||
// =========================================================================
|
||||
// std.map<K,V>
|
||||
// =========================================================================
|
||||
const char* map_qn = "std.map";
|
||||
reg_type(reg, map_qn, "map");
|
||||
const CBMType* t_V = cbm_type_type_param(arena, "V");
|
||||
const CBMType* t_V_ref = cbm_type_reference(arena, t_V);
|
||||
|
||||
reg_method(reg, arena, map_qn, "operator[]", t_V_ref);
|
||||
reg_method(reg, arena, map_qn, "at", t_V_ref);
|
||||
reg_method(reg, arena, map_qn, "find", cbm_type_named(arena, "std.map.iterator"));
|
||||
reg_method(reg, arena, map_qn, "insert", cbm_type_named(arena, "std.pair"));
|
||||
reg_method(reg, arena, map_qn, "emplace", cbm_type_named(arena, "std.pair"));
|
||||
reg_method(reg, arena, map_qn, "erase", t_size_t);
|
||||
reg_method(reg, arena, map_qn, "count", t_size_t);
|
||||
reg_method(reg, arena, map_qn, "contains", t_bool);
|
||||
reg_method(reg, arena, map_qn, "size", t_size_t);
|
||||
reg_method(reg, arena, map_qn, "empty", t_bool);
|
||||
reg_method(reg, arena, map_qn, "clear", t_void);
|
||||
reg_method(reg, arena, map_qn, "begin", cbm_type_named(arena, "std.map.iterator"));
|
||||
reg_method(reg, arena, map_qn, "end", cbm_type_named(arena, "std.map.iterator"));
|
||||
|
||||
// std.unordered_map<K,V> — same interface
|
||||
const char* umap_qn = "std.unordered_map";
|
||||
reg_type(reg, umap_qn, "unordered_map");
|
||||
reg_method(reg, arena, umap_qn, "operator[]", t_V_ref);
|
||||
reg_method(reg, arena, umap_qn, "at", t_V_ref);
|
||||
reg_method(reg, arena, umap_qn, "find", cbm_type_named(arena, "std.unordered_map.iterator"));
|
||||
reg_method(reg, arena, umap_qn, "insert", cbm_type_named(arena, "std.pair"));
|
||||
reg_method(reg, arena, umap_qn, "emplace", cbm_type_named(arena, "std.pair"));
|
||||
reg_method(reg, arena, umap_qn, "erase", t_size_t);
|
||||
reg_method(reg, arena, umap_qn, "count", t_size_t);
|
||||
reg_method(reg, arena, umap_qn, "contains", t_bool);
|
||||
reg_method(reg, arena, umap_qn, "size", t_size_t);
|
||||
reg_method(reg, arena, umap_qn, "empty", t_bool);
|
||||
reg_method(reg, arena, umap_qn, "clear", t_void);
|
||||
reg_method(reg, arena, umap_qn, "begin", cbm_type_named(arena, "std.unordered_map.iterator"));
|
||||
reg_method(reg, arena, umap_qn, "end", cbm_type_named(arena, "std.unordered_map.iterator"));
|
||||
|
||||
// =========================================================================
|
||||
// std.set<T> / std.unordered_set<T>
|
||||
// =========================================================================
|
||||
const char* set_qn = "std.set";
|
||||
reg_type(reg, set_qn, "set");
|
||||
reg_method(reg, arena, set_qn, "insert", cbm_type_named(arena, "std.pair"));
|
||||
reg_method(reg, arena, set_qn, "find", cbm_type_named(arena, "std.set.iterator"));
|
||||
reg_method(reg, arena, set_qn, "erase", t_size_t);
|
||||
reg_method(reg, arena, set_qn, "count", t_size_t);
|
||||
reg_method(reg, arena, set_qn, "contains", t_bool);
|
||||
reg_method(reg, arena, set_qn, "size", t_size_t);
|
||||
reg_method(reg, arena, set_qn, "empty", t_bool);
|
||||
reg_method(reg, arena, set_qn, "clear", t_void);
|
||||
reg_method(reg, arena, set_qn, "begin", cbm_type_named(arena, "std.set.iterator"));
|
||||
reg_method(reg, arena, set_qn, "end", cbm_type_named(arena, "std.set.iterator"));
|
||||
|
||||
const char* uset_qn = "std.unordered_set";
|
||||
reg_type(reg, uset_qn, "unordered_set");
|
||||
reg_method(reg, arena, uset_qn, "insert", cbm_type_named(arena, "std.pair"));
|
||||
reg_method(reg, arena, uset_qn, "find", cbm_type_named(arena, "std.unordered_set.iterator"));
|
||||
reg_method(reg, arena, uset_qn, "erase", t_size_t);
|
||||
reg_method(reg, arena, uset_qn, "count", t_size_t);
|
||||
reg_method(reg, arena, uset_qn, "contains", t_bool);
|
||||
reg_method(reg, arena, uset_qn, "size", t_size_t);
|
||||
reg_method(reg, arena, uset_qn, "empty", t_bool);
|
||||
reg_method(reg, arena, uset_qn, "clear", t_void);
|
||||
|
||||
// =========================================================================
|
||||
// Smart pointers: std.unique_ptr<T>, std.shared_ptr<T>, std.weak_ptr<T>
|
||||
// =========================================================================
|
||||
const char* uptr_qn = "std.unique_ptr";
|
||||
reg_type(reg, uptr_qn, "unique_ptr");
|
||||
reg_method(reg, arena, uptr_qn, "get", t_T_ptr);
|
||||
reg_method(reg, arena, uptr_qn, "reset", t_void);
|
||||
reg_method(reg, arena, uptr_qn, "release", t_T_ptr);
|
||||
reg_method(reg, arena, uptr_qn, "operator->", t_T_ptr);
|
||||
reg_method(reg, arena, uptr_qn, "operator*", t_T_ref);
|
||||
reg_method(reg, arena, uptr_qn, "operator bool", t_bool);
|
||||
reg_method(reg, arena, uptr_qn, "swap", t_void);
|
||||
|
||||
const char* sptr_qn = "std.shared_ptr";
|
||||
reg_type(reg, sptr_qn, "shared_ptr");
|
||||
reg_method(reg, arena, sptr_qn, "get", t_T_ptr);
|
||||
reg_method(reg, arena, sptr_qn, "reset", t_void);
|
||||
reg_method(reg, arena, sptr_qn, "use_count", t_long);
|
||||
reg_method(reg, arena, sptr_qn, "unique", t_bool);
|
||||
reg_method(reg, arena, sptr_qn, "operator->", t_T_ptr);
|
||||
reg_method(reg, arena, sptr_qn, "operator*", t_T_ref);
|
||||
reg_method(reg, arena, sptr_qn, "operator bool", t_bool);
|
||||
reg_method(reg, arena, sptr_qn, "swap", t_void);
|
||||
|
||||
const char* wptr_qn = "std.weak_ptr";
|
||||
reg_type(reg, wptr_qn, "weak_ptr");
|
||||
reg_method(reg, arena, wptr_qn, "lock", cbm_type_named(arena, sptr_qn));
|
||||
reg_method(reg, arena, wptr_qn, "use_count", t_long);
|
||||
reg_method(reg, arena, wptr_qn, "expired", t_bool);
|
||||
reg_method(reg, arena, wptr_qn, "reset", t_void);
|
||||
|
||||
// std.make_unique / std.make_shared — free functions
|
||||
// Return type depends on template arg, registered as returning unique_ptr/shared_ptr
|
||||
reg_func(reg, arena, "std.make_unique", "make_unique",
|
||||
cbm_type_named(arena, uptr_qn));
|
||||
reg_func(reg, arena, "std.make_shared", "make_shared",
|
||||
cbm_type_named(arena, sptr_qn));
|
||||
|
||||
// =========================================================================
|
||||
// std.optional<T>
|
||||
// =========================================================================
|
||||
const char* opt_qn = "std.optional";
|
||||
reg_type(reg, opt_qn, "optional");
|
||||
reg_method(reg, arena, opt_qn, "value", t_T_ref);
|
||||
reg_method(reg, arena, opt_qn, "has_value", t_bool);
|
||||
reg_method(reg, arena, opt_qn, "value_or", t_T);
|
||||
reg_method(reg, arena, opt_qn, "operator*", t_T_ref);
|
||||
reg_method(reg, arena, opt_qn, "operator->", t_T_ptr);
|
||||
reg_method(reg, arena, opt_qn, "operator bool", t_bool);
|
||||
reg_method(reg, arena, opt_qn, "reset", t_void);
|
||||
reg_method(reg, arena, opt_qn, "emplace", t_T_ref);
|
||||
|
||||
// =========================================================================
|
||||
// std.pair<T1, T2>
|
||||
// =========================================================================
|
||||
const char* pair_qn = "std.pair";
|
||||
{
|
||||
static const char* pair_field_names[] = {"first", "second", NULL};
|
||||
const CBMType** pair_field_types = (const CBMType**)cbm_arena_alloc(arena,
|
||||
3 * sizeof(const CBMType*));
|
||||
pair_field_types[0] = cbm_type_type_param(arena, "T1");
|
||||
pair_field_types[1] = cbm_type_type_param(arena, "T2");
|
||||
pair_field_types[2] = NULL;
|
||||
reg_type_with_fields(reg, arena, pair_qn, "pair", pair_field_names, pair_field_types);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// std.tuple<...>
|
||||
// =========================================================================
|
||||
reg_type(reg, "std.tuple", "tuple");
|
||||
reg_func(reg, arena, "std.get", "get", cbm_type_unknown());
|
||||
|
||||
// =========================================================================
|
||||
// std.array<T, N>
|
||||
// =========================================================================
|
||||
const char* arr_qn = "std.array";
|
||||
reg_type(reg, arr_qn, "array");
|
||||
reg_method(reg, arena, arr_qn, "size", t_size_t);
|
||||
reg_method(reg, arena, arr_qn, "data", t_T_ptr);
|
||||
reg_method(reg, arena, arr_qn, "at", t_T_ref);
|
||||
reg_method(reg, arena, arr_qn, "front", t_T_ref);
|
||||
reg_method(reg, arena, arr_qn, "back", t_T_ref);
|
||||
reg_method(reg, arena, arr_qn, "operator[]", t_T_ref);
|
||||
reg_method(reg, arena, arr_qn, "begin", t_T_ptr);
|
||||
reg_method(reg, arena, arr_qn, "end", t_T_ptr);
|
||||
reg_method(reg, arena, arr_qn, "empty", t_bool);
|
||||
reg_method(reg, arena, arr_qn, "fill", t_void);
|
||||
|
||||
// =========================================================================
|
||||
// std.function<F>
|
||||
// =========================================================================
|
||||
reg_type(reg, "std.function", "function");
|
||||
reg_method(reg, arena, "std.function", "operator()", cbm_type_unknown());
|
||||
reg_method(reg, arena, "std.function", "operator bool", t_bool);
|
||||
reg_method(reg, arena, "std.function", "target", t_void_ptr);
|
||||
|
||||
// =========================================================================
|
||||
// I/O streams
|
||||
// =========================================================================
|
||||
const char* os_qn = "std.ostream";
|
||||
reg_type(reg, os_qn, "ostream");
|
||||
const CBMType* t_ostream_ref = cbm_type_reference(arena, cbm_type_named(arena, os_qn));
|
||||
reg_method(reg, arena, os_qn, "operator<<", t_ostream_ref);
|
||||
reg_method(reg, arena, os_qn, "write", t_ostream_ref);
|
||||
reg_method(reg, arena, os_qn, "flush", t_ostream_ref);
|
||||
reg_method(reg, arena, os_qn, "put", t_ostream_ref);
|
||||
reg_method(reg, arena, os_qn, "good", t_bool);
|
||||
reg_method(reg, arena, os_qn, "fail", t_bool);
|
||||
reg_method(reg, arena, os_qn, "bad", t_bool);
|
||||
reg_method(reg, arena, os_qn, "eof", t_bool);
|
||||
|
||||
const char* is_qn = "std.istream";
|
||||
reg_type(reg, is_qn, "istream");
|
||||
const CBMType* t_istream_ref = cbm_type_reference(arena, cbm_type_named(arena, is_qn));
|
||||
reg_method(reg, arena, is_qn, "operator>>", t_istream_ref);
|
||||
reg_method(reg, arena, is_qn, "read", t_istream_ref);
|
||||
reg_method(reg, arena, is_qn, "getline", t_istream_ref);
|
||||
reg_method(reg, arena, is_qn, "get", t_int);
|
||||
reg_method(reg, arena, is_qn, "peek", t_int);
|
||||
reg_method(reg, arena, is_qn, "good", t_bool);
|
||||
reg_method(reg, arena, is_qn, "fail", t_bool);
|
||||
|
||||
// Global stream objects
|
||||
reg_func(reg, arena, "std.cout", "cout", cbm_type_named(arena, os_qn));
|
||||
reg_func(reg, arena, "std.cin", "cin", cbm_type_named(arena, is_qn));
|
||||
reg_func(reg, arena, "std.cerr", "cerr", cbm_type_named(arena, os_qn));
|
||||
reg_func(reg, arena, "std.clog", "clog", cbm_type_named(arena, os_qn));
|
||||
|
||||
// std.endl, std.flush — manipulators (treat as returning ostream ref)
|
||||
reg_func(reg, arena, "std.endl", "endl", t_ostream_ref);
|
||||
reg_func(reg, arena, "std.flush", "flush", t_ostream_ref);
|
||||
|
||||
// =========================================================================
|
||||
// std.filesystem.path
|
||||
// =========================================================================
|
||||
const char* fspath_qn = "std.filesystem.path";
|
||||
reg_type(reg, fspath_qn, "path");
|
||||
const CBMType* t_path = cbm_type_named(arena, fspath_qn);
|
||||
reg_method(reg, arena, fspath_qn, "string", t_string);
|
||||
reg_method(reg, arena, fspath_qn, "c_str", t_char_ptr);
|
||||
reg_method(reg, arena, fspath_qn, "extension", t_path);
|
||||
reg_method(reg, arena, fspath_qn, "filename", t_path);
|
||||
reg_method(reg, arena, fspath_qn, "parent_path", t_path);
|
||||
reg_method(reg, arena, fspath_qn, "stem", t_path);
|
||||
reg_method(reg, arena, fspath_qn, "root_path", t_path);
|
||||
reg_method(reg, arena, fspath_qn, "is_absolute", t_bool);
|
||||
reg_method(reg, arena, fspath_qn, "is_relative", t_bool);
|
||||
reg_method(reg, arena, fspath_qn, "empty", t_bool);
|
||||
reg_method(reg, arena, fspath_qn, "exists", t_bool);
|
||||
reg_method(reg, arena, fspath_qn, "operator/", t_path);
|
||||
reg_method(reg, arena, fspath_qn, "operator/=", cbm_type_reference(arena, t_path));
|
||||
|
||||
// =========================================================================
|
||||
// std.thread
|
||||
// =========================================================================
|
||||
const char* thread_qn = "std.thread";
|
||||
reg_type(reg, thread_qn, "thread");
|
||||
reg_method(reg, arena, thread_qn, "join", t_void);
|
||||
reg_method(reg, arena, thread_qn, "detach", t_void);
|
||||
reg_method(reg, arena, thread_qn, "joinable", t_bool);
|
||||
reg_method(reg, arena, thread_qn, "get_id", cbm_type_named(arena, "std.thread.id"));
|
||||
|
||||
// =========================================================================
|
||||
// std.mutex / std.lock_guard / std.unique_lock
|
||||
// =========================================================================
|
||||
const char* mutex_qn = "std.mutex";
|
||||
reg_type(reg, mutex_qn, "mutex");
|
||||
reg_method(reg, arena, mutex_qn, "lock", t_void);
|
||||
reg_method(reg, arena, mutex_qn, "unlock", t_void);
|
||||
reg_method(reg, arena, mutex_qn, "try_lock", t_bool);
|
||||
|
||||
reg_type(reg, "std.lock_guard", "lock_guard");
|
||||
reg_type(reg, "std.unique_lock", "unique_lock");
|
||||
reg_method(reg, arena, "std.unique_lock", "lock", t_void);
|
||||
reg_method(reg, arena, "std.unique_lock", "unlock", t_void);
|
||||
reg_method(reg, arena, "std.unique_lock", "try_lock", t_bool);
|
||||
reg_method(reg, arena, "std.unique_lock", "owns_lock", t_bool);
|
||||
|
||||
// =========================================================================
|
||||
// std.chrono basics
|
||||
// =========================================================================
|
||||
reg_type(reg, "std.chrono.seconds", "seconds");
|
||||
reg_type(reg, "std.chrono.milliseconds", "milliseconds");
|
||||
reg_type(reg, "std.chrono.microseconds", "microseconds");
|
||||
reg_type(reg, "std.chrono.nanoseconds", "nanoseconds");
|
||||
reg_type(reg, "std.chrono.system_clock", "system_clock");
|
||||
reg_type(reg, "std.chrono.steady_clock", "steady_clock");
|
||||
reg_func(reg, arena, "std.chrono.system_clock.now", "now",
|
||||
cbm_type_named(arena, "std.chrono.time_point"));
|
||||
reg_func(reg, arena, "std.chrono.steady_clock.now", "now",
|
||||
cbm_type_named(arena, "std.chrono.time_point"));
|
||||
|
||||
// =========================================================================
|
||||
// std.algorithm free functions
|
||||
// =========================================================================
|
||||
reg_func(reg, arena, "std.sort", "sort", t_void);
|
||||
reg_func(reg, arena, "std.find", "find", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.find_if", "find_if", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.for_each", "for_each", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.transform", "transform", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.copy", "copy", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.move", "move", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.swap", "swap", t_void);
|
||||
reg_func(reg, arena, "std.min", "min", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.max", "max", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.accumulate", "accumulate", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.count", "count", t_size_t);
|
||||
reg_func(reg, arena, "std.count_if", "count_if", t_size_t);
|
||||
reg_func(reg, arena, "std.remove", "remove", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.remove_if", "remove_if", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.reverse", "reverse", t_void);
|
||||
reg_func(reg, arena, "std.unique", "unique", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.lower_bound", "lower_bound", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.upper_bound", "upper_bound", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.binary_search", "binary_search", t_bool);
|
||||
|
||||
// std.to_string
|
||||
reg_func(reg, arena, "std.to_string", "to_string", t_string);
|
||||
reg_func(reg, arena, "std.stoi", "stoi", t_int);
|
||||
reg_func(reg, arena, "std.stol", "stol", t_long);
|
||||
reg_func(reg, arena, "std.stof", "stof", cbm_type_builtin(arena, "float"));
|
||||
reg_func(reg, arena, "std.stod", "stod", cbm_type_builtin(arena, "double"));
|
||||
|
||||
// =========================================================================
|
||||
// =========================================================================
|
||||
// std.variant<T...>
|
||||
// =========================================================================
|
||||
reg_type(reg, "std.variant", "variant");
|
||||
reg_method(reg, arena, "std.variant", "index", t_size_t);
|
||||
reg_method(reg, arena, "std.variant", "valueless_by_exception", t_bool);
|
||||
reg_func(reg, arena, "std.holds_alternative", "holds_alternative", t_bool);
|
||||
reg_func(reg, arena, "std.get_if", "get_if", t_T_ptr);
|
||||
|
||||
// =========================================================================
|
||||
// std.any
|
||||
// =========================================================================
|
||||
reg_type(reg, "std.any", "any");
|
||||
reg_method(reg, arena, "std.any", "has_value", t_bool);
|
||||
reg_method(reg, arena, "std.any", "type", cbm_type_unknown()); // type_info
|
||||
reg_method(reg, arena, "std.any", "reset", t_void);
|
||||
reg_func(reg, arena, "std.any_cast", "any_cast", t_T);
|
||||
reg_func(reg, arena, "std.make_any", "make_any", cbm_type_named(arena, "std.any"));
|
||||
|
||||
// =========================================================================
|
||||
// std.regex / std.smatch
|
||||
// =========================================================================
|
||||
reg_type(reg, "std.regex", "regex");
|
||||
reg_type(reg, "std.smatch", "smatch");
|
||||
reg_method(reg, arena, "std.smatch", "size", t_size_t);
|
||||
reg_method(reg, arena, "std.smatch", "empty", t_bool);
|
||||
reg_method(reg, arena, "std.smatch", "str", t_string);
|
||||
reg_method(reg, arena, "std.smatch", "prefix", cbm_type_unknown());
|
||||
reg_method(reg, arena, "std.smatch", "suffix", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.regex_search", "regex_search", t_bool);
|
||||
reg_func(reg, arena, "std.regex_match", "regex_match", t_bool);
|
||||
reg_func(reg, arena, "std.regex_replace", "regex_replace", t_string);
|
||||
|
||||
// =========================================================================
|
||||
// std.pair<K,V>
|
||||
// =========================================================================
|
||||
reg_type(reg, "std.pair", "pair");
|
||||
reg_func(reg, arena, "std.make_pair", "make_pair",
|
||||
cbm_type_named(arena, "std.pair"));
|
||||
|
||||
// =========================================================================
|
||||
// <numeric> algorithms
|
||||
// =========================================================================
|
||||
reg_func(reg, arena, "std.accumulate", "accumulate", t_T);
|
||||
reg_func(reg, arena, "std.inner_product", "inner_product", t_T);
|
||||
reg_func(reg, arena, "std.iota", "iota", t_void);
|
||||
reg_func(reg, arena, "std.partial_sum", "partial_sum", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.adjacent_difference", "adjacent_difference", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.reduce", "reduce", t_T);
|
||||
reg_func(reg, arena, "std.transform_reduce", "transform_reduce", t_T);
|
||||
|
||||
// =========================================================================
|
||||
// <iterator> functions
|
||||
// =========================================================================
|
||||
reg_func(reg, arena, "std.advance", "advance", t_void);
|
||||
reg_func(reg, arena, "std.distance", "distance", cbm_type_builtin(arena, "ptrdiff_t"));
|
||||
reg_func(reg, arena, "std.next", "next", cbm_type_unknown()); // iterator
|
||||
reg_func(reg, arena, "std.prev", "prev", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.begin", "begin", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.end", "end", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.rbegin", "rbegin", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.rend", "rend", cbm_type_unknown());
|
||||
|
||||
// =========================================================================
|
||||
// <algorithm> — additional entries with better return types
|
||||
// =========================================================================
|
||||
reg_func(reg, arena, "std.for_each", "for_each", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.all_of", "all_of", t_bool);
|
||||
reg_func(reg, arena, "std.any_of", "any_of", t_bool);
|
||||
reg_func(reg, arena, "std.none_of", "none_of", t_bool);
|
||||
reg_func(reg, arena, "std.min", "min", t_T_ref);
|
||||
reg_func(reg, arena, "std.max", "max", t_T_ref);
|
||||
reg_func(reg, arena, "std.min_element", "min_element", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.max_element", "max_element", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.minmax", "minmax",
|
||||
cbm_type_named(arena, "std.pair"));
|
||||
reg_func(reg, arena, "std.clamp", "clamp", t_T_ref);
|
||||
reg_func(reg, arena, "std.swap", "swap", t_void);
|
||||
reg_func(reg, arena, "std.fill", "fill", t_void);
|
||||
reg_func(reg, arena, "std.replace", "replace", t_void);
|
||||
reg_func(reg, arena, "std.replace_if", "replace_if", t_void);
|
||||
reg_func(reg, arena, "std.equal", "equal", t_bool);
|
||||
reg_func(reg, arena, "std.mismatch", "mismatch",
|
||||
cbm_type_named(arena, "std.pair"));
|
||||
reg_func(reg, arena, "std.lexicographical_compare", "lexicographical_compare", t_bool);
|
||||
reg_func(reg, arena, "std.partition", "partition", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.stable_partition", "stable_partition", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.nth_element", "nth_element", t_void);
|
||||
reg_func(reg, arena, "std.partial_sort", "partial_sort", t_void);
|
||||
reg_func(reg, arena, "std.stable_sort", "stable_sort", t_void);
|
||||
reg_func(reg, arena, "std.is_sorted", "is_sorted", t_bool);
|
||||
reg_func(reg, arena, "std.merge", "merge", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.includes", "includes", t_bool);
|
||||
reg_func(reg, arena, "std.set_union", "set_union", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.set_intersection", "set_intersection", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.set_difference", "set_difference", cbm_type_unknown());
|
||||
reg_func(reg, arena, "std.generate", "generate", t_void);
|
||||
reg_func(reg, arena, "std.generate_n", "generate_n", cbm_type_unknown());
|
||||
|
||||
// =========================================================================
|
||||
// <memory> — additional
|
||||
// =========================================================================
|
||||
reg_func(reg, arena, "std.addressof", "addressof", t_T_ptr);
|
||||
reg_func(reg, arena, "std.allocate_shared", "allocate_shared",
|
||||
cbm_type_named(arena, sptr_qn));
|
||||
|
||||
// =========================================================================
|
||||
// <utility>
|
||||
// =========================================================================
|
||||
reg_func(reg, arena, "std.move", "move", t_T);
|
||||
reg_func(reg, arena, "std.forward", "forward", t_T);
|
||||
reg_func(reg, arena, "std.exchange", "exchange", t_T);
|
||||
reg_func(reg, arena, "std.declval", "declval", t_T);
|
||||
reg_func(reg, arena, "std.as_const", "as_const", t_T_ref);
|
||||
|
||||
// =========================================================================
|
||||
// <type_traits> — common query functions
|
||||
// =========================================================================
|
||||
reg_func(reg, arena, "std.is_same_v", "is_same_v", t_bool);
|
||||
reg_func(reg, arena, "std.is_base_of_v", "is_base_of_v", t_bool);
|
||||
|
||||
// =========================================================================
|
||||
// std.deque<T>
|
||||
// =========================================================================
|
||||
const char* deq_qn = "std.deque";
|
||||
reg_type(reg, deq_qn, "deque");
|
||||
reg_method(reg, arena, deq_qn, "push_back", t_void);
|
||||
reg_method(reg, arena, deq_qn, "push_front", t_void);
|
||||
reg_method(reg, arena, deq_qn, "pop_back", t_void);
|
||||
reg_method(reg, arena, deq_qn, "pop_front", t_void);
|
||||
reg_method(reg, arena, deq_qn, "front", t_T_ref);
|
||||
reg_method(reg, arena, deq_qn, "back", t_T_ref);
|
||||
reg_method(reg, arena, deq_qn, "size", t_size_t);
|
||||
reg_method(reg, arena, deq_qn, "empty", t_bool);
|
||||
reg_method(reg, arena, deq_qn, "clear", t_void);
|
||||
reg_method(reg, arena, deq_qn, "at", t_T_ref);
|
||||
|
||||
// =========================================================================
|
||||
// std.list<T>
|
||||
// =========================================================================
|
||||
const char* list_qn = "std.list";
|
||||
reg_type(reg, list_qn, "list");
|
||||
reg_method(reg, arena, list_qn, "push_back", t_void);
|
||||
reg_method(reg, arena, list_qn, "push_front", t_void);
|
||||
reg_method(reg, arena, list_qn, "pop_back", t_void);
|
||||
reg_method(reg, arena, list_qn, "pop_front", t_void);
|
||||
reg_method(reg, arena, list_qn, "front", t_T_ref);
|
||||
reg_method(reg, arena, list_qn, "back", t_T_ref);
|
||||
reg_method(reg, arena, list_qn, "size", t_size_t);
|
||||
reg_method(reg, arena, list_qn, "empty", t_bool);
|
||||
reg_method(reg, arena, list_qn, "clear", t_void);
|
||||
reg_method(reg, arena, list_qn, "sort", t_void);
|
||||
reg_method(reg, arena, list_qn, "reverse", t_void);
|
||||
reg_method(reg, arena, list_qn, "merge", t_void);
|
||||
reg_method(reg, arena, list_qn, "unique", t_void);
|
||||
|
||||
// =========================================================================
|
||||
// std.stack<T> / std.queue<T> / std.priority_queue<T>
|
||||
// =========================================================================
|
||||
const char* stack_qn = "std.stack";
|
||||
reg_type(reg, stack_qn, "stack");
|
||||
reg_method(reg, arena, stack_qn, "push", t_void);
|
||||
reg_method(reg, arena, stack_qn, "pop", t_void);
|
||||
reg_method(reg, arena, stack_qn, "top", t_T_ref);
|
||||
reg_method(reg, arena, stack_qn, "size", t_size_t);
|
||||
reg_method(reg, arena, stack_qn, "empty", t_bool);
|
||||
|
||||
const char* queue_qn = "std.queue";
|
||||
reg_type(reg, queue_qn, "queue");
|
||||
reg_method(reg, arena, queue_qn, "push", t_void);
|
||||
reg_method(reg, arena, queue_qn, "pop", t_void);
|
||||
reg_method(reg, arena, queue_qn, "front", t_T_ref);
|
||||
reg_method(reg, arena, queue_qn, "back", t_T_ref);
|
||||
reg_method(reg, arena, queue_qn, "size", t_size_t);
|
||||
reg_method(reg, arena, queue_qn, "empty", t_bool);
|
||||
|
||||
const char* pq_qn = "std.priority_queue";
|
||||
reg_type(reg, pq_qn, "priority_queue");
|
||||
reg_method(reg, arena, pq_qn, "push", t_void);
|
||||
reg_method(reg, arena, pq_qn, "pop", t_void);
|
||||
reg_method(reg, arena, pq_qn, "top", t_T_ref);
|
||||
reg_method(reg, arena, pq_qn, "size", t_size_t);
|
||||
reg_method(reg, arena, pq_qn, "empty", t_bool);
|
||||
|
||||
// =========================================================================
|
||||
// std.bitset<N>
|
||||
// =========================================================================
|
||||
reg_type(reg, "std.bitset", "bitset");
|
||||
reg_method(reg, arena, "std.bitset", "set", cbm_type_named(arena, "std.bitset"));
|
||||
reg_method(reg, arena, "std.bitset", "reset", cbm_type_named(arena, "std.bitset"));
|
||||
reg_method(reg, arena, "std.bitset", "flip", cbm_type_named(arena, "std.bitset"));
|
||||
reg_method(reg, arena, "std.bitset", "test", t_bool);
|
||||
reg_method(reg, arena, "std.bitset", "count", t_size_t);
|
||||
reg_method(reg, arena, "std.bitset", "size", t_size_t);
|
||||
reg_method(reg, arena, "std.bitset", "any", t_bool);
|
||||
reg_method(reg, arena, "std.bitset", "none", t_bool);
|
||||
reg_method(reg, arena, "std.bitset", "all", t_bool);
|
||||
reg_method(reg, arena, "std.bitset", "to_string", t_string);
|
||||
reg_method(reg, arena, "std.bitset", "to_ulong", cbm_type_builtin(arena, "unsigned long"));
|
||||
|
||||
// =========================================================================
|
||||
// std.stringstream / std.ostringstream / std.istringstream
|
||||
// =========================================================================
|
||||
const char* ss_qn = "std.stringstream";
|
||||
reg_type(reg, ss_qn, "stringstream");
|
||||
reg_method(reg, arena, ss_qn, "str", t_string);
|
||||
reg_method(reg, arena, ss_qn, "clear", t_void);
|
||||
|
||||
const char* oss_qn = "std.ostringstream";
|
||||
reg_type(reg, oss_qn, "ostringstream");
|
||||
reg_method(reg, arena, oss_qn, "str", t_string);
|
||||
reg_method(reg, arena, oss_qn, "clear", t_void);
|
||||
|
||||
const char* iss_qn = "std.istringstream";
|
||||
reg_type(reg, iss_qn, "istringstream");
|
||||
reg_method(reg, arena, iss_qn, "str", t_string);
|
||||
reg_method(reg, arena, iss_qn, "clear", t_void);
|
||||
|
||||
// =========================================================================
|
||||
// std.filesystem — additional
|
||||
// =========================================================================
|
||||
reg_func(reg, arena, "std.filesystem.exists", "exists", t_bool);
|
||||
reg_func(reg, arena, "std.filesystem.is_directory", "is_directory", t_bool);
|
||||
reg_func(reg, arena, "std.filesystem.is_regular_file", "is_regular_file", t_bool);
|
||||
reg_func(reg, arena, "std.filesystem.file_size", "file_size", cbm_type_builtin(arena, "uintmax_t"));
|
||||
reg_func(reg, arena, "std.filesystem.create_directory", "create_directory", t_bool);
|
||||
reg_func(reg, arena, "std.filesystem.create_directories", "create_directories", t_bool);
|
||||
reg_func(reg, arena, "std.filesystem.remove", "remove", t_bool);
|
||||
reg_func(reg, arena, "std.filesystem.remove_all", "remove_all", cbm_type_builtin(arena, "uintmax_t"));
|
||||
reg_func(reg, arena, "std.filesystem.copy", "copy", t_void);
|
||||
reg_func(reg, arena, "std.filesystem.rename", "rename", t_void);
|
||||
reg_func(reg, arena, "std.filesystem.current_path", "current_path",
|
||||
cbm_type_named(arena, fspath_qn));
|
||||
reg_func(reg, arena, "std.filesystem.absolute", "absolute",
|
||||
cbm_type_named(arena, fspath_qn));
|
||||
reg_func(reg, arena, "std.filesystem.canonical", "canonical",
|
||||
cbm_type_named(arena, fspath_qn));
|
||||
reg_func(reg, arena, "std.filesystem.relative", "relative",
|
||||
cbm_type_named(arena, fspath_qn));
|
||||
reg_func(reg, arena, "std.filesystem.temp_directory_path", "temp_directory_path",
|
||||
cbm_type_named(arena, fspath_qn));
|
||||
|
||||
const char* direntry_qn = "std.filesystem.directory_entry";
|
||||
reg_type(reg, direntry_qn, "directory_entry");
|
||||
reg_method(reg, arena, direntry_qn, "path", cbm_type_named(arena, fspath_qn));
|
||||
reg_method(reg, arena, direntry_qn, "exists", t_bool);
|
||||
reg_method(reg, arena, direntry_qn, "is_directory", t_bool);
|
||||
reg_method(reg, arena, direntry_qn, "is_regular_file", t_bool);
|
||||
reg_method(reg, arena, direntry_qn, "file_size", cbm_type_builtin(arena, "uintmax_t"));
|
||||
|
||||
// =========================================================================
|
||||
// std.chrono — additional
|
||||
// =========================================================================
|
||||
reg_type(reg, "std.chrono.time_point", "time_point");
|
||||
reg_method(reg, arena, "std.chrono.time_point", "time_since_epoch", cbm_type_unknown());
|
||||
reg_method(reg, arena, "std.chrono.system_clock", "now",
|
||||
cbm_type_named(arena, "std.chrono.time_point"));
|
||||
reg_method(reg, arena, "std.chrono.steady_clock", "now",
|
||||
cbm_type_named(arena, "std.chrono.time_point"));
|
||||
reg_func(reg, arena, "std.chrono.duration_cast", "duration_cast", cbm_type_unknown());
|
||||
|
||||
// =========================================================================
|
||||
// Boost smart pointers (common in older codebases)
|
||||
// =========================================================================
|
||||
const char* bsptr_qn = "boost.shared_ptr";
|
||||
reg_type(reg, bsptr_qn, "shared_ptr");
|
||||
reg_method(reg, arena, bsptr_qn, "get", t_T_ptr);
|
||||
reg_method(reg, arena, bsptr_qn, "reset", t_void);
|
||||
reg_method(reg, arena, bsptr_qn, "use_count", t_long);
|
||||
reg_method(reg, arena, bsptr_qn, "operator->", t_T_ptr);
|
||||
reg_method(reg, arena, bsptr_qn, "operator*", t_T_ref);
|
||||
|
||||
const char* bscoped_qn = "boost.scoped_ptr";
|
||||
reg_type(reg, bscoped_qn, "scoped_ptr");
|
||||
reg_method(reg, arena, bscoped_qn, "get", t_T_ptr);
|
||||
reg_method(reg, arena, bscoped_qn, "reset", t_void);
|
||||
reg_method(reg, arena, bscoped_qn, "operator->", t_T_ptr);
|
||||
reg_method(reg, arena, bscoped_qn, "operator*", t_T_ref);
|
||||
|
||||
// =========================================================================
|
||||
// Boost optional / filesystem
|
||||
// =========================================================================
|
||||
const char* bopt_qn = "boost.optional";
|
||||
reg_type(reg, bopt_qn, "optional");
|
||||
reg_method(reg, arena, bopt_qn, "value", t_T_ref);
|
||||
reg_method(reg, arena, bopt_qn, "get", t_T_ref);
|
||||
reg_method(reg, arena, bopt_qn, "value_or", t_T);
|
||||
reg_method(reg, arena, bopt_qn, "is_initialized", t_bool);
|
||||
reg_method(reg, arena, bopt_qn, "operator*", t_T_ref);
|
||||
reg_method(reg, arena, bopt_qn, "operator->", t_T_ptr);
|
||||
|
||||
const char* bfspath_qn = "boost.filesystem.path";
|
||||
reg_type(reg, bfspath_qn, "path");
|
||||
reg_method(reg, arena, bfspath_qn, "string", t_string);
|
||||
reg_method(reg, arena, bfspath_qn, "parent_path", cbm_type_named(arena, bfspath_qn));
|
||||
reg_method(reg, arena, bfspath_qn, "filename", cbm_type_named(arena, bfspath_qn));
|
||||
reg_method(reg, arena, bfspath_qn, "extension", cbm_type_named(arena, bfspath_qn));
|
||||
reg_method(reg, arena, bfspath_qn, "stem", cbm_type_named(arena, bfspath_qn));
|
||||
reg_method(reg, arena, bfspath_qn, "empty", t_bool);
|
||||
reg_method(reg, arena, bfspath_qn, "exists", t_bool);
|
||||
|
||||
// =========================================================================
|
||||
// Protobuf (google.protobuf.Message)
|
||||
// =========================================================================
|
||||
const char* pb_msg_qn = "google.protobuf.Message";
|
||||
reg_type(reg, pb_msg_qn, "Message");
|
||||
reg_method(reg, arena, pb_msg_qn, "SerializeToString", t_bool);
|
||||
reg_method(reg, arena, pb_msg_qn, "SerializeAsString", t_string);
|
||||
reg_method(reg, arena, pb_msg_qn, "ParseFromString", t_bool);
|
||||
reg_method(reg, arena, pb_msg_qn, "ParseFromArray", t_bool);
|
||||
reg_method(reg, arena, pb_msg_qn, "DebugString", t_string);
|
||||
reg_method(reg, arena, pb_msg_qn, "ShortDebugString", t_string);
|
||||
reg_method(reg, arena, pb_msg_qn, "ByteSizeLong", t_size_t);
|
||||
reg_method(reg, arena, pb_msg_qn, "IsInitialized", t_bool);
|
||||
reg_method(reg, arena, pb_msg_qn, "Clear", t_void);
|
||||
reg_method(reg, arena, pb_msg_qn, "CopyFrom", t_void);
|
||||
reg_method(reg, arena, pb_msg_qn, "MergeFrom", t_void);
|
||||
reg_method(reg, arena, pb_msg_qn, "GetTypeName", t_string);
|
||||
// MessageLite is base of Message
|
||||
reg_type(reg, "google.protobuf.MessageLite", "MessageLite");
|
||||
|
||||
// =========================================================================
|
||||
// Abseil (absl.)
|
||||
// =========================================================================
|
||||
const char* absv_qn = "absl.string_view";
|
||||
reg_type(reg, absv_qn, "string_view");
|
||||
reg_method(reg, arena, absv_qn, "data", cbm_type_pointer(arena, cbm_type_builtin(arena, "char")));
|
||||
reg_method(reg, arena, absv_qn, "size", t_size_t);
|
||||
reg_method(reg, arena, absv_qn, "length", t_size_t);
|
||||
reg_method(reg, arena, absv_qn, "empty", t_bool);
|
||||
reg_method(reg, arena, absv_qn, "substr", cbm_type_named(arena, absv_qn));
|
||||
|
||||
const char* abstat_qn = "absl.Status";
|
||||
reg_type(reg, abstat_qn, "Status");
|
||||
reg_method(reg, arena, abstat_qn, "ok", t_bool);
|
||||
reg_method(reg, arena, abstat_qn, "code", cbm_type_unknown());
|
||||
reg_method(reg, arena, abstat_qn, "message", cbm_type_named(arena, absv_qn));
|
||||
reg_method(reg, arena, abstat_qn, "ToString", t_string);
|
||||
reg_func(reg, arena, "absl.OkStatus", "OkStatus", cbm_type_named(arena, abstat_qn));
|
||||
|
||||
const char* absor_qn = "absl.StatusOr";
|
||||
reg_type(reg, absor_qn, "StatusOr");
|
||||
reg_method(reg, arena, absor_qn, "ok", t_bool);
|
||||
reg_method(reg, arena, absor_qn, "status", cbm_type_named(arena, abstat_qn));
|
||||
reg_method(reg, arena, absor_qn, "value", t_T_ref);
|
||||
reg_method(reg, arena, absor_qn, "operator*", t_T_ref);
|
||||
reg_method(reg, arena, absor_qn, "operator->", t_T_ptr);
|
||||
|
||||
const char* abfhm_qn = "absl.flat_hash_map";
|
||||
reg_type(reg, abfhm_qn, "flat_hash_map");
|
||||
reg_method(reg, arena, abfhm_qn, "find", cbm_type_unknown());
|
||||
reg_method(reg, arena, abfhm_qn, "contains", t_bool);
|
||||
reg_method(reg, arena, abfhm_qn, "size", t_size_t);
|
||||
reg_method(reg, arena, abfhm_qn, "empty", t_bool);
|
||||
reg_method(reg, arena, abfhm_qn, "clear", t_void);
|
||||
reg_method(reg, arena, abfhm_qn, "insert", cbm_type_unknown());
|
||||
reg_method(reg, arena, abfhm_qn, "erase", t_size_t);
|
||||
reg_method(reg, arena, abfhm_qn, "count", t_size_t);
|
||||
|
||||
const char* abfhs_qn = "absl.flat_hash_set";
|
||||
reg_type(reg, abfhs_qn, "flat_hash_set");
|
||||
reg_method(reg, arena, abfhs_qn, "contains", t_bool);
|
||||
reg_method(reg, arena, abfhs_qn, "size", t_size_t);
|
||||
reg_method(reg, arena, abfhs_qn, "empty", t_bool);
|
||||
reg_method(reg, arena, abfhs_qn, "clear", t_void);
|
||||
reg_method(reg, arena, abfhs_qn, "insert", cbm_type_unknown());
|
||||
reg_method(reg, arena, abfhs_qn, "erase", t_size_t);
|
||||
reg_method(reg, arena, abfhs_qn, "count", t_size_t);
|
||||
|
||||
const char* abspan_qn = "absl.Span";
|
||||
reg_type(reg, abspan_qn, "Span");
|
||||
reg_method(reg, arena, abspan_qn, "data", t_T_ptr);
|
||||
reg_method(reg, arena, abspan_qn, "size", t_size_t);
|
||||
reg_method(reg, arena, abspan_qn, "empty", t_bool);
|
||||
reg_method(reg, arena, abspan_qn, "front", t_T_ref);
|
||||
reg_method(reg, arena, abspan_qn, "back", t_T_ref);
|
||||
reg_method(reg, arena, abspan_qn, "at", t_T_ref);
|
||||
reg_method(reg, arena, abspan_qn, "subspan", cbm_type_named(arena, abspan_qn));
|
||||
|
||||
// absl string functions
|
||||
reg_func(reg, arena, "absl.StrCat", "StrCat", t_string);
|
||||
reg_func(reg, arena, "absl.StrAppend", "StrAppend", t_void);
|
||||
reg_func(reg, arena, "absl.StrJoin", "StrJoin", t_string);
|
||||
reg_func(reg, arena, "absl.StrSplit", "StrSplit",
|
||||
cbm_type_named(arena, "std.vector"));
|
||||
reg_func(reg, arena, "absl.StrFormat", "StrFormat", t_string);
|
||||
reg_func(reg, arena, "absl.Substitute", "Substitute", t_string);
|
||||
|
||||
// =========================================================================
|
||||
// gRPC
|
||||
// =========================================================================
|
||||
const char* grpc_status_qn = "grpc.Status";
|
||||
reg_type(reg, grpc_status_qn, "Status");
|
||||
reg_method(reg, arena, grpc_status_qn, "ok", t_bool);
|
||||
reg_method(reg, arena, grpc_status_qn, "error_code", cbm_type_unknown());
|
||||
reg_method(reg, arena, grpc_status_qn, "error_message", t_string);
|
||||
reg_func(reg, arena, "grpc.Status.OK", "OK", cbm_type_named(arena, grpc_status_qn));
|
||||
|
||||
const char* grpc_sb_qn = "grpc.ServerBuilder";
|
||||
reg_type(reg, grpc_sb_qn, "ServerBuilder");
|
||||
reg_method(reg, arena, grpc_sb_qn, "AddListeningPort", cbm_type_named(arena, grpc_sb_qn));
|
||||
reg_method(reg, arena, grpc_sb_qn, "RegisterService", cbm_type_named(arena, grpc_sb_qn));
|
||||
reg_method(reg, arena, grpc_sb_qn, "BuildAndStart", cbm_type_unknown());
|
||||
|
||||
const char* grpc_chan_qn = "grpc.Channel";
|
||||
reg_type(reg, grpc_chan_qn, "Channel");
|
||||
reg_func(reg, arena, "grpc.CreateChannel", "CreateChannel",
|
||||
cbm_type_named(arena, grpc_chan_qn));
|
||||
|
||||
const char* grpc_ctx_qn = "grpc.ClientContext";
|
||||
reg_type(reg, grpc_ctx_qn, "ClientContext");
|
||||
reg_method(reg, arena, grpc_ctx_qn, "set_deadline", t_void);
|
||||
reg_method(reg, arena, grpc_ctx_qn, "AddMetadata", t_void);
|
||||
|
||||
// =========================================================================
|
||||
// spdlog
|
||||
// =========================================================================
|
||||
const char* spdlog_qn = "spdlog.logger";
|
||||
reg_type(reg, spdlog_qn, "logger");
|
||||
reg_method(reg, arena, spdlog_qn, "info", t_void);
|
||||
reg_method(reg, arena, spdlog_qn, "warn", t_void);
|
||||
reg_method(reg, arena, spdlog_qn, "error", t_void);
|
||||
reg_method(reg, arena, spdlog_qn, "debug", t_void);
|
||||
reg_method(reg, arena, spdlog_qn, "trace", t_void);
|
||||
reg_method(reg, arena, spdlog_qn, "critical", t_void);
|
||||
reg_method(reg, arena, spdlog_qn, "set_level", t_void);
|
||||
reg_method(reg, arena, spdlog_qn, "flush", t_void);
|
||||
reg_func(reg, arena, "spdlog.info", "info", t_void);
|
||||
reg_func(reg, arena, "spdlog.warn", "warn", t_void);
|
||||
reg_func(reg, arena, "spdlog.error", "error", t_void);
|
||||
reg_func(reg, arena, "spdlog.debug", "debug", t_void);
|
||||
reg_func(reg, arena, "spdlog.trace", "trace", t_void);
|
||||
reg_func(reg, arena, "spdlog.critical", "critical", t_void);
|
||||
reg_func(reg, arena, "spdlog.set_level", "set_level", t_void);
|
||||
reg_func(reg, arena, "spdlog.get", "get",
|
||||
cbm_type_named(arena, spdlog_qn));
|
||||
|
||||
// =========================================================================
|
||||
// Qt basics
|
||||
// =========================================================================
|
||||
const char* qobj_qn = "QObject";
|
||||
reg_type(reg, qobj_qn, "QObject");
|
||||
reg_method(reg, arena, qobj_qn, "parent", cbm_type_pointer(arena, cbm_type_named(arena, qobj_qn)));
|
||||
reg_method(reg, arena, qobj_qn, "children", cbm_type_unknown());
|
||||
reg_method(reg, arena, qobj_qn, "objectName", cbm_type_named(arena, "QString"));
|
||||
reg_method(reg, arena, qobj_qn, "setObjectName", t_void);
|
||||
reg_method(reg, arena, qobj_qn, "deleteLater", t_void);
|
||||
|
||||
const char* qstr_qn = "QString";
|
||||
reg_type(reg, qstr_qn, "QString");
|
||||
reg_method(reg, arena, qstr_qn, "toStdString", t_string);
|
||||
reg_method(reg, arena, qstr_qn, "toUtf8", cbm_type_unknown());
|
||||
reg_method(reg, arena, qstr_qn, "toLatin1", cbm_type_unknown());
|
||||
reg_method(reg, arena, qstr_qn, "size", t_int);
|
||||
reg_method(reg, arena, qstr_qn, "length", t_int);
|
||||
reg_method(reg, arena, qstr_qn, "isEmpty", t_bool);
|
||||
reg_method(reg, arena, qstr_qn, "contains", t_bool);
|
||||
reg_method(reg, arena, qstr_qn, "startsWith", t_bool);
|
||||
reg_method(reg, arena, qstr_qn, "endsWith", t_bool);
|
||||
reg_method(reg, arena, qstr_qn, "trimmed", cbm_type_named(arena, qstr_qn));
|
||||
reg_method(reg, arena, qstr_qn, "simplified", cbm_type_named(arena, qstr_qn));
|
||||
reg_method(reg, arena, qstr_qn, "toLower", cbm_type_named(arena, qstr_qn));
|
||||
reg_method(reg, arena, qstr_qn, "toUpper", cbm_type_named(arena, qstr_qn));
|
||||
reg_method(reg, arena, qstr_qn, "arg", cbm_type_named(arena, qstr_qn));
|
||||
reg_method(reg, arena, qstr_qn, "split", cbm_type_unknown());
|
||||
reg_method(reg, arena, qstr_qn, "replace", cbm_type_named(arena, qstr_qn));
|
||||
reg_method(reg, arena, qstr_qn, "mid", cbm_type_named(arena, qstr_qn));
|
||||
reg_method(reg, arena, qstr_qn, "left", cbm_type_named(arena, qstr_qn));
|
||||
reg_method(reg, arena, qstr_qn, "right", cbm_type_named(arena, qstr_qn));
|
||||
reg_method(reg, arena, qstr_qn, "toInt", t_int);
|
||||
reg_method(reg, arena, qstr_qn, "toDouble", cbm_type_builtin(arena, "double"));
|
||||
reg_func(reg, arena, "QString.number", "number", cbm_type_named(arena, qstr_qn));
|
||||
reg_func(reg, arena, "QString.fromStdString", "fromStdString", cbm_type_named(arena, qstr_qn));
|
||||
reg_func(reg, arena, "QString.fromUtf8", "fromUtf8", cbm_type_named(arena, qstr_qn));
|
||||
|
||||
const char* qwidget_qn = "QWidget";
|
||||
reg_type(reg, qwidget_qn, "QWidget");
|
||||
reg_method(reg, arena, qwidget_qn, "show", t_void);
|
||||
reg_method(reg, arena, qwidget_qn, "hide", t_void);
|
||||
reg_method(reg, arena, qwidget_qn, "close", t_bool);
|
||||
reg_method(reg, arena, qwidget_qn, "setVisible", t_void);
|
||||
reg_method(reg, arena, qwidget_qn, "isVisible", t_bool);
|
||||
reg_method(reg, arena, qwidget_qn, "resize", t_void);
|
||||
reg_method(reg, arena, qwidget_qn, "setWindowTitle", t_void);
|
||||
reg_method(reg, arena, qwidget_qn, "update", t_void);
|
||||
reg_method(reg, arena, qwidget_qn, "repaint", t_void);
|
||||
reg_method(reg, arena, qwidget_qn, "setLayout", t_void);
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,517 @@
|
||||
/*
|
||||
* kotlin_stdlib_data.c — Curated Kotlin stdlib registration for the LSP.
|
||||
*
|
||||
* Hand-distilled subset of the Kotlin standard library: the types and
|
||||
* functions that account for the vast majority of real-world usages.
|
||||
* The reverse-engineered LSP relies on this to resolve calls into
|
||||
* `kotlin.*`, `kotlin.collections.*`, `kotlin.text.*`, `kotlin.io.*`,
|
||||
* `kotlin.ranges.*`, `kotlin.sequences.*`, and a few `java.lang.*` /
|
||||
* `java.util.*` types reachable from default imports under the JVM
|
||||
* target.
|
||||
*
|
||||
* Source of truth: the Kotlin language specification + kotlin-stdlib API
|
||||
* docs (1.9.x). We do NOT vendor or fetch any new source — all type
|
||||
* shapes here were transcribed by hand from the public spec to match
|
||||
* the resolver's expectations.
|
||||
*
|
||||
* This file is included from lsp_all.c.
|
||||
*/
|
||||
|
||||
#include "../type_rep.h"
|
||||
#include "../type_registry.h"
|
||||
#include "../kotlin_lsp.h"
|
||||
#include <string.h>
|
||||
|
||||
/* Convenience macros to compress the repetitive registration calls. */
|
||||
|
||||
#define KT_TYPE_SIMPLE(qn_, short_) \
|
||||
do { \
|
||||
CBMRegisteredType rt = {0}; \
|
||||
rt.qualified_name = (qn_); \
|
||||
rt.short_name = (short_); \
|
||||
cbm_registry_add_type(reg, rt); \
|
||||
} while (0)
|
||||
|
||||
#define KT_TYPE_WITH_METHODS(qn_, short_, methods_) \
|
||||
do { \
|
||||
CBMRegisteredType rt = {0}; \
|
||||
rt.qualified_name = (qn_); \
|
||||
rt.short_name = (short_); \
|
||||
rt.method_names = (methods_); \
|
||||
cbm_registry_add_type(reg, rt); \
|
||||
} while (0)
|
||||
|
||||
#define KT_FUNC0(qn_, short_, ret_qn_) \
|
||||
do { \
|
||||
CBMRegisteredFunc rf = {0}; \
|
||||
rf.qualified_name = (qn_); \
|
||||
rf.short_name = (short_); \
|
||||
rf.min_params = 0; \
|
||||
cbm_registry_add_func(reg, rf); \
|
||||
(void)(ret_qn_); \
|
||||
} while (0)
|
||||
|
||||
#define KT_METHOD(class_qn_, method_qn_, short_) \
|
||||
do { \
|
||||
CBMRegisteredFunc rf = {0}; \
|
||||
rf.qualified_name = (method_qn_); \
|
||||
rf.receiver_type = (class_qn_); \
|
||||
rf.short_name = (short_); \
|
||||
rf.min_params = 0; \
|
||||
cbm_registry_add_func(reg, rf); \
|
||||
} while (0)
|
||||
|
||||
/* Default-imported package list, exposed to the LSP context for
|
||||
* wildcard-style resolution of bare names (e.g. `println` resolves into
|
||||
* `kotlin.io`). Order matters: more-specific shadows less-specific. */
|
||||
static const char *const KT_DEFAULT_IMPORTS[] = {
|
||||
"kotlin",
|
||||
"kotlin.annotation",
|
||||
"kotlin.collections",
|
||||
"kotlin.comparisons",
|
||||
"kotlin.io",
|
||||
"kotlin.ranges",
|
||||
"kotlin.sequences",
|
||||
"kotlin.text",
|
||||
/* JVM target adds: */
|
||||
"java.lang",
|
||||
"kotlin.jvm",
|
||||
};
|
||||
|
||||
const char *const *cbm_kotlin_default_import_packages(int *count_out) {
|
||||
if (count_out) {
|
||||
*count_out = (int)(sizeof(KT_DEFAULT_IMPORTS) / sizeof(KT_DEFAULT_IMPORTS[0]));
|
||||
}
|
||||
return KT_DEFAULT_IMPORTS;
|
||||
}
|
||||
|
||||
/* Method-name tables (NULL-terminated). Used for is_member checks
|
||||
* — the resolver walks these to decide whether `obj.foo()` should
|
||||
* be attributed as a method call on a known type. */
|
||||
|
||||
static const char *KT_ANY_METHODS[] = {
|
||||
"equals", "hashCode", "toString", NULL,
|
||||
};
|
||||
|
||||
static const char *KT_STRING_METHODS[] = {
|
||||
"length", "isEmpty", "isNotEmpty", "isBlank", "isNotBlank", "compareTo", "contains",
|
||||
"startsWith", "endsWith", "indexOf", "lastIndexOf", "substring", "replace", "split",
|
||||
"trim", "trimStart", "trimEnd", "trimIndent", "trimMargin", "toUpperCase", "toLowerCase",
|
||||
"uppercase", "lowercase", "uppercaseChar", "lowercaseChar", "capitalize", "decapitalize",
|
||||
"reversed", "lines", "padStart", "padEnd", "repeat", "removePrefix", "removeSuffix",
|
||||
"removeSurrounding", "toByteArray", "toCharArray", "toInt", "toIntOrNull", "toLong",
|
||||
"toLongOrNull", "toDouble", "toDoubleOrNull", "toFloat", "toFloatOrNull", "toBoolean",
|
||||
"format", "intern", "matches", "replaceFirst", "filter", "filterNot", "filterIndexed",
|
||||
"map", "flatMap", "fold", "reduce", "forEach", "any", "all", "none", "count", "first",
|
||||
"firstOrNull", "last", "lastOrNull", "single", "singleOrNull", "take", "takeLast",
|
||||
"takeWhile", "drop", "dropLast", "dropWhile", "windowed", "chunked", "zip", "associate",
|
||||
"associateBy", "associateWith", "groupBy", "partition", "joinToString", "iterator",
|
||||
"subSequence", "get", "set", "plus", "minus", "times", "div", "rem", "compareTo",
|
||||
"hashCode", "toString", "equals", NULL,
|
||||
};
|
||||
|
||||
static const char *KT_LIST_METHODS[] = {
|
||||
"size", "isEmpty", "contains", "containsAll", "iterator", "listIterator", "subList",
|
||||
"indexOf", "lastIndexOf", "get", "first", "firstOrNull", "last", "lastOrNull", "single",
|
||||
"singleOrNull", "elementAt", "elementAtOrNull", "elementAtOrElse", "find", "findLast",
|
||||
"filter", "filterNot", "filterNotNull", "filterIndexed", "filterIsInstance", "map",
|
||||
"mapNotNull", "mapIndexed", "mapIndexedNotNull", "flatMap", "flatten", "fold", "foldRight",
|
||||
"foldIndexed", "reduce", "reduceRight", "reduceIndexed", "scan", "runningFold",
|
||||
"runningReduce", "forEach", "forEachIndexed", "any", "all", "none", "count", "sum",
|
||||
"sumBy", "sumOf", "max", "maxOrNull", "maxBy", "maxByOrNull", "maxOf", "maxOfOrNull",
|
||||
"min", "minOrNull", "minBy", "minByOrNull", "minOf", "minOfOrNull", "average", "sorted",
|
||||
"sortedBy", "sortedDescending", "sortedByDescending", "sortedWith", "reversed",
|
||||
"shuffled", "distinct", "distinctBy", "intersect", "union", "subtract", "groupBy",
|
||||
"groupingBy", "associate", "associateBy", "associateWith", "partition", "windowed",
|
||||
"chunked", "zip", "zipWithNext", "joinToString", "joinTo", "take", "takeLast",
|
||||
"takeWhile", "takeLastWhile", "drop", "dropLast", "dropWhile", "dropLastWhile",
|
||||
"asSequence", "asIterable", "toList", "toMutableList", "toSet", "toMutableSet",
|
||||
"toHashSet", "toSortedSet", "toTypedArray", "toCollection", "plus", "minus",
|
||||
"ifEmpty", "stream", NULL,
|
||||
};
|
||||
|
||||
static const char *KT_MUTABLE_LIST_METHODS[] = {
|
||||
"add", "addAll", "remove", "removeAt", "removeAll", "removeFirst", "removeLast",
|
||||
"removeIf", "retainAll", "clear", "set", "fill", "sort", "sortBy", "sortByDescending",
|
||||
"sortWith", "reverse", "shuffle", "swap", "trimToSize",
|
||||
/* Plus all read-only methods (inherited): */
|
||||
"size", "isEmpty", "contains", "containsAll", "iterator", "listIterator", "subList",
|
||||
"indexOf", "lastIndexOf", "get", "first", "firstOrNull", "last", "lastOrNull", "single",
|
||||
"singleOrNull", "find", "findLast", "elementAt", "elementAtOrNull", "elementAtOrElse",
|
||||
"filter", "filterNot", "filterNotNull", "filterIndexed", "filterIsInstance", "map",
|
||||
"mapNotNull", "mapIndexed", "flatMap", "flatten", "fold", "foldRight", "reduce",
|
||||
"reduceRight", "forEach", "forEachIndexed", "any", "all", "none", "count", "sum",
|
||||
"sumBy", "sumOf", "max", "maxOrNull", "maxBy", "min", "minOrNull", "minBy", "average",
|
||||
"sorted", "sortedBy", "sortedDescending", "sortedByDescending", "sortedWith",
|
||||
"reversed", "shuffled", "distinct", "distinctBy", "groupBy", "groupingBy", "associate",
|
||||
"associateBy", "associateWith", "partition", "windowed", "chunked", "zip", "zipWithNext",
|
||||
"joinToString", "joinTo", "take", "takeLast", "takeWhile", "drop", "dropLast",
|
||||
"dropWhile", "asSequence", "asIterable", "toList", "toMutableList", "toSet",
|
||||
"toMutableSet", "toHashSet", "toTypedArray", "toCollection", "ifEmpty", "stream", NULL,
|
||||
};
|
||||
|
||||
static const char *KT_MAP_METHODS[] = {
|
||||
"size", "isEmpty", "containsKey", "containsValue", "get", "getOrDefault", "getOrElse",
|
||||
"getOrNull", "getValue", "keys", "values", "entries", "iterator", "forEach", "filter",
|
||||
"filterKeys", "filterValues", "filterNot", "filterTo", "map", "mapKeys", "mapValues",
|
||||
"mapNotNull", "any", "all", "none", "count", "max", "min", "maxBy", "minBy", "toList",
|
||||
"toMap", "toMutableMap", "toSortedMap", "asSequence", "asIterable", "plus", "minus",
|
||||
"ifEmpty", NULL,
|
||||
};
|
||||
|
||||
static const char *KT_MUTABLE_MAP_METHODS[] = {
|
||||
"put", "putAll", "putIfAbsent", "remove", "clear", "computeIfAbsent", "computeIfPresent",
|
||||
"compute", "merge", "replace", "replaceAll", "getOrPut",
|
||||
/* Plus read-only methods: */
|
||||
"size", "isEmpty", "containsKey", "containsValue", "get", "keys", "values", "entries",
|
||||
"iterator", "forEach", "filter", "map", NULL,
|
||||
};
|
||||
|
||||
static const char *KT_SET_METHODS[] = {
|
||||
"size", "isEmpty", "contains", "containsAll", "iterator", "intersect", "union",
|
||||
"subtract", "filter", "map", "fold", "reduce", "forEach", "any", "all", "none",
|
||||
"count", "first", "last", "elementAt", NULL,
|
||||
};
|
||||
|
||||
static const char *KT_ITERATOR_METHODS[] = {
|
||||
"hasNext", "next", "remove", "nextIndex", "previousIndex", "previous", "hasPrevious",
|
||||
"set", "add", NULL,
|
||||
};
|
||||
|
||||
static const char *KT_SEQUENCE_METHODS[] = {
|
||||
"iterator", "filter", "filterNot", "filterNotNull", "filterIndexed", "filterIsInstance",
|
||||
"map", "mapNotNull", "mapIndexed", "flatMap", "flatten", "fold", "foldIndexed", "reduce",
|
||||
"reduceIndexed", "forEach", "forEachIndexed", "any", "all", "none", "count", "sum",
|
||||
"sumBy", "sumOf", "max", "maxOrNull", "min", "minOrNull", "first", "firstOrNull",
|
||||
"last", "lastOrNull", "single", "singleOrNull", "find", "findLast", "elementAt",
|
||||
"elementAtOrNull", "take", "takeWhile", "drop", "dropWhile", "windowed", "chunked",
|
||||
"zip", "zipWithNext", "distinct", "distinctBy", "sorted", "sortedBy", "sortedDescending",
|
||||
"sortedWith", "associate", "associateBy", "associateWith", "groupBy", "partition",
|
||||
"joinToString", "toList", "toSet", "toMap", "toCollection", "asIterable", "constrainOnce",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const char *KT_INT_RANGE_METHODS[] = {
|
||||
"first", "last", "step", "isEmpty", "iterator", "contains", "reversed", "forEach",
|
||||
"map", "filter", "sum", "count", "any", "all", "none", "elementAt", NULL,
|
||||
};
|
||||
|
||||
static const char *KT_PAIR_METHODS[] = {
|
||||
"first", "second", "toString", "toList", "component1", "component2", "copy",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const char *KT_TRIPLE_METHODS[] = {
|
||||
"first", "second", "third", "toString", "toList", "component1", "component2",
|
||||
"component3", "copy", NULL,
|
||||
};
|
||||
|
||||
static const char *KT_THROWABLE_METHODS[] = {
|
||||
"message", "cause", "stackTrace", "stackTraceToString", "printStackTrace",
|
||||
"addSuppressed", "getSuppressed", "fillInStackTrace", "initCause", "toString", NULL,
|
||||
};
|
||||
|
||||
static const char *KT_EXCEPTION_METHODS[] = {
|
||||
"message", "cause", "stackTrace", "stackTraceToString", "printStackTrace", "toString",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const char *KT_NUMBER_METHODS[] = {
|
||||
"toByte", "toShort", "toInt", "toLong", "toFloat", "toDouble", "toChar", "toString",
|
||||
"compareTo", "equals", "hashCode",
|
||||
/* Operator conventions */
|
||||
"plus", "minus", "times", "div", "rem", "mod", "inc", "dec", "unaryMinus", "unaryPlus",
|
||||
"and", "or", "xor", "inv", "shl", "shr", "ushr",
|
||||
"rangeTo", "rangeUntil", "downTo", "until", "coerceAtLeast", "coerceAtMost", "coerceIn",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const char *KT_BOOLEAN_METHODS[] = {
|
||||
"and", "or", "xor", "not", "compareTo", "toString", "equals", "hashCode", NULL,
|
||||
};
|
||||
|
||||
static const char *KT_CHAR_METHODS[] = {
|
||||
"isDigit", "isLetter", "isLetterOrDigit", "isWhitespace", "isUpperCase", "isLowerCase",
|
||||
"uppercase", "lowercase", "uppercaseChar", "lowercaseChar", "digitToInt", "code",
|
||||
"compareTo", "toString", "equals", "hashCode", "plus", "minus", "rangeTo",
|
||||
"isHighSurrogate", "isLowSurrogate", "isSurrogate", NULL,
|
||||
};
|
||||
|
||||
static const char *KT_ARRAY_METHODS[] = {
|
||||
"size", "get", "set", "iterator", "isEmpty", "isNotEmpty", "contains", "indexOf",
|
||||
"lastIndexOf", "first", "last", "filter", "map", "fold", "reduce", "forEach", "any",
|
||||
"all", "none", "count", "sum", "max", "min", "joinToString", "asList", "asSequence",
|
||||
"toList", "toMutableList", "toSet", "copyOf", "copyOfRange", "fill", "sort", "sorted",
|
||||
"reversed", "reverse", "binarySearch", "plus", "component1", "component2", NULL,
|
||||
};
|
||||
|
||||
static const char *KT_REGEX_METHODS[] = {
|
||||
"matchEntire", "matches", "containsMatchIn", "find", "findAll", "replace",
|
||||
"replaceFirst", "split", "splitToSequence", "toPattern", "pattern", NULL,
|
||||
};
|
||||
|
||||
static const char *KT_MATCH_RESULT_METHODS[] = {
|
||||
"value", "range", "groups", "groupValues", "destructured", "next", NULL,
|
||||
};
|
||||
|
||||
static const char *KT_FILE_METHODS[] = {
|
||||
"path", "name", "parent", "exists", "isFile", "isDirectory", "isAbsolute", "length",
|
||||
"lastModified", "canRead", "canWrite", "delete", "deleteRecursively", "createNewFile",
|
||||
"mkdir", "mkdirs", "renameTo", "list", "listFiles", "walk", "walkTopDown",
|
||||
"walkBottomUp", "readText", "readLines", "readBytes", "writeText", "writeBytes",
|
||||
"appendText", "appendBytes", "useLines", "forEachLine", "bufferedReader",
|
||||
"bufferedWriter", "inputStream", "outputStream", "reader", "writer", "printWriter",
|
||||
"absolutePath", "absoluteFile", "canonicalPath", "canonicalFile", "extension",
|
||||
"nameWithoutExtension", "toPath", "toURI", "resolve", "resolveSibling", "relativeTo",
|
||||
"copyTo", "copyRecursively", "endsWith", "startsWith", "normalize", NULL,
|
||||
};
|
||||
|
||||
static const char *KT_SCOPE_METHODS[] = {
|
||||
"let", "run", "with", "apply", "also", "takeIf", "takeUnless", "use", NULL,
|
||||
};
|
||||
|
||||
static const char *KT_LAZY_METHODS[] = {
|
||||
"value", "isInitialized", "getValue", NULL,
|
||||
};
|
||||
|
||||
static const char *KT_RESULT_METHODS[] = {
|
||||
"getOrNull", "exceptionOrNull", "isSuccess", "isFailure", "getOrThrow", "getOrDefault",
|
||||
"getOrElse", "fold", "map", "mapCatching", "recover", "recoverCatching", "onSuccess",
|
||||
"onFailure", NULL,
|
||||
};
|
||||
|
||||
/* ── Top-level builtin function registration. ──────────────────────
|
||||
* We register the names that show up in the wild as bare calls and
|
||||
* route them to their kotlin.* / kotlin.io.* QNs. The resolver checks
|
||||
* in-package and class-method lookups before falling through to these.
|
||||
*/
|
||||
|
||||
void cbm_kotlin_stdlib_register(CBMTypeRegistry *reg, CBMArena *arena) {
|
||||
(void)arena;
|
||||
|
||||
/* Core types — kotlin.* */
|
||||
KT_TYPE_WITH_METHODS("kotlin.Any", "Any", KT_ANY_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.Number", "Number", KT_NUMBER_METHODS);
|
||||
KT_TYPE_SIMPLE("kotlin.Unit", "Unit");
|
||||
KT_TYPE_SIMPLE("kotlin.Nothing", "Nothing");
|
||||
KT_TYPE_WITH_METHODS("kotlin.Boolean", "Boolean", KT_BOOLEAN_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.Byte", "Byte", KT_NUMBER_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.Short", "Short", KT_NUMBER_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.Int", "Int", KT_NUMBER_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.Long", "Long", KT_NUMBER_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.Float", "Float", KT_NUMBER_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.Double", "Double", KT_NUMBER_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.Char", "Char", KT_CHAR_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.String", "String", KT_STRING_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.CharSequence", "CharSequence", KT_STRING_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.Throwable", "Throwable", KT_THROWABLE_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.Pair", "Pair", KT_PAIR_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.Triple", "Triple", KT_TRIPLE_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.Result", "Result", KT_RESULT_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.Lazy", "Lazy", KT_LAZY_METHODS);
|
||||
KT_TYPE_SIMPLE("kotlin.Enum", "Enum");
|
||||
KT_TYPE_SIMPLE("kotlin.Function", "Function");
|
||||
KT_TYPE_SIMPLE("kotlin.KotlinVersion", "KotlinVersion");
|
||||
|
||||
/* Common exceptions */
|
||||
KT_TYPE_WITH_METHODS("kotlin.Exception", "Exception", KT_EXCEPTION_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.RuntimeException", "RuntimeException", KT_EXCEPTION_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.IllegalArgumentException", "IllegalArgumentException",
|
||||
KT_EXCEPTION_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.IllegalStateException", "IllegalStateException",
|
||||
KT_EXCEPTION_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.IndexOutOfBoundsException", "IndexOutOfBoundsException",
|
||||
KT_EXCEPTION_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.NoSuchElementException", "NoSuchElementException",
|
||||
KT_EXCEPTION_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.NumberFormatException", "NumberFormatException",
|
||||
KT_EXCEPTION_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.NullPointerException", "NullPointerException",
|
||||
KT_EXCEPTION_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.ClassCastException", "ClassCastException", KT_EXCEPTION_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.UnsupportedOperationException", "UnsupportedOperationException",
|
||||
KT_EXCEPTION_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.ArithmeticException", "ArithmeticException",
|
||||
KT_EXCEPTION_METHODS);
|
||||
|
||||
/* Collections */
|
||||
KT_TYPE_WITH_METHODS("kotlin.collections.Iterable", "Iterable", KT_LIST_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.collections.Collection", "Collection", KT_LIST_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.collections.MutableCollection", "MutableCollection",
|
||||
KT_MUTABLE_LIST_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.collections.List", "List", KT_LIST_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.collections.MutableList", "MutableList", KT_MUTABLE_LIST_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.collections.Set", "Set", KT_SET_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.collections.MutableSet", "MutableSet", KT_SET_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.collections.HashSet", "HashSet", KT_SET_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.collections.LinkedHashSet", "LinkedHashSet", KT_SET_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.collections.Map", "Map", KT_MAP_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.collections.MutableMap", "MutableMap", KT_MUTABLE_MAP_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.collections.HashMap", "HashMap", KT_MUTABLE_MAP_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.collections.LinkedHashMap", "LinkedHashMap",
|
||||
KT_MUTABLE_MAP_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.collections.ArrayList", "ArrayList", KT_MUTABLE_LIST_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.collections.Iterator", "Iterator", KT_ITERATOR_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.collections.MutableIterator", "MutableIterator",
|
||||
KT_ITERATOR_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.collections.ListIterator", "ListIterator", KT_ITERATOR_METHODS);
|
||||
|
||||
/* Sequences */
|
||||
KT_TYPE_WITH_METHODS("kotlin.sequences.Sequence", "Sequence", KT_SEQUENCE_METHODS);
|
||||
|
||||
/* Arrays */
|
||||
KT_TYPE_WITH_METHODS("kotlin.Array", "Array", KT_ARRAY_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.IntArray", "IntArray", KT_ARRAY_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.LongArray", "LongArray", KT_ARRAY_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.DoubleArray", "DoubleArray", KT_ARRAY_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.FloatArray", "FloatArray", KT_ARRAY_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.ByteArray", "ByteArray", KT_ARRAY_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.ShortArray", "ShortArray", KT_ARRAY_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.BooleanArray", "BooleanArray", KT_ARRAY_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.CharArray", "CharArray", KT_ARRAY_METHODS);
|
||||
|
||||
/* Ranges */
|
||||
KT_TYPE_WITH_METHODS("kotlin.ranges.IntRange", "IntRange", KT_INT_RANGE_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.ranges.LongRange", "LongRange", KT_INT_RANGE_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.ranges.CharRange", "CharRange", KT_INT_RANGE_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.ranges.IntProgression", "IntProgression", KT_INT_RANGE_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.ranges.LongProgression", "LongProgression", KT_INT_RANGE_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.ranges.ClosedRange", "ClosedRange", KT_INT_RANGE_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.ranges.ClosedFloatingPointRange", "ClosedFloatingPointRange",
|
||||
KT_INT_RANGE_METHODS);
|
||||
|
||||
/* Text */
|
||||
KT_TYPE_WITH_METHODS("kotlin.text.Regex", "Regex", KT_REGEX_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.text.MatchResult", "MatchResult", KT_MATCH_RESULT_METHODS);
|
||||
KT_TYPE_WITH_METHODS("kotlin.text.StringBuilder", "StringBuilder", KT_STRING_METHODS);
|
||||
|
||||
/* IO (java.io.File is reachable through kotlin.io extension functions) */
|
||||
KT_TYPE_WITH_METHODS("java.io.File", "File", KT_FILE_METHODS);
|
||||
KT_TYPE_WITH_METHODS("java.io.PrintStream", "PrintStream", KT_FILE_METHODS);
|
||||
|
||||
/* java.lang basics that come in via default JVM imports */
|
||||
KT_TYPE_WITH_METHODS("java.lang.String", "String", KT_STRING_METHODS);
|
||||
KT_TYPE_WITH_METHODS("java.lang.Object", "Object", KT_ANY_METHODS);
|
||||
KT_TYPE_WITH_METHODS("java.lang.Throwable", "Throwable", KT_THROWABLE_METHODS);
|
||||
KT_TYPE_WITH_METHODS("java.lang.Exception", "Exception", KT_EXCEPTION_METHODS);
|
||||
KT_TYPE_WITH_METHODS("java.lang.RuntimeException", "RuntimeException", KT_EXCEPTION_METHODS);
|
||||
KT_TYPE_WITH_METHODS("java.lang.IllegalArgumentException", "IllegalArgumentException",
|
||||
KT_EXCEPTION_METHODS);
|
||||
KT_TYPE_WITH_METHODS("java.lang.IllegalStateException", "IllegalStateException",
|
||||
KT_EXCEPTION_METHODS);
|
||||
KT_TYPE_WITH_METHODS("java.lang.NumberFormatException", "NumberFormatException",
|
||||
KT_EXCEPTION_METHODS);
|
||||
KT_TYPE_SIMPLE("java.lang.System", "System");
|
||||
KT_TYPE_SIMPLE("java.lang.Math", "Math");
|
||||
KT_TYPE_SIMPLE("java.lang.Integer", "Integer");
|
||||
KT_TYPE_SIMPLE("java.lang.Long", "Long");
|
||||
KT_TYPE_SIMPLE("java.lang.Double", "Double");
|
||||
KT_TYPE_SIMPLE("java.lang.Float", "Float");
|
||||
KT_TYPE_SIMPLE("java.lang.Boolean", "Boolean");
|
||||
|
||||
/* ── Top-level functions ── */
|
||||
/* kotlin.io */
|
||||
KT_FUNC0("kotlin.io.println", "println", "kotlin.Unit");
|
||||
KT_FUNC0("kotlin.io.print", "print", "kotlin.Unit");
|
||||
KT_FUNC0("kotlin.io.readLine", "readLine", "kotlin.String");
|
||||
KT_FUNC0("kotlin.io.readln", "readln", "kotlin.String");
|
||||
KT_FUNC0("kotlin.io.readlnOrNull", "readlnOrNull", "kotlin.String");
|
||||
|
||||
/* kotlin (root) */
|
||||
KT_FUNC0("kotlin.error", "error", "kotlin.Nothing");
|
||||
KT_FUNC0("kotlin.check", "check", "kotlin.Unit");
|
||||
KT_FUNC0("kotlin.checkNotNull", "checkNotNull", "kotlin.Any");
|
||||
KT_FUNC0("kotlin.require", "require", "kotlin.Unit");
|
||||
KT_FUNC0("kotlin.requireNotNull", "requireNotNull", "kotlin.Any");
|
||||
KT_FUNC0("kotlin.assert", "assert", "kotlin.Unit");
|
||||
KT_FUNC0("kotlin.TODO", "TODO", "kotlin.Nothing");
|
||||
KT_FUNC0("kotlin.runCatching", "runCatching", "kotlin.Result");
|
||||
KT_FUNC0("kotlin.run", "run", "kotlin.Any");
|
||||
KT_FUNC0("kotlin.with", "with", "kotlin.Any");
|
||||
KT_FUNC0("kotlin.repeat", "repeat", "kotlin.Unit");
|
||||
KT_FUNC0("kotlin.synchronized", "synchronized", "kotlin.Any");
|
||||
KT_FUNC0("kotlin.lazy", "lazy", "kotlin.Lazy");
|
||||
KT_FUNC0("kotlin.lazyOf", "lazyOf", "kotlin.Lazy");
|
||||
KT_FUNC0("kotlin.to", "to", "kotlin.Pair");
|
||||
|
||||
/* kotlin.collections — top-level builders */
|
||||
KT_FUNC0("kotlin.collections.listOf", "listOf", "kotlin.collections.List");
|
||||
KT_FUNC0("kotlin.collections.mutableListOf", "mutableListOf",
|
||||
"kotlin.collections.MutableList");
|
||||
KT_FUNC0("kotlin.collections.arrayListOf", "arrayListOf", "kotlin.collections.ArrayList");
|
||||
KT_FUNC0("kotlin.collections.emptyList", "emptyList", "kotlin.collections.List");
|
||||
KT_FUNC0("kotlin.collections.listOfNotNull", "listOfNotNull", "kotlin.collections.List");
|
||||
KT_FUNC0("kotlin.collections.setOf", "setOf", "kotlin.collections.Set");
|
||||
KT_FUNC0("kotlin.collections.mutableSetOf", "mutableSetOf", "kotlin.collections.MutableSet");
|
||||
KT_FUNC0("kotlin.collections.hashSetOf", "hashSetOf", "kotlin.collections.HashSet");
|
||||
KT_FUNC0("kotlin.collections.linkedSetOf", "linkedSetOf",
|
||||
"kotlin.collections.LinkedHashSet");
|
||||
KT_FUNC0("kotlin.collections.emptySet", "emptySet", "kotlin.collections.Set");
|
||||
KT_FUNC0("kotlin.collections.sortedSetOf", "sortedSetOf", "kotlin.collections.Set");
|
||||
KT_FUNC0("kotlin.collections.mapOf", "mapOf", "kotlin.collections.Map");
|
||||
KT_FUNC0("kotlin.collections.mutableMapOf", "mutableMapOf", "kotlin.collections.MutableMap");
|
||||
KT_FUNC0("kotlin.collections.hashMapOf", "hashMapOf", "kotlin.collections.HashMap");
|
||||
KT_FUNC0("kotlin.collections.linkedMapOf", "linkedMapOf",
|
||||
"kotlin.collections.LinkedHashMap");
|
||||
KT_FUNC0("kotlin.collections.emptyMap", "emptyMap", "kotlin.collections.Map");
|
||||
KT_FUNC0("kotlin.collections.sortedMapOf", "sortedMapOf", "kotlin.collections.Map");
|
||||
KT_FUNC0("kotlin.arrayOf", "arrayOf", "kotlin.Array");
|
||||
KT_FUNC0("kotlin.arrayOfNulls", "arrayOfNulls", "kotlin.Array");
|
||||
KT_FUNC0("kotlin.emptyArray", "emptyArray", "kotlin.Array");
|
||||
KT_FUNC0("kotlin.intArrayOf", "intArrayOf", "kotlin.IntArray");
|
||||
KT_FUNC0("kotlin.longArrayOf", "longArrayOf", "kotlin.LongArray");
|
||||
KT_FUNC0("kotlin.floatArrayOf", "floatArrayOf", "kotlin.FloatArray");
|
||||
KT_FUNC0("kotlin.doubleArrayOf", "doubleArrayOf", "kotlin.DoubleArray");
|
||||
KT_FUNC0("kotlin.byteArrayOf", "byteArrayOf", "kotlin.ByteArray");
|
||||
KT_FUNC0("kotlin.shortArrayOf", "shortArrayOf", "kotlin.ShortArray");
|
||||
KT_FUNC0("kotlin.booleanArrayOf", "booleanArrayOf", "kotlin.BooleanArray");
|
||||
KT_FUNC0("kotlin.charArrayOf", "charArrayOf", "kotlin.CharArray");
|
||||
|
||||
/* kotlin.sequences */
|
||||
KT_FUNC0("kotlin.sequences.sequenceOf", "sequenceOf", "kotlin.sequences.Sequence");
|
||||
KT_FUNC0("kotlin.sequences.emptySequence", "emptySequence", "kotlin.sequences.Sequence");
|
||||
KT_FUNC0("kotlin.sequences.generateSequence", "generateSequence",
|
||||
"kotlin.sequences.Sequence");
|
||||
KT_FUNC0("kotlin.sequences.sequence", "sequence", "kotlin.sequences.Sequence");
|
||||
|
||||
/* kotlin.ranges */
|
||||
KT_FUNC0("kotlin.ranges.until", "until", "kotlin.ranges.IntRange");
|
||||
KT_FUNC0("kotlin.ranges.downTo", "downTo", "kotlin.ranges.IntProgression");
|
||||
KT_FUNC0("kotlin.ranges.coerceAtLeast", "coerceAtLeast", "kotlin.Int");
|
||||
KT_FUNC0("kotlin.ranges.coerceAtMost", "coerceAtMost", "kotlin.Int");
|
||||
KT_FUNC0("kotlin.ranges.coerceIn", "coerceIn", "kotlin.Int");
|
||||
|
||||
/* kotlin.comparisons */
|
||||
KT_FUNC0("kotlin.comparisons.compareValues", "compareValues", "kotlin.Int");
|
||||
KT_FUNC0("kotlin.comparisons.compareBy", "compareBy", "java.util.Comparator");
|
||||
KT_FUNC0("kotlin.comparisons.compareByDescending", "compareByDescending",
|
||||
"java.util.Comparator");
|
||||
KT_FUNC0("kotlin.comparisons.minOf", "minOf", "kotlin.Comparable");
|
||||
KT_FUNC0("kotlin.comparisons.maxOf", "maxOf", "kotlin.Comparable");
|
||||
KT_FUNC0("kotlin.comparisons.naturalOrder", "naturalOrder", "java.util.Comparator");
|
||||
KT_FUNC0("kotlin.comparisons.reverseOrder", "reverseOrder", "java.util.Comparator");
|
||||
|
||||
/* kotlin.text — useful builders on String companion */
|
||||
KT_FUNC0("kotlin.text.buildString", "buildString", "kotlin.String");
|
||||
KT_FUNC0("kotlin.text.toRegex", "toRegex", "kotlin.text.Regex");
|
||||
|
||||
/* kotlin scope-functions are registered as extension functions
|
||||
* (receiver_type = "kotlin.Any") so that resolver finds them on every
|
||||
* receiver. Their full QN is kotlin.<name>. */
|
||||
{
|
||||
const char *names[] = {"let", "run", "apply", "also", "takeIf", "takeUnless", "use", NULL};
|
||||
const char *qns[] = {"kotlin.let", "kotlin.run", "kotlin.apply", "kotlin.also",
|
||||
"kotlin.takeIf", "kotlin.takeUnless",
|
||||
"kotlin.io.use", /* `use` lives in kotlin.io for Closeable */
|
||||
NULL};
|
||||
for (int i = 0; names[i]; i++) {
|
||||
CBMRegisteredFunc rf = {0};
|
||||
rf.qualified_name = qns[i];
|
||||
rf.short_name = names[i];
|
||||
rf.receiver_type = "kotlin.Any";
|
||||
rf.min_params = 1; /* the lambda */
|
||||
cbm_registry_add_func(reg, rf);
|
||||
}
|
||||
}
|
||||
(void)KT_SCOPE_METHODS; /* reserved for future tightening */
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* perl_stdlib_data.c — hand-written Perl stdlib + CPAN type data.
|
||||
*
|
||||
* Strategy mirrors php_stdlib_data.c (docs/PLAN_PHP_LSP_INTEGRATION.md §6):
|
||||
* 1. perlfunc core built-ins (print, bless, ref, ...) registered as global,
|
||||
* package-less functions reachable from any namespace.
|
||||
* 2. Curated, corpus-driven CPAN OOP modules (Scalar::Util, List::Util,
|
||||
* Carp, POSIX, Storable, Data::Dumper) registered as module-qualified
|
||||
* functions.
|
||||
*
|
||||
* Module-qualified functions use dotted QNs (Foo.Bar.func) to match
|
||||
* perl_pkg_to_dot (Foo::Bar -> Foo.Bar) so an Exporter import map
|
||||
* (plan 22-03) can resolve `use Scalar::Util qw(blessed)` to these symbols.
|
||||
*
|
||||
* Return types are left UNKNOWN (cbm_type_unknown) for v1: real signature
|
||||
* inference is out of scope here — this seed only provides a baseline symbol
|
||||
* table for the resolver. Moose meta stubs (has/extends/with) are deferred
|
||||
* (Open Question #4).
|
||||
*/
|
||||
|
||||
#include "../type_rep.h"
|
||||
#include "../type_registry.h"
|
||||
#include "../../arena.h"
|
||||
#include "../perl_lsp.h"
|
||||
#include <string.h>
|
||||
|
||||
#define MIXED cbm_type_unknown()
|
||||
|
||||
/* Register a global (package-less) built-in function returning `ret_type_`.
|
||||
* Reachable from any package — short_name == qualified_name (bare name). */
|
||||
#define REG_BUILTIN(name_, ret_type_) \
|
||||
do { \
|
||||
memset(&rf, 0, sizeof(rf)); \
|
||||
rf.min_params = -1; \
|
||||
rf.qualified_name = (name_); \
|
||||
rf.short_name = (name_); \
|
||||
{ \
|
||||
const CBMType **rets = (const CBMType **)cbm_arena_alloc(arena, 2 * sizeof(*rets)); \
|
||||
rets[0] = (ret_type_); \
|
||||
rets[1] = NULL; \
|
||||
rf.signature = cbm_type_func(arena, NULL, NULL, rets); \
|
||||
} \
|
||||
cbm_registry_add_func(reg, rf); \
|
||||
} while (0)
|
||||
|
||||
/* Register a module-qualified function (an exported sub, not a method).
|
||||
* `module_dot_` is the dotted package QN (e.g. "Scalar.Util"); `name_` is the
|
||||
* bare sub name. QN becomes "Scalar.Util.blessed"; short_name stays bare so an
|
||||
* Exporter import map can resolve `use Scalar::Util qw(blessed)`. */
|
||||
#define REG_FUNC(module_dot_, name_, ret_type_) \
|
||||
do { \
|
||||
memset(&rf, 0, sizeof(rf)); \
|
||||
rf.min_params = -1; \
|
||||
rf.qualified_name = cbm_arena_sprintf(arena, "%s.%s", (module_dot_), (name_)); \
|
||||
rf.short_name = (name_); \
|
||||
{ \
|
||||
const CBMType **rets = (const CBMType **)cbm_arena_alloc(arena, 2 * sizeof(*rets)); \
|
||||
rets[0] = (ret_type_); \
|
||||
rets[1] = NULL; \
|
||||
rf.signature = cbm_type_func(arena, NULL, NULL, rets); \
|
||||
} \
|
||||
cbm_registry_add_func(reg, rf); \
|
||||
} while (0)
|
||||
|
||||
void cbm_perl_stdlib_register(CBMTypeRegistry *reg, CBMArena *arena) {
|
||||
CBMRegisteredFunc rf;
|
||||
|
||||
/* ── perlfunc core built-ins (global, package-less) ─────────────
|
||||
* Source: RESEARCH.md L365 (perldoc perlfunc core list). Reachable from
|
||||
* any package; return types unknown for v1. */
|
||||
REG_BUILTIN("print", MIXED);
|
||||
REG_BUILTIN("printf", MIXED);
|
||||
REG_BUILTIN("sprintf", cbm_type_builtin(arena, "string"));
|
||||
REG_BUILTIN("open", MIXED);
|
||||
REG_BUILTIN("close", MIXED);
|
||||
REG_BUILTIN("push", cbm_type_builtin(arena, "int"));
|
||||
REG_BUILTIN("pop", MIXED);
|
||||
REG_BUILTIN("shift", MIXED);
|
||||
REG_BUILTIN("unshift", cbm_type_builtin(arena, "int"));
|
||||
REG_BUILTIN("map", MIXED);
|
||||
REG_BUILTIN("grep", MIXED);
|
||||
REG_BUILTIN("sort", MIXED);
|
||||
REG_BUILTIN("join", cbm_type_builtin(arena, "string"));
|
||||
REG_BUILTIN("split", MIXED);
|
||||
REG_BUILTIN("length", cbm_type_builtin(arena, "int"));
|
||||
REG_BUILTIN("substr", cbm_type_builtin(arena, "string"));
|
||||
REG_BUILTIN("chomp", MIXED);
|
||||
REG_BUILTIN("chop", MIXED);
|
||||
REG_BUILTIN("die", MIXED);
|
||||
REG_BUILTIN("warn", MIXED);
|
||||
REG_BUILTIN("ref", cbm_type_builtin(arena, "string"));
|
||||
REG_BUILTIN("bless", MIXED);
|
||||
REG_BUILTIN("defined", cbm_type_builtin(arena, "bool"));
|
||||
REG_BUILTIN("exists", cbm_type_builtin(arena, "bool"));
|
||||
REG_BUILTIN("delete", MIXED);
|
||||
REG_BUILTIN("scalar", MIXED);
|
||||
REG_BUILTIN("keys", MIXED);
|
||||
REG_BUILTIN("values", MIXED);
|
||||
REG_BUILTIN("each", MIXED);
|
||||
|
||||
/* ── Scalar::Util ───────────────────────────────────────────────
|
||||
* Source: RESEARCH.md L366. Exported subs; module QN "Scalar.Util". */
|
||||
REG_FUNC("Scalar.Util", "blessed", MIXED);
|
||||
REG_FUNC("Scalar.Util", "reftype", cbm_type_builtin(arena, "string"));
|
||||
REG_FUNC("Scalar.Util", "weaken", MIXED);
|
||||
|
||||
/* ── List::Util ─────────────────────────────────────────────────
|
||||
* Source: RESEARCH.md L366. Module QN "List.Util". */
|
||||
REG_FUNC("List.Util", "sum", MIXED);
|
||||
REG_FUNC("List.Util", "max", MIXED);
|
||||
REG_FUNC("List.Util", "min", MIXED);
|
||||
REG_FUNC("List.Util", "first", MIXED);
|
||||
REG_FUNC("List.Util", "reduce", MIXED);
|
||||
|
||||
/* ── Carp ───────────────────────────────────────────────────────
|
||||
* Source: RESEARCH.md L367. Module QN "Carp". */
|
||||
REG_FUNC("Carp", "croak", MIXED);
|
||||
REG_FUNC("Carp", "carp", MIXED);
|
||||
REG_FUNC("Carp", "confess", MIXED);
|
||||
REG_FUNC("Carp", "cluck", MIXED);
|
||||
|
||||
/* ── POSIX (commonly-imported entry points) ─────────────────────
|
||||
* Source: RESEARCH.md L367. Module QN "POSIX". */
|
||||
REG_FUNC("POSIX", "floor", MIXED);
|
||||
REG_FUNC("POSIX", "ceil", MIXED);
|
||||
REG_FUNC("POSIX", "strftime", cbm_type_builtin(arena, "string"));
|
||||
REG_FUNC("POSIX", "INT_MAX", cbm_type_builtin(arena, "int"));
|
||||
|
||||
/* ── Storable ───────────────────────────────────────────────────
|
||||
* Source: RESEARCH.md L367. Module QN "Storable". */
|
||||
REG_FUNC("Storable", "dclone", MIXED);
|
||||
REG_FUNC("Storable", "freeze", cbm_type_builtin(arena, "string"));
|
||||
REG_FUNC("Storable", "thaw", MIXED);
|
||||
REG_FUNC("Storable", "nstore", MIXED);
|
||||
REG_FUNC("Storable", "retrieve", MIXED);
|
||||
|
||||
/* ── Data::Dumper ───────────────────────────────────────────────
|
||||
* Source: RESEARCH.md L367. Module QN "Data.Dumper". */
|
||||
REG_FUNC("Data.Dumper", "Dumper", cbm_type_builtin(arena, "string"));
|
||||
}
|
||||
@@ -0,0 +1,687 @@
|
||||
/*
|
||||
* php_stdlib_data.c — corpus-seeded PHP stdlib + framework type data.
|
||||
*
|
||||
* Strategy from docs/PLAN_PHP_LSP_INTEGRATION.md §6:
|
||||
* 1. Genuinely-stdlib base — SPL containers/iterators, PSR interfaces,
|
||||
* DateTime, Throwable hierarchy.
|
||||
* 2. Top-N corpus-driven types from laravel/framework benchmark
|
||||
* (Model in=836, Builder, Container, Collection, etc.).
|
||||
*
|
||||
* Each entry is a one-liner. Sources commented inline.
|
||||
*
|
||||
* Class QNs use dotted form (Foo.Bar.Baz) to match how the unified
|
||||
* extractor + php_resolve_class_name produce names.
|
||||
*/
|
||||
|
||||
#include "../type_rep.h"
|
||||
#include "../type_registry.h"
|
||||
#include "../../arena.h"
|
||||
#include "../php_lsp.h"
|
||||
#include <string.h>
|
||||
|
||||
#define REG_TYPE(qn_, short_, is_iface_, parents_) do { \
|
||||
memset(&rt, 0, sizeof(rt)); \
|
||||
rt.qualified_name = (qn_); \
|
||||
rt.short_name = (short_); \
|
||||
rt.is_interface = (is_iface_); \
|
||||
rt.embedded_types = (parents_); \
|
||||
cbm_registry_add_type(reg, rt); \
|
||||
} while (0)
|
||||
|
||||
#define REG_METHOD(class_qn_, method_name_, ret_type_) do { \
|
||||
memset(&rf, 0, sizeof(rf)); \
|
||||
rf.min_params = -1; \
|
||||
rf.qualified_name = cbm_arena_sprintf(arena, "%s.%s", (class_qn_), (method_name_)); \
|
||||
rf.short_name = (method_name_); \
|
||||
rf.receiver_type = (class_qn_); \
|
||||
{ \
|
||||
const CBMType **rets = (const CBMType **)cbm_arena_alloc(arena, 2 * sizeof(*rets)); \
|
||||
rets[0] = (ret_type_); rets[1] = NULL; \
|
||||
rf.signature = cbm_type_func(arena, NULL, NULL, rets); \
|
||||
} \
|
||||
cbm_registry_add_func(reg, rf); \
|
||||
} while (0)
|
||||
|
||||
#define MIXED cbm_type_unknown()
|
||||
|
||||
void cbm_php_stdlib_register(CBMTypeRegistry *reg, CBMArena *arena) {
|
||||
CBMRegisteredType rt;
|
||||
CBMRegisteredFunc rf;
|
||||
|
||||
/* ── Throwable hierarchy ────────────────────────────────────── */
|
||||
static const char *throwable_parents[] = {NULL};
|
||||
static const char *exception_parents[] = {"Throwable", NULL};
|
||||
static const char *error_parents[] = {"Throwable", NULL};
|
||||
static const char *runtime_exception_parents[] = {"Exception", NULL};
|
||||
static const char *logic_exception_parents[] = {"Exception", NULL};
|
||||
static const char *invalid_argument_parents[] = {"LogicException", NULL};
|
||||
static const char *type_error_parents[] = {"Error", NULL};
|
||||
static const char *value_error_parents[] = {"Error", NULL};
|
||||
|
||||
REG_TYPE("Throwable", "Throwable", true, throwable_parents);
|
||||
REG_TYPE("Exception", "Exception", false, exception_parents);
|
||||
REG_TYPE("Error", "Error", false, error_parents);
|
||||
REG_TYPE("RuntimeException", "RuntimeException", false, runtime_exception_parents);
|
||||
REG_TYPE("LogicException", "LogicException", false, logic_exception_parents);
|
||||
REG_TYPE("InvalidArgumentException", "InvalidArgumentException", false,
|
||||
invalid_argument_parents);
|
||||
REG_TYPE("TypeError", "TypeError", false, type_error_parents);
|
||||
REG_TYPE("ValueError", "ValueError", false, value_error_parents);
|
||||
|
||||
REG_METHOD("Throwable", "getMessage", cbm_type_builtin(arena, "string"));
|
||||
REG_METHOD("Throwable", "getCode", cbm_type_builtin(arena, "int"));
|
||||
REG_METHOD("Throwable", "getFile", cbm_type_builtin(arena, "string"));
|
||||
REG_METHOD("Throwable", "getLine", cbm_type_builtin(arena, "int"));
|
||||
REG_METHOD("Throwable", "getTrace", cbm_type_builtin(arena, "array"));
|
||||
REG_METHOD("Throwable", "getTraceAsString", cbm_type_builtin(arena, "string"));
|
||||
REG_METHOD("Throwable", "getPrevious", cbm_type_named(arena, "Throwable"));
|
||||
|
||||
/* ── DateTime family ────────────────────────────────────────── */
|
||||
static const char *datetime_iface_parents[] = {NULL};
|
||||
static const char *datetime_parents[] = {"DateTimeInterface", NULL};
|
||||
static const char *datetime_immutable_parents[] = {"DateTimeInterface", NULL};
|
||||
|
||||
REG_TYPE("DateTimeInterface", "DateTimeInterface", true, datetime_iface_parents);
|
||||
REG_TYPE("DateTime", "DateTime", false, datetime_parents);
|
||||
REG_TYPE("DateTimeImmutable", "DateTimeImmutable", false, datetime_immutable_parents);
|
||||
REG_TYPE("DateInterval", "DateInterval", false, throwable_parents);
|
||||
REG_TYPE("DateTimeZone", "DateTimeZone", false, throwable_parents);
|
||||
|
||||
REG_METHOD("DateTimeInterface", "format", cbm_type_builtin(arena, "string"));
|
||||
REG_METHOD("DateTimeInterface", "getTimestamp", cbm_type_builtin(arena, "int"));
|
||||
REG_METHOD("DateTimeInterface", "getOffset", cbm_type_builtin(arena, "int"));
|
||||
REG_METHOD("DateTime", "modify", cbm_type_named(arena, "DateTime"));
|
||||
REG_METHOD("DateTime", "add", cbm_type_named(arena, "DateTime"));
|
||||
REG_METHOD("DateTime", "sub", cbm_type_named(arena, "DateTime"));
|
||||
REG_METHOD("DateTime", "setDate", cbm_type_named(arena, "DateTime"));
|
||||
REG_METHOD("DateTime", "setTime", cbm_type_named(arena, "DateTime"));
|
||||
REG_METHOD("DateTimeImmutable", "modify", cbm_type_named(arena, "DateTimeImmutable"));
|
||||
REG_METHOD("DateTimeImmutable", "add", cbm_type_named(arena, "DateTimeImmutable"));
|
||||
REG_METHOD("DateTimeImmutable", "sub", cbm_type_named(arena, "DateTimeImmutable"));
|
||||
|
||||
/* ── SPL containers / iterators ─────────────────────────────── */
|
||||
static const char *traversable_parents[] = {NULL};
|
||||
static const char *iterator_parents[] = {"Traversable", NULL};
|
||||
static const char *iterator_aggregate_parents[] = {"Traversable", NULL};
|
||||
static const char *generator_parents[] = {"Iterator", NULL};
|
||||
static const char *array_iterator_parents[] = {"Iterator", "Countable", "ArrayAccess",
|
||||
"Serializable", NULL};
|
||||
static const char *array_object_parents[] = {"IteratorAggregate", "Countable",
|
||||
"ArrayAccess", "Serializable", NULL};
|
||||
static const char *spl_doubly_linked_list_parents[] = {"Iterator", "Countable",
|
||||
"ArrayAccess", "Serializable", NULL};
|
||||
static const char *spl_stack_parents[] = {"SplDoublyLinkedList", NULL};
|
||||
static const char *spl_queue_parents[] = {"SplDoublyLinkedList", NULL};
|
||||
static const char *spl_object_storage_parents[] = {"Countable", "Iterator", "Serializable",
|
||||
"ArrayAccess", NULL};
|
||||
static const char *countable_parents[] = {NULL};
|
||||
static const char *array_access_parents[] = {NULL};
|
||||
static const char *stringable_parents[] = {NULL};
|
||||
|
||||
REG_TYPE("Traversable", "Traversable", true, traversable_parents);
|
||||
REG_TYPE("Iterator", "Iterator", true, iterator_parents);
|
||||
REG_TYPE("IteratorAggregate", "IteratorAggregate", true, iterator_aggregate_parents);
|
||||
REG_TYPE("Generator", "Generator", false, generator_parents);
|
||||
REG_TYPE("ArrayIterator", "ArrayIterator", false, array_iterator_parents);
|
||||
REG_TYPE("ArrayObject", "ArrayObject", false, array_object_parents);
|
||||
REG_TYPE("SplDoublyLinkedList", "SplDoublyLinkedList", false, spl_doubly_linked_list_parents);
|
||||
REG_TYPE("SplStack", "SplStack", false, spl_stack_parents);
|
||||
REG_TYPE("SplQueue", "SplQueue", false, spl_queue_parents);
|
||||
REG_TYPE("SplObjectStorage", "SplObjectStorage", false, spl_object_storage_parents);
|
||||
REG_TYPE("Countable", "Countable", true, countable_parents);
|
||||
REG_TYPE("ArrayAccess", "ArrayAccess", true, array_access_parents);
|
||||
REG_TYPE("Stringable", "Stringable", true, stringable_parents);
|
||||
REG_TYPE("Serializable", "Serializable", true, traversable_parents);
|
||||
|
||||
REG_METHOD("Iterator", "current", MIXED);
|
||||
REG_METHOD("Iterator", "key", MIXED);
|
||||
REG_METHOD("Iterator", "next", cbm_type_builtin(arena, "void"));
|
||||
REG_METHOD("Iterator", "rewind", cbm_type_builtin(arena, "void"));
|
||||
REG_METHOD("Iterator", "valid", cbm_type_builtin(arena, "bool"));
|
||||
REG_METHOD("Countable", "count", cbm_type_builtin(arena, "int"));
|
||||
REG_METHOD("ArrayAccess", "offsetExists", cbm_type_builtin(arena, "bool"));
|
||||
REG_METHOD("ArrayAccess", "offsetGet", MIXED);
|
||||
REG_METHOD("ArrayAccess", "offsetSet", cbm_type_builtin(arena, "void"));
|
||||
REG_METHOD("ArrayAccess", "offsetUnset", cbm_type_builtin(arena, "void"));
|
||||
REG_METHOD("Stringable", "__toString", cbm_type_builtin(arena, "string"));
|
||||
REG_METHOD("ArrayObject", "getIterator", cbm_type_named(arena, "ArrayIterator"));
|
||||
|
||||
/* ── PSR interfaces (de facto stdlib) ───────────────────────── */
|
||||
static const char *psr_logger_parents[] = {NULL};
|
||||
static const char *psr_container_parents[] = {NULL};
|
||||
static const char *psr_request_parents[] = {NULL};
|
||||
static const char *psr_response_parents[] = {NULL};
|
||||
|
||||
REG_TYPE("Psr.Log.LoggerInterface", "LoggerInterface", true, psr_logger_parents);
|
||||
REG_TYPE("Psr.Container.ContainerInterface", "ContainerInterface", true,
|
||||
psr_container_parents);
|
||||
REG_TYPE("Psr.Container.NotFoundExceptionInterface", "NotFoundExceptionInterface", true,
|
||||
psr_container_parents);
|
||||
REG_TYPE("Psr.Http.Message.RequestInterface", "RequestInterface", true, psr_request_parents);
|
||||
REG_TYPE("Psr.Http.Message.ResponseInterface", "ResponseInterface", true,
|
||||
psr_response_parents);
|
||||
REG_TYPE("Psr.Http.Message.ServerRequestInterface", "ServerRequestInterface", true,
|
||||
psr_request_parents);
|
||||
REG_TYPE("Psr.Http.Message.UriInterface", "UriInterface", true, psr_request_parents);
|
||||
|
||||
REG_METHOD("Psr.Log.LoggerInterface", "info", cbm_type_builtin(arena, "void"));
|
||||
REG_METHOD("Psr.Log.LoggerInterface", "warning", cbm_type_builtin(arena, "void"));
|
||||
REG_METHOD("Psr.Log.LoggerInterface", "error", cbm_type_builtin(arena, "void"));
|
||||
REG_METHOD("Psr.Log.LoggerInterface", "debug", cbm_type_builtin(arena, "void"));
|
||||
REG_METHOD("Psr.Log.LoggerInterface", "critical", cbm_type_builtin(arena, "void"));
|
||||
REG_METHOD("Psr.Log.LoggerInterface", "log", cbm_type_builtin(arena, "void"));
|
||||
REG_METHOD("Psr.Container.ContainerInterface", "get", MIXED);
|
||||
REG_METHOD("Psr.Container.ContainerInterface", "has", cbm_type_builtin(arena, "bool"));
|
||||
REG_METHOD("Psr.Http.Message.RequestInterface", "getMethod", cbm_type_builtin(arena, "string"));
|
||||
REG_METHOD("Psr.Http.Message.RequestInterface", "getUri",
|
||||
cbm_type_named(arena, "Psr.Http.Message.UriInterface"));
|
||||
REG_METHOD("Psr.Http.Message.ResponseInterface", "getStatusCode",
|
||||
cbm_type_builtin(arena, "int"));
|
||||
REG_METHOD("Psr.Http.Message.ResponseInterface", "getBody", MIXED);
|
||||
|
||||
/* ── Closure / fibers ──────────────────────────────────────── */
|
||||
REG_TYPE("Closure", "Closure", false, throwable_parents);
|
||||
REG_METHOD("Closure", "bindTo", cbm_type_named(arena, "Closure"));
|
||||
REG_METHOD("Closure", "call", MIXED);
|
||||
REG_METHOD("Closure", "__invoke", MIXED);
|
||||
|
||||
/* ── Laravel framework — top-N corpus-seeded ────────────────── */
|
||||
/* These are NOT stdlib; they are seeded because laravel/framework is the
|
||||
* canonical PHP benchmark corpus and dominates real-world receiver-type
|
||||
* resolution. Comments cite the inbound-edge count from the Q3 row of
|
||||
* docs/BENCHMARK.md and from the Q3 search_graph response. */
|
||||
|
||||
static const char *facade_parents[] = {NULL};
|
||||
REG_TYPE("Illuminate.Support.Facades.Facade", "Facade", false, facade_parents);
|
||||
REG_METHOD("Illuminate.Support.Facades.Facade", "__callStatic", MIXED);
|
||||
REG_METHOD("Illuminate.Support.Facades.Facade", "getFacadeRoot", MIXED);
|
||||
|
||||
/* Each named facade extends Facade so __callStatic detection lights up. */
|
||||
static const char *facade_child_parents[] = {"Illuminate.Support.Facades.Facade", NULL};
|
||||
REG_TYPE("Illuminate.Support.Facades.DB", "DB", false, facade_child_parents);
|
||||
REG_TYPE("Illuminate.Support.Facades.Cache", "Cache", false, facade_child_parents);
|
||||
REG_TYPE("Illuminate.Support.Facades.Auth", "Auth", false, facade_child_parents);
|
||||
REG_TYPE("Illuminate.Support.Facades.Log", "Log", false, facade_child_parents);
|
||||
REG_TYPE("Illuminate.Support.Facades.Route", "Route", false, facade_child_parents);
|
||||
REG_TYPE("Illuminate.Support.Facades.Schema", "Schema", false, facade_child_parents);
|
||||
REG_TYPE("Illuminate.Support.Facades.Storage", "Storage", false, facade_child_parents);
|
||||
REG_TYPE("Illuminate.Support.Facades.Mail", "Mail", false, facade_child_parents);
|
||||
REG_TYPE("Illuminate.Support.Facades.Queue", "Queue", false, facade_child_parents);
|
||||
REG_TYPE("Illuminate.Support.Facades.Event", "Event", false, facade_child_parents);
|
||||
REG_TYPE("Illuminate.Support.Facades.Config", "Config", false, facade_child_parents);
|
||||
REG_TYPE("Illuminate.Support.Facades.Session", "Session", false, facade_child_parents);
|
||||
REG_TYPE("Illuminate.Support.Facades.URL", "URL", false, facade_child_parents);
|
||||
REG_TYPE("Illuminate.Support.Facades.View", "View", false, facade_child_parents);
|
||||
REG_TYPE("Illuminate.Support.Facades.App", "App", false, facade_child_parents);
|
||||
|
||||
/* ── Eloquent + Query Builder ───────────────────────────────── *
|
||||
*
|
||||
* The query builder is the single highest-leverage chain in any Laravel
|
||||
* codebase: `Model::query()->where(...)->orderBy(...)->first()`. We
|
||||
* register a fluent builder where every "where/order/group/select/take/
|
||||
* skip/limit/distinct/join" method returns `Builder` so chains keep
|
||||
* resolving. Terminal methods (get/first/find/value/exists) return
|
||||
* Collection or the model. */
|
||||
|
||||
REG_TYPE("Illuminate.Database.Eloquent.Builder", "Builder", false, throwable_parents);
|
||||
REG_TYPE("Illuminate.Database.Query.Builder", "Builder", false, throwable_parents);
|
||||
REG_TYPE("Illuminate.Database.Eloquent.Model", "Model", false, throwable_parents);
|
||||
REG_TYPE("Illuminate.Database.Eloquent.Collection", "Collection", false,
|
||||
traversable_parents);
|
||||
REG_TYPE("Illuminate.Support.Collection", "Collection", false, traversable_parents);
|
||||
|
||||
/* Eloquent Builder chain methods. */
|
||||
{
|
||||
const char *eb = "Illuminate.Database.Eloquent.Builder";
|
||||
const CBMType *self = cbm_type_named(arena, "Illuminate.Database.Eloquent.Builder");
|
||||
const CBMType *coll = cbm_type_named(arena, "Illuminate.Database.Eloquent.Collection");
|
||||
const CBMType *model = cbm_type_named(arena, "Illuminate.Database.Eloquent.Model");
|
||||
REG_METHOD(eb, "where", self);
|
||||
REG_METHOD(eb, "orWhere", self);
|
||||
REG_METHOD(eb, "whereIn", self);
|
||||
REG_METHOD(eb, "whereNull", self);
|
||||
REG_METHOD(eb, "whereNotNull", self);
|
||||
REG_METHOD(eb, "whereHas", self);
|
||||
REG_METHOD(eb, "with", self);
|
||||
REG_METHOD(eb, "without", self);
|
||||
REG_METHOD(eb, "select", self);
|
||||
REG_METHOD(eb, "distinct", self);
|
||||
REG_METHOD(eb, "orderBy", self);
|
||||
REG_METHOD(eb, "orderByDesc", self);
|
||||
REG_METHOD(eb, "groupBy", self);
|
||||
REG_METHOD(eb, "having", self);
|
||||
REG_METHOD(eb, "join", self);
|
||||
REG_METHOD(eb, "leftJoin", self);
|
||||
REG_METHOD(eb, "rightJoin", self);
|
||||
REG_METHOD(eb, "limit", self);
|
||||
REG_METHOD(eb, "take", self);
|
||||
REG_METHOD(eb, "skip", self);
|
||||
REG_METHOD(eb, "offset", self);
|
||||
REG_METHOD(eb, "tap", self);
|
||||
REG_METHOD(eb, "when", self);
|
||||
REG_METHOD(eb, "unless", self);
|
||||
REG_METHOD(eb, "scopeQuery", self);
|
||||
REG_METHOD(eb, "get", coll);
|
||||
REG_METHOD(eb, "all", coll);
|
||||
REG_METHOD(eb, "pluck", coll);
|
||||
REG_METHOD(eb, "first", model);
|
||||
REG_METHOD(eb, "firstOrFail", model);
|
||||
REG_METHOD(eb, "find", model);
|
||||
REG_METHOD(eb, "findOrFail", model);
|
||||
REG_METHOD(eb, "create", model);
|
||||
REG_METHOD(eb, "firstOrCreate", model);
|
||||
REG_METHOD(eb, "updateOrCreate", model);
|
||||
REG_METHOD(eb, "exists", cbm_type_builtin(arena, "bool"));
|
||||
REG_METHOD(eb, "count", cbm_type_builtin(arena, "int"));
|
||||
REG_METHOD(eb, "sum", cbm_type_builtin(arena, "int"));
|
||||
REG_METHOD(eb, "value", MIXED);
|
||||
REG_METHOD(eb, "toSql", cbm_type_builtin(arena, "string"));
|
||||
}
|
||||
|
||||
/* Eloquent Model: convenience static-call entry points. */
|
||||
{
|
||||
const char *em = "Illuminate.Database.Eloquent.Model";
|
||||
const CBMType *eb = cbm_type_named(arena, "Illuminate.Database.Eloquent.Builder");
|
||||
const CBMType *self = cbm_type_named(arena, "Illuminate.Database.Eloquent.Model");
|
||||
const CBMType *coll = cbm_type_named(arena, "Illuminate.Database.Eloquent.Collection");
|
||||
REG_METHOD(em, "query", eb);
|
||||
REG_METHOD(em, "newQuery", eb);
|
||||
REG_METHOD(em, "where", eb);
|
||||
REG_METHOD(em, "with", eb);
|
||||
REG_METHOD(em, "all", coll);
|
||||
REG_METHOD(em, "find", self);
|
||||
REG_METHOD(em, "findOrFail", self);
|
||||
REG_METHOD(em, "create", self);
|
||||
REG_METHOD(em, "save", cbm_type_builtin(arena, "bool"));
|
||||
REG_METHOD(em, "update", cbm_type_builtin(arena, "bool"));
|
||||
REG_METHOD(em, "delete", cbm_type_builtin(arena, "bool"));
|
||||
REG_METHOD(em, "fresh", self);
|
||||
REG_METHOD(em, "refresh", self);
|
||||
REG_METHOD(em, "load", self);
|
||||
REG_METHOD(em, "loadMissing", self);
|
||||
REG_METHOD(em, "toArray", cbm_type_builtin(arena, "array"));
|
||||
REG_METHOD(em, "toJson", cbm_type_builtin(arena, "string"));
|
||||
REG_METHOD(em, "getKey", MIXED);
|
||||
REG_METHOD(em, "getAttribute", MIXED);
|
||||
}
|
||||
|
||||
/* Illuminate\Support\Collection — chain returns self. */
|
||||
{
|
||||
const char *coll_qn = "Illuminate.Support.Collection";
|
||||
const CBMType *self = cbm_type_named(arena, "Illuminate.Support.Collection");
|
||||
REG_METHOD(coll_qn, "map", self);
|
||||
REG_METHOD(coll_qn, "filter", self);
|
||||
REG_METHOD(coll_qn, "reject", self);
|
||||
REG_METHOD(coll_qn, "where", self);
|
||||
REG_METHOD(coll_qn, "values", self);
|
||||
REG_METHOD(coll_qn, "keys", self);
|
||||
REG_METHOD(coll_qn, "sort", self);
|
||||
REG_METHOD(coll_qn, "sortBy", self);
|
||||
REG_METHOD(coll_qn, "sortByDesc", self);
|
||||
REG_METHOD(coll_qn, "groupBy", self);
|
||||
REG_METHOD(coll_qn, "keyBy", self);
|
||||
REG_METHOD(coll_qn, "merge", self);
|
||||
REG_METHOD(coll_qn, "concat", self);
|
||||
REG_METHOD(coll_qn, "unique", self);
|
||||
REG_METHOD(coll_qn, "take", self);
|
||||
REG_METHOD(coll_qn, "skip", self);
|
||||
REG_METHOD(coll_qn, "chunk", self);
|
||||
REG_METHOD(coll_qn, "flatten", self);
|
||||
REG_METHOD(coll_qn, "flatMap", self);
|
||||
REG_METHOD(coll_qn, "pluck", self);
|
||||
REG_METHOD(coll_qn, "tap", self);
|
||||
REG_METHOD(coll_qn, "each", self);
|
||||
REG_METHOD(coll_qn, "reverse", self);
|
||||
REG_METHOD(coll_qn, "first", MIXED);
|
||||
REG_METHOD(coll_qn, "last", MIXED);
|
||||
REG_METHOD(coll_qn, "count", cbm_type_builtin(arena, "int"));
|
||||
REG_METHOD(coll_qn, "isEmpty", cbm_type_builtin(arena, "bool"));
|
||||
REG_METHOD(coll_qn, "isNotEmpty", cbm_type_builtin(arena, "bool"));
|
||||
REG_METHOD(coll_qn, "contains", cbm_type_builtin(arena, "bool"));
|
||||
REG_METHOD(coll_qn, "toArray", cbm_type_builtin(arena, "array"));
|
||||
REG_METHOD(coll_qn, "toJson", cbm_type_builtin(arena, "string"));
|
||||
REG_METHOD(coll_qn, "implode", cbm_type_builtin(arena, "string"));
|
||||
REG_METHOD(coll_qn, "sum", cbm_type_builtin(arena, "int"));
|
||||
REG_METHOD(coll_qn, "avg", cbm_type_builtin(arena, "float"));
|
||||
REG_METHOD(coll_qn, "max", MIXED);
|
||||
REG_METHOD(coll_qn, "min", MIXED);
|
||||
}
|
||||
|
||||
/* ── Symfony HttpFoundation (used by Laravel + Symfony) ─────── */
|
||||
static const char *symfony_request_parents[] = {NULL};
|
||||
REG_TYPE("Symfony.Component.HttpFoundation.Request", "Request", false,
|
||||
symfony_request_parents);
|
||||
REG_TYPE("Symfony.Component.HttpFoundation.Response", "Response", false,
|
||||
symfony_request_parents);
|
||||
REG_TYPE("Symfony.Component.HttpFoundation.HeaderBag", "HeaderBag", false,
|
||||
symfony_request_parents);
|
||||
REG_TYPE("Symfony.Component.HttpFoundation.ParameterBag", "ParameterBag", false,
|
||||
symfony_request_parents);
|
||||
REG_METHOD("Symfony.Component.HttpFoundation.Request", "getMethod",
|
||||
cbm_type_builtin(arena, "string"));
|
||||
REG_METHOD("Symfony.Component.HttpFoundation.Request", "getPathInfo",
|
||||
cbm_type_builtin(arena, "string"));
|
||||
REG_METHOD("Symfony.Component.HttpFoundation.Request", "getUri",
|
||||
cbm_type_builtin(arena, "string"));
|
||||
REG_METHOD("Symfony.Component.HttpFoundation.Response", "getStatusCode",
|
||||
cbm_type_builtin(arena, "int"));
|
||||
REG_METHOD("Symfony.Component.HttpFoundation.Response", "getContent",
|
||||
cbm_type_builtin(arena, "string"));
|
||||
|
||||
/* ── Carbon (Laravel's date/time wrapper) ───────────────────── */
|
||||
static const char *carbon_parents[] = {"DateTimeImmutable", NULL};
|
||||
REG_TYPE("Carbon.Carbon", "Carbon", false, carbon_parents);
|
||||
REG_TYPE("Carbon.CarbonImmutable", "CarbonImmutable", false, carbon_parents);
|
||||
{
|
||||
const char *c = "Carbon.Carbon";
|
||||
const CBMType *self = cbm_type_named(arena, "Carbon.Carbon");
|
||||
REG_METHOD(c, "addDay", self);
|
||||
REG_METHOD(c, "addDays", self);
|
||||
REG_METHOD(c, "subDay", self);
|
||||
REG_METHOD(c, "addMonth", self);
|
||||
REG_METHOD(c, "addYear", self);
|
||||
REG_METHOD(c, "startOfDay", self);
|
||||
REG_METHOD(c, "endOfDay", self);
|
||||
REG_METHOD(c, "format", cbm_type_builtin(arena, "string"));
|
||||
REG_METHOD(c, "toDateString", cbm_type_builtin(arena, "string"));
|
||||
REG_METHOD(c, "toDateTimeString", cbm_type_builtin(arena, "string"));
|
||||
REG_METHOD(c, "diffInDays", cbm_type_builtin(arena, "int"));
|
||||
REG_METHOD(c, "diffInHours", cbm_type_builtin(arena, "int"));
|
||||
REG_METHOD(c, "diffInMinutes", cbm_type_builtin(arena, "int"));
|
||||
REG_METHOD(c, "now", self);
|
||||
REG_METHOD(c, "today", self);
|
||||
REG_METHOD(c, "yesterday", self);
|
||||
REG_METHOD(c, "tomorrow", self);
|
||||
REG_METHOD(c, "parse", self);
|
||||
}
|
||||
|
||||
/* ── PSR-7 / PSR-15 extras (used by Symfony stack) ──────────── */
|
||||
REG_METHOD("Psr.Http.Message.RequestInterface", "withMethod",
|
||||
cbm_type_named(arena, "Psr.Http.Message.RequestInterface"));
|
||||
REG_METHOD("Psr.Http.Message.RequestInterface", "withUri",
|
||||
cbm_type_named(arena, "Psr.Http.Message.RequestInterface"));
|
||||
REG_METHOD("Psr.Http.Message.ResponseInterface", "withStatus",
|
||||
cbm_type_named(arena, "Psr.Http.Message.ResponseInterface"));
|
||||
REG_METHOD("Psr.Http.Message.ResponseInterface", "withHeader",
|
||||
cbm_type_named(arena, "Psr.Http.Message.ResponseInterface"));
|
||||
|
||||
/* ── Symfony Console ─────────────────────────────────────── */
|
||||
static const char *symfony_console_io_parents[] = {NULL};
|
||||
REG_TYPE("Symfony.Component.Console.Input.InputInterface", "InputInterface", true,
|
||||
symfony_console_io_parents);
|
||||
REG_TYPE("Symfony.Component.Console.Output.OutputInterface", "OutputInterface", true,
|
||||
symfony_console_io_parents);
|
||||
REG_TYPE("Symfony.Component.Console.Style.StyleInterface", "StyleInterface", true,
|
||||
symfony_console_io_parents);
|
||||
REG_TYPE("Symfony.Component.Console.Style.SymfonyStyle", "SymfonyStyle", false,
|
||||
symfony_console_io_parents);
|
||||
REG_METHOD("Symfony.Component.Console.Input.InputInterface", "getArgument", MIXED);
|
||||
REG_METHOD("Symfony.Component.Console.Input.InputInterface", "getOption", MIXED);
|
||||
REG_METHOD("Symfony.Component.Console.Input.InputInterface", "hasOption",
|
||||
cbm_type_builtin(arena, "bool"));
|
||||
REG_METHOD("Symfony.Component.Console.Output.OutputInterface", "writeln",
|
||||
cbm_type_builtin(arena, "void"));
|
||||
REG_METHOD("Symfony.Component.Console.Output.OutputInterface", "write",
|
||||
cbm_type_builtin(arena, "void"));
|
||||
REG_METHOD("Symfony.Component.Console.Style.SymfonyStyle", "success",
|
||||
cbm_type_builtin(arena, "void"));
|
||||
REG_METHOD("Symfony.Component.Console.Style.SymfonyStyle", "error",
|
||||
cbm_type_builtin(arena, "void"));
|
||||
REG_METHOD("Symfony.Component.Console.Style.SymfonyStyle", "info",
|
||||
cbm_type_builtin(arena, "void"));
|
||||
REG_METHOD("Symfony.Component.Console.Style.SymfonyStyle", "warning",
|
||||
cbm_type_builtin(arena, "void"));
|
||||
REG_METHOD("Symfony.Component.Console.Style.SymfonyStyle", "ask",
|
||||
cbm_type_builtin(arena, "string"));
|
||||
REG_METHOD("Symfony.Component.Console.Style.SymfonyStyle", "confirm",
|
||||
cbm_type_builtin(arena, "bool"));
|
||||
|
||||
/* ── Doctrine ORM ───────────────────────────────────────── */
|
||||
static const char *doctrine_parents[] = {NULL};
|
||||
REG_TYPE("Doctrine.ORM.EntityManagerInterface", "EntityManagerInterface", true,
|
||||
doctrine_parents);
|
||||
REG_TYPE("Doctrine.ORM.EntityManager", "EntityManager", false, doctrine_parents);
|
||||
REG_TYPE("Doctrine.ORM.QueryBuilder", "QueryBuilder", false, doctrine_parents);
|
||||
REG_TYPE("Doctrine.ORM.Query", "Query", false, doctrine_parents);
|
||||
REG_TYPE("Doctrine.ORM.EntityRepository", "EntityRepository", false, doctrine_parents);
|
||||
REG_METHOD("Doctrine.ORM.EntityManagerInterface", "find", MIXED);
|
||||
REG_METHOD("Doctrine.ORM.EntityManagerInterface", "persist", cbm_type_builtin(arena, "void"));
|
||||
REG_METHOD("Doctrine.ORM.EntityManagerInterface", "remove", cbm_type_builtin(arena, "void"));
|
||||
REG_METHOD("Doctrine.ORM.EntityManagerInterface", "flush", cbm_type_builtin(arena, "void"));
|
||||
REG_METHOD("Doctrine.ORM.EntityManagerInterface", "createQuery",
|
||||
cbm_type_named(arena, "Doctrine.ORM.Query"));
|
||||
REG_METHOD("Doctrine.ORM.EntityManagerInterface", "createQueryBuilder",
|
||||
cbm_type_named(arena, "Doctrine.ORM.QueryBuilder"));
|
||||
REG_METHOD("Doctrine.ORM.EntityManagerInterface", "getRepository",
|
||||
cbm_type_named(arena, "Doctrine.ORM.EntityRepository"));
|
||||
{
|
||||
const char *qb = "Doctrine.ORM.QueryBuilder";
|
||||
const CBMType *self = cbm_type_named(arena, "Doctrine.ORM.QueryBuilder");
|
||||
REG_METHOD(qb, "select", self);
|
||||
REG_METHOD(qb, "from", self);
|
||||
REG_METHOD(qb, "where", self);
|
||||
REG_METHOD(qb, "andWhere", self);
|
||||
REG_METHOD(qb, "orWhere", self);
|
||||
REG_METHOD(qb, "join", self);
|
||||
REG_METHOD(qb, "leftJoin", self);
|
||||
REG_METHOD(qb, "innerJoin", self);
|
||||
REG_METHOD(qb, "orderBy", self);
|
||||
REG_METHOD(qb, "groupBy", self);
|
||||
REG_METHOD(qb, "setParameter", self);
|
||||
REG_METHOD(qb, "setMaxResults", self);
|
||||
REG_METHOD(qb, "setFirstResult", self);
|
||||
REG_METHOD(qb, "getQuery", cbm_type_named(arena, "Doctrine.ORM.Query"));
|
||||
}
|
||||
REG_METHOD("Doctrine.ORM.Query", "getResult", cbm_type_builtin(arena, "array"));
|
||||
REG_METHOD("Doctrine.ORM.Query", "getOneOrNullResult", MIXED);
|
||||
REG_METHOD("Doctrine.ORM.Query", "execute", cbm_type_builtin(arena, "array"));
|
||||
REG_METHOD("Doctrine.ORM.EntityRepository", "find", MIXED);
|
||||
REG_METHOD("Doctrine.ORM.EntityRepository", "findAll", cbm_type_builtin(arena, "array"));
|
||||
REG_METHOD("Doctrine.ORM.EntityRepository", "findBy", cbm_type_builtin(arena, "array"));
|
||||
REG_METHOD("Doctrine.ORM.EntityRepository", "findOneBy", MIXED);
|
||||
|
||||
/* ── Guzzle HTTP ─────────────────────────────────────────── */
|
||||
static const char *guzzle_parents[] = {NULL};
|
||||
REG_TYPE("GuzzleHttp.Client", "Client", false, guzzle_parents);
|
||||
REG_TYPE("GuzzleHttp.ClientInterface", "ClientInterface", true, guzzle_parents);
|
||||
REG_METHOD("GuzzleHttp.ClientInterface", "request",
|
||||
cbm_type_named(arena, "Psr.Http.Message.ResponseInterface"));
|
||||
REG_METHOD("GuzzleHttp.ClientInterface", "send",
|
||||
cbm_type_named(arena, "Psr.Http.Message.ResponseInterface"));
|
||||
REG_METHOD("GuzzleHttp.ClientInterface", "sendAsync", MIXED);
|
||||
REG_METHOD("GuzzleHttp.Client", "request",
|
||||
cbm_type_named(arena, "Psr.Http.Message.ResponseInterface"));
|
||||
REG_METHOD("GuzzleHttp.Client", "get",
|
||||
cbm_type_named(arena, "Psr.Http.Message.ResponseInterface"));
|
||||
REG_METHOD("GuzzleHttp.Client", "post",
|
||||
cbm_type_named(arena, "Psr.Http.Message.ResponseInterface"));
|
||||
REG_METHOD("GuzzleHttp.Client", "put",
|
||||
cbm_type_named(arena, "Psr.Http.Message.ResponseInterface"));
|
||||
REG_METHOD("GuzzleHttp.Client", "delete",
|
||||
cbm_type_named(arena, "Psr.Http.Message.ResponseInterface"));
|
||||
|
||||
/* ── Twig ─────────────────────────────────────────────── */
|
||||
static const char *twig_parents[] = {NULL};
|
||||
REG_TYPE("Twig.Environment", "Environment", false, twig_parents);
|
||||
REG_TYPE("Twig.TemplateWrapper", "TemplateWrapper", false, twig_parents);
|
||||
REG_METHOD("Twig.Environment", "render", cbm_type_builtin(arena, "string"));
|
||||
REG_METHOD("Twig.Environment", "load",
|
||||
cbm_type_named(arena, "Twig.TemplateWrapper"));
|
||||
REG_METHOD("Twig.Environment", "createTemplate",
|
||||
cbm_type_named(arena, "Twig.TemplateWrapper"));
|
||||
REG_METHOD("Twig.TemplateWrapper", "render", cbm_type_builtin(arena, "string"));
|
||||
REG_METHOD("Twig.TemplateWrapper", "renderBlock", cbm_type_builtin(arena, "string"));
|
||||
|
||||
/* ── Eloquent Model magic-static methods (forwarded to Builder) ─ *
|
||||
*
|
||||
* Eloquent: every Model subclass has access to all Builder methods
|
||||
* via ::__callStatic. We register the full Builder method set as
|
||||
* static methods on Model so chains like User::where()->first()
|
||||
* resolve immediately. */
|
||||
{
|
||||
const char *em = "Illuminate.Database.Eloquent.Model";
|
||||
const CBMType *eb = cbm_type_named(arena, "Illuminate.Database.Eloquent.Builder");
|
||||
const CBMType *coll = cbm_type_named(arena, "Illuminate.Database.Eloquent.Collection");
|
||||
const CBMType *self = cbm_type_named(arena, "Illuminate.Database.Eloquent.Model");
|
||||
REG_METHOD(em, "whereIn", eb);
|
||||
REG_METHOD(em, "whereNotNull", eb);
|
||||
REG_METHOD(em, "whereHas", eb);
|
||||
REG_METHOD(em, "orderBy", eb);
|
||||
REG_METHOD(em, "limit", eb);
|
||||
REG_METHOD(em, "take", eb);
|
||||
REG_METHOD(em, "select", eb);
|
||||
REG_METHOD(em, "get", coll);
|
||||
REG_METHOD(em, "first", self);
|
||||
REG_METHOD(em, "firstOrFail", self);
|
||||
REG_METHOD(em, "firstOrCreate", self);
|
||||
}
|
||||
|
||||
/* (Generator already registered in the SPL section above.) */
|
||||
|
||||
/* ── Symfony Cache ─────────────────────────────────────── */
|
||||
static const char *cache_parents[] = {NULL};
|
||||
REG_TYPE("Symfony.Contracts.Cache.CacheInterface", "CacheInterface", true, cache_parents);
|
||||
REG_TYPE("Symfony.Contracts.Cache.ItemInterface", "ItemInterface", true, cache_parents);
|
||||
REG_TYPE("Psr.Cache.CacheItemPoolInterface", "CacheItemPoolInterface", true, cache_parents);
|
||||
REG_TYPE("Psr.Cache.CacheItemInterface", "CacheItemInterface", true, cache_parents);
|
||||
REG_METHOD("Symfony.Contracts.Cache.CacheInterface", "get", MIXED);
|
||||
REG_METHOD("Symfony.Contracts.Cache.CacheInterface", "delete", cbm_type_builtin(arena, "bool"));
|
||||
REG_METHOD("Symfony.Contracts.Cache.ItemInterface", "set",
|
||||
cbm_type_named(arena, "Symfony.Contracts.Cache.ItemInterface"));
|
||||
REG_METHOD("Symfony.Contracts.Cache.ItemInterface", "expiresAfter",
|
||||
cbm_type_named(arena, "Symfony.Contracts.Cache.ItemInterface"));
|
||||
REG_METHOD("Symfony.Contracts.Cache.ItemInterface", "get", MIXED);
|
||||
REG_METHOD("Psr.Cache.CacheItemPoolInterface", "getItem",
|
||||
cbm_type_named(arena, "Psr.Cache.CacheItemInterface"));
|
||||
REG_METHOD("Psr.Cache.CacheItemPoolInterface", "save",
|
||||
cbm_type_builtin(arena, "bool"));
|
||||
REG_METHOD("Psr.Cache.CacheItemInterface", "get", MIXED);
|
||||
REG_METHOD("Psr.Cache.CacheItemInterface", "set",
|
||||
cbm_type_named(arena, "Psr.Cache.CacheItemInterface"));
|
||||
|
||||
/* ── Symfony EventDispatcher ────────────────────────────── */
|
||||
static const char *event_parents[] = {NULL};
|
||||
REG_TYPE("Symfony.Contracts.EventDispatcher.EventDispatcherInterface",
|
||||
"EventDispatcherInterface", true, event_parents);
|
||||
REG_TYPE("Symfony.Contracts.EventDispatcher.Event", "Event", false, event_parents);
|
||||
REG_METHOD("Symfony.Contracts.EventDispatcher.EventDispatcherInterface", "dispatch", MIXED);
|
||||
|
||||
/* ── Symfony Mailer ─────────────────────────────────────── */
|
||||
static const char *mailer_parents[] = {NULL};
|
||||
REG_TYPE("Symfony.Component.Mailer.MailerInterface", "MailerInterface", true, mailer_parents);
|
||||
REG_TYPE("Symfony.Component.Mime.Email", "Email", false, mailer_parents);
|
||||
REG_METHOD("Symfony.Component.Mailer.MailerInterface", "send",
|
||||
cbm_type_builtin(arena, "void"));
|
||||
{
|
||||
const char *e = "Symfony.Component.Mime.Email";
|
||||
const CBMType *self = cbm_type_named(arena, "Symfony.Component.Mime.Email");
|
||||
REG_METHOD(e, "from", self);
|
||||
REG_METHOD(e, "to", self);
|
||||
REG_METHOD(e, "cc", self);
|
||||
REG_METHOD(e, "bcc", self);
|
||||
REG_METHOD(e, "subject", self);
|
||||
REG_METHOD(e, "text", self);
|
||||
REG_METHOD(e, "html", self);
|
||||
REG_METHOD(e, "attachFromPath", self);
|
||||
}
|
||||
|
||||
/* ── Symfony Validator ─────────────────────────────────── */
|
||||
static const char *validator_parents[] = {NULL};
|
||||
REG_TYPE("Symfony.Component.Validator.Validator.ValidatorInterface", "ValidatorInterface",
|
||||
true, validator_parents);
|
||||
REG_TYPE("Symfony.Component.Validator.ConstraintViolationListInterface",
|
||||
"ConstraintViolationListInterface", true, validator_parents);
|
||||
REG_METHOD("Symfony.Component.Validator.Validator.ValidatorInterface", "validate",
|
||||
cbm_type_named(arena,
|
||||
"Symfony.Component.Validator.ConstraintViolationListInterface"));
|
||||
REG_METHOD("Symfony.Component.Validator.ConstraintViolationListInterface", "count",
|
||||
cbm_type_builtin(arena, "int"));
|
||||
|
||||
/* ── Laravel HTTP Request / Response ──────────────────── */
|
||||
static const char *laravel_request_parents[] = {"Symfony.Component.HttpFoundation.Request",
|
||||
NULL};
|
||||
REG_TYPE("Illuminate.Http.Request", "Request", false, laravel_request_parents);
|
||||
REG_TYPE("Illuminate.Http.Response", "Response", false, laravel_request_parents);
|
||||
REG_TYPE("Illuminate.Http.JsonResponse", "JsonResponse", false, laravel_request_parents);
|
||||
REG_METHOD("Illuminate.Http.Request", "input", MIXED);
|
||||
REG_METHOD("Illuminate.Http.Request", "query", MIXED);
|
||||
REG_METHOD("Illuminate.Http.Request", "all", cbm_type_builtin(arena, "array"));
|
||||
REG_METHOD("Illuminate.Http.Request", "user", MIXED);
|
||||
REG_METHOD("Illuminate.Http.Request", "validate", cbm_type_builtin(arena, "array"));
|
||||
REG_METHOD("Illuminate.Http.Response", "header",
|
||||
cbm_type_named(arena, "Illuminate.Http.Response"));
|
||||
REG_METHOD("Illuminate.Http.Response", "withHeaders",
|
||||
cbm_type_named(arena, "Illuminate.Http.Response"));
|
||||
REG_METHOD("Illuminate.Http.JsonResponse", "header",
|
||||
cbm_type_named(arena, "Illuminate.Http.JsonResponse"));
|
||||
|
||||
/* ── Laravel Auth ─────────────────────────────────────── */
|
||||
static const char *auth_parents[] = {NULL};
|
||||
REG_TYPE("Illuminate.Contracts.Auth.Authenticatable", "Authenticatable", true,
|
||||
auth_parents);
|
||||
REG_TYPE("Illuminate.Contracts.Auth.Guard", "Guard", true, auth_parents);
|
||||
REG_METHOD("Illuminate.Contracts.Auth.Authenticatable", "getAuthIdentifier", MIXED);
|
||||
REG_METHOD("Illuminate.Contracts.Auth.Authenticatable", "getAuthPassword",
|
||||
cbm_type_builtin(arena, "string"));
|
||||
REG_METHOD("Illuminate.Contracts.Auth.Guard", "user",
|
||||
cbm_type_named(arena, "Illuminate.Contracts.Auth.Authenticatable"));
|
||||
REG_METHOD("Illuminate.Contracts.Auth.Guard", "check", cbm_type_builtin(arena, "bool"));
|
||||
REG_METHOD("Illuminate.Contracts.Auth.Guard", "id", MIXED);
|
||||
|
||||
/* ── Laravel Session / View ──────────────────────────── */
|
||||
static const char *session_parents[] = {NULL};
|
||||
REG_TYPE("Illuminate.Contracts.Session.Session", "Session", true, session_parents);
|
||||
REG_METHOD("Illuminate.Contracts.Session.Session", "get", MIXED);
|
||||
REG_METHOD("Illuminate.Contracts.Session.Session", "put", cbm_type_builtin(arena, "void"));
|
||||
REG_METHOD("Illuminate.Contracts.Session.Session", "has", cbm_type_builtin(arena, "bool"));
|
||||
REG_METHOD("Illuminate.Contracts.Session.Session", "flash", cbm_type_builtin(arena, "void"));
|
||||
|
||||
REG_TYPE("Illuminate.View.View", "View", false, session_parents);
|
||||
REG_METHOD("Illuminate.View.View", "render", cbm_type_builtin(arena, "string"));
|
||||
REG_METHOD("Illuminate.View.View", "with",
|
||||
cbm_type_named(arena, "Illuminate.View.View"));
|
||||
|
||||
/* ── ReactPHP / Promise ───────────────────────────────── */
|
||||
static const char *promise_parents[] = {NULL};
|
||||
REG_TYPE("React.Promise.PromiseInterface", "PromiseInterface", true, promise_parents);
|
||||
REG_TYPE("GuzzleHttp.Promise.PromiseInterface", "PromiseInterface", true, promise_parents);
|
||||
REG_METHOD("React.Promise.PromiseInterface", "then",
|
||||
cbm_type_named(arena, "React.Promise.PromiseInterface"));
|
||||
REG_METHOD("React.Promise.PromiseInterface", "catch",
|
||||
cbm_type_named(arena, "React.Promise.PromiseInterface"));
|
||||
REG_METHOD("React.Promise.PromiseInterface", "finally",
|
||||
cbm_type_named(arena, "React.Promise.PromiseInterface"));
|
||||
REG_METHOD("GuzzleHttp.Promise.PromiseInterface", "then",
|
||||
cbm_type_named(arena, "GuzzleHttp.Promise.PromiseInterface"));
|
||||
|
||||
/* ── Monolog (popular logger) ───────────────────────── */
|
||||
static const char *monolog_parents[] = {"Psr.Log.LoggerInterface", NULL};
|
||||
REG_TYPE("Monolog.Logger", "Logger", false, monolog_parents);
|
||||
/* Logger inherits info/warning/error/etc. methods from PSR LoggerInterface. */
|
||||
REG_METHOD("Monolog.Logger", "pushHandler",
|
||||
cbm_type_named(arena, "Monolog.Logger"));
|
||||
REG_METHOD("Monolog.Logger", "pushProcessor",
|
||||
cbm_type_named(arena, "Monolog.Logger"));
|
||||
|
||||
/* ── Reflection API ──────────────────────────────────── */
|
||||
static const char *reflection_parents[] = {NULL};
|
||||
REG_TYPE("ReflectionClass", "ReflectionClass", false, reflection_parents);
|
||||
REG_TYPE("ReflectionMethod", "ReflectionMethod", false, reflection_parents);
|
||||
REG_TYPE("ReflectionProperty", "ReflectionProperty", false, reflection_parents);
|
||||
REG_TYPE("ReflectionFunction", "ReflectionFunction", false, reflection_parents);
|
||||
REG_METHOD("ReflectionClass", "getName", cbm_type_builtin(arena, "string"));
|
||||
REG_METHOD("ReflectionClass", "getMethods", cbm_type_builtin(arena, "array"));
|
||||
REG_METHOD("ReflectionClass", "getMethod",
|
||||
cbm_type_named(arena, "ReflectionMethod"));
|
||||
REG_METHOD("ReflectionClass", "newInstance", MIXED);
|
||||
REG_METHOD("ReflectionClass", "newInstanceArgs", MIXED);
|
||||
REG_METHOD("ReflectionMethod", "invoke", MIXED);
|
||||
REG_METHOD("ReflectionMethod", "invokeArgs", MIXED);
|
||||
REG_METHOD("ReflectionMethod", "getName", cbm_type_builtin(arena, "string"));
|
||||
REG_METHOD("ReflectionProperty", "getValue", MIXED);
|
||||
REG_METHOD("ReflectionProperty", "setValue", cbm_type_builtin(arena, "void"));
|
||||
}
|
||||
|
||||
#undef REG_TYPE
|
||||
#undef REG_METHOD
|
||||
#undef MIXED
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,426 @@
|
||||
/* rust_crates_seed.c — Common-crate API seeds for the Rust LSP.
|
||||
*
|
||||
* Per RUST_LSP_FOLLOWUP.md A3: we don't run Cargo or download deps.
|
||||
* What we CAN do is hand-curate a small registered-method seed for the
|
||||
* most-used external crates (serde, tokio, anyhow, clap, regex, log,
|
||||
* futures, thiserror, reqwest, hyper, async-trait, parking_lot,
|
||||
* chrono, uuid, lazy_static, once_cell, rayon, …) so a project that
|
||||
* just `use`s these crates resolves the obvious calls instead of
|
||||
* leaving them all `unresolved`.
|
||||
*
|
||||
* Each entry is "best-effort" — we register the type with its trait
|
||||
* QNs in embedded_types and synthesize the high-frequency methods
|
||||
* with vaguely-correct return shapes. Anything we get wrong stays
|
||||
* `unresolved` rather than a wrong edge (per the doc's no-false-edge
|
||||
* policy: we only register what we're confident about).
|
||||
*
|
||||
* Designed to be appended to the existing stdlib register: takes the
|
||||
* same (reg, arena) pair. */
|
||||
|
||||
#include "../type_rep.h"
|
||||
#include "../type_registry.h"
|
||||
#include "../rust_lsp.h"
|
||||
#include <string.h>
|
||||
|
||||
/* Shorthand. Mirrors the macros in rust_stdlib_data.c. */
|
||||
#ifndef RUST_CRATES_SEED_INCLUDED
|
||||
#define RUST_CRATES_SEED_INCLUDED 1
|
||||
|
||||
#define CADD_TYPE(qn, sn, iface) do { \
|
||||
CBMRegisteredType _rt; \
|
||||
memset(&_rt, 0, sizeof(_rt)); \
|
||||
_rt.qualified_name = (qn); \
|
||||
_rt.short_name = (sn); \
|
||||
_rt.is_interface = (iface); \
|
||||
cbm_registry_add_type(reg, _rt); \
|
||||
} while (0)
|
||||
|
||||
#define CADD_FUNC(rcv, sn, qn, ret_type) do { \
|
||||
CBMRegisteredFunc _rf; \
|
||||
memset(&_rf, 0, sizeof(_rf)); \
|
||||
_rf.qualified_name = (qn); \
|
||||
_rf.short_name = (sn); \
|
||||
_rf.receiver_type = (rcv); \
|
||||
_rf.min_params = -1; \
|
||||
const CBMType** _rta = (const CBMType**)cbm_arena_alloc(arena, \
|
||||
2 * sizeof(const CBMType*)); \
|
||||
_rta[0] = (ret_type); \
|
||||
_rta[1] = NULL; \
|
||||
_rf.signature = cbm_type_func(arena, NULL, NULL, _rta); \
|
||||
cbm_registry_add_func(reg, _rf); \
|
||||
} while (0)
|
||||
|
||||
void cbm_rust_crates_register(CBMTypeRegistry* reg, CBMArena* arena) {
|
||||
const CBMType* t_unit = cbm_type_builtin(arena, "()");
|
||||
const CBMType* t_bool = cbm_type_builtin(arena, "bool");
|
||||
const CBMType* t_usize = cbm_type_builtin(arena, "usize");
|
||||
const CBMType* t_str_ref = cbm_type_reference(arena,
|
||||
cbm_type_builtin(arena, "str"));
|
||||
const CBMType* t_string = cbm_type_named(arena, "alloc.string.String");
|
||||
|
||||
/* ── serde — Serialize / Deserialize / json ─────────────── */
|
||||
CADD_TYPE("serde.Serialize", "Serialize", true);
|
||||
CADD_TYPE("serde.Deserialize", "Deserialize", true);
|
||||
CADD_TYPE("serde.Serializer", "Serializer", true);
|
||||
CADD_TYPE("serde.Deserializer", "Deserializer", true);
|
||||
CADD_TYPE("serde.de.Error", "Error", true);
|
||||
CADD_TYPE("serde.ser.Error", "Error", true);
|
||||
CADD_TYPE("serde_json.Value", "Value", false);
|
||||
CADD_TYPE("serde_json.Map", "Map", false);
|
||||
CADD_TYPE("serde_json.Number", "Number", false);
|
||||
CADD_TYPE("serde_json.Error", "Error", false);
|
||||
|
||||
CADD_FUNC("serde.Serialize", "serialize", "serde.Serialize.serialize", cbm_type_unknown());
|
||||
CADD_FUNC("serde.Deserialize", "deserialize", "serde.Deserialize.deserialize", cbm_type_unknown());
|
||||
CADD_FUNC("serde_json.Value", "is_null", "serde_json.Value.is_null", t_bool);
|
||||
CADD_FUNC("serde_json.Value", "is_bool", "serde_json.Value.is_bool", t_bool);
|
||||
CADD_FUNC("serde_json.Value", "is_number", "serde_json.Value.is_number", t_bool);
|
||||
CADD_FUNC("serde_json.Value", "is_string", "serde_json.Value.is_string", t_bool);
|
||||
CADD_FUNC("serde_json.Value", "is_array", "serde_json.Value.is_array", t_bool);
|
||||
CADD_FUNC("serde_json.Value", "is_object", "serde_json.Value.is_object", t_bool);
|
||||
CADD_FUNC("serde_json.Value", "as_bool", "serde_json.Value.as_bool", cbm_type_unknown());
|
||||
CADD_FUNC("serde_json.Value", "as_i64", "serde_json.Value.as_i64", cbm_type_unknown());
|
||||
CADD_FUNC("serde_json.Value", "as_f64", "serde_json.Value.as_f64", cbm_type_unknown());
|
||||
CADD_FUNC("serde_json.Value", "as_str", "serde_json.Value.as_str", cbm_type_unknown());
|
||||
CADD_FUNC("serde_json.Value", "as_array", "serde_json.Value.as_array", cbm_type_unknown());
|
||||
CADD_FUNC("serde_json.Value", "as_object", "serde_json.Value.as_object", cbm_type_unknown());
|
||||
CADD_FUNC("serde_json.Value", "get", "serde_json.Value.get", cbm_type_unknown());
|
||||
CADD_FUNC("serde_json.Value", "pointer", "serde_json.Value.pointer", cbm_type_unknown());
|
||||
CADD_FUNC("serde_json.Value", "to_string", "serde_json.Value.to_string", t_string);
|
||||
/* Free functions in serde_json. */
|
||||
CADD_FUNC(NULL, "from_str", "serde_json.from_str", cbm_type_unknown());
|
||||
CADD_FUNC(NULL, "to_string", "serde_json.to_string", cbm_type_unknown());
|
||||
CADD_FUNC(NULL, "from_value","serde_json.from_value",cbm_type_unknown());
|
||||
CADD_FUNC(NULL, "to_value", "serde_json.to_value", cbm_type_unknown());
|
||||
CADD_FUNC(NULL, "json", "serde_json.json", cbm_type_named(arena, "serde_json.Value"));
|
||||
|
||||
/* ── anyhow — Error / Result / ensure!/bail!/anyhow! ───── */
|
||||
CADD_TYPE("anyhow.Error", "Error", false);
|
||||
CADD_TYPE("anyhow.Result", "Result", false);
|
||||
CADD_TYPE("anyhow.Chain", "Chain", false);
|
||||
CADD_FUNC("anyhow.Error", "new", "anyhow.Error.new", cbm_type_named(arena, "anyhow.Error"));
|
||||
CADD_FUNC("anyhow.Error", "from", "anyhow.Error.from", cbm_type_named(arena, "anyhow.Error"));
|
||||
CADD_FUNC("anyhow.Error", "msg", "anyhow.Error.msg", cbm_type_named(arena, "anyhow.Error"));
|
||||
CADD_FUNC("anyhow.Error", "context", "anyhow.Error.context", cbm_type_named(arena, "anyhow.Error"));
|
||||
CADD_FUNC("anyhow.Error", "downcast", "anyhow.Error.downcast", cbm_type_unknown());
|
||||
CADD_FUNC("anyhow.Error", "downcast_ref","anyhow.Error.downcast_ref", cbm_type_unknown());
|
||||
CADD_FUNC("anyhow.Error", "is", "anyhow.Error.is", t_bool);
|
||||
CADD_FUNC("anyhow.Error", "chain", "anyhow.Error.chain", cbm_type_unknown());
|
||||
CADD_FUNC("anyhow.Error", "root_cause","anyhow.Error.root_cause", cbm_type_unknown());
|
||||
CADD_FUNC("anyhow.Error", "source", "anyhow.Error.source", cbm_type_unknown());
|
||||
/* Free functions. */
|
||||
CADD_FUNC(NULL, "anyhow", "anyhow.anyhow", cbm_type_named(arena, "anyhow.Error"));
|
||||
CADD_FUNC(NULL, "bail", "anyhow.bail", cbm_type_unknown());
|
||||
CADD_FUNC(NULL, "ensure", "anyhow.ensure", cbm_type_unknown());
|
||||
|
||||
/* ── thiserror — derive-only crate, but the Error trait. ─── */
|
||||
/* (Error trait is already covered under core.error.Error.) */
|
||||
|
||||
/* ── tokio — runtime + I/O + sync primitives. ───────────── */
|
||||
CADD_TYPE("tokio.runtime.Runtime", "Runtime", false);
|
||||
CADD_TYPE("tokio.runtime.Handle", "Handle", false);
|
||||
CADD_TYPE("tokio.runtime.Builder", "Builder", false);
|
||||
CADD_TYPE("tokio.task.JoinHandle", "JoinHandle",false);
|
||||
CADD_TYPE("tokio.sync.Mutex", "Mutex", false);
|
||||
CADD_TYPE("tokio.sync.RwLock", "RwLock", false);
|
||||
CADD_TYPE("tokio.sync.Semaphore", "Semaphore", false);
|
||||
CADD_TYPE("tokio.sync.Notify", "Notify", false);
|
||||
CADD_TYPE("tokio.sync.oneshot.Sender","Sender", false);
|
||||
CADD_TYPE("tokio.sync.oneshot.Receiver","Receiver", false);
|
||||
CADD_TYPE("tokio.sync.mpsc.Sender", "Sender", false);
|
||||
CADD_TYPE("tokio.sync.mpsc.Receiver", "Receiver", false);
|
||||
CADD_TYPE("tokio.sync.mpsc.UnboundedSender","UnboundedSender", false);
|
||||
CADD_TYPE("tokio.sync.mpsc.UnboundedReceiver","UnboundedReceiver", false);
|
||||
CADD_TYPE("tokio.net.TcpListener", "TcpListener", false);
|
||||
CADD_TYPE("tokio.net.TcpStream", "TcpStream", false);
|
||||
CADD_TYPE("tokio.io.BufReader", "BufReader", false);
|
||||
CADD_TYPE("tokio.io.BufWriter", "BufWriter", false);
|
||||
CADD_TYPE("tokio.time.Sleep", "Sleep", false);
|
||||
CADD_TYPE("tokio.time.Interval", "Interval", false);
|
||||
CADD_TYPE("tokio.fs.File", "File", false);
|
||||
CADD_TYPE("tokio.process.Command", "Command", false);
|
||||
CADD_TYPE("tokio.process.Child", "Child", false);
|
||||
|
||||
CADD_FUNC("tokio.runtime.Runtime", "new", "tokio.runtime.Runtime.new", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.runtime.Runtime", "block_on", "tokio.runtime.Runtime.block_on", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.runtime.Runtime", "spawn", "tokio.runtime.Runtime.spawn", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.runtime.Runtime", "handle", "tokio.runtime.Runtime.handle", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.runtime.Runtime", "shutdown_timeout","tokio.runtime.Runtime.shutdown_timeout", t_unit);
|
||||
CADD_FUNC("tokio.runtime.Builder", "new_multi_thread","tokio.runtime.Builder.new_multi_thread", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.runtime.Builder", "new_current_thread","tokio.runtime.Builder.new_current_thread", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.runtime.Builder", "worker_threads","tokio.runtime.Builder.worker_threads", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.runtime.Builder", "enable_all","tokio.runtime.Builder.enable_all", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.runtime.Builder", "build", "tokio.runtime.Builder.build", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.runtime.Handle", "current", "tokio.runtime.Handle.current", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.runtime.Handle", "spawn", "tokio.runtime.Handle.spawn", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.runtime.Handle", "block_on", "tokio.runtime.Handle.block_on", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.task.JoinHandle", "await", "tokio.task.JoinHandle.await", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.task.JoinHandle", "abort", "tokio.task.JoinHandle.abort", t_unit);
|
||||
CADD_FUNC("tokio.task.JoinHandle", "is_finished","tokio.task.JoinHandle.is_finished", t_bool);
|
||||
CADD_FUNC("tokio.sync.Mutex", "new", "tokio.sync.Mutex.new", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.sync.Mutex", "lock", "tokio.sync.Mutex.lock", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.sync.Mutex", "try_lock", "tokio.sync.Mutex.try_lock", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.sync.RwLock", "new", "tokio.sync.RwLock.new", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.sync.RwLock", "read", "tokio.sync.RwLock.read", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.sync.RwLock", "write", "tokio.sync.RwLock.write", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.sync.Semaphore", "new", "tokio.sync.Semaphore.new", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.sync.Semaphore", "acquire", "tokio.sync.Semaphore.acquire", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.sync.Semaphore", "add_permits","tokio.sync.Semaphore.add_permits", t_unit);
|
||||
CADD_FUNC("tokio.sync.Notify", "new", "tokio.sync.Notify.new", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.sync.Notify", "notified", "tokio.sync.Notify.notified", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.sync.Notify", "notify_one","tokio.sync.Notify.notify_one", t_unit);
|
||||
CADD_FUNC("tokio.sync.Notify", "notify_waiters","tokio.sync.Notify.notify_waiters", t_unit);
|
||||
CADD_FUNC("tokio.sync.mpsc.Sender", "send", "tokio.sync.mpsc.Sender.send", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.sync.mpsc.Sender", "try_send","tokio.sync.mpsc.Sender.try_send",cbm_type_unknown());
|
||||
CADD_FUNC("tokio.sync.mpsc.Receiver","recv", "tokio.sync.mpsc.Receiver.recv", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.sync.mpsc.Receiver","try_recv","tokio.sync.mpsc.Receiver.try_recv",cbm_type_unknown());
|
||||
CADD_FUNC("tokio.sync.oneshot.Sender","send", "tokio.sync.oneshot.Sender.send", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.sync.oneshot.Receiver","await","tokio.sync.oneshot.Receiver.await", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.net.TcpListener", "bind", "tokio.net.TcpListener.bind", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.net.TcpListener", "accept", "tokio.net.TcpListener.accept", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.net.TcpListener", "local_addr","tokio.net.TcpListener.local_addr",cbm_type_unknown());
|
||||
CADD_FUNC("tokio.net.TcpStream", "connect", "tokio.net.TcpStream.connect", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.net.TcpStream", "peer_addr","tokio.net.TcpStream.peer_addr", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.time.Sleep", "await", "tokio.time.Sleep.await", t_unit);
|
||||
CADD_FUNC("tokio.time.Interval", "tick", "tokio.time.Interval.tick", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.fs.File", "open", "tokio.fs.File.open", cbm_type_unknown());
|
||||
CADD_FUNC("tokio.fs.File", "create", "tokio.fs.File.create", cbm_type_unknown());
|
||||
/* Free functions. */
|
||||
CADD_FUNC(NULL, "spawn", "tokio.spawn", cbm_type_named(arena, "tokio.task.JoinHandle"));
|
||||
CADD_FUNC(NULL, "spawn_blocking","tokio.task.spawn_blocking", cbm_type_named(arena, "tokio.task.JoinHandle"));
|
||||
CADD_FUNC(NULL, "yield_now","tokio.task.yield_now", t_unit);
|
||||
CADD_FUNC(NULL, "sleep", "tokio.time.sleep", cbm_type_named(arena, "tokio.time.Sleep"));
|
||||
CADD_FUNC(NULL, "sleep_until","tokio.time.sleep_until", cbm_type_named(arena, "tokio.time.Sleep"));
|
||||
CADD_FUNC(NULL, "interval", "tokio.time.interval", cbm_type_named(arena, "tokio.time.Interval"));
|
||||
CADD_FUNC(NULL, "timeout", "tokio.time.timeout", cbm_type_unknown());
|
||||
CADD_FUNC(NULL, "read_to_string","tokio.fs.read_to_string", cbm_type_unknown());
|
||||
CADD_FUNC(NULL, "write", "tokio.fs.write", cbm_type_unknown());
|
||||
CADD_FUNC(NULL, "read", "tokio.fs.read", cbm_type_unknown());
|
||||
CADD_FUNC(NULL, "channel", "tokio.sync.mpsc.channel", cbm_type_unknown());
|
||||
CADD_FUNC(NULL, "unbounded_channel","tokio.sync.mpsc.unbounded_channel", cbm_type_unknown());
|
||||
CADD_FUNC(NULL, "join", "tokio.join", cbm_type_unknown());
|
||||
CADD_FUNC(NULL, "try_join", "tokio.try_join", cbm_type_unknown());
|
||||
CADD_FUNC(NULL, "select", "tokio.select", cbm_type_unknown());
|
||||
|
||||
/* ── clap — argument parsing. ─────────────────────────── */
|
||||
CADD_TYPE("clap.Parser", "Parser", true);
|
||||
CADD_TYPE("clap.Args", "Args", true);
|
||||
CADD_TYPE("clap.Subcommand", "Subcommand", true);
|
||||
CADD_TYPE("clap.ValueEnum", "ValueEnum", true);
|
||||
CADD_TYPE("clap.Command", "Command", false);
|
||||
CADD_TYPE("clap.Arg", "Arg", false);
|
||||
CADD_TYPE("clap.ArgMatches", "ArgMatches", false);
|
||||
|
||||
CADD_FUNC("clap.Parser", "parse", "clap.Parser.parse", cbm_type_unknown());
|
||||
CADD_FUNC("clap.Parser", "try_parse", "clap.Parser.try_parse", cbm_type_unknown());
|
||||
CADD_FUNC("clap.Parser", "parse_from", "clap.Parser.parse_from", cbm_type_unknown());
|
||||
CADD_FUNC("clap.Parser", "try_parse_from","clap.Parser.try_parse_from",cbm_type_unknown());
|
||||
CADD_FUNC("clap.Parser", "command", "clap.Parser.command", cbm_type_named(arena, "clap.Command"));
|
||||
CADD_FUNC("clap.Command","new", "clap.Command.new", cbm_type_named(arena, "clap.Command"));
|
||||
CADD_FUNC("clap.Command","arg", "clap.Command.arg", cbm_type_named(arena, "clap.Command"));
|
||||
CADD_FUNC("clap.Command","subcommand", "clap.Command.subcommand", cbm_type_named(arena, "clap.Command"));
|
||||
CADD_FUNC("clap.Command","about", "clap.Command.about", cbm_type_named(arena, "clap.Command"));
|
||||
CADD_FUNC("clap.Command","version", "clap.Command.version", cbm_type_named(arena, "clap.Command"));
|
||||
CADD_FUNC("clap.Command","author", "clap.Command.author", cbm_type_named(arena, "clap.Command"));
|
||||
CADD_FUNC("clap.Command","get_matches", "clap.Command.get_matches", cbm_type_named(arena, "clap.ArgMatches"));
|
||||
CADD_FUNC("clap.Command","get_matches_from","clap.Command.get_matches_from", cbm_type_named(arena, "clap.ArgMatches"));
|
||||
CADD_FUNC("clap.Arg", "new", "clap.Arg.new", cbm_type_named(arena, "clap.Arg"));
|
||||
CADD_FUNC("clap.Arg", "short", "clap.Arg.short", cbm_type_named(arena, "clap.Arg"));
|
||||
CADD_FUNC("clap.Arg", "long", "clap.Arg.long", cbm_type_named(arena, "clap.Arg"));
|
||||
CADD_FUNC("clap.Arg", "value_name", "clap.Arg.value_name", cbm_type_named(arena, "clap.Arg"));
|
||||
CADD_FUNC("clap.Arg", "required", "clap.Arg.required", cbm_type_named(arena, "clap.Arg"));
|
||||
CADD_FUNC("clap.Arg", "default_value", "clap.Arg.default_value", cbm_type_named(arena, "clap.Arg"));
|
||||
CADD_FUNC("clap.Arg", "help", "clap.Arg.help", cbm_type_named(arena, "clap.Arg"));
|
||||
CADD_FUNC("clap.ArgMatches","get_one", "clap.ArgMatches.get_one", cbm_type_unknown());
|
||||
CADD_FUNC("clap.ArgMatches","get_many", "clap.ArgMatches.get_many", cbm_type_unknown());
|
||||
CADD_FUNC("clap.ArgMatches","get_flag", "clap.ArgMatches.get_flag", t_bool);
|
||||
CADD_FUNC("clap.ArgMatches","contains_id","clap.ArgMatches.contains_id",t_bool);
|
||||
CADD_FUNC("clap.ArgMatches","subcommand", "clap.ArgMatches.subcommand",cbm_type_unknown());
|
||||
|
||||
/* ── regex — pattern matching. ────────────────────────── */
|
||||
CADD_TYPE("regex.Regex", "Regex", false);
|
||||
CADD_TYPE("regex.Captures", "Captures", false);
|
||||
CADD_TYPE("regex.Match", "Match", false);
|
||||
CADD_TYPE("regex.RegexBuilder","RegexBuilder", false);
|
||||
|
||||
CADD_FUNC("regex.Regex", "new", "regex.Regex.new", cbm_type_unknown());
|
||||
CADD_FUNC("regex.Regex", "is_match", "regex.Regex.is_match", t_bool);
|
||||
CADD_FUNC("regex.Regex", "find", "regex.Regex.find", cbm_type_unknown());
|
||||
CADD_FUNC("regex.Regex", "find_iter", "regex.Regex.find_iter", cbm_type_unknown());
|
||||
CADD_FUNC("regex.Regex", "captures", "regex.Regex.captures", cbm_type_unknown());
|
||||
CADD_FUNC("regex.Regex", "captures_iter","regex.Regex.captures_iter", cbm_type_unknown());
|
||||
CADD_FUNC("regex.Regex", "replace", "regex.Regex.replace", t_string);
|
||||
CADD_FUNC("regex.Regex", "replace_all", "regex.Regex.replace_all", t_string);
|
||||
CADD_FUNC("regex.Regex", "split", "regex.Regex.split", cbm_type_unknown());
|
||||
CADD_FUNC("regex.Regex", "as_str", "regex.Regex.as_str", t_str_ref);
|
||||
CADD_FUNC("regex.Captures","get", "regex.Captures.get", cbm_type_unknown());
|
||||
CADD_FUNC("regex.Captures","name", "regex.Captures.name", cbm_type_unknown());
|
||||
CADD_FUNC("regex.Captures","len", "regex.Captures.len", t_usize);
|
||||
CADD_FUNC("regex.Match", "as_str", "regex.Match.as_str", t_str_ref);
|
||||
CADD_FUNC("regex.Match", "start", "regex.Match.start", t_usize);
|
||||
CADD_FUNC("regex.Match", "end", "regex.Match.end", t_usize);
|
||||
CADD_FUNC("regex.Match", "range", "regex.Match.range", cbm_type_unknown());
|
||||
|
||||
/* ── log — logging macros + Logger trait. ─────────────── */
|
||||
CADD_TYPE("log.Logger", "Logger", true);
|
||||
CADD_TYPE("log.Level", "Level", false);
|
||||
CADD_TYPE("log.LevelFilter", "LevelFilter", false);
|
||||
CADD_TYPE("log.Metadata", "Metadata", false);
|
||||
CADD_TYPE("log.Record", "Record", false);
|
||||
/* The macros log!/info!/warn!/error!/debug!/trace! are matched by
|
||||
* name in rust_lsp.c's macro handler — register them as void
|
||||
* synthetic functions so the resolver can attribute them. */
|
||||
CADD_FUNC(NULL, "info", "log.info", t_unit);
|
||||
CADD_FUNC(NULL, "warn", "log.warn", t_unit);
|
||||
CADD_FUNC(NULL, "error", "log.error", t_unit);
|
||||
CADD_FUNC(NULL, "debug", "log.debug", t_unit);
|
||||
CADD_FUNC(NULL, "trace", "log.trace", t_unit);
|
||||
CADD_FUNC(NULL, "log", "log.log", t_unit);
|
||||
CADD_FUNC(NULL, "set_logger","log.set_logger", cbm_type_unknown());
|
||||
CADD_FUNC(NULL, "set_max_level","log.set_max_level", t_unit);
|
||||
|
||||
/* ── futures — Future combinators + stream / sink. ────── */
|
||||
CADD_TYPE("futures.Future", "Future", true);
|
||||
CADD_TYPE("futures.Stream", "Stream", true);
|
||||
CADD_TYPE("futures.Sink", "Sink", true);
|
||||
CADD_TYPE("futures.StreamExt","StreamExt",true);
|
||||
CADD_TYPE("futures.FutureExt","FutureExt",true);
|
||||
CADD_TYPE("futures.SinkExt", "SinkExt", true);
|
||||
CADD_TYPE("futures.executor.LocalPool","LocalPool", false);
|
||||
|
||||
CADD_FUNC("futures.StreamExt", "next", "futures.StreamExt.next", cbm_type_unknown());
|
||||
CADD_FUNC("futures.StreamExt", "map", "futures.StreamExt.map", cbm_type_unknown());
|
||||
CADD_FUNC("futures.StreamExt", "filter", "futures.StreamExt.filter", cbm_type_unknown());
|
||||
CADD_FUNC("futures.StreamExt", "filter_map", "futures.StreamExt.filter_map", cbm_type_unknown());
|
||||
CADD_FUNC("futures.StreamExt", "for_each", "futures.StreamExt.for_each", cbm_type_unknown());
|
||||
CADD_FUNC("futures.StreamExt", "collect", "futures.StreamExt.collect", cbm_type_unknown());
|
||||
CADD_FUNC("futures.StreamExt", "fold", "futures.StreamExt.fold", cbm_type_unknown());
|
||||
CADD_FUNC("futures.StreamExt", "take", "futures.StreamExt.take", cbm_type_unknown());
|
||||
CADD_FUNC("futures.StreamExt", "skip", "futures.StreamExt.skip", cbm_type_unknown());
|
||||
CADD_FUNC("futures.StreamExt", "chunks", "futures.StreamExt.chunks", cbm_type_unknown());
|
||||
CADD_FUNC("futures.StreamExt", "buffered", "futures.StreamExt.buffered", cbm_type_unknown());
|
||||
CADD_FUNC("futures.SinkExt", "send", "futures.SinkExt.send", cbm_type_unknown());
|
||||
CADD_FUNC("futures.SinkExt", "send_all", "futures.SinkExt.send_all", cbm_type_unknown());
|
||||
CADD_FUNC("futures.SinkExt", "flush", "futures.SinkExt.flush", cbm_type_unknown());
|
||||
CADD_FUNC("futures.SinkExt", "close", "futures.SinkExt.close", cbm_type_unknown());
|
||||
CADD_FUNC("futures.FutureExt", "boxed", "futures.FutureExt.boxed", cbm_type_unknown());
|
||||
CADD_FUNC("futures.FutureExt", "fuse", "futures.FutureExt.fuse", cbm_type_unknown());
|
||||
CADD_FUNC("futures.FutureExt", "shared", "futures.FutureExt.shared", cbm_type_unknown());
|
||||
|
||||
CADD_FUNC(NULL, "join_all", "futures.future.join_all", cbm_type_unknown());
|
||||
CADD_FUNC(NULL, "try_join_all", "futures.future.try_join_all", cbm_type_unknown());
|
||||
CADD_FUNC(NULL, "select", "futures.future.select", cbm_type_unknown());
|
||||
CADD_FUNC(NULL, "ready", "futures.future.ready", cbm_type_unknown());
|
||||
|
||||
/* ── parking_lot — drop-in std::sync alternatives. ────── */
|
||||
CADD_TYPE("parking_lot.Mutex", "Mutex", false);
|
||||
CADD_TYPE("parking_lot.RwLock", "RwLock", false);
|
||||
CADD_TYPE("parking_lot.MutexGuard", "MutexGuard", false);
|
||||
CADD_TYPE("parking_lot.RwLockReadGuard","RwLockReadGuard", false);
|
||||
CADD_TYPE("parking_lot.RwLockWriteGuard","RwLockWriteGuard", false);
|
||||
CADD_TYPE("parking_lot.Condvar", "Condvar", false);
|
||||
CADD_TYPE("parking_lot.Once", "Once", false);
|
||||
|
||||
CADD_FUNC("parking_lot.Mutex", "new", "parking_lot.Mutex.new", cbm_type_unknown());
|
||||
CADD_FUNC("parking_lot.Mutex", "lock", "parking_lot.Mutex.lock", cbm_type_unknown());
|
||||
CADD_FUNC("parking_lot.Mutex", "try_lock", "parking_lot.Mutex.try_lock", cbm_type_unknown());
|
||||
CADD_FUNC("parking_lot.RwLock", "new", "parking_lot.RwLock.new", cbm_type_unknown());
|
||||
CADD_FUNC("parking_lot.RwLock", "read", "parking_lot.RwLock.read", cbm_type_unknown());
|
||||
CADD_FUNC("parking_lot.RwLock", "write", "parking_lot.RwLock.write", cbm_type_unknown());
|
||||
|
||||
/* ── lazy_static / once_cell — singleton init. ────────── */
|
||||
CADD_TYPE("once_cell.sync.Lazy", "Lazy", false);
|
||||
CADD_TYPE("once_cell.sync.OnceCell","OnceCell", false);
|
||||
CADD_TYPE("once_cell.unsync.Lazy","Lazy", false);
|
||||
CADD_FUNC("once_cell.sync.Lazy","new","once_cell.sync.Lazy.new",cbm_type_unknown());
|
||||
CADD_FUNC("once_cell.sync.OnceCell","new","once_cell.sync.OnceCell.new",cbm_type_unknown());
|
||||
CADD_FUNC("once_cell.sync.OnceCell","get","once_cell.sync.OnceCell.get",cbm_type_unknown());
|
||||
CADD_FUNC("once_cell.sync.OnceCell","set","once_cell.sync.OnceCell.set",cbm_type_unknown());
|
||||
CADD_FUNC("once_cell.sync.OnceCell","get_or_init","once_cell.sync.OnceCell.get_or_init", cbm_type_unknown());
|
||||
|
||||
/* ── chrono — date/time. ────────────────────────────── */
|
||||
CADD_TYPE("chrono.DateTime", "DateTime", false);
|
||||
CADD_TYPE("chrono.NaiveDate", "NaiveDate", false);
|
||||
CADD_TYPE("chrono.NaiveTime", "NaiveTime", false);
|
||||
CADD_TYPE("chrono.NaiveDateTime","NaiveDateTime",false);
|
||||
CADD_TYPE("chrono.Duration", "Duration", false);
|
||||
CADD_TYPE("chrono.Utc", "Utc", false);
|
||||
CADD_TYPE("chrono.Local", "Local", false);
|
||||
|
||||
CADD_FUNC("chrono.DateTime", "now", "chrono.DateTime.now", cbm_type_unknown());
|
||||
CADD_FUNC("chrono.DateTime", "format", "chrono.DateTime.format", cbm_type_unknown());
|
||||
CADD_FUNC("chrono.DateTime", "timestamp", "chrono.DateTime.timestamp", cbm_type_unknown());
|
||||
CADD_FUNC("chrono.DateTime", "to_rfc3339","chrono.DateTime.to_rfc3339",t_string);
|
||||
CADD_FUNC("chrono.Utc", "now", "chrono.Utc.now", cbm_type_unknown());
|
||||
CADD_FUNC("chrono.Local", "now", "chrono.Local.now", cbm_type_unknown());
|
||||
CADD_FUNC("chrono.Duration", "seconds", "chrono.Duration.seconds", cbm_type_unknown());
|
||||
CADD_FUNC("chrono.Duration", "minutes", "chrono.Duration.minutes", cbm_type_unknown());
|
||||
CADD_FUNC("chrono.Duration", "hours", "chrono.Duration.hours", cbm_type_unknown());
|
||||
CADD_FUNC("chrono.Duration", "days", "chrono.Duration.days", cbm_type_unknown());
|
||||
|
||||
/* ── uuid — IDs. ────────────────────────────────────── */
|
||||
CADD_TYPE("uuid.Uuid", "Uuid", false);
|
||||
CADD_FUNC("uuid.Uuid", "new_v4", "uuid.Uuid.new_v4", cbm_type_unknown());
|
||||
CADD_FUNC("uuid.Uuid", "nil", "uuid.Uuid.nil", cbm_type_unknown());
|
||||
CADD_FUNC("uuid.Uuid", "parse_str", "uuid.Uuid.parse_str", cbm_type_unknown());
|
||||
CADD_FUNC("uuid.Uuid", "to_string", "uuid.Uuid.to_string", t_string);
|
||||
CADD_FUNC("uuid.Uuid", "to_hyphenated","uuid.Uuid.to_hyphenated", cbm_type_unknown());
|
||||
CADD_FUNC("uuid.Uuid", "as_bytes", "uuid.Uuid.as_bytes", cbm_type_unknown());
|
||||
|
||||
/* ── reqwest — HTTP client. ──────────────────────────── */
|
||||
CADD_TYPE("reqwest.Client", "Client", false);
|
||||
CADD_TYPE("reqwest.RequestBuilder","RequestBuilder", false);
|
||||
CADD_TYPE("reqwest.Response", "Response", false);
|
||||
CADD_TYPE("reqwest.Error", "Error", false);
|
||||
CADD_TYPE("reqwest.Url", "Url", false);
|
||||
CADD_TYPE("reqwest.header.HeaderMap","HeaderMap", false);
|
||||
|
||||
CADD_FUNC("reqwest.Client", "new", "reqwest.Client.new", cbm_type_named(arena, "reqwest.Client"));
|
||||
CADD_FUNC("reqwest.Client", "builder", "reqwest.Client.builder", cbm_type_unknown());
|
||||
CADD_FUNC("reqwest.Client", "get", "reqwest.Client.get", cbm_type_named(arena, "reqwest.RequestBuilder"));
|
||||
CADD_FUNC("reqwest.Client", "post", "reqwest.Client.post", cbm_type_named(arena, "reqwest.RequestBuilder"));
|
||||
CADD_FUNC("reqwest.Client", "put", "reqwest.Client.put", cbm_type_named(arena, "reqwest.RequestBuilder"));
|
||||
CADD_FUNC("reqwest.Client", "delete", "reqwest.Client.delete", cbm_type_named(arena, "reqwest.RequestBuilder"));
|
||||
CADD_FUNC("reqwest.Client", "request", "reqwest.Client.request", cbm_type_named(arena, "reqwest.RequestBuilder"));
|
||||
CADD_FUNC("reqwest.RequestBuilder","header","reqwest.RequestBuilder.header", cbm_type_named(arena, "reqwest.RequestBuilder"));
|
||||
CADD_FUNC("reqwest.RequestBuilder","headers","reqwest.RequestBuilder.headers", cbm_type_named(arena, "reqwest.RequestBuilder"));
|
||||
CADD_FUNC("reqwest.RequestBuilder","body", "reqwest.RequestBuilder.body", cbm_type_named(arena, "reqwest.RequestBuilder"));
|
||||
CADD_FUNC("reqwest.RequestBuilder","json", "reqwest.RequestBuilder.json", cbm_type_named(arena, "reqwest.RequestBuilder"));
|
||||
CADD_FUNC("reqwest.RequestBuilder","form", "reqwest.RequestBuilder.form", cbm_type_named(arena, "reqwest.RequestBuilder"));
|
||||
CADD_FUNC("reqwest.RequestBuilder","query","reqwest.RequestBuilder.query", cbm_type_named(arena, "reqwest.RequestBuilder"));
|
||||
CADD_FUNC("reqwest.RequestBuilder","send", "reqwest.RequestBuilder.send", cbm_type_named(arena, "reqwest.Response"));
|
||||
CADD_FUNC("reqwest.Response","status", "reqwest.Response.status", cbm_type_unknown());
|
||||
CADD_FUNC("reqwest.Response","headers", "reqwest.Response.headers", cbm_type_unknown());
|
||||
CADD_FUNC("reqwest.Response","text", "reqwest.Response.text", cbm_type_unknown());
|
||||
CADD_FUNC("reqwest.Response","json", "reqwest.Response.json", cbm_type_unknown());
|
||||
CADD_FUNC("reqwest.Response","bytes", "reqwest.Response.bytes", cbm_type_unknown());
|
||||
CADD_FUNC("reqwest.Url", "parse", "reqwest.Url.parse", cbm_type_unknown());
|
||||
|
||||
/* ── rayon — parallel iteration. ────────────────────── */
|
||||
CADD_TYPE("rayon.iter.ParallelIterator","ParallelIterator", true);
|
||||
CADD_TYPE("rayon.iter.IntoParallelIterator","IntoParallelIterator", true);
|
||||
|
||||
CADD_FUNC("rayon.iter.ParallelIterator","map", "rayon.iter.ParallelIterator.map", cbm_type_unknown());
|
||||
CADD_FUNC("rayon.iter.ParallelIterator","filter", "rayon.iter.ParallelIterator.filter", cbm_type_unknown());
|
||||
CADD_FUNC("rayon.iter.ParallelIterator","for_each", "rayon.iter.ParallelIterator.for_each", t_unit);
|
||||
CADD_FUNC("rayon.iter.ParallelIterator","collect", "rayon.iter.ParallelIterator.collect", cbm_type_unknown());
|
||||
CADD_FUNC("rayon.iter.ParallelIterator","reduce", "rayon.iter.ParallelIterator.reduce", cbm_type_unknown());
|
||||
CADD_FUNC("rayon.iter.ParallelIterator","sum", "rayon.iter.ParallelIterator.sum", cbm_type_unknown());
|
||||
CADD_FUNC("rayon.iter.ParallelIterator","count", "rayon.iter.ParallelIterator.count", t_usize);
|
||||
CADD_FUNC("rayon.iter.ParallelIterator","any", "rayon.iter.ParallelIterator.any", t_bool);
|
||||
CADD_FUNC("rayon.iter.ParallelIterator","all", "rayon.iter.ParallelIterator.all", t_bool);
|
||||
CADD_FUNC("rayon.iter.IntoParallelIterator","into_par_iter","rayon.iter.IntoParallelIterator.into_par_iter", cbm_type_unknown());
|
||||
|
||||
CADD_FUNC(NULL, "scope", "rayon.scope", cbm_type_unknown());
|
||||
CADD_FUNC(NULL, "par_iter", "rayon.par_iter", cbm_type_unknown());
|
||||
CADD_FUNC(NULL, "spawn", "rayon.spawn", t_unit);
|
||||
CADD_FUNC(NULL, "join", "rayon.join", cbm_type_unknown());
|
||||
|
||||
/* ── async-trait / async_trait — typically derive-only. ──
|
||||
* Calls to trait methods are resolved through the normal trait
|
||||
* dispatch since async_trait emits real Rust impl blocks. No
|
||||
* extra registration needed. */
|
||||
(void)t_str_ref;
|
||||
}
|
||||
|
||||
#endif /* RUST_CRATES_SEED_INCLUDED */
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,160 @@
|
||||
#ifndef CBM_LSP_GO_LSP_H
|
||||
#define CBM_LSP_GO_LSP_H
|
||||
|
||||
#include "type_rep.h"
|
||||
#include "scope.h"
|
||||
#include "type_registry.h"
|
||||
#include "../cbm.h"
|
||||
|
||||
// GoLSPContext holds state for Go expression type evaluation within a file.
|
||||
typedef struct {
|
||||
CBMArena* arena;
|
||||
const char* source;
|
||||
int source_len;
|
||||
const CBMTypeRegistry* registry;
|
||||
CBMScope* current_scope;
|
||||
|
||||
// Import map: local_name -> package QN (arena-allocated)
|
||||
const char** import_local_names; // NULL-terminated
|
||||
const char** import_package_qns; // NULL-terminated
|
||||
int import_count;
|
||||
|
||||
// Current function context
|
||||
const char* enclosing_func_qn;
|
||||
const char* package_qn; // module QN for this file
|
||||
|
||||
// Output: resolved calls accumulate here
|
||||
CBMResolvedCallArray* resolved_calls;
|
||||
|
||||
// AST-walk recursion depth for resolve_calls_in_node (guards stack overflow
|
||||
// on deeply-nested/cyclic files; see cbm_lsp_max_walk_depth). Zero via memset.
|
||||
int walk_depth;
|
||||
|
||||
// Debug mode (CBM_LSP_DEBUG env)
|
||||
bool debug;
|
||||
} GoLSPContext;
|
||||
|
||||
// Initialize a GoLSPContext for processing one file.
|
||||
void go_lsp_init(GoLSPContext* ctx, CBMArena* arena, const char* source, int source_len,
|
||||
const CBMTypeRegistry* registry, const char* package_qn, CBMResolvedCallArray* out);
|
||||
|
||||
// Add an import mapping.
|
||||
void go_lsp_add_import(GoLSPContext* ctx, const char* local_name, const char* package_qn);
|
||||
|
||||
// Process all functions in a file's AST, evaluating types and resolving calls.
|
||||
// root is the tree-sitter root node of the file.
|
||||
void go_lsp_process_file(GoLSPContext* ctx, TSNode root);
|
||||
|
||||
// Evaluate the type of a Go expression node.
|
||||
const CBMType* go_eval_expr_type(GoLSPContext* ctx, TSNode node);
|
||||
|
||||
// Parse a Go type AST node into a CBMType.
|
||||
const CBMType* go_parse_type_node(GoLSPContext* ctx, TSNode node);
|
||||
|
||||
// Process a Go statement, binding variables into the current scope.
|
||||
void go_process_statement(GoLSPContext* ctx, TSNode node);
|
||||
|
||||
// Evaluate a builtin call (make, new, append, len, cap, delete).
|
||||
const CBMType* go_eval_builtin_call(GoLSPContext* ctx, const char* name, TSNode args);
|
||||
|
||||
// Look up a field or method on a type, traversing embedded types.
|
||||
const CBMRegisteredFunc* go_lookup_field_or_method(GoLSPContext* ctx,
|
||||
const char* type_qn, const char* member_name);
|
||||
|
||||
// Entry point: build registry from file's own defs + run LSP resolution.
|
||||
// Called from cbm_extract_file() after definitions and imports are extracted.
|
||||
void cbm_run_go_lsp(CBMArena* arena, CBMFileResult* result,
|
||||
const char* source, int source_len, TSNode root);
|
||||
|
||||
// Register Go stdlib types and functions into a registry.
|
||||
// Auto-generated by scripts/gen-go-stdlib.go.
|
||||
void cbm_go_stdlib_register(CBMTypeRegistry* reg, CBMArena* arena);
|
||||
|
||||
// --- Cross-file LSP resolution ---
|
||||
|
||||
// Simplified definition for cross-file type/function registration.
|
||||
// String fields are borrowed (caller owns memory until cbm_run_go_lsp_cross returns).
|
||||
typedef struct {
|
||||
const char* qualified_name;
|
||||
const char* short_name;
|
||||
const char* label; // "Function", "Method", "Type", "Interface"
|
||||
const char* receiver_type; // for methods: receiver type QN (NULL for functions)
|
||||
const char* def_module_qn; // module QN where this def lives (for type text qualification)
|
||||
const char* return_types; // "|"-separated return type texts, e.g. "*File|error"
|
||||
const char* embedded_types; // "|"-separated embedded type QNs (for struct embedding)
|
||||
const char* field_defs; // "|"-separated "name:type" pairs (for struct fields, e.g. "Binder:Binder|Name:string")
|
||||
const char* method_names_str; // "|"-separated method names for interfaces (e.g. "Get|Put|Delete")
|
||||
bool is_interface;
|
||||
CBMLanguage lang; // language of the file that defined this — used by Tier 2 per-language registry build to filter all_defs
|
||||
const char* namespace_name; // declared namespace/package for source-root-independent JVM filtering
|
||||
} CBMLSPDef;
|
||||
|
||||
// Parse source, build registry from defs + stdlib, run LSP.
|
||||
// Re-parses source with tree-sitter internally.
|
||||
// defs includes both file-local and cross-file definitions.
|
||||
// import_names/import_qns are parallel arrays of length import_count.
|
||||
// Results written to out (arena-allocated).
|
||||
void cbm_run_go_lsp_cross(
|
||||
CBMArena* arena,
|
||||
const char* source, int source_len,
|
||||
const char* module_qn,
|
||||
CBMLSPDef* defs, int def_count,
|
||||
const char** import_names, const char** import_qns, int import_count,
|
||||
TSTree* cached_tree, // NULL = parse internally
|
||||
CBMResolvedCallArray* out);
|
||||
|
||||
/* Tier 2 (gopls package-summary pattern):
|
||||
* Build a project-wide, finalized CBMTypeRegistry from all Go defs ONCE.
|
||||
* Run cross-file LSP per file using that shared registry — no per-file
|
||||
* registry build, O(1) lookups, no Phase 1b/1c mutations. Reg is borrowed
|
||||
* from arena (arena owns the storage); reg pointer is valid for arena
|
||||
* lifetime. */
|
||||
CBMTypeRegistry* cbm_go_build_cross_registry(
|
||||
CBMArena* arena, CBMLSPDef* defs, int def_count);
|
||||
|
||||
void cbm_run_go_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, // NULL = parse internally
|
||||
CBMResolvedCallArray* out);
|
||||
|
||||
/* Tier 3: AST-walk-free fast resolver. Iterates result->calls and
|
||||
* resolves fully-qualified calls (pkg.Func) directly from the
|
||||
* pre-built registry — no tree-sitter parse, no AST walk. Returns
|
||||
* the count of calls that STILL need the slow path (methods on
|
||||
* locals + qualified calls we couldn't resolve). When the return
|
||||
* value is 0, the caller can skip the cbm_run_go_lsp_cross_with_
|
||||
* registry parse+walk entirely. Resolved entries are appended to
|
||||
* result->resolved_calls in result->arena. */
|
||||
int cbm_go_fast_resolve_qualified_calls(
|
||||
CBMFileResult* result,
|
||||
CBMTypeRegistry* reg,
|
||||
const char** import_names, const char** import_qns, int import_count);
|
||||
|
||||
// --- Batch cross-file LSP ---
|
||||
|
||||
// Per-file input for batch Go LSP processing.
|
||||
typedef struct {
|
||||
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
|
||||
int def_count;
|
||||
const char** import_names; // parallel arrays, import_count long
|
||||
const char** import_qns;
|
||||
int import_count;
|
||||
} CBMBatchGoLSPFile;
|
||||
|
||||
// Process multiple Go files' cross-file LSP in one CGo call.
|
||||
// out must point to file_count pre-zeroed CBMResolvedCallArray structs.
|
||||
// Uses per-file arenas internally; results are copied to the output arena.
|
||||
void cbm_batch_go_lsp_cross(
|
||||
CBMArena* arena,
|
||||
CBMBatchGoLSPFile* files, int file_count,
|
||||
CBMResolvedCallArray* out);
|
||||
|
||||
#endif // CBM_LSP_GO_LSP_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,168 @@
|
||||
#ifndef CBM_LSP_JAVA_LSP_H
|
||||
#define CBM_LSP_JAVA_LSP_H
|
||||
|
||||
/*
|
||||
* java_lsp.h — Pure-C Java semantic resolver.
|
||||
*
|
||||
* Reverse-engineered from JLS §6 (Names) and §15 (Expressions) plus the
|
||||
* algorithm shape used by Eclipse JDT-LS / java-language-server (which both
|
||||
* delegate to javac.com.sun.source). The goal is parity with what JDT-LS
|
||||
* exposes through textDocument/definition + textDocument/references for
|
||||
* call-site resolution, *without* shelling out to javac.
|
||||
*
|
||||
* Mirrors the structure of go_lsp.h / c_lsp.h / php_lsp.h / py_lsp.h.
|
||||
*
|
||||
* Resolution scheme (single file):
|
||||
* 1. Tree-sitter parses Java source into AST.
|
||||
* 2. Build a CBMTypeRegistry from this file's CBMDefinitions + Java
|
||||
* stdlib (java.lang.*, java.util.*, java.io.*, java.util.function.*,
|
||||
* java.util.stream.*).
|
||||
* 3. Walk the AST and for every method_invocation / object_creation /
|
||||
* field_access expression, evaluate the expression's type using
|
||||
* JLS-style scope chains (block → method params → class members →
|
||||
* superclass chain → outer class → import single → import on-demand →
|
||||
* java.lang → same package).
|
||||
* 4. Match the textual call (callee name + arity) against the resolved
|
||||
* receiver type's method set, walking superclasses and interfaces.
|
||||
* Best-overload resolution falls back to argument count match.
|
||||
* 5. Emit CBMResolvedCall entries with confidence ≥ 0.6 (the LSP floor
|
||||
* enforced by lsp_resolve.h).
|
||||
*/
|
||||
|
||||
#include "type_rep.h"
|
||||
#include "scope.h"
|
||||
#include "type_registry.h"
|
||||
#include "../cbm.h"
|
||||
#include "go_lsp.h" /* CBMLSPDef, CBMResolvedCallArray reused across languages */
|
||||
|
||||
/* Java `use`-style import kinds. Mirrors PHP's enum with Java semantics. */
|
||||
enum {
|
||||
CBM_JAVA_IMPORT_TYPE = 0, /* import com.foo.Bar; — type import */
|
||||
CBM_JAVA_IMPORT_STATIC = 1, /* import static com.foo.Bar.method; */
|
||||
CBM_JAVA_IMPORT_ON_DEMAND = 2, /* import com.foo.*; — package on-demand */
|
||||
CBM_JAVA_IMPORT_STATIC_OD = 3, /* import static com.foo.Bar.*; */
|
||||
};
|
||||
|
||||
/* Per-file resolution context. */
|
||||
typedef struct {
|
||||
CBMArena *arena;
|
||||
const char *source;
|
||||
int source_len;
|
||||
const CBMTypeRegistry *registry;
|
||||
CBMScope *current_scope;
|
||||
|
||||
/* Java package — the package declared in the file ("com.example"), or
|
||||
* empty string for the unnamed package. Stored in dotted form. */
|
||||
const char *package_name;
|
||||
|
||||
/* The path-derived module QN for this file (passed in from the caller),
|
||||
* used as the QN prefix for types defined here. */
|
||||
const char *module_qn;
|
||||
|
||||
/* Import map. Each entry is one of CBM_JAVA_IMPORT_*.
|
||||
* - TYPE: local_name="Bar", target_qn="com.foo.Bar"
|
||||
* - STATIC: local_name="sqrt", target_qn="java.lang.Math.sqrt"
|
||||
* - ON_DEMAND: local_name="*", target_qn="com.foo"
|
||||
* - STATIC_OD: local_name="*", target_qn="java.lang.Math"
|
||||
*/
|
||||
const char **import_local_names;
|
||||
const char **import_target_qns;
|
||||
int *import_kinds;
|
||||
int import_count;
|
||||
int import_cap;
|
||||
|
||||
/* Current enclosing context. */
|
||||
const char *enclosing_method_qn; /* QN of the nearest method/ctor */
|
||||
const char *enclosing_class_qn; /* QN of the nearest class/interface/enum */
|
||||
const char *enclosing_super_qn; /* QN of the immediate superclass (NULL ⇒ Object) */
|
||||
const char *enclosing_class_short; /* short name of enclosing class — for "this" + ctor */
|
||||
const char **enclosing_class_stack; /* nested-class stack (enclosing_class_qn at each depth) */
|
||||
int enclosing_class_depth;
|
||||
int enclosing_class_cap;
|
||||
|
||||
/* Output: resolved + diagnostic call edges. */
|
||||
CBMResolvedCallArray *resolved_calls;
|
||||
|
||||
/* Recursion guards. */
|
||||
int eval_depth;
|
||||
int statement_depth;
|
||||
int walk_depth; /* java_resolve_calls_in_node self-recursion (AST nesting) */
|
||||
|
||||
/* Debug mode (CBM_LSP_DEBUG env). */
|
||||
bool debug;
|
||||
} JavaLSPContext;
|
||||
|
||||
/* ── Initialization / configuration ───────────────────────────────── */
|
||||
|
||||
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);
|
||||
|
||||
void java_lsp_add_import(JavaLSPContext *ctx, const char *local_name, const char *target_qn,
|
||||
int kind);
|
||||
|
||||
/* ── Walking / resolution ─────────────────────────────────────────── */
|
||||
|
||||
void java_lsp_process_file(JavaLSPContext *ctx, TSNode root);
|
||||
|
||||
/* Evaluate the type of an arbitrary Java expression node. May return
|
||||
* cbm_type_unknown(); never returns NULL. */
|
||||
const CBMType *java_eval_expr_type(JavaLSPContext *ctx, TSNode node);
|
||||
|
||||
/* Convert a Java type AST node (type_identifier, generic_type, array_type,
|
||||
* scoped_type_identifier, void_type, integral_type, ...) into a CBMType. */
|
||||
const CBMType *java_parse_type_node(JavaLSPContext *ctx, TSNode node);
|
||||
|
||||
/* Process a Java statement, binding any declared variables/parameters into
|
||||
* the current scope. */
|
||||
void java_process_statement(JavaLSPContext *ctx, TSNode node);
|
||||
|
||||
/* Resolve a Java type name (bare or qualified) against the current scope
|
||||
* (imports + java.lang + same package). Returns the registered FQN or NULL. */
|
||||
const char *java_resolve_type_name(JavaLSPContext *ctx, const char *name);
|
||||
|
||||
/* Lookup a method on a class, walking the super-chain and implemented
|
||||
* interfaces. Returns the matched function or NULL. */
|
||||
const CBMRegisteredFunc *java_lookup_method(JavaLSPContext *ctx, const char *class_qn,
|
||||
const char *method_name, int arg_count);
|
||||
|
||||
/* Lookup a field on a class, walking the super-chain. Returns the field's
|
||||
* type, or cbm_type_unknown() on miss. */
|
||||
const CBMType *java_lookup_field_type(JavaLSPContext *ctx, const char *class_qn,
|
||||
const char *field_name);
|
||||
|
||||
/* ── Top-level entry points ───────────────────────────────────────── */
|
||||
|
||||
/* Single-file LSP: build registry from file defs + stdlib, walk and resolve. */
|
||||
void cbm_run_java_lsp(CBMArena *arena, CBMFileResult *result, const char *source, int source_len,
|
||||
TSNode root);
|
||||
|
||||
/* Cross-file LSP: build registry from defs + stdlib, re-parse if needed,
|
||||
* walk and resolve. defs include both local + cross-file definitions. */
|
||||
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);
|
||||
|
||||
/* Register the Java standard library (java.lang/util/io/etc.) into reg.
|
||||
* Implementation lives in generated/java_stdlib_data.c. */
|
||||
void cbm_java_stdlib_register(CBMTypeRegistry *reg, CBMArena *arena);
|
||||
|
||||
/* ── Batch cross-file LSP ─────────────────────────────────────────── */
|
||||
|
||||
typedef struct {
|
||||
const char *source;
|
||||
int source_len;
|
||||
const char *module_qn;
|
||||
TSTree *cached_tree;
|
||||
CBMLSPDef *defs;
|
||||
int def_count;
|
||||
const char **import_names;
|
||||
const char **import_qns;
|
||||
int import_count;
|
||||
} CBMBatchJavaLSPFile;
|
||||
|
||||
void cbm_batch_java_lsp_cross(CBMArena *arena, CBMBatchJavaLSPFile *files, int file_count,
|
||||
CBMResolvedCallArray *out);
|
||||
|
||||
#endif /* CBM_LSP_JAVA_LSP_H */
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* kotlin_builtins.c — Minimal Kotlin universal builtins as real graph nodes.
|
||||
*
|
||||
* When a method call lands on an unknown-typed receiver and the member is one
|
||||
* of the universal kotlin.Any methods (toString / equals / hashCode), the
|
||||
* Kotlin LSP resolves it to "kotlin.Any.<member>" and emits the lsp_kt_any
|
||||
* strategy (kotlin_lsp.c, kt_emit_resolved). Any is the supertype of every
|
||||
* Kotlin reference, so this is the same target the fwcd LSP resolves to.
|
||||
*
|
||||
* The missing piece is downstream: pass_calls.c only writes a CALLS edge when
|
||||
* cbm_pipeline_lsp_target_node() resolves the callee_qn to a graph node
|
||||
* (src/pipeline/lsp_resolve.h). There is no "kotlin.Any" node in the graph, so
|
||||
* the resolved call is dropped and the strategy never lands on an edge
|
||||
* (callable=0).
|
||||
*
|
||||
* Fix: inject a small, fixed set of kotlin.Any definitions into result->defs
|
||||
* during the per-file Kotlin LSP run (cbm_run_kotlin_lsp, which executes inside
|
||||
* cbm_extract_file, BEFORE the parallel pipeline mints def nodes from
|
||||
* result->defs). The graph therefore gains real "kotlin.Any[.<method>]" nodes
|
||||
* that the lsp_kt_any edges target. The QNs here MUST match what kt_emit_resolved
|
||||
* emits as callee_qn ("kotlin.Any.<member>").
|
||||
*
|
||||
* Node minting upserts by QN (cbm_gbuf_upsert_node), so injecting the same
|
||||
* builtins per Kotlin file collapses to one node per QN — no duplicates.
|
||||
*
|
||||
* Self-contained: #included from kotlin_lsp.c only (amalgamation pattern; see
|
||||
* lsp_all.c). Not a standalone translation unit. Mirror of py_builtins.c.
|
||||
*/
|
||||
|
||||
/* A single builtin entry to mint as a graph node. */
|
||||
typedef struct {
|
||||
const char *qn; /* graph QN — MUST equal the kt_emit_resolved callee_qn */
|
||||
const char *name; /* short name (last segment of qn) */
|
||||
const char *label; /* "Class" | "Method" */
|
||||
} KtBuiltinNode;
|
||||
|
||||
/*
|
||||
* Universal kotlin.Any members the LSP falls back to (kt_any_methods in
|
||||
* kotlin_lsp.c). The Any class node anchors the three methods.
|
||||
*/
|
||||
static const KtBuiltinNode kKtBuiltinNodes[] = {
|
||||
{"kotlin.Any", "Any", "Class"},
|
||||
{"kotlin.Any.toString", "toString", "Method"},
|
||||
{"kotlin.Any.equals", "equals", "Method"},
|
||||
{"kotlin.Any.hashCode", "hashCode", "Method"},
|
||||
};
|
||||
|
||||
/*
|
||||
* Inject the builtin definitions into result->defs so the pipeline mints them
|
||||
* as graph nodes. All fields beyond name/qn/label are left zero/NULL: builtins
|
||||
* have no body, so complexity/line-range/etc. are irrelevant, and a synthetic
|
||||
* file_path keeps them out of any real source file's def list.
|
||||
*/
|
||||
static void kt_builtins_inject_defs(CBMFileResult *result, CBMArena *arena) {
|
||||
if (!result || !arena) {
|
||||
return;
|
||||
}
|
||||
const int n = (int)(sizeof(kKtBuiltinNodes) / sizeof(kKtBuiltinNodes[0]));
|
||||
for (int i = 0; i < n; i++) {
|
||||
const KtBuiltinNode *b = &kKtBuiltinNodes[i];
|
||||
CBMDefinition def;
|
||||
memset(&def, 0, sizeof(def));
|
||||
def.name = b->name;
|
||||
def.qualified_name = b->qn;
|
||||
def.label = b->label;
|
||||
def.file_path = "<kotlin-builtins>";
|
||||
def.start_line = 1;
|
||||
def.end_line = 1;
|
||||
cbm_defs_push(&result->defs, arena, def);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* kotlin_lsp.h — Kotlin Light Semantic Pass.
|
||||
*
|
||||
* In-process type-aware call resolver for Kotlin. Mirrors go_lsp / php_lsp
|
||||
* shape: a per-file pass that walks the tree-sitter Kotlin AST, tracks
|
||||
* scope, infers expression types, and emits CBMResolvedCall entries for
|
||||
* call_expression / navigation_expression nodes whose target FQN can be
|
||||
* determined statically.
|
||||
*
|
||||
* Reverse-engineered from the fwcd kotlin-language-server reference
|
||||
* implementation (Kotlin compiler frontend) plus the official Kotlin
|
||||
* language specification — distilled into a pure-C resolver with no JVM
|
||||
* dependency. The goal is ≥ 90% quality parity with the LSP for the
|
||||
* call-edge and method-dispatch attribution problems CBM cares about.
|
||||
*
|
||||
* The Kotlin features handled:
|
||||
* - Package declaration (`package a.b.c`)
|
||||
* - Default Kotlin imports (kotlin.*, kotlin.collections.*, etc.)
|
||||
* - Explicit imports with optional `as` aliases
|
||||
* - Wildcard imports (`import a.b.*`)
|
||||
* - Top-level functions and properties
|
||||
* - Class / interface / object / data class / enum / sealed class
|
||||
* - Companion objects (named and anonymous) with `Foo.bar()` static-style dispatch
|
||||
* - Primary and secondary constructors (incl. `class Foo(val x: Int)`)
|
||||
* - Inheritance: delegation_specifier list, super-method lookup
|
||||
* - Extension functions and properties (`fun String.uppercaseFirst()`)
|
||||
* - Infix and operator functions (`a foo b`, `a + b`)
|
||||
* - Smart casts after `is` / `as` / null-checks
|
||||
* - Generics (basic substitution; we track but don't fully unify)
|
||||
* - Nullable types (T?), safe calls (?.) and not-null assertions (!!)
|
||||
* - `lateinit var` and `by` delegation (best-effort)
|
||||
* - Lambdas with implicit `it` parameter
|
||||
* - `with`, `let`, `also`, `apply`, `run`, `takeIf` scope functions
|
||||
* - `when` expressions with subject smart-cast
|
||||
* - `object` declarations as singletons
|
||||
*
|
||||
* Out of scope (low value or impractical without the Kotlin compiler):
|
||||
* - Reified generics across call boundaries
|
||||
* - Full type unification with constraints
|
||||
* - Decompilation of compiled Kotlin/Java bytecode
|
||||
* - DSL-style type-safe builders beyond direct lambda receivers
|
||||
* - Inline class boxing/unboxing
|
||||
*
|
||||
* The unresolved fallthrough goes to the registry's name-based matcher,
|
||||
* just like Go/Python/PHP. We never produce *worse* attribution than the
|
||||
* pre-LSP baseline; if the LSP can't decide, it emits nothing.
|
||||
*/
|
||||
#ifndef CBM_LSP_KOTLIN_LSP_H
|
||||
#define CBM_LSP_KOTLIN_LSP_H
|
||||
|
||||
#include "type_rep.h"
|
||||
#include "scope.h"
|
||||
#include "type_registry.h"
|
||||
#include "../cbm.h"
|
||||
#include "go_lsp.h" /* CBMLSPDef, CBMResolvedCallArray reused across languages */
|
||||
|
||||
/* Use-kind for `import a.b.c.foo` — tracks whether the import refers to a
|
||||
* type, a function/extension, a property, or an object. Determines how
|
||||
* `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 */
|
||||
} CBMKotlinUseKind;
|
||||
|
||||
/* KotlinLSPContext — per-file state for Kotlin call resolution. */
|
||||
typedef struct KotlinLSPContext {
|
||||
CBMArena *arena;
|
||||
const char *source;
|
||||
int source_len;
|
||||
const CBMTypeRegistry *registry;
|
||||
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. "<project>.com.example.foo.<File>" */
|
||||
const char *project_name; /* project prefix (without trailing dot) */
|
||||
const char *file_class_qn; /* JVM file-class QN, "<package>.<File>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 */
|
||||
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_companion_qn; /* if inside a companion object body */
|
||||
const char *enclosing_super_qn; /* primary super-class QN of current class, or NULL */
|
||||
|
||||
/* Current `this` and `super` types. */
|
||||
const CBMType *this_type;
|
||||
const CBMType *super_type;
|
||||
|
||||
/* `it` lambda parameter type, when inside a single-arg lambda.
|
||||
* Saved/restored across nested lambdas. */
|
||||
const CBMType *it_type;
|
||||
|
||||
/* Output: resolved calls accumulate here. */
|
||||
CBMResolvedCallArray *resolved_calls;
|
||||
|
||||
/* Recursion guard for kotlin_eval_expr_type. */
|
||||
int eval_depth;
|
||||
|
||||
/* AST-walk recursion depth for kt_resolve_calls_in_node (guards stack
|
||||
* overflow on deeply-nested/cyclic files; see cbm_lsp_max_walk_depth).
|
||||
* Zero via memset. */
|
||||
int walk_depth;
|
||||
|
||||
/* Debug mode (CBM_LSP_DEBUG env). */
|
||||
bool debug;
|
||||
} 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);
|
||||
|
||||
/* 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,
|
||||
* pass the package prefix as target_qn and CBM_KT_USE_WILDCARD as kind. */
|
||||
void kotlin_lsp_add_import(KotlinLSPContext *ctx, const char *local_name, const char *target_qn,
|
||||
CBMKotlinUseKind kind);
|
||||
|
||||
/* Walk a file's AST: top-level decls, then function/method bodies. */
|
||||
void kotlin_lsp_process_file(KotlinLSPContext *ctx, TSNode root);
|
||||
|
||||
/* Evaluate a Kotlin expression's type. Returns cbm_type_unknown() on
|
||||
* failure — never NULL. */
|
||||
const CBMType *kotlin_eval_expr_type(KotlinLSPContext *ctx, TSNode node);
|
||||
|
||||
/* Parse a Kotlin type-AST node (user_type, nullable_type, function_type, …)
|
||||
* to CBMType. */
|
||||
const CBMType *kotlin_parse_type_node(KotlinLSPContext *ctx, TSNode node);
|
||||
|
||||
/* Resolve a bare class name (possibly qualified like "Foo" or "a.Foo")
|
||||
* to its full QN using current package + import map. NULL if unresolved. */
|
||||
const char *kotlin_resolve_class_name(KotlinLSPContext *ctx, const char *name);
|
||||
|
||||
/* Resolve a bare top-level function name to its target QN. */
|
||||
const char *kotlin_resolve_function_name(KotlinLSPContext *ctx, const char *name);
|
||||
|
||||
/* Look up a method on a class, walking the super-chain (registry-based). */
|
||||
const CBMRegisteredFunc *kotlin_lookup_method(KotlinLSPContext *ctx, const char *class_qn,
|
||||
const char *method_name);
|
||||
|
||||
/* Look up a property/field on a class, walking super-chain. */
|
||||
const CBMType *kotlin_lookup_property_type(KotlinLSPContext *ctx, const char *class_qn,
|
||||
const char *prop_name);
|
||||
|
||||
/* 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);
|
||||
|
||||
/* 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.
|
||||
* Mirrors cbm_run_java_lsp_cross. `defs` carries the graph QNs of every
|
||||
* project definition so a bare top-level call in file B resolves to the
|
||||
* definition node living in file A. Output is appended to `out`. */
|
||||
void cbm_run_kotlin_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);
|
||||
|
||||
/* Register the curated Kotlin stdlib (kotlin.*, kotlin.collections.*, …)
|
||||
* into a registry. Implemented in lsp/generated/kotlin_stdlib_data.c. */
|
||||
void cbm_kotlin_stdlib_register(CBMTypeRegistry *reg, CBMArena *arena);
|
||||
|
||||
/* Register Kotlin default-import targets — the prefixes auto-imported
|
||||
* by every Kotlin file. Used by the LSP context init. */
|
||||
const char *const *cbm_kotlin_default_import_packages(int *count_out);
|
||||
|
||||
#endif /* CBM_LSP_KOTLIN_LSP_H */
|
||||
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* lsp_neg_memo.h — generic negative-lookup memo for LSP resolve cascades.
|
||||
*
|
||||
* Problem (all hybrid-LSP languages): a resolve cascade probes a ladder of
|
||||
* hypotheses (direct hit, module-prefixed retry, alias walk, trait/base
|
||||
* dispatch, short-name fallback) and most probes MISS — macro-expanded or
|
||||
* generated code asks the SAME failing question thousands of times per file,
|
||||
* re-paying the whole ladder each time (linux kernel: 4 trait-heavy rust
|
||||
* files at ~63 s each; the C resolver had the same disease before its memo).
|
||||
*
|
||||
* A miss is a pure fact of (registry, query) — but ONLY while the registry
|
||||
* cannot change. Callers must therefore gate the memo on a SEALED registry
|
||||
* (reg->read_only, set at finalize; cbm_registry_add_* hard-return then) and
|
||||
* must key only queries whose cascade reads nothing but the registry and the
|
||||
* query strings (no per-function scope state in the memoized rungs).
|
||||
*
|
||||
* Shape: open-addressing set of 64-bit keys, arena-backed (dies with the
|
||||
* per-file arena — no explicit free, mirroring the py field overlay). A memo
|
||||
* HIT means "this exact query already ran the full cascade and returned
|
||||
* nothing" → the caller returns its miss result immediately. To make a hash
|
||||
* collision harmless, callers keep their cheap DIRECT lookup before the memo
|
||||
* check (the C-memo pattern): a colliding real hit is still found; only the
|
||||
* expensive registry-pure miss ladder is skipped.
|
||||
*
|
||||
* Per-language wiring is a few lines: add a CBMNegMemo to the language ctx,
|
||||
* key each cascade entry with cbm_negmemo_key (site tag + query strings),
|
||||
* check at entry, insert on the miss return. Wired: rust. Candidates per the
|
||||
* 2026-07 resolve audit: php, c# (extension methods), java, kotlin; the C
|
||||
* resolver's bespoke memo in c_lsp.c predates this header and can migrate.
|
||||
*/
|
||||
#ifndef CBM_LSP_NEG_MEMO_H
|
||||
#define CBM_LSP_NEG_MEMO_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "arena.h"
|
||||
|
||||
typedef struct {
|
||||
uint64_t *slots; /* arena-owned; 0 = empty (keys are never 0) */
|
||||
int cap; /* power of two */
|
||||
int count;
|
||||
} CBMNegMemo;
|
||||
|
||||
enum {
|
||||
CBM_NEGMEMO_INIT_CAP = 1024,
|
||||
CBM_NEGMEMO_GROW = 2,
|
||||
CBM_NEGMEMO_LOAD_NUM = 7, /* grow at 70% load */
|
||||
CBM_NEGMEMO_LOAD_DEN = 10,
|
||||
};
|
||||
|
||||
/* FNV-1a over (site tag, a, 0xff, b). The site tag keeps two cascades with
|
||||
* the same argument strings from sharing keys. Never returns 0. */
|
||||
static inline uint64_t cbm_negmemo_key(uint8_t site, const char *a, const char *b) {
|
||||
uint64_t h = 0xcbf29ce484222325ULL;
|
||||
h ^= site;
|
||||
h *= 0x100000001b3ULL;
|
||||
if (a) {
|
||||
while (*a) {
|
||||
h ^= (unsigned char)*a++;
|
||||
h *= 0x100000001b3ULL;
|
||||
}
|
||||
}
|
||||
h ^= 0xff;
|
||||
h *= 0x100000001b3ULL;
|
||||
if (b) {
|
||||
while (*b) {
|
||||
h ^= (unsigned char)*b++;
|
||||
h *= 0x100000001b3ULL;
|
||||
}
|
||||
}
|
||||
return h ? h : 1;
|
||||
}
|
||||
|
||||
static inline bool cbm_negmemo_contains(const CBMNegMemo *m, uint64_t key) {
|
||||
if (!m->slots || m->count == 0) {
|
||||
return false;
|
||||
}
|
||||
uint64_t mask = (uint64_t)(m->cap - 1);
|
||||
for (uint64_t i = key & mask;; i = (i + 1) & mask) {
|
||||
uint64_t s = m->slots[i];
|
||||
if (s == 0) {
|
||||
return false;
|
||||
}
|
||||
if (s == key) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void cbm_negmemo_insert_raw(uint64_t *slots, int cap, uint64_t key) {
|
||||
uint64_t mask = (uint64_t)(cap - 1);
|
||||
for (uint64_t i = key & mask;; i = (i + 1) & mask) {
|
||||
if (slots[i] == key) {
|
||||
return;
|
||||
}
|
||||
if (slots[i] == 0) {
|
||||
slots[i] = key;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Arena-backed insert: lazy first allocation, grow-by-rehash at 70% load.
|
||||
* The abandoned table stays in the arena (bounded, freed with the file). */
|
||||
static inline void cbm_negmemo_insert(CBMNegMemo *m, CBMArena *arena, uint64_t key) {
|
||||
if (!arena) {
|
||||
return; /* no arena — memo silently disabled */
|
||||
}
|
||||
if (!m->slots) {
|
||||
m->slots = cbm_arena_alloc(arena, sizeof(uint64_t) * CBM_NEGMEMO_INIT_CAP);
|
||||
if (!m->slots) {
|
||||
return;
|
||||
}
|
||||
memset(m->slots, 0, sizeof(uint64_t) * CBM_NEGMEMO_INIT_CAP);
|
||||
m->cap = CBM_NEGMEMO_INIT_CAP;
|
||||
m->count = 0;
|
||||
}
|
||||
if ((int64_t)(m->count + 1) * CBM_NEGMEMO_LOAD_DEN >=
|
||||
(int64_t)m->cap * CBM_NEGMEMO_LOAD_NUM) {
|
||||
int new_cap = m->cap * CBM_NEGMEMO_GROW;
|
||||
uint64_t *ns = cbm_arena_alloc(arena, sizeof(uint64_t) * (size_t)new_cap);
|
||||
if (!ns) {
|
||||
return; /* keep the old (full-ish) table; inserts degrade, reads stay correct */
|
||||
}
|
||||
memset(ns, 0, sizeof(uint64_t) * (size_t)new_cap);
|
||||
for (int i = 0; i < m->cap; i++) {
|
||||
if (m->slots[i]) {
|
||||
cbm_negmemo_insert_raw(ns, new_cap, m->slots[i]);
|
||||
}
|
||||
}
|
||||
m->slots = ns;
|
||||
m->cap = new_cap;
|
||||
}
|
||||
if (!cbm_negmemo_contains(m, key)) {
|
||||
cbm_negmemo_insert_raw(m->slots, m->cap, key);
|
||||
m->count++;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── CBMIdxMemo: exact-match string-key → int index map ─────────────────────
|
||||
* Companion for build-time registration loops that need "have I registered
|
||||
* this QN, and at which index?" in O(1) — the registry's own buckets don't
|
||||
* exist before finalize, and probing it linearly from inside the registration
|
||||
* loop is the classic quadratic (kernel shared rust registry: ~63 s).
|
||||
* Exact match: each slot stores the borrowed key pointer and verifies with
|
||||
* strcmp, so hash collisions can't map to a wrong index. First-put wins. */
|
||||
typedef struct {
|
||||
struct cbm_idxmemo_slot {
|
||||
uint64_t h; /* 0 = empty */
|
||||
const char *key;
|
||||
int32_t val;
|
||||
} *slots;
|
||||
int cap; /* power of two */
|
||||
int count;
|
||||
} CBMIdxMemo;
|
||||
|
||||
static inline int32_t cbm_idxmemo_get(const CBMIdxMemo *m, const char *key) {
|
||||
if (!m->slots || !key || m->count == 0) {
|
||||
return -1;
|
||||
}
|
||||
uint64_t h = cbm_negmemo_key(0, key, NULL);
|
||||
uint64_t mask = (uint64_t)(m->cap - 1);
|
||||
for (uint64_t i = h & mask;; i = (i + 1) & mask) {
|
||||
if (m->slots[i].h == 0) {
|
||||
return -1;
|
||||
}
|
||||
if (m->slots[i].h == h && m->slots[i].key && strcmp(m->slots[i].key, key) == 0) {
|
||||
return m->slots[i].val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void cbm_idxmemo_put_raw(struct cbm_idxmemo_slot *slots, int cap, uint64_t h,
|
||||
const char *key, int32_t val) {
|
||||
uint64_t mask = (uint64_t)(cap - 1);
|
||||
for (uint64_t i = h & mask;; i = (i + 1) & mask) {
|
||||
if (slots[i].h == 0) {
|
||||
slots[i].h = h;
|
||||
slots[i].key = key;
|
||||
slots[i].val = val;
|
||||
return;
|
||||
}
|
||||
if (slots[i].h == h && slots[i].key && strcmp(slots[i].key, key) == 0) {
|
||||
return; /* first-put wins */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void cbm_idxmemo_put_if_absent(CBMIdxMemo *m, CBMArena *arena, const char *key,
|
||||
int32_t val) {
|
||||
if (!arena || !key) {
|
||||
return;
|
||||
}
|
||||
if (!m->slots) {
|
||||
m->slots = cbm_arena_alloc(arena,
|
||||
sizeof(struct cbm_idxmemo_slot) * CBM_NEGMEMO_INIT_CAP);
|
||||
if (!m->slots) {
|
||||
return;
|
||||
}
|
||||
memset(m->slots, 0, sizeof(struct cbm_idxmemo_slot) * CBM_NEGMEMO_INIT_CAP);
|
||||
m->cap = CBM_NEGMEMO_INIT_CAP;
|
||||
m->count = 0;
|
||||
}
|
||||
if ((int64_t)(m->count + 1) * CBM_NEGMEMO_LOAD_DEN >=
|
||||
(int64_t)m->cap * CBM_NEGMEMO_LOAD_NUM) {
|
||||
int new_cap = m->cap * CBM_NEGMEMO_GROW;
|
||||
struct cbm_idxmemo_slot *ns =
|
||||
cbm_arena_alloc(arena, sizeof(struct cbm_idxmemo_slot) * (size_t)new_cap);
|
||||
if (!ns) {
|
||||
return;
|
||||
}
|
||||
memset(ns, 0, sizeof(struct cbm_idxmemo_slot) * (size_t)new_cap);
|
||||
for (int i = 0; i < m->cap; i++) {
|
||||
if (m->slots[i].h) {
|
||||
cbm_idxmemo_put_raw(ns, new_cap, m->slots[i].h, m->slots[i].key, m->slots[i].val);
|
||||
}
|
||||
}
|
||||
m->slots = ns;
|
||||
m->cap = new_cap;
|
||||
}
|
||||
uint64_t h = cbm_negmemo_key(0, key, NULL);
|
||||
if (cbm_idxmemo_get(m, key) < 0) {
|
||||
cbm_idxmemo_put_raw(m->slots, m->cap, h, key, val);
|
||||
m->count++;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* CBM_LSP_NEG_MEMO_H */
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* lsp_node_iter.h — O(n) child enumeration for the per-language LSP walkers.
|
||||
*
|
||||
* tree-sitter's ts_node_child(node, i) is O(i): it walks the child iterator
|
||||
* from the first child every call. So the common idiom
|
||||
*
|
||||
* uint32_t nc = ts_node_child_count(node);
|
||||
* for (uint32_t i = 0; i < nc; i++) { TSNode c = ts_node_child(node, i); ... }
|
||||
*
|
||||
* is O(n²) over a node's children — catastrophic on a wide root (e.g. a program
|
||||
* node holding hundreds of thousands of top-level statements, as in TS's
|
||||
* reallyLargeFile.ts fixture: 583K comment lines made the per-file LSP passes
|
||||
* run for ~133 minutes). cbm_lsp_collect_children() enumerates the children in
|
||||
* a single O(n) cursor pass into an arena array; callers then index the array.
|
||||
*/
|
||||
#ifndef CBM_LSP_NODE_ITER_H
|
||||
#define CBM_LSP_NODE_ITER_H
|
||||
|
||||
#include "../arena.h"
|
||||
#include "tree_sitter/api.h"
|
||||
|
||||
/* Collect `node`'s children into an arena array (source order) via one O(n)
|
||||
* cursor pass. Returns NULL and sets *out_n=0 for a childless node or on OOM. */
|
||||
static inline TSNode* cbm_lsp_collect_children(CBMArena* arena, TSNode node, uint32_t* out_n) {
|
||||
uint32_t nc = ts_node_child_count(node);
|
||||
*out_n = 0;
|
||||
if (nc == 0) return NULL;
|
||||
TSNode* kids = (TSNode*)cbm_arena_alloc(arena, (size_t)nc * sizeof(TSNode));
|
||||
if (!kids) return NULL;
|
||||
uint32_t kn = 0;
|
||||
TSTreeCursor cur = ts_tree_cursor_new(node);
|
||||
if (ts_tree_cursor_goto_first_child(&cur)) {
|
||||
do {
|
||||
kids[kn++] = ts_tree_cursor_current_node(&cur);
|
||||
} while (kn < nc && ts_tree_cursor_goto_next_sibling(&cur));
|
||||
}
|
||||
ts_tree_cursor_delete(&cur);
|
||||
*out_n = kn;
|
||||
return kids;
|
||||
}
|
||||
|
||||
#endif /* CBM_LSP_NODE_ITER_H */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,119 @@
|
||||
#ifndef CBM_LSP_PERL_LSP_H
|
||||
#define CBM_LSP_PERL_LSP_H
|
||||
|
||||
#include "type_rep.h"
|
||||
#include "scope.h"
|
||||
#include "type_registry.h"
|
||||
#include "../cbm.h"
|
||||
#include "go_lsp.h" /* CBMLSPDef reused across languages */
|
||||
|
||||
/* PerlLSPContext — per-file state for Perl type-aware call resolution.
|
||||
* Mirrors PHPLSPContext / GoLSPContext / CLSPContext structure.
|
||||
*
|
||||
* Perl differs from PHP in a few ways that shape this context:
|
||||
* - Packages (`package Foo::Bar;`) replace PHP namespaces. A file may
|
||||
* contain several packages; current_package_qn tracks the active one.
|
||||
* - Inheritance is expressed via the `@ISA` array (or `use parent`/
|
||||
* `use base`), not a class keyword, so we keep an @ISA table.
|
||||
* - `bless` associates a reference variable with a class at runtime;
|
||||
* we track a bless var→class map for method dispatch.
|
||||
* - Exporter-style imports (`use Foo qw(bar baz)`) populate an export
|
||||
* import map (local name → target QN) analogous to PHP `use`. */
|
||||
typedef struct {
|
||||
CBMArena *arena;
|
||||
const char *source;
|
||||
int source_len;
|
||||
const CBMTypeRegistry *registry;
|
||||
CBMScope *current_scope;
|
||||
|
||||
/* Package state. A Perl file may declare multiple packages; this is the
|
||||
* QN of the package currently in effect (dotted form). Empty string means
|
||||
* the default `main` / file-level package. */
|
||||
const char *current_package_qn;
|
||||
|
||||
/* Export import map (Exporter / `use Foo qw(...)`).
|
||||
* use_local_names[i] is the symbol as referenced in this file;
|
||||
* use_target_qns[i] is the fully-qualified target it resolves to. */
|
||||
const char **use_local_names;
|
||||
const char **use_target_qns;
|
||||
int use_count;
|
||||
int use_cap;
|
||||
|
||||
/* @ISA inheritance table: isa_pkg_qns[i] inherits from isa_parent_qns[i].
|
||||
* Populated from @ISA assignments and `use parent`/`use base`. */
|
||||
const char **isa_pkg_qns;
|
||||
const char **isa_parent_qns;
|
||||
int isa_count;
|
||||
int isa_cap;
|
||||
|
||||
/* bless var→class map: blessed_var_names[i] holds a reference blessed
|
||||
* into class blessed_class_qns[i], so $self->method() can dispatch. */
|
||||
const char **blessed_var_names;
|
||||
const char **blessed_class_qns;
|
||||
int blessed_count;
|
||||
int blessed_cap;
|
||||
|
||||
/* Current package/sub context. */
|
||||
const char *enclosing_package_qn; /* package QN of the enclosing scope */
|
||||
const char *enclosing_parent_qn; /* parent class QN (for SUPER::), or NULL */
|
||||
const char *enclosing_func_qn; /* enclosing sub QN, or NULL */
|
||||
const char *module_qn;
|
||||
|
||||
/* Output: resolved calls accumulate here. */
|
||||
CBMResolvedCallArray *resolved_calls;
|
||||
|
||||
/* Recursion guard for perl_eval_expr_type. */
|
||||
int eval_depth;
|
||||
|
||||
/* Recursion guard for the AST-walk passes (perl_resolve_calls_in_node /
|
||||
* perl_pass1_scan). Bounds stack depth on pathologically nested input. */
|
||||
int walk_depth;
|
||||
|
||||
/* Debug mode (CBM_LSP_DEBUG env). */
|
||||
bool debug;
|
||||
} PerlLSPContext;
|
||||
|
||||
/* Initialize a PerlLSPContext for processing one file. */
|
||||
void perl_lsp_init(PerlLSPContext *ctx, CBMArena *arena, const char *source, int source_len,
|
||||
const CBMTypeRegistry *registry, const char *module_qn,
|
||||
CBMResolvedCallArray *out);
|
||||
|
||||
/* Add an export/`use` mapping (local name → target QN). */
|
||||
void perl_lsp_add_use(PerlLSPContext *ctx, const char *local_name, const char *target_qn);
|
||||
|
||||
/* Process a file's AST: walk package decls + sub bodies, resolve calls. */
|
||||
void perl_lsp_process_file(PerlLSPContext *ctx, TSNode root);
|
||||
|
||||
/* Evaluate a Perl expression's type. May return NULL / CBM_TYPE_UNKNOWN. */
|
||||
const CBMType *perl_eval_expr_type(PerlLSPContext *ctx, TSNode node);
|
||||
|
||||
/* Resolve a package/class name (bare or qualified) using current package +
|
||||
* the export import map. */
|
||||
const char *perl_resolve_package_name(PerlLSPContext *ctx, const char *name);
|
||||
|
||||
/* Look up a method on a package, walking the @ISA chain (registry-based). */
|
||||
const CBMRegisteredFunc *perl_lookup_method(PerlLSPContext *ctx, const char *package_qn,
|
||||
const char *method_name);
|
||||
|
||||
/* Entry point: build registry from file defs + stdlib, then run resolution.
|
||||
* Called from cbm_extract_file() via the language dispatch in cbm.c. */
|
||||
void cbm_run_perl_lsp(CBMArena *arena, CBMFileResult *result, const char *source, int source_len,
|
||||
TSNode root);
|
||||
|
||||
/* Register Perl stdlib (perlfunc builtins) + curated CPAN types into a
|
||||
* registry. */
|
||||
void cbm_perl_stdlib_register(CBMTypeRegistry *reg, CBMArena *arena);
|
||||
|
||||
/* --- Cross-file LSP resolution ---
|
||||
*
|
||||
* Mirrors cbm_run_php_lsp_cross / cbm_run_py_lsp_cross. Stub-declared here so
|
||||
* a later plan (Phase 23, cross-file) can implement it without touching the
|
||||
* wiring. Caller supplies the combined CBMLSPDef[] (file-local + cross-file)
|
||||
* and a resolved import map (use → target QN). */
|
||||
void cbm_run_perl_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);
|
||||
|
||||
#endif /* CBM_LSP_PERL_LSP_H */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,134 @@
|
||||
#ifndef CBM_LSP_PHP_LSP_H
|
||||
#define CBM_LSP_PHP_LSP_H
|
||||
|
||||
#include "type_rep.h"
|
||||
#include "scope.h"
|
||||
#include "type_registry.h"
|
||||
#include "../cbm.h"
|
||||
#include "go_lsp.h" /* CBMLSPDef reused across languages */
|
||||
|
||||
/* PHPLSPContext — per-file state for PHP type-aware call resolution.
|
||||
* Mirrors GoLSPContext / CLSPContext structure. */
|
||||
typedef struct {
|
||||
CBMArena *arena;
|
||||
const char *source;
|
||||
int source_len;
|
||||
const CBMTypeRegistry *registry;
|
||||
CBMScope *current_scope;
|
||||
|
||||
/* Namespace state. PHP files declare a single namespace
|
||||
* (or use the global namespace if none); empty string means global. */
|
||||
const char *current_namespace_qn;
|
||||
|
||||
/* `use` clause map.
|
||||
* use_kinds[i] selects whether the local maps a class, function, or const. */
|
||||
const char **use_local_names;
|
||||
const char **use_target_qns;
|
||||
enum { CBM_PHP_USE_CLASS = 0, CBM_PHP_USE_FUNCTION, CBM_PHP_USE_CONST } *use_kinds;
|
||||
int use_count;
|
||||
int use_cap;
|
||||
|
||||
/* Current function/method/class context. */
|
||||
const char *enclosing_func_qn;
|
||||
const char *enclosing_class_qn; /* NULL outside class body */
|
||||
const char *enclosing_parent_qn; /* parent class QN (for parent::), or NULL */
|
||||
const char *module_qn;
|
||||
|
||||
/* Output: resolved calls accumulate here. */
|
||||
CBMResolvedCallArray *resolved_calls;
|
||||
|
||||
/* @phpstan-type alias map (per-file, populated from class docblocks).
|
||||
* Used by resolve_phpdoc_type before generic name resolution so user
|
||||
* type aliases like `@phpstan-type UserId int|string` and references
|
||||
* to `UserId` in @var/@param/@return all resolve to the aliased type.
|
||||
*/
|
||||
const char **phpstan_alias_names; /* arena-allocated, NULL-terminated */
|
||||
const CBMType **phpstan_alias_types;
|
||||
int phpstan_alias_count;
|
||||
int phpstan_alias_cap;
|
||||
|
||||
/* Recursion guard for php_eval_expr_type. */
|
||||
int eval_depth;
|
||||
|
||||
/* AST-walk recursion depth for php_resolve_calls_in_node (guards stack
|
||||
* overflow on deeply-nested/cyclic files; see cbm_lsp_max_walk_depth).
|
||||
* Zero via memset. */
|
||||
int walk_depth;
|
||||
|
||||
/* Debug mode (CBM_LSP_DEBUG env). */
|
||||
bool debug;
|
||||
} PHPLSPContext;
|
||||
|
||||
/* Initialize a PHPLSPContext for processing one file. */
|
||||
void php_lsp_init(PHPLSPContext *ctx, CBMArena *arena, const char *source, int source_len,
|
||||
const CBMTypeRegistry *registry, const char *module_qn,
|
||||
CBMResolvedCallArray *out);
|
||||
|
||||
/* Add a `use` mapping. */
|
||||
void php_lsp_add_use(PHPLSPContext *ctx, const char *local_name, const char *target_qn,
|
||||
int use_kind);
|
||||
|
||||
/* Process a file's AST: walk top-level decls, then function/method bodies. */
|
||||
void php_lsp_process_file(PHPLSPContext *ctx, TSNode root);
|
||||
|
||||
/* Evaluate a PHP expression's type. May return NULL / CBM_TYPE_UNKNOWN. */
|
||||
const CBMType *php_eval_expr_type(PHPLSPContext *ctx, TSNode node);
|
||||
|
||||
/* Parse a PHP type-AST node (named_type, primitive_type, union_type, ...) to CBMType. */
|
||||
const CBMType *php_parse_type_node(PHPLSPContext *ctx, TSNode node);
|
||||
|
||||
/* Resolve a class name (bare or qualified) using current namespace + use map. */
|
||||
const char *php_resolve_class_name(PHPLSPContext *ctx, const char *name);
|
||||
|
||||
/* Look up a method on a class, walking parent chain (registry-based). */
|
||||
const CBMRegisteredFunc *php_lookup_method(PHPLSPContext *ctx, const char *class_qn,
|
||||
const char *method_name);
|
||||
|
||||
/* Entry point: build registry from file defs + stdlib + composer (if present),
|
||||
* then run resolution. Called from cbm_extract_file(). */
|
||||
void cbm_run_php_lsp(CBMArena *arena, CBMFileResult *result, const char *source, int source_len,
|
||||
TSNode root);
|
||||
|
||||
/* Register PHP stdlib + curated framework types into a registry. */
|
||||
void cbm_php_stdlib_register(CBMTypeRegistry *reg, CBMArena *arena);
|
||||
|
||||
/* --- Cross-file LSP resolution ---
|
||||
*
|
||||
* Mirrors cbm_run_py_lsp_cross / cbm_run_ts_lsp_cross. Caller supplies the
|
||||
* combined CBMLSPDef[] (file-local + cross-file) and a resolved import map
|
||||
* (use → target QN). Imports are added as CLASS-kind uses; file-internal
|
||||
* `use` declarations from the AST are layered on top by process_file.
|
||||
*
|
||||
* Reuses go_lsp.h's CBMLSPDef so cross-language registration is uniform. */
|
||||
void cbm_run_php_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);
|
||||
|
||||
/* --- Batch cross-file LSP --- */
|
||||
|
||||
/* Per-file input for batch PHP LSP processing. */
|
||||
typedef struct {
|
||||
const char *source;
|
||||
int source_len;
|
||||
const char *module_qn;
|
||||
TSTree *cached_tree; /* 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;
|
||||
int import_count;
|
||||
} CBMBatchPHPLSPFile;
|
||||
|
||||
/* Process multiple PHP files' cross-file LSP in one call. out must point to
|
||||
* file_count pre-zeroed CBMResolvedCallArray structs. */
|
||||
void cbm_batch_php_lsp_cross(
|
||||
CBMArena *arena,
|
||||
CBMBatchPHPLSPFile *files, int file_count,
|
||||
CBMResolvedCallArray *out);
|
||||
|
||||
#endif /* CBM_LSP_PHP_LSP_H */
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* py_builtins.c — Minimal Python builtins as real graph nodes.
|
||||
*
|
||||
* The Python LSP type registry already knows the builtins (typeshed-derived
|
||||
* generated/python_stdlib_data.c registers builtins.len, builtins.str,
|
||||
* builtins.str.upper, builtins.list.append, ...). So a call like len(v) /
|
||||
* str(v) / "x".upper() / xs.append(1) ALREADY resolves at the LSP layer and
|
||||
* emits the correct strategy (lsp_builtin / lsp_builtin_constructor /
|
||||
* lsp_builtin_method / lsp_generic_method) with callee_qn = "builtins.<name>".
|
||||
*
|
||||
* The missing piece is downstream: pass_calls.c only writes a CALLS edge when
|
||||
* cbm_pipeline_lsp_target_node() resolves the callee_qn to a graph node
|
||||
* (src/pipeline/lsp_resolve.h). There is no "builtins.len" node in the graph,
|
||||
* so the resolved call is dropped and the strategy never lands on an edge.
|
||||
*
|
||||
* Fix: inject a small, fixed set of builtin definitions into result->defs
|
||||
* during the per-file Python LSP run (which executes inside cbm_extract_file,
|
||||
* BEFORE the parallel pipeline mints def nodes from result->defs). The graph
|
||||
* therefore gains real "builtins.*" nodes that the LSP-emitted edges target.
|
||||
* The QNs here MUST match what the typeshed registry emits as callee_qn.
|
||||
*
|
||||
* Node minting upserts by QN (cbm_gbuf_upsert_node), so injecting the same
|
||||
* builtins per Python file collapses to one node per QN — no duplicates.
|
||||
*
|
||||
* Self-contained: #included from py_lsp.c only (CGo amalgamation pattern;
|
||||
* see lsp_all.c). Not a standalone translation unit.
|
||||
*/
|
||||
|
||||
/* A single builtin entry to mint as a graph node. */
|
||||
typedef struct {
|
||||
const char *qn; /* graph QN — MUST equal the registry callee_qn */
|
||||
const char *name; /* short name (last segment of qn) */
|
||||
const char *label; /* "Function" | "Class" | "Method" */
|
||||
} PyBuiltinNode;
|
||||
|
||||
/*
|
||||
* Minimal builtins set. Kept deliberately small and aligned with the registry
|
||||
* (generated/python_stdlib_data.c):
|
||||
* - free functions (lsp_builtin): len, print
|
||||
* - types/ctors (lsp_builtin_constructor): str, int, list, dict, range
|
||||
* - str methods (lsp_builtin_method): upper, lower
|
||||
* - list methods (lsp_generic_method): append, pop
|
||||
* - dict methods (lsp_generic_method): get
|
||||
* Note: str/int/list/dict/range are TYPES in the registry (so X() routes to
|
||||
* lsp_builtin_constructor), hence the "Class" label here.
|
||||
*/
|
||||
static const PyBuiltinNode kPyBuiltinNodes[] = {
|
||||
{"builtins.len", "len", "Function"},
|
||||
{"builtins.print", "print", "Function"},
|
||||
|
||||
{"builtins.str", "str", "Class"},
|
||||
{"builtins.int", "int", "Class"},
|
||||
{"builtins.list", "list", "Class"},
|
||||
{"builtins.dict", "dict", "Class"},
|
||||
{"builtins.range", "range", "Class"},
|
||||
|
||||
{"builtins.str.upper", "upper", "Method"},
|
||||
{"builtins.str.lower", "lower", "Method"},
|
||||
|
||||
{"builtins.list.append", "append", "Method"},
|
||||
{"builtins.list.pop", "pop", "Method"},
|
||||
|
||||
{"builtins.dict.get", "get", "Method"},
|
||||
};
|
||||
|
||||
/*
|
||||
* Inject the builtin definitions into result->defs so the pipeline mints them
|
||||
* as graph nodes. All fields beyond name/qn/label are left zero/NULL: builtins
|
||||
* have no body, so complexity/line-range/etc. are irrelevant, and a synthetic
|
||||
* file_path keeps them out of any real source file's def list.
|
||||
*/
|
||||
static void py_builtins_inject_defs(CBMFileResult *result, CBMArena *arena) {
|
||||
if (!result || !arena) {
|
||||
return;
|
||||
}
|
||||
const int n = (int)(sizeof(kPyBuiltinNodes) / sizeof(kPyBuiltinNodes[0]));
|
||||
for (int i = 0; i < n; i++) {
|
||||
const PyBuiltinNode *b = &kPyBuiltinNodes[i];
|
||||
CBMDefinition def;
|
||||
memset(&def, 0, sizeof(def));
|
||||
def.name = b->name;
|
||||
def.qualified_name = b->qn;
|
||||
def.label = b->label;
|
||||
def.file_path = "<python-builtins>";
|
||||
def.start_line = 1;
|
||||
def.end_line = 1;
|
||||
cbm_defs_push(&result->defs, arena, def);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,188 @@
|
||||
#ifndef CBM_LSP_PY_LSP_H
|
||||
#define CBM_LSP_PY_LSP_H
|
||||
|
||||
#include "type_rep.h"
|
||||
#include "scope.h"
|
||||
#include "type_registry.h"
|
||||
#include "../cbm.h"
|
||||
#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
|
||||
} 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"
|
||||
} CBMDictLiteralEntry;
|
||||
|
||||
// Memoization entry for py_eval_expr_type (issue #710). Keyed by the
|
||||
// tree-sitter node identity pointer (TSNode.id), which is unique per node
|
||||
// within one file's parse tree. NOT the start byte: every leftmost
|
||||
// descendant of a chained call expression shares the chain's start byte,
|
||||
// so byte-keyed entries would alias distinct nodes and silently corrupt
|
||||
// call resolution. `gen` snapshots the scope generation (see
|
||||
// py_scope_bind in py_lsp.c); entries from older generations are treated
|
||||
// as misses so scope rebinding can never serve a stale type.
|
||||
typedef struct {
|
||||
const void *node_id; // TSNode.id; NULL marks an empty slot
|
||||
uint32_t gen; // scope generation at insert time
|
||||
const CBMType *result; // full-fidelity result, never NULL in a live entry
|
||||
} CBMPyTypeCacheEntry;
|
||||
|
||||
// PyLSPContext holds state for one Python file's type evaluation.
|
||||
typedef struct {
|
||||
CBMArena *arena;
|
||||
const char *source;
|
||||
int source_len;
|
||||
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;
|
||||
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;
|
||||
|
||||
// Output: resolved calls accumulate here.
|
||||
CBMResolvedCallArray *resolved_calls;
|
||||
|
||||
// Syntactic-call list (result->calls), borrowed from the per-file
|
||||
// extraction result. The downstream pipeline only turns a resolved_call
|
||||
// into a CALLS edge when a *syntactic* CBMCall with the same
|
||||
// (enclosing_func_qn, callee short-name) exists here. Operator/subscript
|
||||
// dunder desugaring (`s[k]` -> T.__getitem__, `a + b` -> T.__add__) never
|
||||
// appears in result->calls because a `subscript`/`binary_operator` is not
|
||||
// a `call` node, so the syntactic extractor produced no call. When this is
|
||||
// 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;
|
||||
|
||||
// Lambda registry: simple linear list, looked up by name.
|
||||
CBMLambdaEntry *lambdas;
|
||||
int lambda_count;
|
||||
int lambda_cap;
|
||||
|
||||
// Dict-literal-as-dispatch-table tracking.
|
||||
CBMDictLiteralEntry *dict_literals;
|
||||
int dict_literal_count;
|
||||
int dict_literal_cap;
|
||||
|
||||
// AST-walk recursion depth for py_resolve_calls_in (guards stack overflow on
|
||||
// deeply-nested/cyclic files; see cbm_lsp_max_walk_depth). Zero via memset.
|
||||
int walk_depth;
|
||||
|
||||
// py_eval_expr_type memoization + guards (issues #710/#720; mirrors
|
||||
// c_eval_expr_type's guard design in c_lsp.c). All zero via memset —
|
||||
// each file starts with a cold cache and a full budget.
|
||||
CBMPyTypeCacheEntry *type_cache; // open addressing, linear probe, arena-allocated
|
||||
int type_cache_count; // occupied slots (kept < 75% of cap)
|
||||
int type_cache_cap; // power-of-two capacity
|
||||
uint32_t type_cache_gen; // bumped on every scope mutation (O(1) flush)
|
||||
int eval_depth; // evaluator recursion depth (PY_LSP_MAX_EVAL_DEPTH)
|
||||
int eval_steps; // per-file work budget used (PY_EVAL_MAX_STEPS_PER_FILE)
|
||||
uint32_t eval_truncations; // depth/budget cutoff count — gates memo inserts
|
||||
|
||||
// Per-file instance-field OVERLAY. When the Tier-2 registry is shared + sealed
|
||||
// (registry->read_only), `self.x = ...` / PEP-526 field discoveries made during
|
||||
// resolve are recorded HERE instead of mutating the shared registry (which would
|
||||
// bypass the add_* seal, race the other resolve workers, and leave the shared
|
||||
// entry pointing into this file's arena once it is freed). py_lookup_field
|
||||
// consults this overlay alongside the shared base, so same-file attribute-chain
|
||||
// resolution is preserved with zero shared mutation. Arena-allocated (per-file
|
||||
// lifetime); holds no pointer into the shared registry, and the shared registry
|
||||
// holds none into it. Mutable per-file registries keep the direct write.
|
||||
struct {
|
||||
const char *class_qn;
|
||||
const char *field_name;
|
||||
const CBMType *field_type;
|
||||
} *field_overlay;
|
||||
int field_overlay_count;
|
||||
int field_overlay_cap;
|
||||
|
||||
// Debug mode (CBM_LSP_DEBUG env, shared across all language LSPs).
|
||||
bool debug;
|
||||
} 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);
|
||||
|
||||
// Add an import mapping (call once per CBMImport entry).
|
||||
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);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// --- 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);
|
||||
|
||||
/* 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);
|
||||
|
||||
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;
|
||||
int source_len;
|
||||
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;
|
||||
int import_count;
|
||||
} CBMBatchPyLSPFile;
|
||||
|
||||
void cbm_batch_py_lsp_cross(CBMArena *arena, CBMBatchPyLSPFile *files, int file_count,
|
||||
CBMResolvedCallArray *out);
|
||||
|
||||
#endif // CBM_LSP_PY_LSP_H
|
||||
@@ -0,0 +1,319 @@
|
||||
/* rust_cargo.c — Hand-rolled TOML subset parser for Cargo.toml.
|
||||
*
|
||||
* Recognises only the shapes we need (per RUST_LSP_FOLLOWUP §A3):
|
||||
* - `[section]`, `[a.b.c]`, `[workspace]`, `[dependencies]`
|
||||
* - `key = "string"`, `key = 'string'`, `key = [...]`,
|
||||
* `key = { ... }`
|
||||
* - `members = ["a", "b/c"]`
|
||||
*
|
||||
* Everything else (numbers, dates, deep tables, comments past EOL) is
|
||||
* skipped without error.
|
||||
*/
|
||||
|
||||
#include "rust_cargo.h"
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
/* ── Tiny tokenizer ──────────────────────────────────────────── */
|
||||
|
||||
static int skip_ws_and_comment(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 == '#') {
|
||||
while (from < len && s[from] != '\n') from++;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return from;
|
||||
}
|
||||
|
||||
static bool is_ident_char(char c) {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
|
||||
(c >= '0' && c <= '9') || c == '_' || c == '-';
|
||||
}
|
||||
|
||||
/* Parse a bare key (ident-like). Stores arena-allocated copy in *out. */
|
||||
static int parse_key(CBMArena* a, const char* s, int len, int from,
|
||||
const char** out) {
|
||||
int start = from;
|
||||
if (from < len && s[from] == '"') {
|
||||
from++;
|
||||
start = from;
|
||||
while (from < len && s[from] != '"') {
|
||||
if (s[from] == '\\' && from + 1 < len) from += 2;
|
||||
else from++;
|
||||
}
|
||||
*out = cbm_arena_strndup(a, s + start, (size_t)(from - start));
|
||||
if (from < len && s[from] == '"') from++;
|
||||
return from;
|
||||
}
|
||||
while (from < len && is_ident_char(s[from])) from++;
|
||||
if (from > start) {
|
||||
*out = cbm_arena_strndup(a, s + start, (size_t)(from - start));
|
||||
}
|
||||
return from;
|
||||
}
|
||||
|
||||
/* Parse a string literal (single or double quoted). */
|
||||
static int parse_string(CBMArena* a, const char* s, int len, int from,
|
||||
const char** out) {
|
||||
if (from >= len) return from;
|
||||
char q = s[from];
|
||||
if (q != '"' && q != '\'') return from;
|
||||
from++;
|
||||
int start = from;
|
||||
while (from < len && s[from] != q) {
|
||||
if (s[from] == '\\' && from + 1 < len) from += 2;
|
||||
else from++;
|
||||
}
|
||||
*out = cbm_arena_strndup(a, s + start, (size_t)(from - start));
|
||||
if (from < len) from++;
|
||||
return from;
|
||||
}
|
||||
|
||||
/* Skip a value (used for keys we don't care about). Handles strings,
|
||||
* arrays, inline tables, bare values. */
|
||||
static int skip_value(const char* s, int len, int from) {
|
||||
from = skip_ws_and_comment(s, len, from);
|
||||
if (from >= len) return from;
|
||||
char c = s[from];
|
||||
if (c == '"' || c == '\'') {
|
||||
from++;
|
||||
while (from < len && s[from] != c) {
|
||||
if (s[from] == '\\' && from + 1 < len) from += 2;
|
||||
else from++;
|
||||
}
|
||||
if (from < len) from++;
|
||||
return from;
|
||||
}
|
||||
if (c == '[' || c == '{') {
|
||||
char open = c, close = (c == '[' ? ']' : '}');
|
||||
int depth = 1;
|
||||
from++;
|
||||
while (from < len && depth > 0) {
|
||||
char d = s[from];
|
||||
if (d == '"' || d == '\'') {
|
||||
from++;
|
||||
while (from < len && s[from] != d) {
|
||||
if (s[from] == '\\' && from + 1 < len) from += 2;
|
||||
else from++;
|
||||
}
|
||||
if (from < len) from++;
|
||||
continue;
|
||||
}
|
||||
if (d == open) depth++;
|
||||
else if (d == close) depth--;
|
||||
from++;
|
||||
}
|
||||
return from;
|
||||
}
|
||||
/* Bare value: skip to end of line. */
|
||||
while (from < len && s[from] != '\n' && s[from] != '#') from++;
|
||||
return from;
|
||||
}
|
||||
|
||||
/* Parse `[section.path]` header — returns the section name as a flat
|
||||
* dotted string, e.g. "dependencies" or "workspace.dependencies". */
|
||||
static int parse_section(CBMArena* a, const char* s, int len, int from,
|
||||
const char** out) {
|
||||
if (from >= len || s[from] != '[') return from;
|
||||
/* Skip leading `[` or `[[`. */
|
||||
bool array_of_tables = false;
|
||||
from++;
|
||||
if (from < len && s[from] == '[') { array_of_tables = true; from++; }
|
||||
int start = from;
|
||||
while (from < len && s[from] != ']') from++;
|
||||
*out = cbm_arena_strndup(a, s + start, (size_t)(from - start));
|
||||
/* Consume closing `]` (or `]]`). */
|
||||
if (from < len) from++;
|
||||
if (array_of_tables && from < len && s[from] == ']') from++;
|
||||
return from;
|
||||
}
|
||||
|
||||
/* For the `[dependencies]` / `[dev-dependencies]` / `[workspace.dependencies]`
|
||||
* sections, parse `key = value` lines until the next section. The value
|
||||
* may be a string (the version) or an inline table. We capture both
|
||||
* shapes — only the key (crate name) and optional `path = "..."` field
|
||||
* matter for us. */
|
||||
static int parse_dep_entry(CBMArena* a, const char* s, int len, int from,
|
||||
CBMCargoManifest* out) {
|
||||
from = skip_ws_and_comment(s, len, from);
|
||||
if (from >= len || s[from] == '[') return from;
|
||||
const char* key = NULL;
|
||||
from = parse_key(a, s, len, from, &key);
|
||||
from = skip_ws_and_comment(s, len, from);
|
||||
if (from < len && s[from] == '=') {
|
||||
from++;
|
||||
from = skip_ws_and_comment(s, len, from);
|
||||
}
|
||||
const char* path_val = NULL;
|
||||
if (from < len && s[from] == '{') {
|
||||
/* Inline table — scan for `path = "..."`. */
|
||||
int depth = 1;
|
||||
from++;
|
||||
while (from < len && depth > 0) {
|
||||
from = skip_ws_and_comment(s, len, from);
|
||||
if (from >= len) break;
|
||||
char c = s[from];
|
||||
if (c == '}') { depth--; from++; continue; }
|
||||
if (c == ',') { from++; continue; }
|
||||
/* sub-key */
|
||||
const char* sub_key = NULL;
|
||||
from = parse_key(a, s, len, from, &sub_key);
|
||||
from = skip_ws_and_comment(s, len, from);
|
||||
if (from < len && s[from] == '=') {
|
||||
from++;
|
||||
from = skip_ws_and_comment(s, len, from);
|
||||
}
|
||||
if (sub_key && strcmp(sub_key, "path") == 0) {
|
||||
from = parse_string(a, s, len, from, &path_val);
|
||||
} else {
|
||||
from = skip_value(s, len, from);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
from = skip_value(s, len, from);
|
||||
}
|
||||
if (key && out->dep_count < CBM_CARGO_MAX_DEPS) {
|
||||
out->deps[out->dep_count].name = key;
|
||||
out->deps[out->dep_count].path = path_val;
|
||||
out->dep_count++;
|
||||
}
|
||||
return from;
|
||||
}
|
||||
|
||||
/* ── Section dispatcher ──────────────────────────────────────── */
|
||||
|
||||
static int parse_package_kv(CBMArena* a, const char* s, int len, int from,
|
||||
CBMCargoManifest* out) {
|
||||
from = skip_ws_and_comment(s, len, from);
|
||||
if (from >= len || s[from] == '[') return from;
|
||||
const char* key = NULL;
|
||||
from = parse_key(a, s, len, from, &key);
|
||||
from = skip_ws_and_comment(s, len, from);
|
||||
if (from < len && s[from] == '=') {
|
||||
from++;
|
||||
from = skip_ws_and_comment(s, len, from);
|
||||
}
|
||||
if (key && strcmp(key, "name") == 0) {
|
||||
from = parse_string(a, s, len, from, &out->package_name);
|
||||
} else if (key && strcmp(key, "version") == 0) {
|
||||
from = parse_string(a, s, len, from, &out->package_version);
|
||||
} else {
|
||||
from = skip_value(s, len, from);
|
||||
}
|
||||
return from;
|
||||
}
|
||||
|
||||
static int parse_workspace_kv(CBMArena* a, const char* s, int len, int from,
|
||||
CBMCargoManifest* out) {
|
||||
from = skip_ws_and_comment(s, len, from);
|
||||
if (from >= len || s[from] == '[') return from;
|
||||
out->is_workspace_root = true;
|
||||
const char* key = NULL;
|
||||
from = parse_key(a, s, len, from, &key);
|
||||
from = skip_ws_and_comment(s, len, from);
|
||||
if (from < len && s[from] == '=') {
|
||||
from++;
|
||||
from = skip_ws_and_comment(s, len, from);
|
||||
}
|
||||
if (key && strcmp(key, "members") == 0 && from < len && s[from] == '[') {
|
||||
from++;
|
||||
while (from < len && s[from] != ']') {
|
||||
from = skip_ws_and_comment(s, len, from);
|
||||
if (from < len && (s[from] == '"' || s[from] == '\'')) {
|
||||
const char* mem = NULL;
|
||||
from = parse_string(a, s, len, from, &mem);
|
||||
if (mem && out->member_count < CBM_CARGO_MAX_MEMBERS) {
|
||||
/* Derive a member NAME from the path's last segment. */
|
||||
const char* last = mem;
|
||||
for (const char* p = mem; *p; p++) {
|
||||
if (*p == '/') last = p + 1;
|
||||
}
|
||||
out->members[out->member_count].member_name = last;
|
||||
out->members[out->member_count].member_path = mem;
|
||||
out->member_count++;
|
||||
}
|
||||
}
|
||||
from = skip_ws_and_comment(s, len, from);
|
||||
if (from < len && s[from] == ',') from++;
|
||||
from = skip_ws_and_comment(s, len, from);
|
||||
}
|
||||
if (from < len) from++; /* consume `]` */
|
||||
} else {
|
||||
from = skip_value(s, len, from);
|
||||
}
|
||||
return from;
|
||||
}
|
||||
|
||||
void cbm_cargo_parse(CBMArena* arena, const char* src, int src_len,
|
||||
CBMCargoManifest* out) {
|
||||
if (!arena || !src || !out) return;
|
||||
memset(out, 0, sizeof(*out));
|
||||
if (src_len <= 0) src_len = (int)strlen(src);
|
||||
|
||||
int from = 0;
|
||||
/* Default: pre-header content treated as [package]. */
|
||||
const char* section = "package";
|
||||
|
||||
while (from < src_len) {
|
||||
from = skip_ws_and_comment(src, src_len, from);
|
||||
if (from >= src_len) break;
|
||||
if (src[from] == '[') {
|
||||
const char* hdr = NULL;
|
||||
from = parse_section(arena, src, src_len, from, &hdr);
|
||||
section = hdr ? hdr : "";
|
||||
continue;
|
||||
}
|
||||
if (!section) {
|
||||
from = skip_value(src, src_len, from);
|
||||
continue;
|
||||
}
|
||||
if (strcmp(section, "package") == 0) {
|
||||
from = parse_package_kv(arena, src, src_len, from, out);
|
||||
} else if (strcmp(section, "workspace") == 0) {
|
||||
from = parse_workspace_kv(arena, src, src_len, from, out);
|
||||
} else if (strcmp(section, "dependencies") == 0 ||
|
||||
strcmp(section, "dev-dependencies") == 0 ||
|
||||
strcmp(section, "build-dependencies") == 0 ||
|
||||
strcmp(section, "workspace.dependencies") == 0) {
|
||||
from = parse_dep_entry(arena, src, src_len, from, out);
|
||||
} else {
|
||||
/* Section we don't care about — skip the line. */
|
||||
while (from < src_len && src[from] != '\n' && src[from] != '[') {
|
||||
from++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool cbm_cargo_is_known_dep(const CBMCargoManifest* m, const char* head) {
|
||||
if (!m || !head) return false;
|
||||
for (int i = 0; i < m->dep_count; i++) {
|
||||
if (m->deps[i].name && strcmp(m->deps[i].name, head) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < m->member_count; i++) {
|
||||
if (m->members[i].member_name &&
|
||||
strcmp(m->members[i].member_name, head) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const CBMCargoMember* cbm_cargo_find_member(const CBMCargoManifest* m,
|
||||
const char* name) {
|
||||
if (!m || !name) return NULL;
|
||||
for (int i = 0; i < m->member_count; i++) {
|
||||
if (m->members[i].member_name &&
|
||||
strcmp(m->members[i].member_name, name) == 0) {
|
||||
return &m->members[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/* rust_cargo.h — Cargo.toml parser for the Rust LSP.
|
||||
*
|
||||
* Per RUST_LSP_FOLLOWUP §A3: we don't run Cargo or build, but we CAN
|
||||
* parse `Cargo.toml` and `[workspace] members` to learn:
|
||||
* - the crate name (`[package].name`)
|
||||
* - declared dependencies (`[dependencies]` + `[dev-dependencies]`)
|
||||
* - workspace members + their relative paths
|
||||
*
|
||||
* The pipeline uses this to map `other_member::foo` → that member's
|
||||
* module QN, and to mark calls into known external deps as "external,
|
||||
* not local" rather than fully unresolved.
|
||||
*
|
||||
* The parser is a tiny hand-written TOML subset: handles `[section]`
|
||||
* headers, `key = "value"`, `key = { path = "...", … }` (the relevant
|
||||
* subset for our needs), arrays `members = ["a", "b"]`. It IGNORES
|
||||
* everything it doesn't understand — that's safe because Cargo.toml
|
||||
* is much richer than what we use. */
|
||||
|
||||
#ifndef CBM_LSP_RUST_CARGO_H
|
||||
#define CBM_LSP_RUST_CARGO_H
|
||||
|
||||
#include "../arena.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
#define CBM_CARGO_MAX_DEPS 256
|
||||
#define CBM_CARGO_MAX_MEMBERS 64
|
||||
|
||||
typedef struct {
|
||||
const char* name; /* declared dependency name */
|
||||
const char* path; /* path = "../foo" if local, else NULL */
|
||||
} CBMCargoDep;
|
||||
|
||||
typedef struct {
|
||||
const char* member_name; /* directory name */
|
||||
const char* member_path; /* relative path inside workspace root */
|
||||
} CBMCargoMember;
|
||||
|
||||
typedef struct CBMCargoManifest {
|
||||
const char* package_name; /* [package].name, NULL if missing */
|
||||
const char* package_version; /* [package].version, NULL if missing */
|
||||
bool is_workspace_root; /* [workspace] section seen */
|
||||
|
||||
CBMCargoDep deps[CBM_CARGO_MAX_DEPS];
|
||||
int dep_count;
|
||||
|
||||
CBMCargoMember members[CBM_CARGO_MAX_MEMBERS];
|
||||
int member_count;
|
||||
} CBMCargoManifest;
|
||||
|
||||
/* Parse a Cargo.toml-formatted string. The output strings are
|
||||
* arena-allocated (so the caller doesn't need to keep `src` alive). */
|
||||
void cbm_cargo_parse(CBMArena* arena, const char* src, int src_len,
|
||||
CBMCargoManifest* out);
|
||||
|
||||
/* Convenience: does a given path-prefix look like one of the listed
|
||||
* dependency names? Used by the resolver to recognise external crate
|
||||
* paths. */
|
||||
bool cbm_cargo_is_known_dep(const CBMCargoManifest* m, const char* head);
|
||||
|
||||
/* Find a workspace member by crate name. Returns NULL if absent. */
|
||||
const CBMCargoMember* cbm_cargo_find_member(const CBMCargoManifest* m,
|
||||
const char* name);
|
||||
|
||||
#endif /* CBM_LSP_RUST_CARGO_H */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,352 @@
|
||||
/* rust_lsp.h — Type-aware call resolution for Rust source files.
|
||||
*
|
||||
* Architecture mirrors go_lsp / py_lsp: build a `CBMTypeRegistry` from the
|
||||
* file's own definitions plus a small hand-rolled stdlib seed, then walk
|
||||
* each function body with scope-tracked variable bindings, evaluating the
|
||||
* type of each receiver expression and dispatching method calls through
|
||||
* inherent impls, trait impls, deref/autoref, generics, and `Self`.
|
||||
*
|
||||
* The implementation is a structural mirror of the algorithm in
|
||||
* `rust-analyzer` (`hir-def/resolver.rs` + `hir-ty/method_resolution.rs`)
|
||||
* reverse-engineered into pure C with no new runtime dependencies. The
|
||||
* goal is per-file (and cross-file) call attribution at >=90% parity with
|
||||
* what rust-analyzer would produce for the same source — without the cost
|
||||
* of a full IDE process.
|
||||
*
|
||||
* Public entry points:
|
||||
* - `cbm_run_rust_lsp` — single-file resolution invoked from
|
||||
* `cbm_extract_file()`.
|
||||
* - `cbm_run_rust_lsp_cross` — cross-file resolution given a list of
|
||||
* `CBMLSPDef`s gathered by the pipeline.
|
||||
* - `cbm_batch_rust_lsp_cross` — batch wrapper that processes several
|
||||
* files in one CGo call (per-file arenas + result copy).
|
||||
*/
|
||||
#ifndef CBM_LSP_RUST_LSP_H
|
||||
#define CBM_LSP_RUST_LSP_H
|
||||
|
||||
#include "type_rep.h"
|
||||
#include "scope.h"
|
||||
#include "type_registry.h"
|
||||
#include "lsp_neg_memo.h"
|
||||
#include "../cbm.h"
|
||||
#include "go_lsp.h" // for CBMLSPDef (pipeline def), used by the Tier-2 builder
|
||||
|
||||
/* Forward declaration — defined in rust_cargo.h. We keep it forward
|
||||
* here to avoid pulling rust_cargo.h into every consumer that only
|
||||
* needs the LSP API. */
|
||||
struct CBMCargoManifest;
|
||||
|
||||
/* Global confidence assigned to LSP-resolved call edges. The pipeline's
|
||||
* 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) */
|
||||
|
||||
/* 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;
|
||||
int source_len;
|
||||
|
||||
const CBMTypeRegistry *registry;
|
||||
CBMScope *current_scope;
|
||||
|
||||
/* Negative-lookup memo for the registry-pure resolve cascades (trait
|
||||
* method / sole-trait-impl / free-func fallback). Active ONLY when the
|
||||
* registry is sealed (read_only) — see lsp_neg_memo.h. Arena-backed,
|
||||
* dies with the file. Zeroed by rust_lsp_init's memset (lazy alloc). */
|
||||
CBMNegMemo neg_memo;
|
||||
|
||||
/* `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;
|
||||
int use_count;
|
||||
|
||||
const char **glob_module_qns;
|
||||
int glob_count;
|
||||
|
||||
/* Module-qualified name for this file (e.g. "<project>.<crate>.foo"). */
|
||||
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;
|
||||
|
||||
/* `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;
|
||||
|
||||
/* Closure parameter inference: when the call resolver descends into a
|
||||
* call's argument list and the callee is a method like
|
||||
* `.map(|x| ...)` / `.filter(|x| ...)`, it stashes the inferred type
|
||||
* 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;
|
||||
|
||||
/* User-defined `macro_rules!` definitions collected during the
|
||||
* pre-walk phase. Each rule stores its pattern + transcriber text;
|
||||
* `macro_invocation` resolution attempts to match the invocation
|
||||
* against each rule and then substitutes/re-walks the expansion.
|
||||
*
|
||||
* The arrays are doubling-grown out of `arena`. */
|
||||
struct RustMacroRule **macro_rules_arr;
|
||||
int macro_rules_count;
|
||||
|
||||
/* Recursion guard for macro expansion. Real macro_rules! can be
|
||||
* recursive; we cap at 8 nested expansions to keep the walker
|
||||
* bounded. */
|
||||
int macro_expand_depth;
|
||||
|
||||
/* Pathological-input guard: counts the number of call-resolution
|
||||
* + type-evaluation steps spent on the current file. When the
|
||||
* counter exceeds the cap we stop attributing further calls so a
|
||||
* malicious or hand-crafted file cannot wedge the resolver.
|
||||
* Mirrors the cap added to `c_lsp.c` since the worktree branched
|
||||
* (RUST_LSP_FOLLOWUP §B.2). */
|
||||
int eval_step_count;
|
||||
|
||||
/* Cargo.toml manifest, when the caller has parsed one and routed
|
||||
* it through. The resolver consults `dep_count`/`member_count` so
|
||||
* paths beginning with a workspace member or declared dependency
|
||||
* route to a sensible canonical form (`<crate>.<tail>`) instead of
|
||||
* 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;
|
||||
|
||||
/* Chalk-lite trait-bound environment for the *currently-active*
|
||||
* function or impl. Populated at function/impl entry from the
|
||||
* `<T: Bound + …>` parameter list and `where` clause. Consulted
|
||||
* by trait method dispatch when the receiver is typed as a type
|
||||
* parameter — we look up the param's bounds and try resolving
|
||||
* the method on each trait. Also stores associated-type bindings
|
||||
* (`T: Iterator<Item = U>` → `U` aliases the Item of `T`).
|
||||
*
|
||||
* 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" */
|
||||
} *type_param_bounds;
|
||||
int type_param_bound_count;
|
||||
|
||||
struct {
|
||||
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;
|
||||
|
||||
/* Syntactic-call list (result->calls), borrowed from the per-file
|
||||
* extraction result. The downstream pipeline only turns a resolved_call
|
||||
* into a CALLS edge when a *syntactic* CBMCall with the same
|
||||
* (enclosing_func_qn, callee short-name) exists here. Some calls the Rust
|
||||
* resolver recovers — operator-trait desugaring (`a + b`) and method
|
||||
* calls hidden inside macro token-trees (`format!("{}", d.label())`) —
|
||||
* never appear in result->calls because the syntactic extractor cannot
|
||||
* 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;
|
||||
|
||||
/* While >0, rust_emit_resolved_call also injects a matching synthetic
|
||||
* CBMCall into `syn_calls` so the recovered call becomes an edge. Set
|
||||
* around the macro-argument re-parse where the syntactic extractor never
|
||||
* produced a call node. */
|
||||
int inject_syn_calls;
|
||||
|
||||
/* CBM_LSP_DEBUG=1 in env enables verbose stderr trace. */
|
||||
bool debug;
|
||||
|
||||
/* Per-ROOT-invocation macro-expansion memo. Kernel macro_rules are often
|
||||
* self-recursive (define_sizes! re-invokes itself with @internal/@impls
|
||||
* args) and the expansion fallback ("no pattern matched — still expand the
|
||||
* first rule") makes that recursion non-convergent: with an unbounded
|
||||
* BREADTH under the depth-8 guard, 2-44 source invocations exploded into
|
||||
* ~200k expansions (each a full tree-sitter parse) ≈ 63 s per file.
|
||||
* Within one expansion chain an identical (macro, substituted body) is
|
||||
* walked once — identical text implies an identical walk, and recursive
|
||||
* re-invocations have no distinct source site to attribute. Reset at each
|
||||
* top-level invocation so distinct source sites keep their attribution. */
|
||||
CBMNegMemo macro_memo;
|
||||
} 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);
|
||||
|
||||
/* 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);
|
||||
|
||||
/* 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);
|
||||
|
||||
/* 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);
|
||||
|
||||
/* Bidirectional variant: evaluate an expression with an `expected`
|
||||
* hint available. The hint is used to:
|
||||
* - disambiguate `Vec::new()` / `HashMap::default()` when the LHS
|
||||
* of a let or function arg constrains the result type;
|
||||
* - infer turbofish-free method generics (`s.parse()` typed as
|
||||
* `Result<i32, _>`);
|
||||
* - thread context into nested expressions (struct field init,
|
||||
* match arm, if-else branches).
|
||||
*
|
||||
* `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);
|
||||
|
||||
/* 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);
|
||||
|
||||
/* Bind the bindings introduced by a let/for/match-pattern statement into
|
||||
* the current scope. */
|
||||
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);
|
||||
|
||||
/* 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);
|
||||
|
||||
/* Synthesize call edges for curated attribute proc-macros.
|
||||
*
|
||||
* For each `CBMDefinition` whose decorators include a known
|
||||
* attribute proc-macro (`#[tokio::main]`, `#[tracing::instrument]`,
|
||||
* `#[async_trait]`, …), emit synthetic edges from that definition
|
||||
* to the helper functions the macro would have injected (e.g.
|
||||
* `tokio::runtime::Runtime::new` + `block_on`). The edges carry
|
||||
* strategy `lsp_proc_macro` so consumers can distinguish them from
|
||||
* direct call attributions.
|
||||
*
|
||||
* Called automatically from `cbm_run_rust_lsp_with_manifest`. */
|
||||
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);
|
||||
|
||||
/* Register a curated subset of the Rust core/alloc/std prelude into the
|
||||
* given registry. The seed is intentionally compact (~150 types, ~600
|
||||
* methods) and covers Option/Result/Vec/String/HashMap/BTreeMap/Iterator
|
||||
* 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);
|
||||
|
||||
/* 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);
|
||||
|
||||
/* --- Cross-file LSP resolution --- */
|
||||
|
||||
/* Cross-file definition record. The Rust LSP reuses the same shape as
|
||||
* `CBMLSPDef` in `go_lsp.h` for compatibility with the pipeline plumbing
|
||||
* — 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 */
|
||||
} 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);
|
||||
|
||||
/* Same as `cbm_run_rust_lsp_cross`, plus an optional parsed Cargo manifest
|
||||
* (NULL = manifest-free behaviour). The manifest lets call paths whose head
|
||||
* is a workspace member / declared dependency route across the crate
|
||||
* boundary (`crate_a::foo` → the def inside crate_a). Wired from the
|
||||
* cross-file LSP pass (pass_lsp_cross.c) which builds the manifest once from
|
||||
* the project root Cargo.toml. */
|
||||
void cbm_run_rust_lsp_cross_with_manifest(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,
|
||||
const struct CBMCargoManifest *manifest,
|
||||
CBMResolvedCallArray *out);
|
||||
|
||||
/* Tier-2: build the project-wide Rust cross registry ONCE from all defs, finalize,
|
||||
* and seal read-only. Shared across every Rust file's resolve (mirrors C/py/cs/ts).
|
||||
* Def-driven → byte-identical entries to the per-file build. */
|
||||
CBMTypeRegistry *cbm_rust_build_cross_registry(CBMArena *arena, CBMLSPDef *defs, int def_count);
|
||||
|
||||
/* Cross-file Rust resolve using a pre-built shared registry (Tier-2). Skips the
|
||||
* per-file registry build; just parse + resolve. `manifest` = the same Cargo manifest
|
||||
* the per-file path uses (cross-crate #56). `result` may be NULL (the cross path does
|
||||
* not synthesise proc-macro edges). Mirrors cbm_run_c_lsp_cross_with_registry. */
|
||||
void cbm_run_rust_lsp_cross_with_registry(CBMArena *arena, const char *source, int source_len,
|
||||
const char *module_qn, const CBMTypeRegistry *reg,
|
||||
const char **import_names, const char **import_qns,
|
||||
int import_count, TSTree *cached_tree,
|
||||
const struct CBMCargoManifest *manifest,
|
||||
CBMResolvedCallArray *out, CBMFileResult *result);
|
||||
|
||||
/* Per-file input for batch cross-file Rust LSP processing. */
|
||||
typedef struct {
|
||||
const char *source;
|
||||
int source_len;
|
||||
const char *module_qn;
|
||||
TSTree *cached_tree;
|
||||
CBMRustLSPDef *defs;
|
||||
int def_count;
|
||||
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);
|
||||
|
||||
#endif /* CBM_LSP_RUST_LSP_H */
|
||||
@@ -0,0 +1,145 @@
|
||||
/* rust_proc_macros.c — Curated attribute proc-macro expansion.
|
||||
*
|
||||
* RUST_LSP_FOLLOWUP §A1 + improvement Option B: extend derive synthesis
|
||||
* (already done for #[derive(...)]) to *attribute*-style proc-macros.
|
||||
*
|
||||
* We can't run proc-macros (would need rustc). But for the ~20
|
||||
* most-used attribute proc-macros we KNOW the expansion shape and can
|
||||
* synthesize equivalent calls/impls so the resolver doesn't go blind.
|
||||
*
|
||||
* Covered:
|
||||
* - `#[tokio::main]` / `#[tokio::test]` — wrap fn body in
|
||||
* `tokio::runtime::Runtime::new().unwrap().block_on(async { ... })`
|
||||
* - `#[async_std::main]` — same shape via async_std executor
|
||||
* - `#[async_trait::async_trait]` — methods on the trait become
|
||||
* `Pin<Box<dyn Future>>` returning shapes; we just register the
|
||||
* method bodies normally (the resolver already handles `async fn`)
|
||||
* - `#[derive(thiserror::Error)]` — synthesise `source`, `description`
|
||||
* (we already synth via the curated derive table; this just adds
|
||||
* `thiserror::Error` to the alias chain)
|
||||
* - `#[derive(serde::Serialize|Deserialize)]` — already covered by
|
||||
* the curated derive table (alias `serde::Serialize` to
|
||||
* `serde.Serialize`)
|
||||
* - `#[clap(...)]` field attributes — no synthesis needed; the
|
||||
* `#[derive(clap::Parser)]` outer macro handles the impl
|
||||
* - `#[tracing::instrument]` — wraps the function body in a span;
|
||||
* we synthesise a call to `tracing::span::Span::enter`
|
||||
* - `#[rocket::main]`/`#[actix_web::main]` — same shape as tokio
|
||||
* - `#[test]` / `#[bench]` — no synthesis; the function body is
|
||||
* attributed as-is
|
||||
*
|
||||
* For each, we register a synthetic "wrapper call" QN per
|
||||
* decorator → resolved-call edge so the project's tracing/dependency
|
||||
* analyses see what the proc-macro injected. */
|
||||
|
||||
#include "../arena.h"
|
||||
#include "../cbm.h"
|
||||
#include "rust_lsp.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* The attribute-decorator table. For each pattern in `decorator` text
|
||||
* we emit a *synthetic call edge* attributed to the wrapped function
|
||||
* so tracing tools can see the implicit dependency. The synthesis is
|
||||
* conservative: we emit only when the decorator text matches one of
|
||||
* the curated tokens — anything else is left alone. */
|
||||
typedef struct {
|
||||
const char* match; /* substring to find in the decorator text */
|
||||
const char* edges[6]; /* synthesized callee QNs (NULL-terminated) */
|
||||
} RustAttrSynth;
|
||||
|
||||
static const RustAttrSynth ATTR_SYNTH[] = {
|
||||
{"tokio::main", {
|
||||
"tokio.runtime.Runtime.new",
|
||||
"tokio.runtime.Runtime.block_on",
|
||||
NULL
|
||||
}},
|
||||
{"tokio::test", {
|
||||
"tokio.runtime.Runtime.new",
|
||||
"tokio.runtime.Runtime.block_on",
|
||||
NULL
|
||||
}},
|
||||
{"async_std::main", {
|
||||
"async_std.task.block_on",
|
||||
NULL
|
||||
}},
|
||||
{"actix_web::main", {
|
||||
"actix_web.rt.System.new",
|
||||
"actix_web.rt.System.block_on",
|
||||
NULL
|
||||
}},
|
||||
{"actix_rt::main", {
|
||||
"actix_rt.System.new",
|
||||
"actix_rt.System.block_on",
|
||||
NULL
|
||||
}},
|
||||
{"rocket::main", {
|
||||
"rocket.async_main",
|
||||
NULL
|
||||
}},
|
||||
{"rocket::launch", {
|
||||
"rocket.launch",
|
||||
NULL
|
||||
}},
|
||||
{"tracing::instrument", {
|
||||
"tracing.span.Span.enter",
|
||||
NULL
|
||||
}},
|
||||
{"async_trait", {
|
||||
/* The macro wraps each method's return in
|
||||
* `Pin<Box<dyn Future<Output = R> + Send>>` — no method-name
|
||||
* synthesis needed, just record the boxed-future bridging
|
||||
* call so analyses see the async-trait shape. */
|
||||
"alloc.boxed.Box.new",
|
||||
"core.pin.Pin.new",
|
||||
NULL
|
||||
}},
|
||||
{"wasm_bindgen", {
|
||||
"wasm_bindgen.JsValue.from",
|
||||
NULL
|
||||
}},
|
||||
{"napi", {
|
||||
"napi.Env.create",
|
||||
NULL
|
||||
}},
|
||||
{"pyo3::pyfunction", {
|
||||
"pyo3.Python.with_gil",
|
||||
NULL
|
||||
}},
|
||||
{"pyo3::pymethods", {
|
||||
"pyo3.Python.with_gil",
|
||||
NULL
|
||||
}},
|
||||
};
|
||||
|
||||
#define ATTR_SYNTH_COUNT (int)(sizeof(ATTR_SYNTH) / sizeof(ATTR_SYNTH[0]))
|
||||
|
||||
/* Inspect a definition's decorators and, if any matches a curated
|
||||
* proc-macro attribute, emit synthetic edges from that definition's
|
||||
* QN to the wrapper functions the macro would have injected. The
|
||||
* edges are best-effort and tagged `lsp_proc_macro` so consumers can
|
||||
* distinguish them from direct calls. */
|
||||
void cbm_rust_synth_proc_macro_edges(CBMArena* arena, CBMFileResult* result) {
|
||||
if (!arena || !result) return;
|
||||
for (int i = 0; i < result->defs.count; i++) {
|
||||
CBMDefinition* d = &result->defs.items[i];
|
||||
if (!d->decorators || !d->qualified_name) continue;
|
||||
for (int di = 0; d->decorators[di]; di++) {
|
||||
const char* dec = d->decorators[di];
|
||||
for (int t = 0; t < ATTR_SYNTH_COUNT; t++) {
|
||||
const RustAttrSynth* s = &ATTR_SYNTH[t];
|
||||
if (!strstr(dec, s->match)) continue;
|
||||
/* Emit one resolved call per edge. */
|
||||
for (int e = 0; s->edges[e]; e++) {
|
||||
CBMResolvedCall rc;
|
||||
memset(&rc, 0, sizeof(rc));
|
||||
rc.caller_qn = d->qualified_name;
|
||||
rc.callee_qn = cbm_arena_strdup(arena, s->edges[e]);
|
||||
rc.strategy = "lsp_proc_macro";
|
||||
rc.confidence = 0.78f; /* high-but-not-direct */
|
||||
cbm_resolvedcall_push(&result->resolved_calls, arena, rc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
/* rust_rustdoc.c — Parse rustdoc JSON and register into the type
|
||||
* registry. Pure-C JSON ingestion via the already-vendored yyjson.
|
||||
*
|
||||
* The rustdoc JSON shape (as of mid-2024):
|
||||
*
|
||||
* { "format_version": <n>,
|
||||
* "root": "0:0",
|
||||
* "crate_version": "1.2.3",
|
||||
* "includes_private": false,
|
||||
* "index": {
|
||||
* "<id>": {
|
||||
* "id": "<id>",
|
||||
* "name": "Foo",
|
||||
* "visibility": "public",
|
||||
* "inner": {
|
||||
* "struct": {...} | "trait": {...} |
|
||||
* "function": { "decl": { "inputs": [[name, type], ...],
|
||||
* "output": <type> },
|
||||
* "header": {...} }
|
||||
* | ...
|
||||
* },
|
||||
* "links": {...}
|
||||
* }
|
||||
* },
|
||||
* "paths": { "<id>": { "path": ["serde", "Serialize"], "kind": "trait" } }
|
||||
* }
|
||||
*
|
||||
* We walk `index`, look up the corresponding `paths[id]` to get the
|
||||
* fully-qualified path, and register types/methods/functions.
|
||||
*
|
||||
* The JSON format is unstable across versions; we tolerate missing
|
||||
* fields and skip items we can't make sense of (silently).
|
||||
*/
|
||||
|
||||
#include "rust_rustdoc.h"
|
||||
#include "type_rep.h"
|
||||
#include "../../../vendored/yyjson/yyjson.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* Concatenate a path array (yyjson_val of strings) into a single
|
||||
* dotted QN string, optionally prefixed by `crate_qn`. */
|
||||
static const char* join_path(CBMArena* arena, yyjson_val* path_arr,
|
||||
const char* crate_qn) {
|
||||
if (!path_arr || !yyjson_is_arr(path_arr)) return NULL;
|
||||
char buf[1024];
|
||||
int off = 0;
|
||||
if (crate_qn && crate_qn[0]) {
|
||||
off = snprintf(buf, sizeof(buf), "%s", crate_qn);
|
||||
}
|
||||
yyjson_val* part;
|
||||
yyjson_arr_iter it;
|
||||
yyjson_arr_iter_init(path_arr, &it);
|
||||
bool first = true;
|
||||
while ((part = yyjson_arr_iter_next(&it))) {
|
||||
const char* s = yyjson_get_str(part);
|
||||
if (!s) continue;
|
||||
/* Skip the crate's own root segment if we already prefixed. */
|
||||
if (first && crate_qn && crate_qn[0] && strcmp(s, crate_qn) == 0) {
|
||||
first = false;
|
||||
continue;
|
||||
}
|
||||
if (off > 0 && off < (int)sizeof(buf) - 1) {
|
||||
buf[off++] = '.';
|
||||
}
|
||||
int n = snprintf(buf + off, sizeof(buf) - off, "%s", s);
|
||||
if (n < 0) break;
|
||||
off += n;
|
||||
if (off >= (int)sizeof(buf) - 1) break;
|
||||
first = false;
|
||||
}
|
||||
if (off <= 0) return NULL;
|
||||
return cbm_arena_strndup(arena, buf, (size_t)off);
|
||||
}
|
||||
|
||||
/* Convert a rustdoc Type JSON value into a CBMType. Best-effort —
|
||||
* rustdoc's Type union is large. We map:
|
||||
* - { "primitive": "i32" } → BUILTIN
|
||||
* - { "resolved_path": { path, args, ... } } → NAMED or TEMPLATE
|
||||
* - { "borrowed_ref": { type, ... } } → REFERENCE
|
||||
* - { "slice": <T> } → SLICE
|
||||
* - { "array": { type, len }} → SLICE
|
||||
* - { "tuple": [<T>...] } → TUPLE or BUILTIN("()") if empty
|
||||
* - everything else → UNKNOWN
|
||||
*/
|
||||
static const CBMType* parse_rustdoc_type(yyjson_val* t, CBMArena* arena,
|
||||
const char* crate_qn) {
|
||||
if (!t || !yyjson_is_obj(t)) return cbm_type_unknown();
|
||||
|
||||
yyjson_val* prim = yyjson_obj_get(t, "primitive");
|
||||
if (prim && yyjson_is_str(prim)) {
|
||||
return cbm_type_builtin(arena, yyjson_get_str(prim));
|
||||
}
|
||||
yyjson_val* resolved = yyjson_obj_get(t, "resolved_path");
|
||||
if (resolved && yyjson_is_obj(resolved)) {
|
||||
const char* qn = NULL;
|
||||
yyjson_val* name = yyjson_obj_get(resolved, "name");
|
||||
yyjson_val* args = yyjson_obj_get(resolved, "args");
|
||||
if (name && yyjson_is_str(name)) {
|
||||
qn = cbm_arena_strdup(arena, yyjson_get_str(name));
|
||||
}
|
||||
if (!qn) return cbm_type_unknown();
|
||||
/* If `args.angle_bracketed.args` is non-empty, build TEMPLATE. */
|
||||
if (args && yyjson_is_obj(args)) {
|
||||
yyjson_val* angle = yyjson_obj_get(args, "angle_bracketed");
|
||||
if (angle && yyjson_is_obj(angle)) {
|
||||
yyjson_val* arr = yyjson_obj_get(angle, "args");
|
||||
if (arr && yyjson_is_arr(arr) && yyjson_arr_size(arr) > 0) {
|
||||
const CBMType* targs[8];
|
||||
int ti = 0;
|
||||
yyjson_val* item;
|
||||
yyjson_arr_iter it;
|
||||
yyjson_arr_iter_init(arr, &it);
|
||||
while ((item = yyjson_arr_iter_next(&it)) && ti < 8) {
|
||||
yyjson_val* inner_t = yyjson_obj_get(item, "type");
|
||||
if (inner_t) {
|
||||
targs[ti++] = parse_rustdoc_type(inner_t, arena, crate_qn);
|
||||
}
|
||||
}
|
||||
if (ti > 0) {
|
||||
return cbm_type_template(arena, qn, targs, ti);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return cbm_type_named(arena, qn);
|
||||
}
|
||||
yyjson_val* borrowed = yyjson_obj_get(t, "borrowed_ref");
|
||||
if (borrowed && yyjson_is_obj(borrowed)) {
|
||||
yyjson_val* inner_t = yyjson_obj_get(borrowed, "type");
|
||||
return cbm_type_reference(arena,
|
||||
parse_rustdoc_type(inner_t, arena, crate_qn));
|
||||
}
|
||||
yyjson_val* slice = yyjson_obj_get(t, "slice");
|
||||
if (slice) {
|
||||
return cbm_type_slice(arena,
|
||||
parse_rustdoc_type(slice, arena, crate_qn));
|
||||
}
|
||||
yyjson_val* array = yyjson_obj_get(t, "array");
|
||||
if (array && yyjson_is_obj(array)) {
|
||||
yyjson_val* elem_t = yyjson_obj_get(array, "type");
|
||||
return cbm_type_slice(arena,
|
||||
parse_rustdoc_type(elem_t, arena, crate_qn));
|
||||
}
|
||||
yyjson_val* tuple = yyjson_obj_get(t, "tuple");
|
||||
if (tuple && yyjson_is_arr(tuple)) {
|
||||
size_t n = yyjson_arr_size(tuple);
|
||||
if (n == 0) return cbm_type_builtin(arena, "()");
|
||||
const CBMType* elems[8];
|
||||
int ei = 0;
|
||||
yyjson_val* item;
|
||||
yyjson_arr_iter it;
|
||||
yyjson_arr_iter_init(tuple, &it);
|
||||
while ((item = yyjson_arr_iter_next(&it)) && ei < 8) {
|
||||
elems[ei++] = parse_rustdoc_type(item, arena, crate_qn);
|
||||
}
|
||||
if (ei == 1) return elems[0];
|
||||
return cbm_type_tuple(arena, elems, ei);
|
||||
}
|
||||
return cbm_type_unknown();
|
||||
}
|
||||
|
||||
/* Register a function item: figure out the receiver QN (if any), the
|
||||
* short name, and the return type. */
|
||||
static bool register_function(CBMTypeRegistry* reg, CBMArena* arena,
|
||||
const char* short_name, const char* full_qn, const char* receiver,
|
||||
yyjson_val* function_obj, const char* crate_qn) {
|
||||
if (!short_name || !full_qn) return false;
|
||||
CBMRegisteredFunc rf;
|
||||
memset(&rf, 0, sizeof(rf));
|
||||
rf.qualified_name = full_qn;
|
||||
rf.short_name = short_name;
|
||||
rf.receiver_type = receiver;
|
||||
rf.min_params = -1;
|
||||
|
||||
/* Return type — rustdoc's `function.decl.output` may be omitted
|
||||
* (implicit unit). */
|
||||
const CBMType* ret = cbm_type_builtin(arena, "()");
|
||||
if (function_obj && yyjson_is_obj(function_obj)) {
|
||||
yyjson_val* decl = yyjson_obj_get(function_obj, "decl");
|
||||
if (decl && yyjson_is_obj(decl)) {
|
||||
yyjson_val* out = yyjson_obj_get(decl, "output");
|
||||
if (out && !yyjson_is_null(out)) {
|
||||
ret = parse_rustdoc_type(out, arena, crate_qn);
|
||||
}
|
||||
}
|
||||
}
|
||||
const CBMType** ra = (const CBMType**)cbm_arena_alloc(arena,
|
||||
2 * sizeof(const CBMType*));
|
||||
ra[0] = ret;
|
||||
ra[1] = NULL;
|
||||
rf.signature = cbm_type_func(arena, NULL, NULL, ra);
|
||||
cbm_registry_add_func(reg, rf);
|
||||
return true;
|
||||
}
|
||||
|
||||
int cbm_rust_rustdoc_ingest(CBMTypeRegistry* reg, CBMArena* arena,
|
||||
const char* json, int json_len, const char* crate_qn) {
|
||||
if (!reg || !arena || !json) return 0;
|
||||
yyjson_doc* doc = yyjson_read(json, (size_t)(json_len > 0 ? json_len : strlen(json)), 0);
|
||||
if (!doc) return 0;
|
||||
yyjson_val* root = yyjson_doc_get_root(doc);
|
||||
if (!yyjson_is_obj(root)) { yyjson_doc_free(doc); return 0; }
|
||||
|
||||
yyjson_val* index = yyjson_obj_get(root, "index");
|
||||
yyjson_val* paths = yyjson_obj_get(root, "paths");
|
||||
if (!index || !paths) { yyjson_doc_free(doc); return 0; }
|
||||
|
||||
int count = 0;
|
||||
yyjson_obj_iter it;
|
||||
yyjson_obj_iter_init(index, &it);
|
||||
yyjson_val* key;
|
||||
while ((key = yyjson_obj_iter_next(&it))) {
|
||||
const char* item_id = yyjson_get_str(key);
|
||||
if (!item_id) continue;
|
||||
yyjson_val* item = yyjson_obj_iter_get_val(key);
|
||||
if (!item || !yyjson_is_obj(item)) continue;
|
||||
|
||||
yyjson_val* name = yyjson_obj_get(item, "name");
|
||||
const char* short_name = yyjson_get_str(name);
|
||||
if (!short_name) continue;
|
||||
|
||||
yyjson_val* inner = yyjson_obj_get(item, "inner");
|
||||
if (!inner || !yyjson_is_obj(inner)) continue;
|
||||
|
||||
/* Look up the fully-qualified path. */
|
||||
yyjson_val* path_entry = yyjson_obj_get(paths, item_id);
|
||||
const char* qn = NULL;
|
||||
const char* kind = NULL;
|
||||
if (path_entry && yyjson_is_obj(path_entry)) {
|
||||
qn = join_path(arena, yyjson_obj_get(path_entry, "path"), crate_qn);
|
||||
yyjson_val* k = yyjson_obj_get(path_entry, "kind");
|
||||
kind = yyjson_get_str(k);
|
||||
}
|
||||
if (!qn) {
|
||||
/* Methods/impl-items don't have entries in `paths`; we still
|
||||
* register them by name + receiver derived from the parent. */
|
||||
}
|
||||
|
||||
/* Dispatch on the rustdoc kind. */
|
||||
if (kind && (strcmp(kind, "struct") == 0 ||
|
||||
strcmp(kind, "enum") == 0 ||
|
||||
strcmp(kind, "union") == 0)) {
|
||||
CBMRegisteredType rt;
|
||||
memset(&rt, 0, sizeof(rt));
|
||||
rt.qualified_name = qn;
|
||||
rt.short_name = cbm_arena_strdup(arena, short_name);
|
||||
cbm_registry_add_type(reg, rt);
|
||||
count++;
|
||||
} else if (kind && strcmp(kind, "trait") == 0) {
|
||||
CBMRegisteredType rt;
|
||||
memset(&rt, 0, sizeof(rt));
|
||||
rt.qualified_name = qn;
|
||||
rt.short_name = cbm_arena_strdup(arena, short_name);
|
||||
rt.is_interface = true;
|
||||
cbm_registry_add_type(reg, rt);
|
||||
count++;
|
||||
} else if (kind && strcmp(kind, "function") == 0) {
|
||||
yyjson_val* fn = yyjson_obj_get(inner, "function");
|
||||
if (register_function(reg, arena, cbm_arena_strdup(arena, short_name),
|
||||
qn, NULL, fn, crate_qn)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
/* Impl items / methods don't have path entries; they're
|
||||
* referenced from struct/trait items' `impls` list. A complete
|
||||
* walker would chase those — for now we keep ingestion bounded
|
||||
* to the top-level paths. */
|
||||
}
|
||||
yyjson_doc_free(doc);
|
||||
return count;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/* rust_rustdoc.h — Ingest `cargo +nightly rustdoc --output-format json`
|
||||
* output into the Rust LSP type registry.
|
||||
*
|
||||
* Per the FOLLOWUP-extension Option A: when a project opts in by
|
||||
* running rustdoc once, we can ingest the resulting JSON and register
|
||||
* every item (struct, trait, fn, method) with accurate signatures.
|
||||
* That eliminates the "unseeded external crate" gap because rustdoc
|
||||
* already did the macro expansion and type resolution.
|
||||
*
|
||||
* The schema is rustdoc's "Crate" type. We handle:
|
||||
* - paths { id → { path: [..], kind: "struct"|"trait"|... } }
|
||||
* - index { id → Item { name, inner: { Function|Struct|... }, … } }
|
||||
*
|
||||
* We ignore everything we don't understand. The JSON format is unstable
|
||||
* across rustdoc versions; we tolerate missing fields gracefully and
|
||||
* just skip items we can't make sense of.
|
||||
*
|
||||
* Schema reference: https://github.com/rust-lang/rust/blob/master/src/rustdoc-json-types/lib.rs
|
||||
*/
|
||||
|
||||
#ifndef CBM_LSP_RUST_RUSTDOC_H
|
||||
#define CBM_LSP_RUST_RUSTDOC_H
|
||||
|
||||
#include "../arena.h"
|
||||
#include "type_registry.h"
|
||||
|
||||
/* Ingest a rustdoc JSON document into the registry. `crate_qn` is the
|
||||
* QN prefix to use for items (e.g. "serde" for crate `serde`). Returns
|
||||
* the number of items registered. */
|
||||
int cbm_rust_rustdoc_ingest(CBMTypeRegistry* reg, CBMArena* arena,
|
||||
const char* json, int json_len, const char* crate_qn);
|
||||
|
||||
#endif /* CBM_LSP_RUST_RUSTDOC_H */
|
||||
@@ -0,0 +1,74 @@
|
||||
#include "scope.h"
|
||||
#include <string.h>
|
||||
|
||||
CBMScope* cbm_scope_push(CBMArena* a, CBMScope* current) {
|
||||
CBMScope* scope = (CBMScope*)cbm_arena_alloc(a, sizeof(CBMScope));
|
||||
if (!scope) {
|
||||
return current;
|
||||
}
|
||||
memset(scope, 0, sizeof(CBMScope));
|
||||
scope->parent = current;
|
||||
scope->arena = a;
|
||||
return scope;
|
||||
}
|
||||
|
||||
CBMScope* cbm_scope_pop(CBMScope* scope) {
|
||||
if (!scope) {
|
||||
return NULL;
|
||||
}
|
||||
return scope->parent;
|
||||
}
|
||||
|
||||
static CBMScopeChunk* alloc_chunk(CBMScope* scope) {
|
||||
if (!scope->arena) {
|
||||
return NULL;
|
||||
}
|
||||
CBMScopeChunk* c = (CBMScopeChunk*)cbm_arena_alloc(scope->arena, sizeof(CBMScopeChunk));
|
||||
if (!c) {
|
||||
return NULL;
|
||||
}
|
||||
memset(c, 0, sizeof(CBMScopeChunk));
|
||||
c->next = scope->chunks;
|
||||
scope->chunks = c;
|
||||
return c;
|
||||
}
|
||||
|
||||
void cbm_scope_bind(CBMScope* scope, const char* name, const CBMType* type) {
|
||||
if (!scope || !name) {
|
||||
return;
|
||||
}
|
||||
for (CBMScopeChunk* c = scope->chunks; c != NULL; c = c->next) {
|
||||
for (int i = 0; i < c->used; i++) {
|
||||
if (c->bindings[i].name && strcmp(c->bindings[i].name, name) == 0) {
|
||||
c->bindings[i].type = type;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
CBMScopeChunk* head = scope->chunks;
|
||||
if (!head || head->used >= CBM_SCOPE_CHUNK_BINDINGS) {
|
||||
head = alloc_chunk(scope);
|
||||
if (!head) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
head->bindings[head->used].name = name;
|
||||
head->bindings[head->used].type = type;
|
||||
head->used++;
|
||||
}
|
||||
|
||||
const CBMType* cbm_scope_lookup(const CBMScope* scope, const char* name) {
|
||||
if (!name) {
|
||||
return cbm_type_unknown();
|
||||
}
|
||||
for (const CBMScope* s = scope; s != NULL; s = s->parent) {
|
||||
for (CBMScopeChunk* c = s->chunks; c != NULL; c = c->next) {
|
||||
for (int i = 0; i < c->used; i++) {
|
||||
if (c->bindings[i].name && strcmp(c->bindings[i].name, name) == 0) {
|
||||
return c->bindings[i].type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return cbm_type_unknown();
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
#ifndef CBM_LSP_SCOPE_H
|
||||
#define CBM_LSP_SCOPE_H
|
||||
|
||||
#include "type_rep.h"
|
||||
#include "../arena.h"
|
||||
#include <stdlib.h> /* getenv, atoi (cbm_lsp_max_walk_depth) */
|
||||
|
||||
typedef struct {
|
||||
const char* name;
|
||||
const CBMType* type;
|
||||
} CBMVarBinding;
|
||||
|
||||
#define CBM_SCOPE_CHUNK_BINDINGS 16
|
||||
|
||||
typedef struct CBMScopeChunk {
|
||||
CBMVarBinding bindings[CBM_SCOPE_CHUNK_BINDINGS];
|
||||
int used;
|
||||
struct CBMScopeChunk* next;
|
||||
} CBMScopeChunk;
|
||||
|
||||
typedef struct CBMScope {
|
||||
struct CBMScope* parent;
|
||||
CBMScopeChunk* chunks;
|
||||
CBMArena* arena; // owning arena, propagated to children at push time
|
||||
} CBMScope;
|
||||
|
||||
// Bail-to-UNKNOWN depth for type-lookup chains: alias resolution, MRO walks,
|
||||
// embedded-field/struct-traversal. Exceeding this collapses to cbm_type_unknown
|
||||
// rather than recursing — guards against pathological hierarchies.
|
||||
#define CBM_LSP_MAX_LOOKUP_DEPTH 16
|
||||
|
||||
// Recursion cap for the per-language "resolve calls in AST node" walkers. These
|
||||
// recurse once per AST nesting level; a deeply-nested or cyclic file can drive
|
||||
// them into a native stack overflow (SIGSEGV) that takes down the whole index.
|
||||
// Past this cap the wrapper skips the subtree — those calls stay unresolved,
|
||||
// which is graceful degradation, not a crash. 512 is far deeper than any
|
||||
// hand-written source nests; override for pathological/generated repos via the
|
||||
// CBM_LSP_MAX_WALK_DEPTH env var (positive integer).
|
||||
#define CBM_LSP_MAX_WALK_DEPTH 512
|
||||
|
||||
// Resolved walk-depth cap: env override (CBM_LSP_MAX_WALK_DEPTH, if a positive
|
||||
// integer) else CBM_LSP_MAX_WALK_DEPTH. Read once and cached — the walkers call
|
||||
// this per node, so it must not hit getenv on the hot path. The cache is a
|
||||
// benign idempotent race under multi-threaded indexing (every thread computes
|
||||
// the same value).
|
||||
static inline int cbm_lsp_max_walk_depth(void) {
|
||||
static int cached = -1;
|
||||
if (cached < 0) {
|
||||
const char* e = getenv("CBM_LSP_MAX_WALK_DEPTH");
|
||||
int v = (e && *e) ? atoi(e) : 0;
|
||||
cached = (v > 0) ? v : CBM_LSP_MAX_WALK_DEPTH;
|
||||
}
|
||||
return cached;
|
||||
}
|
||||
|
||||
CBMScope* cbm_scope_push(CBMArena* a, CBMScope* current);
|
||||
CBMScope* cbm_scope_pop(CBMScope* scope);
|
||||
void cbm_scope_bind(CBMScope* scope, const char* name, const CBMType* type);
|
||||
const CBMType* cbm_scope_lookup(const CBMScope* scope, const char* name);
|
||||
|
||||
#endif // CBM_LSP_SCOPE_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,155 @@
|
||||
#ifndef CBM_LSP_TS_LSP_H
|
||||
#define CBM_LSP_TS_LSP_H
|
||||
|
||||
#include "type_rep.h"
|
||||
#include "scope.h"
|
||||
#include "type_registry.h"
|
||||
#include "../cbm.h"
|
||||
#include "go_lsp.h" // for CBMLSPDef, CBMResolvedCallArray (shared cross-language)
|
||||
|
||||
// TSLSPContext holds state for TypeScript / JavaScript / JSX / TSX expression type
|
||||
// evaluation within a single file. One context per cbm_run_ts_lsp invocation.
|
||||
//
|
||||
// Mode flags choose dialect behaviour:
|
||||
// - js_mode: .js / .jsx — JSDoc inference is the primary type source; missing annotations
|
||||
// default to UNKNOWN rather than failing.
|
||||
// - jsx_mode: .jsx / .tsx — JSX expressions are recognised; intrinsic elements map to
|
||||
// builtin types; component calls register through the registry.
|
||||
// - dts_mode: .d.ts ambient declarations — no function bodies; populate registry only,
|
||||
// emit no resolved calls.
|
||||
//
|
||||
// Modes are independent: a `.tsx` file sets jsx_mode=true with js_mode=false; a `.jsx` file
|
||||
// sets both true.
|
||||
typedef struct {
|
||||
CBMArena *arena;
|
||||
const char *source;
|
||||
int source_len;
|
||||
const CBMTypeRegistry *registry;
|
||||
CBMScope *current_scope;
|
||||
|
||||
// Import map: local_name -> module QN (resolved or opaque).
|
||||
// Parallel arrays of length import_count.
|
||||
const char **import_local_names;
|
||||
const char **import_module_qns;
|
||||
int import_count;
|
||||
|
||||
// File / surrounding context.
|
||||
const char *module_qn; // QN of this file's module
|
||||
const char *enclosing_func_qn; // current function being walked, NULL at module scope
|
||||
const char *enclosing_class_qn; // current class for `this` resolution
|
||||
|
||||
// Output: resolved calls accumulate here.
|
||||
CBMResolvedCallArray *resolved_calls;
|
||||
|
||||
// Type-parameter scope (innermost generic function/class).
|
||||
// type_param_constraints may be NULL or shorter — entries default to "any".
|
||||
const char **type_param_names;
|
||||
const CBMType **type_param_constraints;
|
||||
int type_param_count;
|
||||
|
||||
// Mode flags — see comment above.
|
||||
bool js_mode;
|
||||
bool jsx_mode;
|
||||
bool dts_mode;
|
||||
bool strict; // tsconfig "strict": true → fewer implicit-any fallbacks
|
||||
bool debug; // CBM_LSP_DEBUG env
|
||||
|
||||
// Recursion guard for ts_eval_expr_type (mirrors c_lsp).
|
||||
int eval_depth;
|
||||
// Recursion guard for lookup_member_type: cyclic type graphs (mutually
|
||||
// recursive unions/wrappers across registered types) otherwise recurse
|
||||
// without bound — stack overflow on real repos.
|
||||
int member_depth;
|
||||
} TSLSPContext;
|
||||
|
||||
// --- Initialization ---
|
||||
|
||||
// Initialise a TSLSPContext for processing one file. Mode flags select dialect.
|
||||
void ts_lsp_init(TSLSPContext *ctx, CBMArena *arena, const char *source, int source_len,
|
||||
const CBMTypeRegistry *registry, const char *module_qn, bool js_mode,
|
||||
bool jsx_mode, bool dts_mode, CBMResolvedCallArray *out);
|
||||
|
||||
// Register an import: local binding name → module QN (or unresolved module specifier).
|
||||
void ts_lsp_add_import(TSLSPContext *ctx, const char *local_name, const char *module_qn);
|
||||
|
||||
// Walk the entire file: bind module-level declarations, then process every function /
|
||||
// method body. Safe on any tree-sitter root; emits zero calls in dts_mode.
|
||||
void ts_lsp_process_file(TSLSPContext *ctx, TSNode root);
|
||||
|
||||
// --- Internals exposed for tests and stdlib data ---
|
||||
|
||||
// Evaluate the type of a TS/JS expression node. Returns cbm_type_unknown() on miss.
|
||||
const CBMType *ts_eval_expr_type(TSLSPContext *ctx, TSNode node);
|
||||
|
||||
// Parse a TS/JS type-position AST node into a CBMType.
|
||||
const CBMType *ts_parse_type_node(TSLSPContext *ctx, TSNode node);
|
||||
|
||||
// Process a single statement node, binding any variables it declares into ctx->current_scope.
|
||||
void ts_process_statement(TSLSPContext *ctx, TSNode node);
|
||||
|
||||
// --- Entry points ---
|
||||
|
||||
// Single-file LSP. Builds a registry from result->defs + the TS stdlib subset, runs
|
||||
// resolution, and writes resolved calls into result->resolved_calls.
|
||||
void cbm_run_ts_lsp(CBMArena *arena, CBMFileResult *result, const char *source, int source_len,
|
||||
TSNode root, bool js_mode, bool jsx_mode, bool dts_mode);
|
||||
|
||||
// Cross-file LSP. Caller passes in cross-file definitions (CBMLSPDef list) and the
|
||||
// resolved import → module-QN map. Re-parses source if cached_tree is NULL.
|
||||
void cbm_run_ts_lsp_cross(CBMArena *arena, const char *source, int source_len,
|
||||
const char *module_qn, bool js_mode, bool jsx_mode, bool dts_mode,
|
||||
CBMLSPDef *defs, int def_count, const char **import_names,
|
||||
const char **import_qns, int import_count, TSTree *cached_tree,
|
||||
CBMResolvedCallArray *out);
|
||||
|
||||
// Tier 2: build a project-wide TS/JS/TSX registry ONCE from all defs
|
||||
// (filters by lang). Shared READ-ONLY base; per-file overlays chain to
|
||||
// it via the registry fallback pointer.
|
||||
CBMTypeRegistry *cbm_ts_build_cross_registry(CBMArena *arena, CBMLSPDef *defs, int def_count);
|
||||
|
||||
// Tier 2 per-file resolve. Builds a small per-file overlay (the file's
|
||||
// own-module defs, AST-refined) that chains to the shared base `reg`.
|
||||
// `defs`/`def_count` are the file's relevant defs (own + imports); only
|
||||
// own-module ones are registered into the overlay.
|
||||
void cbm_run_ts_lsp_cross_with_registry(CBMArena *arena, const char *source, int source_len,
|
||||
const char *module_qn, bool js_mode, bool jsx_mode,
|
||||
bool dts_mode, CBMTypeRegistry *reg, CBMLSPDef *defs,
|
||||
int def_count, const char **import_names,
|
||||
const char **import_qns, int import_count,
|
||||
TSTree *cached_tree, CBMResolvedCallArray *out);
|
||||
|
||||
// Register the TypeScript / JavaScript stdlib subset (Promise, Array<T>, Map<K,V>, Set<T>,
|
||||
// Object, Function, console, JSON) into a registry. v1 is hand-curated; a generator script
|
||||
// will replace this in v1.3.
|
||||
void cbm_ts_stdlib_register(CBMTypeRegistry *reg, CBMArena *arena);
|
||||
|
||||
// TEST HOOKS: count of full per-file cross-registry builds (the quadratic the
|
||||
// shared-registry dispatch eliminates; must stay 0 on the shared path).
|
||||
long cbm_ts_full_registry_builds(void);
|
||||
void cbm_ts_full_registry_builds_reset(void);
|
||||
|
||||
// --- Batch cross-file LSP ---
|
||||
|
||||
// Per-file input for batch TS LSP processing.
|
||||
typedef struct {
|
||||
const char *source;
|
||||
int source_len;
|
||||
const char *module_qn;
|
||||
bool js_mode;
|
||||
bool jsx_mode;
|
||||
bool dts_mode;
|
||||
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;
|
||||
int import_count;
|
||||
} CBMBatchTSLSPFile;
|
||||
|
||||
// Process multiple TS/JS files' cross-file LSP in one CGo call.
|
||||
// out must point to file_count pre-zeroed CBMResolvedCallArray structs.
|
||||
// Project-scope declaration merging happens here (per plan §17 finding #4).
|
||||
void cbm_batch_ts_lsp_cross(CBMArena *arena, CBMBatchTSLSPFile *files, int file_count,
|
||||
CBMResolvedCallArray *out);
|
||||
|
||||
#endif // CBM_LSP_TS_LSP_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,259 @@
|
||||
#ifndef CBM_LSP_TYPE_REGISTRY_H
|
||||
#define CBM_LSP_TYPE_REGISTRY_H
|
||||
|
||||
#include "type_rep.h"
|
||||
#include "../arena.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
// Decorator-derived flags (Python). Added at struct tail so existing
|
||||
// callers that memset to zero before populating other fields keep working.
|
||||
typedef enum {
|
||||
CBM_FUNC_FLAG_NONE = 0,
|
||||
CBM_FUNC_FLAG_PROPERTY = 1 << 0, // @property -> obj.attr returns getter return
|
||||
CBM_FUNC_FLAG_CLASSMETHOD = 1 << 1, // @classmethod -> first arg is cls (the class)
|
||||
CBM_FUNC_FLAG_STATICMETHOD = 1 << 2, // @staticmethod -> no implicit self/cls
|
||||
CBM_FUNC_FLAG_ABSTRACTMETHOD = 1 << 3, // @abstractmethod -> still callable for resolution
|
||||
CBM_FUNC_FLAG_OVERLOAD = 1 << 4, // @overload entry — non-implementation stub
|
||||
CBM_FUNC_FLAG_ASYNC = 1 << 5, // async def — return is Coroutine[..., T]
|
||||
CBM_FUNC_FLAG_GENERATOR = 1 << 6, // contains yield — return is Generator[T, ...]
|
||||
CBM_FUNC_FLAG_FINAL = 1 << 7, // @final — overrides not allowed
|
||||
} CBMFuncFlags;
|
||||
|
||||
// Registered function/method with full type signature.
|
||||
typedef struct {
|
||||
const char *qualified_name; // e.g., "proj.pkg.TypeName.MethodName"
|
||||
const char *receiver_type; // e.g., "proj.pkg.TypeName" (NULL for functions)
|
||||
const char *short_name; // e.g., "MethodName"
|
||||
const CBMType *signature; // FUNC type with param/return types
|
||||
const char **type_param_names; // NULL-terminated, e.g., ["T", "R", NULL] for generics
|
||||
int min_params; // Minimum required params (excluding defaulted). -1 = unknown.
|
||||
int flags; // CBM_FUNC_FLAG_* bitfield (Python decorator info; 0 elsewhere)
|
||||
const char **decorator_qns; // NULL-terminated decorator QNs (Python only); used for
|
||||
// user-decorator return-type substitution.
|
||||
} CBMRegisteredFunc;
|
||||
|
||||
// Registered type with fields and method names.
|
||||
typedef struct {
|
||||
const char *qualified_name; // e.g., "proj.pkg.TypeName"
|
||||
const char *short_name; // e.g., "TypeName"
|
||||
const char **field_names; // NULL-terminated
|
||||
const CBMType **field_types; // NULL-terminated (parallel to field_names)
|
||||
const char **method_names; // NULL-terminated (short names)
|
||||
const char **method_qns; // NULL-terminated (qualified names, parallel)
|
||||
const char **embedded_types; // NULL-terminated (embedded/anonymous field type QNs)
|
||||
const char *alias_of; // QN of aliased type (type Foo = Bar), NULL if not alias
|
||||
const char **type_param_names; // NULL-terminated, e.g., ["T", "K", NULL] for template classes
|
||||
bool is_interface;
|
||||
bool is_object; // Kotlin `object`/`companion object` singleton (member calls are static)
|
||||
|
||||
// --- TS-specific fields (NULL/empty for non-TS types — backward compatible) ---
|
||||
// TS interfaces / object types may be callable: `interface F { (x:number): string }`.
|
||||
const CBMType *call_signature; // FUNC type or NULL
|
||||
// TS objects can have an index signature: `{ [key:string]: V }` or `{ [i:number]: V }`.
|
||||
const CBMType *index_key_type; // BUILTIN("string"|"number") or NULL
|
||||
const CBMType *index_value_type; // V or NULL
|
||||
// Generic constraints, parallel to type_param_names. NULL or shorter array means "any".
|
||||
const CBMType **type_param_constraints; // NULL-terminated, parallel to type_param_names
|
||||
} CBMRegisteredType;
|
||||
|
||||
// Hash-table bucket entry. Chains collisions via next-index list for overload sets.
|
||||
typedef struct {
|
||||
uint64_t hash; // FNV-1a of key
|
||||
int payload_index; // index into reg->funcs[] or reg->types[]
|
||||
int next_index; // -1 = end of chain; else index of next bucket entry in same chain
|
||||
int slot; // bucket slot this entry sits in (for resize)
|
||||
} CBMRegistryHashEntry;
|
||||
|
||||
// Cross-file type/function registry.
|
||||
typedef struct CBMTypeRegistry {
|
||||
CBMRegisteredFunc *funcs;
|
||||
int func_count;
|
||||
int func_cap;
|
||||
|
||||
CBMRegisteredType *types;
|
||||
int type_count;
|
||||
int type_cap;
|
||||
|
||||
CBMArena *arena; // owns all string data
|
||||
|
||||
/* Optional fallback registry (Tier 2 two-level lookup). When a
|
||||
* lookup misses in this registry, it chains to `fallback`. Used by
|
||||
* TS/PHP cross-LSP: a small per-file registry (the file's own
|
||||
* AST-refined types) chains to a shared, immutable base registry
|
||||
* (stdlib + all project defs) built once. NULL = no chaining. */
|
||||
const struct CBMTypeRegistry *fallback;
|
||||
|
||||
// Hash indexes (built lazily by cbm_registry_finalize, NULL until then).
|
||||
// Lookups fall back to linear scan when these are NULL.
|
||||
int *func_qn_buckets; // bucket → first entry index in func_qn_entries; -1 = empty
|
||||
CBMRegistryHashEntry *func_qn_entries; // entries indexed by linear order
|
||||
int func_qn_bucket_count;
|
||||
int func_qn_entry_count;
|
||||
|
||||
int *type_qn_buckets;
|
||||
CBMRegistryHashEntry *type_qn_entries;
|
||||
int type_qn_bucket_count;
|
||||
int type_qn_entry_count;
|
||||
|
||||
// Methods indexed by (receiver_type, short_name) — chain holds overloads.
|
||||
int *method_buckets;
|
||||
CBMRegistryHashEntry *method_entries;
|
||||
int method_bucket_count;
|
||||
int method_entry_count;
|
||||
|
||||
// Auxiliary short-name / embedded-type indexes (built by finalize alongside the
|
||||
// QN buckets). Turn the Rust trait- and free-function fallback scans from
|
||||
// O(type_count)/O(func_count) into O(chain). Read-only after finalize.
|
||||
// Embedded-type index: fnv1a(bare last-'.'-segment of each embedded_type) -> chain
|
||||
// of TYPE indices declaring it. payload_index = type index (a type may appear once
|
||||
// per embedded entry; consumers dedup adjacent same-type via the iterator).
|
||||
int *type_embed_buckets;
|
||||
CBMRegistryHashEntry *type_embed_entries;
|
||||
int type_embed_bucket_count;
|
||||
int type_embed_entry_count;
|
||||
// Free-function short-name index: fnv1a(short_name) -> chain of FREE-function
|
||||
// (receiver_type==NULL) indices. payload_index = func index.
|
||||
int *ffunc_short_buckets;
|
||||
CBMRegistryHashEntry *ffunc_short_entries;
|
||||
int ffunc_short_bucket_count;
|
||||
int ffunc_short_entry_count;
|
||||
|
||||
/* Sealed / read-only. Set true by the cbm_X_build_cross_registry builders
|
||||
* (c/cpp, python, c#, ts, go) right after finalize: a Tier-2 cross-registry
|
||||
* is built ONCE and shared READ-ONLY across the parallel resolve workers.
|
||||
* cbm_registry_add_func/_type no-op on a sealed registry, so a per-file
|
||||
* resolver can never mutate the shared, finalized registry. Without this,
|
||||
* post-finalize adds accumulate in a tail the hash index does not cover ->
|
||||
* every lookup linear-scans it -> O(files*defs) (the Linux-kernel full-index
|
||||
* hang) plus a heap data race across workers. */
|
||||
bool read_only;
|
||||
} CBMTypeRegistry;
|
||||
|
||||
// Initialize a registry.
|
||||
void cbm_registry_init(CBMTypeRegistry *reg, CBMArena *arena);
|
||||
|
||||
// Build the hash indexes after all funcs/types have been added. Subsequent lookups
|
||||
// use O(1) hashed dispatch instead of linear scans. Calling this is OPTIONAL — the
|
||||
// linear-scan path remains correct. Single-file resolvers (small registries) skip
|
||||
// finalize and stay linear; project-wide registries (many thousands of entries) call
|
||||
// it once after pass-1.5 def-collection.
|
||||
void cbm_registry_finalize(CBMTypeRegistry *reg);
|
||||
|
||||
// Like cbm_registry_finalize, but the hash-index allocations (buckets/entries)
|
||||
// come from idx_arena instead of reg->arena. Per-file cross resolvers MUST use
|
||||
// this with a scratch arena destroyed after the walk: their reg->arena is the
|
||||
// pipeline-lifetime result arena, and per-file index allocations accumulated
|
||||
// there add GBs across a large repo (FastAPI incremental test: +1.1 GB RSS).
|
||||
void cbm_registry_finalize_into(CBMTypeRegistry *reg, CBMArena *idx_arena);
|
||||
|
||||
// Register a function/method.
|
||||
void cbm_registry_add_func(CBMTypeRegistry *reg, CBMRegisteredFunc func);
|
||||
|
||||
// Register a type.
|
||||
void cbm_registry_add_type(CBMTypeRegistry *reg, CBMRegisteredType type);
|
||||
|
||||
// Look up a method by receiver type QN + method name.
|
||||
const CBMRegisteredFunc *cbm_registry_lookup_method(const CBMTypeRegistry *reg,
|
||||
const char *receiver_qn,
|
||||
const char *method_name);
|
||||
|
||||
// Look up a type by qualified name.
|
||||
const CBMRegisteredType *cbm_registry_lookup_type(const CBMTypeRegistry *reg,
|
||||
const char *qualified_name);
|
||||
|
||||
// Look up a function by qualified name.
|
||||
const CBMRegisteredFunc *cbm_registry_lookup_func(const CBMTypeRegistry *reg,
|
||||
const char *qualified_name);
|
||||
|
||||
// Look up a symbol (type or function) in a package by short name.
|
||||
// package_qn is the package prefix (e.g., "proj.pkg").
|
||||
const CBMRegisteredFunc *cbm_registry_lookup_symbol(const CBMTypeRegistry *reg,
|
||||
const char *package_qn, const char *name);
|
||||
|
||||
// Resolve type alias chain: follow alias_of until concrete type found (max 16 levels).
|
||||
const CBMRegisteredType *cbm_registry_resolve_alias(const CBMTypeRegistry *reg,
|
||||
const char *type_qn);
|
||||
|
||||
// Look up a method by receiver type QN + method name, following alias chains.
|
||||
const CBMRegisteredFunc *cbm_registry_lookup_method_aliased(const CBMTypeRegistry *reg,
|
||||
const char *receiver_qn,
|
||||
const char *method_name);
|
||||
|
||||
// Look up a method by receiver type + name, preferring the overload with matching arg count.
|
||||
// Falls back to any match if no exact arg count match found.
|
||||
const CBMRegisteredFunc *cbm_registry_lookup_method_by_args(const CBMTypeRegistry *reg,
|
||||
const char *receiver_qn,
|
||||
const char *method_name, int arg_count);
|
||||
|
||||
// Look up a free function by package + name, preferring matching arg count.
|
||||
const CBMRegisteredFunc *cbm_registry_lookup_symbol_by_args(const CBMTypeRegistry *reg,
|
||||
const char *package_qn,
|
||||
const char *name, int arg_count);
|
||||
|
||||
// Look up a method by receiver type + name, scoring overloads by parameter type match.
|
||||
// arg_types may contain NULL entries for unknown types. Falls back to arg-count matching.
|
||||
const CBMRegisteredFunc *cbm_registry_lookup_method_by_types(const CBMTypeRegistry *reg,
|
||||
const char *receiver_qn,
|
||||
const char *method_name,
|
||||
const CBMType **arg_types,
|
||||
int arg_count);
|
||||
|
||||
// Look up a free function by package + name, scoring overloads by parameter type match.
|
||||
const CBMRegisteredFunc *cbm_registry_lookup_symbol_by_types(const CBMTypeRegistry *reg,
|
||||
const char *package_qn,
|
||||
const char *name,
|
||||
const CBMType **arg_types,
|
||||
int arg_count);
|
||||
|
||||
// --- Auxiliary index iterators (Rust trait / free-function fallback fast paths) ---
|
||||
//
|
||||
// Iterate registry TYPE indices whose embedded_types contain an entry whose BARE
|
||||
// name (last '.'-segment) equals `bare`. On a finalized registry this walks the
|
||||
// embedded-type index plus any post-finalize tail; on an unfinalized registry it
|
||||
// degrades to a full linear scan over all types (identical candidate set). Each
|
||||
// matching type index is yielded at most once, in ascending registry order. The
|
||||
// index is a bare-name PREFILTER — the caller MUST still apply its own exact
|
||||
// predicate on each yielded type. Read-only, allocation-free. Usage:
|
||||
// CBMTypeEmbedIter it; cbm_registry_types_by_embedded_bare(reg, bare, &it);
|
||||
// int ti; while ((ti = cbm_type_embed_iter_next(&it)) >= 0) { ... reg->types[ti] ... }
|
||||
typedef struct {
|
||||
const CBMTypeRegistry *reg;
|
||||
uint64_t hash;
|
||||
int chain_idx; // next entry in the embed chain, or -1
|
||||
int tail_i; // next tail/linear type index
|
||||
int tail_end; // reg->type_count snapshot
|
||||
int prev_type; // last yielded type index (adjacent-dedup); -1 = none
|
||||
} CBMTypeEmbedIter;
|
||||
void cbm_registry_types_by_embedded_bare(const CBMTypeRegistry *reg, const char *bare,
|
||||
CBMTypeEmbedIter *out);
|
||||
int cbm_type_embed_iter_next(CBMTypeEmbedIter *it);
|
||||
|
||||
// Iterate FREE-function (receiver_type==NULL) indices whose short_name equals
|
||||
// `short_name`. Same finalized/unfinalized behavior as above; caller re-checks its
|
||||
// own predicate. Read-only, allocation-free.
|
||||
typedef struct {
|
||||
const CBMTypeRegistry *reg;
|
||||
uint64_t hash;
|
||||
int chain_idx;
|
||||
int tail_i;
|
||||
int tail_end;
|
||||
} CBMFreeFuncIter;
|
||||
void cbm_registry_free_funcs_by_short_name(const CBMTypeRegistry *reg, const char *short_name,
|
||||
CBMFreeFuncIter *out);
|
||||
int cbm_free_func_iter_next(CBMFreeFuncIter *it);
|
||||
|
||||
// --- TS-specific helpers (return NULL for types without these signatures) ---
|
||||
|
||||
// If the type has a call signature (e.g., `interface F { (x:number): string }`), return
|
||||
// a synthesised CBMRegisteredFunc whose qualified_name is "<type_qn>.__call" and
|
||||
// short_name is "__call". Returns NULL if no call signature is present, the type is
|
||||
// missing, or the receiver type was not registered. Caller must NOT free.
|
||||
const CBMRegisteredFunc *cbm_registry_lookup_callable(const CBMTypeRegistry *reg, CBMArena *arena,
|
||||
const char *type_qn);
|
||||
|
||||
// If the type has an index signature, return the value type produced by indexing with
|
||||
// the given key type (string vs number). Returns NULL if no matching index signature.
|
||||
const CBMType *cbm_registry_lookup_index_signature(const CBMTypeRegistry *reg, const char *type_qn,
|
||||
const CBMType *key_type);
|
||||
|
||||
#endif // CBM_LSP_TYPE_REGISTRY_H
|
||||
@@ -0,0 +1,930 @@
|
||||
#include "type_rep.h"
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
/* No real allocation lives in the first page; values below this are garbage
|
||||
* (e.g. small integers or truncated string bytes misread as pointers). */
|
||||
enum { TR_MIN_PLAUSIBLE_PTR = 4096 };
|
||||
|
||||
// Singleton UNKNOWN type (no allocation needed).
|
||||
static const CBMType unknown_singleton = {.kind = CBM_TYPE_UNKNOWN};
|
||||
|
||||
const CBMType *cbm_type_unknown(void) {
|
||||
return &unknown_singleton;
|
||||
}
|
||||
|
||||
const CBMType *cbm_type_named(CBMArena *a, const char *qualified_name) {
|
||||
CBMType *t = (CBMType *)cbm_arena_alloc(a, sizeof(CBMType));
|
||||
if (!t)
|
||||
return &unknown_singleton;
|
||||
memset(t, 0, sizeof(CBMType));
|
||||
t->kind = CBM_TYPE_NAMED;
|
||||
t->data.named.qualified_name = cbm_arena_strdup(a, qualified_name);
|
||||
return t;
|
||||
}
|
||||
|
||||
const CBMType *cbm_type_pointer(CBMArena *a, const CBMType *elem) {
|
||||
CBMType *t = (CBMType *)cbm_arena_alloc(a, sizeof(CBMType));
|
||||
if (!t)
|
||||
return &unknown_singleton;
|
||||
memset(t, 0, sizeof(CBMType));
|
||||
t->kind = CBM_TYPE_POINTER;
|
||||
t->data.pointer.elem = elem;
|
||||
return t;
|
||||
}
|
||||
|
||||
const CBMType *cbm_type_slice(CBMArena *a, const CBMType *elem) {
|
||||
CBMType *t = (CBMType *)cbm_arena_alloc(a, sizeof(CBMType));
|
||||
if (!t)
|
||||
return &unknown_singleton;
|
||||
memset(t, 0, sizeof(CBMType));
|
||||
t->kind = CBM_TYPE_SLICE;
|
||||
t->data.slice.elem = elem;
|
||||
return t;
|
||||
}
|
||||
|
||||
const CBMType *cbm_type_map(CBMArena *a, const CBMType *key, const CBMType *value) {
|
||||
CBMType *t = (CBMType *)cbm_arena_alloc(a, sizeof(CBMType));
|
||||
if (!t)
|
||||
return &unknown_singleton;
|
||||
memset(t, 0, sizeof(CBMType));
|
||||
t->kind = CBM_TYPE_MAP;
|
||||
t->data.map.key = key;
|
||||
t->data.map.value = value;
|
||||
return t;
|
||||
}
|
||||
|
||||
const CBMType *cbm_type_channel(CBMArena *a, const CBMType *elem, int direction) {
|
||||
CBMType *t = (CBMType *)cbm_arena_alloc(a, sizeof(CBMType));
|
||||
if (!t)
|
||||
return &unknown_singleton;
|
||||
memset(t, 0, sizeof(CBMType));
|
||||
t->kind = CBM_TYPE_CHANNEL;
|
||||
t->data.channel.elem = elem;
|
||||
t->data.channel.direction = direction;
|
||||
return t;
|
||||
}
|
||||
|
||||
const CBMType *cbm_type_func(CBMArena *a, const char **param_names, const CBMType **param_types,
|
||||
const CBMType **return_types) {
|
||||
CBMType *t = (CBMType *)cbm_arena_alloc(a, sizeof(CBMType));
|
||||
if (!t)
|
||||
return &unknown_singleton;
|
||||
memset(t, 0, sizeof(CBMType));
|
||||
t->kind = CBM_TYPE_FUNC;
|
||||
|
||||
// Copy all arrays into arena memory to avoid dangling stack pointers.
|
||||
if (return_types) {
|
||||
int count = 0;
|
||||
while (return_types[count])
|
||||
count++;
|
||||
const CBMType **arr =
|
||||
(const CBMType **)cbm_arena_alloc(a, (count + 1) * sizeof(const CBMType *));
|
||||
if (arr) {
|
||||
for (int i = 0; i < count; i++)
|
||||
arr[i] = return_types[i];
|
||||
arr[count] = NULL;
|
||||
t->data.func.return_types = arr;
|
||||
}
|
||||
}
|
||||
if (param_types) {
|
||||
int count = 0;
|
||||
while (param_types[count])
|
||||
count++;
|
||||
const CBMType **arr =
|
||||
(const CBMType **)cbm_arena_alloc(a, (count + 1) * sizeof(const CBMType *));
|
||||
if (arr) {
|
||||
for (int i = 0; i < count; i++)
|
||||
arr[i] = param_types[i];
|
||||
arr[count] = NULL;
|
||||
t->data.func.param_types = arr;
|
||||
}
|
||||
}
|
||||
if (param_names) {
|
||||
int count = 0;
|
||||
while (param_names[count])
|
||||
count++;
|
||||
const char **arr = (const char **)cbm_arena_alloc(a, (count + 1) * sizeof(const char *));
|
||||
if (arr) {
|
||||
for (int i = 0; i < count; i++)
|
||||
arr[i] = param_names[i];
|
||||
arr[count] = NULL;
|
||||
t->data.func.param_names = arr;
|
||||
}
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
const CBMType *cbm_type_builtin(CBMArena *a, const char *name) {
|
||||
CBMType *t = (CBMType *)cbm_arena_alloc(a, sizeof(CBMType));
|
||||
if (!t)
|
||||
return &unknown_singleton;
|
||||
memset(t, 0, sizeof(CBMType));
|
||||
t->kind = CBM_TYPE_BUILTIN;
|
||||
t->data.builtin.name = cbm_arena_strdup(a, name);
|
||||
return t;
|
||||
}
|
||||
|
||||
const CBMType *cbm_type_tuple(CBMArena *a, const CBMType **elems, int count) {
|
||||
CBMType *t = (CBMType *)cbm_arena_alloc(a, sizeof(CBMType));
|
||||
if (!t)
|
||||
return &unknown_singleton;
|
||||
memset(t, 0, sizeof(CBMType));
|
||||
t->kind = CBM_TYPE_TUPLE;
|
||||
// Copy elems array
|
||||
const CBMType **arr =
|
||||
(const CBMType **)cbm_arena_alloc(a, (count + 1) * sizeof(const CBMType *));
|
||||
if (!arr)
|
||||
return &unknown_singleton;
|
||||
for (int i = 0; i < count; i++)
|
||||
arr[i] = elems[i];
|
||||
arr[count] = NULL;
|
||||
t->data.tuple.elems = arr;
|
||||
t->data.tuple.count = count;
|
||||
return t;
|
||||
}
|
||||
|
||||
const CBMType *cbm_type_type_param(CBMArena *a, const char *name) {
|
||||
CBMType *t = (CBMType *)cbm_arena_alloc(a, sizeof(CBMType));
|
||||
if (!t)
|
||||
return &unknown_singleton;
|
||||
memset(t, 0, sizeof(CBMType));
|
||||
t->kind = CBM_TYPE_TYPE_PARAM;
|
||||
t->data.type_param.name = cbm_arena_strdup(a, name);
|
||||
return t;
|
||||
}
|
||||
|
||||
const CBMType *cbm_type_reference(CBMArena *a, const CBMType *elem) {
|
||||
CBMType *t = (CBMType *)cbm_arena_alloc(a, sizeof(CBMType));
|
||||
if (!t)
|
||||
return &unknown_singleton;
|
||||
memset(t, 0, sizeof(CBMType));
|
||||
t->kind = CBM_TYPE_REFERENCE;
|
||||
t->data.reference.elem = elem;
|
||||
return t;
|
||||
}
|
||||
|
||||
const CBMType *cbm_type_rvalue_ref(CBMArena *a, const CBMType *elem) {
|
||||
CBMType *t = (CBMType *)cbm_arena_alloc(a, sizeof(CBMType));
|
||||
if (!t)
|
||||
return &unknown_singleton;
|
||||
memset(t, 0, sizeof(CBMType));
|
||||
t->kind = CBM_TYPE_RVALUE_REF;
|
||||
t->data.reference.elem = elem;
|
||||
return t;
|
||||
}
|
||||
|
||||
const CBMType *cbm_type_template(CBMArena *a, const char *name, const CBMType **args,
|
||||
int arg_count) {
|
||||
CBMType *t = (CBMType *)cbm_arena_alloc(a, sizeof(CBMType));
|
||||
if (!t)
|
||||
return &unknown_singleton;
|
||||
memset(t, 0, sizeof(CBMType));
|
||||
t->kind = CBM_TYPE_TEMPLATE;
|
||||
t->data.template_type.template_name = cbm_arena_strdup(a, name);
|
||||
if (args && arg_count > 0) {
|
||||
const CBMType **arr =
|
||||
(const CBMType **)cbm_arena_alloc(a, (arg_count + 1) * sizeof(const CBMType *));
|
||||
if (arr) {
|
||||
for (int i = 0; i < arg_count; i++)
|
||||
arr[i] = args[i];
|
||||
arr[arg_count] = NULL;
|
||||
t->data.template_type.template_args = arr;
|
||||
}
|
||||
}
|
||||
t->data.template_type.arg_count = arg_count;
|
||||
return t;
|
||||
}
|
||||
|
||||
const CBMType *cbm_type_alias(CBMArena *a, const char *alias_qn, const CBMType *underlying) {
|
||||
CBMType *t = (CBMType *)cbm_arena_alloc(a, sizeof(CBMType));
|
||||
if (!t)
|
||||
return &unknown_singleton;
|
||||
memset(t, 0, sizeof(CBMType));
|
||||
t->kind = CBM_TYPE_ALIAS;
|
||||
t->data.alias.alias_qn = cbm_arena_strdup(a, alias_qn);
|
||||
t->data.alias.underlying = underlying;
|
||||
return t;
|
||||
}
|
||||
|
||||
// --- Python-flavored constructors -------------------------------------------
|
||||
|
||||
// Dedupe members by structural equality, in place. Returns new length.
|
||||
// Preserves first-seen order so output is deterministic.
|
||||
static int union_member_dedupe(const CBMType **scratch, int count) {
|
||||
int out = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
bool seen = false;
|
||||
for (int j = 0; j < out; j++) {
|
||||
if (cbm_type_equal(scratch[i], scratch[j])) {
|
||||
seen = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!seen) {
|
||||
scratch[out++] = scratch[i];
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Shared by Python (cbm_type_union) and TS (`A | B`). Flattens nested UNIONs and
|
||||
// dedupes members.
|
||||
const CBMType *cbm_type_union(CBMArena *a, const CBMType **members, int count) {
|
||||
if (!members || count <= 0)
|
||||
return &unknown_singleton;
|
||||
|
||||
// Flatten: nested UNIONs unfold their members into the parent.
|
||||
int flat_cap = count * 2 + 4;
|
||||
const CBMType **flat = (const CBMType **)cbm_arena_alloc(a, flat_cap * sizeof(const CBMType *));
|
||||
if (!flat)
|
||||
return &unknown_singleton;
|
||||
int flat_count = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
const CBMType *m = members[i];
|
||||
if (!m || cbm_type_is_unknown(m))
|
||||
continue;
|
||||
if (m->kind == CBM_TYPE_UNION) {
|
||||
for (int j = 0; j < m->data.union_type.count; j++) {
|
||||
if (flat_count < flat_cap)
|
||||
flat[flat_count++] = m->data.union_type.members[j];
|
||||
}
|
||||
} else {
|
||||
if (flat_count < flat_cap)
|
||||
flat[flat_count++] = m;
|
||||
}
|
||||
}
|
||||
if (flat_count == 0)
|
||||
return &unknown_singleton;
|
||||
|
||||
// Dedupe by structural equality.
|
||||
int unique_count = union_member_dedupe(flat, flat_count);
|
||||
if (unique_count == 1)
|
||||
return flat[0];
|
||||
|
||||
CBMType *t = (CBMType *)cbm_arena_alloc(a, sizeof(CBMType));
|
||||
if (!t)
|
||||
return &unknown_singleton;
|
||||
memset(t, 0, sizeof(CBMType));
|
||||
t->kind = CBM_TYPE_UNION;
|
||||
const CBMType **arr =
|
||||
(const CBMType **)cbm_arena_alloc(a, (unique_count + 1) * sizeof(const CBMType *));
|
||||
if (!arr)
|
||||
return &unknown_singleton;
|
||||
for (int i = 0; i < unique_count; i++)
|
||||
arr[i] = flat[i];
|
||||
arr[unique_count] = NULL;
|
||||
t->data.union_type.members = arr;
|
||||
t->data.union_type.count = unique_count;
|
||||
return t;
|
||||
}
|
||||
|
||||
const CBMType *cbm_type_optional(CBMArena *a, const CBMType *inner) {
|
||||
if (!inner)
|
||||
return &unknown_singleton;
|
||||
const CBMType *none_t = cbm_type_builtin(a, "None");
|
||||
const CBMType *members[2] = {inner, none_t};
|
||||
return cbm_type_union(a, members, 2);
|
||||
}
|
||||
|
||||
const CBMType *cbm_type_literal(CBMArena *a, const CBMType *base, const char *literal_text) {
|
||||
CBMType *t = (CBMType *)cbm_arena_alloc(a, sizeof(CBMType));
|
||||
if (!t)
|
||||
return &unknown_singleton;
|
||||
memset(t, 0, sizeof(CBMType));
|
||||
t->kind = CBM_TYPE_LITERAL;
|
||||
t->data.literal.base = base ? base : &unknown_singleton;
|
||||
t->data.literal.literal_text = literal_text ? cbm_arena_strdup(a, literal_text) : NULL;
|
||||
return t;
|
||||
}
|
||||
|
||||
const CBMType *cbm_type_protocol(CBMArena *a, const char *qualified_name, const char **method_names,
|
||||
const CBMType **method_sigs) {
|
||||
CBMType *t = (CBMType *)cbm_arena_alloc(a, sizeof(CBMType));
|
||||
if (!t)
|
||||
return &unknown_singleton;
|
||||
memset(t, 0, sizeof(CBMType));
|
||||
t->kind = CBM_TYPE_PROTOCOL;
|
||||
t->data.protocol.qualified_name = qualified_name ? cbm_arena_strdup(a, qualified_name) : NULL;
|
||||
|
||||
int n = 0;
|
||||
if (method_names) {
|
||||
while (method_names[n])
|
||||
n++;
|
||||
}
|
||||
if (n > 0) {
|
||||
const char **names = (const char **)cbm_arena_alloc(a, (n + 1) * sizeof(const char *));
|
||||
const CBMType **sigs =
|
||||
(const CBMType **)cbm_arena_alloc(a, (n + 1) * sizeof(const CBMType *));
|
||||
if (names && sigs) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
names[i] = cbm_arena_strdup(a, method_names[i]);
|
||||
sigs[i] = method_sigs ? method_sigs[i] : NULL;
|
||||
}
|
||||
names[n] = NULL;
|
||||
sigs[n] = NULL;
|
||||
t->data.protocol.method_names = names;
|
||||
t->data.protocol.method_sigs = sigs;
|
||||
}
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
const CBMType *cbm_type_module(CBMArena *a, const char *module_qn) {
|
||||
CBMType *t = (CBMType *)cbm_arena_alloc(a, sizeof(CBMType));
|
||||
if (!t)
|
||||
return &unknown_singleton;
|
||||
memset(t, 0, sizeof(CBMType));
|
||||
t->kind = CBM_TYPE_MODULE;
|
||||
t->data.module.module_qn = module_qn ? cbm_arena_strdup(a, module_qn) : NULL;
|
||||
return t;
|
||||
}
|
||||
|
||||
const CBMType *cbm_type_callable(CBMArena *a, const CBMType **param_types, int param_count,
|
||||
const CBMType *return_type) {
|
||||
CBMType *t = (CBMType *)cbm_arena_alloc(a, sizeof(CBMType));
|
||||
if (!t)
|
||||
return &unknown_singleton;
|
||||
memset(t, 0, sizeof(CBMType));
|
||||
t->kind = CBM_TYPE_CALLABLE;
|
||||
t->data.callable.param_count = param_count;
|
||||
t->data.callable.return_type = return_type ? return_type : &unknown_singleton;
|
||||
if (param_count > 0 && param_types) {
|
||||
const CBMType **arr =
|
||||
(const CBMType **)cbm_arena_alloc(a, (param_count + 1) * sizeof(const CBMType *));
|
||||
if (arr) {
|
||||
for (int i = 0; i < param_count; i++)
|
||||
arr[i] = param_types[i];
|
||||
arr[param_count] = NULL;
|
||||
t->data.callable.param_types = arr;
|
||||
}
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
// --- TS-specific constructors -----------------------------------------------
|
||||
|
||||
const CBMType *cbm_type_intersection(CBMArena *a, const CBMType **members, int count) {
|
||||
CBMType *t = (CBMType *)cbm_arena_alloc(a, sizeof(CBMType));
|
||||
if (!t)
|
||||
return &unknown_singleton;
|
||||
memset(t, 0, sizeof(CBMType));
|
||||
t->kind = CBM_TYPE_INTERSECTION;
|
||||
if (members && count > 0) {
|
||||
const CBMType **arr =
|
||||
(const CBMType **)cbm_arena_alloc(a, (count + 1) * sizeof(const CBMType *));
|
||||
if (arr) {
|
||||
for (int i = 0; i < count; i++)
|
||||
arr[i] = members[i];
|
||||
arr[count] = NULL;
|
||||
t->data.union_type.members = arr;
|
||||
}
|
||||
}
|
||||
t->data.union_type.count = count;
|
||||
return t;
|
||||
}
|
||||
|
||||
const CBMType *cbm_type_ts_literal(CBMArena *a, const char *tag, const char *value) {
|
||||
CBMType *t = (CBMType *)cbm_arena_alloc(a, sizeof(CBMType));
|
||||
if (!t)
|
||||
return &unknown_singleton;
|
||||
memset(t, 0, sizeof(CBMType));
|
||||
t->kind = CBM_TYPE_TS_LITERAL;
|
||||
t->data.literal_ts.tag = tag ? cbm_arena_strdup(a, tag) : NULL;
|
||||
t->data.literal_ts.value = value ? cbm_arena_strdup(a, value) : NULL;
|
||||
return t;
|
||||
}
|
||||
|
||||
const CBMType *cbm_type_indexed(CBMArena *a, const CBMType *object, const CBMType *index) {
|
||||
CBMType *t = (CBMType *)cbm_arena_alloc(a, sizeof(CBMType));
|
||||
if (!t)
|
||||
return &unknown_singleton;
|
||||
memset(t, 0, sizeof(CBMType));
|
||||
t->kind = CBM_TYPE_INDEXED;
|
||||
t->data.indexed.object = object;
|
||||
t->data.indexed.index = index;
|
||||
return t;
|
||||
}
|
||||
|
||||
const CBMType *cbm_type_keyof(CBMArena *a, const CBMType *operand) {
|
||||
CBMType *t = (CBMType *)cbm_arena_alloc(a, sizeof(CBMType));
|
||||
if (!t)
|
||||
return &unknown_singleton;
|
||||
memset(t, 0, sizeof(CBMType));
|
||||
t->kind = CBM_TYPE_KEYOF;
|
||||
t->data.keyof.operand = operand;
|
||||
return t;
|
||||
}
|
||||
|
||||
const CBMType *cbm_type_typeof_query(CBMArena *a, const char *expr) {
|
||||
CBMType *t = (CBMType *)cbm_arena_alloc(a, sizeof(CBMType));
|
||||
if (!t)
|
||||
return &unknown_singleton;
|
||||
memset(t, 0, sizeof(CBMType));
|
||||
t->kind = CBM_TYPE_TYPEOF_QUERY;
|
||||
t->data.typeof_query.expr = expr ? cbm_arena_strdup(a, expr) : NULL;
|
||||
return t;
|
||||
}
|
||||
|
||||
const CBMType *cbm_type_conditional(CBMArena *a, const CBMType *check, const CBMType *extends,
|
||||
const CBMType *true_branch, const CBMType *false_branch) {
|
||||
CBMType *t = (CBMType *)cbm_arena_alloc(a, sizeof(CBMType));
|
||||
if (!t)
|
||||
return &unknown_singleton;
|
||||
memset(t, 0, sizeof(CBMType));
|
||||
t->kind = CBM_TYPE_CONDITIONAL;
|
||||
t->data.conditional.check = check;
|
||||
t->data.conditional.extends = extends;
|
||||
t->data.conditional.true_branch = true_branch;
|
||||
t->data.conditional.false_branch = false_branch;
|
||||
return t;
|
||||
}
|
||||
|
||||
const CBMType *cbm_type_object_lit(CBMArena *a, const char **prop_names, const CBMType **prop_types,
|
||||
const CBMType *call_signature, const CBMType *index_value) {
|
||||
CBMType *t = (CBMType *)cbm_arena_alloc(a, sizeof(CBMType));
|
||||
if (!t)
|
||||
return &unknown_singleton;
|
||||
memset(t, 0, sizeof(CBMType));
|
||||
t->kind = CBM_TYPE_OBJECT_LIT;
|
||||
if (prop_names && prop_types) {
|
||||
int count = 0;
|
||||
while (prop_names[count] && prop_types[count])
|
||||
count++;
|
||||
if (count > 0) {
|
||||
const char **name_arr =
|
||||
(const char **)cbm_arena_alloc(a, (count + 1) * sizeof(const char *));
|
||||
const CBMType **type_arr =
|
||||
(const CBMType **)cbm_arena_alloc(a, (count + 1) * sizeof(const CBMType *));
|
||||
if (name_arr && type_arr) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
name_arr[i] = prop_names[i];
|
||||
type_arr[i] = prop_types[i];
|
||||
}
|
||||
name_arr[count] = NULL;
|
||||
type_arr[count] = NULL;
|
||||
t->data.object_lit.prop_names = name_arr;
|
||||
t->data.object_lit.prop_types = type_arr;
|
||||
}
|
||||
}
|
||||
}
|
||||
t->data.object_lit.call_signature = call_signature;
|
||||
t->data.object_lit.index_value = index_value;
|
||||
return t;
|
||||
}
|
||||
|
||||
const CBMType *cbm_type_infer(CBMArena *a, const char *name) {
|
||||
CBMType *t = (CBMType *)cbm_arena_alloc(a, sizeof(CBMType));
|
||||
if (!t)
|
||||
return &unknown_singleton;
|
||||
memset(t, 0, sizeof(CBMType));
|
||||
t->kind = CBM_TYPE_INFER;
|
||||
t->data.infer.name = name ? cbm_arena_strdup(a, name) : NULL;
|
||||
return t;
|
||||
}
|
||||
|
||||
const CBMType *cbm_type_mapped(CBMArena *a, const char *key_name, const CBMType *key_constraint,
|
||||
const CBMType *value) {
|
||||
CBMType *t = (CBMType *)cbm_arena_alloc(a, sizeof(CBMType));
|
||||
if (!t)
|
||||
return &unknown_singleton;
|
||||
memset(t, 0, sizeof(CBMType));
|
||||
t->kind = CBM_TYPE_MAPPED;
|
||||
t->data.mapped.key_name = key_name ? cbm_arena_strdup(a, key_name) : NULL;
|
||||
t->data.mapped.key_constraint = key_constraint;
|
||||
t->data.mapped.value = value;
|
||||
return t;
|
||||
}
|
||||
|
||||
// Operations
|
||||
|
||||
const CBMType *cbm_type_deref(const CBMType *t) {
|
||||
if (!t)
|
||||
return t;
|
||||
// Unwrap references transparently (C++ member access through refs)
|
||||
if (t->kind == CBM_TYPE_REFERENCE || t->kind == CBM_TYPE_RVALUE_REF)
|
||||
return t->data.reference.elem;
|
||||
if (t->kind != CBM_TYPE_POINTER)
|
||||
return t;
|
||||
return t->data.pointer.elem;
|
||||
}
|
||||
|
||||
const CBMType *cbm_type_elem(const CBMType *t) {
|
||||
if (!t)
|
||||
return cbm_type_unknown();
|
||||
switch (t->kind) {
|
||||
case CBM_TYPE_POINTER:
|
||||
return t->data.pointer.elem;
|
||||
case CBM_TYPE_SLICE:
|
||||
return t->data.slice.elem;
|
||||
case CBM_TYPE_CHANNEL:
|
||||
return t->data.channel.elem;
|
||||
case CBM_TYPE_REFERENCE:
|
||||
return t->data.reference.elem;
|
||||
case CBM_TYPE_RVALUE_REF:
|
||||
return t->data.reference.elem;
|
||||
default:
|
||||
return cbm_type_unknown();
|
||||
}
|
||||
}
|
||||
|
||||
bool cbm_type_is_unknown(const CBMType *t) {
|
||||
if (!t)
|
||||
return true;
|
||||
/* Guard against dangling pointers from stale field_types entries.
|
||||
* Check alignment before dereferencing — misaligned pointer means garbage. */
|
||||
if (((uintptr_t)t & (_Alignof(CBMType) - 1)) != 0)
|
||||
return true;
|
||||
return t->kind == CBM_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
bool cbm_type_is_interface(const CBMType *t) {
|
||||
return t && t->kind == CBM_TYPE_INTERFACE;
|
||||
}
|
||||
|
||||
bool cbm_type_is_pointer(const CBMType *t) {
|
||||
return t && t->kind == CBM_TYPE_POINTER;
|
||||
}
|
||||
|
||||
bool cbm_type_is_reference(const CBMType *t) {
|
||||
return t && (t->kind == CBM_TYPE_REFERENCE || t->kind == CBM_TYPE_RVALUE_REF);
|
||||
}
|
||||
|
||||
bool cbm_type_is_union(const CBMType *t) {
|
||||
return t && t->kind == CBM_TYPE_UNION;
|
||||
}
|
||||
|
||||
bool cbm_type_is_protocol(const CBMType *t) {
|
||||
return t && t->kind == CBM_TYPE_PROTOCOL;
|
||||
}
|
||||
|
||||
bool cbm_type_is_module(const CBMType *t) {
|
||||
return t && t->kind == CBM_TYPE_MODULE;
|
||||
}
|
||||
|
||||
static bool str_eq_or_both_null(const char *a, const char *b) {
|
||||
if (a == b)
|
||||
return true;
|
||||
if (!a || !b)
|
||||
return false;
|
||||
return strcmp(a, b) == 0;
|
||||
}
|
||||
|
||||
bool cbm_type_equal(const CBMType *a, const CBMType *b) {
|
||||
if (a == b)
|
||||
return true;
|
||||
if (!a || !b)
|
||||
return false;
|
||||
if (a->kind != b->kind)
|
||||
return false;
|
||||
|
||||
switch (a->kind) {
|
||||
case CBM_TYPE_UNKNOWN:
|
||||
return true;
|
||||
case CBM_TYPE_NAMED:
|
||||
return str_eq_or_both_null(a->data.named.qualified_name, b->data.named.qualified_name);
|
||||
case CBM_TYPE_BUILTIN:
|
||||
return str_eq_or_both_null(a->data.builtin.name, b->data.builtin.name);
|
||||
case CBM_TYPE_TYPE_PARAM:
|
||||
return str_eq_or_both_null(a->data.type_param.name, b->data.type_param.name);
|
||||
case CBM_TYPE_POINTER:
|
||||
return cbm_type_equal(a->data.pointer.elem, b->data.pointer.elem);
|
||||
case CBM_TYPE_SLICE:
|
||||
return cbm_type_equal(a->data.slice.elem, b->data.slice.elem);
|
||||
case CBM_TYPE_REFERENCE:
|
||||
case CBM_TYPE_RVALUE_REF:
|
||||
return cbm_type_equal(a->data.reference.elem, b->data.reference.elem);
|
||||
case CBM_TYPE_MAP:
|
||||
return cbm_type_equal(a->data.map.key, b->data.map.key) &&
|
||||
cbm_type_equal(a->data.map.value, b->data.map.value);
|
||||
case CBM_TYPE_CHANNEL:
|
||||
return a->data.channel.direction == b->data.channel.direction &&
|
||||
cbm_type_equal(a->data.channel.elem, b->data.channel.elem);
|
||||
case CBM_TYPE_TUPLE: {
|
||||
if (a->data.tuple.count != b->data.tuple.count)
|
||||
return false;
|
||||
for (int i = 0; i < a->data.tuple.count; i++) {
|
||||
if (!cbm_type_equal(a->data.tuple.elems[i], b->data.tuple.elems[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case CBM_TYPE_TEMPLATE: {
|
||||
if (!str_eq_or_both_null(a->data.template_type.template_name,
|
||||
b->data.template_type.template_name))
|
||||
return false;
|
||||
if (a->data.template_type.arg_count != b->data.template_type.arg_count)
|
||||
return false;
|
||||
for (int i = 0; i < a->data.template_type.arg_count; i++) {
|
||||
if (!cbm_type_equal(a->data.template_type.template_args[i],
|
||||
b->data.template_type.template_args[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case CBM_TYPE_ALIAS:
|
||||
return str_eq_or_both_null(a->data.alias.alias_qn, b->data.alias.alias_qn);
|
||||
case CBM_TYPE_UNION: {
|
||||
if (a->data.union_type.count != b->data.union_type.count)
|
||||
return false;
|
||||
// Order-independent: every a-member must appear in b's set.
|
||||
for (int i = 0; i < a->data.union_type.count; i++) {
|
||||
bool found = false;
|
||||
for (int j = 0; j < b->data.union_type.count; j++) {
|
||||
if (cbm_type_equal(a->data.union_type.members[i], b->data.union_type.members[j])) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case CBM_TYPE_LITERAL:
|
||||
return cbm_type_equal(a->data.literal.base, b->data.literal.base) &&
|
||||
str_eq_or_both_null(a->data.literal.literal_text, b->data.literal.literal_text);
|
||||
case CBM_TYPE_PROTOCOL:
|
||||
return str_eq_or_both_null(a->data.protocol.qualified_name,
|
||||
b->data.protocol.qualified_name);
|
||||
case CBM_TYPE_MODULE:
|
||||
return str_eq_or_both_null(a->data.module.module_qn, b->data.module.module_qn);
|
||||
case CBM_TYPE_CALLABLE: {
|
||||
if (a->data.callable.param_count != b->data.callable.param_count)
|
||||
return false;
|
||||
if (!cbm_type_equal(a->data.callable.return_type, b->data.callable.return_type))
|
||||
return false;
|
||||
if (a->data.callable.param_count > 0) {
|
||||
for (int i = 0; i < a->data.callable.param_count; i++) {
|
||||
if (!cbm_type_equal(a->data.callable.param_types[i],
|
||||
b->data.callable.param_types[i]))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case CBM_TYPE_INTERSECTION: {
|
||||
// Same shape as UNION; compare order-independently.
|
||||
if (a->data.union_type.count != b->data.union_type.count)
|
||||
return false;
|
||||
for (int i = 0; i < a->data.union_type.count; i++) {
|
||||
bool found = false;
|
||||
for (int j = 0; j < b->data.union_type.count; j++) {
|
||||
if (cbm_type_equal(a->data.union_type.members[i], b->data.union_type.members[j])) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case CBM_TYPE_TS_LITERAL:
|
||||
return str_eq_or_both_null(a->data.literal_ts.tag, b->data.literal_ts.tag) &&
|
||||
str_eq_or_both_null(a->data.literal_ts.value, b->data.literal_ts.value);
|
||||
case CBM_TYPE_INDEXED:
|
||||
return cbm_type_equal(a->data.indexed.object, b->data.indexed.object) &&
|
||||
cbm_type_equal(a->data.indexed.index, b->data.indexed.index);
|
||||
case CBM_TYPE_KEYOF:
|
||||
return cbm_type_equal(a->data.keyof.operand, b->data.keyof.operand);
|
||||
case CBM_TYPE_TYPEOF_QUERY:
|
||||
return str_eq_or_both_null(a->data.typeof_query.expr, b->data.typeof_query.expr);
|
||||
case CBM_TYPE_CONDITIONAL:
|
||||
return cbm_type_equal(a->data.conditional.check, b->data.conditional.check) &&
|
||||
cbm_type_equal(a->data.conditional.extends, b->data.conditional.extends) &&
|
||||
cbm_type_equal(a->data.conditional.true_branch, b->data.conditional.true_branch) &&
|
||||
cbm_type_equal(a->data.conditional.false_branch, b->data.conditional.false_branch);
|
||||
case CBM_TYPE_INFER:
|
||||
return str_eq_or_both_null(a->data.infer.name, b->data.infer.name);
|
||||
case CBM_TYPE_OBJECT_LIT:
|
||||
case CBM_TYPE_MAPPED:
|
||||
case CBM_TYPE_FUNC:
|
||||
case CBM_TYPE_INTERFACE:
|
||||
case CBM_TYPE_STRUCT:
|
||||
// Structural equality on these is expensive and rarely needed by callers
|
||||
// beyond pointer identity (already checked above). Treat as not-equal.
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool cbm_type_protocol_satisfied_by(const CBMType *proto, const CBMType *candidate) {
|
||||
if (!proto || proto->kind != CBM_TYPE_PROTOCOL)
|
||||
return false;
|
||||
if (!candidate)
|
||||
return false;
|
||||
// candidate must be a NAMED or PROTOCOL type with a method-name set we
|
||||
// can inspect. For PROTOCOL candidates, trivially satisfied if every
|
||||
// proto method appears in candidate's method list.
|
||||
if (candidate->kind == CBM_TYPE_PROTOCOL) {
|
||||
if (!proto->data.protocol.method_names)
|
||||
return true;
|
||||
for (int i = 0; proto->data.protocol.method_names[i]; i++) {
|
||||
const char *needed = proto->data.protocol.method_names[i];
|
||||
bool found = false;
|
||||
if (candidate->data.protocol.method_names) {
|
||||
for (int j = 0; candidate->data.protocol.method_names[j]; j++) {
|
||||
if (str_eq_or_both_null(needed, candidate->data.protocol.method_names[j])) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// Nominal candidates require the registry — caller's responsibility.
|
||||
return false;
|
||||
}
|
||||
|
||||
const CBMType *cbm_type_resolve_alias(const CBMType *t) {
|
||||
for (int i = 0; i < 16 && t; i++) {
|
||||
if (t->kind != CBM_TYPE_ALIAS)
|
||||
return t;
|
||||
if (!t->data.alias.underlying)
|
||||
return t;
|
||||
t = t->data.alias.underlying;
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
// Generic substitution: recursively replace TYPE_PARAM with concrete types.
|
||||
const CBMType *cbm_type_substitute(CBMArena *a, const CBMType *t, const char **type_params,
|
||||
const CBMType **type_args) {
|
||||
if (!t)
|
||||
return cbm_type_unknown();
|
||||
if (!type_params || !type_args)
|
||||
return t;
|
||||
|
||||
/* type_args may be SHORTER than type_params — a class template instantiated
|
||||
* with fewer args than declared params, or trailing default template args
|
||||
* (e.g. `Box<Widget>` for `template<class T, class U, class V>`). Indexing
|
||||
* type_args[i] by the type_params loop index would then read past the args
|
||||
* array, yielding a bogus CBMType* that is later dereferenced -> SEGV (#427).
|
||||
* type_params is always NULL-terminated; type_args is either parallel-length
|
||||
* (some callers pass a fixed positional array that is NOT NULL-terminated) or
|
||||
* shorter-and-NULL-terminated. Bound the length walk by the param count so it
|
||||
* can never run off a non-terminated args array, then bound every type_args[i]
|
||||
* access by the result. */
|
||||
int nparams = 0;
|
||||
while (type_params[nparams]) {
|
||||
nparams++;
|
||||
}
|
||||
/* Contract: type_args must be NULL-terminated (it may be shorter than
|
||||
* type_params). A misaligned or null-page value can never be a real
|
||||
* CBMType* — it means a caller passed an unterminated array and the walk
|
||||
* is reading uninitialized memory (seen on bitcoin's serialize.h: an
|
||||
* explicit-template-arg call bound T to stack garbage that was woven into
|
||||
* the registered type graph and dereferenced later -> SIGSEGV). Treat such
|
||||
* values as the terminator so garbage can never enter a type graph. */
|
||||
int args_len = 0;
|
||||
while (args_len < nparams && type_args[args_len] &&
|
||||
((uintptr_t)type_args[args_len] & (sizeof(void *) - 1)) == 0 &&
|
||||
(uintptr_t)type_args[args_len] >= TR_MIN_PLAUSIBLE_PTR) {
|
||||
args_len++;
|
||||
}
|
||||
|
||||
switch (t->kind) {
|
||||
case CBM_TYPE_TYPE_PARAM: {
|
||||
for (int i = 0; type_params[i]; i++) {
|
||||
if (strcmp(t->data.type_param.name, type_params[i]) == 0) {
|
||||
return (i < args_len && type_args[i]) ? type_args[i] : t;
|
||||
}
|
||||
}
|
||||
return t; // unmatched param stays as-is
|
||||
}
|
||||
case CBM_TYPE_NAMED: {
|
||||
// Also substitute NAMED types matching template param names.
|
||||
// c_parse_return_type_text may parse "A" as NAMED("test.main.A")
|
||||
// instead of TYPE_PARAM("A") — check both full QN and short name.
|
||||
const char *qn = t->data.named.qualified_name;
|
||||
if (qn) {
|
||||
const char *short_name = strrchr(qn, '.');
|
||||
short_name = short_name ? short_name + 1 : qn;
|
||||
for (int i = 0; type_params[i]; i++) {
|
||||
if (strcmp(qn, type_params[i]) == 0 || strcmp(short_name, type_params[i]) == 0) {
|
||||
return (i < args_len && type_args[i]) ? type_args[i] : t;
|
||||
}
|
||||
}
|
||||
}
|
||||
return t;
|
||||
}
|
||||
case CBM_TYPE_POINTER:
|
||||
return cbm_type_pointer(
|
||||
a, cbm_type_substitute(a, t->data.pointer.elem, type_params, type_args));
|
||||
case CBM_TYPE_REFERENCE:
|
||||
return cbm_type_reference(
|
||||
a, cbm_type_substitute(a, t->data.reference.elem, type_params, type_args));
|
||||
case CBM_TYPE_RVALUE_REF:
|
||||
return cbm_type_rvalue_ref(
|
||||
a, cbm_type_substitute(a, t->data.reference.elem, type_params, type_args));
|
||||
case CBM_TYPE_SLICE:
|
||||
return cbm_type_slice(a,
|
||||
cbm_type_substitute(a, t->data.slice.elem, type_params, type_args));
|
||||
case CBM_TYPE_MAP:
|
||||
return cbm_type_map(a, cbm_type_substitute(a, t->data.map.key, type_params, type_args),
|
||||
cbm_type_substitute(a, t->data.map.value, type_params, type_args));
|
||||
case CBM_TYPE_CHANNEL:
|
||||
return cbm_type_channel(
|
||||
a, cbm_type_substitute(a, t->data.channel.elem, type_params, type_args),
|
||||
t->data.channel.direction);
|
||||
case CBM_TYPE_TUPLE: {
|
||||
int count = t->data.tuple.count;
|
||||
const CBMType **elems =
|
||||
(const CBMType **)cbm_arena_alloc(a, (count + 1) * sizeof(const CBMType *));
|
||||
if (!elems)
|
||||
return t;
|
||||
for (int i = 0; i < count; i++) {
|
||||
elems[i] = cbm_type_substitute(a, t->data.tuple.elems[i], type_params, type_args);
|
||||
}
|
||||
elems[count] = NULL;
|
||||
return cbm_type_tuple(a, elems, count);
|
||||
}
|
||||
case CBM_TYPE_UNION:
|
||||
case CBM_TYPE_INTERSECTION: {
|
||||
int count = t->data.union_type.count;
|
||||
if (count <= 0 || !t->data.union_type.members)
|
||||
return t;
|
||||
const CBMType **elems =
|
||||
(const CBMType **)cbm_arena_alloc(a, (count + 1) * sizeof(const CBMType *));
|
||||
if (!elems)
|
||||
return t;
|
||||
for (int i = 0; i < count; i++) {
|
||||
elems[i] =
|
||||
cbm_type_substitute(a, t->data.union_type.members[i], type_params, type_args);
|
||||
}
|
||||
elems[count] = NULL;
|
||||
return t->kind == CBM_TYPE_UNION ? cbm_type_union(a, elems, count)
|
||||
: cbm_type_intersection(a, elems, count);
|
||||
}
|
||||
case CBM_TYPE_INDEXED:
|
||||
return cbm_type_indexed(
|
||||
a, cbm_type_substitute(a, t->data.indexed.object, type_params, type_args),
|
||||
cbm_type_substitute(a, t->data.indexed.index, type_params, type_args));
|
||||
case CBM_TYPE_KEYOF:
|
||||
return cbm_type_keyof(
|
||||
a, cbm_type_substitute(a, t->data.keyof.operand, type_params, type_args));
|
||||
case CBM_TYPE_CONDITIONAL:
|
||||
return cbm_type_conditional(
|
||||
a, cbm_type_substitute(a, t->data.conditional.check, type_params, type_args),
|
||||
cbm_type_substitute(a, t->data.conditional.extends, type_params, type_args),
|
||||
cbm_type_substitute(a, t->data.conditional.true_branch, type_params, type_args),
|
||||
cbm_type_substitute(a, t->data.conditional.false_branch, type_params, type_args));
|
||||
case CBM_TYPE_FUNC: {
|
||||
// Recurse into param_types and return_types. Param/return arrays may be NULL.
|
||||
const CBMType **new_params = NULL;
|
||||
const CBMType **new_returns = NULL;
|
||||
if (t->data.func.param_types) {
|
||||
int pc = 0;
|
||||
while (t->data.func.param_types[pc])
|
||||
pc++;
|
||||
new_params =
|
||||
(const CBMType **)cbm_arena_alloc(a, (size_t)(pc + 1) * sizeof(const CBMType *));
|
||||
if (!new_params)
|
||||
return t;
|
||||
for (int i = 0; i < pc; i++) {
|
||||
new_params[i] =
|
||||
cbm_type_substitute(a, t->data.func.param_types[i], type_params, type_args);
|
||||
}
|
||||
new_params[pc] = NULL;
|
||||
}
|
||||
if (t->data.func.return_types) {
|
||||
int rc = 0;
|
||||
while (t->data.func.return_types[rc])
|
||||
rc++;
|
||||
new_returns =
|
||||
(const CBMType **)cbm_arena_alloc(a, (size_t)(rc + 1) * sizeof(const CBMType *));
|
||||
if (!new_returns)
|
||||
return t;
|
||||
for (int i = 0; i < rc; i++) {
|
||||
new_returns[i] =
|
||||
cbm_type_substitute(a, t->data.func.return_types[i], type_params, type_args);
|
||||
}
|
||||
new_returns[rc] = NULL;
|
||||
}
|
||||
return cbm_type_func(a, t->data.func.param_names, new_params, new_returns);
|
||||
}
|
||||
case CBM_TYPE_TEMPLATE: {
|
||||
if (!t->data.template_type.template_args || t->data.template_type.arg_count == 0)
|
||||
return t;
|
||||
int ac = t->data.template_type.arg_count;
|
||||
const CBMType **new_args =
|
||||
(const CBMType **)cbm_arena_alloc(a, (size_t)(ac + 1) * sizeof(const CBMType *));
|
||||
if (!new_args)
|
||||
return t;
|
||||
for (int i = 0; i < ac; i++) {
|
||||
new_args[i] = cbm_type_substitute(a, t->data.template_type.template_args[i],
|
||||
type_params, type_args);
|
||||
}
|
||||
new_args[ac] = NULL;
|
||||
return cbm_type_template(a, t->data.template_type.template_name, new_args, ac);
|
||||
}
|
||||
default:
|
||||
// BUILTIN, INTERFACE, STRUCT, LITERAL, TYPEOF_QUERY, OBJECT_LIT, INFER, MAPPED,
|
||||
// ALIAS — no in-place substitution needed at v1 (or stub-only).
|
||||
return t;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
#ifndef CBM_LSP_TYPE_REP_H
|
||||
#define CBM_LSP_TYPE_REP_H
|
||||
|
||||
#include "../arena.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// CBMTypeKind enumerates all type representations.
|
||||
typedef enum {
|
||||
CBM_TYPE_UNKNOWN = 0,
|
||||
CBM_TYPE_NAMED, // named type: "Database", "http.Request"
|
||||
CBM_TYPE_POINTER, // *T
|
||||
CBM_TYPE_SLICE, // []T
|
||||
CBM_TYPE_MAP, // map[K]V
|
||||
CBM_TYPE_CHANNEL, // chan T
|
||||
CBM_TYPE_FUNC, // func(params) returns
|
||||
CBM_TYPE_INTERFACE, // interface{...}
|
||||
CBM_TYPE_STRUCT, // struct{...}
|
||||
CBM_TYPE_BUILTIN, // int, string, bool, error, etc.
|
||||
CBM_TYPE_TUPLE, // multi-return (T1, T2) / TS tuple [T,U]
|
||||
CBM_TYPE_TYPE_PARAM, // generic type parameter: T, K, V
|
||||
CBM_TYPE_REFERENCE, // T& (C++ lvalue reference)
|
||||
CBM_TYPE_RVALUE_REF, // T&& (C++ rvalue reference)
|
||||
CBM_TYPE_TEMPLATE, // Parameterized type: vector<T> — stores template name + args
|
||||
CBM_TYPE_ALIAS, // Type alias: using/typedef — stores alias name + underlying type
|
||||
CBM_TYPE_UNION, // Python: A | B; TS: A | B | C — sorted-canonical list (shared)
|
||||
CBM_TYPE_LITERAL, // Python: Literal["foo", 3] — wraps a base type + literal value text
|
||||
CBM_TYPE_PROTOCOL, // Python: typing.Protocol — like INTERFACE but matched structurally
|
||||
CBM_TYPE_MODULE, // Python: import os; os is a module-typed binding
|
||||
CBM_TYPE_CALLABLE, // Python: Callable[[A, B], R] — untyped-named callable variant of FUNC
|
||||
|
||||
// --- TS-specific kinds (added in TS LSP integration) ---
|
||||
CBM_TYPE_INTERSECTION, // TS: A & B — intersection type
|
||||
CBM_TYPE_TS_LITERAL, // TS: "foo" / 42 / true literal types (tag+value layout, distinct
|
||||
// from Python's CBM_TYPE_LITERAL which uses base+literal_text)
|
||||
CBM_TYPE_INDEXED, // TS: T[K] — indexed access type
|
||||
CBM_TYPE_KEYOF, // TS: keyof T
|
||||
CBM_TYPE_TYPEOF_QUERY, // TS: typeof x in type position
|
||||
CBM_TYPE_CONDITIONAL, // TS: T extends U ? X : Y
|
||||
CBM_TYPE_OBJECT_LIT, // TS: { a: T1; b: T2 } anonymous object type
|
||||
CBM_TYPE_INFER, // TS: `infer X` placeholder inside conditional
|
||||
CBM_TYPE_MAPPED, // TS: {[K in keyof T]: ...} — v1 stub, members may be NULL
|
||||
} CBMTypeKind;
|
||||
|
||||
// Forward declaration
|
||||
typedef struct CBMType CBMType;
|
||||
|
||||
// CBMTypeParam represents a generic type parameter with optional constraint.
|
||||
typedef struct {
|
||||
const char* name; // "T", "K", "V"
|
||||
const CBMType* constraint; // interface constraint, or NULL for "any"
|
||||
} CBMTypeParam;
|
||||
|
||||
// CBMType is a tagged union representing Go types.
|
||||
struct CBMType {
|
||||
CBMTypeKind kind;
|
||||
union {
|
||||
struct { const char* qualified_name; } named; // NAMED
|
||||
struct { const CBMType* elem; } pointer; // POINTER
|
||||
struct { const CBMType* elem; } slice; // SLICE
|
||||
struct { const CBMType* key; const CBMType* value; } map; // MAP
|
||||
struct { const CBMType* elem; int direction; } channel; // CHANNEL (0=bidi, 1=send, 2=recv)
|
||||
struct {
|
||||
const char** param_names; // NULL-terminated
|
||||
const CBMType** param_types; // NULL-terminated
|
||||
const CBMType** return_types; // NULL-terminated
|
||||
} func; // FUNC
|
||||
struct {
|
||||
const char** method_names; // NULL-terminated
|
||||
const CBMType** method_sigs; // NULL-terminated (each is FUNC)
|
||||
} interface_type; // INTERFACE
|
||||
struct {
|
||||
const char** field_names; // NULL-terminated
|
||||
const CBMType** field_types; // NULL-terminated
|
||||
} struct_type; // STRUCT
|
||||
struct { const char* name; } builtin; // BUILTIN
|
||||
struct {
|
||||
const CBMType** elems; // NULL-terminated
|
||||
int count;
|
||||
} tuple; // TUPLE
|
||||
struct { const char* name; } type_param; // TYPE_PARAM
|
||||
struct { const CBMType* elem; } reference; // REFERENCE / RVALUE_REF
|
||||
struct {
|
||||
const char* template_name; // "std::vector", "std::map"
|
||||
const CBMType** template_args; // NULL-terminated
|
||||
int arg_count;
|
||||
} template_type; // TEMPLATE
|
||||
struct {
|
||||
const char* alias_qn; // "proj.ns.MyAlias"
|
||||
const CBMType* underlying; // the actual type it aliases
|
||||
} alias; // ALIAS
|
||||
struct {
|
||||
const CBMType** members; // NULL-terminated, deduplicated, sorted by kind/qn
|
||||
int count;
|
||||
} union_type; // UNION / INTERSECTION (shared)
|
||||
struct {
|
||||
const CBMType* base; // base type (e.g. BUILTIN("int"), BUILTIN("str"))
|
||||
const char* literal_text; // canonical text: "3", "\"foo\"", "True"
|
||||
} literal; // LITERAL (Python)
|
||||
struct {
|
||||
const char* qualified_name; // e.g. "typing.Iterable"
|
||||
const char** method_names; // NULL-terminated method names — structural matching
|
||||
const CBMType** method_sigs; // NULL-terminated signatures (each is FUNC/CALLABLE)
|
||||
} protocol; // PROTOCOL
|
||||
struct {
|
||||
const char* module_qn; // module qualified name (matches CBMImport.module_path)
|
||||
} module; // MODULE
|
||||
struct {
|
||||
const CBMType** param_types; // NULL-terminated; NULL element means "Any" / unknown
|
||||
const CBMType* return_type; // single return; for tuples wrap in CBM_TYPE_TUPLE
|
||||
int param_count; // -1 = elliptic / Callable[..., R]
|
||||
} callable; // CALLABLE
|
||||
|
||||
// --- TS-specific data ---
|
||||
struct {
|
||||
// Tag distinguishes string / number / boolean / bigint / null / undefined literals.
|
||||
// For boolean literals, value points to "true" or "false".
|
||||
const char* tag; // "string" | "number" | "boolean" | "bigint" | "null" | "undefined"
|
||||
const char* value; // textual representation; arena-owned
|
||||
} literal_ts; // TS_LITERAL
|
||||
struct {
|
||||
const CBMType* object; // T in T[K]
|
||||
const CBMType* index; // K in T[K]
|
||||
} indexed; // INDEXED
|
||||
struct { const CBMType* operand; } keyof; // KEYOF
|
||||
struct { const char* expr; } typeof_query; // TYPEOF_QUERY (referenced expression text)
|
||||
struct {
|
||||
const CBMType* check; // T
|
||||
const CBMType* extends; // U
|
||||
const CBMType* true_branch; // X
|
||||
const CBMType* false_branch; // Y
|
||||
} conditional; // CONDITIONAL
|
||||
struct {
|
||||
const char** prop_names; // NULL-terminated
|
||||
const CBMType** prop_types; // NULL-terminated, parallel to prop_names
|
||||
const CBMType* call_signature; // FUNC type or NULL
|
||||
const CBMType* index_value; // type produced by string/number index, or NULL
|
||||
} object_lit; // OBJECT_LIT
|
||||
struct { const char* name; } infer; // INFER (e.g., `infer R`)
|
||||
struct {
|
||||
const char* key_name; // "K" in {[K in keyof T]: V}
|
||||
const CBMType* key_constraint; // `keyof T`
|
||||
const CBMType* value; // V (may reference key_name as TYPE_PARAM)
|
||||
} mapped; // MAPPED (v1 stub-friendly)
|
||||
} data;
|
||||
};
|
||||
|
||||
// Constructors (arena-allocated)
|
||||
const CBMType* cbm_type_unknown(void);
|
||||
const CBMType* cbm_type_named(CBMArena* a, const char* qualified_name);
|
||||
const CBMType* cbm_type_pointer(CBMArena* a, const CBMType* elem);
|
||||
const CBMType* cbm_type_slice(CBMArena* a, const CBMType* elem);
|
||||
const CBMType* cbm_type_map(CBMArena* a, const CBMType* key, const CBMType* value);
|
||||
const CBMType* cbm_type_channel(CBMArena* a, const CBMType* elem, int direction);
|
||||
const CBMType* cbm_type_func(CBMArena* a, const char** param_names, const CBMType** param_types, const CBMType** return_types);
|
||||
const CBMType* cbm_type_builtin(CBMArena* a, const char* name);
|
||||
const CBMType* cbm_type_tuple(CBMArena* a, const CBMType** elems, int count);
|
||||
const CBMType* cbm_type_type_param(CBMArena* a, const char* name);
|
||||
const CBMType* cbm_type_reference(CBMArena* a, const CBMType* elem);
|
||||
const CBMType* cbm_type_rvalue_ref(CBMArena* a, const CBMType* elem);
|
||||
const CBMType* cbm_type_template(CBMArena* a, const char* name, const CBMType** args, int arg_count);
|
||||
const CBMType* cbm_type_alias(CBMArena* a, const char* alias_qn, const CBMType* underlying);
|
||||
|
||||
// Python-flavored constructors. UNION normalizes input: nested unions are
|
||||
// flattened, duplicates removed, single-member unions collapse to that
|
||||
// member, and the empty union is UNKNOWN. Members must be arena-allocated.
|
||||
// Shared with TS LSP — both call this same constructor for `A | B`.
|
||||
const CBMType* cbm_type_union(CBMArena* a, const CBMType** members, int count);
|
||||
const CBMType* cbm_type_optional(CBMArena* a, const CBMType* t); // Optional[T] == Union[T, None]
|
||||
const CBMType* cbm_type_literal(CBMArena* a, const CBMType* base, const char* literal_text);
|
||||
const CBMType* cbm_type_protocol(CBMArena* a, const char* qualified_name,
|
||||
const char** method_names, const CBMType** method_sigs);
|
||||
const CBMType* cbm_type_module(CBMArena* a, const char* module_qn);
|
||||
const CBMType* cbm_type_callable(CBMArena* a, const CBMType** param_types, int param_count,
|
||||
const CBMType* return_type);
|
||||
|
||||
// --- TS-specific constructors ---
|
||||
const CBMType* cbm_type_intersection(CBMArena* a, const CBMType** members, int count);
|
||||
// tag is one of "string"|"number"|"boolean"|"bigint"|"null"|"undefined".
|
||||
// Distinct from cbm_type_literal (Python) which uses base+literal_text.
|
||||
const CBMType* cbm_type_ts_literal(CBMArena* a, const char* tag, const char* value);
|
||||
const CBMType* cbm_type_indexed(CBMArena* a, const CBMType* object, const CBMType* index);
|
||||
const CBMType* cbm_type_keyof(CBMArena* a, const CBMType* operand);
|
||||
const CBMType* cbm_type_typeof_query(CBMArena* a, const char* expr);
|
||||
const CBMType* cbm_type_conditional(CBMArena* a,
|
||||
const CBMType* check, const CBMType* extends,
|
||||
const CBMType* true_branch, const CBMType* false_branch);
|
||||
// prop_names and prop_types are NULL-terminated parallel arrays; either may be NULL for empty.
|
||||
const CBMType* cbm_type_object_lit(CBMArena* a,
|
||||
const char** prop_names, const CBMType** prop_types,
|
||||
const CBMType* call_signature, const CBMType* index_value);
|
||||
const CBMType* cbm_type_infer(CBMArena* a, const char* name);
|
||||
const CBMType* cbm_type_mapped(CBMArena* a,
|
||||
const char* key_name, const CBMType* key_constraint, const CBMType* value);
|
||||
|
||||
// Operations
|
||||
const CBMType* cbm_type_deref(const CBMType* t); // remove one pointer level
|
||||
const CBMType* cbm_type_elem(const CBMType* t); // get element type (slice/chan/pointer)
|
||||
bool cbm_type_is_unknown(const CBMType* t);
|
||||
bool cbm_type_is_interface(const CBMType* t);
|
||||
bool cbm_type_is_pointer(const CBMType* t);
|
||||
bool cbm_type_is_reference(const CBMType* t);
|
||||
bool cbm_type_is_union(const CBMType* t);
|
||||
bool cbm_type_is_protocol(const CBMType* t);
|
||||
bool cbm_type_is_module(const CBMType* t);
|
||||
|
||||
// Structural equality on type representation (used by union dedup and
|
||||
// protocol-method-set matching). Two types are equal if their kinds match
|
||||
// and their structural members match recursively.
|
||||
bool cbm_type_equal(const CBMType* a, const CBMType* b);
|
||||
|
||||
// Test whether `candidate` satisfies the structural protocol `proto`.
|
||||
// Walks proto.method_names against candidate's method set (NAMED → registry
|
||||
// lookup is the caller's job; this helper only matches existing method
|
||||
// signatures stored on a PROTOCOL).
|
||||
bool cbm_type_protocol_satisfied_by(const CBMType* proto, const CBMType* candidate);
|
||||
|
||||
// Follow alias chain with cycle detection (max 16 levels).
|
||||
const CBMType* cbm_type_resolve_alias(const CBMType* t);
|
||||
|
||||
// Generic type substitution: replace type params in t with concrete types.
|
||||
// type_params: NULL-terminated array of param names
|
||||
// type_args: corresponding concrete types
|
||||
const CBMType* cbm_type_substitute(CBMArena* a, const CBMType* t,
|
||||
const char** type_params, const CBMType** type_args);
|
||||
|
||||
#endif // CBM_LSP_TYPE_REP_H
|
||||
Reference in New Issue
Block a user