Files
codebase-memory-mcp/internal/cbm/lsp/scope.c
Andrew Hundt df8dd1011b internal/cbm/lsp/scope.*: inline complete binding lookup
Previous behavior: cbm_scope_lookup(), cbm_scope_contains(), and cbm_scope_lookup_callable() called an out-of-line complete-binding scan after the Python lookup consolidation.

What changed:
- internal/cbm/lsp/scope.h defines cbm_scope_lookup_binding() as the one static-inline scope-chain scan.
- internal/cbm/lsp/scope.c keeps the type, presence, and callable wrappers on that shared implementation without another function boundary.

Why: Python still reads type and callable identity in one traversal, while existing language resolvers retain the direct hot path used before the consolidation.

Verification:
- CBM_ONLY_SUITE=scope build/c/test-runner: 33 passed
- CBM_ONLY_SUITE=cs_lsp build/c/test-runner: 62 passed
- CBM_ONLY_SUITE=py_lsp build/c/test-runner: 125 passed
- neutral 11-pair extraction matrix: output parity; C# latency -2.97% vs first parent and -1.64% vs upstream; retired instructions +0.29% vs first parent and -1.01% vs upstream; Python scale latency -4.25% and instructions -2.81% vs first parent

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
(cherry picked from commit 150c88e1c8)
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-28 00:13:39 +02:00

123 lines
4.1 KiB
C

#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;
}
/* Returns false when the binding could NOT be recorded in THIS frame.
*
* The failure that matters is arena exhaustion in alloc_chunk: the old void
* form returned silently, so a caller that then consulted the scope CHAIN saw
* the parent's binding for the same name and concluded the child had been
* bound. For callable-value proof that is a fabricated identity -- the shadow
* never took effect, yet the parent's callable looks like the child's. Callers
* needing that distinction must use the checked form and consult the LOCAL
* result, not a chain lookup. */
static bool cbm_scope_bind_value(CBMScope *scope, const char *name, const CBMType *type,
const char *callable_qn) {
if (!scope || !name) {
return false;
}
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;
c->bindings[i].callable_qn = callable_qn;
return true;
}
}
}
CBMScopeChunk* head = scope->chunks;
if (!head || head->used >= CBM_SCOPE_CHUNK_BINDINGS) {
head = alloc_chunk(scope);
if (!head) {
return false; /* arena exhausted: the shadow did NOT take effect */
}
}
head->bindings[head->used].name = name;
head->bindings[head->used].type = type;
head->bindings[head->used].callable_qn = callable_qn;
head->used++;
return true;
}
void cbm_scope_bind(CBMScope *scope, const char *name, const CBMType *type) {
(void)cbm_scope_bind_value(scope, name, type, NULL);
}
bool cbm_scope_bind_checked(CBMScope *scope, const char *name, const CBMType *type) {
return cbm_scope_bind_value(scope, name, type, NULL);
}
void cbm_scope_bind_callable(CBMScope *scope, const char *name, const CBMType *type,
const char *callable_qn) {
(void)cbm_scope_bind_value(scope, name, type, callable_qn);
}
bool cbm_scope_bind_callable_checked(CBMScope *scope, const char *name, const CBMType *type,
const char *callable_qn) {
return cbm_scope_bind_value(scope, name, type, callable_qn);
}
const CBMType* cbm_scope_lookup(const CBMScope* scope, const char* name) {
const CBMVarBinding *binding = cbm_scope_lookup_binding(scope, name);
if (binding)
return binding->type;
return cbm_type_unknown();
}
bool cbm_scope_contains(const CBMScope *scope, const char *name) {
return cbm_scope_lookup_binding(scope, name) != NULL;
}
const char *cbm_scope_lookup_callable(const CBMScope *scope, const char *name) {
const CBMVarBinding *binding = cbm_scope_lookup_binding(scope, name);
return binding ? binding->callable_qn : NULL;
}
bool cbm_scope_update_callable(CBMScope *scope, const char *name, const char *callable_qn) {
if (!name) {
return false;
}
for (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) {
c->bindings[i].callable_qn = callable_qn;
return true;
}
}
}
}
return false;
}