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

189 lines
8.6 KiB
C

#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