mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
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>
2936 lines
171 KiB
C
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]);
|
|
}
|