Files
wehub-resource-sync 41cb1c0170
OpenSSF Scorecard / scorecard (push) Failing after 0s
DCO / dco (push) Failing after 0s
CodeQL SAST / analyze (push) Failing after 1s
Deploy Pages / deploy (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:28:05 +08:00

184 lines
8.8 KiB
C

/*
* 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 */