Merge Java hybrid LSP

Integrates the type-aware Java semantic resolver from
worktree-adding-java-lsp-support (based on 673ac4e): java_lsp.c + generated
java.lang/util stdlib registry, wired into the per-file LSP pass, with 94
java_lsp tests + ~170 java_lsp_coverage tests.

Wiring conflicts (the shared files diverged 146 commits on main) resolved by
keeping main's existing LSP registrations (go/c/php/py/ts/cs) and adding the
Java entries in lsp_all.c, cbm.c (cbm_run_java_lsp dispatch), test_main.c and
Makefile.cbm.

Adapted to the current unity-build: renamed java_lsp.c's static helpers
node_text/return_type_of to java_node_text/java_return_type_of so they no
longer collide with ts_lsp.c inside the shared lsp_all.c translation unit.

4082 tests pass (incl. java_lsp + java_lsp_coverage); ASan/UBSan clean.
This commit is contained in:
Martin Vogel
2026-06-02 19:06:02 +02:00
9 changed files with 9967 additions and 1 deletions
+2 -1
View File
@@ -333,6 +333,7 @@ TEST_PY_LSP_STRESS_SRCS = tests/test_py_lsp_stress.c
TEST_PY_LSP_SCALE_SRCS = tests/test_py_lsp_scale.c
TEST_TS_LSP_SRCS = tests/test_ts_lsp.c
TEST_JAVA_LSP_SRCS = tests/test_java_lsp.c tests/test_java_lsp_coverage.c
TEST_INTEGRATION_SRCS = tests/test_integration.c tests/test_incremental.c
@@ -353,7 +354,7 @@ TEST_SIMHASH_SRCS = tests/test_simhash.c
TEST_STACK_OVERFLOW_SRCS = tests/test_stack_overflow.c
ALL_TEST_SRCS = $(TEST_FOUNDATION_SRCS) $(TEST_EXTRACTION_SRCS) $(TEST_STORE_SRCS) $(TEST_CYPHER_SRCS) $(TEST_MCP_SRCS) $(TEST_DISCOVER_SRCS) $(TEST_GRAPH_BUFFER_SRCS) $(TEST_PIPELINE_SRCS) $(TEST_WATCHER_SRCS) $(TEST_LZ4_SRCS) $(TEST_ZSTD_SRCS) $(TEST_ARTIFACT_SRCS) $(TEST_SQLITE_WRITER_SRCS) $(TEST_GO_LSP_SRCS) $(TEST_C_LSP_SRCS) $(TEST_PHP_LSP_SRCS) $(TEST_CS_LSP_SRCS) $(TEST_CS_LSP_BENCH_SRCS) $(TEST_SCOPE_SRCS) $(TEST_TYPE_REP_SRCS) $(TEST_PY_LSP_SRCS) $(TEST_PY_LSP_BENCH_SRCS) $(TEST_PY_LSP_STRESS_SRCS) $(TEST_PY_LSP_SCALE_SRCS) $(TEST_TS_LSP_SRCS) $(TEST_TRACES_SRCS) $(TEST_CLI_SRCS) $(TEST_MEM_SRCS) $(TEST_UI_SRCS) $(TEST_SECURITY_SRCS) $(TEST_YAML_SRCS) $(TEST_SIMHASH_SRCS) $(TEST_STACK_OVERFLOW_SRCS) $(TEST_INTEGRATION_SRCS)
ALL_TEST_SRCS = $(TEST_FOUNDATION_SRCS) $(TEST_EXTRACTION_SRCS) $(TEST_STORE_SRCS) $(TEST_CYPHER_SRCS) $(TEST_MCP_SRCS) $(TEST_DISCOVER_SRCS) $(TEST_GRAPH_BUFFER_SRCS) $(TEST_PIPELINE_SRCS) $(TEST_WATCHER_SRCS) $(TEST_LZ4_SRCS) $(TEST_ZSTD_SRCS) $(TEST_ARTIFACT_SRCS) $(TEST_SQLITE_WRITER_SRCS) $(TEST_GO_LSP_SRCS) $(TEST_C_LSP_SRCS) $(TEST_PHP_LSP_SRCS) $(TEST_CS_LSP_SRCS) $(TEST_CS_LSP_BENCH_SRCS) $(TEST_SCOPE_SRCS) $(TEST_TYPE_REP_SRCS) $(TEST_PY_LSP_SRCS) $(TEST_PY_LSP_BENCH_SRCS) $(TEST_PY_LSP_STRESS_SRCS) $(TEST_PY_LSP_SCALE_SRCS) $(TEST_TS_LSP_SRCS) $(TEST_JAVA_LSP_SRCS) $(TEST_TRACES_SRCS) $(TEST_CLI_SRCS) $(TEST_MEM_SRCS) $(TEST_UI_SRCS) $(TEST_SECURITY_SRCS) $(TEST_YAML_SRCS) $(TEST_SIMHASH_SRCS) $(TEST_STACK_OVERFLOW_SRCS) $(TEST_INTEGRATION_SRCS)
# ── Build directories ────────────────────────────────────────────
+4
View File
@@ -9,6 +9,7 @@
#include "lsp/py_lsp.h"
#include "lsp/ts_lsp.h"
#include "lsp/cs_lsp.h"
#include "lsp/java_lsp.h"
#include "preprocessor.h"
#include "foundation/compat.h"
#include "tree_sitter/api.h" // TSParser, TSNode, TSTree, TSInput, TSLanguage, TSPoint, TSParseOptions, TSParseState
@@ -503,6 +504,9 @@ CBMFileResult *cbm_extract_file(const char *source, int source_len, CBMLanguage
cbm_run_cs_lsp(a, result, source, source_len, root);
}
}
if (language == CBM_LANG_JAVA) {
cbm_run_java_lsp(a, result, source, source_len, root);
}
atomic_fetch_add(&total_lsp_ns, now_ns() - lsp_start);
// Calls extracted so far all carry ORIGINAL-source line numbers; the C/C++
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+167
View File
@@ -0,0 +1,167 @@
#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;
/* 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 */
+2
View File
@@ -17,3 +17,5 @@
#include "lsp/ts_lsp.c"
#include "lsp/cs_lsp.c"
#include "lsp/generated/cs_stdlib_data.c"
#include "lsp/java_lsp.c"
#include "lsp/generated/java_stdlib_data.c"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+4
View File
@@ -52,6 +52,8 @@ extern void suite_py_lsp_bench(void);
extern void suite_py_lsp_stress(void);
extern void suite_py_lsp_scale(void);
extern void suite_ts_lsp(void);
extern void suite_java_lsp(void);
extern void suite_java_lsp_coverage(void);
extern void suite_store_arch(void);
extern void suite_store_bulk(void);
extern void suite_store_pragmas(void);
@@ -141,6 +143,8 @@ int main(void) {
RUN_SUITE(py_lsp_stress);
RUN_SUITE(py_lsp_scale);
RUN_SUITE(ts_lsp);
RUN_SUITE(java_lsp);
RUN_SUITE(java_lsp_coverage);
/* Architecture + ADR + Louvain */
RUN_SUITE(store_arch);