mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-10 09:13:53 +08:00
test(python): a top-level __go(locals()) keeps the package namespace
sqlalchemy's sql/__init__.py ends with __go(locals()). The callee is a same-module function that never touches its parameter or the module's globals; only the submodules its body imports become package attributes. The extraction test pins the exact shape (the namespace survives, the imported submodules are shadowed at the call) and its controls: the body uses the parameter, global (also nested), globals(), sys.modules[__name__], the own package name, a same-module helper; an imported, rebound or not yet defined callee; an argument with an effect; a module-level locals. The pipeline fixture adds the same shape end to end, with a re-export the body's submodule import replaces and a __go that writes its parameter. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
@@ -12408,6 +12408,116 @@ TEST(python_namespace_facts_string_statements_and_future_imports) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* A top-level call of a same-module function is a namespace effect, unless the
|
||||
* callee provably cannot rebind the caller's names: sqlalchemy's
|
||||
* `sql/__init__.py` ends with `__go(locals())`. Every submodule its body
|
||||
* imports becomes a package attribute, so those names are shadowed at the
|
||||
* call; everything else of the namespace survives. */
|
||||
static bool py_facts_has_effect(const CBMFileResult *r) {
|
||||
for (int i = 0; r && i < r->py_namespace.count; i++) {
|
||||
if (r->py_namespace.items[i].kind == CBM_PY_NS_UNKNOWN &&
|
||||
r->py_namespace.items[i].reason == CBM_PY_NS_REASON_EFFECT)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static int py_facts_count_kind(const CBMFileResult *r, CBMPyNamespaceFactKind kind) {
|
||||
int n = 0;
|
||||
for (int i = 0; r && i < r->py_namespace.count; i++)
|
||||
n += r->py_namespace.items[i].kind == kind;
|
||||
return n;
|
||||
}
|
||||
|
||||
static bool py_facts_valid_complete(const CBMFileResult *r) {
|
||||
return r && !r->has_error && r->py_namespace.status == CBM_PY_NS_COMPLETE &&
|
||||
cbm_py_namespace_facts_valid(&r->py_namespace);
|
||||
}
|
||||
|
||||
TEST(python_namespace_facts_init_call_allowlist) {
|
||||
static const char go[] = "from typing import TYPE_CHECKING, Any\n"
|
||||
"from .impl import base\n"
|
||||
"from .elements import cast\n"
|
||||
"def __go(lcls: Any) -> None:\n"
|
||||
" from .. import util as _sa_util\n"
|
||||
" from . import base\n"
|
||||
" from . import coercions, elements as _el\n"
|
||||
" if not TYPE_CHECKING:\n"
|
||||
" base.coercions = _el.coercions = coercions\n"
|
||||
" from .annotation import _prepare_annotations\n"
|
||||
" _prepare_annotations(cast, cast)\n"
|
||||
" _sa_util.preloaded.import_prefix(\"sqlalchemy.sql\")\n"
|
||||
"\n"
|
||||
"__go(locals())\n";
|
||||
CBMFileResult *r = extract(go, CBM_LANG_PYTHON, "t", "sql/__init__.py");
|
||||
bool ok = py_facts_valid_complete(r) && !py_facts_has_effect(r) && r->py_namespace.count == 9;
|
||||
if (ok) {
|
||||
const CBMPyNamespaceFact *f = r->py_namespace.items;
|
||||
static const char *const shadowed[] = {"base", "coercions", "elements", "annotation"};
|
||||
ok = f[2].kind == CBM_PY_NS_IMPORT_NAME && strcmp(f[2].local_name, "base") == 0 &&
|
||||
f[4].kind == CBM_PY_NS_OWN_DEF && strcmp(f[4].local_name, "__go") == 0;
|
||||
for (int i = 0; ok && i < 4; i++) {
|
||||
ok = f[5 + i].kind == CBM_PY_NS_SHADOW && f[5 + i].local_name &&
|
||||
strcmp(f[5 + i].local_name, shadowed[i]) == 0;
|
||||
}
|
||||
}
|
||||
if (!ok && r) {
|
||||
for (int i = 0; i < r->py_namespace.count; i++) {
|
||||
const CBMPyNamespaceFact *f = &r->py_namespace.items[i];
|
||||
printf(" __go fact %d: kind %d reason %d name %s\n", i, (int)f->kind, (int)f->reason,
|
||||
f->local_name ? f->local_name : "-");
|
||||
}
|
||||
}
|
||||
if (r)
|
||||
cbm_free_result(r);
|
||||
ASSERT_TRUE(ok); /* RED while every top-level call withdraws the namespace */
|
||||
|
||||
/* A plain module is no package: the body's imports set attributes of its
|
||||
* parent package, never of the module itself. */
|
||||
r = extract("def __go(lcls):\n from . import base\n\n__go(locals())\n", CBM_LANG_PYTHON,
|
||||
"t", "sql/mod.py");
|
||||
ok = py_facts_valid_complete(r) && !py_facts_has_effect(r) &&
|
||||
py_facts_count_kind(r, CBM_PY_NS_SHADOW) == 0;
|
||||
if (r)
|
||||
cbm_free_result(r);
|
||||
ASSERT_TRUE(ok);
|
||||
|
||||
/* Each of these can rebind a name of the caller's namespace (or is not
|
||||
* provably a same-module function): the call stays a whole-namespace
|
||||
* effect. */
|
||||
static const char *const effects[][2] = {
|
||||
{"param", "def __go(lcls):\n lcls['base'] = 1\n\n__go(locals())\n"},
|
||||
{"global", "def __go(lcls):\n global base\n base = 1\n\n__go(locals())\n"},
|
||||
{"nested global", "def __go(lcls):\n def inner():\n global base\n"
|
||||
" base = 1\n inner()\n\n__go(locals())\n"},
|
||||
{"globals()", "def __go(lcls):\n globals()['base'] = 1\n\n__go(locals())\n"},
|
||||
{"sys.modules", "def __go(lcls):\n import sys\n sys.modules[__name__].base = 1\n"
|
||||
"\n__go(locals())\n"},
|
||||
{"imported callee", "from .setup import __go\n__go(locals())\n"},
|
||||
{"own package name", "def __go(lcls):\n from .. import sql\n sql.base = 1\n"
|
||||
"\n__go(locals())\n"},
|
||||
{"same-module helper", "def helper():\n pass\n\ndef __go(lcls):\n helper()\n"
|
||||
"\n__go(locals())\n"},
|
||||
{"rebound callee", "def __go(lcls):\n pass\n\n__go = other\n__go(locals())\n"},
|
||||
{"call before def", "__go(locals())\n\ndef __go(lcls):\n pass\n"},
|
||||
{"argument effect", "def __go(lcls):\n pass\n\n__go(make())\n"},
|
||||
{"rebound locals", "def locals():\n return {}\n\ndef __go(lcls):\n pass\n"
|
||||
"\n__go(locals())\n"},
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(effects) / sizeof(effects[0]); i++) {
|
||||
char src[512];
|
||||
snprintf(src, sizeof(src), "from .impl import base\n%s", effects[i][1]);
|
||||
r = extract(src, CBM_LANG_PYTHON, "t", "sql/__init__.py");
|
||||
ok = py_facts_valid_complete(r) && py_facts_has_effect(r);
|
||||
if (!ok)
|
||||
printf(" control '%s' is no namespace effect\n", effects[i][0]);
|
||||
if (r)
|
||||
cbm_free_result(r);
|
||||
ASSERT_TRUE(ok);
|
||||
}
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(python_namespace_facts_distinguish_all_operations_and_unknowns) {
|
||||
static const char source[] = "__all__ = ['old']; __all__ = []\n"
|
||||
"__all__ = dynamic\n"
|
||||
@@ -12796,6 +12906,7 @@ SUITE(extraction) {
|
||||
RUN_TEST(python_namespace_facts_parse_errors_are_complete_unknown);
|
||||
RUN_TEST(python_namespace_facts_type_checking_alternatives);
|
||||
RUN_TEST(python_namespace_facts_string_statements_and_future_imports);
|
||||
RUN_TEST(python_namespace_facts_init_call_allowlist);
|
||||
RUN_TEST(python_namespace_facts_capture_and_copy_fail_atomically);
|
||||
RUN_TEST(python_namespace_facts_validation_rejects_partial_and_unknown_schema);
|
||||
RUN_TEST(python_namespace_facts_compact_spill_and_copy_keep_owned_payload);
|
||||
|
||||
+29
-1
@@ -14423,16 +14423,20 @@ static const char *const py_reexport_calls[][2] = {
|
||||
{"ctl_from_fn", "direct.impl.direct_fn"},
|
||||
{"lazy_tc", "lz.lazy_mod.Lazy"},
|
||||
{"doc_reexport", "docp.impl.Doc"},
|
||||
{"go_reexport", "gop.impl.Thing"},
|
||||
};
|
||||
enum { PY_REEXPORT_N_CALLS = sizeof(py_reexport_calls) / sizeof(py_reexport_calls[0]) };
|
||||
|
||||
/* Calls that must NOT reach their target by the LSP: an `__all__`-unlisted
|
||||
* name, a private name under a star, a re-export the module shadows later,
|
||||
* and one made before a whole-namespace barrier (a top-level `try:`). */
|
||||
* one made before a whole-namespace barrier (a top-level `try:`), a
|
||||
* re-export a submodule import of `__go` replaces, and one behind a `__go`
|
||||
* that writes its parameter. */
|
||||
static const char *const py_reexport_excluded[][2] = {
|
||||
{"excl_unlisted", "pkg.all_mod.Unlisted"}, {"excl_private", "pkg.star_mod._private_fn"},
|
||||
{"excl_shadowed", "shad.impl.Gone"}, {"excl_barrier", "barr.impl.Before"},
|
||||
{"excl_compat", "cmp.types_only.Shim"}, {"excl_fstring", "fsp.impl.Kept"},
|
||||
{"excl_go_submodule", "gop.impl.base"}, {"excl_go_param", "gox.impl.Kept"},
|
||||
};
|
||||
enum { PY_REEXPORT_N_EXCLUDED = sizeof(py_reexport_excluded) / sizeof(py_reexport_excluded[0]) };
|
||||
|
||||
@@ -14505,6 +14509,25 @@ static int py_reexport_run(bool parallel, bool incremental, bool *lsp, bool *exc
|
||||
"from .impl import Kept\n"
|
||||
"f\"{Kept}\"\n");
|
||||
write_temp_file(tmp, "app/fsp/impl.py", "class Kept:\n pass\n");
|
||||
/* sqlalchemy's sql/__init__.py shape: a top-level `__go(locals())`. */
|
||||
write_temp_file(tmp, "app/gop/__init__.py",
|
||||
"from typing import Any\n"
|
||||
"from .impl import Thing\n"
|
||||
"from .impl import base\n\n\n"
|
||||
"def __go(lcls: Any) -> None:\n"
|
||||
" from . import base\n"
|
||||
" from .annotation import prepare\n"
|
||||
" prepare(Thing)\n\n\n"
|
||||
"__go(locals())\n");
|
||||
write_temp_file(tmp, "app/gop/impl.py",
|
||||
"class Thing:\n pass\n\n\ndef base():\n return 1\n");
|
||||
write_temp_file(tmp, "app/gop/base.py", "def helper():\n return 1\n");
|
||||
write_temp_file(tmp, "app/gop/annotation.py", "def prepare(cls):\n return cls\n");
|
||||
write_temp_file(tmp, "app/gox/__init__.py",
|
||||
"from .impl import Kept\n\n\n"
|
||||
"def __go(lcls):\n lcls[\"Kept\"] = None\n\n\n"
|
||||
"__go(locals())\n");
|
||||
write_temp_file(tmp, "app/gox/impl.py", "class Kept:\n pass\n");
|
||||
write_temp_file(tmp, "app/main.py",
|
||||
"from app import pkg\n"
|
||||
"from app import cyc\n"
|
||||
@@ -14514,6 +14537,8 @@ static int py_reexport_run(bool parallel, bool incremental, bool *lsp, bool *exc
|
||||
"from app import cmp\n"
|
||||
"from app import docp\n"
|
||||
"from app import fsp\n"
|
||||
"from app import gop\n"
|
||||
"from app import gox\n"
|
||||
"from app.db import models\n"
|
||||
"from app.pkg import Explicit, Listed, Deep, star_fn, Renamed\n"
|
||||
"from app.direct.impl import Direct, direct_fn\n\n\n"
|
||||
@@ -14541,6 +14566,9 @@ static int py_reexport_run(bool parallel, bool incremental, bool *lsp, bool *exc
|
||||
"def lazy_tc():\n lz.Lazy()\n\n\n"
|
||||
"def doc_reexport():\n docp.Doc()\n\n\n"
|
||||
"def excl_fstring():\n fsp.Kept()\n\n\n"
|
||||
"def go_reexport():\n gop.Thing()\n\n\n"
|
||||
"def excl_go_submodule():\n gop.base()\n\n\n"
|
||||
"def excl_go_param():\n gox.Kept()\n\n\n"
|
||||
"def ctl_from_cls():\n Direct()\n\n\n"
|
||||
"def ctl_from_fn():\n direct_fn()\n");
|
||||
for (int i = 0; parallel && i < 52; i++) {
|
||||
|
||||
Reference in New Issue
Block a user