mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
fix(python): bind a nested class base to an earlier sibling of its class body
Python evaluates a class statement's bases in the body that encloses it, so a bare base of a class nested in a class names an earlier sibling of that body (sqlalchemy's asyncpg adapter: class IntegrityError(DatabaseError)) before any same-named module or imported class. The base resolvers ran the registry's name strategies instead, which prefer an import-reachable candidate, so the sibling lost as soon as the adapter's exc import resolved. cbm_registry_resolve_python_class_body_base looks the sibling up exactly (<enclosing class>.<base>, both type-like) and requires it to be defined earlier in the same file, from the graph-buffer node lines: a later sibling is not bound yet when the base is evaluated. The sequential and incremental semantic pass, the parallel resolver and the cross-LSP base resolver ask it first; a sibling needs no external-base gate. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
1 parent
9054309fee
commit
79cb4f7227
5 files changed
+87
-20
No files matched your search
@@ -305,10 +305,16 @@ static const char *pxc_python_dotted_base_qn(const pxc_base_scope_t *bs, const c
|
||||
* Returns NULL when the base is not a confidently-known project type;
|
||||
* stdlib and third-party bases land here and keep their raw spelling. */
|
||||
static const char *pxc_resolve_base_qn(const pxc_base_scope_t *bs, const char *raw,
|
||||
const char *module_qn) {
|
||||
const char *module_qn, const char *class_qn) {
|
||||
if (!bs || !bs->reg || !raw || !raw[0]) {
|
||||
return NULL;
|
||||
}
|
||||
/* A base of a class nested in a class body: an earlier sibling first. */
|
||||
const char *sibling =
|
||||
cbm_registry_resolve_python_class_body_base(bs->reg, bs->gbuf, class_qn, raw, bs->lang);
|
||||
if (sibling) {
|
||||
return sibling;
|
||||
}
|
||||
const char *dotted = pxc_python_dotted_base_qn(bs, raw);
|
||||
if (dotted) {
|
||||
return dotted;
|
||||
@@ -338,7 +344,8 @@ static const char *pxc_resolve_base_qn(const pxc_base_scope_t *bs, const char *r
|
||||
* source spelling. Unresolved entries pass through verbatim so a base the
|
||||
* registry does not know keeps working exactly as before. */
|
||||
static const char *pxc_join_base_qns(CBMArena *arena, const char *const *bases,
|
||||
const pxc_base_scope_t *bs, const char *module_qn) {
|
||||
const pxc_base_scope_t *bs, const char *module_qn,
|
||||
const char *class_qn) {
|
||||
if (!bases || !bases[0]) {
|
||||
return NULL;
|
||||
}
|
||||
@@ -352,7 +359,7 @@ static const char *pxc_join_base_qns(CBMArena *arena, const char *const *bases,
|
||||
return pxc_join_pipe(arena, bases);
|
||||
}
|
||||
for (int i = 0; i < count; i++) {
|
||||
const char *qn = pxc_resolve_base_qn(bs, bases[i], module_qn);
|
||||
const char *qn = pxc_resolve_base_qn(bs, bases[i], module_qn, class_qn);
|
||||
resolved[i] = qn ? qn : bases[i];
|
||||
}
|
||||
resolved[count] = NULL;
|
||||
@@ -520,9 +527,10 @@ static int pxc_build_lsp_def(CBMArena *arena, const CBMDefinition *src, const ch
|
||||
/* Languages whose cross registrars read embedded_types as QNs get their
|
||||
* bases resolved against the project registry; everyone else keeps the
|
||||
* raw source spelling their own registrar already knows how to handle. */
|
||||
dst->embedded_types = (bs && bs->reg && pxc_lang_resolves_base_qns(lang))
|
||||
? pxc_join_base_qns(arena, src->base_classes, bs, module_qn)
|
||||
: pxc_join_pipe(arena, src->base_classes);
|
||||
dst->embedded_types =
|
||||
(bs && bs->reg && pxc_lang_resolves_base_qns(lang))
|
||||
? pxc_join_base_qns(arena, src->base_classes, bs, module_qn, src->qualified_name)
|
||||
: pxc_join_pipe(arena, src->base_classes);
|
||||
dst->signature_param_types = NULL;
|
||||
dst->signature_param_count = 0;
|
||||
if (src->signature_param_types && src->signature_param_count > 0) {
|
||||
@@ -727,7 +735,7 @@ static void pxc_fold_py_field_types(CBMArena *arena, const CBMFileResult *result
|
||||
fields[f].type_qn = NULL;
|
||||
CBMLSPDef *owner = ft->class_qn ? (CBMLSPDef *)cbm_ht_get(by_qn, ft->class_qn) : NULL;
|
||||
const char *name = owner ? pxc_py_annotation_type_name(arena, ft->type_text) : NULL;
|
||||
const char *qn = name ? pxc_resolve_base_qn(bs, name, module_qn) : NULL;
|
||||
const char *qn = name ? pxc_resolve_base_qn(bs, name, module_qn, NULL) : NULL;
|
||||
if (!qn || !ft->field_name || !ft->field_name[0]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -3863,13 +3863,17 @@ static void resolve_def_inherits(resolve_ctx_t *rc, resolve_worker_state_t *ws,
|
||||
if (cbm_pipeline_php_vendor_bound(php_vendor, def->base_classes[b])) {
|
||||
continue;
|
||||
}
|
||||
const char *bqn =
|
||||
resolve_as_class(rc->registry, def->base_classes[b], mq, ik, iv, ic, fs->lang);
|
||||
/* Same external-base gate as the sequential semantic pass. */
|
||||
if (!bqn ||
|
||||
cbm_python_external_base_contradicts(fs->lang, fs->imports, def->base_classes[b], bqn,
|
||||
rc->main_gbuf, rc->project_name, fs->rel)) {
|
||||
continue;
|
||||
/* Same order as the sequential semantic pass: an earlier sibling of
|
||||
* the class body first (no external-base gate), then the registry. */
|
||||
const char *bqn = cbm_registry_resolve_python_class_body_base(
|
||||
rc->registry, rc->main_gbuf, def->qualified_name, def->base_classes[b], fs->lang);
|
||||
if (!bqn) {
|
||||
bqn = resolve_as_class(rc->registry, def->base_classes[b], mq, ik, iv, ic, fs->lang);
|
||||
if (!bqn || cbm_python_external_base_contradicts(
|
||||
fs->lang, fs->imports, def->base_classes[b], bqn, rc->main_gbuf,
|
||||
rc->project_name, fs->rel)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const cbm_gbuf_node_t *bn = cbm_gbuf_find_by_qn(rc->main_gbuf, bqn);
|
||||
if (bn && node->id != bn->id) {
|
||||
|
||||
@@ -643,12 +643,18 @@ static void sem_process_def_edges(cbm_pipeline_ctx_t *ctx, const CBMDefinition *
|
||||
if (cbm_pipeline_php_vendor_bound(php_vendor, def->base_classes[b])) {
|
||||
continue;
|
||||
}
|
||||
const char *base_qn = resolve_as_class(ctx->registry, def->base_classes[b], module_qn,
|
||||
imp_keys, imp_vals, imp_count, lang);
|
||||
if (!base_qn || cbm_python_external_base_contradicts(
|
||||
fi->language, &result->imports, def->base_classes[b], base_qn,
|
||||
ctx->gbuf, ctx->project_name, fi->rel_path)) {
|
||||
continue; /* unresolved, or an external base (`unittest.TestCase`) */
|
||||
/* An earlier sibling of the class body shadows every module or
|
||||
* imported name, so it needs no external-base gate. */
|
||||
const char *base_qn = cbm_registry_resolve_python_class_body_base(
|
||||
ctx->registry, ctx->gbuf, def->qualified_name, def->base_classes[b], lang);
|
||||
if (!base_qn) {
|
||||
base_qn = resolve_as_class(ctx->registry, def->base_classes[b], module_qn, imp_keys,
|
||||
imp_vals, imp_count, lang);
|
||||
if (!base_qn || cbm_python_external_base_contradicts(
|
||||
fi->language, &result->imports, def->base_classes[b], base_qn,
|
||||
ctx->gbuf, ctx->project_name, fi->rel_path)) {
|
||||
continue; /* unresolved, or an external base (`unittest.TestCase`) */
|
||||
}
|
||||
}
|
||||
const cbm_gbuf_node_t *base_node = cbm_gbuf_find_by_qn(ctx->gbuf, base_qn);
|
||||
if (base_node && node->id != base_node->id) {
|
||||
|
||||
@@ -270,6 +270,14 @@ cbm_resolution_t cbm_registry_resolve_call_scoped(const cbm_registry_t *r, const
|
||||
const char **import_map_vals,
|
||||
int import_map_count, CBMLanguage lang,
|
||||
const char *scope_qn);
|
||||
/* Python: the bare base `base` of the class `class_qn` nested in a class body
|
||||
* binds the sibling class of that body (`<enclosing class>.<base>`) when the
|
||||
* sibling is defined earlier in the same file (Python evaluates the bases in
|
||||
* the class body). Exact lookups only; the registry-owned sibling QN, or NULL
|
||||
* to resolve the base as before. Every base-class resolver asks this first. */
|
||||
const char *cbm_registry_resolve_python_class_body_base(const cbm_registry_t *r,
|
||||
const cbm_gbuf_t *gb, const char *class_qn,
|
||||
const char *base, CBMLanguage lang);
|
||||
#ifdef CBM_ENABLE_TEST_SEAMS
|
||||
void cbm_pxc_test_import_probe_fail_after(int successful_allocations);
|
||||
#endif
|
||||
|
||||
@@ -1383,6 +1383,47 @@ const char *cbm_registry_value_target(const cbm_registry_t *r, const char *qn) {
|
||||
return owned ? owned : qn;
|
||||
}
|
||||
|
||||
/* Python evaluates a class statement's bases in the namespace of the body
|
||||
* that encloses it. For a class nested in a class, a bare base naming a class
|
||||
* defined EARLIER in that same class body is that sibling
|
||||
* (`class IntegrityError(DatabaseError)` in sqlalchemy's asyncpg adapter),
|
||||
* whatever same-named classes the project has elsewhere. A sibling defined
|
||||
* later is not bound yet when the base is evaluated. Exact lookups only. */
|
||||
const char *cbm_registry_resolve_python_class_body_base(const cbm_registry_t *r,
|
||||
const cbm_gbuf_t *gb, const char *class_qn,
|
||||
const char *base, CBMLanguage lang) {
|
||||
if (lang != CBM_LANG_PYTHON || !r || !gb || !class_qn || !base || !base[0] ||
|
||||
strchr(base, '.')) {
|
||||
return NULL;
|
||||
}
|
||||
const char *dot = strrchr(class_qn, '.');
|
||||
if (!dot || dot == class_qn) {
|
||||
return NULL;
|
||||
}
|
||||
char sibling[CBM_SZ_1K];
|
||||
int enclosing_len = (int)(dot - class_qn);
|
||||
int n = snprintf(sibling, sizeof(sibling), "%.*s", enclosing_len, class_qn);
|
||||
if (n < 0 || (size_t)n >= sizeof(sibling) ||
|
||||
!cbm_label_is_type_like(cbm_registry_label_of(r, sibling))) {
|
||||
return NULL; /* not nested in a class body (module level, or a def) */
|
||||
}
|
||||
n = snprintf(sibling, sizeof(sibling), "%.*s.%s", enclosing_len, class_qn, base);
|
||||
if (n < 0 || (size_t)n >= sizeof(sibling) || strcmp(sibling, class_qn) == 0) {
|
||||
return NULL;
|
||||
}
|
||||
const char *owned = cbm_ht_get_key(r->exact, sibling);
|
||||
if (!owned || !cbm_label_is_type_like(cbm_registry_label_of(r, owned))) {
|
||||
return NULL;
|
||||
}
|
||||
const cbm_gbuf_node_t *cls = cbm_gbuf_find_by_qn(gb, class_qn);
|
||||
const cbm_gbuf_node_t *sib = cbm_gbuf_find_by_qn(gb, owned);
|
||||
if (!cls || !sib || !cls->file_path || !sib->file_path ||
|
||||
strcmp(cls->file_path, sib->file_path) != 0 || sib->start_line >= cls->start_line) {
|
||||
return NULL;
|
||||
}
|
||||
return owned;
|
||||
}
|
||||
|
||||
int cbm_registry_find_by_name(const cbm_registry_t *r, const char *name, const char ***out,
|
||||
int *count) {
|
||||
if (!r || !out || !count) {
|
||||
|
||||
Reference in new issue
Block a user