Files
codebase-memory-mcp/tests/test_resolver_python.c
T
Martin Vogel 7c8b1360ac fix(python): a namespace handed on or aliased only to be read writes nothing
sqlalchemy's package inits (sqlalchemy, sqlalchemy.orm, sqlalchemy.sql) and
orm/collections.py end with `__go(locals())`. Any module-level locals() handed
to a call failed the whole file as a dynamic namespace effect, so every import
there was unaudited and every export UNSUPPORTED: 14,464 real CALLS edges of
sqlalchemy abstained as python_stabilized_export_binding, and their children
(`from sqlalchemy import event`) as python_export_member_dispatch_unproven.

Three rules, each true of Python:

- `f(locals())` / `f(vars())` at module level writes nothing when f is the
  module's one undecorated def, bound before the call, whose body never spells
  the parameter receiving the namespace, nor locals, vars, eval, exec or a
  frame's locals. The body's own effects are walked as anywhere else.
- eval/exec of one plain string literal that is a single identifier only reads
  it (pydantic/v1/utils.py `eval('__IPYTHON__')`).
- `alias = globals()` in a function writes nothing when every other spelling
  of the alias there is a membership test, an item read or a read-only method,
  and the function has no other way to reach it (sqlalchemy exc.py and
  ext/automap.py). A module-level alias stays an effect: it is a module
  attribute another file can write through.

Referee against SCIP, python, 10 repos: recall 70.5% -> 82.0%, precision
99.3% -> 99.2%. sqlalchemy 42.9% -> 67.6%, pydantic 85.4% -> 90.6%; click,
flask, poetry, django, celery, httpx, scrapy, starlette unchanged. Every new
sqlalchemy SCIP disagreement was listed and read: builtin/typing name
collisions, same-line chain mappings, and narrowed-type selections.

The pipeline gap python-reexport.go_reexport is now required: __go never
spells lcls. Its parameter-write decoy stays forbidden.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 06:21:26 +01:00

2936 lines
171 KiB
C

