mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
Round 10. Closes the last documented gaps from Round 9 plus 21 new
real-world stress tests covering SQLAlchemy / Pydantic / dataclass /
TypedDict / Protocol / generator / async patterns.
Lambda call-site inference:
- PyLSPContext gains a per-context lambda registry. `fn = lambda x:
body` records the lambda's AST node bound to fn. At call site
fn(arg), we push a sub-scope, bind lambda params from arg types,
and walk the body. Edges emitted under a synthetic <lambda> child
of the enclosing function so traces attribute correctly.
Dict-literal dispatch table:
- `funcs = {"a": foo, "b": bar}` records per-literal-key target QNs
in CBMDictLiteralEntry[]. funcs["a"]() emits an edge directly to
foo's QN with strategy=lsp_dict_dispatch.
Async with / async for:
- `async with X() as f` looks up __aenter__ before __enter__.
__aenter__'s declared return type wins; the manager type is the
fallback (async __aenter__s typically return self).
- `async for x in iter` falls back to __anext__'s return type when
the iterable's TEMPLATE form isn't recognized as AsyncIterator /
AsyncGenerator.
Callable / next / iter:
- `f()` where f resolves to CBM_TYPE_CALLABLE returns the callable's
return type. TEMPLATE("Callable", [..., R]) returns R.
- factory()() / (lambda)() etc — when the function expression is
itself a call / parenthesized / lambda, evaluate it and use its
return type.
- `next(it)` returns py_iterable_element_type(type-of-it).
- `iter(x)` is identity.
*args / **kwargs typed-parameter wrapping:
- `def f(*args: T)` -> args is TEMPLATE("tuple", [T]).
- `def f(**kwargs: T)` -> kwargs is TEMPLATE("dict", [str, T]).
- Detected by walking typed_parameter > list_splat_pattern /
dictionary_splat_pattern wrappers in py_bind_parameters.
Tuple / list / set / dict literal expressions:
- py_eval_expr_type now builds structured TUPLE / TEMPLATE types
from element types of the literal. `(Foo(), Bar())` -> TUPLE([Foo,
Bar]); `[Foo()]` -> TEMPLATE("list", [Foo]); `{"k": Foo()}` ->
TEMPLATE("dict", [str, Foo]). Without this, unpacking lost element
precision.
Match sequence pattern:
- list_pattern wraps each element in another case_pattern; capture
appears as dotted_name -> identifier; splat as splat_pattern.
Element-binder walks both wrappers and binds capture/splat to the
subject's iterable element type / list[elem].
Stress test suite expanded from 22 to 43 patterns. All 43 hard-
asserted as PASS:
- typing helpers: NamedTuple, TypedDict (incl. total=False), Protocol
(nominal + structural), ABC + abstractmethod, Mapped[T] (SQLAlchemy
2.0), Pydantic-style BaseModel
- control flow: with-as via __enter__, except-as, post-early-return
narrowing, async with via __aenter__, async for via __anext__
- containers: tuple unpack from function return + literal,
dict.items() comprehension k/v unpack, list slice, dict.get
default, recursive self-referencing types via forward refs
- decorators: factory + functools.wraps, classmethod chained to
method, property setter
- inheritance: Self in chain, diamond MRO with C3, multi-class match
- functional: lambda call-site inference, function-as-dict-value
dispatch, callable return-value, next(iter), generator delegation
(yield from), iterator next, double-star **kwargs, isinstance |
branches, module-level const, dataclass + default_factory
Bench: 178 LOC, 52 calls, **52 resolved (100%)**, 11.25 ms (ASan +
UBSan). Total 2941 / 0. Zero remaining KNOWN GAPs in the stress
suite — every probed pattern resolves.
125 lines
4.4 KiB
C
125 lines
4.4 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;
|
|
|
|
// 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;
|
|
|
|
// 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;
|
|
|
|
// 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);
|
|
|
|
// --- 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
|