#include "test_framework.h"
#include "test_helpers.h"
#include "pipeline/pipeline.h"
#include "store/store.h"
#include "sqlite3.h"
#include <stdlib.h>
typedef struct {
const char *path, *text;
} python_proof_file_t;
static int python_proof_edges(const python_proof_file_t *files, size_t count, const char *target,
const char *name, const char *label) {
const char *root = th_mktempdir("cbm_python_proof");
if (!root)
return -1;
char repo[768], database[768];
snprintf(repo, sizeof(repo), "%s/repo", root);
snprintf(database, sizeof(database), "%s/index.db", root);
bool ok = cbm_mkdir_p(repo, 0755) && cbm_mkdir_p(TH_PATH(repo, "lib"), 0755);
for (size_t i = 0; ok && i < count; i++)
ok = th_write_file(TH_PATH(repo, files[i].path), files[i].text) == 0;
cbm_pipeline_t *pipeline = ok ? cbm_pipeline_new(repo, database, CBM_MODE_FULL) : NULL;
int rc = pipeline && cbm_pipeline_set_project_name(pipeline, "python-proof")
? cbm_pipeline_run(pipeline)
: -1;
cbm_pipeline_free(pipeline);
cbm_store_t *store = rc == 0 ? cbm_store_open_path_existing(database) : NULL;
sqlite3_stmt *statement = NULL;
int edges = -1;
if (store &&
sqlite3_prepare_v2(
cbm_store_get_db(store),
"SELECT count(*) FROM edges e JOIN nodes s ON s.id=e.source_id "
"JOIN nodes t ON t.id=e.target_id WHERE e.type='CALLS' AND s.file_path='main.py' "
"AND t.file_path=?1 AND t.name=?2 AND t.label=?3",
-1, &statement, NULL) == SQLITE_OK) {
sqlite3_bind_text(statement, 1, target, -1, SQLITE_STATIC);
sqlite3_bind_text(statement, 2, name, -1, SQLITE_STATIC);
sqlite3_bind_text(statement, 3, label, -1, SQLITE_STATIC);
if (sqlite3_step(statement) == SQLITE_ROW)
edges = sqlite3_column_int(statement, 0);
}
sqlite3_finalize(statement);
if (store)
cbm_store_close(store);
th_cleanup(root);
return edges;
}
TEST(resolver_python_reexport_chain_preserves_actual_declaration) {
const python_proof_file_t files[] = {
{"lib/actual.py", "def real():\n return 1\n"},
{"lib/mid.py", "from lib.actual import real as alias\n"},
{"lib/front.py", "from lib.mid import alias as endpoint\n"},
{"main.py", "from lib.front import endpoint\ndef run():\n return endpoint()\n"}};
ASSERT_EQ(python_proof_edges(files, 4, "lib/actual.py", "real", "Function"), 1);
PASS();
}
TEST(resolver_python_imported_class_is_the_static_callable) {
const python_proof_file_t files[] = {
{"lib/errors.py", "class Problem(Exception):\n pass\n"},
{"lib/front.py", "from lib.errors import Problem as Error\n"},
{"main.py", "from lib.front import Error\ndef run():\n raise Error()\n"}};
ASSERT_EQ(python_proof_edges(files, 3, "lib/errors.py", "Problem", "Class"), 1);
PASS();
}
TEST(resolver_python_reexport_shadow_and_cycle_abstain) {
const char *front[] = {
"from lib.actual import real as endpoint\nendpoint = unknown\n",
"from lib.actual import real as endpoint\nfrom missing import endpoint\n",
"from lib.mid import endpoint\n"};
for (size_t i = 0; i < sizeof(front) / sizeof(front[0]); i++) {
const python_proof_file_t files[] = {
{"lib/actual.py", "def real():\n return 1\n"},
{"lib/front.py", front[i]},
{"lib/mid.py", "from lib.front import endpoint\n"},
{"main.py", "from lib.front import endpoint\ndef run():\n return endpoint()\n"}};
ASSERT_EQ(python_proof_edges(files, 4, "lib/actual.py", "real", "Function"), 0);
}
PASS();
}
TEST(resolver_python_package_value_blocks_physical_submodule) {
const python_proof_file_t files[] = {
{"lib/__init__.py", "from .actual import real as mod\n"},
{"lib/actual.py", "def real():\n return 1\n"},
{"lib/mod.py", "def wrong():\n return 2\n"},
{"main.py", "from lib import mod\ndef run():\n return mod.wrong()\n"}};
ASSERT_EQ(python_proof_edges(files, 4, "lib/mod.py", "wrong", "Function"), 0);
PASS();
}
TEST(resolver_python_typed_receiver_preserves_crossfile_method) {
const python_proof_file_t files[] = {
{"lib/service.py", "class Service:\n def go(self):\n pass\n"},
{"lib/decoy.py", "class Service:\n def go(self):\n pass\n"},
{"main.py",
"from lib.service import Service as Svc\ndef run(value: Svc):\n value.go()\n"}};
ASSERT_EQ(python_proof_edges(files, 3, "lib/service.py", "go", "Method"), 1);
ASSERT_EQ(python_proof_edges(files, 3, "lib/decoy.py", "go", "Method"), 0);
PASS();
}
/* ── Rules recovered on real Python, forms taken from django ──────────────
* Each test states a positive and the decoy or shadow it must not confuse with
* it. Every case runs both drivers: sequential, and the parallel route that the
* filler files select. */
/* Rows of one fixture share its index, per route. A full index writes up to 57 files, and on
* Windows every one of them is scanned, so repeating them ran this suite past the 900 s cap. */
typedef struct {
char *key; /* every path and text of the fixture this index holds */
char *root;
char database[768];
int rc;
} python_route_index_t;
static python_route_index_t python_routes[2];
static void python_route_release(python_route_index_t *route) {
if (route->root)
th_cleanup(route->root);
free(route->root);
free(route->key);
memset(route, 0, sizeof(*route));
}
static char *python_fixture_key(const python_proof_file_t *files, size_t count) {
size_t bytes = 1;
for (size_t i = 0; i < count; i++)
bytes += strlen(files[i].path) + strlen(files[i].text) + 2;
char *key = malloc(bytes);
if (!key)
return NULL;
size_t at = 0;
for (size_t i = 0; i < count; i++)
at += (size_t)snprintf(key + at, bytes - at, "%s\x1f%s\x1e", files[i].path, files[i].text);
return key;
}
/* Writes and indexes the fixture for one route; the result stays until another fixture. */
static python_route_index_t *python_route_index(const python_proof_file_t *files, size_t count,
bool parallel) {
python_route_index_t *route = &python_routes[parallel ? 1 : 0];
char *key = python_fixture_key(files, count);
if (key && route->key && !strcmp(route->key, key)) {
free(key);
return route;
}
python_route_release(route);
const char *made = key ? th_mktempdir("cbm_python_rule") : NULL;
route->root = made ? strdup(made) : NULL;
if (!route->root) {
free(key);
return NULL;
}
route->key = key;
const char *root = route->root;
char repo[768];
snprintf(repo, sizeof(repo), "%s/repo", root);
snprintf(route->database, sizeof(route->database), "%s/index.db", root);
const char *database = route->database;
bool ok = cbm_mkdir_p(repo, 0755);
for (size_t i = 0; ok && i < count; i++) {
char directory[1024];
snprintf(directory, sizeof(directory), "%s/%s", repo, files[i].path);
char *slash = strrchr(directory, '/');
*slash = '\0';
ok = cbm_mkdir_p(directory, 0755) &&
th_write_file(TH_PATH(repo, files[i].path), files[i].text) == 0;
}
for (int i = 0; ok && parallel && i < 55; i++) {
char path[32];
snprintf(path, sizeof(path), "filler%02d.py", i);
ok = th_write_file(TH_PATH(repo, path), "# unrelated source\n") == 0;
}
const char *old_workers = getenv("CBM_WORKERS");
char *saved_workers = old_workers ? strdup(old_workers) : NULL;
cbm_setenv("CBM_WORKERS", parallel ? "4" : "1", 1);
cbm_pipeline_t *pipeline = ok ? cbm_pipeline_new(repo, database, CBM_MODE_FULL) : NULL;
int rc = pipeline && cbm_pipeline_set_project_name(pipeline, "python-rule")
? cbm_pipeline_run(pipeline)
: -1;
cbm_pipeline_free(pipeline);
if (saved_workers) {
cbm_setenv("CBM_WORKERS", saved_workers, 1);
free(saved_workers);
} else {
cbm_unsetenv("CBM_WORKERS");
}
route->rc = rc;
return route;
}
static int python_route_edges(const python_proof_file_t *files, size_t count, const char *source,
const char *relation, const char *target, const char *name,
const char *label, bool parallel) {
python_route_index_t *route = python_route_index(files, count, parallel);
if (!route)
return -1;
int rc = route->rc;
const char *database = route->database;
cbm_store_t *store = rc == 0 ? cbm_store_open_path_existing(database) : NULL;
sqlite3_stmt *statement = NULL;
int edges = -1;
if (store && sqlite3_prepare_v2(cbm_store_get_db(store),
"SELECT count(*) FROM edges e JOIN nodes s ON s.id=e.source_id "
"JOIN nodes t ON t.id=e.target_id WHERE e.type=?4 "
"AND s.file_path=?5 AND t.file_path=?1 AND t.name=?2 "
"AND t.label=?3",
-1, &statement, NULL) == SQLITE_OK) {
sqlite3_bind_text(statement, 1, target, -1, SQLITE_STATIC);
sqlite3_bind_text(statement, 2, name, -1, SQLITE_STATIC);
sqlite3_bind_text(statement, 3, label, -1, SQLITE_STATIC);
sqlite3_bind_text(statement, 4, relation, -1, SQLITE_STATIC);
sqlite3_bind_text(statement, 5, source, -1, SQLITE_STATIC);
if (sqlite3_step(statement) == SQLITE_ROW)
edges = sqlite3_column_int(statement, 0);
}
sqlite3_finalize(statement);
if (store)
cbm_store_close(store);
return edges;
}
/* Both drivers must agree on the expected count. */
static bool python_expect_from(const char *what, const python_proof_file_t *files, size_t count,
const char *source, const char *relation, const char *target,
const char *name, const char *label, int expected) {
for (int parallel = 0; parallel < 2; parallel++) {
int found = python_route_edges(files, count, source, relation, target, name, label,
parallel != 0);
if (found != expected) {
fprintf(stderr, "python rule [%s] parallel=%d %s:%s -> %s:%s found=%d expected=%d\n",
what, parallel, source, relation, target, name, found, expected);
return false;
}
}
return true;
}
static bool python_expect(const char *what, const python_proof_file_t *files, size_t count,
const char *relation, const char *target, const char *name,
const char *label, int expected) {
return python_expect_from(what, files, count, "main.py", relation, target, name, label,
expected);
}
#define PY_FILES(...) \
(const python_proof_file_t[]){__VA_ARGS__}, \
sizeof((const python_proof_file_t[]){__VA_ARGS__}) / sizeof(python_proof_file_t)
#define EXPECT_CALL(what, target, name, label, expected, ...) \
ASSERT(python_expect(what, PY_FILES(__VA_ARGS__), "CALLS", target, name, label, expected))
#define EXPECT_USAGE(what, target, name, label, expected, ...) \
ASSERT(python_expect(what, PY_FILES(__VA_ARGS__), "USAGE", target, name, label, expected))
TEST(resolver_python_ordinary_syntax_keeps_the_file) {
static const char *bodies[] = {
"def forward(*args, **kwargs):\n return local(*args, **kwargs)\n",
"def label(value):\n return f'{value!r:>4}'\ndef run():\n return local()\n",
"def stub(): ...\ndef run():\n return local()\n",
("counter = 0\ndef bump():\n global counter\n counter += 1\n"
"def run():\n return local()\n"),
("def outer():\n state = 0\n def inner():\n nonlocal state\n"
" state += 1\n return local()\n"),
("def run(value):\n match value:\n case [first, *rest]:\n"
" return local()\n"),
"note: dict[str, int]\ndef run():\n return local()\n",
"total = 1 + \\\n 2\ndef run():\n return local()\n",
"def run():\n try:\n return local()\n except* ValueError:\n pass\n"};
for (size_t i = 0; i < sizeof(bodies) / sizeof(bodies[0]); i++) {
char source[512];
snprintf(source, sizeof(source), "def local():\n return 1\n%s", bodies[i]);
const python_proof_file_t file = {"main.py", source};
ASSERT(python_expect(bodies[i], &file, 1, "CALLS", "main.py", "local", "Function", 1));
}
/* The statement still counts for the name it rebinds. */
EXPECT_CALL("global rebinds the name", "main.py", "local", "Function", 0,
{"main.py", "def local():\n return 1\ndef swap():\n global local\n"
" local = None\ndef run():\n return local()\n"});
EXPECT_CALL("walrus inside an interpolation binds", "main.py", "local", "Function", 0,
{"main.py", "def local():\n return 1\ntag = f'{(local := None)}'\n"
"def run():\n return local()\n"});
EXPECT_CALL("match capture binds", "main.py", "local", "Function", 0,
{"main.py", "def local():\n return 1\n"
"def run(value):\n match value:\n case local:\n"
" return local()\n"});
PASS();
}
TEST(resolver_python_only_real_namespace_effects_are_dynamic) {
static const char *inert[] = {
"class Node:\n def eval(self, context):\n return self.eval(context)\n",
"def render(node):\n return node.eval(1)\n",
"def call(exec=None):\n return exec\n",
"def apply(target):\n setattr(target, 'name', 1)\n delattr(target, 'name')\n",
"def state(obj):\n return obj.__dict__.copy()\n",
"def pick():\n value = 1\n return locals()['value']\n",
"def attrs(obj):\n return vars(obj)\n",
"call(eval=1)\n",
"def load(name):\n return __import__(name, {}, {}, ['x'])\n",
"def run_it(code, space):\n exec(code, space)\n",
"def known(code):\n return eval(code, {}, {'x': 1})\n",
"import sys\ndef seen():\n return hasattr(sys.modules[__name__], 'apps')\n",
"def where():\n return '__file__' in globals()\n",
"def merged():\n return {**globals(), 'x': 1}\n",
/* pydantic/v1/utils.py in_ipython: one literal name is only read */
"def in_ipython():\n try:\n eval('__IPYTHON__')\n except NameError:\n"
" return False\n return True\n",
"seen = eval(\"local\")\n",
/* lib/sqlalchemy/exc.py: an alias of the namespace that is only read */
"def translate(name):\n glob = globals()\n if name in glob and callable(glob[name]):\n"
" return glob[name]\n return glob.get(name)\n"};
for (size_t i = 0; i < sizeof(inert) / sizeof(inert[0]); i++) {
char source[512];
snprintf(source, sizeof(source),
"def local():\n return 1\n%sdef run():\n return local()\n", inert[i]);
const python_proof_file_t file = {"main.py", source};
ASSERT(python_expect(inert[i], &file, 1, "CALLS", "main.py", "local", "Function", 1));
}
static const char *effects[] = {"exec('local = None')\n", "eval('1')\n",
"eval('(local := None)')\n", "eval(name)\n",
"eval('local' + suffix)\n", "eval(u'local = None')\n",
"glob = globals()\nglob['local'] = None\n",
"__locals = locals()\n__locals['local'] = None\n",
/* httpx/__init__.py: read here, but a module attribute
* another file can write through */
"__locals = locals()\nfor __name in []:\n"
" setattr(__locals[__name], '__module__', 'x')\n",
"def f():\n glob = globals()\n glob.update(local=None)\n",
"def f():\n glob = globals()\n del glob['local']\n",
"def f():\n glob = globals()\n keep(glob)\n",
"def f():\n glob = globals()\n return glob\n",
"def f():\n glob = globals()\n"
" for glob['local'] in [None]:\n pass\n",
"def f():\n glob = globals()\n a, glob['local'] = 1, None\n",
"def f():\n glob = globals()\n glob['local'] += 1\n",
"def f():\n glob = globals()\n"
" locals()['glob']['local'] = None\n",
"def f():\n glob = globals()\n"
" def g():\n glob['local'] = None\n g()\n",
"globals()['local'] = None\n", "locals()['local'] = None\n",
"vars()['local'] = None\n",
"setattr(sys.modules[__name__], 'local', None)\n"};
for (size_t i = 0; i < sizeof(effects) / sizeof(effects[0]); i++) {
char source[512];
snprintf(source, sizeof(source),
"import sys\ndef local():\n return 1\n%sdef run():\n return local()\n",
effects[i]);
const python_proof_file_t file = {"main.py", source};
ASSERT(python_expect(effects[i], &file, 1, "CALLS", "main.py", "local", "Function", 0));
}
PASS();
}
/* `value.helper` reads value's attribute. Its spelling says nothing about the
* module-level helper, so no usage edge may reach it. */
TEST(resolver_python_member_spelling_is_not_a_lexical_read) {
EXPECT_USAGE("attribute read", "main.py", "helper", "Function", 0,
{"main.py", "def helper():\n return 1\ndef run(value):\n return value.helper\n"});
EXPECT_USAGE("keyword name", "main.py", "helper", "Function", 0,
{"main.py", "def helper():\n return 1\ndef run(call):\n return call(helper=1)\n"});
EXPECT_USAGE("plain read control", "main.py", "helper", "Function", 1,
{"main.py", "def helper():\n return 1\ndef run():\n return helper\n"});
/* The argument carrier spells value.helper as helper over the whole attribute. */
EXPECT_USAGE("attribute argument", "main.py", "helper", "Function", 0,
{"main.py", "def helper():\n return 1\ndef run(value):\n return check(value.helper)\n"});
EXPECT_USAGE("attribute argument, imported spelling", "lib.py", "helper", "Function", 0,
{"main.py", "from lib import helper\ndef run(value):\n return check(value.helper)\n"},
{"lib.py", "def helper():\n return 1\n"});
/* A class read through its module is a real reference, argument or not. */
EXPECT_USAGE("module member argument", "lib.py", "Field", "Class", 1,
{"main.py", "import lib\ndef run(value):\n return isinstance(value, lib.Field)\n"},
{"lib.py", "class Field:\n pass\n"});
EXPECT_USAGE("module member assigned", "lib.py", "Field", "Class", 1,
{"main.py", "import lib\ndef run():\n kind = lib.Field\n return kind\n"},
{"lib.py", "class Field:\n pass\n"});
PASS();
}
TEST(resolver_python_function_body_sees_later_module_binders) {
EXPECT_CALL("deferred body", "main.py", "later", "Function", 1,
{"main.py", "def run():\n return later()\ndef later():\n return 1\n"});
EXPECT_CALL("module statement runs in order", "main.py", "later", "Function", 0,
{"main.py", "value = later()\ndef later():\n return 1\n"});
PASS();
}
TEST(resolver_python_conditional_definitions_are_honest) {
EXPECT_CALL("one conditional def", "main.py", "helper", "Function", 1,
{"main.py", "import sys\nif sys.platform == 'x':\n def helper():\n return 1\n"
"def run():\n return helper()\n"});
EXPECT_CALL("alternative defs", "main.py", "helper", "Function", 0,
{"main.py", "import sys\nif sys.platform == 'x':\n def helper():\n return 1\n"
"else:\n def helper():\n return 2\n"
"def run():\n return helper()\n"});
EXPECT_CALL("def and fallback value", "main.py", "helper", "Function", 0,
{"main.py", "try:\n def helper():\n return 1\nexcept Exception:\n"
" helper = None\ndef run():\n return helper()\n"});
PASS();
}
TEST(resolver_python_import_forms_reach_the_declaration) {
static const python_proof_file_t util = {"pkg/util.py", "def helper():\n return 2\n"};
static const python_proof_file_t decoy = {"other/util.py", "def helper():\n return 3\n"};
static const python_proof_file_t pkg = {"pkg/__init__.py", ""};
static const python_proof_file_t other = {"other/__init__.py", ""};
static const char *forms[] = {
"from pkg import util\ndef run():\n return util.helper()\n",
"from pkg.util import helper\ndef run():\n return helper()\n",
"import pkg.util\ndef run():\n return pkg.util.helper()\n",
"import pkg.util as u\ndef run():\n return u.helper()\n",
"from pkg.util import helper as h\ndef run():\n return h()\n",
"def run():\n from pkg.util import helper\n return helper()\n",
"def run():\n from pkg import util\n return util.helper()\n",
"def run():\n import pkg.util as u\n return u.helper()\n",
("import sys\nif sys.platform:\n from pkg.util import helper\n"
"def run():\n return helper()\n")};
for (size_t i = 0; i < sizeof(forms) / sizeof(forms[0]); i++) {
const python_proof_file_t files[] = {{"main.py", forms[i]}, util, decoy, pkg, other};
ASSERT(python_expect(forms[i], files, 5, "CALLS", "pkg/util.py", "helper", "Function", 1));
ASSERT(python_expect(forms[i], files, 5, "CALLS", "other/util.py", "helper", "Function", 0));
}
/* Two dotted imports of one package bind the same root. */
{
const python_proof_file_t files[] = {
{"main.py", "import pkg.util\nimport pkg.more\nimport pkg\n"
"def run():\n pkg.util.helper()\n pkg.more.again()\n"},
util, pkg, {"pkg/more.py", "def again():\n return 1\n"}};
ASSERT(python_expect("shared root", files, 4, "CALLS", "pkg/util.py", "helper", "Function", 1));
ASSERT(python_expect("shared root", files, 4, "CALLS", "pkg/more.py", "again", "Function", 1));
}
/* The bound package also reads the submodules another import loaded. */
{
const python_proof_file_t files[] = {
{"main.py", "import pkg.more\nimport pkg.util as u\n"
"def run():\n pkg.util.helper()\n"},
util, pkg, {"pkg/more.py", "def again():\n return 1\n"}};
ASSERT(python_expect("through the root", files, 4, "CALLS", "pkg/util.py", "helper", "Function", 1));
}
/* A relative form, from inside the package. */
{
const python_proof_file_t files[] = {
{"pkg/main.py", "from . import util\nfrom .util import helper\n"
"def run():\n util.helper()\n helper()\n"},
util, pkg, {"main.py", "from pkg.main import run\ndef go():\n return run()\n"}};
ASSERT(python_expect("relative", files, 4, "CALLS", "pkg/main.py", "run", "Function", 1));
}
/* Two functions importing different modules under one name each see their own. */
{
const python_proof_file_t files[] = {
{"main.py", "def one():\n from pkg.util import helper\n return helper()\n"
"def two():\n from other.util import helper\n return helper()\n"},
util, decoy, pkg, other};
ASSERT(python_expect("scoped imports", files, 5, "CALLS", "pkg/util.py", "helper", "Function", 1));
ASSERT(python_expect("scoped imports", files, 5, "CALLS", "other/util.py", "helper", "Function", 1));
}
/* Alternatives for one name stay ambiguous. */
{
const python_proof_file_t files[] = {
{"main.py", "try:\n from pkg.util import helper\nexcept ImportError:\n"
" from other.util import helper\ndef run():\n return helper()\n"},
util, decoy, pkg, other};
ASSERT(python_expect("alternatives", files, 5, "CALLS", "pkg/util.py", "helper", "Function", 0));
ASSERT(python_expect("alternatives", files, 5, "CALLS", "other/util.py", "helper", "Function", 0));
}
PASS();
}
/* django.db.__init__ does real work and django.db.models is loaded all over the
* project: neither stops `from django.db import models` naming the submodule. */
/* Absolute imports follow sys.path as the file's own place fixes it. Ambiguity abstains. */
TEST(resolver_python_absolute_imports_follow_the_search_path) {
static const char go[] = "def go():\n return 1\n";
static const char package[] = "VERSION = 1\n";
{
const python_proof_file_t files[] = {
{"src/pkg/__init__.py", "from .inner import go as exported\n__all__ = ['exported']\n"},
{"src/pkg/inner.py", go},
{"src/other/__init__.py", package},
{"src/other/thing.py", "def thing():\n return 1\n"},
{"src/pkg/user.py",
"from pkg.inner import go\nfrom other.thing import thing\ndef run():\n go()\n"
" thing()\n"}};
ASSERT(python_expect_from("src layout, own package", files, 5, "src/pkg/user.py", "CALLS", "src/pkg/inner.py", "go", "Function", 1));
ASSERT(python_expect_from("src layout, sibling package", files, 5, "src/pkg/user.py", "CALLS", "src/other/thing.py", "thing", "Function", 1));
}
{
const python_proof_file_t files[] = {{"src/pkg/__init__.py", ""},
{"src/pkg/inner.py", go},
{"src/pkg/user.py",
"from pkg.inner import go\ndef run():\n go()\n"}};
ASSERT(python_expect_from("an empty __init__ still makes a package", files, 3, "src/pkg/user.py", "CALLS", "src/pkg/inner.py", "go", "Function", 1));
}
{
/* django/tests: runtests.py runs from tests/, each test package imports by its name. */
const python_proof_file_t files[] = {
{"tests/admin_views/__init__.py", package},
{"tests/admin_views/models.py", go},
{"tests/admin_views/tests.py",
"from admin_views.models import go\ndef run():\n go()\n"}};
ASSERT(python_expect_from("test package by its name", files, 3, "tests/admin_views/tests.py", "CALLS", "tests/admin_views/models.py", "go", "Function", 1));
}
{
const python_proof_file_t files[] = {
{"scripts/helpers.py", go},
{"scripts/run.py", "import helpers\ndef run():\n helpers.go()\n"}};
ASSERT(python_expect_from("script beside its module", files, 2, "scripts/run.py", "CALLS", "scripts/helpers.py", "go", "Function", 1));
}
{
const python_proof_file_t files[] = {
{"lib/x.py", go},
{"tests/test_a.py", "from lib.x import go\ndef run():\n go()\n"}};
ASSERT(python_expect_from("root module with nothing nearer", files, 2, "tests/test_a.py", "CALLS", "lib/x.py", "go", "Function", 1));
}
{
const python_proof_file_t files[] = {
{"utils.py", go},
{"tests/utils.py", go},
{"tests/test_a.py", "import utils\ndef run():\n utils.go()\n"}};
ASSERT(python_expect_from("two candidates, root", files, 3, "tests/test_a.py", "CALLS", "utils.py", "go", "Function", 0));
ASSERT(python_expect_from("two candidates, near", files, 3, "tests/test_a.py", "CALLS", "tests/utils.py", "go", "Function", 0));
}
{
const python_proof_file_t files[] = {
{"utils.py", go},
{"src/utils.py", go},
{"src/pkg/__init__.py", package},
{"src/pkg/user.py", "import utils\ndef run():\n utils.go()\n"}};
ASSERT(python_expect_from("two candidates from a package, root", files, 4, "src/pkg/user.py", "CALLS", "utils.py", "go", "Function", 0));
ASSERT(python_expect_from("two candidates from a package, near", files, 4, "src/pkg/user.py", "CALLS", "src/utils.py", "go", "Function", 0));
}
{
const python_proof_file_t files[] = {
{"pkg/x.py", go},
{"src/pkg/__init__.py", package},
{"src/pkg/x.py", go},
{"src/pkg/user.py", "from pkg.x import go\ndef run():\n go()\n"}};
ASSERT(python_expect_from("own package beats a root namesake", files, 4, "src/pkg/user.py", "CALLS", "src/pkg/x.py", "go", "Function", 1));
ASSERT(python_expect_from("the root namesake is no candidate", files, 4, "src/pkg/user.py", "CALLS", "pkg/x.py", "go", "Function", 0));
}
{
const python_proof_file_t files[] = {
{"foo/__init__.py", package},
{"foo/baz.py", go},
{"tests/foo/__init__.py", package},
{"tests/foo/bar.py", "from foo.baz import go\ndef run():\n go()\n"}};
ASSERT(python_expect_from("a nearer package hides the root one", files, 4, "tests/foo/bar.py", "CALLS", "foo/baz.py", "go", "Function", 0));
}
{
const python_proof_file_t files[] = {
{"src/pkg/__init__.py", package},
{"src/pkg/inner.py", go},
{"src/pkg/user.py", "import inner\ndef run():\n inner.go()\n"}};
ASSERT(python_expect_from("no implicit relative import", files, 3, "src/pkg/user.py", "CALLS", "src/pkg/inner.py", "go", "Function", 0));
}
PASS();
}
TEST(resolver_python_submodule_import_survives_a_working_package) {
static const python_proof_file_t child = {"pkg/mod.py", "def real():\n return 1\n"};
static const char *packages[] = {
("import os\nfrom pkg.other import VALUE\nDEFAULT = VALUE\nclass Handler:\n pass\n"
"def setup():\n return Handler()\n"),
"from pkg.other import *\n"};
for (size_t i = 0; i < sizeof(packages) / sizeof(packages[0]); i++) {
const python_proof_file_t files[] = {
{"pkg/__init__.py", packages[i]},
child,
{"pkg/other.py", "__all__ = ['VALUE']\nVALUE = 1\n"},
{"loader.py", "import pkg.mod\nimport importlib\ndef load(name):\n"
" print(pkg.DEFAULT)\n"
" getattr(pkg, '__file__', None)\n"
" return importlib.import_module(name)\n"},
{"main.py", "from pkg import mod\ndef run():\n return mod.real()\n"}};
ASSERT(python_expect(packages[i], files, 5, "CALLS", "pkg/mod.py", "real", "Function", 1));
}
/* A value the package binds wins over the submodule file. */
{
const python_proof_file_t files[] = {
{"pkg/__init__.py", "from pkg.other import *\n"},
child,
{"pkg/other.py", "__all__ = ['mod']\ndef mod():\n return 1\n"},
{"main.py", "from pkg import mod\ndef run():\n return mod.real()\n"}};
ASSERT(python_expect("star value", files, 4, "CALLS", "pkg/mod.py", "real", "Function", 0));
}
PASS();
}
TEST(resolver_python_star_import_follows_the_module_interface) {
static const python_proof_file_t fields = {
"pkg/fields.py", "__all__ = ['CharField']\nclass CharField:\n pass\n"
"class Hidden:\n pass\n"};
static const python_proof_file_t plain = {
"pkg/plain.py", "def public():\n return 1\ndef _private():\n return 2\n"};
static const python_proof_file_t init = {"pkg/__init__.py",
"from pkg.fields import *\nfrom pkg.plain import *\n"};
{
const python_proof_file_t files[] = {
{"main.py", "from pkg import CharField, public\ndef run():\n CharField()\n public()\n"},
fields, plain, init};
ASSERT(python_expect("reexport", files, 4, "CALLS", "pkg/fields.py", "CharField", "Class", 1));
ASSERT(python_expect("reexport", files, 4, "CALLS", "pkg/plain.py", "public", "Function", 1));
}
{
const python_proof_file_t files[] = {
{"main.py", "import pkg\ndef run():\n pkg.CharField()\n"}, fields, plain, init};
ASSERT(python_expect("module member", files, 4, "CALLS", "pkg/fields.py", "CharField", "Class", 1));
}
{
const python_proof_file_t files[] = {
{"main.py", "from pkg.fields import *\ndef run():\n CharField()\n"}, fields, plain, init};
ASSERT(python_expect("local star", files, 4, "CALLS", "pkg/fields.py", "CharField", "Class", 1));
}
/* Not in __all__, or private: the star does not bind it. */
{
const python_proof_file_t files[] = {
{"main.py", "from pkg import Hidden, _private\ndef run():\n Hidden()\n _private()\n"},
fields, plain, init};
ASSERT(python_expect("not exported", files, 4, "CALLS", "pkg/fields.py", "Hidden", "Class", 0));
ASSERT(python_expect("not exported", files, 4, "CALLS", "pkg/plain.py", "_private", "Function", 0));
}
/* A later explicit binder wins; a star of an unknown module rebinds what precedes it. */
{
const python_proof_file_t files[] = {
{"main.py", "from pkg.fields import *\nclass CharField:\n pass\ndef run():\n CharField()\n"},
fields, plain, init};
ASSERT(python_expect("explicit after", files, 4, "CALLS", "main.py", "CharField", "Class", 1));
ASSERT(python_expect("explicit after", files, 4, "CALLS", "pkg/fields.py", "CharField", "Class", 0));
}
{
const python_proof_file_t files[] = {
{"main.py", "from pkg.fields import CharField\nfrom external import *\n"
"def run():\n CharField()\n"},
fields, plain, init};
ASSERT(python_expect("open star", files, 4, "CALLS", "pkg/fields.py", "CharField", "Class", 0));
}
{
const python_proof_file_t files[] = {
{"main.py", "from external import *\nfrom pkg.fields import CharField\n"
"def run():\n CharField()\n"},
fields, plain, init};
ASSERT(python_expect("binder after open star", files, 4, "CALLS", "pkg/fields.py", "CharField", "Class", 1));
}
PASS();
}
TEST(resolver_python_self_selects_through_c3_order) {
static const python_proof_file_t base = {
"lib/base.py", "class Base:\n def shared(self):\n return 1\n"
" def top(self):\n return 1\n"};
static const python_proof_file_t decoy = {
"lib/decoy.py", "class Unrelated:\n def shared(self):\n return 2\n"
" def only_here(self):\n return 2\n"};
{
const python_proof_file_t files[] = {
{"main.py", "from lib.base import Base\nclass Child(Base):\n"
" def own(self):\n return 1\n"
" def run(self):\n self.own()\n self.shared()\n"
" self.only_here()\n"},
base, decoy};
ASSERT(python_expect("own", files, 3, "CALLS", "main.py", "own", "Method", 1));
ASSERT(python_expect("inherited", files, 3, "CALLS", "lib/base.py", "shared", "Method", 1));
ASSERT(python_expect("decoy", files, 3, "CALLS", "lib/decoy.py", "shared", "Method", 0));
ASSERT(python_expect("unique name elsewhere", files, 3, "CALLS", "lib/decoy.py", "only_here", "Method", 0));
}
/* Diamond: C3 puts Right before Top, where depth-first would find Top first. */
{
const python_proof_file_t files[] = {
{"main.py",
"class Top:\n def pick(self):\n return 0\n"
"class Left(Top):\n pass\n"
"class Right(Top):\n def pick(self):\n return 1\n"
"class Bottom(Left, Right):\n def run(self):\n return self.pick()\n"}};
ASSERT(python_expect("diamond", files, 1, "CALLS", "main.py", "pick", "Method", 1));
}
{
const char *source =
"class Top:\n def pick(self):\n return 0\n"
"class Left(Top):\n pass\n"
"class Right(Top):\n def pick(self):\n return 1\n"
"class Bottom(Left, Right):\n def run(self):\n return self.pick()\n";
const char *root = th_mktempdir("cbm_python_c3");
ASSERT_NOT_NULL(root);
char repo[768], database[768];
snprintf(repo, sizeof(repo), "%s/repo", root);
snprintf(database, sizeof(database), "%s/index.db", root);
ASSERT(cbm_mkdir_p(repo, 0755));
ASSERT_EQ(th_write_file(TH_PATH(repo, "main.py"), source), 0);
cbm_pipeline_t *pipeline = cbm_pipeline_new(repo, database, CBM_MODE_FULL);
ASSERT_EQ(cbm_pipeline_set_project_name(pipeline, "python-c3") ? cbm_pipeline_run(pipeline) : -1, 0);
cbm_pipeline_free(pipeline);
cbm_store_t *store = cbm_store_open_path_existing(database);
ASSERT_NOT_NULL(store);
sqlite3_stmt *statement = NULL;
ASSERT_EQ(sqlite3_prepare_v2(cbm_store_get_db(store),
"SELECT t.qualified_name FROM edges e JOIN nodes s ON s.id=e.source_id "
"JOIN nodes t ON t.id=e.target_id WHERE e.type='CALLS' AND s.name='run'",
-1, &statement, NULL),
SQLITE_OK);
ASSERT_EQ(sqlite3_step(statement), SQLITE_ROW);
ASSERT_NOT_NULL(strstr((const char *)sqlite3_column_text(statement, 0), ".Right.pick"));
ASSERT_EQ(sqlite3_step(statement), SQLITE_DONE);
sqlite3_finalize(statement);
cbm_store_close(store);
th_cleanup(root);
}
/* The proven base is a real INHERITS edge, through a reexport or a star. */
{
const python_proof_file_t files[] = {
{"main.py", "import pkg\nfrom pkg import Base as Named\n"
"class Dotted(pkg.Base):\n pass\nclass Aliased(Named):\n pass\n"},
{"pkg/__init__.py", "from pkg.impl import *\n"},
{"pkg/impl.py", "__all__ = ['Base']\nclass Base:\n pass\nclass Decoy:\n pass\n"}};
ASSERT(python_expect("reexported base", files, 3, "INHERITS", "pkg/impl.py", "Base", "Class", 2));
ASSERT(python_expect("reexported base decoy", files, 3, "INHERITS", "pkg/impl.py", "Decoy", "Class", 0));
}
/* What is not certain stops the walk, and own members never depend on it. */
{
const python_proof_file_t files[] = {
{"main.py", "from lib.base import Base\nfrom external import Ext\n"
"class First(Ext, Base):\n"
" def own(self):\n return 1\n"
" def run(self):\n self.own()\n self.top()\n"},
base};
ASSERT(python_expect("own before external", files, 2, "CALLS", "main.py", "own", "Method", 1));
ASSERT(python_expect("external first", files, 2, "CALLS", "lib/base.py", "top", "Method", 0));
}
{
const python_proof_file_t files[] = {
{"main.py", "from lib.base import Base\nfrom external import Ext\n"
"class Last(Base, Ext):\n def run(self):\n self.top()\n"},
base};
ASSERT(python_expect("external last", files, 2, "CALLS", "lib/base.py", "top", "Method", 1));
}
{
const python_proof_file_t files[] = {
{"main.py", "from lib.base import Base\nclass Made(make_base(), Base):\n"
" def own(self):\n return 1\n"
" def run(self):\n self.own()\n self.top()\n"},
base};
ASSERT(python_expect("computed base, own", files, 2, "CALLS", "main.py", "own", "Method", 1));
ASSERT(python_expect("computed base", files, 2, "CALLS", "lib/base.py", "top", "Method", 0));
}
/* An unknown base further up can sit ahead of a sibling in the real order. */
{
const python_proof_file_t files[] = {
{"main.py", "from lib.base import Base\n"
"class Middle(make_base()):\n def mid(self):\n return 1\n"
"class Multi(Middle, Base):\n def run(self):\n"
" self.mid()\n self.top()\n"
"class Single(Middle):\n def run(self):\n return self.mid()\n"},
base};
ASSERT(python_expect("sibling after unknown ancestry", files, 2, "CALLS", "lib/base.py", "top", "Method", 0));
/* Single.run through its only base, Multi.run through its first written base. */
ASSERT(python_expect("single chain and first base keep their order", files, 2, "CALLS", "main.py", "mid", "Method", 2));
}
/* C3 puts the first written base directly behind its class, or refuses the class. So
* an unknown second base cannot overtake it, from django TupleIn(TupleLookupMixin, In).
* Nothing behind that base is certain: the unknown one can subclass its parent. */
{
const char *classes =
"class Root:\n def far(self):\n return 1\n"
"class Mixin(Root):\n def near(self):\n return 1\n"
" def both(self):\n return 1\n";
static const char *const first_cases[][2] = {
{"first base before an unknown one",
"class A(Mixin, made()):\n def run(self):\n return self.near()\n"},
{"first base through a subclass",
"class A(Mixin, made()):\n pass\nclass B(A):\n def run(self):\n"
" return self.near()\n"},
{"own member wins over the first base",
"class A(Mixin, made()):\n def near(self):\n return 2\n"
" def run(self):\n return self.near()\n"},
};
static const int first_expected[] = {1, 1, 0};
for (size_t i = 0; i < 3; i++) {
char source[1024];
snprintf(source, sizeof(source), "%s%s", classes, first_cases[i][1]);
const python_proof_file_t files[] = {{"main.py", source}, base};
/* A count by name in one file: the own near is a second declaration of that name. */
int expected = first_expected[i] + (i == 2 ? 1 : 0);
ASSERT(python_expect(first_cases[i][0], files, 2, "CALLS", "main.py", "near", "Method", expected));
}
char source[1024];
snprintf(source, sizeof(source), "%s%s", classes,
"class A(Mixin, made()):\n def run(self):\n return self.far()\n"
"class C(made(), Mixin):\n def run(self):\n return self.both()\n");
const python_proof_file_t files[] = {{"main.py", source}, base};
ASSERT(python_expect("behind the first base nothing is certain", files, 2, "CALLS", "main.py", "far", "Method", 0));
ASSERT(python_expect("a mixin behind an unknown first base", files, 2, "CALLS", "main.py", "both", "Method", 0));
}
/* Base[int] inherits Base, unless the class answers the subscript itself. */
{
const char *generic =
"from typing import Generic, TypeVar\nT = TypeVar('T')\n"
"class Repo(Generic[T]):\n def load(self):\n return 1\n"
"class UserRepo(Repo[int]):\n def run(self):\n return self.load()\n";
const python_proof_file_t files[] = {{"main.py", generic}};
ASSERT(python_expect("subscripted base", files, 1, "CALLS", "main.py", "load", "Method", 1));
const char *answers =
"class Other:\n def load(self):\n return 2\n"
"class Weird:\n def __class_getitem__(cls, item):\n return Other\n"
" def load(self):\n return 1\n"
"class Sub(Weird):\n pass\n";
char source[512];
snprintf(source, sizeof(source), "%s%s", answers,
"class A(Weird[int]):\n def run(self):\n return self.load()\n"
"class B(Sub[int]):\n def run(self):\n return self.load()\n");
const python_proof_file_t subscript[] = {{"main.py", source}};
ASSERT(python_expect("a class that answers its subscript", subscript, 1, "CALLS", "main.py", "load", "Method", 0));
snprintf(source, sizeof(source), "%s%s", answers,
"class A(Weird):\n def run(self):\n return self.load()\n");
const python_proof_file_t plain[] = {{"main.py", source}};
ASSERT(python_expect("the same class, not subscripted", plain, 1, "CALLS", "main.py", "load", "Method", 1));
}
/* The instance and the class can answer differently. */
{
const python_proof_file_t files[] = {
{"main.py", "class Stored:\n def go(self):\n return 1\n"
" def __init__(self):\n self.go = other\n"
" def run(self):\n return self.go()\n"}};
ASSERT(python_expect("instance rebinding", files, 1, "CALLS", "main.py", "go", "Method", 0));
}
{
const python_proof_file_t files[] = {
{"main.py", "class Hooked:\n def go(self):\n return 1\n"
" def __getattribute__(self, name):\n return other\n"
" def run(self):\n return self.go()\n"}};
ASSERT(python_expect("getattribute", files, 1, "CALLS", "main.py", "go", "Method", 0));
}
{
const python_proof_file_t files[] = {
{"main.py", "class Parent:\n def go(self):\n return 1\n"
"class Value(Parent):\n go = make_go()\n"
" def run(self):\n return self.go()\n"}};
ASSERT(python_expect("class value stops lookup", files, 1, "CALLS", "main.py", "go", "Method", 0));
}
{
const python_proof_file_t files[] = {
{"main.py", "class Rebound:\n def go(self):\n return 1\n"
" def run(self):\n self = other\n return self.go()\n"}};
ASSERT(python_expect("self reassigned", files, 1, "CALLS", "main.py", "go", "Method", 0));
}
{
const python_proof_file_t files[] = {
{"main.py", "class Parent:\n def __hidden(self):\n return 1\n"
"class Sub(Parent):\n def run(self):\n return self.__hidden()\n"}};
ASSERT(python_expect("name mangling", files, 1, "CALLS", "main.py", "__hidden", "Method", 0));
}
PASS();
}
TEST(resolver_python_super_and_class_level_receivers) {
static const python_proof_file_t base = {
"lib/base.py", "class Base:\n def __init__(self):\n pass\n"
" def save(self):\n return 1\n"
" @classmethod\n def build(cls):\n return cls()\n"
" @staticmethod\n def plain():\n return 1\n"};
{
const python_proof_file_t files[] = {
{"main.py", "from lib.base import Base\nclass Child(Base):\n"
" def __init__(self):\n super().__init__()\n"
" def save(self):\n return super().save()\n"
" def old(self):\n return super(Child, self).save()\n"
" @classmethod\n def make(cls):\n return cls.build()\n"
" def fixed(self):\n return self.plain()\n"
"def direct():\n Base.build()\n return Base.plain()\n"},
base};
ASSERT(python_expect("super init", files, 2, "CALLS", "lib/base.py", "__init__", "Method", 1));
ASSERT(python_expect("super method", files, 2, "CALLS", "lib/base.py", "save", "Method", 2));
ASSERT(python_expect("cls and class", files, 2, "CALLS", "lib/base.py", "build", "Method", 2));
ASSERT(python_expect("static", files, 2, "CALLS", "lib/base.py", "plain", "Method", 2));
}
/* super() skips the writing class. */
{
const python_proof_file_t files[] = {
{"main.py", "class Only:\n def save(self):\n return super().save()\n"}};
ASSERT(python_expect("no base method", files, 1, "CALLS", "main.py", "save", "Method", 0));
}
/* staticmethod's first parameter is an ordinary argument. */
{
const python_proof_file_t files[] = {
{"main.py", "class Tool:\n def go(self):\n return 1\n"
" @staticmethod\n def run(self):\n return self.go()\n"}};
ASSERT(python_expect("static parameter", files, 1, "CALLS", "main.py", "go", "Method", 0));
}
PASS();
}
/* The class of a value comes from a fact about it. self.compiler below has none,
* however unique apply_converters is: that stays an abstention. */
TEST(resolver_python_receiver_types_need_a_fact) {
static const python_proof_file_t store = {
"lib/store.py", "class Store:\n def get(self):\n return 1\n"
" def apply_converters(self):\n return 1\n"
"class Other:\n def get(self):\n return 2\n"
"def make() -> Store:\n return Store()\n"
"async def amake() -> Store:\n return Store()\n"
"def untyped():\n return Store()\n"
"def mixed(flag):\n if flag:\n return Store()\n"
" return Other()\n"
"def echo(value):\n return value\n"
"def stream():\n yield Store()\n"
"def again():\n return again()\n"
"class Fluent(Store):\n"
" def chain(self):\n return self\n"
" def clone(self):\n made = self.__class__()\n return made\n"};
static const struct {
const char *what, *source;
int store_get;
} cases[] = {
{"attribute from construction",
"from lib.store import Store\nclass A:\n def __init__(self):\n self.s = Store()\n"
" def run(self):\n return self.s.get()\n",
1},
{"attribute with a None default",
"from lib.store import Store\nclass A:\n def __init__(self):\n self.s = None\n"
" def load(self):\n self.s = Store()\n"
" def run(self):\n return self.s.get()\n",
1},
{"attribute with a None class default",
"from lib.store import Store\nclass A:\n s = None\n"
" def __init__(self):\n self.s = Store()\n"
" def run(self):\n return self.s.get()\n",
1},
{"attribute from annotated parameter",
"from lib.store import Store\nclass A:\n def __init__(self, s: Store):\n self.s = s\n"
" def run(self):\n return self.s.get()\n",
1},
{"attribute annotation",
"from lib.store import Store\nclass A:\n s: Store\n"
" def run(self):\n return self.s.get()\n",
1},
{"attribute inherited from base",
"from lib.store import Store\nclass A:\n def __init__(self):\n self.s = Store()\n"
"class B(A):\n def run(self):\n return self.s.get()\n",
1},
{"attribute with two types",
"from lib.store import Store, Other\nclass A:\n def one(self):\n self.s = Store()\n"
" def two(self):\n self.s = Other()\n"
" def run(self):\n return self.s.get()\n",
0},
{"attribute from an untyped parameter",
"class A:\n def __init__(self, s):\n self.s = s\n"
" def run(self):\n return self.s.get()\n",
0},
{"local from construction",
"from lib.store import Store\ndef run():\n s = Store()\n return s.get()\n", 1},
{"local copied",
"from lib.store import Store\ndef run():\n s = Store()\n t = s\n return t.get()\n", 1},
{"local reassigned",
"from lib.store import Store\ndef run(flag):\n s = Store()\n s = flag\n return s.get()\n",
0},
{"local with a None start",
"from lib.store import Store\ndef run(flag):\n s = None\n if flag:\n"
" s = Store()\n return s.get()\n",
1},
{"local reassigned to the same class",
"from lib.store import Store\ndef run(flag):\n s = Store()\n if flag:\n"
" s = Store()\n return s.get()\n",
1},
{"local rebound through a fluent method",
"from lib.store import Fluent\ndef run():\n s = Fluent()\n s = s.chain()\n"
" return s.get()\n",
1},
{"local rebound through an unknown step",
"from lib.store import Fluent\ndef run():\n s = Fluent()\n s = s.other()\n"
" return s.get()\n",
0},
{"parameter annotation",
"from lib.store import Store\ndef run(s: Store):\n return s.get()\n", 1},
{"optional annotation",
"from typing import Optional\nfrom lib.store import Store\n"
"def run(s: Optional[Store]):\n return s.get()\n",
1},
{"qualified optional annotation",
"import typing\nfrom lib.store import Store\ndef run(s: typing.Optional[Store]):\n"
" return s.get()\n",
1},
{"aliased optional annotation",
"from typing import Optional as Maybe\nfrom lib.store import Store\n"
"def run(s: Maybe[Store]):\n return s.get()\n",
1},
{"a project class named Optional says nothing",
"from lib.maybe import Optional\nfrom lib.store import Store\n"
"def run(s: Optional[Store]):\n return s.get()\n",
0},
{"optional that was never imported",
"from lib.store import Store\ndef run(s: Optional[Store]):\n return s.get()\n", 0},
{"union with None on the right",
"from lib.store import Store\ndef run(s: Store | None):\n return s.get()\n", 1},
{"union with None on the left",
"from lib.store import Store\ndef run(s: None | Store):\n return s.get()\n", 1},
{"union of two classes",
"from lib.store import Store, Other\ndef run(s: Store | Other):\n return s.get()\n", 0},
{"union of two classes and None",
"from lib.store import Store, Other\ndef run(s: Store | Other | None):\n return s.get()\n", 0},
{"optional of a union",
"from typing import Optional\nfrom lib.store import Store, Other\n"
"def run(s: Optional[Store | Other]):\n return s.get()\n",
0},
{"attribute from a union parameter",
"from lib.store import Store\nclass A:\n def __init__(self, s: Store | None):\n"
" self.s = s\n def run(self):\n return self.s.get()\n",
1},
{"forward reference annotation",
"from lib.store import Store\ndef run(s: 'Store'):\n return s.get()\n", 1},
{"annotated return",
"from lib.store import make\ndef run():\n return make().get()\n", 1},
{"return inferred from its construction",
"from lib.store import untyped\ndef run():\n return untyped().get()\n", 1},
{"returns disagree",
"from lib.store import mixed\ndef run():\n return mixed(1).get()\n", 0},
{"returns a parameter",
"from lib.store import echo\ndef run(x):\n return echo(x).get()\n", 0},
{"generator",
"from lib.store import stream\ndef run():\n return stream().get()\n", 0},
{"recursion proves nothing",
"from lib.store import again\ndef run():\n return again().get()\n", 0},
{"fluent method returning self",
"from lib.store import Fluent\ndef run():\n return Fluent().chain().get()\n", 1},
{"method returning a clone of its class",
"from lib.store import Fluent\ndef run():\n return Fluent().clone().get()\n", 1},
{"direct construction", "from lib.store import Store\ndef run():\n return Store().get()\n", 1},
{"awaited annotated coroutine",
"from lib.store import amake\nasync def run():\n s = await amake()\n return s.get()\n",
1},
{"untyped parameter", "def run(s):\n return s.get()\n", 0},
{"parameter shadows the class",
"from lib.store import Store\ndef run(Store):\n return Store().get()\n", 0},
};
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
const python_proof_file_t files[] = {
{"main.py", cases[i].source},
store,
{"lib/maybe.py", "class Optional:\n def __class_getitem__(cls, item):\n"
" return cls\n"}};
ASSERT(python_expect(cases[i].what, files, 3, "CALLS", "lib/store.py", "get", "Method",
cases[i].store_get));
}
/* The last assignment selects, and the earlier class is no candidate. A branch
* leaves both possible, so neither is selected. */
{
const python_proof_file_t elsewhere = {
"lib/other.py", "class Elsewhere:\n def get(self):\n return 2\n"};
const python_proof_file_t rebound[] = {
{"main.py", "from lib.store import Store\nfrom lib.other import Elsewhere\n"
"def run():\n s = Store()\n s = Elsewhere()\n return s.get()\n"},
store,
elsewhere};
ASSERT(python_expect("rebound leaves the first class", rebound, 3, "CALLS", "lib/store.py",
"get", "Method", 0));
ASSERT(python_expect("rebound reaches the last class", rebound, 3, "CALLS", "lib/other.py",
"get", "Method", 1));
const python_proof_file_t branch[] = {
{"main.py", "from lib.store import Store\nfrom lib.other import Elsewhere\n"
"def run(flag):\n s = Store()\n if flag:\n"
" s = Elsewhere()\n return s.get()\n"},
store,
elsewhere};
ASSERT(python_expect("branch selects neither", branch, 3, "CALLS", "lib/store.py", "get",
"Method", 0));
ASSERT(python_expect("branch selects neither", branch, 3, "CALLS", "lib/other.py", "get",
"Method", 0));
}
/* TYPE_CHECKING imports name classes for annotations and bind nothing at runtime. */
EXPECT_CALL("annotation through TYPE_CHECKING", "lib/store.py", "get", "Method", 1,
{"main.py", "from typing import TYPE_CHECKING\nif TYPE_CHECKING:\n"
" from lib.store import Store\n"
"def run(s: 'Store'):\n return s.get()\n"},
store);
EXPECT_CALL("no runtime binding through TYPE_CHECKING", "lib/store.py", "Store", "Class", 0,
{"main.py", "from typing import TYPE_CHECKING\nif TYPE_CHECKING:\n"
" from lib.store import Store\n"
"def run():\n return Store()\n"},
store);
EXPECT_CALL("custom __new__ can return anything", "lib/made.py", "go", "Method", 0,
{"main.py", "from lib.made import Made\ndef run():\n return Made().go()\n"},
{"lib/made.py", "class Made:\n def __new__(cls):\n return other\n"
" def go(self):\n return 1\n"});
EXPECT_CALL("untyped attribute keeps abstaining", "lib/store.py", "apply_converters", "Method", 0,
{"main.py", "class A:\n def __init__(self, compiler):\n self.compiler = compiler\n"
" def run(self):\n return self.compiler.apply_converters()\n"},
store);
PASS();
}
/* A receiver fact holds only while the name still means that binding. Every form that
* rebinds it, and every `self` that is not the method's own, abstains. */
/* A class body's assignment says what a read yields only while nothing can replace it. In
* django `self.title` on a model is a str, never the CharField the body assigned. */
/* A class body's value is the attribute's declared value. django's objects = UserManager() reads
* as what it says under ModelBase; only the standard library's Enum and NamedTuple state that a
* member reads as something else. */
TEST(resolver_python_class_body_values_can_be_replaced) {
static const struct {
const char *what;
const char *body;
int expected;
} cases[] = {
{"plain class attribute",
"class A:\n title = Field()\n def run(self):\n return self.title.clean()\n", 1},
{"inherited plain class attribute",
"class A:\n title = Field()\nclass B(A):\n def run(self):\n"
" return self.title.clean()\n",
1},
{"own metaclass",
"class A(metaclass=Meta):\n title = Field()\n def run(self):\n"
" return self.title.clean()\n",
1},
{"metaclass on an ancestor",
"class M(metaclass=Meta):\n pass\nclass A(M):\n title = Field()\n"
" def run(self):\n return self.title.clean()\n",
1},
{"metaclass on the receiver's own class",
"class P:\n title = Field()\nclass A(P, metaclass=Meta):\n def run(self):\n"
" return self.title.clean()\n",
1},
{"read from outside, metaclass",
"class A(metaclass=Meta):\n title = Field()\ndef run():\n return A.title.clean()\n", 1},
{"__init_subclass__",
"class H:\n def __init_subclass__(cls):\n pass\nclass A(H):\n"
" title = Field()\n def run(self):\n return self.title.clean()\n",
1},
{"enum member",
"from enum import Enum\nclass A(Enum):\n RED = Field()\n def run(self):\n"
" return self.RED.clean()\n",
0},
{"enum member from outside",
"from enum import Enum\nclass A(Enum):\n RED = Field()\ndef run():\n"
" return A.RED.clean()\n",
0},
/* From django's tests: rf = RequestFactory() in a unittest.TestCase is what it says. */
{"external base keeps it",
"from unittest import TestCase\nclass A(TestCase):\n title = Field()\n"
" def run(self):\n return self.title.clean()\n",
1},
{"aliased enum",
"import enum as e\nclass A(e.IntEnum):\n RED = Field()\n def run(self):\n"
" return self.RED.clean()\n",
0},
{"named tuple field",
"from typing import NamedTuple\nclass A(NamedTuple):\n title = Field()\n"
" def run(self):\n return self.title.clean()\n",
0},
{"a project class named Enum",
"from lib.enum import Enum\nclass A(Enum):\n RED = Field()\n def run(self):\n"
" return self.RED.clean()\n",
1},
{"unknown base",
"class A(make_base()):\n title = Field()\n def run(self):\n"
" return self.title.clean()\n",
0},
{"instance store under a metaclass",
"class A(metaclass=Meta):\n def __init__(self):\n self.title = Field()\n"
" def run(self):\n return self.title.clean()\n",
1},
{"annotation under a metaclass",
"class A(metaclass=Meta):\n title: Field\n def run(self):\n"
" return self.title.clean()\n",
1},
{"a class stored as a value under a metaclass",
"class A(metaclass=Meta):\n kind = Field\n def run(self):\n"
" return self.kind().clean()\n",
1},
};
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
char source[1024];
snprintf(source, sizeof(source),
"class Meta(type):\n pass\nclass Field:\n def clean(self):\n"
" return 1\n%s",
cases[i].body);
const python_proof_file_t files[] = {{"main.py", source},
{"lib/enum.py", "class Enum:\n pass\n"}};
ASSERT(python_expect(cases[i].what, files, 2, "CALLS", "main.py", "clean", "Method", cases[i].expected));
}
PASS();
}
/* A comprehension body does not see the class it is written in. */
TEST(resolver_python_class_comprehension_skips_the_class) {
const char *shadowed = "def helper():\n return 1\nclass A:\n def helper(self):\n"
" return 2\n values = [helper() for _ in ()]\n";
const python_proof_file_t both[] = {{"main.py", shadowed}};
ASSERT(python_expect("never the class's name", both, 1, "CALLS", "main.py", "helper", "Method", 0));
/* KNOWN GAP, not a contract: the module function is what runs. A comprehension has no
* scope of its own in the index, so a name the class binds abstains. */
ASSERT(python_expect("the module function abstains", both, 1, "CALLS", "main.py", "helper", "Function", 0));
const char *outer = "def helper():\n return 1\nclass A:\n"
" values = [helper() for _ in ()]\n";
const python_proof_file_t only[] = {{"main.py", outer}};
ASSERT(python_expect("a name the class does not bind", only, 1, "CALLS", "main.py", "helper", "Function", 1));
const char *direct = "class A:\n def helper(value=None):\n return 2\n"
" value = helper()\n";
const python_proof_file_t body[] = {{"main.py", direct}};
ASSERT(python_expect("the class body itself does see it", body, 1, "CALLS", "main.py", "helper", "Method", 1));
PASS();
}
TEST(resolver_python_shadowed_super_and_type_are_not_the_builtins) {
static const char base[] = "class Base:\n def go(self):\n return 1\n";
static const struct {
const char *what;
const char *body;
int expected;
} cases[] = {
{"builtin super", "class A(Base):\n def run(self):\n return super().go()\n", 1},
{"builtin type", "class A(Base):\n def run(self):\n return type(self).go(self)\n", 1},
{"module function named super",
"def super():\n return 1\nclass A(Base):\n def run(self):\n return super().go()\n",
0},
{"module function named type",
"def type(value):\n return 1\nclass A(Base):\n def run(self):\n"
" return type(self).go(self)\n",
0},
{"import named super",
"from lib.names import super\nclass A(Base):\n def run(self):\n return super().go()\n",
0},
{"local named super",
"class A(Base):\n def run(self, super):\n return super().go()\n", 0},
};
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
char source[1024];
snprintf(source, sizeof(source), "%s%s", base, cases[i].body);
const python_proof_file_t files[] = {
{"main.py", source}, {"lib/names.py", "def super():\n return 1\n"}};
ASSERT(python_expect(cases[i].what, files, 2, "CALLS", "main.py", "go", "Method", cases[i].expected));
}
PASS();
}
TEST(resolver_python_rebound_receivers_abstain) {
static const struct {
const char *what;
const char *body;
int expected;
} cases[] = {
{"control, one construction", "def run():\n s = Store()\n return s.get()\n", 1},
{"construction in one arm", "def run(flag):\n if flag:\n s = Store()\n return s.get()\n", 1},
{"for binder", "def run(items):\n s = Store()\n for s in items:\n s.get()\n", 0},
{"with binder", "def run(thing):\n s = Store()\n with thing as s:\n return s.get()\n", 0},
{"except binder",
"def run():\n s = Store()\n try:\n return 1\n except ValueError as s:\n"
" return s.get()\n",
0},
{"walrus", "def run(other):\n s = Store()\n if (s := other):\n return s.get()\n", 0},
{"import", "def run():\n s = Store()\n import os as s\n return s.get()\n", 0},
{"nested def", "def run():\n s = Store()\n def s():\n return 1\n return s.get()\n", 0},
{"nonlocal write",
"def run(other):\n s = Store()\n def change():\n nonlocal s\n s = other\n"
" change()\n return s.get()\n",
0},
{"augmented", "def run(other):\n s = Store()\n s += other\n return s.get()\n", 0},
{"tuple target", "def run(pair):\n s = Store()\n s, t = pair\n return s.get()\n", 0},
{"star target", "def run(items):\n s = Store()\n *s, t = items\n return s.get()\n", 0},
{"deleted", "def run():\n s = Store()\n del s\n return s.get()\n", 0},
/* What the signature says it holds unless told otherwise, as a checker reads it. */
{"parameter default", "def run(s=Store()):\n return s.get()\n", 1},
{"annotated then reassigned", "def run(other, s: Store):\n s = other\n return s.get()\n", 0},
{"self rebound", "class A(Store):\n def run(self, other):\n self = other\n return self.get()\n", 0},
{"static parameter named self", "class A(Store):\n @staticmethod\n def run(self):\n return self.get()\n", 0},
{"nested parameter named self",
"class A(Store):\n def run(self):\n def inner(self):\n return self.get()\n"
" return inner\n",
0},
{"lambda parameter named self", "class A(Store):\n def run(self):\n return lambda self: self.get()\n", 0},
{"module function parameter named self", "def run(self):\n return self.get()\n", 0},
{"closure keeps the method receiver",
"class A(Store):\n def run(self):\n def inner():\n return self.get()\n"
" return inner\n",
1},
{"__getattribute__ answers every read",
"class A(Store):\n def __getattribute__(self, name):\n return name\n"
" def run(self):\n return self.get()\n",
0},
{"__getattr__ answers only what is missing",
"class A(Store):\n def __getattr__(self, name):\n return name\n"
" def run(self):\n return self.get()\n",
1},
};
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
char source[1024];
snprintf(source, sizeof(source), "class Store:\n def get(self):\n return 1\n%s",
cases[i].body);
const python_proof_file_t files[] = {{"main.py", source}};
ASSERT(python_expect(cases[i].what, files, 1, "CALLS", "main.py", "get", "Method", cases[i].expected));
}
/* Either arm can bind the class, so neither is selected. */
{
const python_proof_file_t files[] = {
{"main.py", "try:\n from lib.a import S\nexcept ImportError:\n from lib.b import S\n"
"def run():\n return S().shared()\n"},
{"lib/__init__.py", ""},
{"lib/a.py", "class S:\n def shared(self):\n return 1\n"},
{"lib/b.py", "class S:\n def shared(self):\n return 2\n"}};
ASSERT(python_expect("import in both arms", files, 4, "CALLS", "lib/a.py", "shared", "Method", 0));
ASSERT(python_expect("import in both arms", files, 4, "CALLS", "lib/b.py", "shared", "Method", 0));
}
PASS();
}
TEST(resolver_python_module_level_instance_has_its_class) {
static const python_proof_file_t router = {
"pkg/__init__.py", "from pkg.routing import Router\nrouter = Router()\n"};
static const python_proof_file_t definition = {
"pkg/routing.py", "class Router:\n def route(self):\n return 1\n"};
EXPECT_CALL("imported instance", "pkg/routing.py", "route", "Method", 1,
{"main.py", "from pkg import router\ndef run():\n return router.route()\n"},
router, definition);
EXPECT_CALL("rebound instance", "pkg/routing.py", "route", "Method", 0,
{"main.py", "from pkg import router\ndef run():\n return router.route()\n"},
{"pkg/__init__.py", "from pkg.routing import Router\nrouter = Router()\nrouter = None\n"},
definition);
/* Calling a module variable calls what it holds, when that has a fact. */
EXPECT_CALL("variable holds a bound method", "pkg/routing.py", "route", "Method", 1,
{"main.py", "from pkg import send\ndef run():\n return send()\n"},
{"pkg/__init__.py", "from pkg.routing import Router\nrouter = Router()\n"
"send = router.route\n"},
definition);
EXPECT_CALL("instance call runs __call__", "lib.py", "__call__", "Method", 1,
{"main.py", "from lib import handler\ndef run():\n return handler()\n"},
{"lib.py", "class Handler:\n def __call__(self):\n return 1\n"
"handler = Handler()\n"});
EXPECT_CALL("variable holds an unknown value", "lib.py", "real", "Function", 0,
{"main.py", "from lib import send\ndef run():\n return send()\n"},
{"lib.py", "def real():\n return 1\nsend = make(real)\n"});
EXPECT_CALL("local alias of a function", "lib.py", "real", "Function", 1,
{"main.py", "from lib import real\ndef run():\n call = real\n return call()\n"},
{"lib.py", "def real():\n return 1\n"});
EXPECT_CALL("functools.partial calls what it wraps", "lib.py", "real", "Function", 1,
{"main.py", "from lib import send\ndef run():\n return send()\n"},
{"lib.py", "from functools import partial\ndef real(a):\n return a\n"
"send = partial(real, 1)\n"});
EXPECT_CALL("functools.partial spelled through the module", "lib.py", "real", "Function", 1,
{"main.py", "from lib import send\ndef run():\n return send()\n"},
{"lib.py", "import functools\ndef real(a):\n return a\n"
"send = functools.partial(real, 1)\n"});
EXPECT_CALL("a project partial is not functools.partial", "lib.py", "real", "Function", 0,
{"main.py", "from lib import send\ndef run():\n return send()\n"},
{"lib.py", "from mine import partial\ndef real(a):\n return a\n"
"send = partial(real, 1)\n"});
EXPECT_USAGE("module constant", "lib.py", "LIMIT", "Variable", 1,
{"main.py", "from lib import LIMIT\ndef run():\n return LIMIT\n"},
{"lib.py", "LIMIT = 3\n"});
EXPECT_USAGE("module constant rebound", "lib.py", "LIMIT", "Variable", 0,
{"main.py", "from lib import LIMIT\ndef run():\n return LIMIT\n"},
{"lib.py", "LIMIT = 3\nLIMIT = 4\n"});
PASS();
}
TEST(resolver_python_decorators_keep_identity_only_when_proven) {
static const python_proof_file_t decorators = {
"lib/deco.py",
"from functools import wraps, update_wrapper\n"
"def wraps_called(function):\n"
" def inner(*args):\n return function(*args)\n"
" return wraps(function)(inner)\n"
"def updates(function):\n"
" def inner(*args):\n return function(*args)\n"
" update_wrapper(inner, function)\n"
" return inner\n"
"def update_returned(function):\n"
" def inner(*args):\n return function(*args)\n"
" return update_wrapper(inner, function)\n"
"def both_arms(function):\n"
" if function:\n"
" def inner(*args):\n return function(*args)\n"
" else:\n"
" def inner(*args):\n return function(*args)\n"
" return wraps(function)(inner)\n"
"def one_arm(function):\n"
" if function:\n"
" def inner(*args):\n return function(*args)\n"
" else:\n"
" def inner(*args):\n return None\n"
" return wraps(function)(inner)\n"
"def one_arm_wrapped(function):\n"
" if function:\n"
" @wraps(function)\n"
" def inner(*args):\n return function(*args)\n"
" else:\n"
" def inner(*args):\n return None\n"
" return inner\n"
"def hands_on(function):\n return tagged('x')(function)\n"
"def hands_on_direct(function):\n return same(function)\n"
"def hands_to_bad(function):\n return replace(function)\n"
"class only(classmethod):\n"
" def __get__(self, instance, owner=None):\n"
" return super().__get__(instance, owner)\n"
"def same(function):\n return function\n"
"def logged(function):\n"
" @wraps(function)\n"
" def wrapper(*args, **kwargs):\n return function(*args, **kwargs)\n"
" return wrapper\n"
"def tagged(name):\n"
" def apply(function):\n return function\n"
" return apply\n"
"def either(*args, path=None):\n"
" def apply(klass):\n return klass\n"
" if not args:\n return apply\n"
" return apply(*args)\n"
"def replace(function):\n return 42\n"
"def other():\n return 2\n"
"def replaces(function):\n return other\n"
"def factory(name):\n def apply(function):\n return function\n return apply\n"
"def stubbed(function): ...\n"
"def raises(function):\n raise NotImplementedError\n"
"def nothing(function):\n pass\n"
"def opaque(function):\n return make(function)\n"
"def swallow(function):\n"
" @wraps(function)\n"
" def wrapper():\n return 1\n"
" return wrapper\n"
"def conditional(function):\n"
" if function:\n return function\n"
"def generated(function):\n yield function\n"
"class described:\n"
" def __init__(self, function):\n self.function = function\n"
" def __get__(self, instance, owner=None):\n return self.function(instance)\n"};
static const struct {
const char *decorator;
int expected;
} cases[] = {
{"@same", 1}, {"@logged", 1}, {"@tagged('x')", 1}, {"@either", 1},
{"@either(path='p')", 1}, {"@same\n@logged", 1}, {"@wraps_called", 1}, {"@updates", 1},
{"@update_returned", 1}, {"@hands_on", 1}, {"@hands_on_direct", 1}, {"@both_arms", 1},
/* A decorator it cannot see through still leaves the name on this declaration:
* what runs is dispatch, and flask's @setupmethod and django's @register.filter
* are exactly this. */
{"@opaque", 1}, {"@swallow", 1}, {"@conditional", 1},
{"@same('x')", 1}, {"@external_deco", 1}, {"@one_arm", 1}, {"@one_arm_wrapped", 1},
/* The source proves the name stops holding the function. */
{"@replace", 0}, {"@replaces", 0}, {"@factory('x')", 1}, {"@tagged", 0},
{"@nothing", 0}, {"@generated", 0}, {"@same\n@replace", 0},
{"@hands_to_bad", 1}, {"@stubbed", 1}, {"@raises", 1},
};
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
char source[768];
snprintf(source, sizeof(source),
"from lib.deco import same, logged, tagged, either, replace, nothing, opaque, "
"swallow, conditional, generated, wraps_called, updates, update_returned, "
"hands_on, hands_on_direct, hands_to_bad, both_arms, one_arm, one_arm_wrapped, "
"stubbed, raises, replaces, factory\n"
"from external import external_deco\n"
"%s\ndef target():\n return 1\ndef run():\n return target()\n",
cases[i].decorator);
const python_proof_file_t files[] = {{"main.py", source}, decorators};
ASSERT(python_expect(cases[i].decorator, files, 2, "CALLS", "main.py", "target", "Function",
cases[i].expected));
}
/* A kept declaration is an ordinary export, and an ordinary base class. */
EXPECT_CALL("kept across files", "lib/made.py", "target", "Function", 1,
{"main.py", "from lib.made import target\ndef run():\n return target()\n"},
{"lib/made.py", "from lib.deco import logged\n@logged\ndef target():\n return 1\n"},
decorators);
EXPECT_CALL("decorated base class", "lib/made.py", "save", "Method", 1,
{"main.py", "from lib.made import Base\nclass Child(Base):\n"
" def run(self):\n return self.save()\n"},
{"lib/made.py", "from lib.deco import either\n@either(path='x')\nclass Base:\n"
" def save(self):\n return 1\n"},
decorators);
EXPECT_CALL("decorated base in one file", "main.py", "save", "Method", 1,
{"main.py", "from functools import total_ordering\n@total_ordering\nclass Base:\n"
" def save(self):\n return 1\n"
"class Child(Base):\n def run(self):\n return super().save()\n"});
/* celery: @Thenable.register class AsyncResult(ResultBase). */
EXPECT_CALL("base behind an unproven decorator", "main.py", "save", "Method", 1,
{"main.py", "from external import wrap\n@wrap\nclass Base:\n"
" def save(self):\n return 1\n"
"class Child(Base):\n def run(self):\n return super().save()\n"});
EXPECT_CALL("a base the decorator discards", "main.py", "save", "Method", 0,
{"main.py", "from lib.deco import replace\n@replace\nclass Base:\n"
" def save(self):\n return 1\n"
"class Child(Base):\n def run(self):\n return super().save()\n"},
decorators);
/* flask: @setupmethod def add_url_rule, called on self from a subclass. */
EXPECT_CALL("a method behind a decorator it cannot see through", "main.py", "go", "Method", 1,
{"main.py", "from external import setup\nclass Base:\n @setup\n"
" def go(self):\n return 1\n"
"class Child(Base):\n def run(self):\n return self.go()\n"});
EXPECT_CALL("a method the decorator discards", "main.py", "go", "Method", 0,
{"main.py", "from lib.deco import nothing\nclass Base:\n @nothing\n"
" def go(self):\n return 1\n"
" def run(self):\n return self.go()\n"},
decorators);
/* celery: kombu's @cached_property, whose value is what the site calls. */
EXPECT_CALL("a property by its decorator's name is not called", "main.py", "go", "Method", 0,
{"main.py", "from elsewhere import cached_property\nclass A:\n"
" @cached_property\n def go(self):\n return make()\n"
" def run(self):\n return self.go()\n"});
EXPECT_CALL("classmethod subclass binds the function", "main.py", "go", "Method", 1,
{"main.py", "from lib.deco import only\nclass A:\n @only\n def go(cls):\n"
" return 1\ndef run():\n return A.go()\n"},
decorators);
/* Standard library facts, and only for the real module. */
EXPECT_CALL("stdlib cache", "main.py", "target", "Function", 1,
{"main.py", "import functools\n@functools.lru_cache(maxsize=1)\ndef target():\n"
" return 1\ndef run():\n return target()\n"});
EXPECT_CALL("stdlib abstract method", "main.py", "go", "Method", 1,
{"main.py", "from abc import abstractmethod\nclass A:\n @abstractmethod\n"
" def go(self):\n return 1\n"
" def run(self):\n return self.go()\n"});
EXPECT_CALL("project module named like stdlib", "main.py", "target", "Function", 0,
{"main.py", "from lib.functools import lru_cache\n@lru_cache\ndef target():\n"
" return 1\ndef run():\n return target()\n"},
{"lib/functools.py", "def lru_cache(function):\n return 42\n"});
/* A property runs on read: a read references it, and calling the result does not call it. */
{
const char *source = "class A:\n @property\n def value(self):\n return 1\n"
" def read(self):\n return self.value\n";
const python_proof_file_t files[] = {{"main.py", source}};
ASSERT(python_expect("property read", files, 1, "USAGE", "main.py", "value", "Method", 1));
/* A getter and setter are one property, and the one node stands for it. */
const char *paired = "class A:\n @property\n def value(self):\n return 1\n"
" @value.setter\n def value(self, item):\n pass\n"
" def read(self):\n return self.value\n";
const python_proof_file_t pair[] = {{"main.py", paired}};
ASSERT(python_expect("getter and setter share one node", pair, 1, "USAGE", "main.py", "value",
"Method", 1));
const char *argument = "class A:\n @property\n def value(self):\n return 1\n"
" def plain(self):\n return 2\n"
" def use(self, check):\n"
" check(self.value)\n check(self.plain)\n";
const python_proof_file_t passed[] = {{"main.py", argument}};
ASSERT(python_expect("property argument is a read", passed, 1, "USAGE", "main.py", "value", "Method", 1));
ASSERT(python_expect("property argument is no reference", passed, 1, "CALL_REFERENCE", "main.py", "value", "Method", 0));
ASSERT(python_expect("method argument is a reference", passed, 1, "CALL_REFERENCE", "main.py", "plain", "Method", 1));
const char *call = "class A:\n @property\n def value(self):\n return other\n"
" def use(self):\n return self.value()\n";
const python_proof_file_t called[] = {{"main.py", call}};
ASSERT(python_expect("property result called", called, 1, "CALLS", "main.py", "value", "Method", 0));
}
{
const char *source = "from lib.deco import described\nclass A:\n @described\n"
" def value(self):\n return 1\n"
" def read(self):\n return self.value\n"
" def use(self):\n return self.value()\n";
const python_proof_file_t files[] = {{"main.py", source}, decorators};
ASSERT(python_expect("descriptor read", files, 2, "USAGE", "main.py", "value", "Method", 1));
ASSERT(python_expect("descriptor call", files, 2, "CALLS", "main.py", "value", "Method", 0));
}
/* A decorator whose source nothing shows keeps the declaration, as on a function. */
EXPECT_CALL("unknown method decorator", "main.py", "go", "Method", 1,
{"main.py", "from external import wrap\nclass A:\n @wrap\n def go(self):\n"
" return 1\n def run(self):\n return self.go()\n"});
PASS();
}
/* Five suites because each case indexes through both drivers: together they ran past the
* 900 s per-suite cap on Windows, about 500 full indexes in all. */
/* ── Stated source roots ──────────────────────────────────────────────────
* Forms from click, flask and poetry: a src layout reached from tests and examples, and a
* namespace package kept without __init__.py. Without a manifest nothing states the layout. */
TEST(resolver_python_stated_source_root_reaches_the_module) {
static const char echo[] = "def echo():\n return 1\n";
static const char caller[] = "import click\ndef run():\n click.echo()\n";
{
const python_proof_file_t files[] = {
{"pyproject.toml", "[project]\nname = \"click\"\n"},
{"src/click/__init__.py", "from .utils import echo as echo\n"},
{"src/click/utils.py", echo},
{"tests/test_utils.py", caller},
{"examples/colors/colors.py", caller},
{"conftest.py", caller}};
ASSERT(python_expect_from("a test reaches src by name", files, 6, "tests/test_utils.py",
"CALLS", "src/click/utils.py", "echo", "Function", 1));
ASSERT(python_expect_from("an example reaches src by name", files, 6,
"examples/colors/colors.py", "CALLS", "src/click/utils.py",
"echo", "Function", 1));
ASSERT(python_expect_from("a root file reaches src by name", files, 6, "conftest.py",
"CALLS", "src/click/utils.py", "echo", "Function", 1));
}
{
const python_proof_file_t files[] = {{"src/click/__init__.py",
"from .utils import echo as echo\n"},
{"src/click/utils.py", echo},
{"tests/test_utils.py", caller}};
ASSERT(python_expect_from("no manifest states the layout", files, 3, "tests/test_utils.py",
"CALLS", "src/click/utils.py", "echo", "Function", 0));
}
{
/* flask/examples: a project of its own, depending on the enclosing one. */
const python_proof_file_t files[] = {
{"pyproject.toml", "[project]\nname = \"Flask\"\n"},
{"src/flask/__init__.py", "from .app import Flask as Flask\n"},
{"src/flask/app.py", "class Flask:\n pass\n"},
{"examples/celery/pyproject.toml", "[project]\nname = \"flask-example-celery\"\n"},
{"examples/celery/src/task_app/__init__.py",
"from flask import Flask\nfrom .views import home\ndef create_app():\n"
" home()\n return Flask()\n"},
{"examples/celery/src/task_app/views.py", echo},
{"examples/celery/tests/test_app.py",
"from task_app.views import echo\ndef run():\n echo()\n"}};
ASSERT(python_expect_from("an example reaches the enclosing project", files, 7,
"examples/celery/src/task_app/__init__.py", "CALLS",
"src/flask/app.py", "Flask", "Class", 1));
ASSERT(python_expect_from("an example's test reaches its own project", files, 7,
"examples/celery/tests/test_app.py", "CALLS",
"examples/celery/src/task_app/views.py", "echo", "Function", 1));
}
PASS();
}
TEST(resolver_python_namespace_package_under_a_source_root) {
/* poetry: src/poetry has no __init__.py and src/poetry/poetry.py shares its name. */
const python_proof_file_t files[] = {
{"pyproject.toml", "[project]\nname = \"poetry\"\n"},
{"src/poetry/poetry.py", "class Poetry:\n pass\n"},
{"src/poetry/config/__init__.py", ""},
{"src/poetry/config/source.py", "def split_key():\n return 1\n"},
{"src/poetry/config/config.py",
"from poetry.config.source import split_key\nfrom poetry.poetry import Poetry\n"
"from poetry.core.packages import Package\nfrom config.source import split_key as near\n"
"def merge():\n split_key()\n Poetry()\n Package()\n"},
{"src/poetry/config/other.py",
"from config.source import split_key\ndef merge():\n split_key()\n"},
{"src/poetry/__main__.py",
"from poetry.config.source import split_key\ndef main():\n split_key()\n"},
{"tests/helpers.py", "def helper():\n return 1\n"},
{"tests/test_config.py",
"from helpers import helper\nfrom poetry.config.source import split_key\n"
"def run():\n helper()\n split_key()\n"}};
ASSERT(python_expect_from("own namespace package", files, 9, "src/poetry/config/config.py",
"CALLS", "src/poetry/config/source.py", "split_key", "Function", 1));
ASSERT(python_expect_from("own namespace package, the same-named module", files, 9,
"src/poetry/config/config.py", "CALLS", "src/poetry/poetry.py",
"Poetry", "Class", 1));
ASSERT(python_expect_from("a package's parent is not on sys.path below a root", files, 9,
"src/poetry/config/other.py", "CALLS", "src/poetry/config/source.py",
"split_key", "Function", 0));
ASSERT(python_expect_from("a module directly in the namespace package", files, 9,
"src/poetry/__main__.py", "CALLS", "src/poetry/config/source.py",
"split_key", "Function", 1));
ASSERT(python_expect_from("a test reaches the namespace package", files, 9,
"tests/test_config.py", "CALLS", "src/poetry/config/source.py",
"split_key", "Function", 1));
ASSERT(python_expect_from("a test's neighbour stays first", files, 9, "tests/test_config.py",
"CALLS", "tests/helpers.py", "helper", "Function", 1));
PASS();
}
/* Each form a backend reads, and one decoy root it must not take from the file's other text. */
TEST(resolver_python_manifests_state_their_roots) {
static const char go[] = "def go():\n return 1\n";
static const char caller[] = "import pkg.inner\nfrom pkg.inner import go\ndef run():\n go()\n";
static const struct {
const char *what, *manifest, *text, *module;
int expected;
} forms[] = {
{"setuptools where", "pyproject.toml",
"[tool.setuptools.packages.find]\nwhere = [\"lib\"]\n", "lib/pkg/inner.py", 1},
{"setuptools package-dir", "pyproject.toml",
"[tool.setuptools]\npackage-dir = {\"\" = \"lib\"}\n", "lib/pkg/inner.py", 1},
{"setuptools package-dir table", "pyproject.toml",
"[tool.setuptools.package-dir]\n\"\" = \"lib\"\n", "lib/pkg/inner.py", 1},
{"poetry from", "pyproject.toml",
"[tool.poetry]\npackages = [\n { include = \"pkg\", from = \"lib\" }, # \"nope\"\n]\n",
"lib/pkg/inner.py", 1},
{"poetry from as a table", "pyproject.toml",
"[[tool.poetry.packages]]\ninclude = \"pkg\"\nfrom = \"lib\"\n", "lib/pkg/inner.py", 1},
{"hatch packages", "pyproject.toml",
"[tool.hatch.build.targets.wheel]\npackages = [\"lib/pkg\"]\n", "lib/pkg/inner.py", 1},
{"pdm package-dir", "pyproject.toml", "[tool.pdm.build]\npackage-dir = \"lib\"\n",
"lib/pkg/inner.py", 1},
{"maturin python-source", "pyproject.toml", "[tool.maturin]\npython-source = \"lib\"\n",
"lib/pkg/inner.py", 1},
{"pytest pythonpath", "pyproject.toml",
"[tool.pytest.ini_options]\npythonpath = [\"lib\", \".\"]\n", "lib/pkg/inner.py", 1},
{"ruff src", "pyproject.toml", "[tool.ruff]\nsrc = [\"lib\"]\n", "lib/pkg/inner.py", 1},
{"pyright extraPaths", "pyproject.toml", "[tool.pyright]\nextraPaths = [\"lib\"]\n",
"lib/pkg/inner.py", 1},
{"pytest.ini pythonpath", "pytest.ini", "[pytest]\npythonpath = lib\n", "lib/pkg/inner.py",
1},
{"tox.ini pythonpath", "tox.ini", "[pytest]\npythonpath = lib\n", "lib/pkg/inner.py", 1},
{"setup.cfg package_dir", "setup.cfg", "[options]\npackage_dir =\n = lib\n",
"lib/pkg/inner.py", 1},
{"setup.cfg where", "setup.cfg", "[options.packages.find]\nwhere = lib\n",
"lib/pkg/inner.py", 1},
{"setup.py package_dir", "setup.py", "setup(package_dir={\"\": \"lib\"})\n",
"lib/pkg/inner.py", 1},
{"setup.py find_packages", "setup.py", "setup(packages=find_packages(where='lib'))\n",
"lib/pkg/inner.py", 1},
{"convention beside setup.py", "setup.py", "setup()\n", "src/pkg/inner.py", 1},
{"a key elsewhere states nothing", "pyproject.toml",
"[tool.other]\nwhere = [\"lib\"]\n", "lib/pkg/inner.py", 0},
{"another package's mapping states nothing", "setup.cfg",
"[options]\npackage_dir =\n other = lib\n", "lib/pkg/inner.py", 0},
{"a commented root states nothing", "pyproject.toml",
"[tool.setuptools.packages.find]\nwhere = [\n # \"lib\"\n \"elsewhere\",\n]\n",
"lib/pkg/inner.py", 0},
};
for (size_t i = 0; i < sizeof(forms) / sizeof(forms[0]); i++) {
char init[64];
snprintf(init, sizeof(init), "%.*s__init__.py",
(int)(strrchr(forms[i].module, '/') - forms[i].module + 1), forms[i].module);
const python_proof_file_t files[] = {{forms[i].manifest, forms[i].text},
{init, ""},
{forms[i].module, go},
{"tests/test_a.py", caller}};
ASSERT(python_expect_from(forms[i].what, files, 4, "tests/test_a.py", "CALLS",
forms[i].module, "go", "Function", forms[i].expected));
}
PASS();
}
/* pydantic-ai's shape: members of one uv workspace import each other. */
TEST(resolver_python_workspace_members_see_each_other) {
static const char caller[] = "from beta.inner import go\ndef run():\n go()\n";
static const char go[] = "def go():\n return 1\n";
static const char workspace[] = "[tool.uv.workspace]\nmembers = [\"packages/*\", \"extra\"]\n";
static const char member[] = "[project]\nname = \"member\"\n";
{
const python_proof_file_t files[] = {{"pyproject.toml", workspace},
{"packages/alpha/pyproject.toml", member},
{"packages/alpha/src/alpha/__init__.py", ""},
{"packages/alpha/src/alpha/user.py", caller},
{"packages/beta/pyproject.toml", member},
{"packages/beta/src/beta/__init__.py", ""},
{"packages/beta/src/beta/inner.py", go}};
ASSERT(python_expect_from("a sibling member's src layout", files, 7,
"packages/alpha/src/alpha/user.py", "CALLS",
"packages/beta/src/beta/inner.py", "go", "Function", 1));
}
{
const python_proof_file_t files[] = {{"pyproject.toml", workspace},
{"extra/pyproject.toml", member},
{"extra/beta/__init__.py", ""},
{"extra/beta/inner.py", go},
{"packages/alpha/pyproject.toml", member},
{"packages/alpha/alpha/__init__.py", ""},
{"packages/alpha/alpha/user.py", caller}};
ASSERT(python_expect_from("an exact member, flat", files, 7, "packages/alpha/alpha/user.py",
"CALLS", "extra/beta/inner.py", "go", "Function", 1));
}
{
/* Not listed: another project that happens to be in the tree. */
const python_proof_file_t files[] = {{"pyproject.toml", workspace},
{"packages/alpha/pyproject.toml", member},
{"packages/alpha/alpha/__init__.py", ""},
{"packages/alpha/alpha/user.py", caller},
{"examples/beta_demo/pyproject.toml", member},
{"examples/beta_demo/beta/__init__.py", ""},
{"examples/beta_demo/beta/inner.py", go}};
ASSERT(python_expect_from("a project outside the members", files, 7,
"packages/alpha/alpha/user.py", "CALLS",
"examples/beta_demo/beta/inner.py", "go", "Function", 0));
}
{
/* Two members provide the name: nothing says which. */
const python_proof_file_t files[] = {{"pyproject.toml", workspace},
{"packages/alpha/pyproject.toml", member},
{"packages/alpha/alpha/__init__.py", ""},
{"packages/alpha/alpha/user.py", caller},
{"packages/one/pyproject.toml", member},
{"packages/one/beta/__init__.py", ""},
{"packages/one/beta/inner.py", go},
{"packages/two/pyproject.toml", member},
{"packages/two/beta/__init__.py", ""},
{"packages/two/beta/inner.py", go}};
ASSERT(python_expect_from("two members of one name", files, 10,
"packages/alpha/alpha/user.py", "CALLS",
"packages/one/beta/inner.py", "go", "Function", 0));
}
{
/* No workspace declared: siblings stay out of reach. */
const python_proof_file_t files[] = {{"packages/alpha/pyproject.toml", member},
{"packages/alpha/alpha/__init__.py", ""},
{"packages/alpha/alpha/user.py", caller},
{"packages/beta/pyproject.toml", member},
{"packages/beta/beta/__init__.py", ""},
{"packages/beta/beta/inner.py", go}};
ASSERT(python_expect_from("siblings without a workspace", files, 6,
"packages/alpha/alpha/user.py", "CALLS",
"packages/beta/beta/inner.py", "go", "Function", 0));
}
PASS();
}
TEST(resolver_python_source_root_decoys_abstain) {
static const char go[] = "def go():\n return 1\n";
static const char manifest[] = "[project]\nname = \"demo\"\n";
static const char caller[] = "import helper\ndef run():\n helper.go()\n";
{
/* A src directory that is a package holds src.helper, not helper. */
const python_proof_file_t files[] = {{"pyproject.toml", manifest},
{"src/__init__.py", ""},
{"src/helper.py", go},
{"tests/test_a.py", caller}};
ASSERT(python_expect_from("src is itself a package", files, 4, "tests/test_a.py", "CALLS",
"src/helper.py", "go", "Function", 0));
}
{
/* The standard library comes before an installed distribution. */
const python_proof_file_t files[] = {
{"pyproject.toml", manifest},
{"src/logging.py", "def getLogger():\n return 1\n"},
{"tests/test_a.py", "import logging\ndef run():\n logging.getLogger()\n"}};
ASSERT(python_expect_from("a standard library name", files, 3, "tests/test_a.py", "CALLS",
"src/logging.py", "getLogger", "Function", 0));
}
{
const python_proof_file_t files[] = {{"pyproject.toml", manifest},
{"helper.py", go},
{"src/helper.py", go},
{"tests/test_a.py", caller}};
/* The repository root comes first, as it does for a type checker. */
ASSERT(python_expect_from("root and src both have it, root", files, 4, "tests/test_a.py",
"CALLS", "helper.py", "go", "Function", 1));
ASSERT(python_expect_from("root and src both have it, src", files, 4, "tests/test_a.py",
"CALLS", "src/helper.py", "go", "Function", 0));
}
{
const python_proof_file_t files[] = {{"sdk/pyproject.toml", manifest},
{"sdk/helper.py", go},
{"sdk/src/helper.py", go},
{"sdk/tests/test_a.py", caller}};
ASSERT(python_expect_from("flat and src both have it, flat", files, 4,
"sdk/tests/test_a.py", "CALLS", "sdk/helper.py", "go", "Function",
0));
ASSERT(python_expect_from("flat and src both have it, src", files, 4, "sdk/tests/test_a.py",
"CALLS", "sdk/src/helper.py", "go", "Function", 0));
}
{
/* A flat project in a subdirectory: its own directory is the root, and its scripts
* still see their neighbours first. */
const python_proof_file_t files[] = {
{"sdk/python/pyproject.toml", manifest},
{"sdk/python/mypkg/__init__.py", ""},
{"sdk/python/mypkg/inner.py", go},
{"sdk/python/tests/test_a.py", "from mypkg.inner import go\ndef run():\n go()\n"},
{"sdk/python/scripts/helper.py", "def near():\n return 1\n"},
{"sdk/python/scripts/run.py", "from helper import near\ndef run():\n near()\n"},
{"docs/conf.py", "from mypkg.inner import go\ndef run():\n go()\n"}};
ASSERT(python_expect_from("a flat project in a subdirectory", files, 7,
"sdk/python/tests/test_a.py", "CALLS",
"sdk/python/mypkg/inner.py", "go", "Function", 1));
ASSERT(python_expect_from("its script sees its neighbour", files, 7,
"sdk/python/scripts/run.py", "CALLS",
"sdk/python/scripts/helper.py", "near", "Function", 1));
ASSERT(python_expect_from("outside that project it is not on the path", files, 7,
"docs/conf.py", "CALLS", "sdk/python/mypkg/inner.py", "go",
"Function", 0));
}
{
/* A nearer project's module of that name hides the enclosing project's. */
const python_proof_file_t files[] = {
{"pyproject.toml", manifest},
{"src/shared/__init__.py", ""},
{"src/shared/inner.py", go},
{"sub/pyproject.toml", manifest},
{"sub/src/shared/__init__.py", ""},
{"sub/tests/test_a.py", "from shared.inner import go\ndef run():\n go()\n"}};
ASSERT(python_expect_from("a nearer project hides the enclosing one", files, 6,
"sub/tests/test_a.py", "CALLS", "src/shared/inner.py", "go",
"Function", 0));
}
PASS();
}
/* ── What changes a module's namespace ────────────────────────────────────
* Forms from django: a test's `django.__file__ = None` beside `from django import forms`,
* and `from . import signals` in a package's own __init__. */
TEST(resolver_python_only_a_store_of_that_name_blocks_a_submodule) {
static const python_proof_file_t child = {"pkg/mod.py", "def real():\n return 1\n"};
static const char main_py[] = "from pkg import mod\ndef run():\n return mod.real()\n";
static const struct {
const char *patcher;
int expected;
} cases[] = {
/* Handing a module on, and storing some other name, select no other declaration. */
{"import pkg\ndef patch():\n install(pkg)\n", 1},
{"import pkg\nsaved = pkg\n", 1},
{"import pkg\nalias = pkg\nalias.__file__ = None\n", 1},
{"import pkg\npkg.__file__ = None\n", 1},
{"import pkg\ndel pkg.__file__\n", 1},
{"import pkg\nsetattr(pkg, '__file__', None)\n", 1},
{"import pkg\nwith patch.object(pkg, 'mod', None):\n pass\n", 1},
{"import pkg.mod\nimport pkg.other\npkg.other.value = 1\n", 1},
/* The name itself. */
{"import pkg\npkg.mod = None\n", 0},
{"import pkg\ndel pkg.mod\n", 0},
{"import pkg\nsetattr(pkg, 'mod', None)\n", 0},
{"import pkg\ndelattr(pkg, 'mod')\n", 0},
{"import pkg\npkg.mod, other = 1, 2\n", 0},
{"import pkg\nalias = pkg\nalias.mod = None\n", 0},
{"import pkg\nfirst = pkg\nsecond = first\nsecond.mod = None\n", 0},
/* Anything could be the name. */
{"import pkg\nsetattr(pkg, choose(), None)\n", 0},
{"import pkg\npkg.__dict__['mod'] = None\n", 0},
{"import pkg\nvars(pkg)['mod'] = None\n", 0},
};
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
const python_proof_file_t files[] = {{"pkg/__init__.py", ""},
child,
{"pkg/other.py", "value = 0\n"},
{"patcher.py", cases[i].patcher},
{"main.py", main_py}};
ASSERT(python_expect(cases[i].patcher, files, 5, "CALLS", "pkg/mod.py", "real", "Function",
cases[i].expected));
}
PASS();
}
TEST(resolver_python_writes_through_the_submodule_leave_its_identity) {
/* tests/mail: `del django.conf.settings.X` and mock.patch.object(django.utils.timezone, ...)
* change members; neither changes which module `from django.utils import timezone` is. */
const python_proof_file_t files[] = {
{"pkg/__init__.py", ""},
{"pkg/mod.py", "def real():\n return 1\n"},
{"tests/patcher.py", "import pkg.mod\ndel pkg.mod.settings\npkg.mod.real = None\n"},
{"main.py", "from pkg import mod\ndef run():\n return mod.real()\n"}};
ASSERT(python_expect("a store inside the child", files, 4, "CALLS", "pkg/mod.py", "real",
"Function", 1));
PASS();
}
/* click/__init__.py has a __getattr__ for its deprecations, and `from .types import X` above it. */
TEST(resolver_python_module_getattr_only_answers_what_is_missing) {
static const char go[] = "def real():\n return 1\n";
static const char user[] = "from pkg import mod\ndef run():\n return mod.real()\n";
static const char dotted[] = "import pkg\ndef run():\n return pkg.mod.real()\n";
static const char lazy[] = "def __getattr__(name):\n return load(name)\n";
{
char init[128];
snprintf(init, sizeof(init), "from .mod import real as real\n%s", lazy);
const python_proof_file_t files[] = {
{"pkg/__init__.py", init}, {"pkg/mod.py", go}, {"user.py", user}, {"dotted.py", dotted}};
ASSERT(python_expect_from("a child __init__ loads, by from-import", files, 4, "user.py",
"CALLS", "pkg/mod.py", "real", "Function", 1));
ASSERT(python_expect_from("a child __init__ loads, by attribute", files, 4, "dotted.py",
"CALLS", "pkg/mod.py", "real", "Function", 1));
}
{
char init[128];
snprintf(init, sizeof(init), "from . import mod\n%s", lazy);
const python_proof_file_t files[] = {
{"pkg/__init__.py", init}, {"pkg/mod.py", go}, {"user.py", user}, {"dotted.py", dotted}};
ASSERT(python_expect_from("a child __init__ binds by from-dot", files, 4, "user.py", "CALLS",
"pkg/mod.py", "real", "Function", 1));
}
{
const python_proof_file_t files[] = {
{"pkg/__init__.py", lazy}, {"pkg/mod.py", go}, {"user.py", user}, {"dotted.py", dotted}};
ASSERT(python_expect_from("a child only __getattr__ could answer for", files, 4, "user.py",
"CALLS", "pkg/mod.py", "real", "Function", 0));
}
PASS();
}
TEST(resolver_python_package_importing_its_own_child) {
static const char go[] = "def go():\n return 1\n";
static const char user[] = "from pkg import mod\ndef run():\n return mod.go()\n";
static const char direct[] = "import pkg\ndef run():\n return pkg.mod.go()\n";
static const struct {
const char *init;
int expected;
} cases[] = {
{"from . import mod\n", 1},
{"from pkg import mod\n", 1},
{"from . import mod, other\n", 1},
/* The statement is not the name's only binder. */
{"mod = None\nfrom . import mod\n", 0},
{"from . import mod\nmod = None\n", 0},
{"from .other import mod\n", 0},
};
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
const python_proof_file_t files[] = {{"pkg/__init__.py", cases[i].init},
{"pkg/mod.py", go},
{"pkg/other.py", "mod = 1\n"},
{"user.py", user},
{"main.py", direct}};
ASSERT(python_expect_from(cases[i].init, files, 5, "user.py", "CALLS", "pkg/mod.py", "go",
"Function", cases[i].expected));
ASSERT(python_expect_from(cases[i].init, files, 5, "main.py", "CALLS", "pkg/mod.py", "go",
"Function", cases[i].expected));
}
PASS();
}
/* sqlalchemy's package inits end with `__go(locals())`. Handing the namespace to the module's
* own def writes nothing when that def never spells the parameter, nor reaches it another way;
* what the body does by other means is seen as anywhere else. */
TEST(resolver_python_package_init_hands_its_namespace_on) {
static const char make[] = "def make():\n return 1\n";
static const char user[] = "from pkg import make\ndef run():\n return make()\n";
static const char direct[] = "import pkg\ndef run():\n return pkg.make()\n";
static const struct {
const char *tail;
int expected;
} cases[] = {
/* lib/sqlalchemy/orm/__init__.py */
{"from typing import Any\nfrom . import other as _sa_util\n\n\n"
"def __go(lcls: Any) -> None:\n _sa_util.preloaded.import_prefix(\"pkg\")\n\n\n"
"__go(locals())\n",
1},
/* lib/sqlalchemy/__init__.py */
{"def __go(lcls):\n from . import other\n\n other._token = \"\".join([])\n\n\n"
"__go(locals())\n",
1},
/* lib/sqlalchemy/orm/collections.py: globals the body rebinds are its own names */
{"def __go(lcls):\n global keyfunc\n from .other import keyfunc\n\n\n"
"__go(locals())\n",
1},
{"def __go(lcls):\n pass\n\n\nresult = __go(vars())\n", 1},
/* The body reaches the namespace it was given. */
{"def __go(lcls):\n lcls['make'] = None\n\n\n__go(locals())\n", 0},
{"def __go(lcls):\n helper(lcls)\n\n\ndef helper(d):\n pass\n\n\n__go(locals())\n",
0},
{"def __go(lcls):\n locals()['lcls']['make'] = None\n\n\n__go(locals())\n", 0},
{"def __go(lcls):\n eval(\"lcls.clear()\")\n\n\n__go(locals())\n", 0},
{"import sys\n\n\ndef __go(lcls):\n sys._getframe().f_locals['lcls'].clear()\n\n\n"
"__go(locals())\n",
0},
{"def __go(lcls):\n def inner():\n lcls.clear()\n inner()\n\n\n"
"__go(locals())\n",
0},
{"def __go(first, lcls=None):\n first['make'] = None\n\n\n__go(locals())\n", 0},
{"def __go(*args):\n args[0]['make'] = None\n\n\n__go(locals())\n", 0},
{"def __go(lcls):\n global make\n make = None\n\n\n__go(locals())\n", 0},
/* The callee is not provably this module's one plain def at the call. */
{"from .other import __go\n__go(locals())\n", 0},
{"def __go(lcls):\n pass\n\n\n__go = print\n__go(locals())\n", 0},
{"__go(locals())\n\n\ndef __go(lcls):\n pass\n", 0},
{"def wrap(f):\n return f\n\n\n@wrap\ndef __go(lcls):\n pass\n\n\n__go(locals())\n",
0},
{"def __go(lcls):\n pass\n\n\nfrom .other import *\n__go(locals())\n", 0},
{"def __go(other, lcls):\n pass\n\n\n__go(1, locals())\n", 0},
{"__go = lambda lcls: None\n__go(locals())\n", 0},
};
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
char init[640];
snprintf(init, sizeof(init), "from .impl import make\n%s", cases[i].tail);
const python_proof_file_t files[] = {{"pkg/__init__.py", init},
{"pkg/impl.py", make},
{"pkg/other.py", "keyfunc = 1\n__all__ = []\n"},
{"user.py", user},
{"main.py", direct}};
ASSERT(python_expect_from(cases[i].tail, files, 5, "user.py", "CALLS", "pkg/impl.py",
"make", "Function", cases[i].expected));
ASSERT(python_expect_from(cases[i].tail, files, 5, "main.py", "CALLS", "pkg/impl.py",
"make", "Function", cases[i].expected));
}
PASS();
}
/* ── What a call of a class, and of an unannotated method, gives ──────────
* Forms from django and celery. */
TEST(resolver_python_special_methods_that_read_through_the_base) {
/* django.utils.functional.LazyObject: answers through super, declared members stand. */
EXPECT_CALL("__getattribute__ reading through super", "main.py", "go", "Method", 1,
{"main.py",
"class Lazy:\n def __getattribute__(self, name):\n"
" if name == '_wrapped':\n"
" return super().__getattribute__(name)\n"
" value = super().__getattribute__(name)\n return value\n"
" def go(self):\n return 1\n"
" def run(self):\n return self.go()\n"});
EXPECT_CALL("__getattribute__ answering by itself", "main.py", "go", "Method", 0,
{"main.py", "class Lazy:\n def __getattribute__(self, name):\n"
" return name\n def go(self):\n return 1\n"
" def run(self):\n return self.go()\n"});
/* django Operation.__new__ allocates; celery chain.__new__ allocates on one path. */
static const char *allocating[] = {
" self = object.__new__(cls)\n return self\n",
" return super().__new__(cls)\n",
" return super(Made, cls).__new__(cls)\n",
" if args:\n return reduce(args)\n return super().__new__(cls)\n"};
for (size_t i = 0; i < sizeof(allocating) / sizeof(allocating[0]); i++) {
char source[512];
snprintf(source, sizeof(source),
"class Made:\n def __new__(cls, *args):\n%s"
" def go(self):\n return 1\n"
"def run():\n made = Made()\n return made.go()\n",
allocating[i]);
const python_proof_file_t file = {"main.py", source};
ASSERT(python_expect(allocating[i], &file, 1, "CALLS", "main.py", "go", "Method", 1));
}
EXPECT_CALL("__new__ that never allocates", "main.py", "go", "Method", 0,
{"main.py", "class Made:\n def __new__(cls):\n return other\n"
" def go(self):\n return 1\n"
"def run():\n made = Made()\n return made.go()\n"});
PASS();
}
TEST(resolver_python_returns_follow_the_receiver) {
static const char signature[] =
"class Signature:\n def set(self, **options):\n return self\n"
" def clone(self):\n return copy.copy(self)\n"
" def apply(self):\n return 1\n"
" @classmethod\n def make(cls):\n return cls()\n"
"class Chord(Signature):\n def apply(self):\n return 2\n";
char source[768];
/* celery: chord(...).set(...) is a chord, although set is declared on Signature. */
snprintf(source, sizeof(source), "import copy\n%sdef run():\n return Chord().set().apply()\n",
signature);
{
const python_proof_file_t file = {"main.py", source};
python_route_release(&python_routes[0]);
python_route_release(&python_routes[1]);
ASSERT(python_expect("return self gives the receiver", &file, 1, "CALLS", "main.py",
"apply", "Method", 1));
ASSERT_EQ(python_route_edges(&file, 1, "main.py", "CALLS", "main.py", "set", "Method",
false),
1);
}
{
/* Two files, so the two `apply`s can be told apart. */
const python_proof_file_t files[] = {
{"sig.py", "class Signature:\n def set(self):\n return self\n"
" def apply(self):\n return 1\n"},
{"main.py", "from sig import Signature\nclass Chord(Signature):\n"
" def apply(self):\n return 2\n"
"def run():\n return Chord().set().apply()\n"}};
ASSERT(python_expect("return self selects the subclass's method", files, 2, "CALLS",
"main.py", "apply", "Method", 1));
ASSERT(python_expect("return self does not select the base's", files, 2, "CALLS", "sig.py",
"apply", "Method", 0));
}
/* django: BaseExpression.copy returns copy.copy(self). */
EXPECT_CALL("copy.copy is its argument", "main.py", "only", "Method", 1,
{"main.py", "import copy\nclass A:\n def clone(self):\n"
" return copy.copy(self)\n def only(self):\n"
" return 1\n"
"def run():\n return A().clone().only()\n"});
EXPECT_CALL("a project copy is not the standard library's", "main.py", "only", "Method", 0,
{"main.py", "from lib import copy\nclass A:\n def clone(self):\n"
" return copy(self)\n def only(self):\n"
" return 1\n"
"def run():\n return A().clone().only()\n"},
{"lib.py", "def copy(value):\n return 1\n"});
/* django: ModelAdmin.get_changelist returns the class, get_changelist_instance calls it. */
EXPECT_CALL("a method returning a class", "main.py", "filters", "Method", 1,
{"main.py", "class Listing:\n def filters(self):\n return 1\n"
"class Admin:\n def kind(self):\n return Listing\n"
" def make(self):\n kind = self.kind()\n"
" return kind()\n"
"def run():\n return Admin().make().filters()\n"});
/* django.contrib.sitemaps: an unannotated property's value is what it returns. */
EXPECT_CALL("an unannotated property", "main.py", "page", "Method", 1,
{"main.py", "class Paginator:\n def page(self):\n return 1\n"
"class Sitemap:\n @property\n def paginator(self):\n"
" return Paginator()\n"
" def run(self):\n return self.paginator.page()\n"});
EXPECT_CALL("a property whose returns disagree", "main.py", "page", "Method", 0,
{"main.py", "class Paginator:\n def page(self):\n return 1\n"
"class Other:\n def page(self):\n return 2\n"
"class Sitemap:\n @property\n def paginator(self):\n"
" if self:\n return Paginator()\n"
" return Other()\n"
" def run(self):\n return self.paginator.page()\n"});
PASS();
}
/* ── Which binder reaches a site ──────────────────────────────────────────
* Forms from django.utils.archive, django.db.models.base and contrib.postgres.constraints. */
TEST(resolver_python_with_and_except_bind_what_they_say) {
static const char archive[] =
"class Archive:\n def __enter__(self):\n return self\n"
" def __exit__(self, *args):\n return None\n"
" def extract(self):\n return 1\n"
"class Other:\n def extract(self):\n return 2\n"
"class Shut:\n def __enter__(self):\n return Other()\n"
" def __exit__(self, *args):\n return None\n";
char source[1024];
snprintf(source, sizeof(source),
"%sdef run():\n with Archive() as archive:\n archive.extract()\n", archive);
{
const python_proof_file_t file = {"main.py", source};
ASSERT(python_expect("with-as is __enter__'s value", &file, 1, "CALLS", "main.py",
"extract", "Method", 1));
}
{
/* Both defs of extract share a name: the query counts targets per class below. */
const python_proof_file_t files[] = {
{"lib.py", archive},
{"main.py",
"from lib import Shut\ndef run():\n with Shut() as held:\n held.extract()\n"}};
ASSERT_EQ(python_route_edges(files, 2, "main.py", "CALLS", "lib.py", "extract", "Method",
false),
1);
ASSERT(python_expect("__enter__ returning another class", files, 2, "CALLS", "lib.py",
"extract", "Method", 1));
}
EXPECT_CALL("with-as with no __enter__ of its own", "main.py", "extract", "Method", 0,
{"main.py", "from external import Base\nclass Archive(Base):\n"
" def extract(self):\n return 1\n"
"def run():\n with Archive() as archive:\n archive.extract()\n"});
static const char errors[] = "class Invalid(Exception):\n def update(self):\n return 1\n"
"class Other(Exception):\n def update(self):\n return 2\n";
{
char text[512];
snprintf(text, sizeof(text),
"from errors import Invalid, Other\ndef run():\n try:\n pass\n"
" except Invalid as e:\n e.update()\n");
const python_proof_file_t files[] = {{"errors.py", errors}, {"main.py", text}};
ASSERT(python_expect("except-as is the class", files, 2, "CALLS", "errors.py", "update",
"Method", 1));
}
{
/* base.py: one name across two clauses; each clause sees its own class. */
const python_proof_file_t files[] = {
{"errors.py", "class Invalid(Exception):\n def only(self):\n return 1\n"
"class Other(Exception):\n def rest(self):\n return 2\n"},
{"main.py",
"from errors import Invalid, Other\ndef run():\n try:\n pass\n"
" except Invalid as e:\n e.only()\n except Other as e:\n e.rest()\n"}};
ASSERT(python_expect("first clause", files, 2, "CALLS", "errors.py", "only", "Method", 1));
ASSERT(python_expect("second clause", files, 2, "CALLS", "errors.py", "rest", "Method", 1));
}
{
const python_proof_file_t files[] = {
{"errors.py", errors},
{"main.py", "from errors import Invalid, Other\ndef run():\n try:\n pass\n"
" except (Invalid, Other) as e:\n e.update()\n"}};
ASSERT(python_expect("a tuple of classes says neither", files, 2, "CALLS", "errors.py",
"update", "Method", 0));
}
PASS();
}
TEST(resolver_python_the_binder_every_path_passes_last) {
static const char classes[] = "class A:\n def go(self):\n return 1\n"
" def clone(self):\n return self\n"
"class B:\n def go(self):\n return 2\n";
static const struct {
const char *what, *body;
int a, b;
} cases[] = {
{"straight line", " x = A()\n x = B()\n x.go()\n", 0, 1},
{"before the rebinding", " x = A()\n x.go()\n x = B()\n", 1, 0},
/* contrib/postgres/constraints.py: the loop variable rebound by a real class. */
{"rebinding a loop variable",
" for x in items:\n x = A()\n x.go()\n", 1, 0},
{"a nested use after the binder", " x = A()\n if c:\n x.go()\n x = B()\n", 1, 0},
{"inside an arm", " x = B()\n if c:\n x = A()\n x.go()\n", 1, 0},
{"after an arm, either could reach", " x = B()\n if c:\n x = A()\n x.go()\n",
0, 0},
{"the right side reads the earlier binder", " x = A()\n x = x.clone()\n x.go()\n",
1, 0},
/* A later binder comes back round through the loop. */
{"a loop that rebinds later", " x = A()\n for i in items:\n x.go()\n x = B()\n",
0, 0},
{"a loop that rebinds elsewhere", " x = A()\n for i in items:\n x.go()\n x = B()\n",
1, 0},
{"a while that rebinds later", " x = A()\n while c:\n x.go()\n x = B()\n",
0, 0},
{"binder and use in one iteration", " for i in items:\n x = A()\n x.go()\n x = B()\n",
1, 0},
{"another binder form", " x = A()\n for x in items:\n x.go()\n", 0, 0},
{"a None between", " x = A()\n x = None\n x.go()\n", 0, 0},
{"something unfollowable between", " x = A()\n x = pick()\n x.go()\n", 0, 0},
{"a parameter, rebound later", " x.go()\n x = A()\n", 0, 0},
};
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
char source[512];
snprintf(source, sizeof(source), "from lib import A, B\ndef run(x, items, c):\n%s",
cases[i].body);
const python_proof_file_t files[] = {{"lib.py", classes}, {"main.py", source}};
char label[96];
snprintf(label, sizeof(label), "%s, A", cases[i].what);
python_route_release(&python_routes[0]);
python_route_release(&python_routes[1]);
/* The two classes share go: their nodes differ by start line, so count by class. */
int a = python_route_edges(files, 2, "main.py", "CALLS", "lib.py", "go", "Method", false);
ASSERT_EQ(a, cases[i].a + cases[i].b);
ASSERT(python_expect(label, files, 2, "CALLS", "lib.py", "go", "Method",
cases[i].a + cases[i].b));
}
/* Which class, for the cases where only one is right. */
EXPECT_CALL("straight line picks the last", "lib.py", "only_b", "Method", 1,
{"lib.py", "class A:\n def only_a(self):\n return 1\n"
"class B:\n def only_b(self):\n return 2\n"},
{"main.py", "from lib import A, B\ndef run():\n x = A()\n x = B()\n"
" x.only_b()\n"});
EXPECT_CALL("and not the one before", "lib.py", "only_a", "Method", 0,
{"lib.py", "class A:\n def only_a(self):\n return 1\n"
"class B:\n def only_b(self):\n return 2\n"},
{"main.py", "from lib import A, B\ndef run():\n x = A()\n x = B()\n"
" x.only_a()\n"});
PASS();
}
TEST(resolver_python_names_a_module_states_only_for_checkers) {
/* celery/__init__.py: lazy at run time, stated under TYPE_CHECKING. */
static const char canvas[] = "class chain:\n def link(self):\n return 1\n";
static const char user[] = "from pkg import chain\ndef run():\n s = chain()\n s.link()\n";
{
const python_proof_file_t files[] = {
{"pkg/__init__.py", "from typing import TYPE_CHECKING\nif TYPE_CHECKING:\n"
" from pkg.canvas import chain\n"
"def __getattr__(name):\n return load(name)\n"},
{"pkg/canvas.py", canvas},
{"main.py", user}};
ASSERT(python_expect("the class", files, 3, "CALLS", "pkg/canvas.py", "chain", "Class", 1));
ASSERT(python_expect("its method", files, 3, "CALLS", "pkg/canvas.py", "link", "Method", 1));
}
{
const python_proof_file_t files[] = {
{"pkg/__init__.py", "from typing import TYPE_CHECKING\nif TYPE_CHECKING:\n"
" from pkg.canvas import chain\nelse:\n chain = None\n"},
{"pkg/canvas.py", canvas},
{"main.py", user}};
ASSERT(python_expect("a runtime binder competes", files, 3, "CALLS", "pkg/canvas.py",
"chain", "Class", 0));
}
{
/* celery: `local.recreate_module(__name__, ...)` builds the module at run time. */
const python_proof_file_t handed[] = {
{"pkg/__init__.py", "from typing import TYPE_CHECKING\nif TYPE_CHECKING:\n"
" from pkg.canvas import chain\n"
"import local\nlocal.recreate_module(__name__, by={})\n"},
{"pkg/canvas.py", canvas},
{"main.py", user}};
ASSERT(python_expect("a module that hands on its own name", handed, 3, "CALLS",
"pkg/canvas.py", "chain", "Class", 1));
/* Nothing provides the name at run time: no __getattr__, no binder. */
const python_proof_file_t files[] = {
{"pkg/__init__.py", "from typing import TYPE_CHECKING\nif TYPE_CHECKING:\n"
" from pkg.canvas import chain\n"},
{"pkg/canvas.py", canvas},
{"main.py", user}};
ASSERT(python_expect("a closed module's checker-only name", files, 3, "CALLS",
"pkg/canvas.py", "chain", "Class", 0));
}
{
const python_proof_file_t files[] = {
{"pkg/__init__.py", "from typing import TYPE_CHECKING\nif TYPE_CHECKING:\n"
" from pkg.canvas import *\n"},
{"pkg/canvas.py", canvas},
{"main.py", user}};
ASSERT(python_expect("a closed module's checker-only star", files, 3, "CALLS",
"pkg/canvas.py", "chain", "Class", 0));
}
{
/* A lazy package states a whole module's names by a star under TYPE_CHECKING. */
const python_proof_file_t files[] = {
{"pkg/__init__.py", "import typing\nif typing.TYPE_CHECKING:\n from .canvas import *\n"
"def __getattr__(name):\n return load(name)\n"},
{"pkg/canvas.py", "__all__ = ['chain']\nclass chain:\n def link(self):\n"
" return 1\nclass hidden:\n pass\n"},
{"main.py", "from pkg import chain, hidden\ndef run():\n hidden()\n s = chain()\n"
" s.link()\n"}};
ASSERT(python_expect("a star under TYPE_CHECKING", files, 3, "CALLS", "pkg/canvas.py", "link",
"Method", 1));
ASSERT(python_expect("what that star does not export", files, 3, "CALLS", "pkg/canvas.py",
"hidden", "Class", 0));
}
{
const python_proof_file_t files[] = {
{"pkg/__init__.py", "import os\nif os.environ:\n from .canvas import *\n"
"else:\n from .other import *\n"},
{"pkg/canvas.py", "__all__ = ['chain']\nclass chain:\n pass\n"},
{"pkg/other.py", "__all__ = ['chain']\nclass chain:\n pass\n"},
{"main.py", "from pkg import chain\ndef run():\n chain()\n"}};
ASSERT(python_expect("two stars a condition chooses between", files, 4, "CALLS",
"pkg/canvas.py", "chain", "Class", 0));
ASSERT(python_expect("the other of those stars", files, 4, "CALLS", "pkg/other.py", "chain",
"Class", 0));
}
/* A class body's `X = X` reads X from outside: celery's ResultConsumer. */
EXPECT_CALL("a class attribute named as what it holds", "main.py", "go", "Method", 1,
{"main.py", "class Consumer:\n def go(self):\n return 1\n"
"class Backend:\n Consumer = Consumer\n def make(self):\n"
" return self.Consumer()\n"
"def run():\n return Backend().make().go()\n"});
/* tests/migrations/test_writer.py: exec's assignments go to its third argument. */
EXPECT_CALL("exec with globals() and its own locals", "main.py", "local", "Function", 1,
{"main.py", "def local():\n return 1\ndef safe(code):\n d = {}\n"
" exec(code, globals(), d)\ndef run():\n return local()\n"});
EXPECT_CALL("exec into globals() itself", "main.py", "local", "Function", 0,
{"main.py", "def local():\n return 1\ndef safe(code):\n"
" exec(code, globals())\ndef run():\n return local()\n"});
/* A computed key names no binding the audit saw. */
EXPECT_CALL("a computed globals() store", "main.py", "local", "Function", 1,
{"main.py", "def local():\n return 1\nglobals()[key()] = None\n"
"def run():\n return local()\n"});
EXPECT_CALL("a literal globals() store", "main.py", "local", "Function", 0,
{"main.py", "def local():\n return 1\nglobals()['local'] = None\n"
"def run():\n return local()\n"});
{
/* What the module lacks is unknowable after one, so a submodule is unproven. */
const python_proof_file_t files[] = {
{"pkg/__init__.py", "globals()[key()] = None\n"},
{"pkg/mod.py", "def real():\n return 1\n"},
{"main.py", "from pkg import mod\ndef run():\n return mod.real()\n"}};
ASSERT(python_expect("absence after a computed store", files, 3, "CALLS", "pkg/mod.py",
"real", "Function", 0));
}
/* test_geos_mutation.py: a read-only method of the view. */
EXPECT_CALL("vars().items() reads", "main.py", "local", "Function", 1,
{"main.py", "def local():\n return 1\nnames = list(vars().items())\n"
"def run():\n return local()\n"});
PASS();
}
/* click's @t.overload def prompt, and Command.main. */
TEST(resolver_python_overload_stubs_are_one_declaration) {
static const char lib[] =
"import typing as t\nfrom typing import overload\ndef helper():\n return 1\n"
"@t.overload\ndef prompt(text: str) -> str: ...\n"
"@t.overload\ndef prompt(text: int) -> int: ...\n"
"def prompt(text):\n return helper()\n"
"class Command:\n @overload\n def main(self, a: str) -> str: ...\n"
" @overload\n def main(self, a: int) -> int: ...\n"
" def main(self, a):\n return self.invoke(a)\n"
" def invoke(self, a):\n return helper()\n";
static const char user[] = "from lib import prompt, Command\ndef run():\n prompt('x')\n"
" Command().main('x')\n";
const python_proof_file_t files[] = {{"lib.py", lib}, {"main.py", user}};
ASSERT(python_expect("an overloaded function", files, 2, "CALLS", "lib.py", "prompt",
"Function", 1));
ASSERT(python_expect("an overloaded method", files, 2, "CALLS", "lib.py", "main", "Method", 1));
ASSERT(python_expect_from("from an overloaded function's body", files, 2, "lib.py", "CALLS",
"lib.py", "helper", "Function", 2));
ASSERT(python_expect_from("from an overloaded method's body", files, 2, "lib.py", "CALLS",
"lib.py", "invoke", "Method", 1));
/* A project decorator of that name proves nothing: two defs, the later one rebinds. */
EXPECT_CALL("a project decorator named overload", "main.py", "go", "Function", 0,
{"main.py", "from project import overload\n@overload\ndef go():\n return 1\n"
"def go():\n return 2\ndef run():\n return go()\n"},
{"project.py", "def overload(function):\n return function\n"});
PASS();
}
TEST(resolver_python_loading_a_child_the_parent_imported) {
/* django.db.models: `from django.db.models import signals` in its own __init__, and other
* files load django.db.models.signals. Both name the one module. */
const python_proof_file_t files[] = {
{"pkg/__init__.py", "from pkg import signals\n"},
{"pkg/signals.py", "class Signal:\n def send(self):\n return 1\n"
"pre_delete = Signal()\n"},
{"loader.py", "from pkg.signals import pre_delete\n"},
{"main.py", "from pkg import signals\ndef run():\n return signals.pre_delete.send()\n"}};
ASSERT(python_expect("through the parent", files, 4, "CALLS", "pkg/signals.py", "send",
"Method", 1));
/* Under another name the parent's binding is not the module's own attribute. */
const python_proof_file_t renamed[] = {
{"pkg/__init__.py", "from pkg.other import value as signals\n"},
{"pkg/other.py", "value = 1\n"},
{"pkg/signals.py", "class Signal:\n def send(self):\n return 1\n"
"pre_delete = Signal()\n"},
{"loader.py", "from pkg.signals import pre_delete\n"},
{"main.py", "from pkg import signals\ndef run():\n return signals.pre_delete.send()\n"}};
ASSERT(python_expect("a value of that name competes", renamed, 5, "CALLS", "pkg/signals.py",
"send", "Method", 0));
PASS();
}
/* ── What annotations and isinstance say ──────────────────────────────────
* Forms from click (dict[str, _Option], -> list[Parameter]), httpx (self: T -> T, async with),
* poetry (isinstance on a loop variable) and django (isinstance on self.rhs). */
TEST(resolver_python_annotated_containers_hold_their_element) {
static const char lib[] =
"from __future__ import annotations\nimport typing as t\n"
"from typing import Iterator, Optional, Self, TypeVar\n"
"from contextlib import contextmanager\nT = TypeVar('T', bound='Client')\n"
"class Param:\n def clean(self):\n return 1\n"
"class Option:\n def process(self):\n return 1\n"
"class Response:\n def read(self):\n return 1\n"
"class Gen(t.Generic[T]):\n def own(self):\n return 1\n"
"class Client:\n def __init__(self):\n self._opts: dict[str, Option] = {}\n"
" self._params: list[Param] = []\n"
" def __enter__(self: T) -> T:\n return self\n"
" def chain(self) -> Self:\n return self\n"
" def only(self):\n return 1\n"
" def params(self) -> list[Param]:\n return self._params\n"
" def first(self) -> Optional[Param]:\n return None\n"
" @contextmanager\n def stream(self) -> Iterator[Response]:\n"
" yield Response()\n"
"class Sub(Client):\n def only(self):\n return 2\n";
static const struct {
const char *what, *body, *name;
int expected;
} cases[] = {
{"for over a returned list",
"def run(c: Client):\n for p in c.params():\n p.clean()\n", "clean", 1},
{"for over an annotated attribute",
"def run(c: Client):\n for p in c._params:\n p.clean()\n", "clean", 1},
{"a comprehension", "def run(c: Client):\n return [p.clean() for p in c._params]\n",
"clean", 1},
{"sorted keeps what it was given",
"def run(c: Client):\n for p in sorted(c._params, key=None):\n p.clean()\n",
"clean", 1},
{"reversed keeps what it was given",
"def run(c: Client):\n for p in reversed(c._params):\n p.clean()\n", "clean", 1},
{"a project function named sorted says nothing",
"from other import sorted\ndef run(c: Client):\n for p in sorted(c._params):\n"
" p.clean()\n",
"clean", 0},
{"a subscript of a list annotation",
"def run(items: list[Param]):\n return items[0].clean()\n", "clean", 1},
{"a slice stays a list",
"def run(items: list[Param]):\n for p in items[1:]:\n p.clean()\n", "clean", 1},
{"a dict value by key", "def run(c: Client, k):\n c._opts[k].process()\n", "process", 1},
{"a dict value by get", "def run(c: Client, k):\n c._opts.get(k).process()\n", "process",
1},
{"dict values", "def run(c: Client):\n for o in c._opts.values():\n o.process()\n",
"process", 1},
{"iterating a dict yields keys",
"def run(c: Client):\n for o in c._opts:\n o.process()\n", "process", 0},
{"items, the value",
"def run(c: Client):\n for k, o in c._opts.items():\n o.process()\n", "process",
1},
{"items of a class that has its own",
"class Mine:\n def items(self):\n return [(1, 2)]\n"
" def values(self) -> list[Param]:\n return []\n"
"def run(m: Mine):\n for k, p in m.items():\n p.clean()\n",
"clean", 0},
{"items, the key says nothing",
"def run(c: Client):\n for o, k in c._opts.items():\n o.process()\n", "process",
0},
{"enumerate, the element",
"def run(c: Client):\n for i, p in enumerate(c._params):\n p.clean()\n",
"clean", 1},
{"enumerate, the count says nothing",
"def run(c: Client):\n for p, i in enumerate(c._params):\n p.clean()\n",
"clean", 0},
{"zip, by position",
"def run(c: Client, xs):\n for x, p in zip(xs, c._params):\n p.clean()\n",
"clean", 1},
{"zip, the other position",
"def run(c: Client, xs):\n for p, x in zip(xs, c._params):\n p.clean()\n",
"clean", 0},
{"a project enumerate says nothing",
"from other import enumerate\ndef run(c: Client):\n"
" for i, p in enumerate(c._params):\n p.clean()\n",
"clean", 0},
{"a walrus", "def run(c: Client):\n if (p := c.first()):\n p.clean()\n", "clean",
1},
{"a typing alias",
"import typing as t\ndef run(items: t.Sequence[Param]):\n for p in items:\n"
" p.clean()\n",
"clean", 1},
{"typing.cast", "import typing as t\ndef run(v):\n t.cast('Param', v).clean()\n", "clean",
1},
{"a project cast is not typing's",
"from other import cast\ndef run(v):\n cast('Param', v).clean()\n", "clean", 0},
{"type[X] is the class",
"def run(kind: type[Param]):\n return kind().clean()\n", "clean", 1},
{"a generic of the project's own",
"def run(g: Gen[int]):\n return g.own()\n", "own", 1},
{"a contextmanager's yielded type",
"def run(c: Client):\n with c.stream() as r:\n r.read()\n", "read", 1},
/* Nothing here says which class holds the name. */
{"an unannotated parameter", "def run(items):\n for p in items:\n p.clean()\n",
"clean", 0},
{"nested containers are not carried",
"def run(items: list[list[Param]]):\n for p in items:\n p.clean()\n", "clean", 0},
{"a tuple of two types is not a sequence of one",
"def run(items: tuple[Param, Option]):\n return items[0].clean()\n", "clean", 0},
};
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
char source[640];
snprintf(source, sizeof(source), "from lib import Client, Param, Option, Gen\n%s",
cases[i].body);
const python_proof_file_t files[] = {{"lib.py", lib},
{"other.py", "def sorted(x):\n return 1\n"
"def cast(x, y):\n return 1\n"
"def enumerate(x):\n return 1\n"},
{"main.py", source}};
ASSERT(python_expect(cases[i].what, files, 3, "CALLS", "lib.py", cases[i].name, "Method",
cases[i].expected));
}
/* A method annotated to return its receiver gives the receiver's class. */
{
const python_proof_file_t files[] = {
{"lib.py", lib},
{"main.py", "from lib import Sub\ndef run(c: Sub):\n c.chain().only()\n"
" with c as held:\n held.only()\n"}};
int found = python_route_edges(files, 2, "main.py", "CALLS", "lib.py", "only", "Method",
false);
ASSERT_EQ(found, 1);
}
PASS();
}
TEST(resolver_python_isinstance_narrows_where_it_holds) {
static const char classes[] = "class Stmt:\n def rename(self):\n return 1\n"
"class Other:\n def rename(self):\n return 2\n"
"def make():\n return None\n";
static const struct {
const char *what, *body;
int expected;
} cases[] = {
{"a parameter", "def run(x):\n if isinstance(x, Stmt):\n x.rename()\n", 1},
{"a loop variable",
"def run(items):\n for x in items:\n if isinstance(x, Stmt):\n"
" x.rename()\n",
1},
{"self's attribute",
"class H:\n def run(self):\n if isinstance(self.rhs, Stmt):\n"
" self.rhs.rename()\n",
1},
{"an early exit", "def run(x):\n if not isinstance(x, Stmt):\n return None\n"
" x.rename()\n", 1},
{"an early raise", "def run(x):\n if not isinstance(x, Stmt):\n raise ValueError()\n"
" x.rename()\n", 1},
{"an assert", "def run(x):\n assert isinstance(x, Stmt)\n x.rename()\n", 1},
{"a conjunct", "def run(x, c):\n if c and isinstance(x, Stmt):\n x.rename()\n", 1},
{"after the other conjunct",
"def run(x, c):\n if isinstance(x, Stmt) and x.rename():\n pass\n", 1},
{"elif", "def run(x):\n if x:\n pass\n elif isinstance(x, Stmt):\n"
" x.rename()\n", 1},
/* Where it does not hold. */
{"after the if", "def run(x):\n if isinstance(x, Stmt):\n pass\n x.rename()\n",
0},
{"the else", "def run(x):\n if isinstance(x, Stmt):\n pass\n else:\n"
" x.rename()\n", 0},
{"a not without the exit",
"def run(x):\n if not isinstance(x, Stmt):\n print(x)\n x.rename()\n", 0},
{"a tuple of classes",
"def run(x):\n if isinstance(x, (Stmt, Other)):\n x.rename()\n", 0},
{"rebound inside", "def run(x):\n if isinstance(x, Stmt):\n x = make()\n"
" x.rename()\n", 0},
{"stored inside",
"class H:\n def run(self):\n if isinstance(self.rhs, Stmt):\n"
" self.rhs = make()\n self.rhs.rename()\n",
0},
{"in a nested function",
"def run(x):\n if isinstance(x, Stmt):\n def later():\n x.rename()\n",
0},
{"a shadowed isinstance",
"def run(x, isinstance):\n if isinstance(x, Stmt):\n x.rename()\n", 0},
{"a computed value",
"def run(x):\n if isinstance(x.pop(), Stmt):\n x.pop().rename()\n", 0},
};
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
char source[512];
snprintf(source, sizeof(source), "from lib import Stmt, Other, make\n%s", cases[i].body);
const python_proof_file_t files[] = {{"lib.py", classes}, {"main.py", source}};
ASSERT(python_expect(cases[i].what, files, 2, "CALLS", "lib.py", "rename", "Method",
cases[i].expected));
}
PASS();
}
/* ── The smaller shapes django's tree has ───────────────────────────────── */
TEST(resolver_python_class_and_module_shapes) {
/* A class stated inside a class, read through either. */
EXPECT_CALL("a nested class by its owner", "main.py", "go", "Method", 1,
{"main.py", "class Tests:\n class Form:\n def go(self):\n"
" return 1\n"
" def run(self):\n form = Tests.Form()\n"
" return form.go()\n"});
EXPECT_CALL("a nested class by self", "main.py", "go", "Method", 1,
{"main.py", "class Tests:\n class Form:\n def go(self):\n"
" return 1\n"
" def run(self):\n return self.Form().go()\n"});
/* geos: ListMixin stores self._get_single_internal only when no subclass declares it. */
EXPECT_CALL("a store in a base beyond the declaring class", "main.py", "go", "Method", 1,
{"main.py", "class Mixin:\n def __init__(self):\n self.go = make()\n"
"class Child(Mixin):\n def go(self):\n return 1\n"
" def run(self):\n return self.go()\n"});
EXPECT_CALL("a store in the declaring class", "main.py", "go", "Method", 0,
{"main.py", "class Child:\n def __init__(self):\n self.go = make()\n"
" def go(self):\n return 1\n"
" def run(self):\n return self.go()\n"});
/* point.py: a property's setter is the same property. */
{
const python_proof_file_t files[] = {
{"main.py", "def helper():\n return 1\nclass P:\n @property\n def x(self):\n"
" return helper()\n @x.setter\n def x(self, value):\n"
" helper()\n"}};
ASSERT(python_expect_from("calls from a property with a setter", files, 1, "main.py", "CALLS",
"main.py", "helper", "Function", 1));
}
/* tests/backends: a subclass's store selects over its base's, as a method would. */
{
const python_proof_file_t files[] = {
{"lib.py", "class Table:\n def only_table(self):\n return 1\n"
"class Columns:\n def only_columns(self):\n return 2\n"},
{"main.py", "from lib import Table, Columns\nclass Base:\n def setUp(self):\n"
" self.reference = Table()\n"
"class Sub(Base):\n def setUp(self):\n"
" self.reference = Columns()\n"
" def run(self):\n return self.reference.only_columns()\n"}};
ASSERT(python_expect("the nearer store selects", files, 2, "CALLS", "lib.py", "only_columns",
"Method", 1));
}
/* click: self.type: ParamType = ... in Parameter, self.type = convert(...) in Option. */
EXPECT_CALL("an annotation holds what a subclass stores", "main.py", "go", "Method", 1,
{"main.py", "class Kind:\n def go(self):\n return 1\n"
"class Base:\n def __init__(self):\n"
" self.kind: Kind = make()\n"
"class Sub(Base):\n def set(self):\n self.kind = other()\n"
" def run(self):\n return self.kind.go()\n"});
/* tests: cls.a = Article() in setUpTestData, read back as cls.a. */
EXPECT_CALL("a store through cls read through cls", "main.py", "go", "Method", 1,
{"main.py", "class Article:\n def go(self):\n return 1\n"
"class T:\n @classmethod\n def setup(cls):\n"
" cls.a = Article()\n cls.a.go()\n"});
/* `self.go = make()` rebinds an instance's name, and the class keeps its own. */
EXPECT_CALL("a declared method through its class beside an instance store", "main.py", "go",
"Method", 1,
{"main.py", "class T:\n def __init__(self):\n self.go = make()\n"
" def go(self):\n return 1\n"
"def run(t):\n return T.go(t)\n"});
EXPECT_CALL("a store through cls over the class's None", "main.py", "go", "Method", 1,
{"main.py", "class Article:\n def go(self):\n return 1\n"
"class T:\n a = None\n @classmethod\n def setup(cls):\n"
" cls.a = Article()\n cls.a.go()\n"});
EXPECT_CALL("a store through cls beside a store of something else", "main.py", "go", "Method",
0,
{"main.py", "class Article:\n def go(self):\n return 1\n"
"class T:\n @classmethod\n def setup(cls, other):\n"
" cls.a = Article()\n cls.a = other\n"
" cls.a.go()\n"});
/* `app.conf = Mock()` says what app.conf holds from there on. */
EXPECT_CALL("a store through a receiver before the read", "main.py", "go", "Method", 0,
{"main.py", "class A:\n def go(self):\n return 1\n"
"class T:\n x = A()\n"
"def run(t: T):\n t.x = make()\n t.x.go()\n"});
EXPECT_CALL("a store of what it was declared to hold", "main.py", "go", "Method", 1,
{"main.py", "class A:\n def go(self):\n return 1\n"
"class T:\n x = A()\n"
"def run(t: T):\n t.x = A()\n t.x.go()\n"});
EXPECT_CALL("a store of something else", "main.py", "go", "Method", 0,
{"main.py", "class A:\n def go(self):\n return 1\n"
"class B:\n def go(self):\n return 2\n"
"class T:\n x = A()\n"
"def run(t: T):\n t.x = B()\n t.x.go()\n"});
{
/* The member itself is still the member: `r.content = x` then reading r.content. */
const python_proof_file_t files[] = {
{"main.py", "class R:\n @property\n def content(self):\n return 1\n"
" @content.setter\n def content(self, value):\n pass\n"
"def run(r: R):\n r.content = 2\n return r.content\n"}};
ASSERT(python_expect("a read of the member just stored", files, 1, "USAGE", "main.py",
"content", "Method", 1));
}
/* A class-body value whose class has __get__: the read runs that. */
EXPECT_CALL("a descriptor's own method", "main.py", "go", "Method", 0,
{"main.py", "class D:\n def __get__(self, i, o):\n return make()\n"
" def go(self):\n return 1\n"
"class T:\n x = D()\n"
" def run(self):\n self.x.go()\n"});
EXPECT_CALL("what a descriptor's __get__ returns", "main.py", "go", "Method", 1,
{"main.py", "class A:\n def go(self):\n return 1\n"
"class D:\n def __get__(self, i, o):\n return A()\n"
"class T:\n x = D()\n"
" def run(self):\n self.x.go()\n"});
EXPECT_CALL("a store through a receiver after the read", "main.py", "go", "Method", 1,
{"main.py", "class A:\n def go(self):\n return 1\n"
"class T:\n x = A()\n"
"def run(t: T):\n t.x.go()\n t.x = make()\n"});
EXPECT_CALL("a store through that spelling in another function", "main.py", "go", "Method", 1,
{"main.py", "class A:\n def go(self):\n return 1\n"
"class T:\n x = A()\n"
"def other(t: T):\n t.x = make()\n"
"def run(t: T):\n t.x.go()\n"});
EXPECT_CALL("a store at module level before the function", "main.py", "go", "Method", 0,
{"main.py", "class A:\n def go(self):\n return 1\n"
"class T:\n x = A()\n"
"t = T()\nt.x = make()\n"
"def run():\n t.x.go()\n"});
EXPECT_CALL("a store past the first attribute of self", "main.py", "go", "Method", 0,
{"main.py", "class A:\n def go(self):\n return 1\n"
"class H:\n y = A()\n"
"class T:\n def __init__(self):\n self.x = H()\n"
" def run(self):\n self.x.y = make()\n"
" self.x.y.go()\n"});
EXPECT_CALL("the value of a store reads what was there", "main.py", "go", "Method", 1,
{"main.py", "class A:\n def go(self):\n return 1\n"
"class T:\n x = A()\n"
"def run(t: T):\n t.x = t.x.go()\n"});
/* QuerySet.as_manager: `as_manager = classmethod(as_manager)`, the decorator spelled out. */
EXPECT_CALL("a classmethod spelled out", "main.py", "go", "Method", 1,
{"main.py", "class T:\n def go(cls):\n return 1\n"
" go = classmethod(go)\n"
"class S(T):\n pass\n"
"def run():\n return S.go()\n"});
EXPECT_CALL("a staticmethod spelled out", "main.py", "go", "Method", 1,
{"main.py", "class T:\n def go():\n return 1\n"
" go = staticmethod(go)\n"
" def run(self):\n return self.go()\n"});
EXPECT_CALL("a project's own classmethod", "main.py", "go", "Method", 0,
{"main.py", "from other import classmethod\n"
"class T:\n def go(cls):\n return 1\n"
" go = classmethod(go)\n"
"def run():\n return T.go()\n"},
{"other.py", "def classmethod(f):\n return f\n"});
EXPECT_CALL("a classmethod of something else", "main.py", "go", "Method", 0,
{"main.py", "def other(cls):\n return 1\n"
"class T:\n def go(cls):\n return 1\n"
" go = classmethod(other)\n"
"def run():\n return T.go()\n"});
/* `def f(self, Request=Request)`: what the signature says it holds. */
EXPECT_CALL("a parameter defaulting to a class", "main.py", "go", "Method", 1,
{"main.py", "class Request:\n def go(self):\n return 1\n"
"def run(Request=Request):\n return Request().go()\n"});
EXPECT_CALL("a parameter defaulting to an instance", "main.py", "go", "Method", 1,
{"main.py", "class Apps:\n def go(self):\n return 1\nglobal_apps = Apps()\n"
"def run(apps=global_apps):\n return apps.go()\n"});
EXPECT_CALL("a parameter with no default says nothing", "main.py", "go", "Method", 0,
{"main.py", "class Request:\n def go(self):\n return 1\n"
"def run(Request):\n return Request().go()\n"});
/* `questioner or Questioner()` for a parameter defaulting to None. */
EXPECT_CALL("None or a class", "main.py", "go", "Method", 1,
{"main.py", "class Q:\n def go(self):\n return 1\n"
"class A:\n def __init__(self, q=None):\n"
" self.q = q or Q()\n"
" def run(self):\n return self.q.go()\n"});
EXPECT_CALL("a required parameter or a class", "main.py", "go", "Method", 0,
{"main.py", "class Q:\n def go(self):\n return 1\n"
"class A:\n def __init__(self, q):\n"
" self.q = q or Q()\n"
" def run(self):\n return self.q.go()\n"});
/* `A() if c else None`. */
EXPECT_CALL("a value or None", "main.py", "go", "Method", 1,
{"main.py", "class A:\n def go(self):\n return 1\n"
"def run(c):\n x = A() if c else None\n return x.go()\n"});
EXPECT_CALL("two values", "main.py", "go", "Method", 0,
{"main.py", "class A:\n def go(self):\n return 1\n"
"def run(c, y):\n x = A() if c else y\n return x.go()\n"});
/* django.contrib.gis.db.models: a star of a module whose __all__ is computed. */
{
const python_proof_file_t files[] = {
{"base/__init__.py", "from base.parts import __all__ as parts_all\n"
"from base.parts import *\n"
"__all__ = parts_all\n__all__ += ['Q']\nclass Q:\n pass\n"},
{"base/parts.py", "__all__ = ['Count']\nclass Count:\n pass\n"},
{"gis/__init__.py", "from base import *\n"},
{"gis/functions.py", "class Area:\n pass\n"},
{"main.py", "from gis import functions, Q, Count\ndef run():\n functions.Area()\n"
" Q()\n Count()\n"}};
ASSERT(python_expect("a submodule past that star", files, 5, "CALLS", "gis/functions.py",
"Area", "Class", 1));
ASSERT(python_expect("a public name through that star", files, 5, "CALLS", "base/__init__.py",
"Q", "Class", 1));
ASSERT(python_expect("a name that star itself got by a star", files, 5, "CALLS",
"base/parts.py", "Count", "Class", 1));
}
{
/* A dynamic module stays open whatever its __all__ says. */
const python_proof_file_t files[] = {
{"base/__init__.py", "__all__ = ['Q']\ndef __getattr__(name):\n return 1\n"},
{"gis/__init__.py", "from base import *\n"},
{"gis/functions.py", "class Area:\n pass\n"},
{"main.py", "from gis import functions\ndef run():\n functions.Area()\n"}};
ASSERT(python_expect("a star of a module with __getattr__", files, 4, "CALLS",
"gis/functions.py", "Area", "Class", 0));
}
/* views/decorators: if/else twins of a nested def are one declaration. */
{
const python_proof_file_t files[] = {
{"main.py",
"from functools import wraps\ndef helper():\n return 1\n"
"def deco(func, c):\n if c:\n async def inner(request):\n"
" helper()\n else:\n def inner(request):\n"
" helper()\n return wraps(func)(inner)\n"}};
ASSERT(python_expect_from("calls from nested twin defs", files, 1, "main.py", "CALLS",
"main.py", "helper", "Function", 1));
}
{
const python_proof_file_t files[] = {
{"main.py",
"def helper():\n return 1\ndef deco(c):\n def inner():\n helper()\n"
" def inner():\n return 2\n return inner\n"}};
ASSERT(python_expect_from("two defs in a row are a rebinding", files, 1, "main.py", "CALLS",
"main.py", "helper", "Function", 0));
}
PASS();
}
SUITE(resolver_python) {
RUN_TEST(resolver_python_ordinary_syntax_keeps_the_file);
RUN_TEST(resolver_python_only_real_namespace_effects_are_dynamic);
RUN_TEST(resolver_python_member_spelling_is_not_a_lexical_read);
RUN_TEST(resolver_python_function_body_sees_later_module_binders);
RUN_TEST(resolver_python_conditional_definitions_are_honest);
RUN_TEST(resolver_python_import_forms_reach_the_declaration);
RUN_TEST(resolver_python_absolute_imports_follow_the_search_path);
RUN_TEST(resolver_python_submodule_import_survives_a_working_package);
RUN_TEST(resolver_python_star_import_follows_the_module_interface);
RUN_TEST(resolver_python_self_selects_through_c3_order);
python_route_release(&python_routes[0]);
python_route_release(&python_routes[1]);
}
SUITE(resolver_python_members) {
RUN_TEST(resolver_python_super_and_class_level_receivers);
RUN_TEST(resolver_python_receiver_types_need_a_fact);
RUN_TEST(resolver_python_class_body_values_can_be_replaced);
RUN_TEST(resolver_python_class_comprehension_skips_the_class);
RUN_TEST(resolver_python_shadowed_super_and_type_are_not_the_builtins);
python_route_release(&python_routes[0]);
python_route_release(&python_routes[1]);
}
SUITE(resolver_python_layout) {
RUN_TEST(resolver_python_stated_source_root_reaches_the_module);
RUN_TEST(resolver_python_namespace_package_under_a_source_root);
RUN_TEST(resolver_python_manifests_state_their_roots);
RUN_TEST(resolver_python_source_root_decoys_abstain);
RUN_TEST(resolver_python_workspace_members_see_each_other);
RUN_TEST(resolver_python_only_a_store_of_that_name_blocks_a_submodule);
RUN_TEST(resolver_python_writes_through_the_submodule_leave_its_identity);
RUN_TEST(resolver_python_module_getattr_only_answers_what_is_missing);
RUN_TEST(resolver_python_package_importing_its_own_child);
RUN_TEST(resolver_python_names_a_module_states_only_for_checkers);
RUN_TEST(resolver_python_overload_stubs_are_one_declaration);
RUN_TEST(resolver_python_loading_a_child_the_parent_imported);
RUN_TEST(resolver_python_package_init_hands_its_namespace_on);
python_route_release(&python_routes[0]);
python_route_release(&python_routes[1]);
}
SUITE(resolver_python_flow) {
RUN_TEST(resolver_python_special_methods_that_read_through_the_base);
RUN_TEST(resolver_python_returns_follow_the_receiver);
RUN_TEST(resolver_python_with_and_except_bind_what_they_say);
RUN_TEST(resolver_python_the_binder_every_path_passes_last);
RUN_TEST(resolver_python_annotated_containers_hold_their_element);
RUN_TEST(resolver_python_isinstance_narrows_where_it_holds);
python_route_release(&python_routes[0]);
python_route_release(&python_routes[1]);
}
SUITE(resolver_python_shapes) {
RUN_TEST(resolver_python_class_and_module_shapes);
RUN_TEST(resolver_python_rebound_receivers_abstain);
RUN_TEST(resolver_python_module_level_instance_has_its_class);
RUN_TEST(resolver_python_decorators_keep_identity_only_when_proven);
RUN_TEST(resolver_python_typed_receiver_preserves_crossfile_method);
RUN_TEST(resolver_python_reexport_chain_preserves_actual_declaration);
RUN_TEST(resolver_python_imported_class_is_the_static_callable);
RUN_TEST(resolver_python_reexport_shadow_and_cycle_abstain);
RUN_TEST(resolver_python_package_value_blocks_physical_submodule);
python_route_release(&python_routes[0]);
python_route_release(&python_routes[1]);
}